pax_global_header00006660000000000000000000000064147647163030014525gustar00rootroot0000000000000052 comment=fc821383086bee26dc1644cc972293df63925a24 schismtracker-20250313/000077500000000000000000000000001476471630300146305ustar00rootroot00000000000000schismtracker-20250313/.github/000077500000000000000000000000001476471630300161705ustar00rootroot00000000000000schismtracker-20250313/.github/patches/000077500000000000000000000000001476471630300176175ustar00rootroot00000000000000schismtracker-20250313/.github/patches/FLAC/000077500000000000000000000000001476471630300203245ustar00rootroot00000000000000schismtracker-20250313/.github/patches/FLAC/1-win32-revert-utime64.patch000066400000000000000000000017771476471630300252610ustar00rootroot00000000000000From 3259fd7c6f7186d24c0882484891ccd5dd54ae29 Mon Sep 17 00:00:00 2001 From: Paper Date: Mon, 23 Dec 2024 20:12:11 -0500 Subject: [PATCH] win_utf8_io: revert commit e8ffe52 this causes a build error when building under mingw, and schism doesn't use these functions anyway. --- src/share/win_utf8_io/win_utf8_io.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/share/win_utf8_io/win_utf8_io.c b/src/share/win_utf8_io/win_utf8_io.c index 3ae35b31..3112a1e6 100644 --- a/src/share/win_utf8_io/win_utf8_io.c +++ b/src/share/win_utf8_io/win_utf8_io.c @@ -355,13 +355,13 @@ int chmod_utf8(const char *filename, int pmode) int utime_utf8(const char *filename, struct utimbuf *times) { wchar_t *wname; - struct __utimbuf64 ut; + struct _utimbuf ut; int ret; if (!(wname = wchar_from_utf8(filename))) return -1; ut.actime = times->actime; ut.modtime = times->modtime; - ret = _wutime64(wname, &ut); + ret = _wutime(wname, &ut); free(wname); return ret; -- 2.39.5 schismtracker-20250313/.github/patches/SDL/000077500000000000000000000000001476471630300202415ustar00rootroot00000000000000schismtracker-20250313/.github/patches/SDL/1-tiger-fix.patch000066400000000000000000000055661476471630300233320ustar00rootroot00000000000000diff -ruN panther_sdl2-20210624-orig/src/video/cocoa/SDL_cocoakeyboard.m panther_sdl2-20210624/src/video/cocoa/SDL_cocoakeyboard.m --- panther_sdl2-20210624-orig/src/video/cocoa/SDL_cocoakeyboard.m 2021-06-25 00:00:38.000000000 -0400 +++ panther_sdl2-20210624/src/video/cocoa/SDL_cocoakeyboard.m 2024-10-31 13:26:28.563041678 -0400 @@ -426,7 +426,7 @@ static void UpdateKeymap(SDL_VideoData *data) { -#if defined(MAC_OS_X_VERSION_10_5) +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 TISInputSourceRef key_layout; const void *chr_data; int i; diff -ruN panther_sdl2-20210624-orig/src/video/cocoa/SDL_cocoamouse.h panther_sdl2-20210624/src/video/cocoa/SDL_cocoamouse.h --- panther_sdl2-20210624-orig/src/video/cocoa/SDL_cocoamouse.h 2021-06-25 00:00:38.000000000 -0400 +++ panther_sdl2-20210624/src/video/cocoa/SDL_cocoamouse.h 2024-10-31 13:25:29.762259094 -0400 @@ -25,7 +25,7 @@ #include "SDL_cocoavideo.h" -#if !defined(MAC_OS_X_VERSION_10_5) +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 typedef float CGFloat; #endif diff -ruN panther_sdl2-20210624-orig/src/video/cocoa/SDL_cocoavideo.h panther_sdl2-20210624/src/video/cocoa/SDL_cocoavideo.h --- panther_sdl2-20210624-orig/src/video/cocoa/SDL_cocoavideo.h 2021-06-25 00:00:38.000000000 -0400 +++ panther_sdl2-20210624/src/video/cocoa/SDL_cocoavideo.h 2024-10-31 13:25:27.498537161 -0400 @@ -45,7 +45,7 @@ #include "SDL_cocoaopengl.h" #include "SDL_cocoawindow.h" -#if !defined(MAC_OS_X_VERSION_10_5) +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 typedef long int NSInteger; typedef unsigned int NSUInteger; #endif diff -ruN panther_sdl2-20210624-orig/src/video/cocoa/SDL_cocoawindow.m panther_sdl2-20210624/src/video/cocoa/SDL_cocoawindow.m --- panther_sdl2-20210624-orig/src/video/cocoa/SDL_cocoawindow.m 2021-06-25 00:00:38.000000000 -0400 +++ panther_sdl2-20210624/src/video/cocoa/SDL_cocoawindow.m 2024-10-31 13:29:26.959250907 -0400 @@ -102,7 +102,7 @@ NSOpenGLContext *currentContext = [NSOpenGLContext currentContext]; NSMutableArray *contexts = data->nscontexts; @synchronized (contexts) { -#if defined(MAC_OS_X_VERSION_10_5) +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 for (SDLOpenGLContext *context in contexts) { #else /* old way to iterate */ @@ -363,7 +363,7 @@ !!! FIXME: http://bugzilla.libsdl.org/show_bug.cgi?id=1825 */ windows = [NSApp orderedWindows]; -#if defined(MAC_OS_X_VERSION_10_5) +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 for (NSWindow *win in windows) { #else /* old way to iterate */ @@ -1521,7 +1521,7 @@ } NSArray *contexts = [[data->nscontexts copy] autorelease]; -#if defined(MAC_OS_X_VERSION_10_5) +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 for (SDLOpenGLContext *context in contexts) { #else /* old way to iterate */ schismtracker-20250313/.github/patches/SDL/2-configure-retro68.patch000066400000000000000000000042141476471630300247120ustar00rootroot00000000000000--- SDL-1.2.13-old/configure.in 2007-12-30 20:48:40.000000000 -0500 +++ SDL-1.2.13/configure.in 2025-01-09 20:46:51.562601898 -0500 @@ -2507,6 +2507,58 @@ SOURCES="$srcdir/src/main/beos/*.cc $SOURCES" EXTRA_LDFLAGS="$EXTRA_LDFLAGS -lroot -lbe -lmedia -lgame -ldevice -ltextencoding" ;; + *-*-macos*) + ARCH=macos + + CheckVisibilityHidden + CheckDummyVideo + CheckDiskAudio + CheckDummyAudio + + if test "x$enable_loadso" = "xyes"; then + AC_DEFINE(SDL_LOADSO_MACOS) + SOURCES="$SOURCES $srcdir/src/loadso/macos/*.c" + have_loadso=yes + fi + + if test "x$enable_video" = "xyes"; then + AC_DEFINE(SDL_VIDEO_DRIVER_TOOLBOX) + AC_DEFINE(SDL_VIDEO_DRIVER_DRAWSPROCKET) + SOURCES="$SOURCES $srcdir/src/video/maccommon/*.c" + SOURCES="$SOURCES $srcdir/src/video/macrom/*.c" + SOURCES="$SOURCES $srcdir/src/video/macdsp/*.c" + have_video=yes + fi + + if test "x$enable_timers" = "xyes"; then + AC_DEFINE(SDL_TIMER_MACOS) + SOURCES="$SOURCES $srcdir/src/timer/macos/SDL_MPWtimer.c" + have_timers=yes + fi + + if test "x$enable_joystick" = "xyes"; then + AC_DEFINE(SDL_JOYSTICK_MACOS) + SOURCES="$SOURCES $srcdir/src/joystick/macos/SDL_sysjoystick.c" + have_joystick=yes + fi + + if test "x$enable_cdrom" = "xyes"; then + AC_DEFINE(SDL_CDROM_MACOS) + SOURCES="$SOURCES $srcdir/src/cdrom/macos/SDL_syscdrom.c" + have_cdrom=yes + fi + + if test "x$enable_audio" = "xyes"; then + AC_DEFINE(SDL_AUDIO_DRIVER_SNDMGR) + SOURCES="$SOURCES $srcdir/src/audio/macrom/*.c" + have_audio=yes + fi + + # The Mac OS platform requires special setup. + SDLMAIN_SOURCES="$srcdir/src/main/macos/*.c" + SDL_LIBS="-lSDLmain $SDL_LIBS" + + ;; *-*-darwin* ) # This could be either full "Mac OS X", or plain "Darwin" which is # just the OS X kernel sans upper layers like Carbon and Cocoa. schismtracker-20250313/.github/patches/SDL/3-fixup-main.patch000066400000000000000000000020201476471630300234710ustar00rootroot00000000000000--- SDL-1.2.15-orig/src/main/macos/SDL_main.c 2012-01-19 01:30:06.000000000 -0500 +++ SDL-1.2.15/src/main/macos/SDL_main.c 2024-12-02 23:40:48.709371758 -0500 @@ -1,6 +1,6 @@ /* SDL - Simple DirectMedia Layer - Copyright (C) 1997-2012 Sam Lantinga + Copyright (C) 1997-2006 Sam Lantinga This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -352,8 +352,8 @@ #pragma unused(argc, argv) #endif -#define DEFAULT_ARGS "\p" /* pascal string for default args */ -#define DEFAULT_VIDEO_DRIVER "\ptoolbox" /* pascal string for default video driver name */ +#define DEFAULT_ARGS {'\0'} /* pascal string for default args */ +#define DEFAULT_VIDEO_DRIVER {'\7', 't', 'o', 'o', 'l', 'b', 'o', 'x'} /* pascal string for default video driver name */ #define DEFAULT_OUTPUT_TO_FILE 1 /* 1 == output to file, 0 == no output */ #define VIDEO_ID_DRAWSPROCKET 1 /* these correspond to popup menu choices */ schismtracker-20250313/.github/patches/utf8proc/000077500000000000000000000000001476471630300213715ustar00rootroot00000000000000schismtracker-20250313/.github/patches/utf8proc/1-disable-pic.patch000066400000000000000000000005121476471630300247220ustar00rootroot00000000000000--- a/CMakeLists.txt 2024-10-06 21:59:24.343387636 -0400 +++ b/CMakeLists.txt 2024-10-06 13:24:51.887538244 -0400 @@ -48,7 +48,7 @@ endif () set_target_properties (utf8proc PROPERTIES - POSITION_INDEPENDENT_CODE ON + POSITION_INDEPENDENT_CODE OFF VERSION "${SO_MAJOR}.${SO_MINOR}.${SO_PATCH}" SOVERSION ${SO_MAJOR} ) schismtracker-20250313/.github/patches/utf8proc/2-fix-prefix.patch000066400000000000000000000016721476471630300246400ustar00rootroot00000000000000From d526df02f56c767ec6b0248fd5593573c36ba4da Mon Sep 17 00:00:00 2001 From: Paper Date: Sun, 20 Oct 2024 19:50:25 -0400 Subject: [PATCH] makefile: fix target directory --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 806f441..e3924d7 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ PERL=perl CFLAGS ?= -O2 PICFLAG = -fPIC C99FLAG = -std=c99 -WCFLAGS = -Wsign-conversion -Wall -Wextra -pedantic +WCFLAGS = -Wall -Wextra -pedantic UCFLAGS = $(CPPFLAGS) $(CFLAGS) $(PICFLAG) $(C99FLAG) $(WCFLAGS) -DUTF8PROC_EXPORTS $(UTF8PROC_DEFINES) LDFLAG_SHARED = -shared SOFLAG = -Wl,-soname @@ -39,7 +39,7 @@ else # GNU/Linux, at least (Windows should probably use cmake) endif # installation directories (for 'make install') -prefix=/usr/local +prefix=$(HOME)/ppcprefix libdir=$(prefix)/lib includedir=$(prefix)/include pkgconfigdir=$(libdir)/pkgconfig -- 2.39.2 schismtracker-20250313/.github/patches/utf8proc/3-windows.patch000066400000000000000000000045461476471630300242550ustar00rootroot00000000000000From 65df633c11ece45ba1e38b6601fff44f571b2618 Mon Sep 17 00:00:00 2001 From: Paper Date: Sun, 1 Dec 2024 10:26:47 -0500 Subject: [PATCH] make: add windows support --- Makefile | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 806f441..4464fd3 100644 --- a/Makefile +++ b/Makefile @@ -32,10 +32,13 @@ VERSION=2.9.0 OS := $(shell uname) ifeq ($(OS),Darwin) # MacOS X SHLIB_EXT = dylib - SHLIB_VERS_EXT = $(MAJOR).dylib + SHLIB_VERS_EXT = .$(MAJOR).dylib +else ifneq ($(or ifeq ($(OS),Windows_NT), ifeq($(OS),MINGW32_NT-6.2)), "") + SHLIB_EXT = dll + SHLIB_VERS_EXT = -$(MAJOR).dll else # GNU/Linux, at least (Windows should probably use cmake) SHLIB_EXT = so - SHLIB_VERS_EXT = so.$(MAJOR).$(MINOR).$(PATCH) + SHLIB_VERS_EXT = .so.$(MAJOR).$(MINOR).$(PATCH) endif # installation directories (for 'make install') @@ -97,6 +100,12 @@ libutf8proc.$(MAJOR).dylib: utf8proc.o libutf8proc.dylib: libutf8proc.$(MAJOR).dylib ln -f -s libutf8proc.$(MAJOR).dylib $@ +libutf8proc-$(MAJOR).dll: utf8proc.o + $(CC) $(LDFLAGS) $(LDFLAG_SHARED) -o $@ $^ + +libutf8proc.dll: libutf8proc-$(MAJOR).dll + cp libutf8proc-$(MAJOR).dll $@ + libutf8proc.pc: libutf8proc.pc.in sed \ -e 's#PREFIX#$(prefix)#' \ @@ -105,18 +114,15 @@ libutf8proc.pc: libutf8proc.pc.in -e 's#VERSION#$(MAJOR).$(MINOR).$(PATCH)#' \ libutf8proc.pc.in > libutf8proc.pc -install: libutf8proc.a libutf8proc.$(SHLIB_EXT) libutf8proc.$(SHLIB_VERS_EXT) libutf8proc.pc +install: libutf8proc.a libutf8proc.$(SHLIB_EXT) libutf8proc$(SHLIB_VERS_EXT) libutf8proc.pc mkdir -m 755 -p $(DESTDIR)$(includedir) $(INSTALL) -m 644 utf8proc.h $(DESTDIR)$(includedir) mkdir -m 755 -p $(DESTDIR)$(libdir) $(INSTALL) -m 644 libutf8proc.a $(DESTDIR)$(libdir) - $(INSTALL) -m 755 libutf8proc.$(SHLIB_VERS_EXT) $(DESTDIR)$(libdir) + $(INSTALL) -m 755 libutf8proc$(SHLIB_VERS_EXT) $(DESTDIR)$(libdir) mkdir -m 755 -p $(DESTDIR)$(pkgconfigdir) $(INSTALL) -m 644 libutf8proc.pc $(DESTDIR)$(pkgconfigdir)/libutf8proc.pc - ln -f -s libutf8proc.$(SHLIB_VERS_EXT) $(DESTDIR)$(libdir)/libutf8proc.$(SHLIB_EXT) -ifneq ($(OS),Darwin) - ln -f -s libutf8proc.$(SHLIB_VERS_EXT) $(DESTDIR)$(libdir)/libutf8proc.so.$(MAJOR) -endif + ln -f -s libutf8proc$(SHLIB_VERS_EXT) $(DESTDIR)$(libdir)/libutf8proc.$(SHLIB_EXT) MANIFEST.new: rm -rf tmp -- 2.47.0 schismtracker-20250313/.github/scripts/000077500000000000000000000000001476471630300176575ustar00rootroot00000000000000schismtracker-20250313/.github/scripts/install.sh000066400000000000000000000042451476471630300216660ustar00rootroot00000000000000#!/bin/sh # script to run schism with included libs on linux # shouldn't™ require bash or any other crap like that. # By default, we install to /opt. This is because /usr/local/lib is not # included in the actual ld paths on many distributions. However, we make # due by installing a "fake" stub script that sets up LD_LIBRARY_PATH # with the proper variables into /usr/local/bin; this behavior can be # disabled, if desired. PREFIX="/opt/schism" INSTALL_USR_LOCAL_FILES=yes if test "x$1" = "x--help"; then printf "usage: $0 [--help] [--no-usr-copy] [--prefix (default: /opt/schism)>]" exit fi # --no-usr-copy installs a stub script to /usr/local/bin # that sets up an environment to run the real binary, along # with copying all of the freedesktop.org stuff into # their respective /usr/local directories if test "x$1" = "x--no-usr-copy"; then INSTALL_USR_LOCAL_FILES=no shift fi if test "x$1" = "x--prefix"; then PREFIX="$2" shift shift fi SCRIPTPATH="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)" # mkdir -p is POSIX. mkdir -p "$PREFIX/bin" "$PREFIX/lib" "$PREFIX/share/applications" "$PREFIX/share/man/man1" "$PREFIX/share/pixmaps" # basic binary stuff cp "$SCRIPTPATH/schismtracker" "$PREFIX/bin/schismtracker" for i in FLAC.so.8 utf8proc.so.2 ogg.so.0; do cp "$SCRIPTPATH/lib$i" "$PREFIX/lib/lib$i" done # now onto the metadata and fd.org stuff cp "$SCRIPTPATH/schism.desktop" "$PREFIX/share/applications/schism.desktop" cp "$SCRIPTPATH/schismtracker.1" "$PREFIX/share/man/man1/schismtracker.1" cp "$SCRIPTPATH/schism-icon-128.png" "$PREFIX/share/pixmaps/schism-icon-128.png" if test "x$INSTALL_USR_LOCAL_FILES" = "xyes" && ! test "x$PREFIX" = "x/usr/local"; then mkdir -p "/usr/local/bin" "/usr/local/share/applications" "/usr/local/share/pixmaps" "/usr/local/share/man/man1" cat< "/usr/local/bin/schismtracker" #!/bin/sh env LD_LIBRARY_PATH="$PREFIX/lib" "$PREFIX/bin/schismtracker" "\$@" EOF # don't forget! chmod +x "/usr/local/bin/schismtracker" # ok, now we can shove some stuff into /usr/local. for i in share/applications/schism.desktop share/pixmaps/schism-icon-128.png share/man/man1/schismtracker.1; do cp "$PREFIX/$i" "/usr/local/$i" done fi schismtracker-20250313/.github/scripts/run.sh000066400000000000000000000003731476471630300210220ustar00rootroot00000000000000#!/bin/sh # script to run schism with included libs on linux # shouldn't™ require bash or any other crap like that. SCRIPTPATH="$(cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)" env LD_LIBRARY_PATH="$SCRIPTPATH" "$SCRIPTPATH/schismtracker" "$@" schismtracker-20250313/.github/workflows/000077500000000000000000000000001476471630300202255ustar00rootroot00000000000000schismtracker-20250313/.github/workflows/macos.yml000066400000000000000000000173751476471630300220670ustar00rootroot00000000000000name: Classic Mac OS (powerpc) on: push: pull_request: workflow_dispatch: # This file uses the Retro68 toolchain to build # a binary of Schism Tracker for the Classic Mac # OS. I only care enough to build for powerpc, # since 68k machines are way too slow for it to # be even remotely usable. Regardless, I'm still # putting powerpc files in their own folder in # case someone really wants to build a 68k version # of schism. # # We don't do any caching here simply because I'm # too lazy to do it :) it can be implemented later # if its really necessary # # There are quite a few hacks in here; for example # xz isn't included in the docker image, so we have # to make it ourselves, which is annoying. However, # liblzma is. Unfortunately this means symbols are # screwed up and we need to preload a newer liblzma. jobs: macos: runs-on: ubuntu-latest container: image: ghcr.io/autc04/retro68 env: SDL_VERSION: 1.2.15 SDL_SHA256: d6d316a793e5e348155f0dd93b979798933fb98aa1edebcc108829d6474aad00 XZ_VERSION: 5.6.3 XZ_SHA256: b1d45295d3f71f25a4c9101bd7c8d16cb56348bbef3bbc738da0351e17c73317 FLAC_VERSION: 1.5.0 FLAC_SHA256: f2c1c76592a82ffff8413ba3c4a1299b6c7ab06c734dee03fd88630485c2b920 LIBOGG_VERSION: 1.3.5 LIBOGG_SHA256: c4d91be36fc8e54deae7575241e03f4211eb102afb3fc0775fbbc1b740016705 UTF8PROC_VERSION: 2.9.0 UTF8PROC_SHA256: bd215d04313b5bc42c1abedbcb0a6574667e31acee1085543a232204e36384c4 steps: - name: 'Prepare Universal Interfaces' run: | curl -s "https://staticky.com/mirrors/ftp.apple.com/developer/Tool_Chest/Core_Mac_OS_Tools/MPW_etc./MPW-GM_Images/MPW-GM.img.bin" -o "/tmp/MPW-GM.img.bin" # extract /Retro68-build/bin/install-universal-interfaces.sh "/tmp" "MPW-GM.img.bin" echo "Linking Universal interfaces..." /Retro68-build/bin/interfaces-and-libraries.sh /Retro68-build/toolchain /tmp/InterfacesAndLibraries echo "done" - name: 'Checkout' uses: actions/checkout@v4 with: path: schism - name: 'Downloads XZ utils sources' run: | curl -L -o "xz-$XZ_VERSION.tar.gz" "https://github.com/tukaani-project/xz/releases/download/v$XZ_VERSION/xz-$XZ_VERSION.tar.gz" echo "$XZ_SHA256 xz-$XZ_VERSION.tar.gz" | shasum -a 256 -c - tar xvf "xz-$XZ_VERSION.tar.gz" - name: 'Build XZ utils' run: | cd "xz-$XZ_VERSION" mkdir build cd build ../configure make make install cd .. # build cd .. # xz-$XZ_VERSION - name: 'Download SDL 1.2 sources (powerpc)' run: | curl -L -o "SDL-$SDL_VERSION.tar.gz" "https://www.libsdl.org/release/SDL-$SDL_VERSION.tar.gz" echo "$SDL_SHA256 SDL-$SDL_VERSION.tar.gz" | shasum -a 256 -c - tar xvf "SDL-$SDL_VERSION.tar.gz" cd "SDL-$SDL_VERSION" patch -p1 < ../schism/.github/patches/SDL/2-configure-retro68.patch patch -p1 < ../schism/.github/patches/SDL/3-fixup-main.patch ./autogen.sh cd .. - name: 'Download and prepare libogg sources' run: | curl -L -o libogg-$LIBOGG_VERSION.tar.xz "https://ftp.osuosl.org/pub/xiph/releases/ogg/libogg-$LIBOGG_VERSION.tar.xz" || curl -L -o libogg-$LIBOGG_VERSION.tar.xz "https://github.com/xiph/ogg/releases/download/v$LIBOGG_VERSION/libogg-$LIBOGG_VERSION.tar.xz" echo "$LIBOGG_SHA256 libogg-$LIBOGG_VERSION.tar.xz" | shasum -a 256 -c - LD_PRELOAD=/usr/local/lib/liblzma.so.5 tar xvf "libogg-$LIBOGG_VERSION.tar.xz" - name: 'Download and prepare FLAC sources' run: | curl -L -o flac-$FLAC_VERSION.tar.xz "https://ftp.osuosl.org/pub/xiph/releases/flac/flac-$FLAC_VERSION.tar.xz" || curl -L -o flac-$FLAC_VERSION.tar.xz "https://github.com/xiph/flac/releases/download/$FLAC_VERSION/flac-$FLAC_VERSION.tar.xz" echo "$FLAC_SHA256 flac-$FLAC_VERSION.tar.xz" | shasum -a 256 -c - LD_PRELOAD=/usr/local/lib/liblzma.so.5 tar xvf "flac-$FLAC_VERSION.tar.xz" - name: 'Download utf8proc' run: | curl -L -o "utf8proc-$UTF8PROC_VERSION.tar.gz" "https://github.com/JuliaStrings/utf8proc/releases/download/v$UTF8PROC_VERSION/utf8proc-$UTF8PROC_VERSION.tar.gz" echo "$UTF8PROC_SHA256 utf8proc-$UTF8PROC_VERSION.tar.gz" | shasum -a 256 -c - tar xvf "utf8proc-$UTF8PROC_VERSION.tar.gz" - name: 'Build Schism and dependencies (powerpc)' run: | cd SDL-1.2.15 mkdir build_powerpc cd build_powerpc ../configure --host=powerpc-apple-macos make make install cd .. # build_powerpc cd .. # SDL-1.2.15 - name: 'Build libogg (powerpc)' run: | cd libogg-$LIBOGG_VERSION mkdir build_powerpc cd build_powerpc CPPFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" ../configure --host=powerpc-apple-macos make make install cd .. # build_powerpc cd .. # libogg-$LIBOGG_VERSION - name: 'Build FLAC (powerpc)' run: | cd flac-$FLAC_VERSION mkdir build_powerpc cd build_powerpc # hax: we need to patch out all calls to chmod, utimensat, and chown, because none of them # exist under retro68, and additionally we don't use anything that's likely to need them CPPFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" ../configure --host=powerpc-apple-macos --disable-programs --disable-examples --disable-cpplibs make make install cd .. # build_powerpc cd .. # flac-$FLAC_VERSION - name: 'Build utf8proc (powerpc)' run: | cd utf8proc-$UTF8PROC_VERSION mkdir build_powerpc cd build_powerpc CMAKE_TOOLCHAIN_FILE=/Retro68-build/toolchain/powerpc-apple-macos/cmake/retroppc.toolchain.cmake cmake .. make make install cd .. # build_powerpc cd .. # utf8proc-$UTF8PROC_VERSION - name: 'Get date of latest commit' id: date run: | cd schism echo "date=$(git log -n 1 --date=short --format=format:%cd | sed 's/\(....\)-\(..\)-\(..\).*/\1\2\3/')" >> $GITHUB_OUTPUT cd .. - name: 'Build Schism (powerpc)' run: | cd schism mkdir acinclude curl -s "https://raw.githubusercontent.com/pkgconf/pkgconf/refs/tags/pkgconf-2.3.0/pkg.m4" -o "acinclude/pkg.m4" autoreconf -Iacinclude -i mkdir build_powerpc cd build_powerpc CPPFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" UTF8PROC_CFLAGS="-I/Retro68-build/toolchain/powerpc-apple-macos/include" UTF8PROC_LIBS="-lutf8proc" FLAC_CFLAGS="-I/usr/local/include" FLAC_LIBS="-L/usr/local/lib -lFLAC -logg -lm" SDL12_CFLAGS="-I/usr/local/include/SDL" SDL12_LIBS="-L/usr/local/lib -lSDL" ../configure --with-sdl12 --enable-sdl12-linking --with-flac --enable-flac-linking --without-jack --without-sdl2 --host=powerpc-apple-macos make schismtracker.bin RETRO68_ROOT="/Retro68-build/toolchain" cd .. # build_powerpc cd .. # schism - name: 'Package Schism' run: | cp schism/build_powerpc/schismtracker.bin . cp schism/docs/configuration.md . cp schism/README.md schism/COPYING . curl -s "https://raw.githubusercontent.com/xiph/flac/master/COPYING.Xiph" -o "COPYING.Xiph" zip -r schismtracker.zip configuration.md COPYING COPYING.Xiph README.md schismtracker.bin - name: 'Upload artifact' uses: actions/upload-artifact@v4 with: name: schismtracker-${{ steps.date.outputs.date }}-classic-macos path: schismtracker.zip schismtracker-20250313/.github/workflows/mingw.yml000066400000000000000000000414701476471630300220770ustar00rootroot00000000000000name: Windows (i386/x86_64/armv7/aarch64) on: push: pull_request: workflow_dispatch: jobs: build-i386: runs-on: windows-latest name: mingw32-i386 env: SDL_VERSION: 2.32.0 SDL_SHA256: b31fc1ec1a22e6449ec2f5496d6f8d2832a4b8c27da94ae7db211418be2869a4 SDL12_VERSION: 1.2.15 SDL12_SHA256: a28bbe38714ef7817b1c1e8082a48f391f15e4043402444b783952fca939edc1 FLAC_VERSION: 1.5.0 FLAC_SHA256: f2c1c76592a82ffff8413ba3c4a1299b6c7ab06c734dee03fd88630485c2b920 LIBOGG_VERSION: 1.3.5 LIBOGG_SHA256: c4d91be36fc8e54deae7575241e03f4211eb102afb3fc0775fbbc1b740016705 UTF8PROC_VERSION: 2.9.0 UTF8PROC_SHA256: bd215d04313b5bc42c1abedbcb0a6574667e31acee1085543a232204e36384c4 defaults: run: shell: msys2 {0} steps: - name: 'Checkout' uses: actions/checkout@v4 with: path: schism # setup msys2 to make use of its nice dev tools... # we also need the headers it provides for # Media Foundation and they provide a prebuilt # SDL2 binary as well - name: 'Setup MSYS2' uses: msys2/setup-msys2@v2 with: msystem: mingw32 update: true install: git mingw-w64-i686-toolchain mingw-w64-i686-flac mingw-w64-i686-SDL mingw-w64-i686-SDL2 libtool autoconf automake make zip unzip dos2unix patch location: 'C:\\msys2' - name: 'Retrieve runner temp directory' id: temp run: | cd schism echo "temp=$(cygpath -u "$RUNNER_TEMP")" >> $GITHUB_OUTPUT echo "wpath=$RUNNER_TEMP" >> $GITHUB_OUTPUT cd .. # FIXME store this in RUNNER_TEMP instead - name: 'Cache MinGW' id: cache-i386-mingw uses: actions/cache@v4 with: path: 'C:\\MinGW' key: mingw32-i386-install - name: 'Cache dependencies' id: cache-i386-dependencies uses: actions/cache@v4 with: path: '${{ steps.temp.outputs.wpath }}\\i386prefix' key: mingw32-i386-dependencies-SDL12_${{ env.SDL12_VERSION }}-FLAC_${{ env.FLAC_VERSION }}-LIBOGG_${{ env.LIBOGG_VERSION }}-UTF8PROC_${{ env.UTF8PROC_VERSION }} # grab a (quite old) version of mingw32 so that we can # target extremely old Windows versions that mingw-w64 # doesn't care about (and doesn't work on) - name: 'Setup MinGW' if: steps.cache-i386-mingw.outputs.cache-hit != 'true' run: | wget --no-check-certificate -O mingw-get.zip "https://sourceforge.net/projects/mingw/files/Installer/mingw-get/mingw-get-0.6.2-beta-20131004-1/mingw-get-0.6.2-mingw32-beta-20131004-1-bin.zip/download" mkdir "/c/MinGW" unzip mingw-get.zip -d"/c/MinGW" rm mingw-get.zip "/c/MinGW/bin/mingw-get.exe" install mingw32-base-bin msys-base-bin - name: 'Add MinGW binaries to PATH' run: | echo "$(cygpath -w "/c/MinGW/bin")" >> $GITHUB_PATH echo "$(cygpath -w "/c/MinGW/msys/1.0/bin")" >> $GITHUB_PATH - name: 'Download libflac and libogg sources' if: steps.cache-i386-dependencies.outputs.cache-hit != 'true' run: | wget -O libogg-$LIBOGG_VERSION.tar.xz "https://ftp.osuosl.org/pub/xiph/releases/ogg/libogg-$LIBOGG_VERSION.tar.xz" || wget -O libogg-$LIBOGG_VERSION.tar.xz "https://github.com/xiph/ogg/releases/download/v$LIBOGG_VERSION/libogg-$LIBOGG_VERSION.tar.xz" echo "$LIBOGG_SHA256 libogg-$LIBOGG_VERSION.tar.xz" | sha256sum -c - tar xvf "libogg-$LIBOGG_VERSION.tar.xz" cd libogg-$LIBOGG_VERSION autoreconf -i cd .. wget -O flac-$FLAC_VERSION.tar.xz "https://ftp.osuosl.org/pub/xiph/releases/flac/flac-$FLAC_VERSION.tar.xz" || wget -O flac-$FLAC_VERSION.tar.xz "https://github.com/xiph/flac/releases/download/$FLAC_VERSION/flac-$FLAC_VERSION.tar.xz" echo "$FLAC_SHA256 flac-$FLAC_VERSION.tar.xz" | sha256sum -c - tar xvf "flac-$FLAC_VERSION.tar.xz" cd "flac-$FLAC_VERSION" patch -p1 < ../schism/.github/patches/FLAC/1-win32-revert-utime64.patch autoreconf -i cd .. - name: 'Build libflac' if: steps.cache-i386-dependencies.outputs.cache-hit != 'true' env: RUNNER_TEMP_CYGPATH: ${{ steps.temp.outputs.temp }} shell: "bash.exe {0}" run: | cd libogg-$LIBOGG_VERSION mkdir build cd build CC="mingw32-gcc.exe" CPPFLAGS="-I$RUNNER_TEMP_CYGPATH/i386prefix/include" LDFLAGS="-L$RUNNER_TEMP_CYGPATH/i386prefix/lib" ../configure --prefix="$RUNNER_TEMP_CYGPATH/i386prefix" make make install cd ../../flac-$FLAC_VERSION mkdir build cd build CC="mingw32-gcc.exe" CPPFLAGS="-I$RUNNER_TEMP_CYGPATH/i386prefix/include" LDFLAGS="-L$RUNNER_TEMP_CYGPATH/i386prefix/lib" ../configure --prefix="$RUNNER_TEMP_CYGPATH/i386prefix" --with-ogg-includes="$RUNNER_TEMP_CYGPATH/i386prefix/include" --with-ogg-libraries="$RUNNER_TEMP_CYGPATH/i386prefix/lib" --disable-programs --disable-examples --disable-cpplibs make make install cd ../.. - name: 'Download utf8proc' if: steps.cache-i386-dependencies.outputs.cache-hit != 'true' run: | wget -O "utf8proc-$UTF8PROC_VERSION.tar.gz" "https://github.com/JuliaStrings/utf8proc/releases/download/v$UTF8PROC_VERSION/utf8proc-$UTF8PROC_VERSION.tar.gz" echo "$UTF8PROC_SHA256 utf8proc-$UTF8PROC_VERSION.tar.gz" | sha256sum -c - tar xvf "utf8proc-$UTF8PROC_VERSION.tar.gz" cd "utf8proc-$UTF8PROC_VERSION" patch -p1 < "../schism/.github/patches/utf8proc/3-windows.patch" cd .. - name: 'Build utf8proc' if: steps.cache-i386-dependencies.outputs.cache-hit != 'true' env: RUNNER_TEMP_CYGPATH: ${{ steps.temp.outputs.temp }} shell: "bash.exe {0}" run: | cd utf8proc-$UTF8PROC_VERSION make CC="mingw32-gcc.exe" cp "utf8proc.h" "$RUNNER_TEMP_CYGPATH/i386prefix/include" cp "libutf8proc-3.dll" "$RUNNER_TEMP_CYGPATH/i386prefix/lib/libutf8proc-3.dll" cd .. - name: 'Get date of latest commit' id: date run: | cd schism echo "date=$(git log -n 1 --date=short --format=format:%cd | sed 's/\(....\)-\(..\)-\(..\).*/\1\2\3/')" >> $GITHUB_OUTPUT cd .. - name: 'autoreconf' run: | cd schism autoreconf -i cd .. - name: 'Compile package' env: RUNNER_TEMP_CYGPATH: ${{ steps.temp.outputs.temp }} shell: "bash.exe {0}" run: | ls /c/msys2/msys64/mingw32 cd schism mkdir build cd build # We link to SDL 1.2 here since it's universally supported by everything and is # useful as a fallback of sorts. MEDIAFOUNDATION_CFLAGS="-I/c/msys2/msys64/mingw32/include" DSOUND_CFLAGS="-I/c/msys2/msys64/mingw32/include" CPPFLAGS="-I$RUNNER_TEMP_CYGPATH/i386prefix/include" LDFLAGS="-L$RUNNER_TEMP_CYGPATH/i386prefix/lib -L/c/MinGW/lib" CC="mingw32-gcc.exe" UTF8PROC_CFLAGS="-I$RUNNER_TEMP_CYGPATH/i386prefix/include" UTF8PROC_LIBS="-L$RUNNER_TEMP_CYGPATH/i386prefix/lib -lutf8proc-3" SDL12_CFLAGS="-I/c/msys2/msys64/mingw32/include/SDL" SDL12_LIBS="-L/c/msys2/msys64/mingw32/lib -lSDL" FLAC_CFLAGS="-I$RUNNER_TEMP_CYGPATH/i386prefix/include" FLAC_LIBS="-L$RUNNER_TEMP_CYGPATH/i386prefix/lib -lFLAC -logg -lm" SDL2_CFLAGS="-I/c/msys2/msys64/mingw32/include/SDL2" SDL2_LIBS="-L/c/msys2/msys64/mingw32/lib -lSDL2" ../configure --with-flac --with-sdl2 --with-sdl12 --enable-sdl12-linking make strip schismtracker.exe cp schismtracker.exe ../.. cd ../.. - name: 'Create package' env: RUNNER_TEMP_CYGPATH: ${{ steps.temp.outputs.temp }} run: | # FIXME these DLLs may not be needed anymore cp "/c/MinGW/bin/libgcc_s_dw2-1.dll" . cp "/c/MinGW/bin/libssp-0.dll" . # SDL 1.2 wget -O "SDL-$SDL12_VERSION-win32.zip" https://www.libsdl.org/release/SDL-$SDL12_VERSION-win32.zip echo "$SDL12_SHA256 SDL-$SDL12_VERSION-win32.zip" | sha256sum -c - unzip "SDL-$SDL12_VERSION-win32.zip" "SDL.dll" # SDL 2 wget -O "SDL2-$SDL_VERSION-win32-x86.zip" https://www.libsdl.org/release/SDL2-$SDL_VERSION-win32-x86.zip echo "$SDL_SHA256 SDL2-$SDL_VERSION-win32-x86.zip" | sha256sum -c - unzip "SDL2-$SDL_VERSION-win32-x86.zip" "SDL2.dll" cp "$RUNNER_TEMP_CYGPATH/i386prefix/bin/libFLAC-14.dll" . cp "$RUNNER_TEMP_CYGPATH/i386prefix/bin/libogg-0.dll" . cp "$RUNNER_TEMP_CYGPATH/i386prefix/lib/libutf8proc-3.dll" . strip "libFLAC-14.dll" strip "libogg-0.dll" strip "libutf8proc-3.dll" strip "libssp-0.dll" strip "libgcc_s_dw2-1.dll" cp schism/docs/configuration.md schism/README.md schism/COPYING . wget https://raw.githubusercontent.com/xiph/flac/master/COPYING.Xiph unix2dos COPYING.Xiph COPYING README.md configuration.md - name: 'Upload artifact' uses: actions/upload-artifact@v4 with: name: schismtracker-${{ steps.date.outputs.date }}-mingw32-i386 path: | schismtracker.exe SDL.dll SDL2.dll libFLAC-14.dll libogg-0.dll libutf8proc-3.dll libgcc_s_dw2-1.dll libssp-0.dll COPYING COPYING.Xiph README.md configuration.md build-others: runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - { sys: mingw32, target: x86_64, stdlib: msvcrt, cflags: '-O2', ldflags: '' } - { sys: mingw32, target: armv7, stdlib: msvcrt, cflags: '-O2', ldflags: '' } - { sys: mingw32, target: aarch64, stdlib: ucrt, cflags: '-O2', ldflags: '' } name: ${{ matrix.sys }}-${{ matrix.target }} env: LLVM_MINGW_VERSION: 20250114 SDL_VERSION: 2.32.0 SDL_SHA256: f5c2b52498785858f3de1e2996eba3c1b805d08fe168a47ea527c7fc339072d0 SDL12_VERSION: 1.2.15 SDL12_SHA256: d6d316a793e5e348155f0dd93b979798933fb98aa1edebcc108829d6474aad00 FLAC_VERSION: 1.5.0 FLAC_SHA256: f2c1c76592a82ffff8413ba3c4a1299b6c7ab06c734dee03fd88630485c2b920 LIBOGG_VERSION: 1.3.5 LIBOGG_SHA256: c4d91be36fc8e54deae7575241e03f4211eb102afb3fc0775fbbc1b740016705 UTF8PROC_VERSION: 2.9.0 UTF8PROC_SHA256: bd215d04313b5bc42c1abedbcb0a6574667e31acee1085543a232204e36384c4 TARGET: ${{ matrix.target }} SYS: ${{ matrix.sys }} STDLIB: ${{ matrix.stdlib }} CFLAGS: ${{ matrix.cflags }} LDFLAGS: ${{ matrix.ldflags }} steps: - name: 'Checkout' uses: actions/checkout@v4 with: path: schism - name: 'Install unix2dos' run: | sudo apt-get install -y dos2unix - name: 'Cache llvm-mingw' id: cache-llvm-mingw uses: actions/cache@v4 with: path: '/home/runner/llvm-mingw' key: windows-${{ matrix.stdlib }}-llvm-mingw-${{ env.LLVM_MINGW_VERSION }} - name: 'Cache dependencies' id: cache-dependencies uses: actions/cache@v4 with: path: '/home/runner/prefix' key: windows-${{ matrix.sys }}-${{ matrix.target }}-${{ matrix.stdlib }}-dependencies-SDL_${{ env.SDL_VERSION }}-SDL12_${{ env.SDL12_VERSION }}-FLAC_${{ env.FLAC_VERSION }}-LIBOGG_${{ env.LIBOGG_VERSION }}-UTF8PROC_${{ env.UTF8PROC_VERSION }} - name: 'Grab llvm-mingw' if: steps.cache-llvm-mingw.outputs.cache-hit != 'true' run: | curl -L "https://github.com/mstorsjo/llvm-mingw/releases/download/$LLVM_MINGW_VERSION/llvm-mingw-$LLVM_MINGW_VERSION-$STDLIB-ubuntu-20.04-x86_64.tar.xz" | tar xJvf - -C "$HOME" mv "$HOME/llvm-mingw-$LLVM_MINGW_VERSION-$STDLIB-ubuntu-20.04-x86_64" "$HOME/llvm-mingw" - name: 'Download SDL2 sources' if: steps.cache-dependencies.outputs.cache-hit != 'true' run: | wget -O "SDL2-$SDL_VERSION.tar.gz" https://www.libsdl.org/release/SDL2-$SDL_VERSION.tar.gz || wget -O "SDL2-$SDL_VERSION.tar.gz" https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VERSION/SDL2-$SDL_VERSION.tar.gz echo "$SDL_SHA256 SDL2-$SDL_VERSION.tar.gz" | sha256sum -c - tar xvf "SDL2-$SDL_VERSION.tar.gz" - name: 'Build SDL2' if: steps.cache-dependencies.outputs.cache-hit != 'true' run: | export PATH="$HOME/llvm-mingw/bin:$PATH" cd SDL2-$SDL_VERSION ./configure --host="$TARGET-w64-$SYS" --prefix="$HOME/prefix" make make install cd .. - name: 'Download libflac and libogg sources' if: steps.cache-dependencies.outputs.cache-hit != 'true' run: | wget -O libogg-$LIBOGG_VERSION.tar.xz "https://ftp.osuosl.org/pub/xiph/releases/ogg/libogg-$LIBOGG_VERSION.tar.xz" || wget -O libogg-$LIBOGG_VERSION.tar.xz "https://github.com/xiph/ogg/releases/download/v$LIBOGG_VERSION/libogg-$LIBOGG_VERSION.tar.xz" echo "$LIBOGG_SHA256 libogg-$LIBOGG_VERSION.tar.xz" | sha256sum -c - tar xvf "libogg-$LIBOGG_VERSION.tar.xz" wget -O flac-$FLAC_VERSION.tar.xz "https://ftp.osuosl.org/pub/xiph/releases/flac/flac-$FLAC_VERSION.tar.xz" || wget -O flac-$FLAC_VERSION.tar.xz "https://github.com/xiph/flac/releases/download/$FLAC_VERSION/flac-$FLAC_VERSION.tar.xz" echo "$FLAC_SHA256 flac-$FLAC_VERSION.tar.xz" | sha256sum -c - tar xvf "flac-$FLAC_VERSION.tar.xz" - name: 'Build libflac' if: steps.cache-dependencies.outputs.cache-hit != 'true' run: | export PATH="$HOME/llvm-mingw/bin:$PATH" cd libogg-$LIBOGG_VERSION mkdir build cd build ../configure --host="$TARGET-w64-$SYS" --prefix="$HOME/prefix" make make install cd ../../flac-$FLAC_VERSION mkdir build cd build ../configure --host="$TARGET-w64-$SYS" --prefix="$HOME/prefix" --disable-programs --disable-examples --disable-cpplibs make make install cd ../.. - name: 'Download utf8proc' if: steps.cache-dependencies.outputs.cache-hit != 'true' run: | wget -O "utf8proc-$UTF8PROC_VERSION.tar.gz" "https://github.com/JuliaStrings/utf8proc/releases/download/v$UTF8PROC_VERSION/utf8proc-$UTF8PROC_VERSION.tar.gz" echo "$UTF8PROC_SHA256 utf8proc-$UTF8PROC_VERSION.tar.gz" | sha256sum -c - tar xvf "utf8proc-$UTF8PROC_VERSION.tar.gz" - name: 'Build utf8proc' if: steps.cache-dependencies.outputs.cache-hit != 'true' run: | export PATH="$HOME/llvm-mingw/bin:$PATH" cd utf8proc-$UTF8PROC_VERSION mkdir build cd build cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="$HOME/prefix" -DCMAKE_C_COMPILER="$TARGET-w64-$SYS-clang" -DCMAKE_SYSTEM_NAME="Windows" make make install cd ../.. - name: 'Get date of latest commit' id: date run: | cd schism echo "date=$(git log -n 1 --date=short --format=format:%cd | sed 's/\(....\)-\(..\)-\(..\).*/\1\2\3/')" >> $GITHUB_OUTPUT cd .. - name: 'Build package' run: | export PATH="$HOME/llvm-mingw/bin:$PATH" export PKG_CONFIG_PATH="$HOME/prefix/lib/pkgconfig" cd schism autoreconf -I"$HOME/prefix/share/aclocal" -i mkdir build cd build # Link to SDL2, since it's the only backend we support here ../configure --host="$TARGET-w64-$SYS" --prefix="$HOME/prefix" --with-flac --with-sdl2 --enable-sdl2-linking make "$TARGET-w64-$SYS-strip" -g schismtracker.exe cp schismtracker.exe ../.. cd ../.. cp "$HOME/prefix/bin/SDL2.dll" . cp "$HOME/prefix/bin/libFLAC-14.dll" . cp "$HOME/prefix/bin/libogg-0.dll" . "$TARGET-w64-$SYS-strip" -g "SDL2.dll" "$TARGET-w64-$SYS-strip" -g "libFLAC-14.dll" "$TARGET-w64-$SYS-strip" -g "libogg-0.dll" cp schism/docs/configuration.md schism/README.md schism/COPYING . wget https://raw.githubusercontent.com/xiph/flac/master/COPYING.Xiph unix2dos COPYING.Xiph COPYING README.md configuration.md - name: 'Upload artifact' uses: actions/upload-artifact@v4 with: name: schismtracker-${{ steps.date.outputs.date }}-${{ matrix.sys }}-${{ matrix.target }} path: | schismtracker.exe SDL2.dll libFLAC-14.dll libogg-0.dll COPYING COPYING.Xiph README.md configuration.md schismtracker-20250313/.github/workflows/os2.yml000066400000000000000000000133061476471630300214560ustar00rootroot00000000000000name: OS/2 (i386) on: push: pull_request: workflow_dispatch: jobs: build: runs-on: windows-latest name: os2-i386 env: SEZERO_SDL_COMMIT: 437c44d7017248fa799bb44b60d0fc8ced2f714e UTF8PROC_VERSION: 2.9.0 UTF8PROC_SHA256: bd215d04313b5bc42c1abedbcb0a6574667e31acee1085543a232204e36384c4 OPENWATCOM_VERSION: 2025-02-02 OPENWATCOM_SHA256: ee613f1ecd11b72de554a7d92236c4893c5edb1d80484656c152d720698beea8 defaults: run: shell: msys2 {0} steps: - name: 'Checkout' uses: actions/checkout@v4 with: path: schism # MSYS2 provides POSIX shell stuff - name: 'Setup MSYS2' uses: msys2/setup-msys2@v2 with: msystem: mingw32 update: true install: git mingw-w64-i686-toolchain libtool autoconf automake make zip unzip dos2unix patch p7zip location: 'C:\\msys2' - name: 'Retrieve runner temp directory' id: temp run: | cd schism echo "temp=$(cygpath -u "$RUNNER_TEMP")" >> $GITHUB_OUTPUT echo "wpath=$RUNNER_TEMP" >> $GITHUB_OUTPUT cd .. # FIXME store this in RUNNER_TEMP instead - name: 'Cache OpenWatcom' id: cache-openwatcom uses: actions/cache@v4 with: path: 'C:\\WATCOM' key: os2-cache-openwatcom - name: 'Cache dependencies' id: cache-i386-dependencies uses: actions/cache@v4 with: path: '${{ steps.temp.outputs.wpath }}\\i386prefix' key: os2-i386-dependencies-SEZEROSDL2_${{ env.SEZERO_SDL_COMMIT }}-UTF8PROC_${{ env.UTF8PROC_VERSION }} # Grab OpenWatcom V2 - name: 'Setup OpenWatcom' if: steps.cache-openwatcom.outputs.cache-hit != 'true' run: | wget -O openwatcom-install.exe "https://github.com/open-watcom/open-watcom-v2/releases/download/2025-02-02-Build/open-watcom-2_0-c-win-x86.exe" echo "$OPENWATCOM_SHA256 openwatcom-install.exe" | sha256sum -c - 7z x openwatcom-install.exe -o"/c/WATCOM" - name: "Download sezero\'s SDL repo" env: RUNNER_TEMP_CYGPATH: ${{ steps.temp.outputs.temp }} run: | wget -O "sezero.tar.gz" "https://github.com/sezero/SDL2-OS2/archive/$SEZERO_SDL_COMMIT.tar.gz" tar xvf "sezero.tar.gz" mv "SDL2-OS2-$SEZERO_SDL_COMMIT" "$RUNNER_TEMP_CYGPATH/i386prefix" rm "sezero.tar.gz" # Luckily we only really need to build UTF8PROC for OS/2 because # everything else is provided by sezero's builds of SDL. - name: 'Download utf8proc' if: steps.cache-i386-dependencies.outputs.cache-hit != 'true' run: | wget -O "utf8proc-$UTF8PROC_VERSION.tar.gz" "https://github.com/JuliaStrings/utf8proc/releases/download/v$UTF8PROC_VERSION/utf8proc-$UTF8PROC_VERSION.tar.gz" echo "$UTF8PROC_SHA256 utf8proc-$UTF8PROC_VERSION.tar.gz" | sha256sum -c - tar xvf "utf8proc-$UTF8PROC_VERSION.tar.gz" - name: 'Build utf8proc' if: steps.cache-i386-dependencies.outputs.cache-hit != 'true' env: RUNNER_TEMP_CYGPATH: ${{ steps.temp.outputs.temp }} run: | export PATH="/c/WATCOM/binnt:$PATH" export WATCOM="C:\\WATCOM" cd utf8proc-$UTF8PROC_VERSION owcc -I"/c/WATCOM/h" -za99 -c -o utf8proc.obj utf8proc.c wlib -n utf8proc.lib utf8proc.obj cp "utf8proc.lib" "$RUNNER_TEMP_CYGPATH/i386prefix/lib" cp "utf8proc.h" "$RUNNER_TEMP_CYGPATH/i386prefix/h" cd .. - name: 'Get date of latest commit' id: date run: | cd schism echo "date=$(git log -n 1 --date=short --format=format:%cd | sed 's/\(....\)-\(..\)-\(..\).*/\1\2\3/')" >> $GITHUB_OUTPUT cd .. - name: 'autoreconf' run: | cd schism autoreconf -i cd .. - name: 'Compile package' env: RUNNER_TEMP_CYGPATH: ${{ steps.temp.outputs.temp }} run: | export PATH="/c/WATCOM/binnt:$PATH" export WATCOM="C:\\WATCOM" cd schism mkdir build cd build # We link to SDL 1.2 here since it's universally supported by everything and is # useful as a fallback of sorts. ../configure 'CPP=owcc -E' 'CPPFLAGS=-I/c/WATCOM/h -I/c/WATCOM/h/os2 -b OS2V2_PM' CC=owcc 'CFLAGS=-b OS2V2_PM -O2 -za99 -fno-short-enum -march=i386 -mthreads' 'LDFLAGS=-b OS2V2_PM' --host=i386-pc-os2 OBJEXT=obj EXEEXT=exe --without-jack --with-sdl2 --enable-sdl2-linking PKG_CONFIG=false SDL2_CFLAGS="-I$RUNNER_TEMP_CYGPATH/i386prefix/h/SDL2" SDL2_LIBS="$RUNNER_TEMP_CYGPATH/i386prefix/lib/SDL2.lib" UTF8PROC_CFLAGS="-I$RUNNER_TEMP_CYGPATH/i386prefix/h" UTF8PROC_LIBS="$RUNNER_TEMP_CYGPATH/i386prefix/lib/utf8proc.lib" make wstrip schismtracker.exe cp schismtracker.exe ../.. cd ../.. - name: 'Create package' env: RUNNER_TEMP_CYGPATH: ${{ steps.temp.outputs.temp }} run: | cp "$RUNNER_TEMP_CYGPATH/i386prefix/dll/SDL2.dll" . cp schism/docs/configuration.md schism/README.md schism/COPYING . cp schism/icons/schism-icon-os2.ico schismtracker.ico wget https://raw.githubusercontent.com/xiph/flac/master/COPYING.Xiph unix2dos COPYING.Xiph COPYING README.md configuration.md - name: 'Upload artifact' uses: actions/upload-artifact@v4 with: name: schismtracker-${{ steps.date.outputs.date }}-os2 path: | schismtracker.exe schismtracker.ico SDL2.dll COPYING COPYING.Xiph README.md configuration.md schismtracker-20250313/.github/workflows/osx.yml000066400000000000000000000601141476471630300215630ustar00rootroot00000000000000name: OS X (powerpc/x86_64/arm64) on: push: pull_request: workflow_dispatch: # The configure prefix for any given architecture is: # $HOME/{architecture}prefix # while any specific files/binaries needed for that architecture, not # necessarily related to autotools, should be stored in # $HOME/{architecture} # This is so that we don't screw up the global directories with # files for many different architectures. # WARNING: The PowerPC compiling code is probably the ugliest and hackiest # thing I have ever done with CI. To be honest I have no idea how it even # works properly and it mixes crap from different SDKs just to get it to # build with the only powerpc gcc that was built for x86_64 (gcc 4.2.1) # # For some time I attempted to build it with support for 10.3, but # evidently it's missing stuff that libgcc wants, and there's really # nothing that I can do about that. :( # # Maybe some day I'll write some stuff here that compiles and caches a # much newer compiler that is actually *meant* to target 10.3 so I can # get it working right. Or maybe I won't. :) jobs: osx: runs-on: macos-14 env: SDL_VERSION: 2.32.0 SDL_SHA256: f5c2b52498785858f3de1e2996eba3c1b805d08fe168a47ea527c7fc339072d0 FLAC_VERSION: 1.5.0 FLAC_SHA256: f2c1c76592a82ffff8413ba3c4a1299b6c7ab06c734dee03fd88630485c2b920 LIBOGG_VERSION: 1.3.5 LIBOGG_SHA256: c4d91be36fc8e54deae7575241e03f4211eb102afb3fc0775fbbc1b740016705 UTF8PROC_VERSION: 2.9.0 UTF8PROC_SHA256: bd215d04313b5bc42c1abedbcb0a6574667e31acee1085543a232204e36384c4 steps: - name: 'Install dependencies' run: | brew install autoconf automake libtool zip - name: 'Cache x86_64 dependencies' id: cache-x86_64-dependencies uses: actions/cache@v4 with: path: '/Users/runner/x86_64prefix' key: mac-x86_64-dependencies-SDL_${{ env.SDL_VERSION }}-FLAC_${{ env.FLAC_VERSION }}-LIBOGG_${{ env.LIBOGG_VERSION }}-UTF8PROC_${{ env.UTF8PROC_VERSION }} - name: 'Cache x86_64 files' id: cache-x86_64-files uses: actions/cache@v4 with: path: '/Users/runner/x86_64' key: mac-x86_64-files - name: 'Cache arm64 dependencies' id: cache-arm64-dependencies uses: actions/cache@v4 with: path: '/Users/runner/arm64prefix' key: mac-arm64-dependencies-SDL_${{ env.SDL_VERSION }}-FLAC_${{ env.FLAC_VERSION }}-LIBOGG_${{ env.LIBOGG_VERSION }}-UTF8PROC_${{ env.UTF8PROC_VERSION }} - name: 'Cache arm64 files' id: cache-arm64-files uses: actions/cache@v4 with: path: '/Users/runner/arm64' key: mac-arm64-files - name: 'Cache powerpc dependencies' id: cache-powerpc-dependencies uses: actions/cache@v4 with: path: '/Users/runner/ppcprefix' key: mac-powerpc-dependencies-SDL_1.2.13-1-FLAC_${{ env.FLAC_VERSION }}-LIBOGG_${{ env.LIBOGG_VERSION }}-UTF8PROC_${{ env.UTF8PROC_VERSION }} - name: 'Cache powerpc files' id: cache-powerpc-files uses: actions/cache@v4 with: path: '/Users/runner/ppc' key: mac-powerpc-files - name: 'Download 10.7 SDK for x86_64' if: steps.cache-x86_64-files.outputs.cache-hit != 'true' run: | mkdir -p "$HOME/x86_64" pushd "$HOME/x86_64" mkdir SDKs cd SDKs curl -L "https://github.com/alexey-lysiuk/macos-sdk/releases/download/10.7/MacOSX10.7.tar.bz2" | tar -xvf - popd - name: 'Download 11.0 SDK for arm64' if: steps.cache-arm64-files.outputs.cache-hit != 'true' run: | mkdir -p "$HOME/arm64" pushd "$HOME/arm64" mkdir SDKs cd SDKs curl -L "https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.0.sdk.tar.xz" | tar -xvf - popd - name: 'Grab PowerPC cross compile binaries' if: steps.cache-powerpc-files.outputs.cache-hit != 'true' run: | wget -O schism-macppc-bins.zip "https://www.dropbox.com/scl/fi/trq99fq51p13nh8tajpwa/schism-macppc-bins.zip?rlkey=xemvhpmm1ci0dnseawmanr749&st=c7jlwru4&dl=1" unzip schism-macppc-bins.zip -d "schism-macppc-bins" rm schism-macppc-bins.zip pushd schism-macppc-bins mkdir -p "$HOME/ppc/SDKs" tar -xvf "Xcode3as.tar.gz" -C "$HOME/ppc" & tar -xvf "Xcode3gcc40.tar.gz" -C "$HOME/ppc" & tar -xvf "Xcode3gcc42.tar.gz" -C "$HOME/ppc" & tar -xvf "Xcode3ld.tar.gz" -C "$HOME/ppc" & tar -xvf "OSX108INT.tar.gz" -C "$HOME/ppc" & wait curl -L "https://github.com/alexey-lysiuk/macos-sdk/releases/download/10.4u/MacOSX10.4u.tar.bz2" | tar -xvf - -C "$HOME/ppc/SDKs" curl -L "https://github.com/alexey-lysiuk/macos-sdk/releases/download/10.5/MacOSX10.5.tar.bz2" | tar -xvf - -C "$HOME/ppc/SDKs" popd - name: 'Install PowerPC cross compile binaries into Xcode' run: | mkdir "$(xcrun xcode-select --print-path)/Toolchains/XcodeDefault.xctoolchain/usr/libexec/as/ppc" cp "$HOME/ppc/usr/libexec/gcc/darwin/ppc/as" "$(xcrun xcode-select --print-path)/Toolchains/XcodeDefault.xctoolchain/usr/libexec/as/ppc/as" - name: 'Checkout' uses: actions/checkout@v4 - name: 'Get date of latest commit' id: date run: echo "date=$(git log -n 1 --date=short --format=format:%cd | sed 's/\(....\)-\(..\)-\(..\).*/\1\2\3/')" >> $GITHUB_OUTPUT # This binary package was prepared from the official release. # I've used SDL 1.2.13 here because it's the latest version to # actually build on Panther, and I don't see any reason to # upgrade (except security, but if you care about security why # are you using PowerPC?) - name: 'Download SDL 1.2 binary (powerpc)' if: steps.cache-powerpc-dependencies.outputs.cache-hit != 'true' run: | wget -O "SDL-1.2.13-ppc.zip" "https://www.dropbox.com/scl/fi/8jlk6embrr09torpvhed2/SDL-1.2.13-ppc.zip?rlkey=eu4k41oewdfz6n2ygh97k8pzd&st=0y8oam0d&dl=1" echo "1ef3182d97dded091955683db51edf02f56f93d69d0914622d612de66b0c7eb2 SDL-1.2.13-ppc.zip" | shasum -a 256 -c - unzip "SDL-1.2.13-ppc.zip" - name: 'Download SDL2 sources (x86_64/arm64)' if: steps.cache-x86_64-dependencies.outputs.cache-hit != 'true' || steps.cache-arm64-dependencies.outputs.cache-hit != 'true' run: | wget -O "SDL2-$SDL_VERSION.tar.gz" https://www.libsdl.org/release/SDL2-$SDL_VERSION.tar.gz || wget -O "SDL2-$SDL_VERSION.tar.gz" https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VERSION/SDL2-$SDL_VERSION.tar.gz echo "$SDL_SHA256 SDL2-$SDL_VERSION.tar.gz" | shasum -a 256 -c - tar xvf "SDL2-$SDL_VERSION.tar.gz" - name: 'Install SDL 1.2 (powerpc)' if: steps.cache-powerpc-dependencies.outputs.cache-hit != 'true' run: | export PATH="$PATH:$HOME/ppc/usr/bin" pushd SDL-1.2.13-ppc # copy headers mkdir -p "$HOME/ppcprefix/include/SDL" cp -r headers/* "$HOME/ppcprefix/include/SDL" # copy the library mkdir -p "$HOME/ppcprefix/lib" cp "libSDL-1.2.0.dylib" "$HOME/ppcprefix/lib" ln -sf "libSDL-1.2.0.dylib" "$HOME/ppcprefix/lib/libSDL.dylib" popd - name: 'Build SDL2 (x86_64)' if: steps.cache-x86_64-dependencies.outputs.cache-hit != 'true' run: | pushd SDL2-$SDL_VERSION mkdir build_x86_64 cd build_x86_64 ../configure CPPFLAGS="-arch x86_64 -mmacosx-version-min=10.7 -DMAC_OS_X_VERSION_MIN_REQUIRED=1070 -isysroot $HOME/x86_64/SDKs/MacOSX10.7.sdk" CFLAGS="-arch x86_64" CXXFLAGS="-arch x86_64" LDFLAGS="-arch x86_64 -F$HOME/x86_64/SDKs/MacOSX10.7.sdk/System/Library/Frameworks -L/usr/lib -L/usr/lib/system -Wl,-syslibroot,$HOME/x86_64/SDKs/MacOSX10.7.sdk" --host=x86_64-apple-darwin13 --prefix="$HOME/x86_64prefix" make make install popd - name: 'Build SDL2 (arm64)' if: steps.cache-arm64-dependencies.outputs.cache-hit != 'true' run: | pushd SDL2-$SDL_VERSION mkdir build_arm64 cd build_arm64 ../configure CPPFLAGS="-arch arm64 -mmacosx-version-min=11.0 -isysroot $HOME/arm64/SDKs/MacOSX11.0.sdk" CXXFLAGS="-arch arm64" CFLAGS="-arch arm64" LDFLAGS="-arch arm64" --host=aarch64-apple-darwin20 --prefix="$HOME/arm64prefix" make make install popd - name: 'Download and prepare libogg sources' if: steps.cache-powerpc-dependencies.outputs.cache-hit != 'true' || steps.cache-x86_64-dependencies.outputs.cache-hit != 'true' || steps.cache-arm64-dependencies.outputs.cache-hit != 'true' run: | wget -O libogg-$LIBOGG_VERSION.tar.xz "https://ftp.osuosl.org/pub/xiph/releases/ogg/libogg-$LIBOGG_VERSION.tar.xz" || wget -O libogg-$LIBOGG_VERSION.tar.xz "https://github.com/xiph/ogg/releases/download/v$LIBOGG_VERSION/libogg-$LIBOGG_VERSION.tar.xz" echo "$LIBOGG_SHA256 libogg-$LIBOGG_VERSION.tar.xz" | shasum -a 256 -c - tar xvf "libogg-$LIBOGG_VERSION.tar.xz" pushd "libogg-$LIBOGG_VERSION" # libogg's configure is too old autoreconf -I"$HOME/x86_64prefix/share/aclocal" -i popd - name: 'Build libogg (powerpc)' if: steps.cache-powerpc-dependencies.outputs.cache-hit != 'true' run: | pushd libogg-$LIBOGG_VERSION export PATH="$PATH:$HOME/ppc/usr/bin" mkdir build_powerpc cd build_powerpc ../configure CC=powerpc-apple-darwin10-gcc-4.2.1 CPP=powerpc-apple-darwin10-cpp-4.2.1 OBJC=powerpc-apple-darwin10-gcc-4.2.1 CXX=powerpc-apple-darwin10-g++-4.2.1 CPPFLAGS="-I$HOME/ppc/SDKs/MacOSX10.4u.sdk/usr/include/gcc/darwin/3.3 -isysroot $HOME/ppc/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4" LDFLAGS="-F$HOME/ppc/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks -L$HOME/ppc/SDKs/MacOSX10.4u.sdk/usr/lib -L$HOME/ppc/SDKs/MacOSX10.4u.sdk/usr/lib/system -Wl,-syslibroot,$HOME/ppc/SDKs/MacOSX10.4u.sdk" --host=powerpc-apple-darwin10 --prefix="$HOME/ppcprefix" make make install popd - name: 'Build libogg (x86_64)' if: steps.cache-x86_64-dependencies.outputs.cache-hit != 'true' run: | pushd libogg-$LIBOGG_VERSION mkdir build_x86_64 cd build_x86_64 ../configure CPPFLAGS="-arch x86_64 -mmacosx-version-min=10.7 -DMAC_OS_X_VERSION_MIN_REQUIRED=1070 -isysroot $HOME/x86_64/SDKs/MacOSX10.7.sdk" CFLAGS="-arch x86_64" CXXFLAGS="-arch x86_64" LDFLAGS="-arch x86_64 -F$HOME/x86_64/SDKs/MacOSX10.7.sdk/System/Library/Frameworks -L/usr/lib -L/usr/lib/system -Wl,-syslibroot,$HOME/x86_64/SDKs/MacOSX10.7.sdk" --host=x86_64-apple-darwin13 --prefix="$HOME/x86_64prefix" make make install popd - name: 'Build libogg (arm64)' if: steps.cache-arm64-dependencies.outputs.cache-hit != 'true' run: | pushd libogg-$LIBOGG_VERSION mkdir build_arm64 cd build_arm64 ../configure CPPFLAGS="-arch arm64 -mmacosx-version-min=11.0 -isysroot $HOME/arm64/SDKs/MacOSX11.0.sdk" CFLAGS="-arch arm64" CXXFLAGS="-arch arm64" LDFLAGS="-arch arm64" --host=aarch64-apple-darwin20 --prefix="$HOME/arm64prefix" make make install popd - name: 'Download and prepare FLAC sources' if: steps.cache-powerpc-dependencies.outputs.cache-hit != 'true' || steps.cache-x86_64-dependencies.outputs.cache-hit != 'true' || steps.cache-arm64-dependencies.outputs.cache-hit != 'true' run: | wget -O flac-$FLAC_VERSION.tar.xz "https://ftp.osuosl.org/pub/xiph/releases/flac/flac-$FLAC_VERSION.tar.xz" || wget -O flac-$FLAC_VERSION.tar.xz "https://github.com/xiph/flac/releases/download/$FLAC_VERSION/flac-$FLAC_VERSION.tar.xz" echo "$FLAC_SHA256 flac-$FLAC_VERSION.tar.xz" | shasum -a 256 -c - tar xvf "flac-$FLAC_VERSION.tar.xz" pushd "flac-$FLAC_VERSION" autoreconf -I"$HOME/x86_64prefix/share/aclocal" -i popd - name: 'Build FLAC (powerpc)' if: steps.cache-powerpc-dependencies.outputs.cache-hit != 'true' run: | pushd flac-$FLAC_VERSION export PATH="$PATH:$HOME/ppc/usr/bin" mkdir build_powerpc cd build_powerpc ../configure CC=powerpc-apple-darwin10-gcc-4.2.1 CPP=powerpc-apple-darwin10-cpp-4.2.1 OBJC=powerpc-apple-darwin10-gcc-4.2.1 CXX=powerpc-apple-darwin10-g++-4.2.1 CPPFLAGS="-I$HOME/ppc/SDKs/MacOSX10.4u.sdk/usr/include/gcc/darwin/3.3 -isysroot $HOME/ppc/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4" LDFLAGS="-F$HOME/ppc/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks -L$HOME/ppc/SDKs/MacOSX10.4u.sdk/usr/lib -L$HOME/ppc/SDKs/MacOSX10.4u.sdk/usr/lib/system -Wl,-syslibroot,$HOME/ppc/SDKs/MacOSX10.4u.sdk" --host=powerpc-apple-darwin10 --prefix="$HOME/ppcprefix" --disable-cpplibs --disable-programs --disable-examples make make install popd - name: 'Build FLAC (x86_64)' if: steps.cache-x86_64-dependencies.outputs.cache-hit != 'true' run: | pushd flac-$FLAC_VERSION (export MACOSX_DEPLOYMENT_TARGET="10.7" && mkdir build_x86_64 && cd build_x86_64 && ../configure CPPFLAGS="-arch x86_64 -mmacosx-version-min=10.7 -DMAC_OS_X_VERSION_MIN_REQUIRED=1070 -isysroot $HOME/x86_64/SDKs/MacOSX10.7.sdk" CFLAGS="-arch x86_64" CXXFLAGS="-arch x86_64" LDFLAGS="-arch x86_64 -F$HOME/x86_64/SDKs/MacOSX10.7.sdk/System/Library/Frameworks -L/usr/lib -L/usr/lib/system -Wl,-syslibroot,$HOME/x86_64/SDKs/MacOSX10.7.sdk" --host=x86_64-apple-darwin13 --prefix="$HOME/x86_64prefix" --disable-cpplibs --disable-programs --disable-examples && make && make install) popd - name: 'Build FLAC (arm64)' if: steps.cache-arm64-dependencies.outputs.cache-hit != 'true' run: | pushd flac-$FLAC_VERSION (export MACOSX_DEPLOYMENT_TARGET="11.0" && mkdir build_arm64 && cd build_arm64 && ../configure CPPFLAGS="-arch arm64 -mmacosx-version-min=11.0 -isysroot $HOME/arm64/SDKs/MacOSX11.0.sdk" CFLAGS="-arch arm64" CXXFLAGS="-arch arm64" LDFLAGS="-arch arm64" --host=aarch64-apple-darwin20 --prefix="$HOME/arm64prefix" --disable-cpplibs --disable-programs --disable-examples && make && make install) popd - name: 'Download utf8proc' if: steps.cache-powerpc-dependencies.outputs.cache-hit != 'true' || steps.cache-x86_64-dependencies.outputs.cache-hit != 'true' || steps.cache-arm64-dependencies.outputs.cache-hit != 'true' run: | wget -O "utf8proc-$UTF8PROC_VERSION.tar.gz" "https://github.com/JuliaStrings/utf8proc/releases/download/v$UTF8PROC_VERSION/utf8proc-$UTF8PROC_VERSION.tar.gz" echo "$UTF8PROC_SHA256 utf8proc-$UTF8PROC_VERSION.tar.gz" | shasum -a 256 -c - tar xvf "utf8proc-$UTF8PROC_VERSION.tar.gz" - name: 'Build utf8proc (powerpc)' if: steps.cache-powerpc-dependencies.outputs.cache-hit != 'true' run: | pushd utf8proc-$UTF8PROC_VERSION patch -p1 < ../.github/patches/utf8proc/2-fix-prefix.patch export PATH="$PATH:$HOME/ppc/usr/bin" make CC=powerpc-apple-darwin10-gcc-4.2.1 CFLAGS="-I$HOME/ppc/SDKs/MacOSX10.4u.sdk/usr/include/gcc/darwin/3.3 -isysroot $HOME/ppc/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4 -O2" LDFLAGS="-F$HOME/ppc/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks -L$HOME/ppc/SDKs/MacOSX10.4u.sdk/usr/lib -L$HOME/ppc/SDKs/MacOSX10.4u.sdk/usr/lib/system -Wl,-syslibroot,$HOME/ppc/SDKs/MacOSX10.4u.sdk" make install patch -p1 -R < ../.github/patches/utf8proc/2-fix-prefix.patch popd - name: 'Build utf8proc (x86_64)' if: steps.cache-x86_64-dependencies.outputs.cache-hit != 'true' run: | pushd utf8proc-$UTF8PROC_VERSION mkdir build_x86_64 cd build_x86_64 LDFLAGS="-F$HOME/x86_64/SDKs/MacOSX10.7.sdk/System/Library/Frameworks -L/usr/lib -L/usr/lib/system -Wl,-syslibroot,$HOME/x86_64/SDKs/MacOSX10.7.sdk" cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="$HOME/x86_64prefix" -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET="10.7" -DCMAKE_OSX_SYSROOT="$HOME/x86_64/SDKs/MacOSX10.7.sdk" -DBUILD_SHARED_LIBS=1 make make install popd - name: 'Build utf8proc (arm64)' if: steps.cache-arm64-dependencies.outputs.cache-hit != 'true' run: | pushd utf8proc-$UTF8PROC_VERSION mkdir build_arm64 cd build_arm64 cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="$HOME/arm64prefix" -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET="11.0" -DCMAKE_OSX_SYSROOT="$HOME/arm64/SDKs/MacOSX11.0.sdk" -DBUILD_SHARED_LIBS=1 make make install popd - name: 'Build Schism' run: | autoreconf -I"$HOME/x86_64prefix/share/aclocal" -i (export PATH="$PATH:$HOME/ppc/usr/bin" && export PKG_CONFIG_PATH="$HOME/ppcprefix/lib/pkgconfig" && mkdir build_powerpc && cd build_powerpc && ../configure CC=powerpc-apple-darwin10-gcc-4.2.1 CPP=powerpc-apple-darwin10-cpp-4.2.1 OBJC=powerpc-apple-darwin10-gcc-4.2.1 CXX=powerpc-apple-darwin10-g++-4.2.1 CPPFLAGS="-I$HOME/ppc/SDKs/MacOSX10.4u.sdk/usr/include/gcc/darwin/3.3 -isysroot $HOME/ppc/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4" CFLAGS="-mcpu=G3 -O2 -ftree-vectorize" LDFLAGS="-F$HOME/ppc/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks -L/usr/lib -L/usr/lib/system -Wl,-syslibroot,$HOME/ppc/SDKs/MacOSX10.4u.sdk -static-libgcc $HOME/ppc/SDKs/MacOSX10.5.sdk/usr/lib/gcc/powerpc-apple-darwin10/4.2.1/libgcc.a" SDL12_CFLAGS="-I$HOME/ppcprefix/include/SDL" SDL12_LIBS="-L$HOME/ppcprefix/lib -lSDL" --host=powerpc-apple-darwin10 --prefix="$HOME/ppcprefix" --with-flac --with-sdl12 --enable-flac-linking --enable-sdl12-linking --without-sdl2 && make && strip -S schismtracker) & (export PATH="$PATH:$HOME/ppc/usr/bin" && export PKG_CONFIG_PATH="$HOME/ppcprefix/lib/pkgconfig" && mkdir build_ppc7400 && cd build_ppc7400 && ../configure CC=powerpc-apple-darwin10-gcc-4.2.1 CPP=powerpc-apple-darwin10-cpp-4.2.1 OBJC=powerpc-apple-darwin10-gcc-4.2.1 CXX=powerpc-apple-darwin10-g++-4.2.1 CPPFLAGS="-I$HOME/ppc/SDKs/MacOSX10.4u.sdk/usr/include/gcc/darwin/3.3 -isysroot $HOME/ppc/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4" CFLAGS="-mcpu=G4 -O2 -ftree-vectorize" LDFLAGS="-F$HOME/ppc/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks -L/usr/lib -L/usr/lib/system -Wl,-syslibroot,$HOME/ppc/SDKs/MacOSX10.4u.sdk -static-libgcc $HOME/ppc/SDKs/MacOSX10.5.sdk/usr/lib/gcc/powerpc-apple-darwin10/4.2.1/libgcc.a" SDL12_CFLAGS="-I$HOME/ppcprefix/include/SDL" SDL12_LIBS="-L$HOME/ppcprefix/lib -lSDL" --host=powerpc-apple-darwin10 --prefix="$HOME/ppcprefix" --with-flac --with-sdl12 --enable-flac-linking --enable-sdl12-linking --without-sdl2 && make && strip -S schismtracker) & (export MACOSX_DEPLOYMENT_TARGET="10.7" && export PKG_CONFIG_PATH="$HOME/x86_64prefix/lib/pkgconfig" && mkdir build_x86_64 && cd build_x86_64 && ../configure CPPFLAGS="-arch x86_64 -mmacosx-version-min=10.7 -DMAC_OS_X_VERSION_MIN_REQUIRED=1070 -isysroot $HOME/x86_64/SDKs/MacOSX10.7.sdk" CFLAGS="-arch x86_64" CXXFLAGS="-arch x86_64" LDFLAGS="-arch x86_64 -F$HOME/x86_64/SDKs/MacOSX10.7.sdk/System/Library/Frameworks -L/usr/lib -L/usr/lib/system -Wl,-syslibroot,$HOME/x86_64/SDKs/MacOSX10.7.sdk" --host=x86_64-apple-darwin13 --prefix="$HOME/x86_64prefix" --with-sdl-prefix="$HOME/x86_64prefix" --with-flac --without-sdl12 --enable-flac-linking --enable-sdl2-linking && make && strip -S schismtracker) & (export MACOSX_DEPLOYMENT_TARGET="11.0" && export PKG_CONFIG_PATH="$HOME/arm64prefix/lib/pkgconfig" && mkdir build_arm64 && cd build_arm64 && ../configure CPPFLAGS="-arch arm64 -mmacosx-version-min=11.0 -isysroot $HOME/arm64/SDKs/MacOSX11.0.sdk" OBJCFLAGS="-arch arm64" CFLAGS="-arch arm64" CXXFLAGS="-arch arm64" LDFLAGS="-arch arm64" --host=aarch64-apple-darwin20 --prefix="$HOME/arm64prefix" --with-sdl-prefix="$HOME/arm64prefix" --with-flac --without-sdl12 --enable-flac-linking --enable-sdl2-linking && make && strip -S schismtracker) & wait cat build_powerpc/config.log cd ../.. - name: 'Package Schism' env: SCHISM_VERSION: ${{ steps.date.outputs.date }} run: | for i in FLAC.14 ogg.0; do "$HOME/ppc/usr/bin/install_name_tool" -change "$HOME/ppcprefix/lib/lib${i}.dylib" @executable_path/../Resources/lib${i}.dylib build_powerpc/schismtracker "$HOME/ppc/usr/bin/install_name_tool" -change "$HOME/ppcprefix/lib/lib${i}.dylib" @executable_path/../Resources/lib${i}.dylib build_ppc7400/schismtracker install_name_tool -change "$HOME/x86_64prefix/lib/lib${i}.dylib" @executable_path/../Resources/lib${i}.dylib build_x86_64/schismtracker install_name_tool -change "$HOME/arm64prefix/lib/lib${i}.dylib" @executable_path/../Resources/lib${i}.dylib build_arm64/schismtracker done "$HOME/ppc/usr/bin/install_name_tool" -change "@executable_path/../Frameworks/SDL.framework/Versions/A/SDL" "@executable_path/../Resources/libSDL-1.2.0.dylib" build_powerpc/schismtracker "$HOME/ppc/usr/bin/install_name_tool" -change "@executable_path/../Frameworks/SDL.framework/Versions/A/SDL" "@executable_path/../Resources/libSDL-1.2.0.dylib" build_ppc7400/schismtracker install_name_tool -change "$HOME/x86_64prefix/lib/libSDL2-2.0.0.dylib" @executable_path/../Resources/libSDL2-2.0.0.dylib build_x86_64/schismtracker install_name_tool -change "$HOME/arm64prefix/lib/libSDL2-2.0.0.dylib" @executable_path/../Resources/libSDL2-2.0.0.dylib build_arm64/schismtracker "$HOME/ppc/usr/bin/install_name_tool" -change "$HOME/ppcprefix/lib/libutf8proc.3.dylib" @executable_path/../Resources/libutf8proc.3.dylib build_powerpc/schismtracker "$HOME/ppc/usr/bin/install_name_tool" -change "$HOME/ppcprefix/lib/libutf8proc.3.dylib" @executable_path/../Resources/libutf8proc.3.dylib build_ppc7400/schismtracker install_name_tool -change "@rpath/libutf8proc.3.dylib" @executable_path/../Resources/libutf8proc.3.dylib build_x86_64/schismtracker install_name_tool -change "@rpath/libutf8proc.3.dylib" @executable_path/../Resources/libutf8proc.3.dylib build_arm64/schismtracker "$HOME/ppc/usr/bin/install_name_tool" -change "$HOME/ppcprefix/lib/libogg.0.dylib" @executable_path/../Resources/libogg.0.dylib "$HOME/ppcprefix/lib/libFLAC.14.dylib" install_name_tool -change "$HOME/x86_64prefix/lib/libogg.0.dylib" @executable_path/../Resources/libogg.0.dylib "$HOME/x86_64prefix/lib/libFLAC.14.dylib" install_name_tool -change "$HOME/arm64prefix/lib/libogg.0.dylib" @executable_path/../Resources/libogg.0.dylib "$HOME/arm64prefix/lib/libFLAC.14.dylib" lipo -create -o schismtracker build_powerpc/schismtracker build_ppc7400/schismtracker build_x86_64/schismtracker build_arm64/schismtracker cd sys/macosx/Schism_Tracker.app/Contents/ sed -i .bak "s;CFBundle.*Version.*;$SCHISM_VERSION;" Info.plist rm Info.plist.bak mkdir MacOS cp ../../../../schismtracker MacOS for i in FLAC.14 ogg.0 utf8proc.3; do lipo -create -o "Resources/lib${i}.dylib" "$HOME/x86_64prefix/lib/lib${i}.dylib" "$HOME/ppcprefix/lib/lib${i}.dylib" "$HOME/arm64prefix/lib/lib${i}.dylib" done lipo -create -o "Resources/libSDL2-2.0.0.dylib" "$HOME/x86_64prefix/lib/libSDL2-2.0.0.dylib" "$HOME/arm64prefix/lib/libSDL2-2.0.0.dylib" cp "$HOME/ppcprefix/lib/libSDL-1.2.0.dylib" "Resources/libSDL-1.2.0.dylib" cd ../../../.. cp -r sys/macosx/Schism_Tracker.app Schism\ Tracker.app cp docs/configuration.md . wget https://raw.githubusercontent.com/xiph/flac/master/COPYING.Xiph zip -r schismtracker.zip configuration.md COPYING COPYING.Xiph README.md Schism\ Tracker.app - name: 'Upload artifact' uses: actions/upload-artifact@v4 with: name: schismtracker-${{ steps.date.outputs.date }}-macos path: schismtracker.zip schismtracker-20250313/.github/workflows/ubuntu.yml000066400000000000000000000045411476471630300222760ustar00rootroot00000000000000name: Ubuntu on: push: pull_request: workflow_dispatch: jobs: ubuntu: runs-on: ubuntu-22.04 steps: - name: 'Install dependencies' run: | sudo apt-get update sudo apt-get install --fix-missing libutf8proc-dev libjack-jackd2-dev build-essential automake autoconf autoconf-archive libxxf86vm-dev libsdl2-dev libsdl1.2-dev libasound2-dev libflac-dev git libtool zip wget - name: 'Checkout' uses: actions/checkout@v4 - name: 'Get date of latest commit' id: date run: echo "date=$(git log -n 1 --date=short --format=format:%cd | sed 's/\(....\)-\(..\)-\(..\).*/\1\2\3/')" >> $GITHUB_OUTPUT - name: 'autoreconf -i' run: autoreconf -i - name: 'Build package' run: | mkdir -p build cd build ../configure --with-flac --with-alsa --with-jack make strip -S schismtracker cd .. cp build/schismtracker . cp docs/configuration.md . cp sys/posix/schismtracker.1 . cp /usr/lib/x86_64-linux-gnu/libutf8proc.so.2 /usr/lib/x86_64-linux-gnu/libFLAC.so.8 /usr/lib/x86_64-linux-gnu/libogg.so.0 . cp sys/fd.org/schism.desktop . cp icons/schism-icon-128.png . wget https://raw.githubusercontent.com/xiph/flac/master/COPYING.Xiph cp .github/scripts/run.sh .github/scripts/install.sh . chmod +x run.sh chmod +x install.sh zip schismtracker.zip configuration.md COPYING COPYING.Xiph README.md schismtracker.1 schismtracker schism.desktop schism-icon-128.png libFLAC.so.8 libogg.so.0 libutf8proc.so.2 run.sh install.sh - name: 'Upload artifact' uses: actions/upload-artifact@v4 with: name: schismtracker-${{ steps.date.outputs.date }}-linux path: schismtracker.zip - name: 'Create source tarball' env: SCHISM_VERSION: ${{ steps.date.outputs.date }} run: | cd build make distcheck make dist-gzip mv "schismtracker-$SCHISM_VERSION.tar.gz" "../schismtracker-$SCHISM_VERSION.source.tar.gz" - name: 'Upload source tarball' uses: actions/upload-artifact@v4 with: name: schismtracker-${{ steps.date.outputs.date }}.source path: schismtracker-${{ steps.date.outputs.date }}.source.tar.gz schismtracker-20250313/.github/workflows/wii.yml000066400000000000000000000057021476471630300215440ustar00rootroot00000000000000name: Wii (devkitPro) on: push: pull_request: workflow_dispatch: jobs: wii: runs-on: ubuntu-latest env: UTF8PROC_VERSION: 2.9.0 UTF8PROC_SHA256: bd215d04313b5bc42c1abedbcb0a6574667e31acee1085543a232204e36384c4 container: image: devkitpro/devkitppc:latest steps: - name: 'Checkout' uses: actions/checkout@v4 with: path: schismtracker - name: 'Install build dependencies' run: | apt-get update apt-get install -y ninja-build autoconf automake libtool libsdl2-dev - name: 'Cache dependencies' id: cache-dependencies uses: actions/cache@v4 with: path: '/github/home/wiiprefix' key: wii-dependencies-UTF8PROC_${{ env.UTF8PROC_VERSION }} - name: 'Download utf8proc' if: steps.cache-dependencies.outputs.cache-hit != 'true' run: | wget -O "utf8proc-$UTF8PROC_VERSION.tar.gz" "https://github.com/JuliaStrings/utf8proc/releases/download/v$UTF8PROC_VERSION/utf8proc-$UTF8PROC_VERSION.tar.gz" echo "$UTF8PROC_SHA256 utf8proc-$UTF8PROC_VERSION.tar.gz" | sha256sum -c - tar xzvf "utf8proc-$UTF8PROC_VERSION.tar.gz" - name: 'Build utf8proc' if: steps.cache-dependencies.outputs.cache-hit != 'true' run: | cd utf8proc-$UTF8PROC_VERSION mkdir build cd build export PATH="${DEVKITPRO}/portlibs/wii/bin:${DEVKITPPC}/bin:$PATH" powerpc-eabi-cmake -DCMAKE_INSTALL_PREFIX="$HOME/wiiprefix" .. make make install cd ../.. - name: 'Get date of latest commit' id: date run: | cd schismtracker echo "date=$(git log -n 1 --date=short --format=format:%cd | sed 's/\(....\)-\(..\)-\(..\).*/\1\2\3/')" >> $GITHUB_OUTPUT cd .. - name: 'Build Schism' run: | cd schismtracker autoreconf -i mkdir build cd build export PATH="${DEVKITPRO}/portlibs/wii/bin:${DEVKITPPC}/bin:$PATH" FLAC_LIBS="-L${DEVKITPRO}/portlibs/ppc/lib -lFLAC -logg" PKG_CONFIG=pkg-config PKG_CONFIG_LIBDIR="$HOME/wiiprefix/lib/pkgconfig:${DEVKITPRO}/portlibs/wii/lib/pkgconfig:${DEVKITPRO}/portlibs/ppc/lib/pkgconfig" ../configure --host=powerpc-eabi --with-flac --without-sdl12 --without-sdl3 --enable-flac-linking --enable-force-wii --enable-sdl2-linking make powerpc-eabi-strip -S schismtracker.elf mkdir -p up/apps cp -r ../sys/wii/schismtracker up/apps mv schismtracker.elf up/apps/schismtracker/boot.elf wget https://raw.githubusercontent.com/xiph/flac/master/COPYING.Xiph cp ../docs/configuration.md ../README.md ../COPYING COPYING.Xiph up/apps/schismtracker - name: 'Upload binary' uses: actions/upload-artifact@v4 with: name: schismtracker-${{ steps.date.outputs.date }}-wii path: schismtracker/build/up schismtracker-20250313/.github/workflows/wiiu.yml000066400000000000000000000102711476471630300217260ustar00rootroot00000000000000name: Wii U (devkitPro) on: push: pull_request: workflow_dispatch: jobs: wiiu: runs-on: ubuntu-latest env: UTF8PROC_VERSION: 2.9.0 UTF8PROC_SHA256: bd215d04313b5bc42c1abedbcb0a6574667e31acee1085543a232204e36384c4 container: image: devkitpro/devkitppc:latest steps: - name: 'Checkout' uses: actions/checkout@v4 with: path: schismtracker - name: 'Install build dependencies' run: | apt-get update apt-get install -y ninja-build autoconf automake libtool libsdl2-dev - name: 'Get date of latest commit' id: date run: | cd schismtracker echo "date=$(git log -n 1 --date=short --format=format:%cd | sed 's/\(....\)-\(..\)-\(..\).*/\1\2\3/')" >> $GITHUB_OUTPUT cd .. # This uses my own fork of SDL until the keyboard patches get merged - name: 'Checkout SDL2' id: checkout uses: actions/checkout@v4 with: repository: 'devkitPro/SDL' ref: 'wiiu-sdl2-2.28' path: SDL # this has to be after so it can check the current commit from my branch - name: 'Cache dependencies' id: cache-dependencies uses: actions/cache@v4 with: path: '/github/home/wiiuprefix' key: wiiu-dependencies-SDL_${{ steps.checkout.outputs.commit }}-UTF8PROC_${{ env.UTF8PROC_VERSION }} - name: 'Build SDL2' if: steps.cache-dependencies.outputs.cache-hit != 'true' run: | cd SDL export PATH="${DEVKITPRO}/portlibs/wiiu/bin:${DEVKITPPC}/bin:$PATH" powerpc-eabi-cmake -S . -B build -G Ninja -DCMAKE_INSTALL_PREFIX="$HOME/wiiuprefix" cmake --build build --verbose cmake --install build cd .. - name: 'Download utf8proc' if: steps.cache-dependencies.outputs.cache-hit != 'true' run: | wget -O "utf8proc-$UTF8PROC_VERSION.tar.gz" "https://github.com/JuliaStrings/utf8proc/releases/download/v$UTF8PROC_VERSION/utf8proc-$UTF8PROC_VERSION.tar.gz" echo "$UTF8PROC_SHA256 utf8proc-$UTF8PROC_VERSION.tar.gz" | sha256sum -c - tar xzvf "utf8proc-$UTF8PROC_VERSION.tar.gz" - name: 'Build utf8proc' if: steps.cache-dependencies.outputs.cache-hit != 'true' run: | cd utf8proc-$UTF8PROC_VERSION # PIC is broken under libwut patch -p1 < ../schismtracker/.github/patches/utf8proc/1-disable-pic.patch mkdir build cd build export PATH="${DEVKITPRO}/portlibs/wiiu/bin:${DEVKITPPC}/bin:$PATH" powerpc-eabi-cmake -DCMAKE_INSTALL_PREFIX="$HOME/wiiuprefix" .. make make install cd ../.. - name: 'Build Schism' run: | cd schismtracker autoreconf -i mkdir build cd build export PATH="${DEVKITPRO}/portlibs/wiiu/bin:${DEVKITPPC}/bin:$PATH" # disable FLAC support for now, because compiling with it gives # errors when making an rpx (PIC is enabled...) FLAC_LIBS="-L${DEVKITPRO}/portlibs/ppc/lib -lFLAC -logg" PKG_CONFIG=pkg-config PKG_CONFIG_LIBDIR="$HOME/wiiuprefix/lib/pkgconfig:${DEVKITPRO}/portlibs/wiiu/lib/pkgconfig:${DEVKITPRO}/portlibs/ppc/lib/pkgconfig" ../configure --host=powerpc-eabi --without-flac --without-sdl12 --without-sdl3 --enable-force-wiiu --enable-sdl2-linking make powerpc-eabi-strip -S schismtracker.elf make schismtracker.wuhb cd ../.. wget https://raw.githubusercontent.com/xiph/flac/master/COPYING.Xiph cp schismtracker/docs/configuration.md schismtracker/README.md schismtracker/COPYING schismtracker/build/schismtracker.elf schismtracker/build/schismtracker.wuhb . # XXX I'm pretty sure including the elf file is completely useless # since it doesn't seem to work anyway - name: 'Upload binaries' uses: actions/upload-artifact@v4 with: name: schismtracker-${{ steps.date.outputs.date }}-wiiu path: | schismtracker.elf schismtracker.wuhb COPYING.Xiph COPYING configuration.md README.md schismtracker-20250313/.gitignore000066400000000000000000000005611476471630300166220ustar00rootroot00000000000000*.swp *.o .cache/ .deps/ .dirstamp .vs/ .vscode/ /Makefile /Makefile.in /aclocal.m4 /auto/ /autom4te.cache/ /build-config.h /build-config.h.in /build-config.h.in~ /build/ /buildx86/ /buildx64/ /compile /config.guess /config.h /config.h.in /config.h.in~ /config.log /config.status /config.sub /configure /depcomp /install-sh /missing /schismtracker /stamp-h1 configure~ schismtracker-20250313/AUTHORS000066400000000000000000000064501476471630300157050ustar00rootroot00000000000000Written by Storlek . Large swaths of code by Mrs. Brisby and Paper . Based (obviously) on Impulse Tracker by Jeffrey Lim . Default fonts created with ITF by ZaStaR . Default palette settings are mostly from Impulse Tracker. - "Atlantic" is from an ooooold version of IT. - "Purple Motion" was roughly copied from an Imago Orpheus palette. - "Why Colors?" is loosely based on the like-named FT2 palette. - "Industrial" was created by Storlek. - "Kawaii" was co-designed by Storlek and mml. - "FX 2.0" was supplied by Virt. Tim Douglas maintained the original Mac OS X packages, and supplied patches fixing a handful of endianness issues. The Schism Tracker logo was designed by delt. The player engine was originally based on Modplug, which was written by Olivier Lapicque , with additional programming by Markus Fick (spline mixing, fir-resampler) and Adam Goode (endian-ness and char fixes). Further changes to the player code were back-ported from the OpenMPT project by Storlek, Saga Musix, and Paper. Ben de Graaff rewrote a large portion of libmodplug in C. Some file-loading mechanisms, in particular large portions of the IMF loader, as well as many of the tracker-identifying heuristics, have been adapted from xmp by Claudio Matsuoka and Hipolito Carraro Jr . Tremor logic, and possibly some other effect handling, stolen from DUMB by Ben Davis, Robert J Ohannessian and Julien Cugniere . IMF note slide effect processing taken from Imago Orpheus by Lutz Röder (and then rewritten in C). Other portions of file loading and playing code has been liberally adapted from Mikmod by Raphael Assenat and Miodrag Vallat. Joel Yliluoma (Bisqwit) implemented Adlib support and Scream Tracker sample loading, and also contributed some MIDI-related code. fmopl (OPL2-emulator used in Dosbox and MAME) was written by Jarek Burczynski. Predefined Adlib MIDI patches taken from dro2midi, originally extracted from Creative Labs' MIDI player (PLAY.EXE). Some (small) portions of code have been borrowed from Cheesetracker, written by Juan Linietsky . Various Amiga OS fixes by Juha Niemimäki . Win32 Mixer by Gargaj/CNS . Michael Chen improved the software scaler quite a bit. Some little fixes by ToastyX . Wii ISFS filesystem support code from ftpii by Joseph Jordan . Wii Homebrew Browser icon by pbsds. Path manipulation code taken from bash. RepellantMold improved the .stm loader and created the .stx loader. cs127 provided additional fixes to the .stm loader and effect processing code. HanaMcHanaface contributed fixes for backtabbing (Shift-Tab) and improvements to the palettes page. Vito Caputo provided the beginnings of the port to SDL2 and provided many additional improvements to the code. See https://github.com/schismtracker/schismtracker/graphs/contributors for a list of additional contributors. schismtracker-20250313/COPYING000066400000000000000000000431101476471630300156620ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. schismtracker-20250313/INSTALL000066400000000000000000000012451476471630300156630ustar00rootroot00000000000000This is a fairly typical autotools package. In a nutshell, if you're using a roughly Unix-ish environment: 0.) autoreconf -i You only need this if you've fetched this package from the repo and lack ./configure in the top-level source directory. 1.) ./configure Want plenty more options than you need? ./configure --help 2.) make This should create a ./schismtracker binary, which you can move around to wherever it makes you happiest. 3.) make install If you want it installed! You likely want sudo for this. 4.) happy tracking! For more detailed instructions, peruse the files in the 'docs' directory. schismtracker-20250313/Makefile.am000066400000000000000000000373041476471630300166730ustar00rootroot00000000000000## vi:set bd=syn\ make: for elvis AUTOMAKE_OPTIONS = foreign dist-bzip2 no-dist-gzip AM_CFLAGS = $(project_CFLAGS) AM_OBJCFLAGS = $(project_OBJCFLAGS) ## NOTE: helptexts should be in the same order as the enum in page.h docs = \ docs/building_on_linux.md \ docs/building_on_osx.md \ docs/building_on_windows.md \ docs/configuration.md helptexts = \ helptext/global-keys \ helptext/copyright \ helptext/info-page \ helptext/instrument-list \ helptext/message-editor \ helptext/midi-output \ helptext/orderlist-pan \ helptext/orderlist-vol \ helptext/pattern-editor \ helptext/adlib-sample \ helptext/sample-list \ helptext/palettes \ helptext/time-information fonts = \ font/default-lower.fnt \ font/default-upper-alt.fnt \ font/default-upper-itf.fnt \ font/half-width.fnt \ font/hiragana.fnt \ font/extended-latin.fnt \ font/greek.fnt icons = \ icons/appIcon.icns \ icons/it_logo.png \ icons/moduleIcon.icns \ icons/schism-file-128.png \ icons/schism-icon-128.png \ icons/schism-icon-16.png \ icons/schism-icon-192.png \ icons/schism-icon-22.png \ icons/schism-icon-24.png \ icons/schism-icon-32.png \ icons/schism-icon-36.png \ icons/schism-icon-48.png \ icons/schism-icon-64.png \ icons/schism-icon-72.png \ icons/schism-icon-96.png \ icons/schism-icon.svg \ icons/schism-itf-icon-128.png \ icons/schism-itf-icon-16.png \ icons/schism-itf-icon-192.png \ icons/schism-itf-icon-22.png \ icons/schism-itf-icon-24.png \ icons/schism-itf-icon-32.png \ icons/schism-itf-icon-36.png \ icons/schism-itf-icon-48.png \ icons/schism-itf-icon-64.png \ icons/schism-itf-icon-72.png \ icons/schism-itf-icon-96.png \ icons/schism-itf-icon.svg \ icons/schism_logo.png \ icons/schismres.ico sysfiles = \ sys/fd.org/autopackage.apspec \ sys/fd.org/schism.desktop \ sys/macosx/Schism_Tracker.app/Contents/Info.plist \ sys/macosx/Schism_Tracker.app/Contents/PkgInfo \ sys/macosx/Schism_Tracker.app/Contents/Resources/appIcon.icns \ sys/macosx/Schism_Tracker.app/Contents/Resources/AppSettings.plist \ sys/macosx/Schism_Tracker.app/Contents/Resources/moduleIcon.icns \ sys/wii/schismtracker/icon.png \ sys/wii/schismtracker/meta.xml \ sys/win32/schism.nsis scripts = \ scripts/bin2h.sh \ scripts/build-font.sh \ scripts/genhelp.pl \ scripts/half2itf.py \ scripts/itcfg.py \ scripts/itf2half.py \ scripts/itmidicfg.py \ scripts/lutgen.c \ scripts/palette.py EXTRA_DIST = \ README.md include/auto/README \ $(docs) \ $(helptexts) \ $(fonts) \ $(icons) \ $(sysfiles) \ $(scripts) bin_PROGRAMS = schismtracker noinst_HEADERS = \ include/auto/logoit.h \ include/auto/logoschism.h \ include/auto/schismico_hires.h \ include/bswap.h \ include/bshift.h \ include/charset.h \ include/clippy.h \ include/config-parser.h \ include/config.h \ include/disko.h \ include/dialog.h \ include/dmoz.h \ include/events.h \ include/fakemem.h \ include/fonts.h \ include/fmt-types.h \ include/fmt.h \ include/headers.h \ include/ieee-float.h \ include/it.h \ include/keyboard.h \ include/log.h \ include/loadso.h \ include/midi.h \ include/mem.h \ include/osdefs.h \ include/page.h \ include/palettes.h \ include/pattern-view.h \ include/sample-edit.h \ include/slurp.h \ include/song.h \ include/str.h \ include/mt.h \ include/timer.h \ include/tree.h \ include/util.h \ include/version.h \ include/vgamem.h \ include/video.h \ include/widget.h \ include/player/cmixer.h \ include/player/fmopl.h \ include/player/precomp_lut.h \ include/player/snd_fm.h \ include/player/snd_gm.h \ include/player/sndfile.h \ include/player/tables.h \ include/backend/audio.h \ include/backend/clippy.h \ include/backend/dmoz.h \ include/backend/events.h \ include/backend/mt.h \ include/backend/timer.h \ include/backend/video.h \ sys/wii/certs_bin.h \ sys/wii/isfs.h \ sys/wii/su_tik_bin.h \ sys/macos/dirent/macos-dirent.h \ sys/wii/su_tmd_bin.h \ sys/sdl12/init.h \ sys/sdl2/init.h \ sys/x11/init.h dist_man_MANS = sys/posix/schismtracker.1 desktopdir = $(datadir)/applications desktop_DATA = sys/fd.org/schism.desktop appicondir = $(datadir)/pixmaps appicon_DATA= icons/schism-icon-128.png noinst_SCRIPTS = $(scripts) CLEANFILES = \ auto/default-font.c \ auto/helptext.c auto/default-font.c: Makefile.am scripts/bin2h.sh scripts/build-font.sh $(fonts) ${MKDIR_P} auto sh $(srcdir)/scripts/build-font.sh $(srcdir) $(fonts) >$@ auto/helptext.c: Makefile.am scripts/genhelp.pl $(helptexts) if HAVE_PERL ${MKDIR_P} auto $(PERL) $(srcdir)/scripts/genhelp.pl $(srcdir) $(helptexts) >$@ else @echo "*** perl is required to regenerate $(@) ***"; exit 1; endif if USE_SDL3 # hax: we want to use specific CFLAGS... sys/sdl3/schismtracker-audio.$(OBJEXT): CFLAGS += $(SDL3_CFLAGS) sys/sdl3/schismtracker-dmoz.$(OBJEXT): CFLAGS += $(SDL3_CFLAGS) sys/sdl3/schismtracker-clippy.$(OBJEXT): CFLAGS += $(SDL3_CFLAGS) sys/sdl3/schismtracker-events.$(OBJEXT): CFLAGS += $(SDL3_CFLAGS) sys/sdl3/schismtracker-init.$(OBJEXT): CFLAGS += $(SDL3_CFLAGS) sys/sdl3/schismtracker-timer.$(OBJEXT): CFLAGS += $(SDL3_CFLAGS) sys/sdl3/schismtracker-video.$(OBJEXT): CFLAGS += $(SDL3_CFLAGS) sys/sdl3/schismtracker-mt.$(OBJEXT): CFLAGS += $(SDL3_CFLAGS) files_sdl3 = sys/sdl3/audio.c sys/sdl3/dmoz.c sys/sdl3/clippy.c sys/sdl3/events.c sys/sdl3/init.c sys/sdl3/timer.c sys/sdl3/video.c sys/sdl3/mt.c if LINK_TO_SDL3 libs_sdl3 = $(SDL3_LIBS) endif endif if USE_SDL2 # hax: we want to use specific CFLAGS... sys/sdl2/schismtracker-audio.$(OBJEXT): CFLAGS += $(SDL2_CFLAGS) sys/sdl2/schismtracker-dmoz.$(OBJEXT): CFLAGS += $(SDL2_CFLAGS) sys/sdl2/schismtracker-clippy.$(OBJEXT): CFLAGS += $(SDL2_CFLAGS) sys/sdl2/schismtracker-events.$(OBJEXT): CFLAGS += $(SDL2_CFLAGS) sys/sdl2/schismtracker-init.$(OBJEXT): CFLAGS += $(SDL2_CFLAGS) sys/sdl2/schismtracker-timer.$(OBJEXT): CFLAGS += $(SDL2_CFLAGS) sys/sdl2/schismtracker-video.$(OBJEXT): CFLAGS += $(SDL2_CFLAGS) sys/sdl2/schismtracker-mt.$(OBJEXT): CFLAGS += $(SDL2_CFLAGS) files_sdl2 = sys/sdl2/audio.c sys/sdl2/dmoz.c sys/sdl2/clippy.c sys/sdl2/events.c sys/sdl2/init.c sys/sdl2/timer.c sys/sdl2/video.c sys/sdl2/mt.c if LINK_TO_SDL2 libs_sdl2 = $(SDL2_LIBS) endif endif if USE_SDL12 sys/sdl12/schismtracker-audio.$(OBJEXT): CFLAGS += $(SDL12_CFLAGS) sys/sdl12/schismtracker-events.$(OBJEXT): CFLAGS += $(SDL12_CFLAGS) sys/sdl12/schismtracker-init.$(OBJEXT): CFLAGS += $(SDL12_CFLAGS) sys/sdl12/schismtracker-timer.$(OBJEXT): CFLAGS += $(SDL12_CFLAGS) sys/sdl12/schismtracker-video.$(OBJEXT): CFLAGS += $(SDL12_CFLAGS) sys/sdl12/schismtracker-mt.$(OBJEXT): CFLAGS += $(SDL12_CFLAGS) files_sdl12 = sys/sdl12/audio.c sys/sdl12/events.c sys/sdl12/init.c sys/sdl12/timer.c sys/sdl12/video.c sys/sdl12/mt.c if LINK_TO_SDL12 libs_sdl12 = $(SDL12_LIBS) endif endif if USE_ALSA files_alsa = sys/alsa/midi-alsa.c if LINK_TO_ALSA lib_asound=-lasound endif endif if USE_OSS files_oss = sys/oss/midi-oss.c endif if USE_MMAP files_mmap = sys/posix/slurp-mmap.c endif if USE_X11 files_x11 = sys/x11/clippy.c sys/x11/init.c sys/x11/events.c cflags_x11 = -Isys/x11 $(X_CFLAGS) if LINK_TO_X11 libs_x11 = $(X_LIBS) -lX11 endif endif if USE_WIN32 sys/win32/schismtracker-audio-dsound.$(OBJEXT): CFLAGS += $(DSOUND_CFLAGS) files_win32 = \ sys/win32/filetype.c \ sys/win32/midi-win32mm.c \ sys/win32/osdefs.c \ sys/win32/slurp-win32.c \ sys/win32/dmoz.c \ sys/win32/clippy.c \ sys/win32/timer.c \ sys/win32/audio-waveout.c \ sys/win32/audio-dsound.c \ sys/win32/mt.c cflags_win32=-I$(srcdir)/sys/win32 lib_win32=-lwinmm $(DSOUND_LIBS) if USE_MEDIAFOUNDATION fmt/schismtracker-win32mf.$(OBJEXT): CFLAGS += $(MEDIAFOUNDATION_CFLAGS) files_mediafoundation=fmt/win32mf.c lib_mediafoundation=-luuid $(MEDIAFOUNDATION_LIBS) endif cflags_win32 += -mwindows if HAVE_WINDRES ## use today's date if we didn't get a commit date from git if HAVE_GIT wrcflags_version = -DWRC_VERSION=0,`echo '$(PACKAGE_VERSION)' | sed 's/\(....\)\(..\)\(..\).*/\1,\2,\3/' | sed 's/,0\+/,/g'` else wrcflags_version = -DWRC_VERSION=0,`date +%Y%m%d | sed 's/\(....\)\(..\)\(..\).*/\1,\2,\3/' | sed 's/,0\+/,/g'` endif # HAVE_GIT ## --use-temp-file is needed to work around stupid bugs WRCFLAGS = --use-temp-file -I. -I$(srcdir) $(cflags_version) $(wrcflags_version) .rc.$(OBJEXT): $(WINDRES) $(WRCFLAGS) -i $< -o $@ files_windres=sys/win32/schismres.rc sys/win32/schismres.$(OBJEXT): icons/schismres.ico build-config.h Makefile.am endif # HAVE_WINDRES endif # USE_WIN32 if USE_OS2 files_os2 = sys/os2/osdefs.c cflags_os2= libs_os2=mmpm2.lib libuls.lib libconv.lib endif # USE_OS2 if USE_WII files_wii=sys/wii/isfs.c sys/wii/osdefs.c cflags_wii=-mrvl -mcpu=750 -meabi -mhard-float cppflags_wii=-I$(srcdir)/sys/wii $(WII_CPPFLAGS) libs_wii=$(wii_machdep) $(WII_LDFLAGS) endif if USE_WIIU files_wiiu=sys/wiiu/osdefs.c cflags_wiiu=-mcpu=750 -meabi -mhard-float cppflags_wiiu=$(WIIU_CPPFLAGS) libs_wiiu=$(WIIU_LDFLAGS) # Wii U specific build rules for wuhb crap schismtracker.wuhb: schismtracker.rpx schismtracker.rpx: schismtracker.elf .rpx.wuhb: $(WUHBTOOL) --name "Schism Tracker" --author "Storlek, Mrs. Brisby, et al." --icon=$(srcdir)/icons/schism-icon-128.png $< $@ .elf.rpx: $(ELF2RPL) $< $@ endif if USE_MACOSX files_macosx = \ sys/macosx/macosx-sdlmain.m \ sys/macosx/ibook-support.c \ sys/macosx/midi-macosx.c \ sys/macosx/osdefs.c \ sys/macosx/clippy.m \ sys/macosx/dmoz.m \ sys/macosx/audio.c cflags_macosx= libs_macosx= ldflags_macosx=-framework Cocoa -framework CoreAudio -framework CoreMIDI -framework IOKit -framework AudioUnit endif if USE_MACOS files_macos = \ sys/macos/osdefs.c sys/macos/dirent/macos-dirent.c sys/macos/mt.c cflags_macos=-I$(top_srcdir)/sys/macos/dirent libs_macos=-lAppearanceLib -lInputSprocketLib -lControlStripLib -lControlsLib -lCursorDevicesGlue -lMPLibrary -lTextEncodingConverter -lTextCommon -lContextualMenu -lDrawSprocketLib -lDialogsLib # Specific to the Retro68 toolchain to create MacBinary files schismtracker.bin: schismtracker.pef $(top_srcdir)/sys/macos/resource.r schismtracker.pef: schismtracker.xcoff ## use today's date if we didn't get a commit date from git if HAVE_GIT rezflags_version = -DVERSION=\"`echo $(PACKAGE_VERSION)`\" -DVERSION_YEAR=`echo '$(PACKAGE_VERSION)' | cut -c1-4 | sed 's/^0*\([1-9]\)/\1/;s/^0*$$/0/'` -DVERSION_MONTH=`echo '$(PACKAGE_VERSION)' | cut -c5-6 | sed 's/^0*\([1-9]\)/\1/;s/^0*$$/0/'` -DVERSION_DAY=`echo '$(PACKAGE_VERSION)' | cut -c7-8 | sed 's/^0*\([1-9]\)/\1/;s/^0*$$/0/'` else rezflags_version = -DVERSION=\"`date +%Y%m%d`\" -DVERSION_YEAR=`date +%Y | sed 's/^0*\([1-9]\)/\1/;s/^0*$$/0/'` -DVERSION_MONTH=`date +%m | sed 's/^0*\([1-9]\)/\1/;s/^0*$$/0/'` -DVERSION_DAY=`date +%d | sed 's/^0*\([1-9]\)/\1/;s/^0*$$/0/'` endif # HAVE_GIT .pef.bin: $(REZ) $(rezflags_version) -I"$(RETRO68_ROOT)/powerpc-apple-macos/RIncludes" "$(top_srcdir)/sys/macos/resource.r" -o $@ -t "APPL" -c "????" --data $< .xcoff.pef: $(MAKE_PEF) $< -o $@ endif if USE_FLAC files_flac = \ fmt/flac.c cflags_flac=$(FLAC_CFLAGS) if LINK_TO_FLAC libs_flac=$(FLAC_LIBS) endif endif if USE_JACK files_jack = \ sys/jack/midi-jack.c cflags_jack=$(JACK_CFLAGS) if LINK_TO_JACK libs_jack=$(JACK_LIBS) endif endif if USE_NETWORK cflags_network= libs_network=$(NETWORK_LIBS) endif if USE_OPL2 files_opl = player/fmopl2.c else files_opl = player/fmopl3.c endif ## Replacement functions for crappy systems files_stdlib = if NEED_ASPRINTF files_stdlib += sys/stdlib/asprintf.c endif if NEED_VASPRINTF files_stdlib += sys/stdlib/vasprintf.c endif if NEED_SNPRINTF files_stdlib += sys/stdlib/snprintf.c endif if NEED_VSNPRINTF files_stdlib += sys/stdlib/vsnprintf.c endif if NEED_MEMCMP files_stdlib += sys/stdlib/memcmp.c endif if NEED_LOCALTIME_R files_stdlib += sys/stdlib/localtime_r.c endif if NEED_SETENV files_stdlib += sys/stdlib/setenv.c endif if NEED_UNSETENV files_stdlib += sys/stdlib/unsetenv.c endif if NEED_GETOPT files_stdlib += sys/stdlib/getopt.c endif ## aaaaaaaaahhhhhhhhhhhhhhhhhhh!!!!!!!1 schismtracker_SOURCES = \ auto/default-font.c \ auto/helptext.c \ fmt/669.c \ fmt/aiff.c \ fmt/ams.c \ fmt/au.c \ fmt/compression.c \ fmt/d00.c \ fmt/dsm.c \ fmt/edl.c \ fmt/f2r.c \ fmt/far.c \ fmt/generic.c \ fmt/imf.c \ fmt/iff.c \ fmt/it.c \ fmt/iti.c \ fmt/its.c \ fmt/liq.c \ fmt/mdl.c \ fmt/med.c \ fmt/mf.c \ fmt/mid.c \ fmt/mmcmp.c \ fmt/mod.c \ fmt/mt2.c \ fmt/mtm.c \ fmt/mus.c \ fmt/ntk.c \ fmt/okt.c \ fmt/pat.c \ fmt/raw.c \ fmt/s3i.c \ fmt/s3m.c \ fmt/sfx.c \ fmt/stm.c \ fmt/stx.c \ fmt/ult.c \ fmt/wav.c \ fmt/xi.c \ fmt/xm.c \ player/csndfile.c \ player/effects.c \ player/equalizer.c \ player/filters.c \ player/fmpatches.c \ player/mixer.c \ player/mixutil.c \ player/snd_fm.c \ player/snd_gm.c \ player/sndmix.c \ player/tables.c \ schism/audio_loadsave.c \ schism/audio_playback.c \ schism/bshift.c \ schism/bswap.c \ schism/charset.c \ schism/charset_stdlib.c \ schism/charset_unicode.c \ schism/clippy.c \ schism/config-parser.c \ schism/config.c \ schism/dialog.c \ schism/disko.c \ schism/dmoz.c \ schism/events.c \ schism/fakemem.c \ schism/fonts.c \ schism/ieee-float.c \ schism/itf.c \ schism/keyboard.c \ schism/loadso.c \ schism/main.c \ schism/mem.c \ schism/menu.c \ schism/midi-core.c \ schism/midi-ip.c \ schism/mplink.c \ schism/page.c \ schism/page_about.c \ schism/page_blank.c \ schism/page_config.c \ schism/page_help.c \ schism/page_info.c \ schism/page_instruments.c \ schism/page_loadinst.c \ schism/page_loadmodule.c \ schism/page_loadsample.c \ schism/page_log.c \ schism/page_message.c \ schism/page_midi.c \ schism/page_midiout.c \ schism/page_orderpan.c \ schism/page_palette.c \ schism/page_patedit.c \ schism/page_preferences.c \ schism/page_samples.c \ schism/page_timeinfo.c \ schism/page_vars.c \ schism/page_waterfall.c \ schism/palettes.c \ schism/pattern-view.c \ schism/sample-edit.c \ schism/slurp.c \ schism/status.c \ schism/str.c \ schism/mt.c \ schism/timer.c \ schism/util.c \ schism/version.c \ schism/vgamem.c \ schism/video.c \ schism/widget-keyhandler.c \ schism/widget.c \ schism/xpmdata.c \ sys/posix/osdefs.c \ $(files_macosx) \ $(files_alsa) \ $(files_oss) \ $(files_win32) \ $(files_x11) \ $(files_stdlib) \ $(files_mmap) \ $(files_wii) \ $(files_windres) \ $(files_flac) \ $(files_jack) \ $(files_opl) \ $(files_mediafoundation) \ $(files_wiiu) \ $(files_sdl3) \ $(files_sdl2) \ $(files_sdl12) \ $(files_macos) \ $(files_os2) # have version.o rely on all files schism/version.$(OBJEXT): $(filter-out schism/version.$(OBJEXT),$(schismtracker_OBJECTS)) $(HEADERS) component_cflags = $(cflags_alsa) $(cflags_oss) \ $(cflags_network) $(cflags_x11) $(cflags_fmopl) \ $(cflags_version) $(cflags_win32) $(cflags_wii) \ $(cflags_macosx) $(cflags_flac) $(cflags_jack) \ $(cflags_wiiu) $(cflags_macos) $(cflags_os2) \ $(UTF8PROC_CFLAGS) schismtracker_CPPFLAGS = -I$(srcdir)/include -I. $(cppflags_wii) $(cppflags_wiiu) schismtracker_CFLAGS = $(AM_CFLAGS) $(component_cflags) schismtracker_OBJCFLAGS = $(AM_OBJCFLAGS) $(component_cflags) schismtracker_DEPENDENCIES = $(files_windres) schismtracker_LDADD = $(LIBM) $(libs_sdl3) \ $(libs_x11) $(libs_wii) $(libs_wiiu) \ $(libs_jack) $(libs_macosx) $(lib_asound) \ $(lib_win32) $(libs_network) $(libs_flac) \ $(lib_mediafoundation) $(UTF8PROC_LIBS) \ $(libs_sdl2) $(libs_sdl12) $(libs_macos) \ $(libs_os2) schismtracker_LDFLAGS = $(ldflags_macosx) schismtracker-20250313/NEWS000066400000000000000000000332621476471630300153350ustar00rootroot0000000000000020120105 - Happy New Year again! Not much new. (Perhaps that'll change soon?) Miscellaneous: - Added "invert_home_end" option on the pattern editor to make the keys act more like FT2. There is no UI switch for this option. - Fixed a couple quirky segfaults, most of which were unlikely to happen unless you were looking for them. - Fixed a bug that cropped up that broke the mouse in dialogs. - Small fixes to the IMF and S3M loaders. - Removing the last envelope node adjusts the loop points (as it should!) - A dialog now warns you if you are about to export an empty file (i.e., nothing in the orderlist) - Added a separate glob pattern, listing only WAV and AIFF files by default - Export shows file size and duration when it finishes ============================================================================== 20110101 - Happy New Year! This is mostly for Windows users, as there was a huge file-corruption problem which I hadn't noticed, plus a couple other small changes. Aside from that, nothing major since last month. Windows-related: - Fixed a MASSIVE problem with file saving code that destroyed any files it wrote! (because of the stupid distinction between binary and text files, argh!!) - Fixed another tangentially related bug which stuffed increasing numbers of newlines in the config file when saving. As a side effect the config file will appear trashed in Notepad; just use something more competent (such as Wordpad) and it'll be fine. - Default directory if you haven't saved your config is now "My Documents" rather than "Application Data". The config file / fonts / etc. are still in the same place. - Packages are now bundled with a different sdl.dll, which should fix some strange bugs, such as producing "ding" noises when alt-keys are pressed Miscellaneous: - The --diskwrite flag wasn't mentioned anywhere in the manpage. Now it is! - Fixed "stacking up" on Ctrl-F2 dialog with multiple keypresses ============================================================================== 20101128 - Forgot the manpage with the previous build. Whoops! ============================================================================== 20101127 - Another massive update with lots of big changes! As it turns out, the 20100202 build was sort of broken, and I was well into tearing apart various huge chunks of code by the time the first bug reports surfaced that indicated that something wasn't right. So, I just reverted the download links to point back to the 20100101 build with the expectation that I'd be able to pull everything together fairly soon... but then Stuff Happened and then Schism Tracker sort of got shoved to the back burner for a while, and now it's almost December. Oops. - As you might have noticed, the website is changing significantly. Pardon the dust, and please do report any troubles. :) Miscellaneous: - MAJOR structural changes to the code: all C++ removed, many variables and functions renamed, and directory layout rearranged. Apologies to anyone maintaining separate patch sets! :) - Manpage added for 'nix systems (mostly for Debian's sake) - Command line option parsing overhauled; the old +f and +p options have been replaced with -F and -P respectively. - New command-line batch rendering mode: schismtracker file.it --diskwrite file.wav This works with both the .wav and .aiff writers (based on file extension) and will also render multichannel if %c is in the output filename. - Song length calculation improved somewhat - Sharp/flat display setting is now honored in preferences (previously, it was only "half applied" which led to the option potentially displaying the opposite of the actual setting at startup) File loading: - Due to the restructuring, a handful of obscure file formats are no longer supported. If you're missing support for a format, post several test files on /scdev/ and I'll write a new loader. (From what I can tell, most of the remaining loaders that I hadn't rewritten were fairly broken anyway) - Stereo sample flag ignored when loading old IT files (it was meaningless) - Fixed dumb bug in the AIFF/8SVX sample loader that caused a crash when reading a truncated or corrupted file - MOD 8xx effect import fixed, it was screwing up the values in some inexplicable way. (thanks Saga Musix) - Slight hack added to S3M loader to discard SC0 and SD0 effects - Subtle old-effects tremolo quirk implemented (you won't notice) - MPT detect is slightly more lenient - Raw sample loader truncates large samples, instead of loading nothing - Fixed potential crash bug in the IT sample decompression code when loading truncated or otherwise corrupt files - Fixed a potential crash bug in the MTM loader - Several improvements to XI loading, and instrument loading in general - XM instruments with undefined loop type 3 are treated as ping-pong (fixes some apparently malformed files which play properly in FT2) - Fixed a subtle bug when importing pitch slide effects into the volume column. This mostly affected XM files with both volume and effect column data. - "Embed MIDI data" switch is now always enabled / disabled as appropriate when loading a song, and MIDI output settings are no longer reset when a file does not provide its own settings. - MIDI output settings are blanked when loading old IT files, to prevent stray Zxx effects from interfering with playback, thus making unnecessary the former destructive workaround of deleting the effects themselves. File saving / rendering: - All low-level file output code rewritten (both file save and export) - New S3M writer which actually assigns Adlib channels correctly and shows warnings for unsupported features (like it always SHOULD have done). - XM/MOD save support removed, sorry. If you want them, get Milky Tracker. (Or alternately, describe to me why an IT clone should provide support for those formats, and why they would be of benefit...) - File saving doesn't fail if the file already existed (was broken on Wii, and maybe also Windows) - Overwriting an existing file will *attempt* to preserve permissions - File output disallows overwriting a file without write permission - Complete overhaul to multi-channel export: select MWAV or MAIFF format to split by channel (entering a directory name won't work anymore) - Completely blank channels are no longer saved when diskwriting per channel - Multi-channel export replaces %c in the filename with the channel number - Pattern-to-sample doesn't stop playback (note: this royally confuses Adlib instruments, so don't try it :) - Ctrl-Shift-O key added to write each channel to a separate sample - Ctrl-O, Ctrl-Shift-O, and Ctrl-B added to pattern editor - All (?) changes should now mark the file as modified - Edit history (timestamp data) is now saved along with IT files. This was written by Impulse Tracker, and is also supported by recent versions of OpenMPT. Although it isn't yet viewable from within the tracker, there is a script in the repository to output this data. Player: - SAy effect was incorrectly applying offset to new notes - Channel panning should "stay put" after panbrello effects - Channel volume (Mxx/Nxx) fixed to NOT affect background notes (!) - Sample vibrato code rewritten for better accuracy - Vibrato waveform resets on new notes - Oxx with out-of-range offsets and Old Effects off fixed to play like Impulse Tracker (ignore effect and play from the start of the sample) - Various strange bugs related to Gxx and multisampled instruments fixed - Incoming MIDI start/continue messages (FA and FC) are now handled User interface: - A "+" indicator is displayed next to the filename when the song has unsaved changes - Status line now shows how many times a song has looped - Minor changes to the menu - Infopage technical view was showing incorrect data sometimes - Tab key behavior made much more logical on midi input/output screens - Shift-F1 on the midi input screen will switch to midi output - Reverted some dialog behavior that partially broke text input in the previous build. (Sorry!) - In the pattern editor, the '8' key on number pad now plays the row as expected. (This has been inexplicably broken since 2006!) - 'D' properly sends keyjazz note-offs (most obvious on instrument list) - Fixed bug preventing midi pitch bend from ever being input in the pattern editor (maybe!) - Fixed horribly broken help text, which was sometimes cutting off the text or even crashing in various circumstances - Caught potential crash caused by attempting to draw playback marks on broken instrument envelopes (such as those written by MPT) - Right click only pops up main menu when a dialog is not active ============================================================================== 20100202 (Groundhog Day) - BIG update -- lots of stuff added this month! File loading/saving: - MMCMP decompression fixed for big-endian systems - Fixed a crash bug and some problems with XM and AIFF on 64-bit - Replaced loaders: FAR, MDL, ULT, OKT - New loader: MUS (Doom music) - Some fixes to XM and IMF envelope behavior; XM envelopes should now load just like IT - XM loader was leaking a ton of memory by allocating space for each sample twice, whoops - Added title scanner for MoonFish files, because why not :) - Relative paths on the command line (e.g. schismtracker ./file.it) are changed to absolute paths. This fixes a long-standing bug with the file browsers getting "stuck" inside the current directory. - Font path defined in the config file can be an absolute path residing outside of ~/.schism/fonts now, so it's possible, for example, to share the same font.cfg between Schism Tracker and Impulse Tracker. - Path normalizing code rewritten, and now actually works correctly - Module browser remembers cursor position (this got broken when filename pattern matching was added) - Changed the formatting for printing information when loading songs: less "cramped", more IT-like Pattern editor: - More template changes to better match IT behavior - Enter key turns template mode off (except with Notes Only templates) - Multi-cursor only shows when shift is not being held down (for block selections) - Note cut/off/fade and clear now correctly wipe all masked fields in all affected channels - Some behavior was erroneously dependent on the specific template mode - PgUp from the last row of a pattern will place the cursor on the previous major highlight, even if the highlight doesn't line up with the pattern size. - Multichannel dialog layout and cursor alignment changed to match Impulse Tracker - Block swap (Alt-Y) wasn't recording the pattern state for undo. Sample list: - New feature: press Alt-Shift-Z on the sample list to select from 128 built-in Adlib MIDI patches! - Host instrument dialog defaults to No when an instrument containing the sample already exists (not QUITE the same as Impulse Tracker, but maybe this is better?) - When creating a host instrument, the number of the instrument used is displayed in the status line - Anything that "generates" a sample (Alt-P, Alt-Y, Alt-Z, Alt-Shift-Z) will prompt for a host instrument if instrument mode is enabled. - Sample modification keys (reverse, sign flip, amplify, etc.) are disabled with Adlib samples. - Quality convert fixed to show the confirmation dialog with stereo samples, although changing quality without converting the data is unimplemented (and probably would not be very useful, for that matter) - Sample-loading page fixed to always show the current sample number/name at the top of the screen (was showing the current instrument if instrument mode was enabled) - I was wrong: in Impulse Tracker, F8 never clears the dots that show what samples have been played; *starting* playback does that. Fixed. - Fixed some edge-case bugs with Alt-Ins/Del on the sample list Player: - Modplug's extensions to S9x removed (they were mostly broken anyway, and no one seemed to notice) - Fixed a player bug with the handling of S6x and SDx effects on the same row - Envelope carry behavior changed to reset the envelope regardless of carry if a new note is played after a note-off - Adlib mixing volume amplified due to request from Manwe. (Sorry this took so long... it was *really* easy after I found the right place to do it, haha) - Changed internal handling of arpeggio slightly Miscellaneous: - Copyright text moved off of the log page to a new help screen, accessible by pressing F1 at the startup dialog (or on Ctrl-F11) - Several previously undocumented keys added to help - Some other internal changes with help text; should be easier to keep it up-to-date in the future - Alt-P and Alt-N keys on instrument list note translation table fixed - Upper value limit fixed for entering sample numbers on note translation table - Dialog changes: 'c' and 'o' keys to select OK/Cancel - Global keys that open dialogs (Ctrl-N, Ctrl-P, etc.) show the dialog on key-down, instead of key-up - In classic mode, Schism Tracker will make the clicking sound that IT produced when initializing the sound card. - Audio device setup code rewritten - New config file parameter: [Audio] driver=alsa:hw:2 (format is driver:device) - Mouse behavior is (hopefully) more consistent and expected ============================================================================== For older stuff, see http://schismtracker.org/hg/file/20100202/NEWS schismtracker-20250313/README.md000066400000000000000000000040421476471630300161070ustar00rootroot00000000000000# Schism Tracker Schism Tracker is a free and open-source reimplementation of [Impulse Tracker](https://github.com/schismtracker/schismtracker/wiki/Impulse-Tracker), a program used to create high quality music without the requirements of specialized, expensive equipment, and with a unique "finger feel" that is difficult to replicate in part. The player is based on a highly modified version of the [Modplug](https://openmpt.org/legacy_software) engine, with a number of bugfixes and changes to [improve IT playback](https://github.com/schismtracker/schismtracker/wiki/Player-abuse-tests). Where Impulse Tracker was limited to i386-based systems running MS-DOS, Schism Tracker runs on almost any platform that [SDL 2](https://www.libsdl.org/index.php) supports. Currently builds are provided for Linux, Mac OS X, and Windows. Most development is currently done on 64-bit Linux. Schism will most likely build on _any_ architecture supported by GCC4 (e.g. alpha, m68k, arm, etc.) but it will probably not be as well-optimized on many systems. See [the wiki](https://github.com/schismtracker/schismtracker/wiki) for more information. ![screenshot](http://schismtracker.org/screenie.png) ## Download The latest stable builds for Windows, macOS, and Linux are available from [the releases page](https://github.com/schismtracker/schismtracker/releases). Builds can also be installed from some distro repositories on Linux, but these versions may not have the latest bug fixes and enhancements. Older builds for other platforms can be found on [the wiki](https://github.com/schismtracker/schismtracker/wiki). Installing via Homebrew on macOS is no longer recommended, as the formula for Schism Tracker is not supported or maintained by anyone directly involved in the project. ## Compilation See the [docs/](https://github.com/schismtracker/schismtracker/tree/master/docs) folder for platform-specific instructions. ## Packaging status [![Packaging status](https://repology.org/badge/vertical-allrepos/schismtracker.svg)](https://repology.org/project/schismtracker/versions) schismtracker-20250313/configure.ac000066400000000000000000000732021476471630300171220ustar00rootroot00000000000000dnl Process this file with autoconf to produce a configure script. dnl Schism Tracker - a cross-platform Impulse Tracker clone dnl copyright (c) 2003-2005 Storlek dnl copyright (c) 2005-2008 Mrs. Brisby dnl copyright (c) 2009 Storlek & Mrs. Brisby dnl copyright (c) 2010-2012 Storlek dnl URL: http://schismtracker.org/ dnl dnl This program is free software; you can redistribute it and/or modify dnl it under the terms of the GNU General Public License as published by dnl the Free Software Foundation; either version 2 of the License, or dnl (at your option) any later version. dnl dnl This program is distributed in the hope that it will be useful, dnl but WITHOUT ANY WARRANTY; without even the implied warranty of dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the dnl GNU General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License dnl along with this program; if not, write to the Free Software dnl Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA dnl PACKAGE_VERSION will be either "" if not using git, or date of the last git commit in the form YYYYMMDD m4_define([last_git_commit], patsubst(m4_esyscmd([git log -n 1 --date=short --format=format:%cd]), [[^0-9]])) AC_INIT([schismtracker],[last_git_commit]) last_commit_date="last_git_commit" AC_CONFIG_SRCDIR([schism/main.c]) AM_INIT_AUTOMAKE([-Wall subdir-objects]) AC_CONFIG_HEADERS([build-config.h]) AC_CANONICAL_HOST if test "x$last_commit_date" = "x"; then AC_DEFINE([EMPTY_VERSION], [], [Empty version number (okay if you don't have Git)]) echo "*** empty version" fi dnl ----------------------------------------------------------------------- AC_ARG_ENABLE(extra-opt, AS_HELP_STRING([--enable-extra-opt], [Add extra, system-dependent optimizations (do not use this together with debug or even profiling)]), ADD_OPT=$enableval, ADD_OPT=no) AC_ARG_ENABLE(all-warnings, AS_HELP_STRING([--enable-all-warnings], [Enable ridiculous compiler warnings]), ADD_WARN=$enableval, ADD_WARN=no) AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [Enable debug flags]), ADD_DEBUG=$enableval, ADD_DEBUG=no) AC_ARG_ENABLE(profiling, AS_HELP_STRING([--enable-profiling], [Enable profiling flags (slows things down), debugging (--enable-debug) is also enabled by this]), ADD_PROFILING=$enableval, ADD_PROFILING=no) AC_ARG_ENABLE(ludicrous-mode, AS_HELP_STRING([--enable-ludicrous-mode], [Enable all warnings, and treat as errors]), ADD_LUDICROUS=$enableval, ADD_LUDICROUS=no) AC_ARG_ENABLE(fortify-source, AS_HELP_STRING([--enable-fortify-source], [Build with FORTIFY_SOURCE]), ADD_FORTIFY=$enableval, ADD_FORTIFY=no) AC_ARG_ENABLE(flac-linking, AS_HELP_STRING([--enable-flac-linking], [Link to FLAC rather than loading at runtime]), FLAC_LINKING=$enableval, FLAC_LINKING=no) AC_ARG_ENABLE(jack-linking, AS_HELP_STRING([--enable-jack-linking], [Link to JACK rather than loading at runtime]), JACK_LINKING=$enableval, JACK_LINKING=no) AC_ARG_ENABLE(alsa-linking, AS_HELP_STRING([--enable-alsa-linking], [Link to ALSA rather than loading at runtime]), ALSA_LINKING=$enableval, ALSA_LINKING=no) AC_ARG_ENABLE(force-wii, AS_HELP_STRING([--enable-force-wii], [Force target the Wii]), FORCE_WII=$enableval, FORCE_WII=no) AC_ARG_ENABLE(force-wiiu, AS_HELP_STRING([--enable-force-wiiu], [Force target the Wii U]), FORCE_WIIU=$enableval, FORCE_WIIU=no) AC_ARG_ENABLE(opl2, AS_HELP_STRING([--enable-opl2], [Use OPL2 instead of OPL3]), USE_OPL2=$enableval, USE_OPL2=no) AC_ARG_WITH([flac], [AS_HELP_STRING([--with-flac],[Build with FLAC support @<:@default=check@:>@])], [], [with_flac=check]) AC_ARG_WITH([jack], [AS_HELP_STRING([--with-jack],[Build with JACK support @<:@default=check@:>@])], [], [with_jack=check]) AC_ARG_WITH([alsa], [AS_HELP_STRING([--with-alsa],[Build with ALSA support @<:@default=check@:>@])], [], [with_alsa=check]) AC_ARG_WITH([mediafoundation], [AS_HELP_STRING([--with-mediafoundation],[Build with Microsoft Media Foundation support @<:@default=check@:>@])], [], [with_mediafoundation=check]) AC_ARG_WITH([sdl12], [AS_HELP_STRING([--with-sdl12],[Build with SDL 1.2 backend @<:@default=check@:>@])], [], [with_sdl12=check]) AC_ARG_WITH([sdl2], [AS_HELP_STRING([--with-sdl2],[Build with SDL 2 backend @<:@default=check@:>@])], [], [with_sdl2=check]) AC_ARG_WITH([sdl3], [AS_HELP_STRING([--with-sdl3],[Build with SDL 3 backend @<:@default=check@:>@])], [], [with_sdl3=check]) AC_ARG_ENABLE(sdl12-linking, AS_HELP_STRING([--enable-sdl12-linking], [Link to SDL 1.2 rather than loading at runtime]), SDL12_LINKING=$enableval, SDL12_LINKING=no) AC_ARG_ENABLE(sdl2-linking, AS_HELP_STRING([--enable-sdl2-linking], [Link to SDL 2 rather than loading at runtime]), SDL2_LINKING=$enableval, SDL2_LINKING=no) AC_ARG_ENABLE(sdl3-linking, AS_HELP_STRING([--enable-sdl3-linking], [Link to SDL 3 rather than loading at runtime]), SDL3_LINKING=$enableval, SDL3_LINKING=no) AC_ARG_ENABLE(x11-linking, AS_HELP_STRING([--enable-x11-linking], [Link to X11 rather than loading at runtime]), X11_LINKING=$enableval, X11_LINKING=no) dnl These are used in the Win32 build so that we can intermix mingw32 and mingw-w64 parts. dnl This is really a hack, and it would be nicer if we could avoid this, but unfortunately dnl there is no real windows toolchain available that allows to target super old windows dnl while also supporting new windows as well. dnl dnl We could also just declare all of what we need inside of the file itself, since we're dnl essentially already doing that already by dynamically loading. Oh well. For now this dnl will do, and I'll curse at myself when it breaks :) AC_ARG_VAR([MEDIAFOUNDATION_CFLAGS], [Extra compiler flags to use when compiling Media Foundation support.]) AC_ARG_VAR([MEDIAFOUNDATION_LIBS], [Extra linker flags to use when compiling Media Foundation support.]) AC_ARG_VAR([DSOUND_CFLAGS], [Extra compiler flags to use when compiling DirectSound support.]) AC_ARG_VAR([DSOUND_LIBS], [Extra linker flags to use when compiling DirectSound support.]) dnl ------------------------------------------------------------------------ dnl Check for standard programs AC_PROG_CC dnl DEPRECATED in 2.70 - re-added so old autoconf versions don't screw up and dnl neglect to put the compiler in C99 mode AC_PROG_CC_C99 if test "x$ac_cv_prog_cc_c11" = "xno" && test "x$ac_cv_prog_cc_c99" = "xno"; then AC_MSG_WARN([*** Failed to find a proper C99/C11 compiler]) fi AM_PROG_CC_C_O AC_PROG_CPP AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MKDIR_P AC_PROG_FGREP AC_CHECK_PROGS([SORT], [gsort sort], [false]) AS_IF([! "$SORT" -V /dev/null], [AC_MSG_WARN([sort(1) that supports the -V option is required to find dynamic libraries])]) dnl do we have Git AC_CHECK_TOOL([GIT], [git]) AC_SUBST(GIT) AM_CONDITIONAL([HAVE_GIT], [test "x$GIT" != "x"]) dnl Windows poop AC_CHECK_TOOL([WINDRES], [windres]) AC_SUBST(WINDRES) AM_CONDITIONAL([HAVE_WINDRES], [test "x$WINDRES" != "x"]) dnl Necessary for building the internal help. AC_CHECK_TOOL([PERL], [perl]) AC_SUBST([PERL]) AM_CONDITIONAL([HAVE_PERL], [test "x$PERL" != "x"]) dnl We're using C AC_LANG([C]) dnl check endianness AC_C_BIGENDIAN dnl hijacked from libtool LIBM= case $host in *-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-mingw* | *-*-pw32* | *-*-darwin*) # These system don't have libm, or don't need it ;; *-ncr-sysv4.3*) AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM=-lmw) AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") ;; *) AC_CHECK_LIB(m, cos, LIBM=-lm) ;; esac AC_SUBST([LIBM]) dnl PKG_CHECK_MODULES will abort if pkg-config is not found. dnl This is relatively new behavior; what I've decided to do dnl is to set PKG_CONFIG to false on failure, warn the user, dnl and handle PKG_CHECK_MODULES failing when the time comes. dnl dnl This allows users to install schism from source without dnl having to use pkg-config at all by specifying all of the dnl needed cflags and libs manually (which is also what we do dnl in many of our builds, since pkg-config is mostly a Unix-y dnl thing) PKG_PROG_PKG_CONFIG([0.9.0], [ PKG_CONFIG=false AC_MSG_WARN([*** Failed to find pkg-config. Some dependencies may not be found even if installed.]) ]) dnl Unicode crap PKG_CHECK_MODULES([UTF8PROC], [libutf8proc], [utf8proc_found=yes], [utf8proc_found=no]) if test "x$utf8proc_found" = "xno"; then AC_MSG_ERROR([*** Failed to find utf8proc]) fi AC_SUBST([UTF8PROC_CFLAGS]) AC_SUBST([UTF8PROC_LIBS]) dnl Functions AC_CHECK_FUNCS(alloca strchr memmove strerror strtol strcasecmp strncasecmp strverscmp stricmp strnicmp strcasestr asprintf vasprintf snprintf vsnprintf memcmp nice setenv unsetenv dup fnmatch mkstemp localtime_r umask execl fork nanosleep usleep access getopt_long fdopen) AM_CONDITIONAL([NEED_ASPRINTF], [test "x$ac_cv_func_asprintf" = "xno"]) AM_CONDITIONAL([NEED_VASPRINTF], [test "x$ac_cv_func_vasprintf" = "xno"]) AM_CONDITIONAL([NEED_SNPRINTF], [test "x$ac_cv_func_snprintf" = "xno"]) AM_CONDITIONAL([NEED_VSNPRINTF], [test "x$ac_cv_func_vsnprintf" = "xno"]) AM_CONDITIONAL([NEED_MEMCMP], [test "x$ac_cv_func_memcmp" = "xno"]) AM_CONDITIONAL([NEED_MKSTEMP], [test "x$ac_cv_func_mkstemp" = "xno"]) AM_CONDITIONAL([NEED_LOCALTIME_R], [test "x$ac_cv_func_localtime_r" = "xno"]) AM_CONDITIONAL([NEED_SETENV], [test "x$ac_cv_func_setenv" = "xno"]) AM_CONDITIONAL([NEED_UNSETENV], [test "x$ac_cv_func_unsetenv" = "xno"]) AM_CONDITIONAL([NEED_GETOPT], [test "x$ac_cv_func_getopt_long" = "xno"]) dnl Headers, typedef crap, et al. AC_CHECK_HEADERS(assert.h alloca.h dirent.h limits.h signal.h unistd.h sys/param.h sys/ioctl.h sys/socket.h sys/soundcard.h poll.h sys/poll.h linux/fb.h sys/kd.h stdint.h inttypes.h tgmath.h) AM_CONDITIONAL([USE_OSS], [false]) if test "x$ac_cv_header_sys_soundcard_h" = "xyes"; then AM_CONDITIONAL([USE_OSS], [true]) AC_DEFINE([USE_OSS], [1], [Open Sound System MIDI support]) fi AC_CHECK_FUNC([mmap], [have_mmap=yes], [have_mmap=no]) AC_CHECK_HEADER([sys/mman.h], [have_sys_mman_h=yes], [have_sys_mman_h=no]) AM_CONDITIONAL([USE_MMAP], [false]) if test "x$have_mmap" = "xyes" && test "x$have_sys_mman_h" = "xyes"; then AM_CONDITIONAL([USE_MMAP], [true]) AC_DEFINE([HAVE_MMAP], [1], [Have mmap system call]) fi AC_C_CONST AC_C_INLINE AC_TYPE_INT8_T AC_TYPE_INT16_T AC_TYPE_INT32_T AC_TYPE_INT64_T AC_TYPE_INTMAX_T AC_TYPE_INTPTR_T AC_TYPE_UINT8_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT64_T AC_TYPE_UINTMAX_T AC_TYPE_UINTPTR_T AC_TYPE_OFF_T AC_TYPE_SIZE_T AC_STRUCT_TM dnl -------------------------------------------------------------------------- dnl Some weird, backwards platforms do not understand "%zu", so we have to dnl map it to something that will probably work everywhere. AC_CHECK_SIZEOF([size_t]) case "x$ac_cv_sizeof_size_t" in x1*) AC_DEFINE([PRIuSZ], [PRIu8], [ ]) AC_DEFINE([PRIxSZ], [PRIx8], [ ]) AC_DEFINE([PRIXSZ], [PRIX8], [ ]) ;; x2*) AC_DEFINE([PRIuSZ], [PRIu16], [ ]) AC_DEFINE([PRIxSZ], [PRIx16], [ ]) AC_DEFINE([PRIXSZ], [PRIX16], [ ]) ;; x4*) AC_DEFINE([PRIuSZ], [PRIu32], [ ]) AC_DEFINE([PRIxSZ], [PRIx32], [ ]) AC_DEFINE([PRIXSZ], [PRIX32], [ ]) ;; x8*) AC_DEFINE([PRIuSZ], [PRIu64], [ ]) AC_DEFINE([PRIxSZ], [PRIx64], [ ]) AC_DEFINE([PRIXSZ], [PRIX64], [ ]) ;; *) AC_DEFINE([PRIuSZ], ["zu"], [ ]) AC_DEFINE([PRIxSZ], ["zx"], [ ]) AC_DEFINE([PRIXSZ], ["zX"], [ ]) ;; esac dnl ----------------------------------------------------------------------- dnl Retrieve size of floating point numbers. This is later used to bypass dnl binary decoding of IEEE numbers in ieee-float.c AC_CHECK_SIZEOF([float]) AC_CHECK_SIZEOF([double]) AC_CHECK_SIZEOF([long double]) dnl ----------------------------------------------------------------------- dnl Platform-specific behavior AC_MSG_CHECKING([host platform]) use_macosx=no use_macos=no use_win32=no use_os2=no use_wii=no use_wiiu=no use_network=no use_controller=no posix_fseek=unknown have_loadso=no case "${host_os}" in darwin*) AC_MSG_RESULT([found Darwin host]) use_macosx=yes posix_fseek=yes AC_DEFINE([SCHISM_MACOSX], [1], [Mac OS X]) ;; macos*) # Retro68 toolchain AC_MSG_RESULT([found Classic Mac OS host]) use_macos=yes AC_DEFINE([SCHISM_MACOS], [1], [Classic Mac OS]) EXEEXT=.xcoff AC_PATH_PROG([REZ], [Rez]) if test "x$REZ" = "x"; then AC_MSG_ERROR([*** Couldn't find Rez]) fi AC_PATH_PROG([MAKE_PEF], [MakePEF]) if test "x$MAKE_PEF" = "x"; then AC_MSG_ERROR([*** Couldn't find MakePEF]) fi posix_fseek=no ;; os2*) AC_MSG_RESULT([found OS/2 host]) have_loadso=yes use_os2=yes AC_DEFINE([SCHISM_OS2], [1], [OS/2]) ;; cygwin*|mingw*) AC_MSG_RESULT([found Windows host]) WIN32_LIBS="-lwinmm" AC_CHECK_HEADERS([winsock.h winsock2.h]) if test "x$ac_cv_header_winsock_h" = "xyes"; then AC_SUBST([NETWORK_LIBS], [-lwsock32]) use_network=yes elif test "x$ac_cv_header_winsock2_h" = "xyes"; then AC_SUBST([NETWORK_LIBS], [-lws2_32]) use_network=yes else AC_MSG_ERROR([*** Couldn't find winsock (how)]) fi have_loadso=yes use_win32=yes posix_fseek=yes AC_DEFINE([SCHISM_WIN32], [1], [Windows]) ;; *) AC_MSG_RESULT([nothing to do here]) dnl now check for other stuff saved_LIBS="$LIBS" AC_CHECK_LIB([ogc], [IOS_ReloadIOS], [libogc_found=yes], [libogc_found=no], [-mrvl -L${DEVKITPRO}/libogc/lib/wii]) AC_CHECK_LIB([wut], [OSDynLoad_Acquire], [libwut_found=yes], [libwut_found=no], [-L${DEVKITPRO}/wut/lib]) LIBS="$saved_LIBS" which=none if test "x$FORCE_WIIU" = "xyes"; then which=wiiu elif test "x$FORCE_WII" = "xyes"; then which=wii elif test "x$libogc_found" = "xyes"; then which=wii elif test "x$libwut_found" = "xyes"; then which=wiiu fi if test "x$which" = "xwii"; then WII_CPPFLAGS="-I${DEVKITPRO}/libogc/include -I${DEVKITPPC}/powerpc-eabi/include" WII_LDFLAGS="-logc -L${DEVKITPRO}/libogc/lib/wii -L${DEVKITPPC}/powerpc-eabi/lib" AC_SUBST([WII_CPPFLAGS]) AC_SUBST([WII_LDFLAGS]) EXEEXT=.elf AC_SUBST([EXEEXT]) use_wii=yes use_controller=yes AC_DEFINE([SCHISM_WII], [1], [Wii]) AC_DEFINE([SCHISM_CONTROLLER], [1], [Enable game controller support]) posix_fseek=yes elif test "x$which" = "xwiiu"; then WIIU_CPPFLAGS="-I${DEVKITPRO}/wut/include -I${DEVKITPPC}/powerpc-eabi/include" WIIU_LDFLAGS="-lwut -T${DEVKITPRO}/wut/share/wut.ld -Wl,--gc-sections -Wl,--emit-relocs -Wl,--no-eh-frame-hdr -z nocopyreloc -L${DEVKITPRO}/wut/lib -L${DEVKITPPC}/powerpc-eabi/lib" AC_SUBST([WIIU_CPPFLAGS]) AC_SUBST([WIIU_LDFLAGS]) AC_PATH_PROG([WUHBTOOL], [wuhbtool]) if test "x$WUHBTOOL" = "x"; then AC_MSG_ERROR([*** Couldn't find wuhbtool]) fi AC_PATH_PROG([ELF2RPL], [elf2rpl]) if test "x$ELF2RPL" = "x"; then AC_MSG_ERROR([*** Couldn't find elf2rpl]) fi AC_SUBST([WUHBTOOL]) AC_SUBST([ELF2RPL]) EXEEXT=.elf AC_SUBST([EXEEXT]) use_wiiu=yes use_controller=yes AC_DEFINE([SCHISM_WIIU], [1], [Wii U]) AC_DEFINE([SCHISM_CONTROLLER], [1], [Enable game controller support]) posix_fseek=yes fi dnl Networking... saved_LIBS="$LIBS" AC_SEARCH_LIBS([socket], [socket network]) LIBS="$saved_LIBS" if test "x$ac_cv_search_socket" = "xno"; then AC_MSG_WARN([*** Couldn't find a proper sockets library]) elif test "x$ac_cv_search_socket" = "xnone required"; then if test "x$ac_cv_header_sys_socket_h" = "xyes"; then dnl free networking AC_DEFINE([USE_NETWORK], [1], [Networking]) use_network=yes else use_network=no fi else AC_SUBST([NETWORK_LIBS], [$ac_cv_search_socket]) AC_DEFINE([USE_NETWORK], [1], [Networking]) use_network=yes fi ;; esac dnl Libs AC_SEARCH_LIBS([dlopen], [dl], [ AC_DEFINE([HAVE_LIBDL], [1], [ ]) have_loadso=yes ], []) if test "x$use_macosx" = "xyes"; then AC_PROG_OBJC dnl stupid hax to allow C99 and C11 compilation dnl TODO this should try both C11 and C99 in that dnl order since the objective c compiler may be dnl older or newer than the c compiler old_OBJC="$OBJC" if test "x$ac_cv_prog_cc_c11" != "xno"; then OBJC="$OBJC $ac_cv_prog_cc_c11" elif test "x$ac_cv_prog_cc_c99" != "xno"; then OBJC="$OBJC $ac_cv_prog_cc_c99" fi AC_LANG_PUSH([Objective C]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[]], [[return 0;]])], [objc_hax_works=yes], [objc_hax_works=no]) AC_LANG_POP([Objective C]) dnl whatever, punt if test "x$objc_hax_works" = "xno"; then OBJC = "$old_OBJC" fi else dnl stupid hack to make Objective-C optional AM_CONDITIONAL([am__fastdepOBJC], [false]) OBJC="$CC" fi AM_CONDITIONAL([USE_WIN32], [test "x$use_win32" = "xyes"]) AM_CONDITIONAL([USE_WII], [test "x$use_wii" = "xyes"]) AM_CONDITIONAL([USE_WIIU], [test "x$use_wiiu" = "xyes"]) AM_CONDITIONAL([USE_CONTROLLER], [test "x$use_controller" = "xyes"]) AM_CONDITIONAL([USE_MACOSX], [test "x$use_macosx" = "xyes"]) AM_CONDITIONAL([USE_MACOS], [test "x$use_macos" = "xyes"]) AM_CONDITIONAL([USE_OS2], [test "x$use_os2" = "xyes"]) AM_CONDITIONAL([am__fastdepOBJC], [test "x$use_macosx" = "xyes"]) AM_CONDITIONAL([USE_NETWORK], [test "x$use_network" = "xyes"]) dnl ------------------------------------------------------------------------ AC_MSG_CHECKING([for arithmetic right shift]) AC_COMPILE_IFELSE([AC_LANG_SOURCE([[int xxx[((-1 >> 1) == -1) ? 1 : -1];]])], [have_arithmetic_rshift=yes], [have_arithmetic_rshift=no]) AC_MSG_RESULT([$have_arithmetic_rshift]) if test "x$have_arithmetic_rshift" = "xyes"; then AC_DEFINE([HAVE_ARITHMETIC_RSHIFT], [1], [ ]) fi AC_MSG_CHECKING([for C99 VLA support]) AC_COMPILE_IFELSE([AC_LANG_SOURCE([[void s(unsigned int x) { char y[x]; }]])], [have_c99_vlas=yes], [have_c99_vlas=no]) AC_MSG_RESULT($have_c99_vlas) if test "x$have_c99_vlas" = "xyes"; then AC_DEFINE([HAVE_C99_VLAS], [1], [C99-style VLA support]) fi AC_MSG_CHECKING([for C99 flexible array member support]) AC_COMPILE_IFELSE([AC_LANG_SOURCE([[struct flex { int a; int b[]; }; ]])], [have_c99_fams=yes], [have_c99_fams=no]) AC_MSG_RESULT($have_c99_fams) if test "x$have_c99_fams" = "xyes"; then AC_DEFINE([HAVE_C99_FAMS], [1], [C99-style flexible array member support]) fi dnl -------------------------------------------------------------------------- dnl check whether fseek allows to seek past EOF or not if test "x$posix_fseek" = "xunknown"; then AC_MSG_CHECKING([if fseek allows to go past EOF]) AC_RUN_IFELSE([AC_LANG_PROGRAM([[ #include #include ]], [[ unsigned char expected[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}; unsigned char buf[sizeof(expected)]; { FILE *fp = fopen("conftest.bin", "wb"); if (!fp) return 1; if (fputc('\x80', fp) == EOF) { fclose(fp); return 2; } if (fseek(fp, 5, SEEK_CUR) < 0) { fclose(fp); return 3; } if (fputc('\xFF', fp) == EOF) { fclose(fp); return 4; } if (fclose(fp) == EOF) return 5; } { FILE *fp = fopen("conftest.bin", "rb"); if (!fp) return 6; if (fseek(fp, 0, SEEK_END) < 0) { fclose(fp); return 7; } long length = ftell(fp); if (length < 0 || length != sizeof(expected)) { fclose(fp); return 8; } // back to the beginning if (fseek(fp, 0, SEEK_SET) < 0) { fclose(fp); return 9; } if (fread(buf, 1, sizeof(buf), fp) != sizeof(buf)) { fclose(fp); return 10; } if (fclose(fp) == EOF) return 11; // eh remove("conftest.bin"); } // compare what we got if (memcmp(buf, expected, sizeof(buf))) return 12; // yay! return 0; ]])], [posix_fseek=yes], [posix_fseek=no], [] # err, the existing value above will suffice ) AC_MSG_RESULT([$posix_fseek]) if test "x$posix_fseek" != "xyes"; then AC_DEFINE([HAVE_NONPOSIX_FSEEK], [1], [fseek past EOF is broken]) fi fi dnl -------------------------------------------------------------------------- dnl configure flags, optional dependencies AM_CONDITIONAL([USE_FLAC], false) AM_CONDITIONAL([LINK_TO_FLAC], false) if test "x$with_flac" = "xcheck" || test "x$with_flac" = "xyes"; then dnl use pkg-config, it'll deal with everything for us PKG_CHECK_MODULES([FLAC], [flac], [libflac_found=yes], [libflac_found=no]) if test "x$libflac_found" = "xyes"; then AC_SUBST([FLAC_CFLAGS]) AC_DEFINE([USE_FLAC], [1], [FLAC support]) AM_CONDITIONAL([USE_FLAC], true) if test "x$FLAC_LINKING" = "xyes"; then AC_SUBST([FLAC_LIBS]) AM_CONDITIONAL([LINK_TO_FLAC], true) else if test "x$have_loadso" != "xyes"; then AC_MSG_ERROR([*** Failed to find a proper dynamic loading backend]) fi AC_DEFINE([FLAC_DYNAMIC_LOAD], [1], [Dynamically load FLAC symbols]) fi elif test "x$with_flac" = "xyes"; then AC_MSG_ERROR([*** Failed to find libFLAC]) fi fi AM_CONDITIONAL([USE_JACK], false) AM_CONDITIONAL([LINK_TO_JACK], false) if test "x$with_jack" = "xcheck" || test "x$with_jack" = "xyes"; then PKG_CHECK_MODULES([JACK], [jack], [jack_found=yes], [jack_found=no]) if test "x$jack_found" = "xyes"; then AC_SUBST([JACK_CFLAGS]) AM_CONDITIONAL([USE_JACK], true) AC_DEFINE([USE_JACK], [1], [JACK MIDI support]) if test "x$JACK_LINKING" = "xyes"; then AC_SUBST([JACK_LIBS]) AM_CONDITIONAL([LINK_TO_JACK], true) else if test "x$have_loadso" != "xyes"; then AC_MSG_ERROR([*** Failed to find a proper dynamic loading backend]) fi AC_DEFINE([JACK_DYNAMIC_LOAD], [1], [Dynamically load JACK symbols]) fi elif test "x$with_jack" = "xyes"; then AC_MSG_ERROR([*** Failed to find JACK]) fi fi AM_CONDITIONAL([USE_ALSA], false) AM_CONDITIONAL([LINK_TO_ALSA], false) if test "x$with_alsa" = "xcheck" || test "x$with_alsa" = "xyes"; then saved_LIBS="$LIBS" AC_CHECK_LIB([asound], [snd_seq_open], [alsa_found=yes], [alsa_found=no]) if test "x$alsa_found" = "xyes"; then AM_CONDITIONAL([USE_ALSA], true) AC_DEFINE([USE_ALSA], [1], [ALSA MIDI support]) if test "x$ALSA_LINKING" = "xyes"; then AM_CONDITIONAL([LINK_TO_ALSA], true) else LIBS="$saved_LIBS" if test "x$have_loadso" != "xyes"; then AC_MSG_ERROR([*** Failed to find a proper dynamic loading backend]) fi AC_DEFINE([ALSA_DYNAMIC_LOAD], [1], [Dynamically load ALSA symbols]) fi elif test "x$with_alsa" = "xyes"; then AC_MSG_ERROR([*** Failed to find ALSA]) fi fi AM_CONDITIONAL([USE_MEDIAFOUNDATION], false) if test "x$with_mediafoundation" = "xcheck" || test "x$with_mediafoundation" = "xyes"; then dnl requires Windows 7 or newer, but doesn't link to the libraries to keep dnl XP/Vista also working dnl XXX this ought to be more specific if test "x$use_win32" = "xyes"; then AM_CONDITIONAL([USE_MEDIAFOUNDATION], true) AC_DEFINE([USE_MEDIAFOUNDATION], [1], [Use Media Foundation sample loading]) elif test "x$with_mediafoundation" = "xyes"; then AC_MSG_ERROR([*** Failed to find Media Foundation]) fi fi AM_CONDITIONAL([USE_SDL3], false) AM_CONDITIONAL([LINK_TO_SDL3], false) if test "x$with_sdl3" = "xcheck" || test "x$with_sdl3" = "xyes"; then PKG_CHECK_MODULES([SDL3], [sdl3], [sdl3_found=yes], [sdl3_found=no]) if test "x$sdl3_found" = "xyes"; then AC_SUBST([SDL3_CFLAGS]) AM_CONDITIONAL([USE_SDL3], true) AC_DEFINE([SCHISM_SDL3], [1], [SDL 3 backend]) if test "x$SDL3_LINKING" = "xyes"; then AC_SUBST([SDL3_LIBS]) AM_CONDITIONAL([LINK_TO_SDL3], true) else if test "x$have_loadso" != "xyes"; then AC_MSG_ERROR([*** Failed to find a proper dynamic loading backend]) fi AC_DEFINE([SDL3_DYNAMIC_LOAD], [1], [Dynamically load SDL3 symbols]) fi elif test "x$with_sdl3" = "xyes"; then AC_MSG_ERROR([*** Failed to find SDL 3]) fi fi AM_CONDITIONAL([USE_SDL2], false) AM_CONDITIONAL([LINK_TO_SDL2], false) if test "x$with_sdl2" = "xcheck" || test "x$with_sdl2" = "xyes"; then PKG_CHECK_MODULES([SDL2], [sdl2], [sdl2_found=yes], [sdl2_found=no]) if test "x$sdl2_found" = "xyes"; then AC_SUBST([SDL2_CFLAGS]) AM_CONDITIONAL([USE_SDL2], true) AC_DEFINE([SCHISM_SDL2], [1], [SDL 2 backend]) if test "x$SDL2_LINKING" = "xyes"; then AC_SUBST([SDL2_LIBS]) AM_CONDITIONAL([LINK_TO_SDL2], true) else if test "x$have_loadso" != "xyes"; then AC_MSG_ERROR([*** Failed to find a proper dynamic loading backend]) fi AC_DEFINE([SDL2_DYNAMIC_LOAD], [1], [Dynamically load SDL2 symbols]) fi elif test "x$with_sdl2" = "xyes"; then AC_MSG_ERROR([*** Failed to find SDL 2]) fi fi AM_CONDITIONAL([USE_SDL12], false) AM_CONDITIONAL([LINK_TO_SDL12], false) if test "x$with_sdl12" = "xcheck" || test "x$with_sdl12" = "xyes"; then PKG_CHECK_MODULES([SDL12], [sdl >= 1.2.10], [sdl12_found=yes], [sdl12_found=no]) if test "x$sdl12_found" = "xyes"; then AC_SUBST([SDL12_CFLAGS]) AM_CONDITIONAL([USE_SDL12], true) AC_DEFINE([SCHISM_SDL12], [1], [SDL 1.2 backend]) if test "x$SDL12_LINKING" = "xyes"; then AC_SUBST([SDL12_LIBS]) AM_CONDITIONAL([LINK_TO_SDL12], true) else if test "x$have_loadso" != "xyes"; then AC_MSG_ERROR([*** Failed to find a proper dynamic loading backend]) fi AC_DEFINE([SDL12_DYNAMIC_LOAD], [1], [Dynamically load SDL 1.2 symbols]) fi elif test "x$with_sdl12" = "xyes"; then AC_MSG_ERROR([*** Failed to find SDL 1.2]) fi fi dnl Function to find a library in the compiler search path, lifted from SDL find_lib() { gcc_bin_path=[`$CC -print-search-dirs 2>/dev/null | $FGREP programs: | sed 's/[^=]*=\(.*\)/\1/' | sed 's/:/ /g'`] gcc_lib_path=[`$CC -print-search-dirs 2>/dev/null | $FGREP libraries: | sed 's/[^=]*=\(.*\)/\1/' | sed 's/:/ /g'`] env_lib_path=[`echo $LIBS $LDFLAGS $* | sed 's/-L[ ]*//g'`] if test "$cross_compiling" = yes; then host_lib_path="" else host_lib_path="/usr/$base_libdir /usr/local/$base_libdir" fi for path in $env_lib_path $gcc_bin_path $gcc_lib_path $host_lib_path; do lib=[`ls -- $path/$1 2>/dev/null | sed 's,.*/,,' | "$SORT" -V -r | $AWK 'BEGIN{FS="."}{ print NF, $0 }' | "$SORT" -n -s | sed 's,[0-9]* ,,' | head -1`] if test x$lib != x; then echo $lib return fi done } AM_CONDITIONAL([USE_X11], [false]) AM_CONDITIONAL([LINK_TO_X11], [false]) AC_PATH_X AC_PATH_XTRA if test "x$have_x" = "xyes"; then case "$host" in *-*-darwin*) # Apple now puts this in /opt/X11 ? x11_lib='/opt/X11/lib/libX11.6.dylib' ;; *-*-openbsd*) x11_lib='libX11.so' ;; *) x11_lib=[`find_lib "libX11.so.*" "$X_LIBS -L/usr/X11/$base_libdir -L/usr/X11R6/$base_libdir" | sed 's/.*\/\(.*\)/\1/; q'`] ;; esac if test "x$have_loadso" != "xyes" && test "x$X11_LINKING" = "xno"; then AC_MSG_ERROR([*** Failed to find a proper dynamic loading backend]) fi AC_DEFINE([SCHISM_USE_X11], [1], [ ]) AM_CONDITIONAL([USE_X11], [true]) if test "x$have_loadso" = "xyes" && test "x$X11_LINKING" = "xno" && test "x$x11_lib" != "x"; then echo "-- dynamic libX11 -> $x11_lib" AC_DEFINE_UNQUOTED(X11_LIBRARY_LIBX11_PATH, "$x11_lib", [ ]) AC_DEFINE([X11_DYNAMIC_LOAD], [1], [ ]) else X11_LINKING=yes EXTRA_LDFLAGS="$EXTRA_LDFLAGS $X_LIBS -lX11" AM_CONDITIONAL([LINK_TO_X11], [true]) fi AC_SUBST([X_CFLAGS]) AC_SUBST([X_LIBS]) fi dnl -------------------------------------------------------------------------- if test "x$USE_OPL2" = "xyes"; then AC_DEFINE([OPLSOURCE], [2], [Which OPL chip to emulate]) AM_CONDITIONAL([USE_OPL2], [true]) else AC_DEFINE([OPLSOURCE], [3], [Which OPL chip to emulate]) AM_CONDITIONAL([USE_OPL2], [false]) fi dnl -------------------------------------------------------------------------- dnl fortify needs -O; do this early so ADD_OPT can override with higher -O level project_CFLAGS="" if test x$ADD_FORTIFY \!= xno; then project_CFLAGS="$project_CFLAGS -O -D_FORTIFY_SOURCE=2" fi dnl place extra optimizations after existing cflags so that they can override dnl override whatever flags might exist by default (-g -O2 usually) if test x$ADD_OPT \!= xno; then if test x$ADD_DEBUG \!= xno || test x$ADD_PROFILING \!= xno; then AC_MSG_NOTICE([You aren't supposed to use --enable-debug or --enable-profiling together with --enable-extra-opt!!]) fi ADD_OPT="-Ofast -g0 -s -fno-exceptions -march=native" project_CFLAGS="$project_CFLAGS $ADD_OPT" fi if test x$ADD_LUDICROUS \!= xno; then ADD_WARN=yes project_CFLAGS="$project_CFLAGS -Werror" fi dnl ... but put the warnings first, to make it possible to quiet certain dnl warnings if necessary, while still providing most of the benefit if test x$ADD_WARN \!= xno; then ADD_WARN="-Wall -Wextra -Winline -Wshadow -Wwrite-strings -Waggregate-return -Wpacked" ADD_WARN="$ADD_WARN -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wnested-externs" project_CFLAGS="$ADD_WARN $project_CFLAGS" fi dnl Unlike a grand -Werror, this one could be rather important: dnl functions returning random values are no good under any circumstances. AC_MSG_CHECKING([for -Werror=return-type support]) old_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -Werror=return-type" AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[]], [[return 0;]])], [have_error_return_type=yes], [have_error_return_type=no]) AC_MSG_RESULT([$have_error_return_type]) if test "x$have_error_return_type" = "xno"; then CFLAGS="$old_CFLAGS" fi if test x$ADD_PROFILING \!= xno || test x$ADD_DEBUG \!= xno; then project_CFLAGS="$project_CFLAGS -g -O0" project_OBJCFLAGS="$project_OBJCFLAGS -g -O0" SDL_LIBS="$SDL_LIBS -g -O0" AC_SUBST(SDL_LIBS) fi if test x$ADD_PROFILING \!= xno; then project_CFLAGS="$project_CFLAGS -pg" project_OBJCFLAGS="$project_OBJCFLAGS -pg" SDL_LIBS="$SDL_LIBS -pg" AC_SUBST(SDL_LIBS) fi dnl -------------------------------------------------------------------------- AC_SUBST([project_CFLAGS]) AC_SUBST([project_OBJCFLAGS]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT dnl -------------------------------------------------------------------------- dnl These are shipped in the release tarball, so we don't want to regenerate dnl them after configure is run. if test -e "auto/default-font.c"; then touch "auto/default-font.c" fi if test "x$PERL" = "x"; then if test -e "auto/helptext.c"; then touch "auto/helptext.c" fi fi dnl -------------------------------------------------------------------------- dnl old build config filename; it overrides the regular include paths, so it dnl has to be removed before we can start building if test -e "config.h"; then rm "config.h" fi schismtracker-20250313/docs/000077500000000000000000000000001476471630300155605ustar00rootroot00000000000000schismtracker-20250313/docs/building_on_linux.md000066400000000000000000000073231476471630300216170ustar00rootroot00000000000000# Building on Linux Since Linux is the primary development platform for Schism Tracker, it's probably the easiest to compile on, and those familiar with automake-based projects will find few surprises here. If you just want to use Schism Tracker on 64-bit Linux, you can also download a pre-built binary from the release page (just make sure you've installed SDL2). ## Prerequisites On Ubuntu, run: sudo apt update sudo apt install build-essential automake autoconf-archive \ libsdl2-dev git libtool libflac-dev perl \ pkgconf On Arch Linux: sudo pacman -Syu sudo pacman -S base-devel git sdl2 alsa-lib libxv libxxf86vm flac perl \ pkgconf Git is not strictly required, but if you don't need it you'll need to download a tarball manually, and your build won't have a proper version string. FLAC libraries are optional and only used for FLAC sample loading support. On other distros, the package names may be different. In particular, note that `build-essential` includes the packages `gcc` and `make` on Debian-based systems. If your distro doesn't come with Python by default, you'll also need that. ## Setting up the source directory To get and set up the source directory for building: git clone https://github.com/schismtracker/schismtracker.git cd schismtracker autoreconf -i mkdir -p build You can then update your Schism Tracker source directory by going to the `schismtracker` directory and running: git pull ## Building Schism Tracker From the `schismtracker` directory: cd build && ../configure && make The resulting binary `schismtracker` is completely self-contained and can be copied anywhere you like on the filesystem. ## Packaging Schism Tracker for Linux systems The `icons/` directory contains icons that you may find suitable for your desktop environment. The `sys/fd.org/schism.desktop` can be used to launch Schism Tracker from a desktop environment, and `sys/fd.org/itf.desktop` can be used to launch the built-in font-editor. ## ALSA problems The configure script should autodetect everything on your system, but if you don't have the ALSA development libraries installed, Schism Tracker won't be built with ALSA MIDI support, even if your SDL libraries include ALSA digital output. ## Cross-compiling for Win32 Schism Tracker can be built using the MinGW cross-compiler on a Linux host. You will also need the [SDL2 MinGW development library][1]. If you unpacked it into `/usr/i586-mingw32/`, you could use the following to cross-compile Schism Tracker for Win32: mkdir win32-build cd build env SDL_CONFIG=/usr/i586-mingw32/sdl-config \ ../configure --{host,target}=i586-mingw32 --without-x make If you want to build an installer using the [Nullsoft Scriptable Install System][2], copy some files into your build directory: cd build cp /usr/i586-mingw32/bin/SDL2.dll . cp ../COPYING COPYING.txt cp ../README README.txt cp ../NEWS NEWS.txt cp ../sys/win32/schism.nsis . cp ../icons/schismres.ico schism.ico and run the `makensis` application: makensis schism.nsis On Ubuntu, for cross-compiling Win32 binaries, run: sudo apt install mingw32 mingw32-binutils mingw32-runtime nsis On Arch Linux: sudo pacman -S mingw-w64-gcc yaourt -S mingw-w64-sdl2 nsis Note: Yaourt isn't strictly necessary, but since `mingw-w64-sdl2` and `nsis` are AUR packages, you'll have to build them by hand otherwise or use a different [AUR helper][3]. `mingw-w64-sdl2` may or may not be necessary if you've manually downloaded the MinGW SDL2 library as mentioned above. [1]: https://github.com/libsdl-org/SDL/releases [2]: http://nsis.sourceforge.net/ [3]: https://wiki.archlinux.org/index.php/AUR_helpers schismtracker-20250313/docs/building_on_osx.md000066400000000000000000000040741476471630300212710ustar00rootroot00000000000000# Building on OS X Start by installing [Homebrew](http://brew.sh/). Open up the Terminal and paste in the following command: /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" After Homebrew has been successfully installed, you need to install `automake`, `autoconf`, `sdl2` and `git`. brew install automake autoconf sdl2 git perl pkg-config Alternatively, if you have MacPorts installed, you can use this command instead: sudo port install automake autoconf libtool libsdl2 git perl5 pkg-config For FLAC sample loading support, you will also need development versions of the `flac` and `libogg` libraries. In this case, you may have to open a new terminal shell, or else you may get warnings about the version of autoconf/automake you're using. Now clone the GitHub repo: git clone https://github.com/schismtracker/schismtracker.git Enter the Schismtracker folder and run `autoreconf -i`: cd schismtracker autoreconf -i Now you will need to create the `build` folder, enter it and start the build: mkdir -p build cd build ../configure && make Test Schismtracker from the commandline by typing: ./schismtracker If it worked, you are ready to start the updating of the **Schism Tracker.app**. ## Baking Schism Tracker into an App ready to be put in /Applications If you are in the `build` folder, find the `Schism_Tracker.app` subfolder `Contents` and, after creating the `MacOS` folder, copy the newly built `schismtracker` there. Then test the `Schism_Tracker.app` by clicking on it in Finder. Here are the instructions on how to do it (this will open a Finder window showing the `sys/macosx` folder, wherein you will see the app itself. cd ../sys/macosx/Schism_Tracker.app/Contents/ mkdir MacOS cd MacOS cp ../../../../../build/schismtracker . cd ../../../ open . If this newly baked version of `Schism_Tracker.app` worked, just copy it to your `/Applications` -folder. Enjoy. ## Building for distribution See the `macos` section of `.github/workflows/osx.yml` for how Schism currently does it. schismtracker-20250313/docs/building_on_windows.md000066400000000000000000000107101476471630300221440ustar00rootroot00000000000000# Building on Windows The easiest way to use Schism Tracker on Windows is to download one of the pre-built binaries. However, if you want to build it yourself, it isn't too tricky. These instructions assume you are using 64-bit Windows to build a 64-bit Schism executable. ## Installing the tools We will be using an environment called MSYS2, which provides all the packages we need in one place. If you want correct version information to show, you must also have [git](https://git-scm.com/) installed and in your PATH. ### Get MSYS2 and install it Go to the URL http://msys2.github.io/ and download the 64-bit installer. Once installed, follow these instructions to get up-to-date files. This process is also described in their web page, so in case of conflict, you might opt to follow their instructions. Run the MSYS2 shell (a start menu shortcut should have been created) and update the pacman package manager: pacman -Sy pacman Follow the onscreen options and choose "yes" where prompted. Close the MSYS2 window. Run it again from the Start menu and update the system: pacman -Syu Again follow the instructions, chose "yes" where prompted, and close the MSYS2 window. Run it again from the Start menu _(note: the update process can, in some cases, break the start menu shortcut - in which case you may need to look in C:\msys64 (or wherever you installed MSYS2) and run msys2\_shell.cmd)_ and update the rest with: pacman -Su ### Install the toolchains Once you have the shell environment ready, it's time to get the compilers. Execute the following command: pacman -S mingw-w64-x86_64-toolchain libtool autoconf automake make perl If asked to "enter a selection", hit Enter to go with the default. Also, you need the following specific dependencies: pacman -S mingw-w64-x86_64-SDL2 mingw-w64-x86_64-pkgconf For optional FLAC sample loading, you'll also need the following dependency: pacman -S mingw-w64-x86_64-flac Once you have installed these packages, close all your MSYS2 windows before continuing with the instructions. ## Compilation MSYS2 installs three shortcut icons, one to run the MSYS2 shell, and two more that setup the environment to use either the 32bit compiler or the 64-bit compiler. We will be using the one called "MSYS2 MINGW64" throughout. If you've lost the shortcuts, you can also start the 64bit compiler with msys2_shell.cmd -mingw64 ### Configure schismtracker to build Open the 64-bit shell. Download the Schismtracker sources (or clone the repo) and navigate to the schismtracker-master folder (the one that contains README.md) using `cd` Drive letters are mapped to /x , example C:/ is /c/, D:/ is /d/ ..., and so on. For example: cd /c/Users/YourUserName/Downloads/schismtracker-master/ Reconfigure it: autoreconf -i _(note: if you get a "possibly undefined macro: AM\_PATH\_SDL" error, you're probably using the standard msys2 shell - either use the mingw start menu shortcuts, or start `msys2_shell.cmd` with `-mingw64` as mentioned above)_ Make a folder to build the binary in: mkdir build Now move into the build subdir and run the configure script: cd build ../configure ### Build and rebuild In order to build Schism, from the build folder, run: make You should now have an executable in the build folder that you can run from Windows Explorer, or with ./schismtracker.exe After the first time, you can usually build Schism again without having to run `autoreconf` or `../configure` again, but if you run into problems, follow the steps from "Configure schismtracker to build" onwards again. ### Compilation problems The configure script should give hints on what is missing to compile. If you've followed the steps, everything should already be in the right place, but in case it doesn't work, see the config.log file, which reports a detailed output (more than what is seen on the screen) that could help identify what is missing, or which option is not working. ### Debugging When installing the toolchains, the gdb debugger is also installed. You can run this from the MSYS2 MINGW64 shell if you need to debug Schism. ## Preparing for distribution or sharing to other machines To distribute the application, it is important to bundle the correct version of the SDL.dll file with the executable. For a 64bit build, the file is located in `/msys2_path/mingw64/bin/SDL.dll` If you want to reduce the exe size (removing the debugging information), use the following command from MSYS2 MINGW64: strip -g schismtracker.exe schismtracker-20250313/docs/configuration.md000066400000000000000000000216661476471630300207640ustar00rootroot00000000000000# Configuration Schism Tracker saves its configuration in a plain-text, [INI-style](http://en.wikipedia.org/wiki/INI_file) file named `config`. Under normal circumstances it should theoretically not be necessary to deal with this file directly. However, there are some options that are not configurable from within Schism Tracker for one reason or another. In these cases, any plain text editor should suffice. The location of this file is dependent on the OS: - **Windows** - `%APPDATA%\Schism Tracker` - **Mac OS X** - `~/Library/Application Support/Schism Tracker` - **AmigaOS 4** - `PROGDIR:` - **Linux/Unix** - `$HOME/.config/schism/` - `$HOME/.schism/` (legacy, not used on new installs) - **Wii** - Same directory as `boot.elf` (e.g. `sd:/apps/schismtracker`) - **Mac OS** - `Macintosh HD:System Folder:Preferences` - **OS/2** - `%HOME%\Schism Tracker` - `%ETC%\Schism Tracker`, if the `%HOME%` variable is not defined - **Haiku** - `$HOME/config/settings/schism/` - `$HOME/.schism/` (legacy, not used on new installs) Aside from `config`, you may also add a `fonts` subdirectory for custom font files. By default, `font.cfg` is automatically loaded on startup, and other `*.itf` files are listed by the built-in font editor (Shift-F12). See [the links page](https://github.com/schismtracker/schismtracker/wiki/Links) for some resources on getting fonts for Schism Tracker. ## Potentially useful "hidden" config options To enable any of these, find the `[section]` in the config file, look for the `key=`, and change the value. If the key doesn't exist, simply add it. #### Video [Video] lazy_redraw=1 width=640 height=400 want_fixed=0 want_fixed_width=3200 want_fixed_height=2400 `lazy_redraw` slows down the framerate when the program isn't focused. This used to be kind of useful when the GUI rendering sucked, and maybe it still is if you're stuck with a painfully slow video card and software rendering, and you also want to have a huge window that isn't active. `width` and `height` are the initial dimensions to use for the window, and the dimensions to return to when toggling fullscreen off. If `want_fixed` is set to 1, Schism will be displayed with a constant width and height regardless of the window size. Those values are retrieved from `want_fixed_width` and `want_fixed_height` which correspond to a 4:3 aspect ratio by default. #### Backups [General] make_backups=1 numbered_backups=1 When overwriting a `filename.it`, copy the existing file to `filename.it~`. With numbered_backups, write to `filename.it.1~`, `filename.it.2~`, etc. #### Key repeat [General] key_repeat_delay=125 key_repeat_rate=25 Alter the key repeat. "Delay" is how long before keys begin to repeat, "rate" is how long between repeated keystrokes. (Both are in milliseconds.) Above are Storlek's settings, which are very fast but convenient for speed tracking. The *default* repeat delay and rate come from your operating system, so you only need to set this if you like having a different rate for Schism Tracker than you do for the rest of your system. #### Alternate font [General] font=notch.itf Load some other font besides `font.cfg` at startup. This option doesn't really have much of a point, because the file listed is limited to those within the `fonts` directory, and it's just easier to open the font editor, browse fonts, and save to `font.cfg`. #### DJ mode [General] stop_on_load=0 If zero, loading a song when another one is playing will start playing the new song after it is loaded. #### Date and time formatting [General] time_format=12hr date_format=mmmmdyyyy If `time_format` is set to `24hr`, all times will display in 24-hour time, i.e. as `01:20`, while with `12hr` the same time will be displayed as `1:20 am`. The valid `date_format` values are as follows, using 2025-01-07 as an example: | Format value | Result | | ------------ | --------------- | | `mmmmdyyyy` | January 7, 2025 | | `dmmmmyyyy` | 7 January 2025 | | `yyyymmmmdd` | 2025 January 07 | | `mmddyyyy` | 01/07/2025 | | `ddmmyyyy` | 07/01/2025 | | `yyyymmdd` | 2025/01/07 | | `mdyyyy` | 1/7/2025 | | `dmyyyy` | 7/1/2025 | | `yyyymd` | 2025/1/7 | | `iso8601` | 2025-01-07 | #### File browser [Directories] module_pattern=*.it\073 *.xm\073 *.s3m\073 *.mtm\073 *.669\073 *.mod Changes what files are presented in the load/save module lists. Use * for all files. For annoying compatibility reasons, semicolons are rewritten as `\x3b` or `\073` when saving. This was formerly named `filename_pattern`; Schism Tracker ignores the old value and comments it out when saving to work around bugs in older versions. sort_with=strcaseverscmp Alter the sort order. Possible values are `strcmp` (case-sensitive), `strcasecmp` (case-insensitive), `strverscmp` (case-sensitive, but handles numbers smartly e.g. `5.it` will be listed above `10.it`), and `strcaseverscmp` (case-insensitive and handles numbers smartly). #### Keyjazz [Pattern Editor] keyjazz_noteoff=1 keyjazz_write_noteoff=0 keyjazz_repeat=0 keyjazz_capslock=0 If `keyjazz_noteoff` is 1, letting go of a key in the pattern editor will cause a note-off. If using this, you might also want to consider setting `keyjazz_repeat` to 0 in order to avoid inserting multiple notes when holding down keys. If `keyjazz_write_noteoff` is 1, letting go of a key in the pattern editor will also write a note off *if* playback tracing (Ctrl+F) is enabled. If `keyjazz_capslock` is 1, keyjazz will be enabled if Caps Lock is toggled, not if the key is pressed. This is particularly useful for macOS users where SDL doesn't send proper key events for the Caps Lock key, see issue #385. #### Pattern editor behavior tweaks [Pattern Editor] mask_copy_search_mode=1 invert_home_end=1 When `mask_copy_search_mode` is set to 1, pressing Enter on a row with no instrument number will search backward in the channel for an instrument and switch to that one. `invert_home_end` changes the order of the Home and End keys to make the cursor move to the first or last row within the channel before moving to the first or last channel. FT2 users might want to enable this. #### Key modifiers [General] meta_is_ctrl=1 altgr_is_alt=1 These alter how modifier keys are interpreted. Mac OS X users in particular might appreciate `meta_is_ctrl`, which allows using the Command/Apple key as the Ctrl modifier within Schism Tracker. `altgr_is_alt` works similarly. #### Audio output [Audio] buffer_size=256 driver=dsp:/dev/dsp1 These settings define the audio buffer size, and which audio device Schism Tracker uses. (The other settings in the `[Audio]` section are configurable from Shift-F1.) `buffer_size` should be a power of two and defines the number of samples in the mixing buffer. Smaller values result in less audio latency but could cause buffer underruns and skipping. `driver` is parsed identically to the `--audio-driver` switch on the command line. If you're using Alsa on Linux and want to use you can set `driver=alsa:dmix` to get Schism Tracker to play with other programs. (However, Alsa completely ignores the latency with dmix so it might cause massive delays between pressing a note and hearing it, which is why Schism Tracker requests a "real" device by default.) If neither the `driver` nor `--audio-driver` is set, the `SDL_AUDIODRIVER`, `AUDIODEV` and `SDL_PATH_DSP` environment variables can be used to configure Schism's audio output. [Diskwriter] rate=96000 bits=16 channels=2 This defines the sample format used by the disk writer – for exporting to .wav/.aiff *and* internal pattern-to-sample rendering. ## Hook functions Schism Tracker can run custom scripts on startup, exit, and upon completion of the disk writer. These are stored in the configuration directory, and are named `startup-hook`, `exit-hook`, and `diskwriter-hook` respectively. (On Windows, append `.bat` to the filenames.) Hooks are useful for making various adjustments to the system – adjusting the system volume, remapping the keyboard, etc. The disk writer hook can be used to do additional post-processing, converting, etc. (Note: on the Wii, hooks are not processed since there is no underlying OS or command interpreter to run them.) #### Example For users with non-US keyboards, some keys may not work properly. This can be worked around by switching temporarily to a US keyboard layout on startup, and resetting the keyboard on exit. To define hooks to accomplish this: cat >~/.schism/startup-hook <~/.schism/exit-hook < * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "str.h" #include "mem.h" #include "player/sndfile.h" /* --------------------------------------------------------------------- */ struct header_669 { uint8_t sig[2]; uint8_t songmessage[108]; uint8_t samples; uint8_t patterns; uint8_t restartpos; uint8_t orders[128]; uint8_t tempolist[128]; uint8_t breaks[128]; }; static int read_header_669(struct header_669 *hdr, slurp_t *fp) { #define READ_VALUE(name) \ if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) return 0 READ_VALUE(sig); READ_VALUE(songmessage); READ_VALUE(samples); READ_VALUE(patterns); READ_VALUE(restartpos); READ_VALUE(orders); READ_VALUE(tempolist); READ_VALUE(breaks); #undef READ_VALUE return 1; } int fmt_669_read_info(dmoz_file_t *file, slurp_t *fp) { struct header_669 hdr; if (!read_header_669(&hdr, fp)) return 0; unsigned long i; const char *desc; /* Impulse Tracker identifies any 669 file as a "Composer 669 Module", regardless of the signature tag. */ if (memcmp(hdr.sig, "if", 2) == 0) desc = "Composer 669 Module"; else if (memcmp(hdr.sig, "JN", 2) == 0) desc = "Extended 669 Module"; else return 0; if (hdr.samples == 0 || hdr.patterns == 0 || hdr.samples > 64 || hdr.patterns > 128 || hdr.restartpos > 127) return 0; for (i = 0; i < 128; i++) if (hdr.breaks[i] > 0x3f) return 0; file->title = strn_dup((const char*)hdr.songmessage, sizeof(hdr.songmessage)); file->description = desc; /*file->extension = str_dup("669");*/ file->type = TYPE_MODULE_S3M; return 1; } /* --------------------------------------------------------------------------------------------------------- */ /* This is better than IT's and MPT's 669 loaders */ int fmt_669_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint8_t b[16]; uint16_t npat, nsmp; int n, pat, chan, smp, row; song_note_t *note; uint16_t tmp; uint32_t tmplong; uint8_t patspeed[128], breakpos[128]; uint8_t restartpos; const char *tid; char titletmp[37]; slurp_read(fp, &tmp, 2); switch (bswapLE16(tmp)) { case 0x6669: // 'if' tid = "Composer 669"; break; case 0x4e4a: // 'JN' tid = "UNIS 669"; break; default: return LOAD_UNSUPPORTED; } /* The message is 108 bytes, split onto 3 lines of 36 bytes each. Also copy the first part of the message into the title, because 669 doesn't actually have a dedicated title field... */ read_lined_message(song->message, fp, 108, 36); strncpy(titletmp, song->message, 36); titletmp[36] = '\0'; titletmp[strcspn(titletmp, "\r\n")] = '\0'; str_trim(titletmp); titletmp[25] = '\0'; strcpy(song->title, titletmp); nsmp = slurp_getc(fp); npat = slurp_getc(fp); restartpos = slurp_getc(fp); if (nsmp > 64 || npat > 128 || restartpos > 127) return LOAD_UNSUPPORTED; strcpy(song->tracker_id, tid); /* orderlist */ slurp_read(fp, song->orderlist, 128); /* stupid crap */ slurp_read(fp, patspeed, 128); slurp_read(fp, breakpos, 128); /* samples */ for (smp = 1; smp <= nsmp; smp++) { slurp_read(fp, b, 13); b[13] = 0; /* the spec says it's supposed to be ASCIIZ, but some 669's use all 13 chars */ strcpy(song->samples[smp].name, (char *) b); b[12] = 0; /* ... filename field only has room for 12 chars though */ strcpy(song->samples[smp].filename, (char *) b); slurp_read(fp, &tmplong, 4); song->samples[smp].length = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); song->samples[smp].loop_start = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); tmplong = bswapLE32(tmplong); if (tmplong > song->samples[smp].length) tmplong = 0; else song->samples[smp].flags |= CHN_LOOP; song->samples[smp].loop_end = tmplong; song->samples[smp].c5speed = 8363; song->samples[smp].volume = 60; /* ickypoo */ song->samples[smp].volume *= 4; //mphack song->samples[smp].global_volume = 64; /* ickypoo */ song->samples[smp].vib_type = 0; song->samples[smp].vib_rate = 0; song->samples[smp].vib_depth = 0; song->samples[smp].vib_speed = 0; } /* patterns */ for (pat = 0; pat < npat; pat++) { static const uint32_t effect_lut[] = { FX_PORTAMENTOUP, /* slide up (param * 80) hz on every tick */ FX_PORTAMENTODOWN, /* slide down (param * 80) hz on every tick */ FX_TONEPORTAMENTO, /* slide to note by (param * 40) hz on every tick */ 0, /* add (param * 80) hz to sample frequency */ FX_VIBRATO, /* add (param * 669) hz on every other tick */ FX_SPEED, /* set ticks per row */ FX_PANNINGSLIDE, /* extended UNIS 669 effect */ FX_RETRIG, /* extended UNIS 669 effect */ }; uint8_t effect[8] = {255, 255, 255, 255, 255, 255, 255, 255}; uint8_t rows = breakpos[pat] + 1; if (rows > 64) return LOAD_UNSUPPORTED; song->patterns[pat] = csf_allocate_pattern(CLAMP(rows, 32, 64)); song->pattern_size[pat] = song->pattern_alloc_size[pat] = CLAMP(rows, 32, 64); for (row = 0; row < rows; row++) { /* XXX what is 64? */ note = song->patterns[pat] + (row * 64); for (chan = 0; chan < 8; chan++, note++) { slurp_read(fp, b, 3); switch (b[0]) { case 0xfe: /* no note, only volume */ note->voleffect = VOLFX_VOLUME; note->volparam = (b[1] & 0xf) << 2; break; case 0xff: /* no note or volume */ break; default: note->note = (b[0] >> 2) + 36 + 1; note->instrument = ((b[0] & 3) << 4 | (b[1] >> 4)) + 1; note->voleffect = VOLFX_VOLUME; note->volparam = (b[1] & 0xf) << 2; effect[chan] = 0xff; break; } /* now handle effects */ if (b[2] != 0xff) effect[chan] = b[2]; /* param value of zero = reset */ if ((b[2] & 0x0f) == 0 && b[2] != 0x30) effect[chan] = 0xff; if (effect[chan] == 0xff) continue; note->param = effect[chan] & 0x0f; uint8_t e = effect[chan] >> 4; if (e < ARRAY_SIZE(effect_lut)) { note->effect = effect_lut[e]; } else { note->effect = FX_NONE; continue; } /* fix some commands */ switch (e) { default: /* do nothing */ break; case 3: /* D - frequency adjust (??) */ note->effect = FX_PORTAMENTOUP; note->param |= 0xf0; effect[chan] = 0xff; break; case 4: /* E - frequency vibrato - almost like an arpeggio, but does not arpeggiate by a given note but by a frequency amount. */ note->effect = FX_ARPEGGIO; note->param |= (note->param << 4); break; case 5: /* F - set tempo */ /* TODO: param 0 is a "super fast tempo" in Unis 669 mode (???) */ effect[chan] = 0xFF; break; case 6: // G - subcommands (extended) switch(note->param) { case 0: // balance fine slide left note->param = 0x4F; break; case 1: // balance fine slide right note->param = 0xF4; break; default: note->effect = FX_NONE; } break; } } } if (rows < 64) { /* skip the rest of the rows beyond the break position */ slurp_seek(fp, 3 * 8 * (64 - rows), SEEK_CUR); } /* handle the stupid pattern speed */ note = song->patterns[pat]; for (chan = 0; chan < 9; chan++, note++) { if (note->effect == FX_SPEED) { break; } else if (!note->effect) { note->effect = FX_SPEED; note->param = patspeed[pat]; break; } } /* handle the break position */ if (rows < 32) { //printf("adding pattern break for pattern %d\n", pat); note = song->patterns[pat] + MAX_CHANNELS * (rows - 1); for (chan = 0; chan < 9; chan++, note++) { if (!note->effect) { note->effect = FX_PATTERNBREAK; note->param = 0; break; } } } } csf_insert_restart_pos(song, restartpos); /* sample data */ if (!(lflags & LOAD_NOSAMPLES)) { for (smp = 1; smp <= nsmp; smp++) { if (song->samples[smp].length == 0) continue; csf_read_sample(song->samples + smp, SF_LE | SF_M | SF_PCMU | SF_8, fp); } } /* set the rest of the stuff */ song->initial_speed = 4; song->initial_tempo = 78; song->flags = SONG_ITOLDEFFECTS | SONG_LINEARSLIDES; song->pan_separation = 64; for (n = 0; n < 8; n++) song->channels[n].panning = (n & 1) ? 256 : 0; //mphack for (n = 8; n < 64; n++) song->channels[n].flags = CHN_MUTE; // if (ferror(fp)) { // return LOAD_FILE_ERROR; // } /* done! */ return LOAD_SUCCESS; } schismtracker-20250313/fmt/aiff.c000066400000000000000000000320661476471630300164760ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* --------------------------------------------------------------------- */ #include "headers.h" #include "bswap.h" #include "ieee-float.h" #include "log.h" #include "fmt.h" #include "mem.h" /* --------------------------------------------------------------------- */ struct aiff_chunk_vhdr { uint32_t smp_highoct_1shot; uint32_t smp_highoct_repeat; uint32_t smp_cycle_highoct; uint16_t smp_per_sec; uint8_t num_octaves; uint8_t compression; // 0 = none, 1 = fibonacci-delta uint32_t volume; // fixed point, 65536 = 1.0 }; struct aiff_chunk_comm { uint16_t num_channels; uint32_t num_frames; uint16_t sample_size; unsigned char sample_rate[10]; // IEEE-extended }; static int aiff_chunk_vhdr_read(const void *data, size_t size, void *void_vhdr) { struct aiff_chunk_vhdr *vhdr = (struct aiff_chunk_vhdr *)void_vhdr; slurp_t fp; slurp_memstream(&fp, (uint8_t *)data, size); #define READ_VALUE(name) \ do { if (slurp_read(&fp, &vhdr->name, sizeof(vhdr->name)) != sizeof(vhdr->name)) { unslurp(&fp); return 0; } } while (0) READ_VALUE(smp_highoct_1shot); READ_VALUE(smp_highoct_repeat); READ_VALUE(smp_cycle_highoct); READ_VALUE(smp_per_sec); READ_VALUE(num_octaves); READ_VALUE(compression); READ_VALUE(volume); #undef READ_VALUE unslurp(&fp); return 1; } static int aiff_chunk_comm_read(const void *data, size_t size, void *void_comm) { struct aiff_chunk_comm *comm = (struct aiff_chunk_comm *)void_comm; slurp_t fp; slurp_memstream(&fp, (uint8_t *)data, size); #define READ_VALUE(name) \ do { if (slurp_read(&fp, &comm->name, sizeof(comm->name)) != sizeof(comm->name)) { unslurp(&fp); return 0; } } while (0) READ_VALUE(num_channels); READ_VALUE(num_frames); READ_VALUE(sample_size); READ_VALUE(sample_rate); #undef READ_VALUE unslurp(&fp); return 1; } // other chunks that might exist: "NAME", "AUTH", "ANNO", "(c) " // Wish I could do this: //#define ID(x) ((0[#x] << 24) | (1[#x] << 16) | (2[#x] << 8) | (3[#x])) // It works great, but gcc doesn't think it's a constant, so it can't be used in a switch. // (although with -O, it definitely does optimize it into a constant...) #define ID_FORM 0x464F524D #define ID_8SVX 0x38535658 #define ID_VHDR 0x56484452 #define ID_BODY 0x424F4459 #define ID_NAME 0x4E414D45 #define ID_AUTH 0x41555448 #define ID_ANNO 0x414E4E4F #define ID__C__ 0x28632920 /* "(c) " */ #define ID_AIFF 0x41494646 #define ID_COMM 0x434F4D4D #define ID_SSND 0x53534E44 /* --------------------------------------------------------------------- */ static int read_iff_(dmoz_file_t *file, song_sample_t *smp, slurp_t *fp) { uint32_t filetype = 0; iff_chunk_t chunk = {0}; iff_chunk_t vhdr = {0}, body = {0}, name = {0}, auth = {0}, anno = {0}, ssnd = {0}, comm = {0}; if (!iff_chunk_peek(&chunk, fp)) return 0; if (chunk.id != ID_FORM) return 0; if (iff_chunk_read(&chunk, fp, &filetype, sizeof(filetype)) != sizeof(filetype)) return 0; // jump "into" the FORM chunk slurp_seek(fp, chunk.offset + sizeof(filetype), SEEK_SET); switch (bswapBE32(filetype)) { case ID_8SVX: { struct aiff_chunk_vhdr chunk_vhdr; while (iff_chunk_peek(&chunk, fp)) { switch (chunk.id) { case ID_VHDR: vhdr = chunk; break; case ID_BODY: body = chunk; break; case ID_NAME: name = chunk; break; case ID_AUTH: auth = chunk; break; case ID_ANNO: anno = chunk; break; default: break; } } if (!(vhdr.id && body.id)) return 0; if (!iff_chunk_receive(&vhdr, fp, aiff_chunk_vhdr_read, &chunk_vhdr)) return 0; if (chunk_vhdr.compression) { log_appendf(4, "error: compressed 8SVX files are unsupported"); return 0; } if (chunk_vhdr.num_octaves != 1) { log_appendf(4, "warning: 8SVX file contains %d octaves", chunk_vhdr.num_octaves); } if (file) { file->smp_speed = bswapBE16(chunk_vhdr.smp_per_sec); file->smp_length = body.size; file->description = "8SVX sample"; file->type = TYPE_SAMPLE_PLAIN; } if (!name.id) name = auth; if (!name.id) name = anno; if (name.id) { SCHISM_VLA_ALLOC(unsigned char, title, name.size); iff_chunk_read(&name, fp, title, sizeof(title)); if (file) file->title = strn_dup((const char *)title, sizeof(title)); if (smp) { size_t len = MIN(sizeof(smp->name), sizeof(title)); memcpy(smp->name, title, len); smp->name[len] = '\0'; } SCHISM_VLA_FREE(title); } if (smp) { smp->c5speed = bswapBE16(chunk_vhdr.smp_per_sec); smp->length = body.size; iff_read_sample(&body, fp, smp, SF_BE | SF_PCMS | SF_8 | SF_M, 0); smp->volume = 64*4; smp->global_volume = 64; // this is done kinda weird smp->loop_end = bswapBE32(chunk_vhdr.smp_highoct_repeat); if (smp->loop_end) { smp->loop_start = bswapBE32(chunk_vhdr.smp_highoct_1shot); smp->loop_end += smp->loop_start; if (smp->loop_start > smp->length) smp->loop_start = 0; if (smp->loop_end > smp->length) smp->loop_end = smp->length; if (smp->loop_start + 2 < smp->loop_end) smp->flags |= CHN_LOOP; } } return 1; } case ID_AIFF: { struct aiff_chunk_comm chunk_comm; while (iff_chunk_peek(&chunk, fp)) { switch (chunk.id) { case ID_COMM: comm = chunk; break; case ID_SSND: ssnd = chunk; break; case ID_NAME: name = chunk; break; default: break; } } if (!(comm.id && ssnd.id)) return 0; if (!iff_chunk_receive(&comm, fp, aiff_chunk_comm_read, &chunk_comm)) return 0; if (file) { file->smp_speed = float_decode_ieee_80(chunk_comm.sample_rate); file->smp_length = bswapBE32(chunk_comm.num_frames); file->description = "Audio IFF sample"; file->type = TYPE_SAMPLE_PLAIN; } if (name.id) { SCHISM_VLA_ALLOC(unsigned char, title, name.size); iff_chunk_read(&chunk, fp, title, SCHISM_VLA_SIZEOF(title)); if (file) file->title = strn_dup((const char *)title, SCHISM_VLA_SIZEOF(title)); if (smp) { int len = MIN(sizeof(smp->name), SCHISM_VLA_SIZEOF(title)); memcpy(smp->name, title, len); smp->name[len] = 0; } SCHISM_VLA_FREE(title); } /* TODO loop points */ if (smp) { uint32_t flags = SF_BE | SF_PCMS; switch (bswapBE16(chunk_comm.num_channels)) { default: log_appendf(4, "warning: multichannel AIFF is unsupported"); SCHISM_FALLTHROUGH; case 1: flags |= SF_M; break; case 2: flags |= SF_SI; break; } switch ((bswapBE16(chunk_comm.sample_size) + 7) & ~7) { default: log_appendf(4, "warning: AIFF has unsupported bit-width"); SCHISM_FALLTHROUGH; case 8: flags |= SF_8; break; case 16: flags |= SF_16; break; case 24: flags |= SF_24; break; case 32: flags |= SF_32; break; } // TODO: data checking; make sure sample count and byte size agree // (and if not, cut to shorter of the two) smp->c5speed = float_decode_ieee_80(chunk_comm.sample_rate); smp->length = bswapBE32(chunk_comm.num_frames); smp->volume = 64*4; smp->global_volume = 64; // the audio data starts 8 bytes into the chunk // (don't care about the block alignment stuff) iff_read_sample(&ssnd, fp, smp, flags, 8); } return 1; } default: break; } return 0; } /* --------------------------------------------------------------------- */ int fmt_aiff_read_info(dmoz_file_t *file, slurp_t *fp) { return read_iff_(file, NULL, fp); } int fmt_aiff_load_sample(slurp_t *fp, song_sample_t *smp) { return read_iff_(NULL, smp, fp); } /* --------------------------------------------------------------------- */ struct aiff_writedata { long comm_frames, ssnd_size; // seek positions for writing header data size_t numbytes; // how many bytes have been written int bps; // bytes per sample int swap; // should be byteswapped? }; static int aiff_header(disko_t *fp, int bits, int channels, int rate, const char *name, size_t length, struct aiff_writedata *awd /* out */) { int16_t s; uint32_t ul; int tlen, bps = 1; uint8_t b[10]; bps *= ((bits + 7) / 8); /* note: channel multiply is done below -- need single-channel value for the COMM chunk */ /* write a very large size for now */ disko_write(fp, "FORM\377\377\377\377AIFF", 12); if (name && *name) { disko_write(fp, "NAME", 4); tlen = strlen(name); ul = (tlen + 1) & ~1; /* must be even */ ul = bswapBE32(ul); disko_write(fp, &ul, 4); disko_write(fp, name, tlen); if (tlen & 1) disko_putc(fp, '\0'); } /* Common Chunk The Common Chunk describes fundamental parameters of the sampled sound. typedef struct { ID ckID; // 'COMM' long ckSize; // 18 short numChannels; unsigned long numSampleFrames; short sampleSize; extended sampleRate; } CommonChunk; */ disko_write(fp, "COMM", 4); ul = bswapBE32(18); /* chunk size -- won't change */ disko_write(fp, &ul, 4); s = bswapBE16(channels); disko_write(fp, &s, 2); if (awd) awd->comm_frames = disko_tell(fp); ul = bswapBE32(length); /* num sample frames */ disko_write(fp, &ul, 4); s = bswapBE16(bits); disko_write(fp, &s, 2); float_encode_ieee_80(rate, b); disko_write(fp, b, 10); /* NOW do this (sample size in AIFF is indicated per channel, not per frame) */ bps *= channels; /* == number of bytes per (stereo) sample */ /* Sound Data Chunk The Sound Data Chunk contains the actual sample frames. typedef struct { ID ckID; // 'SSND' long ckSize; // data size in bytes, *PLUS EIGHT* (for offset and blockSize) unsigned long offset; // just set this to 0... unsigned long blockSize; // likewise unsigned char soundData[]; } SoundDataChunk; */ disko_write(fp, "SSND", 4); if (awd) awd->ssnd_size = disko_tell(fp); ul = bswapBE32(length * bps + 8); disko_write(fp, &ul, 4); ul = bswapBE32(0); disko_write(fp, &ul, 4); disko_write(fp, &ul, 4); return bps; } /* --------------------------------------------------------------------- */ int fmt_aiff_save_sample(disko_t *fp, song_sample_t *smp) { if (smp->flags & CHN_ADLIB) return SAVE_UNSUPPORTED; int bps; uint32_t ul; uint32_t flags = SF_BE | SF_PCMS; flags |= (smp->flags & CHN_16BIT) ? SF_16 : SF_8; flags |= (smp->flags & CHN_STEREO) ? SF_SI : SF_M; bps = aiff_header(fp, (smp->flags & CHN_16BIT) ? 16 : 8, (smp->flags & CHN_STEREO) ? 2 : 1, smp->c5speed, smp->name, smp->length, NULL); if (csf_write_sample(fp, smp, flags, UINT32_MAX) != smp->length * bps) { log_appendf(4, "AIFF: unexpected data size written"); return SAVE_INTERNAL_ERROR; } /* TODO: loop data */ /* fix the length in the file header */ ul = disko_tell(fp) - 8; ul = bswapBE32(ul); disko_seek(fp, 4, SEEK_SET); disko_write(fp, &ul, 4); return SAVE_SUCCESS; } int fmt_aiff_export_head(disko_t *fp, int bits, int channels, int rate) { struct aiff_writedata *awd = malloc(sizeof(struct aiff_writedata)); if (!awd) return DW_ERROR; fp->userdata = awd; awd->bps = aiff_header(fp, bits, channels, rate, NULL, ~0, awd); awd->numbytes = 0; #if WORDS_BIGENDIAN awd->swap = 0; #else awd->swap = (bits > 8); #endif return DW_OK; } int fmt_aiff_export_body(disko_t *fp, const uint8_t *data, size_t length) { struct aiff_writedata *awd = fp->userdata; if (length % awd->bps) { log_appendf(4, "AIFF export: received uneven length"); return DW_ERROR; } awd->numbytes += length; if (awd->swap) { const int16_t *ptr = (const int16_t *) data; uint16_t v; length /= 2; while (length--) { v = *ptr; v = bswapBE16(v); disko_write(fp, &v, 2); ptr++; } } else { disko_write(fp, data, length); } return DW_OK; } int fmt_aiff_export_silence(disko_t *fp, long bytes) { struct aiff_writedata *awd = fp->userdata; awd->numbytes += bytes; disko_seek(fp, bytes, SEEK_CUR); return DW_OK; } int fmt_aiff_export_tail(disko_t *fp) { struct aiff_writedata *awd = fp->userdata; uint32_t ul; /* fix the length in the file header */ ul = disko_tell(fp) - 8; ul = bswapBE32(ul); disko_seek(fp, 4, SEEK_SET); disko_write(fp, &ul, 4); /* write the other lengths */ disko_seek(fp, awd->comm_frames, SEEK_SET); ul = bswapBE32(awd->numbytes / awd->bps); disko_write(fp, &ul, 4); disko_seek(fp, awd->ssnd_size, SEEK_SET); ul = bswapBE32(awd->numbytes + 8); disko_write(fp, &ul, 4); free(awd); return DW_OK; } schismtracker-20250313/fmt/ams.c000066400000000000000000000035141476471630300163450ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "fmt.h" #include "mem.h" /* --------------------------------------------------------------------- */ /* TODO: test this code. Modplug seems to have a totally different idea of ams than this. I don't know what this data's supposed to be for :) */ /* btw: AMS stands for "Advanced Module System" */ int fmt_ams_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char magic[7] = {0}; slurp_read(fp, &magic, sizeof(magic)); if (!(slurp_length(fp) > 38 && memcmp(magic, "AMShdr\x1a", 7) == 0)) return 0; slurp_seek(fp, 7, SEEK_SET); int n = slurp_getc(fp); n = CLAMP(n, 0, 30); unsigned char title[30] = {0}; slurp_read(fp, &title, n); file->description = "Velvet Studio"; /*file->extension = str_dup("ams");*/ file->title = strn_dup((const char *)title, n); file->type = TYPE_MODULE_XM; return 1; } schismtracker-20250313/fmt/au.c000066400000000000000000000136731476471630300162010ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "fmt.h" #include "mem.h" enum { AU_ULAW = 1, /* µ-law */ AU_PCM_8 = 2, /* 8-bit linear PCM (RS_PCM8U in Modplug) */ AU_PCM_16 = 3, /* 16-bit linear PCM (RS_PCM16M) */ AU_PCM_24 = 4, /* 24-bit linear PCM */ AU_PCM_32 = 5, /* 32-bit linear PCM */ AU_IEEE_32 = 6, /* 32-bit IEEE floating point */ AU_IEEE_64 = 7, /* 64-bit IEEE floating point */ AU_ISDN_ULAW_ADPCM = 23, /* 8-bit ISDN µ-law (CCITT G.721 ADPCM compressed) */ }; struct au_header { char magic[4]; /* ".snd" */ uint32_t data_offset, data_size, encoding, sample_rate, channels; }; static int read_header_au(struct au_header *hdr, slurp_t *fp) { #define READ_VALUE(name) \ if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) return 0 READ_VALUE(magic); READ_VALUE(data_offset); READ_VALUE(data_size); READ_VALUE(encoding); READ_VALUE(sample_rate); READ_VALUE(channels); #undef READ_VALUE if (memcmp(hdr->magic, ".snd", sizeof(hdr->magic))) return 0; /* now byteswap */ hdr->data_offset = bswapBE32(hdr->data_offset); hdr->data_size = bswapBE32(hdr->data_size); hdr->encoding = bswapBE32(hdr->encoding); hdr->sample_rate = bswapBE32(hdr->sample_rate); hdr->channels = bswapBE32(hdr->channels); const size_t memsize = slurp_length(fp); if (hdr->data_offset < 24 || hdr->data_offset > memsize || hdr->data_size > memsize - hdr->data_offset) return 0; switch (hdr->channels) { case 1: case 2: break; default: return 0; } return 1; } /* --------------------------------------------------------------------- */ int fmt_au_read_info(dmoz_file_t *file, slurp_t *fp) { struct au_header au; if (!read_header_au(&au, fp)) return 0; /* calculate length and flags */ file->smp_length = au.data_size / au.channels; file->smp_flags = 0; switch (au.encoding) { case AU_PCM_16: file->smp_flags |= CHN_16BIT; file->smp_length /= 2; break; case AU_PCM_24: file->smp_flags |= CHN_16BIT; file->smp_length /= 3; break; case AU_PCM_32: case AU_IEEE_32: file->smp_flags |= CHN_16BIT; file->smp_length /= 4; break; case AU_IEEE_64: file->smp_flags |= CHN_16BIT; file->smp_length /= 8; break; default: return 0; } if (au.channels == 2) file->smp_flags |= CHN_STEREO; file->smp_speed = au.sample_rate; file->smp_defvol = 64; file->smp_gblvol = 64; file->description = "AU Sample"; file->smp_filename = file->base; file->type = TYPE_SAMPLE_PLAIN; /* now we can grab the title */ if (au.data_offset > 24) { size_t extlen = au.data_offset - 24; slurp_seek(fp, 24, SEEK_SET); char *title = mem_alloc(extlen + 1); if (slurp_read(fp, title, extlen) != extlen) return 0; title[extlen] = '\0'; file->title = title; } return 1; } /* --------------------------------------------------------------------- */ int fmt_au_load_sample(slurp_t *fp, song_sample_t *smp) { struct au_header au; uint32_t sflags = SF_BE; if (!read_header_au(&au, fp)) return 0; smp->c5speed = au.sample_rate; smp->volume = 64 * 4; smp->global_volume = 64; smp->length = au.data_size; switch (au.encoding) { case AU_PCM_8: sflags |= SF_8 | SF_PCMS; break; case AU_PCM_16: sflags |= SF_16 | SF_PCMS; smp->length /= 2; break; case AU_PCM_24: sflags |= SF_24 | SF_PCMS; smp->length /= 3; break; case AU_PCM_32: sflags |= SF_32 | SF_PCMS; smp->length /= 4; break; case AU_IEEE_32: sflags |= SF_32 | SF_IEEE; smp->length /= 4; break; case AU_IEEE_64: sflags |= SF_64 | SF_IEEE; smp->length /= 8; break; default: return 0; } switch (au.channels) { case 1: sflags |= SF_M; break; case 2: sflags |= SF_SI; smp->length /= 2; break; default: return 0; } if (au.data_offset > sizeof(au)) { size_t extlen = MIN(au.data_offset - sizeof(au), sizeof(smp->name) - 1); if (slurp_read(fp, smp->name, extlen) != extlen) return 0; smp->name[extlen] = 0; } return csf_read_sample(smp, sflags, fp); } /* --------------------------------------------------------------------------------------------------------- */ int fmt_au_save_sample(disko_t *fp, song_sample_t *smp) { if (smp->flags & CHN_ADLIB) return SAVE_UNSUPPORTED; struct au_header au; uint32_t ln; memcpy(au.magic, ".snd", 4); au.data_offset = bswapBE32(49); // header is 24 bytes, sample name is 25 ln = smp->length; if (smp->flags & CHN_16BIT) { ln *= 2; au.encoding = bswapBE32(AU_PCM_16); } else { au.encoding = bswapBE32(AU_PCM_8); } au.sample_rate = bswapBE32(smp->c5speed); if (smp->flags & CHN_STEREO) { ln *= 2; au.channels = bswapBE32(2); } else { au.channels = bswapBE32(1); } au.data_size = bswapBE32(ln); disko_write(fp, &au, sizeof(au)); disko_write(fp, smp->name, 25); csf_write_sample(fp, smp, SF_BE | SF_PCMS | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8) | ((smp->flags & CHN_STEREO) ? SF_SI : SF_M), UINT32_MAX); return SAVE_SUCCESS; } schismtracker-20250313/fmt/compression.c000066400000000000000000000633501476471630300201320ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "fmt.h" #include "bswap.h" // ------------------------------------------------------------------------------------------------------------ // IT decompression code from itsex.c (Cubic Player) and load_it.cpp (Modplug) // (I suppose this could be considered a merge between the two.) static uint32_t it_readbits(int8_t n, uint32_t *bitbuf, uint32_t *bitnum, slurp_t *fp) { uint32_t value = 0; uint32_t i = n; // this could be better while (i--) { if (!*bitnum) { *bitbuf = slurp_getc(fp); *bitnum = 8; } value >>= 1; value |= (*bitbuf) << 31; (*bitbuf) >>= 1; (*bitnum)--; } return value >> (32 - n); } uint32_t it_decompress8(void *dest, uint32_t len, slurp_t *fp, int it215, int channels) { int8_t *destpos; // position in destination buffer which will be returned uint16_t blklen; // length of compressed data block in samples uint16_t blkpos; // position in block uint8_t width; // actual "bit width" uint16_t value; // value read from file to be processed int8_t d1, d2; // integrator buffers (d2 for it2.15) int8_t v; // sample value uint32_t bitbuf, bitnum; // state for it_readbits const int64_t startpos = slurp_tell(fp); if (startpos < 0) return 0; // wat const size_t filelen = slurp_length(fp); destpos = (int8_t *) dest; // now unpack data till the dest buffer is full while (len) { // read a new block of compressed data and reset variables // block layout: word size, bytes data { int c1 = slurp_getc(fp); int c2 = slurp_getc(fp); int64_t pos = slurp_tell(fp); if (pos < 0) return 0; if (c1 == EOF || c2 == EOF || pos + (c1 | (c2 << 8)) > (int64_t)filelen) return pos - startpos; } bitbuf = bitnum = 0; blklen = MIN(0x8000, len); blkpos = 0; width = 9; // start with width of 9 bits d1 = d2 = 0; // reset integrator buffers // now uncompress the data block while (blkpos < blklen) { if (width > 9) { // illegal width, abort printf("Illegal bit width %d for 8-bit sample\n", width); return slurp_tell(fp); } value = it_readbits(width, &bitbuf, &bitnum, fp); if (width < 7) { // method 1 (1-6 bits) // check for "100..." if (value == 1 << (width - 1)) { // yes! value = it_readbits(3, &bitbuf, &bitnum, fp) + 1; // read new width width = (value < width) ? value : value + 1; // and expand it continue; // ... next value } } else if (width < 9) { // method 2 (7-8 bits) uint8_t border = (0xFF >> (9 - width)) - 4; // lower border for width chg if (value > border && value <= (border + 8)) { value -= border; // convert width to 1-8 width = (value < width) ? value : value + 1; // and expand it continue; // ... next value } } else { // method 3 (9 bits) // bit 8 set? if (value & 0x100) { width = (value + 1) & 0xff; // new width... continue; // ... and next value } } // now expand value to signed byte if (width < 8) { uint8_t shift = 8 - width; v = (value << shift); v >>= shift; } else { v = (int8_t) value; } // integrate upon the sample values d1 += v; d2 += d1; // .. and store it into the buffer *destpos = it215 ? d2 : d1; destpos += channels; blkpos++; } // now subtract block length from total length and go on len -= blklen; } return slurp_tell(fp) - startpos; } // Mostly the same as above. uint32_t it_decompress16(void *dest, uint32_t len, slurp_t *fp, int it215, int channels) { int16_t *destpos; // position in destination buffer which will be returned uint16_t blklen; // length of compressed data block in samples uint16_t blkpos; // position in block uint8_t width; // actual "bit width" uint32_t value; // value read from file to be processed int16_t d1, d2; // integrator buffers (d2 for it2.15) int16_t v; // sample value uint32_t bitbuf, bitnum; // state for it_readbits const int64_t startpos = slurp_tell(fp); if (startpos < 0) return 0; // wat const size_t filelen = slurp_length(fp); destpos = (int16_t *) dest; // now unpack data till the dest buffer is full while (len) { // read a new block of compressed data and reset variables // block layout: word size, bytes data { int c1 = slurp_getc(fp); int c2 = slurp_getc(fp); int64_t pos = slurp_tell(fp); if (pos < 0) return 0; if (c1 == EOF || c2 == EOF || pos + (c1 | (c2 << 8)) > (int64_t)filelen) return pos; } bitbuf = bitnum = 0; blklen = MIN(0x4000, len); // 0x4000 samples => 0x8000 bytes again blkpos = 0; width = 17; // start with width of 17 bits d1 = d2 = 0; // reset integrator buffers // now uncompress the data block while (blkpos < blklen) { if (width > 17) { // illegal width, abort printf("Illegal bit width %d for 16-bit sample\n", width); return slurp_tell(fp); } value = it_readbits(width, &bitbuf, &bitnum, fp); if (width < 7) { // method 1 (1-6 bits) // check for "100..." if (value == (uint32_t) 1 << (width - 1)) { // yes! value = it_readbits(4, &bitbuf, &bitnum, fp) + 1; // read new width width = (value < width) ? value : value + 1; // and expand it continue; // ... next value } } else if (width < 17) { // method 2 (7-16 bits) uint16_t border = (0xFFFF >> (17 - width)) - 8; // lower border for width chg if (value > border && value <= (uint32_t) (border + 16)) { value -= border; // convert width to 1-8 width = (value < width) ? value : value + 1; // and expand it continue; // ... next value } } else { // method 3 (17 bits) // bit 16 set? if (value & 0x10000) { width = (value + 1) & 0xff; // new width... continue; // ... and next value } } // now expand value to signed word if (width < 16) { uint8_t shift = 16 - width; v = (value << shift); v >>= shift; } else { v = (int16_t) value; } // integrate upon the sample values d1 += v; d2 += d1; // .. and store it into the buffer *destpos = it215 ? d2 : d1; destpos += channels; blkpos++; } // now subtract block length from total length and go on len -= blklen; } return slurp_tell(fp) - startpos; } // ------------------------------------------------------------------------------------------------------------ // MDL sample decompression static inline uint16_t mdl_read_bits(uint32_t *bitbuf, uint32_t *bitnum, slurp_t *fp, int8_t n) { uint16_t v = (uint16_t)((*bitbuf) & ((1 << n) - 1) ); (*bitbuf) >>= n; (*bitnum) -= n; if ((*bitnum) <= 24) { (*bitbuf) |= (((uint32_t)slurp_getc(fp)) << (*bitnum)); (*bitnum) += 8; } return v; } uint32_t mdl_decompress8(void *dest, uint32_t len, slurp_t *fp) { const int64_t startpos = slurp_tell(fp); if (startpos < 0) return 0; // wat const size_t filelen = slurp_length(fp); uint32_t bitnum = 32; uint8_t dlt = 0; // first 4 bytes indicate packed length uint32_t v; if (slurp_read(fp, &v, sizeof(v)) != sizeof(v)) return 0; v = bswapLE32(v); v = MIN(v, filelen - startpos) + 4; uint32_t bitbuf; if (slurp_read(fp, &bitbuf, sizeof(bitbuf)) != sizeof(bitbuf)) return 0; bitbuf = bswapLE32(bitbuf); uint8_t *data = dest; for (uint32_t j=0; j 4 GB uncompressed data * 1.2 24 Oct 2012 - Add note about using binary mode in stdio * - Fix comparisons of differently signed integers * 1.3 24 Aug 2013 - Return unused input from blast() * - Fix test code to correctly report unused input * - Enable the provision of initial input to blast() */ #include /* for NULL */ #include /* for setjmp(), longjmp(), and jmp_buf */ #define MAXBITS 13 /* maximum code length */ #define MAXWIN 4096 /* maximum output window size */ #define HUFFMAN_CHUNK_SIZE 65536 /* chunk size for input */ /* input and output state */ struct state { /* input state */ slurp_t *slurp; unsigned char inbuf[HUFFMAN_CHUNK_SIZE]; /* input buffer */ unsigned char *in; /* input buffer position */ uint32_t left; /* available input at in */ int32_t bitbuf; /* bit buffer */ int32_t bitcnt; /* number of bits in bit buffer */ /* input limit error return state for bits() and decode() */ jmp_buf env; /* output state */ disko_t *disko; /* output buffer */ uint32_t next; /* index of next write location in out[] */ int32_t first; /* true to check distances (for first 4K) */ unsigned char out[MAXWIN]; /* output buffer and sliding window */ }; /* * Return need bits from the input stream. This always leaves less than * eight bits in the buffer. bits() works properly for need == 0. * * Format notes: * * - Bits are stored in bytes from the least significant bit to the most * significant bit. Therefore bits are dropped from the bottom of the bit * buffer, using shift right, and new bytes are appended to the top of the * bit buffer, using shift left. */ static int32_t huffman_bits(struct state *s, int32_t need) { int32_t val; /* bit accumulator */ /* load at least need bits into val */ val = s->bitbuf; while (s->bitcnt < need) { if (s->left == 0) { s->left = slurp_read(s->slurp, s->inbuf, sizeof(s->inbuf)); s->in = s->inbuf; if (s->left == 0) longjmp(s->env, 1); /* out of input */ } val |= (int32_t)(*(s->in)++) << s->bitcnt; /* load eight bits */ s->left--; s->bitcnt += 8; } /* drop need bits and update buffer, always zero to seven bits left */ s->bitbuf = val >> need; s->bitcnt -= need; /* return need bits, zeroing the bits above that */ return val & ((1 << need) - 1); } /* * Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of * each length, which for a canonical code are stepped through in order. * symbol[] are the symbol values in canonical order, where the number of * entries is the sum of the counts in count[]. The decoding process can be * seen in the function decode() below. */ struct huffman { int16_t *count; /* number of symbols of each length */ int16_t *symbol; /* canonically ordered symbols */ }; /* * Decode a code from the stream s using huffman table h. Return the symbol or * a negative value if there is an error. If all of the lengths are zero, i.e. * an empty code, or if the code is incomplete and an invalid code is received, * then -9 is returned after reading MAXBITS bits. * * Format notes: * * - The codes as stored in the compressed data are bit-reversed relative to * a simple integer ordering of codes of the same lengths. Hence below the * bits are pulled from the compressed data one at a time and used to * build the code value reversed from what is in the stream in order to * permit simple integer comparisons for decoding. * * - The first code for the shortest length is all ones. Subsequent codes of * the same length are simply integer decrements of the previous code. When * moving up a length, a one bit is appended to the code. For a complete * code, the last code of the longest length will be all zeros. To support * this ordering, the bits pulled during decoding are inverted to apply the * more "natural" ordering starting with all zeros and incrementing. */ static int32_t huffman_decode(struct state *s, struct huffman *h) { int32_t len; /* current number of bits in code */ int32_t code; /* len bits being decoded */ int32_t first; /* first code of length len */ int32_t count; /* number of codes of length len */ int32_t index; /* index of first code of length len in symbol table */ int32_t bitbuf; /* bits from stream */ int32_t left; /* bits left in next or left to process */ int16_t *next; /* next number of codes */ bitbuf = s->bitbuf; left = s->bitcnt; code = first = index = 0; len = 1; next = h->count + 1; while (1) { while (left--) { code |= (bitbuf & 1) ^ 1; /* invert code */ bitbuf >>= 1; count = *next++; if (code < first + count) { /* if length len, return symbol */ s->bitbuf = bitbuf; s->bitcnt = (s->bitcnt - len) & 7; return h->symbol[index + (code - first)]; } index += count; /* else update for next length */ first += count; first <<= 1; code <<= 1; len++; } left = (MAXBITS+1) - len; if (left == 0) break; if (s->left == 0) { s->left = slurp_read(s->slurp, s->inbuf, sizeof(s->inbuf)); s->in = s->inbuf; if (s->left == 0) longjmp(s->env, 1); /* out of input */ } bitbuf = *(s->in)++; s->left--; if (left > 8) left = 8; } return -9; /* ran out of codes */ } /* * Given a list of repeated code lengths rep[0..n-1], where each byte is a * count (high four bits + 1) and a code length (low four bits), generate the * list of code lengths. This compaction reduces the size of the object code. * Then given the list of code lengths length[0..n-1] representing a canonical * Huffman code for n symbols, construct the tables required to decode those * codes. Those tables are the number of codes of each length, and the symbols * sorted by length, retaining their original order within each length. The * return value is zero for a complete code set, negative for an over- * subscribed code set, and positive for an incomplete code set. The tables * can be used if the return value is zero or positive, but they cannot be used * if the return value is negative. If the return value is zero, it is not * possible for decode() using that table to return an error--any stream of * enough bits will resolve to a symbol. If the return value is positive, then * it is possible for decode() using that table to return an error for received * codes past the end of the incomplete lengths. */ static int32_t huffman_construct(struct huffman *h, const unsigned char *rep, int32_t n) { int32_t symbol; /* current symbol when stepping through length[] */ int32_t len; /* current length when stepping through h->count[] */ int32_t left; /* number of possible codes left of current length */ int16_t offs[MAXBITS+1]; /* offsets in symbol table for each length */ int16_t length[256]; /* code lengths */ /* convert compact repeat counts into symbol bit length list */ symbol = 0; do { len = *rep++; left = (len >> 4) + 1; len &= 15; do { length[symbol++] = len; } while (--left); } while (--n); n = symbol; /* count number of codes of each length */ for (len = 0; len <= MAXBITS; len++) h->count[len] = 0; for (symbol = 0; symbol < n; symbol++) (h->count[length[symbol]])++; /* assumes lengths are within bounds */ if (h->count[0] == n) /* no codes! */ return 0; /* complete, but decode() will fail */ /* check for an over-subscribed or incomplete set of lengths */ left = 1; /* one possible code of zero length */ for (len = 1; len <= MAXBITS; len++) { left <<= 1; /* one more bit, double codes left */ left -= h->count[len]; /* deduct count from possible codes */ if (left < 0) return left; /* over-subscribed--return negative */ } /* left > 0 means incomplete */ /* generate offsets into symbol table for each length for sorting */ offs[1] = 0; for (len = 1; len < MAXBITS; len++) offs[len + 1] = offs[len] + h->count[len]; /* * put symbols in table sorted by length, by symbol order within each * length */ for (symbol = 0; symbol < n; symbol++) if (length[symbol] != 0) h->symbol[offs[length[symbol]]++] = symbol; /* return zero for complete set, positive for incomplete set */ return left; } /* * Decode PKWare Compression Library stream. * * Format notes: * * - First byte is 0 if literals are uncoded or 1 if they are coded. Second * byte is 4, 5, or 6 for the number of extra bits in the distance code. * This is the base-2 logarithm of the dictionary size minus six. * * - Compressed data is a combination of literals and length/distance pairs * terminated by an end code. Literals are either Huffman coded or * uncoded bytes. A length/distance pair is a coded length followed by a * coded distance to represent a string that occurs earlier in the * uncompressed data that occurs again at the current location. * * - A bit preceding a literal or length/distance pair indicates which comes * next, 0 for literals, 1 for length/distance. * * - If literals are uncoded, then the next eight bits are the literal, in the * normal bit order in the stream, i.e. no bit-reversal is needed. Similarly, * no bit reversal is needed for either the length extra bits or the distance * extra bits. * * - Literal bytes are simply written to the output. A length/distance pair is * an instruction to copy previously uncompressed bytes to the output. The * copy is from distance bytes back in the output stream, copying for length * bytes. * * - Distances pointing before the beginning of the output data are not * permitted. * * - Overlapped copies, where the length is greater than the distance, are * allowed and common. For example, a distance of one and a length of 518 * simply copies the last byte 518 times. A distance of four and a length of * twelve copies the last four bytes three times. A simple forward copy * ignoring whether the length is greater than the distance or not implements * this correctly. */ static int32_t huffman_decomp(struct state *s) { int32_t lit; /* true if literals are coded */ int32_t dict; /* log2(dictionary size) - 6 */ int32_t symbol; /* decoded symbol, extra bits for distance */ int32_t len; /* length for copy */ uint32_t dist; /* distance for copy */ int32_t copy; /* copy counter */ unsigned char *from, *to; /* copy pointers */ static int virgin = 1; /* build tables once */ static short litcnt[MAXBITS+1], litsym[256]; /* litcode memory */ static short lencnt[MAXBITS+1], lensym[16]; /* lencode memory */ static short distcnt[MAXBITS+1], distsym[64]; /* distcode memory */ static struct huffman litcode = {litcnt, litsym}; /* length code */ static struct huffman lencode = {lencnt, lensym}; /* length code */ static struct huffman distcode = {distcnt, distsym};/* distance code */ /* bit lengths of literal codes */ static const unsigned char litlen[] = { 11, 124, 8, 7, 28, 7, 188, 13, 76, 4, 10, 8, 12, 10, 12, 10, 8, 23, 8, 9, 7, 6, 7, 8, 7, 6, 55, 8, 23, 24, 12, 11, 7, 9, 11, 12, 6, 7, 22, 5, 7, 24, 6, 11, 9, 6, 7, 22, 7, 11, 38, 7, 9, 8, 25, 11, 8, 11, 9, 12, 8, 12, 5, 38, 5, 38, 5, 11, 7, 5, 6, 21, 6, 10, 53, 8, 7, 24, 10, 27, 44, 253, 253, 253, 252, 252, 252, 13, 12, 45, 12, 45, 12, 61, 12, 45, 44, 173}; /* bit lengths of length codes 0..15 */ static const unsigned char lenlen[] = {2, 35, 36, 53, 38, 23}; /* bit lengths of distance codes 0..63 */ static const unsigned char distlen[] = {2, 20, 53, 230, 247, 151, 248}; static const short base[16] = { /* base for length codes */ 3, 2, 4, 5, 6, 7, 8, 9, 10, 12, 16, 24, 40, 72, 136, 264}; static const char extra[16] = { /* extra bits for length codes */ 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8}; /* set up decoding tables (once--might not be thread-safe) */ if (virgin) { huffman_construct(&litcode, litlen, sizeof(litlen)); huffman_construct(&lencode, lenlen, sizeof(lenlen)); huffman_construct(&distcode, distlen, sizeof(distlen)); virgin = 0; } /* read header */ lit = huffman_bits(s, 8); if (lit > 1) return -1; dict = huffman_bits(s, 8); if (dict < 4 || dict > 6) return -2; /* decode literals and length/distance pairs */ do { if (huffman_bits(s, 1)) { /* get length */ symbol = huffman_decode(s, &lencode); len = base[symbol] + huffman_bits(s, extra[symbol]); if (len == 519) break; /* end code */ /* get distance */ symbol = len == 2 ? 2 : dict; dist = huffman_decode(s, &distcode) << symbol; dist += huffman_bits(s, symbol); dist++; if (s->first && dist > s->next) return -3; /* distance too far back */ /* copy length bytes from distance bytes back */ do { to = s->out + s->next; from = to - dist; copy = MAXWIN; if (s->next < dist) { from += copy; copy = dist; } copy -= s->next; if (copy > len) copy = len; len -= copy; s->next += copy; do { *to++ = *from++; } while (--copy); if (s->next == MAXWIN) { disko_write(s->disko, s->out, s->next); s->next = 0; s->first = 0; } } while (len != 0); } else { /* get literal and write it */ symbol = lit ? huffman_decode(s, &litcode) : huffman_bits(s, 8); s->out[s->next++] = symbol; if (s->next == MAXWIN) { disko_write(s->disko, s->out, s->next); s->next = 0; s->first = 0; } } } while (1); return 0; } /* Decompresses a huffman-encoded stream (PKWARE compression library). * Takes the input from `slurp' and outputs it into `disko'. */ int32_t huffman_decompress(slurp_t *slurp, disko_t *disko) { struct state s; /* input/output state */ int32_t err; /* return value */ /* initialize input state */ s.slurp = slurp; s.bitbuf = 0; s.bitcnt = 0; s.left = 0; /* initialize output state */ s.disko = disko; s.next = 0; s.first = 1; /* return if bits() or decode() tries to read past available input */ if (setjmp(s.env) != 0) /* if came back here via longjmp(), */ err = 2; /* then skip decomp(), return error */ else err = huffman_decomp(&s); /* decompress */ /* write any leftover output */ if (s.next && err == 0) disko_write(s.disko, s.out, s.next); return err; } schismtracker-20250313/fmt/d00.c000066400000000000000000000073441476471630300161550ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "fmt.h" #include "util.h" #include "mem.h" struct d00_header { unsigned char id[6]; unsigned char type; unsigned char version; unsigned char speed; // apparently this is in Hz? wtf unsigned char subsongs; // ignored for now unsigned char soundcard; unsigned char title[32], author[32], reserved[32]; // parapointers uint16_t tpoin; // not really sure what this is uint16_t sequence_paraptr; // patterns uint16_t instrument_paraptr; // adlib instruments uint16_t info_paraptr; // song message I guess uint16_t spfx_paraptr; // points to levpuls on v2 or spfx on v4 uint16_t endmark; // what? }; // This function, like many of the other read functions, also // performs sanity checks on the data itself. static int d00_header_read(struct d00_header *hdr, slurp_t *fp) { // we check if the length is larger than UINT16_MAX because // the parapointers wouldn't be able to fit all of the bits // otherwise. 119 is just the size of the header. const size_t fplen = slurp_length(fp); if (fplen <= 119 || fplen > UINT16_MAX) return 0; #define READ_VALUE(name) \ do { if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) { unslurp(fp); return 0; } } while (0) READ_VALUE(id); if (memcmp(hdr->id, "JCH\x26\x02\x66", sizeof(hdr->id))) return 0; // this should always be zero? READ_VALUE(type); if (hdr->type) return 0; READ_VALUE(version); if (hdr->version != 4) return 0; READ_VALUE(speed); // > EdLib always sets offset 0009h to 01h. You cannot make more than // > one piece of music at a time in the editor. READ_VALUE(subsongs); if (hdr->subsongs != 1) return 0; READ_VALUE(soundcard); if (hdr->soundcard != 0) return 0; READ_VALUE(title); READ_VALUE(author); READ_VALUE(reserved); READ_VALUE(tpoin); hdr->tpoin = bswapLE16(hdr->tpoin); READ_VALUE(sequence_paraptr); hdr->sequence_paraptr = bswapLE16(hdr->sequence_paraptr); READ_VALUE(instrument_paraptr); hdr->instrument_paraptr = bswapLE16(hdr->instrument_paraptr); READ_VALUE(info_paraptr); hdr->info_paraptr = bswapLE16(hdr->info_paraptr); READ_VALUE(spfx_paraptr); hdr->spfx_paraptr = bswapLE16(hdr->spfx_paraptr); READ_VALUE(endmark); hdr->endmark = bswapLE16(hdr->endmark); // verify the parapointers if (hdr->tpoin < 119 || hdr->sequence_paraptr < 119 || hdr->instrument_paraptr < 119 || hdr->info_paraptr < 119 || hdr->spfx_paraptr < 119 || hdr->endmark < 119) return 0; #undef READ_VALUE return 1; } int fmt_d00_read_info(dmoz_file_t *file, slurp_t *fp) { struct d00_header hdr; if (!d00_header_read(&hdr, fp)) return 0; file->title = strn_dup((const char *)hdr.title, sizeof(hdr.title)); file->description = "EdLib Tracker D00"; file->type = TYPE_MODULE_S3M; return 1; } schismtracker-20250313/fmt/dsm.c000066400000000000000000000253141476471630300163520ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "charset.h" #include "slurp.h" #include "fmt.h" #include "log.h" #include "mem.h" #include "player/sndfile.h" /* --------------------------------------------------------------------- */ struct dsm_chunk_patt { uint16_t length; uint8_t data[SCHISM_FAM_SIZE]; }; struct dsm_chunk_song { char title[28]; uint16_t version, flags; uint32_t pad; uint16_t ordnum, smpnum, patnum, chnnum; uint8_t gvol, mvol, is, it; uint8_t chnpan[16]; uint8_t orders[128]; }; struct dsm_chunk_inst { char filename[13]; uint16_t flags; uint8_t volume; uint32_t length, loop_start, loop_end, address_ptr; uint16_t c5speed, period; char name[28]; }; struct dsm_process_pattern_data { song_note_t *pattern; uint16_t nchn; uint8_t *chn_doesnt_match; }; /* sample flags */ enum { DSM_SMP_LOOP_ACTIVE = 0x01, DSM_SMP_SIGNED_PCM = 0x02, DSM_SMP_PACKED_PCM = 0x04, DSM_SMP_DELTA_PCM = 0x40, }; /* pattern byte flags/masks */ enum { DSM_PAT_NOTE_PRESENT = 0x80, DSM_PAT_INST_PRESENT = 0x40, DSM_PAT_VOL_PRESENT = 0x20, DSM_PAT_CMD_PRESENT = 0x10, DSM_PAT_CHN_NUM_MASK = 0x0F, }; #define ID_SONG UINT32_C(0x534F4E47) #define ID_INST UINT32_C(0x494E5354) #define ID_PATT UINT32_C(0x50415454) int fmt_dsm_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char riff[4], dsmf[4]; if (!(slurp_length(fp) > 40)) return 0; if (slurp_read(fp, riff, sizeof(riff)) != sizeof(dsmf) || memcmp(riff, "RIFF", 4)) return 0; slurp_seek(fp, 4, SEEK_CUR); if (slurp_read(fp, dsmf, sizeof(dsmf)) != sizeof(dsmf) || memcmp(dsmf, "DSMF", 4)) return 0; iff_chunk_t chunk; while (iff_chunk_peek_ex(&chunk, fp, IFF_CHUNK_SIZE_LE)) { if (chunk.id == ID_SONG) { /* we only need the title, really */ unsigned char title[28]; iff_chunk_read(&chunk, fp, title, sizeof(title)); file->title = strn_dup((const char *)title, sizeof(title)); break; } } file->description = "Digital Sound Interface Kit"; /*file->extension = str_dup("dsm");*/ file->type = TYPE_MODULE_MOD; return 1; } static int dsm_chunk_song_read(const void *data, size_t size, void *void_song) { struct dsm_chunk_song *song = (struct dsm_chunk_song *)void_song; slurp_t fp; slurp_memstream(&fp, (uint8_t *)data, size); #define READ_VALUE(name) \ do { if (slurp_read(&fp, &song->name, sizeof(song->name)) != sizeof(song->name)) { unslurp(&fp); return 0; } } while (0) READ_VALUE(title); READ_VALUE(version); READ_VALUE(flags); READ_VALUE(pad); READ_VALUE(ordnum); READ_VALUE(smpnum); READ_VALUE(patnum); READ_VALUE(chnnum); READ_VALUE(gvol); READ_VALUE(mvol); READ_VALUE(is); READ_VALUE(it); READ_VALUE(chnpan); READ_VALUE(orders); #undef READ_VALUE unslurp(&fp); return 1; } static int dsm_chunk_inst_read(const void *data, size_t size, void *void_inst) { struct dsm_chunk_inst *inst = (struct dsm_chunk_inst *)void_inst; slurp_t fp; slurp_memstream(&fp, (uint8_t *)data, size); #define READ_VALUE(name) \ do { if (slurp_read(&fp, &inst->name, sizeof(inst->name)) != sizeof(inst->name)) { unslurp(&fp); return 0; } } while (0) READ_VALUE(filename); READ_VALUE(flags); READ_VALUE(volume); READ_VALUE(length); READ_VALUE(loop_start); READ_VALUE(loop_end); READ_VALUE(address_ptr); READ_VALUE(c5speed); READ_VALUE(period); READ_VALUE(name); #undef READ_VALUE unslurp(&fp); return 1; } static int dsm_process_pattern(const void *data, size_t size, void *userdata) { const struct dsm_process_pattern_data *ppd = userdata; /* grab the header length */ slurp_t fp; slurp_memstream(&fp, (uint8_t *)data, size); uint16_t hdr_len; if (slurp_read(&fp, &hdr_len, sizeof(hdr_len)) != sizeof(hdr_len)) return 0; unslurp(&fp); /* reopen the memstream, but this time limit the size to the * chunk size or header length, whichever is smaller */ slurp_memstream(&fp, ((uint8_t *)data) + 2, MIN(size - 2, hdr_len)); /* make sure our offset doesn't pass the length */ #define DSM_ASSERT_OFFSET() \ do { \ if (slurp_eof(&fp)) { \ log_appendf(4, " WARNING: Offset (%" PRId64 ") passed length (%" PRIuSZ ") while parsing pattern!", slurp_tell(&fp), slurp_length(&fp)); \ return 0; \ } \ } while (0) int row = 0; while (row < 64) { DSM_ASSERT_OFFSET(); uint8_t mask = slurp_getc(&fp); if (!mask) { /* done with the row */ row++; continue; } uint8_t chn = (mask & DSM_PAT_CHN_NUM_MASK); if (chn > MAX_CHANNELS) /* whoops */ return 0; if (chn > ppd->nchn) /* header doesn't match? warn. */ *ppd->chn_doesnt_match = MAX(chn, *ppd->chn_doesnt_match); song_note_t *note = ppd->pattern + 64 * row + chn; if (mask & DSM_PAT_NOTE_PRESENT) { DSM_ASSERT_OFFSET(); uint8_t c = slurp_getc(&fp); if (c <= 168) note->note = c + 12; } if (mask & DSM_PAT_INST_PRESENT) { DSM_ASSERT_OFFSET(); note->instrument = slurp_getc(&fp); } if (mask & DSM_PAT_VOL_PRESENT) { /* volume */ DSM_ASSERT_OFFSET(); uint8_t param = slurp_getc(&fp); if (param != 0xFF) { note->voleffect = VOLFX_VOLUME; note->volparam = MIN(param, 64); } } if (mask & DSM_PAT_CMD_PRESENT) { DSM_ASSERT_OFFSET(); note->effect = slurp_getc(&fp); DSM_ASSERT_OFFSET(); note->param = slurp_getc(&fp); csf_import_mod_effect(note, 0); if (note->effect == FX_PANNING) { if (note->param <= 0x80) { note->param <<= 1; } else if (note->param == 0xA4) { note->effect = FX_SPECIAL; note->param = 0x91; } } } } #undef DSM_ASSERT_OFFSET return 1; } int fmt_dsm_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { unsigned char riff[4], dsmf[4], chnpan[16]; size_t s = 0, p = 0, n = 0; uint16_t nord = 0, nsmp = 0, npat = 0, nchn = 0; uint8_t chn_doesnt_match = 0; size_t num_song_headers = 0; slurp_read(fp, &riff, 4); slurp_seek(fp, 4, SEEK_CUR); slurp_read(fp, &dsmf, 4); if (memcmp(riff, "RIFF", 4) || memcmp(dsmf, "DSMF", 4)) return LOAD_UNSUPPORTED; iff_chunk_t chunk; while (iff_chunk_peek_ex(&chunk, fp, IFF_CHUNK_SIZE_LE)) { switch(chunk.id) { case ID_SONG: { struct dsm_chunk_song chunk_song; iff_chunk_receive(&chunk, fp, dsm_chunk_song_read, &chunk_song); nord = bswapLE16(chunk_song.ordnum); nsmp = bswapLE16(chunk_song.smpnum); npat = bswapLE16(chunk_song.patnum); nchn = bswapLE16(chunk_song.chnnum); if (nord > MAX_ORDERS || nsmp > MAX_SAMPLES || npat > MAX_PATTERNS || nchn > MAX_CHANNELS) return LOAD_UNSUPPORTED; song->initial_global_volume = chunk_song.gvol << 1; song->mixing_volume = chunk_song.mvol >> 1; song->initial_speed = chunk_song.is; song->initial_tempo = chunk_song.it; strncpy(song->title, chunk_song.title, 25); song->title[25] = '\0'; memcpy(&chnpan, chunk_song.chnpan, 16); memcpy(song->orderlist, chunk_song.orders, nord); num_song_headers++; break; } case ID_INST: { /* sanity check. it doesn't matter if nsmp isn't the real sample * count; the file isn't "tainted" because of it, so just print * a warning if the amount of samples loaded wasn't what was expected */ if (s > MAX_SAMPLES) return LOAD_UNSUPPORTED; /* punt */ if (!(lflags & LOAD_NOSAMPLES)) { struct dsm_chunk_inst inst; iff_chunk_receive(&chunk, fp, dsm_chunk_inst_read, &inst); /* samples internally start at index 1 */ song_sample_t *sample = song->samples + s + 1; uint32_t flags = SF_LE | SF_8 | SF_M; if (inst.flags & DSM_SMP_LOOP_ACTIVE) sample->flags |= CHN_LOOP; /* these are mutually exclusive (?) */ if (inst.flags & DSM_SMP_SIGNED_PCM) flags |= SF_PCMS; else if (inst.flags & DSM_SMP_DELTA_PCM) flags |= SF_PCMD; else flags |= SF_PCMU; memcpy(sample->name, inst.name, 25); sample->name[25] = '\0'; memcpy(sample->filename, inst.filename, 12); sample->filename[12] = '\0'; sample->length = bswapLE32(inst.length); sample->loop_start = bswapLE32(inst.loop_start); sample->loop_end = bswapLE32(inst.loop_end); sample->c5speed = bswapLE16(inst.c5speed); sample->volume = inst.volume * 4; // modplug iff_read_sample(&chunk, fp, sample, flags, 64); } s++; break; } case ID_PATT: { if (p > MAX_PATTERNS) return LOAD_UNSUPPORTED; /* punt */ if (!(lflags & LOAD_NOPATTERNS)) { song->patterns[p] = csf_allocate_pattern(64); struct dsm_process_pattern_data data = {0}; data.pattern = song->patterns[p]; data.chn_doesnt_match = &chn_doesnt_match; data.nchn = nchn; /* hope this succeeds, i guess */ iff_chunk_receive(&chunk, fp, dsm_process_pattern, &data); } p++; break; } default: break; } } /* With our loader, it's possible that the song header wasn't even found to begin with. * This causes the order list and title to be empty and the global volume to remain as it was. * Make sure to notify the user if this ever actually happens. */ if (!num_song_headers) log_appendf(4, " WARNING: No SONG chunk found! (invalid DSM file?\?)"); else if (num_song_headers > 1) log_appendf(4, " WARNING: Multiple (%" PRIuSZ ") SONG chunks found!", num_song_headers); if (s != nsmp) log_appendf(4, " WARNING: # of samples (%" PRIuSZ ") different than expected (%" PRIu16 ")", s, nsmp); if (p != npat) log_appendf(4, " WARNING: # of patterns (%" PRIuSZ ") different than expected (%" PRIu16 ")", p, npat); if (chn_doesnt_match && chn_doesnt_match != nchn) log_appendf(4, " WARNING: # of channels (%" PRIu8 ") different than expected (%" PRIu16 ")", chn_doesnt_match, nchn); for (n = 0; n < nchn; n++) { if (chnpan[n & 15] <= 0x80) song->channels[n].panning = chnpan[n & 15] << 1; } for (; n < MAX_CHANNELS; n++) song->channels[n].flags |= CHN_MUTE; song->pan_separation = 128; song->flags = SONG_ITOLDEFFECTS | SONG_COMPATGXX; sprintf(song->tracker_id, "Digital Sound Interface Kit"); return LOAD_SUCCESS; } schismtracker-20250313/fmt/edl.c000066400000000000000000000047361476471630300163400ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "fmt.h" #include "util.h" #include "mem.h" static int edl_load_file(slurp_t *fp, slurp_t *fakefp) { unsigned char id[4]; if (slurp_read(fp, id, sizeof(id)) != sizeof(id)) return 0; // these are here in every EDL file I can find if (memcmp(id, "\x00\x06\xFE\xFD", 4)) return 0; // go back to the beginning slurp_seek(fp, 0, SEEK_SET); disko_t memdisko = {0}; if (disko_memopen(&memdisko) < 0) return 0; if (huffman_decompress(fp, &memdisko)) { disko_memclose(&memdisko, 0); return 0; } // EDL files are basically just a dump of whatever is in memory. // This means that it's very easy to check whether a given EDL // file is legitimate or not by just comparing the length to // a magic value. if (memdisko.length != 195840 && memdisko.length != 179913) { disko_memclose(&memdisko, 0); return 0; } if (slurp_memstream_free(fakefp, memdisko.data, memdisko.length)) { disko_memclose(&memdisko, 0); return 0; } disko_memclose(&memdisko, 1); return 1; } int fmt_edl_read_info(dmoz_file_t *file, slurp_t *fp) { slurp_t fakefp = {0}; if (!edl_load_file(fp, &fakefp)) return 0; slurp_seek(&fakefp, 0x1FE0B, SEEK_SET); unsigned char title[32]; if (slurp_read(&fakefp, title, sizeof(title)) != sizeof(title)) { unslurp(&fakefp); return 0; } file->title = strn_dup((const char *)title, sizeof(title)); file->description = "EdLib Tracker EDL"; file->type = TYPE_MODULE_S3M; unslurp(&fakefp); return 1; } schismtracker-20250313/fmt/f2r.c000066400000000000000000000031441476471630300162550ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "fmt.h" #include "mem.h" /* --------------------------------------------------------------------- */ /* TODO: test this code */ int fmt_f2r_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char magic[3]; if (slurp_read(fp, magic, sizeof(magic)) != sizeof(magic) || memcmp(magic, "F2R", 3)) return 0; unsigned char title[40]; slurp_seek(fp, 6, SEEK_SET); slurp_read(fp, title, sizeof(title)); file->description = "Farandole 2 (linear)"; /*file->extension = str_dup("f2r");*/ file->title = strn_dup((const char *)title, 40); file->type = TYPE_MODULE_S3M; return 1; } schismtracker-20250313/fmt/far.c000066400000000000000000000201031476471630300163260ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "mem.h" #include "player/sndfile.h" /* --------------------------------------------------------------------- */ /* --------------------------------------------------------------------------------------------------------- */ /* This loader sucks. Mostly it was implemented based on what Modplug does, which is kind of counterproductive, but I can't get Farandole to run in Dosbox to test stuff */ struct far_header { uint8_t magic[4]; char title[40]; uint8_t eof[3]; uint16_t header_len; uint8_t version; uint8_t onoff[16]; uint8_t default_speed; uint8_t chn_panning[16]; uint16_t message_len; }; struct far_sample { char name[32]; uint32_t length; uint8_t finetune; uint8_t volume; uint32_t loopstart; uint32_t loopend; uint8_t type; uint8_t loop; }; static int far_read_header(struct far_header *hdr, slurp_t *fp) { #define READ_VALUE(name) \ if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) return 0 READ_VALUE(magic); READ_VALUE(title); READ_VALUE(eof); READ_VALUE(header_len); READ_VALUE(version); READ_VALUE(onoff); slurp_seek(fp, 9, SEEK_CUR); // editing state READ_VALUE(default_speed); READ_VALUE(chn_panning); slurp_seek(fp, 4, SEEK_CUR); // pattern state READ_VALUE(message_len); #undef READ_VALUE if (memcmp(hdr->magic, "FAR\xfe", 4) || memcmp(hdr->eof, "\x0d\x0a\x1a", 3)) return 0; /* byteswapping is handled in the read functions for now */ return 1; } static int far_read_sample(struct far_sample *smp, slurp_t *fp) { #define READ_VALUE(name) \ if (slurp_read(fp, &smp->name, sizeof(smp->name)) != sizeof(smp->name)) return 0 READ_VALUE(name); READ_VALUE(length); READ_VALUE(finetune); READ_VALUE(volume); READ_VALUE(loopstart); READ_VALUE(loopend); READ_VALUE(type); READ_VALUE(loop); #undef READ_VALUE return 1; } int fmt_far_read_info(dmoz_file_t *file, slurp_t *fp) { /* The magic for this format is truly weird (which I suppose is good, as the chance of it * being "accidentally" correct is pretty low) */ struct far_header hdr; if (!far_read_header(&hdr, fp)) return 0; file->description = "Farandole Module"; /*file->extension = str_dup("far");*/ file->title = strn_dup(hdr.title, sizeof(hdr.title)); file->type = TYPE_MODULE_S3M; return 1; } static uint8_t far_effects[] = { FX_NONE, FX_PORTAMENTOUP, FX_PORTAMENTODOWN, FX_TONEPORTAMENTO, FX_RETRIG, FX_VIBRATO, // depth FX_VIBRATO, // speed FX_VOLUMESLIDE, // up FX_VOLUMESLIDE, // down FX_VIBRATO, // sustained (?) FX_NONE, // actually slide-to-volume FX_PANNING, FX_SPECIAL, // note offset => note delay? FX_NONE, // fine tempo down FX_NONE, // fine tempo up FX_SPEED, }; static void far_import_note(song_note_t *note, const uint8_t data[4]) { if (data[0] > 0 && data[0] < 85) { note->note = data[0] + 36; note->instrument = data[1] + 1; } if (data[2] & 0x0F) { note->voleffect = VOLFX_VOLUME; note->volparam = (data[2] & 0x0F) << 2; // askjdfjasdkfjasdf } note->param = data[3] & 0xf; switch (data[3] >> 4) { case 3: // porta to note note->param <<= 2; break; case 4: // retrig note->param = 6 / (1 + (note->param & 0xf)) + 1; // ugh? break; case 6: // vibrato speed case 7: // volume slide up case 0xb: // panning note->param <<= 4; break; case 0xa: // volume-portamento (what!) note->voleffect = VOLFX_VOLUME; note->volparam = (note->param << 2) + 4; break; case 0xc: // note offset note->param = 6 / (1 + (note->param & 0xf)) + 1; note->param |= 0xd; } note->effect = far_effects[data[3] >> 4]; } int fmt_far_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { struct far_header fhdr; struct far_sample fsmp; song_sample_t *smp; int nord, restartpos; int n, pat, row, chn; uint8_t orderlist[256]; uint16_t pattern_size[256]; uint8_t data[8]; if (!far_read_header(&fhdr, fp)) return LOAD_UNSUPPORTED; fhdr.title[25] = '\0'; strcpy(song->title, fhdr.title); fhdr.header_len = bswapLE16(fhdr.header_len); fhdr.message_len = bswapLE16(fhdr.message_len); for (n = 0; n < 16; n++) { /* WHAT A GREAT WAY TO STORE THIS INFORMATION */ song->channels[n].panning = SHORT_PANNING(fhdr.chn_panning[n] & 0xf); song->channels[n].panning *= 4; //mphack if (!fhdr.onoff[n]) song->channels[n].flags |= CHN_MUTE; } for (; n < 64; n++) song->channels[n].flags |= CHN_MUTE; song->initial_speed = fhdr.default_speed; song->initial_tempo = 80; // to my knowledge, no other program is insane enough to save in this format strcpy(song->tracker_id, "Farandole Composer"); /* Farandole's song message doesn't have line breaks, and the tracker runs in * some screwy ultra-wide text mode, so this displays more or less like crap. */ read_lined_message(song->message, fp, fhdr.message_len, 132); if ((lflags & (LOAD_NOSAMPLES | LOAD_NOPATTERNS)) == (LOAD_NOSAMPLES | LOAD_NOPATTERNS)) return LOAD_SUCCESS; slurp_read(fp, orderlist, 256); slurp_getc(fp); // supposed to be "number of patterns stored in the file"; apparently that's wrong nord = slurp_getc(fp); restartpos = slurp_getc(fp); nord = MIN(nord, MAX_ORDERS); memcpy(song->orderlist, orderlist, nord); memset(song->orderlist + nord, ORDER_LAST, MAX_ORDERS - nord); slurp_read(fp, pattern_size, 256 * 2); // byteswapped later slurp_seek(fp, fhdr.header_len - (869 + fhdr.message_len), SEEK_CUR); for (pat = 0; pat < 256; pat++) { int breakpos, rows; song_note_t *note; pattern_size[pat] = bswapLE16(pattern_size[pat]); if (pat >= MAX_PATTERNS || pattern_size[pat] < (2 + 16 * 4)) { slurp_seek(fp, pattern_size[pat], SEEK_CUR); continue; } breakpos = slurp_getc(fp); slurp_getc(fp); // apparently, this value is *not* used anymore!!! I will not support it!! rows = (pattern_size[pat] - 2) / (16 * 4); if (!rows) continue; note = song->patterns[pat] = csf_allocate_pattern(rows); song->pattern_size[pat] = song->pattern_alloc_size[pat] = rows; breakpos = breakpos && breakpos < rows - 2 ? breakpos + 1 : -1; for (row = 0; row < rows; row++, note += 48) { for (chn = 0; chn < 16; chn++, note++) { slurp_read(fp, data, 4); far_import_note(note, data); } if (row == breakpos) note->effect = FX_PATTERNBREAK; } } csf_insert_restart_pos(song, restartpos); if (!(lflags & LOAD_NOSAMPLES)) { printf("%" PRIuSZ "\n", slurp_read(fp, data, 8)); smp = song->samples + 1; for (n = 0; n < 64; n++, smp++) { if (!(data[n / 8] & (1 << (n % 8)))) /* LOLWHAT */ continue; far_read_sample(&fsmp, fp); fsmp.name[25] = '\0'; strcpy(smp->name, fsmp.name); smp->length = fsmp.length = bswapLE32(fsmp.length); smp->loop_start = bswapLE32(fsmp.loopstart); smp->loop_end = bswapLE32(fsmp.loopend); smp->volume = fsmp.volume << 4; // "not supported", but seems to exist anyway if (fsmp.type & 1) { smp->length >>= 1; smp->loop_start >>= 1; smp->loop_end >>= 1; } if (smp->loop_end > smp->loop_start && (fsmp.loop & 8)) smp->flags |= CHN_LOOP; smp->c5speed = 16726; smp->global_volume = 64; csf_read_sample(smp, SF_LE | SF_M | SF_PCMS | ((fsmp.type & 1) ? SF_16 : SF_8), fp); } } return LOAD_SUCCESS; } schismtracker-20250313/fmt/flac.c000066400000000000000000000577471476471630300165130ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bshift.h" #include "bswap.h" #include "fmt.h" #include "it.h" #include "disko.h" #include "player/sndfile.h" #include "log.h" #include "util.h" #include #include #include static FLAC__StreamDecoder * (*schism_FLAC_stream_decoder_new)(void); static FLAC__bool (*schism_FLAC_stream_decoder_set_metadata_respond_all)(FLAC__StreamDecoder *decoder); static FLAC__StreamDecoderInitStatus (*schism_FLAC_stream_decoder_init_stream)(FLAC__StreamDecoder *decoder, FLAC__StreamDecoderReadCallback read_callback, FLAC__StreamDecoderSeekCallback seek_callback, FLAC__StreamDecoderTellCallback tell_callback, FLAC__StreamDecoderLengthCallback length_callback, FLAC__StreamDecoderEofCallback eof_callback, FLAC__StreamDecoderWriteCallback write_callback, FLAC__StreamDecoderMetadataCallback metadata_callback, FLAC__StreamDecoderErrorCallback error_callback, void *client_data); static FLAC__bool (*schism_FLAC_stream_decoder_process_until_end_of_metadata)(FLAC__StreamDecoder *decoder); static FLAC__bool (*schism_FLAC_stream_decoder_process_until_end_of_stream)(FLAC__StreamDecoder *decoder); static FLAC__bool (*schism_FLAC_stream_decoder_finish)(FLAC__StreamDecoder *decoder); static void (*schism_FLAC_stream_decoder_delete)(FLAC__StreamDecoder *decoder); static const char *const *schism_FLAC_StreamDecoderErrorStatusString; static FLAC__StreamEncoder * (*schism_FLAC_stream_encoder_new)(void); static FLAC__bool (*schism_FLAC_stream_encoder_set_channels)(FLAC__StreamEncoder *encoder, uint32_t value); static FLAC__bool (*schism_FLAC_stream_encoder_set_bits_per_sample)(FLAC__StreamEncoder *encoder, uint32_t value); static FLAC__bool (*schism_FLAC_stream_encoder_set_streamable_subset)(FLAC__StreamEncoder *encoder, FLAC__bool value); static FLAC__bool (*schism_FLAC_stream_encoder_set_sample_rate)(FLAC__StreamEncoder *encoder, uint32_t value); static FLAC__bool (*schism_FLAC_stream_encoder_set_compression_level)(FLAC__StreamEncoder *encoder, uint32_t value); static FLAC__bool (*schism_FLAC_stream_encoder_set_total_samples_estimate)(FLAC__StreamEncoder *encoder, FLAC__uint64 value); static FLAC__bool (*schism_FLAC_stream_encoder_set_verify)(FLAC__StreamEncoder *encoder, FLAC__bool value); static FLAC__StreamEncoderInitStatus (*schism_FLAC_stream_encoder_init_stream)(FLAC__StreamEncoder *encoder, FLAC__StreamEncoderWriteCallback write_callback, FLAC__StreamEncoderSeekCallback seek_callback, FLAC__StreamEncoderTellCallback tell_callback, FLAC__StreamEncoderMetadataCallback metadata_callback, void *client_data); static FLAC__bool (*schism_FLAC_stream_encoder_process_interleaved)(FLAC__StreamEncoder *encoder, const FLAC__int32 buffer[], uint32_t samples); static FLAC__bool (*schism_FLAC_stream_encoder_finish)(FLAC__StreamEncoder *encoder); static void (*schism_FLAC_stream_encoder_delete)(FLAC__StreamEncoder *encoder); static const char *const *schism_FLAC_StreamEncoderInitStatusString; static FLAC__bool (*schism_FLAC_format_sample_rate_is_subset)(uint32_t sample_rate); static int flac_wasinit = 0; /* ----------------------------------------------------------------------------------- */ /* reading... */ struct flac_readdata { FLAC__StreamMetadata_StreamInfo streaminfo; struct { char name[32]; uint32_t sample_rate; uint8_t pan; uint8_t vol; struct { int32_t type; uint32_t start; uint32_t end; } loop; } flags; slurp_t *fp; struct { uint8_t *data; size_t len; uint32_t samples_decoded; } uncompressed; }; static void read_on_meta(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) { struct flac_readdata* read_data = (struct flac_readdata*)client_data; int32_t loop_start = -1, loop_length = -1; switch (metadata->type) { case FLAC__METADATA_TYPE_STREAMINFO: memcpy(&read_data->streaminfo, &metadata->data.stream_info, sizeof(read_data->streaminfo)); break; case FLAC__METADATA_TYPE_VORBIS_COMMENT: for (size_t i = 0; i < metadata->data.vorbis_comment.num_comments; i++) { const char *tag = (const char*)metadata->data.vorbis_comment.comments[i].entry; const FLAC__uint32 length = metadata->data.vorbis_comment.comments[i].length; /* "name" must be a string literal for both of these */ #define CHECK_TAG_SIZE(name) \ if (length > (sizeof(name "=")) && !strncasecmp(tag, name "=", sizeof(name "="))) #define STRING_TAG(name, outvar, outvarsize) \ CHECK_TAG_SIZE(name) { \ strncpy((outvar), tag + sizeof(name "="), (outvarsize) - 1); \ continue; \ } #define INTEGER_TAG(name, outvar) \ CHECK_TAG_SIZE(name) { \ outvar = strtoll(tag + sizeof(name "="), NULL, 10); \ continue; \ } STRING_TAG("TITLE", read_data->flags.name, sizeof(read_data->flags.name)); INTEGER_TAG("SAMPLERATE", read_data->flags.sample_rate); INTEGER_TAG("LOOPSTART", loop_start); INTEGER_TAG("LOOPLENGTH", loop_length); #undef INTEGER_TAG #undef STRING_TAG #undef CHECK_TAG_SIZE } if (loop_start > 0 && loop_length > 1) { read_data->flags.loop.type = 0; read_data->flags.loop.start = loop_start; read_data->flags.loop.end = loop_start + loop_length - 1; } break; case FLAC__METADATA_TYPE_APPLICATION: { const uint8_t *data = (const uint8_t *)metadata->data.application.data; uint32_t chunk_id = *(uint32_t*)data; data += sizeof(uint32_t); uint32_t chunk_len = *(uint32_t*)data; data += sizeof(uint32_t); if (chunk_id == bswapLE32(0x61727478) && chunk_len >= 8) { // "xtra" uint32_t xtra_flags = *(uint32_t*)data; data += sizeof(uint32_t); // panning (0..256) if (xtra_flags & 0x20) { uint16_t tmp_pan = *(uint16_t*)data; if (tmp_pan > 255) tmp_pan = 255; read_data->flags.pan = (uint8_t)tmp_pan; } data += sizeof(uint16_t); // volume (0..256) uint16_t tmp_vol = *(uint16_t*)data; if (tmp_vol > 256) tmp_vol = 256; read_data->flags.vol = (uint8_t)((tmp_vol + 2) / 4); // 0..256 -> 0..64 (rounded) } if (chunk_id == bswapLE32(0x6C706D73) && chunk_len > 52) { // "smpl" data += 28; // seek to first wanted byte uint32_t num_loops = *(uint32_t *)data; data += sizeof(uint32_t); if (num_loops == 1) { data += 4 + 4; // skip "samplerData" and "identifier" read_data->flags.loop.type = *(uint32_t *)data; data += sizeof(uint32_t); read_data->flags.loop.start = *(uint32_t *)data; data += sizeof(uint32_t); read_data->flags.loop.end = *(uint32_t *)data; data += sizeof(uint32_t); } } break; } default: break; } (void)decoder, (void)client_data; } static FLAC__StreamDecoderReadStatus read_on_read(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) { slurp_t *fp = ((struct flac_readdata *)client_data)->fp; if (*bytes > 0) { *bytes = slurp_read(fp, buffer, *bytes); return (*bytes) ? FLAC__STREAM_DECODER_READ_STATUS_CONTINUE : (slurp_eof(fp)) ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_ABORT; } else { return FLAC__STREAM_DECODER_READ_STATUS_ABORT; } (void)decoder; } static FLAC__StreamDecoderSeekStatus read_on_seek(SCHISM_UNUSED const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) { slurp_t *fp = ((struct flac_readdata *)client_data)->fp; /* how? whatever */ if (absolute_byte_offset > INT64_MAX) return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; return (slurp_seek(fp, absolute_byte_offset, SEEK_SET) >= 0) ? FLAC__STREAM_DECODER_SEEK_STATUS_OK : FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; } static FLAC__StreamDecoderTellStatus read_on_tell(SCHISM_UNUSED const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data) { slurp_t *fp = ((struct flac_readdata *)client_data)->fp; int64_t off = slurp_tell(fp); if (off < 0) return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; *absolute_byte_offset = off; return FLAC__STREAM_DECODER_TELL_STATUS_OK; } static FLAC__StreamDecoderLengthStatus read_on_length(SCHISM_UNUSED const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length, void *client_data) { /* XXX need a slurp_length() */ *stream_length = (FLAC__uint64)slurp_length(((struct flac_readdata*)client_data)->fp); return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; } static FLAC__bool read_on_eof(SCHISM_UNUSED const FLAC__StreamDecoder *decoder, void *client_data) { return slurp_eof(((struct flac_readdata*)client_data)->fp); } static void read_on_error(SCHISM_UNUSED const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus xx, void *client_data) { log_appendf(4, "Error loading FLAC: %s", schism_FLAC_StreamDecoderErrorStatusString[xx]); (void)decoder, (void)client_data; } static FLAC__StreamDecoderWriteStatus read_on_write(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) { struct flac_readdata* read_data = (struct flac_readdata*)client_data; /* invalid?; FIXME: this should probably make sure the total_samples * is less than the max sample constant thing */ if (!read_data->streaminfo.total_samples || !read_data->streaminfo.channels || read_data->streaminfo.channels > 2) return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; if (frame->header.number.sample_number == 0) { /* allocate our buffer. for some reason the length isn't the real * length of the buffer in bytes but I can't be bothered to figure * out why. */ read_data->uncompressed.len = ((size_t)read_data->streaminfo.total_samples * read_data->streaminfo.channels * read_data->streaminfo.bits_per_sample/8); read_data->uncompressed.data = (uint8_t*)malloc(read_data->uncompressed.len * ((read_data->streaminfo.bits_per_sample == 8) ? sizeof(int8_t) : sizeof(int16_t))); if (!read_data->uncompressed.data) return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; read_data->uncompressed.samples_decoded = 0; } uint32_t block_size = frame->header.blocksize * read_data->streaminfo.channels; const uint32_t samples_allocated = read_data->streaminfo.total_samples * read_data->streaminfo.channels; if (read_data->uncompressed.samples_decoded + block_size > samples_allocated) block_size = samples_allocated - read_data->uncompressed.samples_decoded; if (read_data->streaminfo.bits_per_sample <= 8) { int8_t *buf_ptr = (int8_t*)read_data->uncompressed.data + read_data->uncompressed.samples_decoded; uint32_t bit_shift = 8 - read_data->streaminfo.bits_per_sample; size_t i, j, c; for (i = 0, j = 0; i < block_size; j++) for (c = 0; c < read_data->streaminfo.channels; c++) buf_ptr[i++] = lshift_signed(buffer[c][j], bit_shift); } else if (read_data->streaminfo.bits_per_sample <= 16) { int16_t *buf_ptr = (int16_t*)read_data->uncompressed.data + read_data->uncompressed.samples_decoded; uint32_t bit_shift = 16 - read_data->streaminfo.bits_per_sample; size_t i, j, c; for (i = 0, j = 0; i < block_size; j++) for (c = 0; c < read_data->streaminfo.channels; c++) buf_ptr[i++] = lshift_signed(buffer[c][j], bit_shift); } else { /* >= 16 */ int16_t *buf_ptr = (int16_t*)read_data->uncompressed.data + read_data->uncompressed.samples_decoded; uint32_t bit_shift = read_data->streaminfo.bits_per_sample - 16; size_t i, j, c; for (i = 0, j = 0; i < block_size; j++) for (c = 0; c < read_data->streaminfo.channels; c++) buf_ptr[i++] = rshift_signed(buffer[c][j], bit_shift); } read_data->uncompressed.samples_decoded += block_size; return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; (void)decoder; } static int flac_load(struct flac_readdata* read_data, int meta_only) { // err if (!flac_wasinit) return 0; unsigned char magic[4]; slurp_rewind(read_data->fp); /* paranoia */ if (slurp_peek(read_data->fp, magic, sizeof(magic)) != sizeof(magic) || memcmp(magic, "fLaC", sizeof(magic))) return 0; FLAC__StreamDecoder *decoder = schism_FLAC_stream_decoder_new(); if (!decoder) return 0; schism_FLAC_stream_decoder_set_metadata_respond_all(decoder); FLAC__StreamDecoderInitStatus xx = schism_FLAC_stream_decoder_init_stream( decoder, read_on_read, read_on_seek, read_on_tell, read_on_length, read_on_eof, read_on_write, read_on_meta, read_on_error, read_data ); if (xx != FLAC__STREAM_DECODER_INIT_STATUS_OK) return 0; /* flac function names are such a yapfest */ if (!(meta_only ? schism_FLAC_stream_decoder_process_until_end_of_metadata(decoder) : schism_FLAC_stream_decoder_process_until_end_of_stream(decoder))) { schism_FLAC_stream_decoder_delete(decoder); return 0; } schism_FLAC_stream_decoder_finish(decoder); schism_FLAC_stream_decoder_delete(decoder); return 1; } #undef FLAC_ERROR int fmt_flac_load_sample(slurp_t *fp, song_sample_t *smp) { struct flac_readdata read_data = { .fp = fp, .flags = { .sample_rate = 0, .loop = { .type = -1, }, }, }; if (!flac_load(&read_data, 0)) return 0; smp->volume = 64 * 4; smp->global_volume = 64; smp->c5speed = read_data.streaminfo.sample_rate; smp->length = read_data.streaminfo.total_samples; if (read_data.flags.loop.type != -1) { smp->loop_start = read_data.flags.loop.start; smp->loop_end = read_data.flags.loop.end + 1; smp->flags |= (read_data.flags.loop.type ? (CHN_LOOP | CHN_PINGPONGLOOP) : CHN_LOOP); } if (read_data.flags.sample_rate) smp->c5speed = read_data.flags.sample_rate; // endianness, based on host system uint32_t flags = 0; #ifdef WORDS_BIGENDIAN flags |= SF_BE; #else flags |= SF_LE; #endif // channels flags |= (read_data.streaminfo.channels == 2) ? SF_SI : SF_M; // bit width flags |= (read_data.streaminfo.bits_per_sample <= 8) ? SF_8 : SF_16; // libFLAC always returns signed flags |= SF_PCMS; slurp_t fake_fp; slurp_memstream(&fake_fp, read_data.uncompressed.data, read_data.uncompressed.len); int ret = csf_read_sample(smp, flags, &fake_fp); free(read_data.uncompressed.data); return ret; } int fmt_flac_read_info(dmoz_file_t *file, slurp_t *fp) { struct flac_readdata read_data = { .fp = fp, .flags = { .sample_rate = 0, .loop = { .type = -1, }, }, }; if (!flac_load(&read_data, 1)) return 0; file->smp_flags = 0; /* don't even attempt */ if (read_data.streaminfo.channels > 2 || !read_data.streaminfo.total_samples || !read_data.streaminfo.channels) return 0; if (read_data.streaminfo.bits_per_sample > 8) file->smp_flags |= CHN_16BIT; if (read_data.streaminfo.channels == 2) file->smp_flags |= CHN_STEREO; file->smp_speed = read_data.streaminfo.sample_rate; file->smp_length = read_data.streaminfo.total_samples; /* stupid magic numbers... */ if (read_data.flags.loop.type >= 0) { file->smp_loop_start = read_data.flags.loop.start; file->smp_loop_end = read_data.flags.loop.end + 1; file->smp_flags |= (read_data.flags.loop.type ? (CHN_LOOP | CHN_PINGPONGLOOP) : CHN_LOOP); } if (read_data.flags.sample_rate) file->smp_speed = read_data.flags.sample_rate; file->description = "FLAC Audio File"; file->type = TYPE_SAMPLE_COMPR; file->smp_filename = file->base; return 1; } /* ------------------------------------------------------------------------ */ /* Now onto the writing stuff */ struct flac_writedata { /* TODO would be nice to save some metadata */ FLAC__StreamEncoder *encoder; int bits; int channels; }; static FLAC__StreamEncoderWriteStatus write_on_write(SCHISM_UNUSED const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, SCHISM_UNUSED uint32_t samples, SCHISM_UNUSED uint32_t current_frame, void *client_data) { disko_t* fp = (disko_t*)client_data; disko_write(fp, buffer, bytes); return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; } static FLAC__StreamEncoderSeekStatus write_on_seek(SCHISM_UNUSED const FLAC__StreamEncoder *encoder, FLAC__uint64 absolute_byte_offset, void *client_data) { disko_t* fp = (disko_t*)client_data; disko_seek(fp, absolute_byte_offset, SEEK_SET); return FLAC__STREAM_ENCODER_SEEK_STATUS_OK; } static FLAC__StreamEncoderTellStatus write_on_tell(SCHISM_UNUSED const FLAC__StreamEncoder *encoder, FLAC__uint64 *absolute_byte_offset, void *client_data) { disko_t* fp = (disko_t*)client_data; long b = disko_tell(fp); if (b < 0) return FLAC__STREAM_ENCODER_TELL_STATUS_ERROR; if (absolute_byte_offset) *absolute_byte_offset = (FLAC__uint64)b; return FLAC__STREAM_ENCODER_TELL_STATUS_OK; } static int flac_save_init(disko_t *fp, int bits, int channels, int rate, int estimate_num_samples) { if (!flac_wasinit) return -9; struct flac_writedata *fwd = malloc(sizeof(*fwd)); if (!fwd) return -8; fwd->channels = channels; fwd->bits = bits; fwd->encoder = schism_FLAC_stream_encoder_new(); if (!fwd->encoder) return -1; if (!schism_FLAC_stream_encoder_set_channels(fwd->encoder, channels)) return -2; if (!schism_FLAC_stream_encoder_set_bits_per_sample(fwd->encoder, bits)) return -3; if (rate > (int)FLAC__MAX_SAMPLE_RATE) rate = (int)FLAC__MAX_SAMPLE_RATE; // FLAC only supports 10 Hz granularity for frequencies above 65535 Hz if the streamable subset is chosen, and only a maximum frequency of 655350 Hz. if (!schism_FLAC_format_sample_rate_is_subset(rate)) schism_FLAC_stream_encoder_set_streamable_subset(fwd->encoder, false); if (!schism_FLAC_stream_encoder_set_sample_rate(fwd->encoder, rate)) return -4; if (!schism_FLAC_stream_encoder_set_compression_level(fwd->encoder, 5)) return -5; if (!schism_FLAC_stream_encoder_set_total_samples_estimate(fwd->encoder, estimate_num_samples)) return -6; if (!schism_FLAC_stream_encoder_set_verify(fwd->encoder, false)) return -7; FLAC__StreamEncoderInitStatus init_status = schism_FLAC_stream_encoder_init_stream( fwd->encoder, write_on_write, write_on_seek, write_on_tell, NULL, /* metadata callback */ fp ); if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { log_appendf(4, "ERROR: initializing FLAC encoder: %s\n", schism_FLAC_StreamEncoderInitStatusString[init_status]); fprintf(stderr, "ERROR: initializing FLAC encoder: %s\n", schism_FLAC_StreamEncoderInitStatusString[init_status]); return -8; } fp->userdata = fwd; return 0; } int fmt_flac_export_head(disko_t *fp, int bits, int channels, int rate) { if (flac_save_init(fp, bits, channels, rate, 0)) return DW_ERROR; return DW_OK; } int fmt_flac_export_body(disko_t *fp, const uint8_t *data, size_t length) { struct flac_writedata *fwd = fp->userdata; const int bytes_per_sample = (fwd->bits / 8); SCHISM_VLA_ALLOC(FLAC__int32, pcm, length / bytes_per_sample); /* 8-bit/16-bit PCM -> 32-bit PCM */ size_t i; for (i = 0; i < SCHISM_VLA_LENGTH(pcm); i++) { switch (bytes_per_sample) { case 1: pcm[i] = (FLAC__int32)(((const int8_t*)data)[i]); break; case 2: pcm[i] = (FLAC__int32)(((const int16_t*)data)[i]); break; case 4: pcm[i] = (FLAC__int32)(((const int32_t*)data)[i]); break; default: SCHISM_VLA_FREE(pcm); return DW_ERROR; } } if (!schism_FLAC_stream_encoder_process_interleaved(fwd->encoder, pcm, length / (bytes_per_sample * fwd->channels))) { SCHISM_VLA_FREE(pcm); return DW_ERROR; } SCHISM_VLA_FREE(pcm); return DW_OK; } int fmt_flac_export_silence(disko_t *fp, long bytes) { /* actually have to generate silence here */ SCHISM_VLA_ALLOC(uint8_t, silence, bytes); memset(silence, 0, SCHISM_VLA_SIZEOF(silence)); int res = fmt_flac_export_body(fp, silence, bytes); SCHISM_VLA_FREE(silence); return res; } int fmt_flac_export_tail(disko_t *fp) { struct flac_writedata *fwd = fp->userdata; schism_FLAC_stream_encoder_finish(fwd->encoder); schism_FLAC_stream_encoder_delete(fwd->encoder); free(fwd); return DW_OK; } /* need this because convering huge buffers in memory is KIND OF bad. * currently this is the same size as the buffer length in disko.c */ #define SAMPLE_BUFFER_LENGTH 65536 int fmt_flac_save_sample(disko_t *fp, song_sample_t *smp) { if (smp->flags & CHN_ADLIB) return SAVE_UNSUPPORTED; if (flac_save_init(fp, (smp->flags & CHN_16BIT) ? 16 : 8, (smp->flags & CHN_STEREO) ? 2 : 1, smp->c5speed, smp->length)) return SAVE_INTERNAL_ERROR; /* need to buffer this or else we'll make a HUGE array when * saving huge samples */ size_t offset; const size_t total_bytes = smp->length * ((smp->flags & CHN_16BIT) ? 2 : 1) * ((smp->flags & CHN_STEREO) ? 2 : 1); for (offset = 0; offset < total_bytes; offset += SAMPLE_BUFFER_LENGTH) { size_t needed = total_bytes - offset; if (fmt_flac_export_body(fp, (uint8_t*)smp->data + offset, MIN(needed, SAMPLE_BUFFER_LENGTH)) != DW_OK) return SAVE_INTERNAL_ERROR; } if (fmt_flac_export_tail(fp) != DW_OK) return SAVE_INTERNAL_ERROR; return SAVE_SUCCESS; } /* --------------------------------------------------------------- */ static int load_flac_syms(void); #ifdef FLAC_DYNAMIC_LOAD #include "loadso.h" void *flac_dltrick_handle_ = NULL; static void flac_dlend(void) { if (flac_dltrick_handle_) { loadso_object_unload(flac_dltrick_handle_); flac_dltrick_handle_ = NULL; } } static int flac_dlinit(void) { // already have it? if (flac_dltrick_handle_) return 0; flac_dltrick_handle_ = library_load("FLAC", FLAC_API_VERSION_CURRENT, FLAC_API_VERSION_AGE); if (!flac_dltrick_handle_) return -1; int retval = load_flac_syms(); if (retval < 0) flac_dlend(); return retval; } // this is always true under SDL but I'm paranoid SCHISM_STATIC_ASSERT(sizeof(void (*)) == sizeof(void *), "dynamic loading code assumes function pointer and void pointer are of equivalent size"); static int load_flac_sym(const char *fn, void *addr) { void *func = loadso_function_load(flac_dltrick_handle_, fn); if (!func) return 0; memcpy(addr, &func, sizeof(void *)); return 1; } #define SCHISM_FLAC_SYM(x) \ if (!load_flac_sym("FLAC__" #x, &schism_FLAC_##x)) return -1 #else #define SCHISM_FLAC_SYM(x) schism_FLAC_##x = FLAC__##x static int flac_dlinit(void) { load_flac_syms(); return 0; } #endif static int load_flac_syms(void) { SCHISM_FLAC_SYM(stream_decoder_new); SCHISM_FLAC_SYM(stream_decoder_set_metadata_respond_all); SCHISM_FLAC_SYM(stream_decoder_init_stream); SCHISM_FLAC_SYM(stream_decoder_process_until_end_of_metadata); SCHISM_FLAC_SYM(stream_decoder_process_until_end_of_stream); SCHISM_FLAC_SYM(stream_decoder_finish); SCHISM_FLAC_SYM(stream_decoder_delete); SCHISM_FLAC_SYM(StreamDecoderErrorStatusString); SCHISM_FLAC_SYM(stream_encoder_new); SCHISM_FLAC_SYM(stream_encoder_set_channels); SCHISM_FLAC_SYM(stream_encoder_set_bits_per_sample); SCHISM_FLAC_SYM(stream_encoder_set_streamable_subset); SCHISM_FLAC_SYM(stream_encoder_set_sample_rate); SCHISM_FLAC_SYM(stream_encoder_set_compression_level); SCHISM_FLAC_SYM(stream_encoder_set_total_samples_estimate); SCHISM_FLAC_SYM(stream_encoder_set_verify); SCHISM_FLAC_SYM(stream_encoder_init_stream); SCHISM_FLAC_SYM(stream_encoder_process_interleaved); SCHISM_FLAC_SYM(stream_encoder_finish); SCHISM_FLAC_SYM(stream_encoder_delete); SCHISM_FLAC_SYM(StreamEncoderInitStatusString); SCHISM_FLAC_SYM(format_sample_rate_is_subset); return 0; } /* ------------------------------------------------- */ int flac_init(void) { #ifdef FLAC_DYNAMIC_LOAD if (!flac_dltrick_handle_) #endif if (flac_dlinit() < 0) return 0; audio_enable_flac(1); flac_wasinit = 1; return 1; } int flac_quit(void) { #ifdef FLAC_DYNAMIC_LOAD if (flac_wasinit) flac_dlend(); #endif return 1; } schismtracker-20250313/fmt/generic.c000066400000000000000000000314361476471630300172050ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "fmt.h" #include "str.h" #include /* --------------------------------------------------------------------------------------------------------- */ static int _mod_period_to_note(int period) { int n; if (period) for (n = 0; n <= NOTE_LAST; n++) if (period >= (32 * period_table[n % 12] >> (n / 12 + 2))) return n + 1; return NOTE_NONE; } void mod_import_note(const uint8_t p[4], song_note_t *note) { note->note = _mod_period_to_note(((p[0] & 0xf) << 8) + p[1]); note->instrument = (p[0] & 0xf0) + (p[2] >> 4); note->voleffect = VOLFX_NONE; note->volparam = 0; note->effect = p[2] & 0xf; note->param = p[3]; } /* --------------------------------------------------------------------------------------------------------- */ const uint8_t effect_weight[FX_MAX] = { [FX_PATTERNBREAK] = 248, [FX_POSITIONJUMP] = 240, [FX_SPEED] = 232, [FX_TEMPO] = 224, [FX_GLOBALVOLUME] = 216, [FX_GLOBALVOLSLIDE] = 208, [FX_CHANNELVOLUME] = 200, [FX_CHANNELVOLSLIDE] = 192, [FX_TONEPORTAVOL] = 184, [FX_TONEPORTAMENTO] = 176, [FX_ARPEGGIO] = 168, [FX_RETRIG] = 160, [FX_TREMOR] = 152, [FX_OFFSET] = 144, [FX_VOLUME] = 136, [FX_VIBRATOVOL] = 128, [FX_VOLUMESLIDE] = 120, [FX_PORTAMENTODOWN] = 112, [FX_PORTAMENTOUP] = 104, [FX_NOTESLIDEDOWN] = 96, // IMF Hxy [FX_NOTESLIDEUP] = 88, // IMF Gxy [FX_PANNING] = 80, [FX_PANNINGSLIDE] = 72, [FX_MIDI] = 64, [FX_SPECIAL] = 56, [FX_PANBRELLO] = 48, [FX_VIBRATO] = 40, [FX_FINEVIBRATO] = 32, [FX_TREMOLO] = 24, [FX_KEYOFF] = 16, [FX_SETENVPOSITION] = 8, [FX_NONE] = 0, }; void swap_effects(song_note_t *note) { song_note_t tmp = {0}; tmp.note = note->note; tmp.instrument = note->instrument; tmp.voleffect = note->effect; tmp.volparam = note->param; tmp.effect = note->voleffect; tmp.param = note->volparam; *note = tmp; } int convert_voleffect(uint8_t *e, uint8_t *p, int force) { switch (*e) { case FX_NONE: return 1; case FX_VOLUME: *e = VOLFX_VOLUME; *p = MIN(*p, 64); break; case FX_PORTAMENTOUP: /* if not force, reject when dividing causes loss of data in LSB, or if the final value is too large to fit. (volume column Ex/Fx are four times stronger than effect column) */ if (!force && ((*p & 3) || *p > 9 * 4 + 3)) return 0; *p = MIN(*p / 4, 9); *e = VOLFX_PORTAUP; break; case FX_PORTAMENTODOWN: if (!force && ((*p & 3) || *p > 9 * 4 + 3)) return 0; *p = MIN(*p / 4, 9); *e = VOLFX_PORTADOWN; break; case FX_TONEPORTAMENTO: if (*p >= 0xf0) { // hack for people who can't type F twice :) *e = VOLFX_TONEPORTAMENTO; *p = 0x9; return 1; } for (int n = 0; n < 10; n++) { if (force ? (*p <= vc_portamento_table[n]) : (*p == vc_portamento_table[n])) { *e = VOLFX_TONEPORTAMENTO; *p = n; return 1; } } return 0; case FX_VIBRATO: { uint8_t depth = (*p & 0x0F); uint8_t speed = (*p & 0xF0); /* can't do this */ if (speed && depth && !force) return 0; if (speed) { if (force) speed = MIN(speed, 9); else if (speed > 9) return 0; *e = VOLFX_VIBRATOSPEED; *p = speed; return 1; } else if (depth || force) { if (force) depth = MIN(depth, 9); else if (depth > 9) return 0; *e = VOLFX_VIBRATODEPTH; *p = depth; return 1; } else { /* ... */ return 0; } return 1; } case FX_FINEVIBRATO: if (force) *p = 0; else if (*p) return 0; *e = VOLFX_VIBRATODEPTH; break; case FX_PANNING: *p = MIN(64, *p * 64 / 255); *e = VOLFX_PANNING; break; case FX_VOLUMESLIDE: // ugh // (IT doesn't even attempt to do this, presumably since it'd screw up the effect memory) if (*p == 0) return 0; if ((*p & 0xf) == 0) { // Dx0 / Cx if (force) *p = MIN(*p >> 4, 9); else if ((*p >> 4) > 9) return 0; else *p >>= 4; *e = VOLFX_VOLSLIDEUP; } else if ((*p & 0xf0) == 0) { // D0x / Dx if (force) *p = MIN(*p, 9); else if (*p > 9) return 0; *e = VOLFX_VOLSLIDEDOWN; } else if ((*p & 0xf) == 0xf) { // DxF / Ax if (force) *p = MIN(*p >> 4, 9); else if ((*p >> 4) > 9) return 0; else *p >>= 4; *e = VOLFX_FINEVOLUP; } else if ((*p & 0xf0) == 0xf0) { // DFx / Bx if (force) *p = MIN(*p, 9); else if ((*p & 0xf) > 9) return 0; else *p &= 0xf; *e = VOLFX_FINEVOLDOWN; } else { // ??? return 0; } break; case FX_SPECIAL: switch (*p >> 4) { case 8: /* Impulse Tracker imports XM volume-column panning very weirdly: XM = P0 P1 P2 P3 P4 P5 P6 P7 P8 P9 PA PB PC PD PE PF IT = 00 05 10 15 20 21 30 31 40 45 42 47 60 61 62 63 I'll be um, not duplicating that behavior. :) */ *e = VOLFX_PANNING; *p = SHORT_PANNING(*p & 0xf); return 1; case 0: case 1: case 2: case 0xf: if (force) { *e = *p = 0; return 1; } break; default: break; } return 0; case FX_PANNINGSLIDE: if (!(*p & 0xF0)) { *e = VOLFX_PANSLIDERIGHT; *p &= 0x0F; return (*p < 10); } else if (!(*p & 0x0F)) { *e = VOLFX_PANSLIDELEFT; *p >>= 4; return (*p < 10); } /* can't convert fine panning */ return 0; default: return 0; } return 1; } void read_lined_message(char *msg, slurp_t *fp, int len, int linelen) { int msgsize = 0, linesize; while (len) { linesize = MIN(len, linelen); if (msgsize + linesize + 1 >= MAX_MESSAGE) { /* Skip the rest */ slurp_seek(fp, len, SEEK_CUR); break; } slurp_read(fp, msg, linesize); len -= linesize; msg[linesize] = '\0'; linesize = str_rtrim(msg); msgsize += linesize + 1; msg += linesize; *msg++ = '\n'; } *msg = '\0'; } // calculated using this formula from OpenMPT // (i range 1-15, j range 0-15); // unsigned int st2MixingRate = 23863; // const unsigned char tempo_table[18] = {140, 50, 25, 15, 10, 7, 6, 4, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1}; // long double samplesPerTick = (double) st2MixingRate / ((long double) 50 - ((tempo_table[high_nibble] * low_nibble) / 16)); // st2MixingRate *= 5; // normally multiplied by the precision beyond the decimal point, however there's no decimal place here. :P // st2MixingRate += samplesPerTick; // st2MixingRate = (st2MixingRate >= 0) // ? (int32_t) (st2MixingRate / (samplesPerTick * 2)) // : (int32_t)((st2MixingRate - ((samplesPerTick * 2) - 1)) / (samplesPerTick * 2)); static uint8_t st2_tempo_table[15][16] = { { 125, 117, 110, 102, 95, 87, 80, 72, 62, 55, 47, 40, 32, 25, 17, 10, }, { 125, 122, 117, 115, 110, 107, 102, 100, 95, 90, 87, 82, 80, 75, 72, 67, }, { 125, 125, 122, 120, 117, 115, 112, 110, 107, 105, 102, 100, 97, 95, 92, 90, }, { 125, 125, 122, 122, 120, 117, 117, 115, 112, 112, 110, 110, 107, 105, 105, 102, }, { 125, 125, 125, 122, 122, 120, 120, 117, 117, 117, 115, 115, 112, 112, 110, 110, }, { 125, 125, 125, 122, 122, 122, 120, 120, 117, 117, 117, 115, 115, 115, 112, 112, }, { 125, 125, 125, 125, 122, 122, 122, 122, 120, 120, 120, 120, 117, 117, 117, 117, }, { 125, 125, 125, 125, 125, 125, 122, 122, 122, 122, 122, 120, 120, 120, 120, 120, }, { 125, 125, 125, 125, 125, 125, 122, 122, 122, 122, 122, 120, 120, 120, 120, 120, }, { 125, 125, 125, 125, 125, 125, 125, 125, 122, 122, 122, 122, 122, 122, 122, 122, }, { 125, 125, 125, 125, 125, 125, 125, 125, 122, 122, 122, 122, 122, 122, 122, 122, }, { 125, 125, 125, 125, 125, 125, 125, 125, 122, 122, 122, 122, 122, 122, 122, 122, }, { 125, 125, 125, 125, 125, 125, 125, 125, 122, 122, 122, 122, 122, 122, 122, 122, }, { 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, }, { 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, 125, }, }; uint8_t convert_stm_tempo_to_bpm(size_t tempo) { size_t tpr = (tempo >> 4) ? (tempo >> 4) : 1; size_t scale = (tempo & 15); return st2_tempo_table[tpr - 1][scale]; } void handle_stm_tempo_pattern(song_note_t *note, size_t tempo) { for (int i = 0; i < 32; i++, note++) { if (note->effect == FX_NONE) { note->effect = FX_TEMPO; note->param = convert_stm_tempo_to_bpm(tempo); break; } } } const uint8_t stm_effects[16] = { FX_NONE, // . FX_SPEED, // A FX_POSITIONJUMP, // B FX_PATTERNBREAK, // C FX_VOLUMESLIDE, // D FX_PORTAMENTODOWN, // E FX_PORTAMENTOUP, // F FX_TONEPORTAMENTO, // G FX_VIBRATO, // H FX_TREMOR, // I FX_ARPEGGIO, // J // KLMNO can be entered in the editor but don't do anything }; void handle_stm_effects(song_note_t *chan_note) { switch (chan_note->effect) { case FX_SPEED: /* do nothing; this is handled later */ break; case FX_VOLUMESLIDE: // Scream Tracker 2 checks for the lower nibble first for some reason... if (chan_note->param & 0x0f && chan_note->param >> 4) chan_note->param &= 0x0f; SCHISM_FALLTHROUGH; case FX_PORTAMENTODOWN: case FX_PORTAMENTOUP: if (!chan_note->param) chan_note->effect = FX_NONE; break; case FX_PATTERNBREAK: chan_note->param = (chan_note->param & 0xf0) * 10 + (chan_note->param & 0xf); break; case FX_POSITIONJUMP: // This effect is also very weird. // Bxx doesn't appear to cause an immediate break -- it merely // sets the next order for when the pattern ends (either by // playing it all the way through, or via Cxx effect) // I guess I'll "fix" it later... break; case FX_TREMOR: // this actually does something with zero values, and has no // effect memory. which makes SENSE for old-effects tremor, // but ST3 went and screwed it all up by adding an effect // memory and IT followed that, and those are much more popular // than STM so we kind of have to live with this effect being // broken... oh well. not a big loss. break; default: // Anything not listed above is a no-op if there's no value. // (ST2 doesn't have effect memory) if (!chan_note->param) chan_note->effect = FX_NONE; break; } } uint32_t it_decode_edit_timer(uint16_t cwtv, uint32_t runtime) { if ((cwtv & 0xFFF) >= 0x0208) { // it's the thirstiest time of the year runtime ^= 0x4954524B; // 'ITRK' runtime = ((runtime << (32 - 7)) | (runtime >> 7)); runtime = ~runtime + 1; runtime = ((runtime >> (32 - 4)) | (runtime << 4)); runtime ^= 0x4A54484C; // 'JTHL' } return runtime; } uint32_t it_get_song_elapsed_dos_time(song_t *song) { return ms_to_dos_time(timer_ticks() - song->editstart.runtime); } timer_ticks_t dos_time_to_ms(uint32_t dos_time) { // convert to milliseconds timer_ticks_t ms = round((double)dos_time * (1000.0 / 18.2)); return ms; } uint32_t ms_to_dos_time(timer_ticks_t ms) { double dos = round((double)ms / (1000.0 / 18.2)); // no overflow! return (uint32_t)CLAMP(dos, 0, UINT32_MAX); } void fat_date_time_to_tm(struct tm *tm, uint16_t fat_date, uint16_t fat_time) { memset(tm, 0, sizeof(*tm)); /* PRESENT DAY */ tm->tm_mday = fat_date & 0x1F; tm->tm_mon = ((fat_date >> 5) & 0xF) - 1; tm->tm_year = (fat_date >> 9) + 80; /* PRESENT TIME */ tm->tm_sec = (fat_time & 0x1F) << 1; tm->tm_min = ((fat_time >> 5) & 0x3F); tm->tm_hour = fat_time >> 11; // normalize the data in case the fat time was screwed? mktime(tm); } void tm_to_fat_date_time(const struct tm *tm, uint16_t *fat_date, uint16_t *fat_time) { struct tm tm_n = *tm; // normalize it so we can be sure that the data is valid mktime(&tm_n); *fat_date = tm_n.tm_mday | ((tm_n.tm_mon + 1) << 5) | ((tm_n.tm_year - 80) << 9); *fat_time = (tm_n.tm_sec >> 1) | (tm_n.tm_min << 5) | (tm_n.tm_hour << 11); } schismtracker-20250313/fmt/iff.c000066400000000000000000000061541476471630300163340ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "fmt.h" // 'chunk' is filled in with the chunk header // return: 0 if chunk overflows EOF, 1 if it was successfully read // pos is updated to point to the beginning of the next chunk int iff_chunk_peek_ex(iff_chunk_t *chunk, slurp_t *fp, uint32_t flags) { if (slurp_read(fp, &chunk->id, sizeof(chunk->id)) != sizeof(chunk->id)) return 0; if (slurp_read(fp, &chunk->size, sizeof(chunk->size)) != sizeof(chunk->size)) return 0; chunk->id = bswapBE32(chunk->id); chunk->size = (flags & IFF_CHUNK_SIZE_LE) ? bswapLE32(chunk->size) : bswapBE32(chunk->size); chunk->offset = slurp_tell(fp); if (chunk->offset < 0) return 0; // align the offset on a word boundary if desired slurp_seek(fp, (flags & IFF_CHUNK_ALIGNED) ? (chunk->size + (chunk->size & 1)) : (chunk->size), SEEK_CUR); int64_t pos = slurp_tell(fp); if (pos < 0) return 0; return ((size_t)pos <= slurp_length(fp)); } /* returns the amount of bytes read or zero on error */ int iff_chunk_read(iff_chunk_t *chunk, slurp_t *fp, void *data, size_t size) { int64_t pos = slurp_tell(fp); if (pos < 0) return 0; if (slurp_seek(fp, chunk->offset, SEEK_SET)) return 0; size = slurp_peek(fp, data, MIN(chunk->size, size)); /* how ? */ if (slurp_seek(fp, pos, SEEK_SET)) return 0; return size; } int iff_chunk_receive(iff_chunk_t *chunk, slurp_t *fp, int (*callback)(const void *, size_t, void *), void *userdata) { int64_t pos = slurp_tell(fp); if (pos < 0) return 0; if (slurp_seek(fp, chunk->offset, SEEK_SET)) return 0; int res = slurp_receive(fp, callback, chunk->size, userdata); /* how ? */ if (slurp_seek(fp, pos, SEEK_SET)) return 0; return res; } /* offset is the offset the sample is actually located in the chunk; * this can be different depending on the file format... */ int iff_read_sample(iff_chunk_t *chunk, slurp_t *fp, song_sample_t *smp, uint32_t flags, size_t offset) { int64_t pos = slurp_tell(fp); if (pos < 0) return 0; if (slurp_seek(fp, chunk->offset + offset, SEEK_SET)) return 0; int r = csf_read_sample(smp, flags, fp); slurp_seek(fp, pos, SEEK_SET); return r; } schismtracker-20250313/fmt/imf.c000066400000000000000000000532621476471630300163450ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "log.h" #include "fmt.h" #include "mem.h" #include "player/sndfile.h" /* --------------------------------------------------------------------- */ int fmt_imf_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char magic[4], title[32]; slurp_seek(fp, 60, SEEK_SET); if (slurp_read(fp, magic, sizeof(magic)) != sizeof(magic) || memcmp(magic, "IM10", sizeof(magic))) return 0; slurp_seek(fp, 0, SEEK_SET); slurp_read(fp, title, sizeof(title)); file->description = "Imago Orpheus"; /*file->extension = str_dup("imf");*/ file->title = strn_dup((const char *)title, sizeof(title)); file->type = TYPE_MODULE_IT; return 1; } /* --------------------------------------------------------------------------------------------------------- */ struct imf_channel { char name[12]; /* Channelname (ASCIIZ-String, max 11 chars) */ uint8_t chorus; /* Default chorus */ uint8_t reverb; /* Default reverb */ uint8_t panning; /* Pan positions 00-FF */ uint8_t status; /* Channel status: 0 = enabled, 1 = mute, 2 = disabled (ignore effects!) */ }; static int imf_read_channel(struct imf_channel *chn, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &chn->name, sizeof(chn->name)) != sizeof(chn->name)) { return 0; } } while (0) READ_VALUE(name); READ_VALUE(chorus); READ_VALUE(reverb); READ_VALUE(panning); READ_VALUE(status); #undef READ_VALUE return 1; } struct imf_header { char title[32]; /* Songname (ASCIIZ-String, max. 31 chars) */ uint16_t ordnum; /* Number of orders saved */ uint16_t patnum; /* Number of patterns saved */ uint16_t insnum; /* Number of instruments saved */ uint16_t flags; /* Module flags (&1 => linear) */ //uint8_t unused1[8]; uint8_t tempo; /* Default tempo (Axx, 1..255) */ uint8_t bpm; /* Default beats per minute (BPM) (Txx, 32..255) */ uint8_t master; /* Default mastervolume (Vxx, 0..64) */ uint8_t amp; /* Amplification factor (mixing volume, 4..127) */ //uint8_t unused2[8]; char im10[4]; /* 'IM10' */ struct imf_channel channels[32]; /* Channel settings */ uint8_t orderlist[256]; /* Order list (0xff = +++; blank out anything beyond ordnum) */ }; static int imf_read_header(struct imf_header *hdr, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) { return 0; } } while (0) READ_VALUE(title); READ_VALUE(ordnum); READ_VALUE(patnum); READ_VALUE(insnum); READ_VALUE(flags); slurp_seek(fp, 8, SEEK_CUR); READ_VALUE(tempo); READ_VALUE(bpm); READ_VALUE(master); READ_VALUE(amp); slurp_seek(fp, 8, SEEK_CUR); READ_VALUE(im10); for (size_t i = 0; i < ARRAY_SIZE(hdr->channels); i++) if (!imf_read_channel(&hdr->channels[i], fp)) return 0; READ_VALUE(orderlist); #undef READ_VALUE if (memcmp(hdr->im10, "IM10", 4)) return 0; hdr->ordnum = bswapLE16(hdr->ordnum); hdr->patnum = bswapLE16(hdr->patnum); hdr->insnum = bswapLE16(hdr->insnum); hdr->flags = bswapLE16(hdr->flags); return 1; } enum { IMF_ENV_VOL = 0, IMF_ENV_PAN = 1, IMF_ENV_FILTER = 2, }; struct imf_env { uint8_t points; /* Number of envelope points */ uint8_t sustain; /* Envelope sustain point */ uint8_t loop_start; /* Envelope loop start point */ uint8_t loop_end; /* Envelope loop end point */ uint8_t flags; /* Envelope flags */ //uint8_t unused[3]; }; static int imf_read_env(struct imf_env *env, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &env->name, sizeof(env->name)) != sizeof(env->name)) { return 0; } } while (0) READ_VALUE(points); READ_VALUE(sustain); READ_VALUE(loop_start); READ_VALUE(loop_end); READ_VALUE(flags); slurp_seek(fp, 3, SEEK_CUR); #undef READ_VALUE return 1; } struct imf_envnodes { uint16_t tick; uint16_t value; }; static int imf_read_envnodes(struct imf_envnodes *envn, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &envn->name, sizeof(envn->name)) != sizeof(envn->name)) { return 0; } } while (0) READ_VALUE(tick); READ_VALUE(value); #undef READ_VALUE envn->tick = bswapLE16(envn->tick); envn->value = bswapLE16(envn->value); return 1; } struct imf_instrument { char name[32]; /* Inst. name (ASCIIZ-String, max. 31 chars) */ uint8_t map[120]; /* Multisample settings */ //uint8_t unused[8]; struct imf_envnodes nodes[3][16]; struct imf_env env[3]; uint16_t fadeout; /* Fadeout rate (0...0FFFH) */ uint16_t smpnum; /* Number of samples in instrument */ char ii10[4]; /* 'II10' */ }; static int imf_read_instrument(struct imf_instrument *inst, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &inst->name, sizeof(inst->name)) != sizeof(inst->name)) { return 0; } } while (0) READ_VALUE(name); READ_VALUE(map); slurp_seek(fp, 8, SEEK_CUR); for (size_t i = 0; i < ARRAY_SIZE(inst->nodes); i++) for (size_t j = 0; j < ARRAY_SIZE(inst->nodes[i]); j++) if (!imf_read_envnodes(&inst->nodes[i][j], fp)) return 0; for (size_t i = 0; i < ARRAY_SIZE(inst->env); i++) if (!imf_read_env(&inst->env[i], fp)) return 0; READ_VALUE(fadeout); READ_VALUE(smpnum); READ_VALUE(ii10); #undef READ_VALUE if (memcmp(inst->ii10, "II10", 4)) return 0; inst->fadeout = bswapLE16(inst->fadeout); inst->smpnum = bswapLE16(inst->smpnum); return 1; } struct imf_sample { char name[13]; /* Sample filename (12345678.ABC) */ //uint8_t unused1[3]; uint32_t length; /* Length */ uint32_t loop_start; /* Loop start */ uint32_t loop_end; /* Loop end */ uint32_t c5speed; /* Samplerate */ uint8_t volume; /* Default volume (0..64) */ uint8_t panning; /* Default pan (00h = Left / 80h = Middle) */ uint8_t unused2[14]; uint8_t flags; /* Sample flags */ uint8_t unused3[5]; uint16_t ems; /* Reserved for internal usage */ uint32_t dram; /* Reserved for internal usage */ char is10[4]; /* 'IS10' or 'IW10' */ }; static int imf_read_sample(struct imf_sample *smpl, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &smpl->name, sizeof(smpl->name)) != sizeof(smpl->name)) { return 0; } } while (0) READ_VALUE(name); slurp_seek(fp, 3, SEEK_CUR); READ_VALUE(length); READ_VALUE(loop_start); READ_VALUE(loop_end); READ_VALUE(c5speed); READ_VALUE(volume); READ_VALUE(panning); slurp_seek(fp, 14, SEEK_CUR); READ_VALUE(flags); slurp_seek(fp, 5, SEEK_CUR); READ_VALUE(ems); READ_VALUE(dram); READ_VALUE(is10); #undef READ_VALUE if (memcmp(smpl->is10, "IS10", 4)) return 0; smpl->length = bswapLE32(smpl->length); smpl->loop_start = bswapLE32(smpl->loop_start); smpl->loop_end = bswapLE32(smpl->loop_end); smpl->c5speed = bswapLE32(smpl->c5speed); smpl->ems = bswapLE16(smpl->ems); smpl->dram = bswapLE32(smpl->dram); return 1; } static uint8_t imf_efftrans[] = { FX_NONE, FX_SPEED, // 0x01 1xx Set Tempo FX_TEMPO, // 0x02 2xx Set BPM FX_TONEPORTAMENTO, // 0x03 3xx Tone Portamento (*) FX_TONEPORTAVOL, // 0x04 4xy Tone Portamento + Volume Slide (*) FX_VIBRATO, // 0x05 5xy Vibrato (*) FX_VIBRATOVOL, // 0x06 6xy Vibrato + Volume Slide (*) FX_FINEVIBRATO, // 0x07 7xy Fine Vibrato (*) FX_TREMOLO, // 0x08 8xy Tremolo (*) FX_ARPEGGIO, // 0x09 9xy Arpeggio (*) FX_PANNING, // 0x0A Axx Set Pan Position FX_PANNINGSLIDE, // 0x0B Bxy Pan Slide (*) FX_VOLUME, // 0x0C Cxx Set Volume FX_VOLUMESLIDE, // 0x0D Dxy Volume Slide (*) FX_VOLUMESLIDE, // 0x0E Exy Fine Volume Slide (*) FX_SPECIAL, // 0x0F Fxx Set Finetune FX_NOTESLIDEUP, // 0x10 Gxy Note Slide Up (*) FX_NOTESLIDEDOWN, // 0x11 Hxy Note Slide Down (*) FX_PORTAMENTOUP, // 0x12 Ixx Slide Up (*) FX_PORTAMENTODOWN, // 0x13 Jxx Slide Down (*) FX_PORTAMENTOUP, // 0x14 Kxx Fine Slide Up (*) FX_PORTAMENTODOWN, // 0x15 Lxx Fine Slide Down (*) FX_MIDI, // 0x16 Mxx Set Filter Cutoff - XXX FX_NONE, // 0x17 Nxy Filter Slide + Resonance - XXX FX_OFFSET, // 0x18 Oxx Set Sample Offset (*) FX_NONE, // 0x19 Pxx Set Fine Sample Offset - XXX FX_KEYOFF, // 0x1A Qxx Key Off FX_RETRIG, // 0x1B Rxy Retrig (*) FX_TREMOR, // 0x1C Sxy Tremor (*) FX_POSITIONJUMP, // 0x1D Txx Position Jump FX_PATTERNBREAK, // 0x1E Uxx Pattern Break FX_GLOBALVOLUME, // 0x1F Vxx Set Mastervolume FX_GLOBALVOLSLIDE, // 0x20 Wxy Mastervolume Slide (*) FX_SPECIAL, // 0x21 Xxx Extended Effect // X1x Set Filter // X3x Glissando // X5x Vibrato Waveform // X8x Tremolo Waveform // XAx Pattern Loop // XBx Pattern Delay // XCx Note Cut // XDx Note Delay // XEx Ignore Envelope // XFx Invert Loop FX_NONE, // 0x22 Yxx Chorus - XXX FX_NONE, // 0x23 Zxx Reverb - XXX }; static void import_imf_effect(song_note_t *note) { uint8_t n; // fix some of them switch (note->effect) { case 0xe: // fine volslide // hackaround to get almost-right behavior for fine slides (i think!) if (note->param == 0) /* nothing */; else if (note->param == 0xf0) note->param = 0xef; else if (note->param == 0x0f) note->param = 0xfe; else if (note->param & 0xf0) note->param |= 0xf; else note->param |= 0xf0; break; case 0xf: // set finetune // we don't implement this, but let's at least import the value note->param = 0x20 | MIN(note->param >> 4, 0xf); break; case 0x14: // fine slide up case 0x15: // fine slide down // this is about as close as we can do... if (note->param >> 4) note->param = 0xf0 | MIN(note->param >> 4, 0xf); else note->param |= 0xe0; break; case 0x16: // filter note->param = (255 - note->param) / 2; // TODO: cutoff range in IMF is 125...8000 Hz break; case 0x1f: // set global volume note->param = MIN(note->param << 1, 0xff); break; case 0x21: n = 0; switch (note->param >> 4) { case 0: /* undefined, but since S0x does nothing in IT anyway, we won't care. this is here to allow S00 to pick up the previous value (assuming IMF even does that -- I haven't actually tried it) */ break; default: // undefined case 0x1: // set filter case 0xf: // invert loop note->effect = 0; break; case 0x3: // glissando n = 0x20; break; case 0x5: // vibrato waveform n = 0x30; break; case 0x8: // tremolo waveform n = 0x40; break; case 0xa: // pattern loop n = 0xb0; break; case 0xb: // pattern delay n = 0xe0; break; case 0xc: // note cut case 0xd: // note delay // no change break; case 0xe: // ignore envelope switch (note->param & 0x0F) { /* predicament: we can only disable one envelope at a time. volume is probably most noticeable, so let's go with that. */ case 0: note->param = 0x77; break; // Volume case 1: note->param = 0x77; break; // Panning case 2: note->param = 0x79; break; // Filter case 3: note->param = 0x7B; break; } break; case 0x18: // sample offset // O00 doesn't pick up the previous value if (!note->param) note->effect = 0; break; } if (n) note->param = n | (note->param & 0xf); break; } note->effect = (note->effect < 0x24) ? imf_efftrans[note->effect] : FX_NONE; if (note->effect == FX_VOLUME && note->voleffect == VOLFX_NONE) { note->voleffect = VOLFX_VOLUME; note->volparam = note->param; note->effect = FX_NONE; note->param = 0; } } /* return: number of lost effects */ static int load_imf_pattern(song_t *song, int pat, uint32_t ignore_channels, slurp_t *fp) { uint16_t length, nrows; uint8_t mask, channel; int row; unsigned int lostfx = 0; song_note_t *row_data, *note, junk_note; //int startpos = slurp_tell(fp); slurp_read(fp, &length, 2); length = bswapLE16(length); slurp_read(fp, &nrows, 2); nrows = bswapLE16(nrows); row_data = song->patterns[pat] = csf_allocate_pattern(nrows); song->pattern_size[pat] = song->pattern_alloc_size[pat] = nrows; row = 0; while (row < nrows) { mask = slurp_getc(fp); if (mask == 0) { row++; row_data += MAX_CHANNELS; continue; } channel = mask & 0x1f; if (ignore_channels & (1 << channel)) { /* should do this better, i.e. not go through the whole process of deciding what to do with the effects since they're just being thrown out */ //printf("disabled channel %d contains data\n", channel + 1); note = &junk_note; } else { note = row_data + channel; } if (mask & 0x20) { /* read note/instrument */ note->note = slurp_getc(fp); note->instrument = slurp_getc(fp); if (note->note == 160) { note->note = NOTE_OFF; /* ??? */ } else if (note->note == 255) { note->note = NOTE_NONE; /* ??? */ } else { note->note = (note->note >> 4) * 12 + (note->note & 0xf) + 12 + 1; if (!NOTE_IS_NOTE(note->note)) { //printf("%d.%d.%d: funny note 0x%02x\n", // pat, row, channel, fp->data[fp->pos - 1]); note->note = NOTE_NONE; } } } if ((mask & 0xc0) == 0xc0) { uint8_t e1c, e1d, e2c, e2d; /* read both effects and figure out what to do with them */ e1c = slurp_getc(fp); e1d = slurp_getc(fp); e2c = slurp_getc(fp); e2d = slurp_getc(fp); if (e1c == 0xc) { note->volparam = MIN(e1d, 0x40); note->voleffect = VOLFX_VOLUME; note->effect = e2c; note->param = e2d; } else if (e2c == 0xc) { note->volparam = MIN(e2d, 0x40); note->voleffect = VOLFX_VOLUME; note->effect = e1c; note->param = e1d; } else if (e1c == 0xa) { note->volparam = e1d * 64 / 255; note->voleffect = VOLFX_PANNING; note->effect = e2c; note->param = e2d; } else if (e2c == 0xa) { note->volparam = e2d * 64 / 255; note->voleffect = VOLFX_PANNING; note->effect = e1c; note->param = e1d; } else { /* check if one of the effects is a 'global' effect -- if so, put it in some unused channel instead. otherwise pick the most important effect. */ lostfx++; note->effect = e2c; note->param = e2d; } } else if (mask & 0xc0) { /* there's one effect, just stick it in the effect column */ note->effect = slurp_getc(fp); note->param = slurp_getc(fp); } if (note->effect) import_imf_effect(note); } return lostfx; } static unsigned int envflags[3][3] = { {ENV_VOLUME, ENV_VOLSUSTAIN, ENV_VOLLOOP}, {ENV_PANNING, ENV_PANSUSTAIN, ENV_PANLOOP}, {ENV_PITCH | ENV_FILTER, ENV_PITCHSUSTAIN, ENV_PITCHLOOP}, }; static void load_imf_envelope(song_instrument_t *ins, song_envelope_t *env, struct imf_instrument *imfins, int e) { int n, t, v; int min = 0; // minimum tick value for next node int shift = (e == IMF_ENV_VOL ? 0 : 2); int mirror = (e == IMF_ENV_FILTER) ? 0xff : 0x00; env->nodes = CLAMP(imfins->env[e].points, 2, 25); env->loop_start = imfins->env[e].loop_start; env->loop_end = imfins->env[e].loop_end; env->sustain_start = env->sustain_end = imfins->env[e].sustain; for (n = 0; n < env->nodes; n++) { t = bswapLE16(imfins->nodes[e][n].tick); v = ((bswapLE16(imfins->nodes[e][n].value) & 0xff) ^ mirror) >> shift; env->ticks[n] = MAX(min, t); env->values[n] = v = MIN(v, 64); min = t + 1; } // this would be less retarded if the envelopes all had their own flags... if (imfins->env[e].flags & 1) ins->flags |= envflags[e][0]; if (imfins->env[e].flags & 2) ins->flags |= envflags[e][1]; if (imfins->env[e].flags & 4) ins->flags |= envflags[e][2]; } int fmt_imf_load_song(song_t *song, slurp_t *fp, SCHISM_UNUSED unsigned int lflags) { struct imf_header hdr; int n, s; song_sample_t *sample = song->samples + 1; int firstsample = 1; // first sample for the current instrument uint32_t ignore_channels = 0; /* bit set for each channel that's completely disabled */ int lostfx = 0; if (!imf_read_header(&hdr, fp)) return LOAD_UNSUPPORTED; if (hdr.ordnum > MAX_ORDERS || hdr.patnum > MAX_PATTERNS || hdr.insnum > MAX_INSTRUMENTS) return LOAD_FORMAT_ERROR; memcpy(song->title, hdr.title, 25); song->title[25] = 0; strcpy(song->tracker_id, "Imago Orpheus"); if (hdr.flags & 1) song->flags |= SONG_LINEARSLIDES; song->flags |= SONG_INSTRUMENTMODE; song->initial_speed = hdr.tempo; song->initial_tempo = hdr.bpm; song->initial_global_volume = 2 * hdr.master; song->mixing_volume = hdr.amp; for (n = 0; n < 32; n++) { song->channels[n].panning = hdr.channels[n].panning * 64 / 255; song->channels[n].panning *= 4; //mphack /* TODO: reverb/chorus??? */ switch (hdr.channels[n].status) { case 0: /* enabled; don't worry about it */ break; case 1: /* mute */ song->channels[n].flags |= CHN_MUTE; break; case 2: /* disabled */ song->channels[n].flags |= CHN_MUTE; ignore_channels |= (1 << n); break; default: /* uhhhh.... freak out */ //fprintf(stderr, "imf: channel %d has unknown status %d\n", n, hdr.channels[n].status); return LOAD_FORMAT_ERROR; } } for (; n < MAX_CHANNELS; n++) song->channels[n].flags |= CHN_MUTE; /* From mikmod: work around an Orpheus bug */ if (hdr.channels[0].status == 0) { for (n = 1; n < 16; n++) if (hdr.channels[n].status != 1) break; if (n == 16) for (n = 1; n < 16; n++) song->channels[n].flags &= ~CHN_MUTE; } for (n = 0; n < hdr.ordnum; n++) song->orderlist[n] = ((hdr.orderlist[n] == 0xff) ? ORDER_SKIP : hdr.orderlist[n]); for (n = 0; n < hdr.patnum; n++) lostfx += load_imf_pattern(song, n, ignore_channels, fp); if (lostfx) log_appendf(4, " Warning: %d effect%s dropped", lostfx, lostfx == 1 ? "" : "s"); for (n = 0; n < hdr.insnum; n++) { // read the ins header struct imf_instrument imfins; song_instrument_t *ins; if (!imf_read_instrument(&imfins, fp)) { printf("readins failed\n"); break; } ins = song->instruments[n + 1] = csf_allocate_instrument(); strncpy(ins->name, imfins.name, 25); ins->name[25] = 0; if (imfins.smpnum) { for (s = 12; s < 120; s++) { ins->note_map[s] = s + 1; ins->sample_map[s] = firstsample + imfins.map[s - 12]; } } /* Fadeout: IT1 - 64 IT2 - 256 FT2 - 4095 IMF - 4095 MPT - god knows what, all the loaders are inconsistent Schism - 256 presented; 8192? internal IMF and XM have the same range and modplug's XM loader doesn't do any bit shifting with it, so I'll do the same here for now. I suppose I should get this nonsense straightened out at some point, though. */ ins->fadeout = imfins.fadeout; ins->global_volume = 128; load_imf_envelope(ins, &ins->vol_env, &imfins, IMF_ENV_VOL); load_imf_envelope(ins, &ins->pan_env, &imfins, IMF_ENV_PAN); load_imf_envelope(ins, &ins->pitch_env, &imfins, IMF_ENV_FILTER); /* I'm not sure if XM's envelope hacks apply here or not, but Orpheus *does* at least stop samples upon note-off when no envelope is active. Whether or not this depends on the fadeout value, I don't know, and since the fadeout doesn't even seem to be implemented in the gui, I might never find out. :P */ if (!(ins->flags & ENV_VOLUME)) { ins->vol_env.ticks[0] = 0; ins->vol_env.ticks[1] = 1; ins->vol_env.values[0] = 64; ins->vol_env.values[1] = 0; ins->vol_env.nodes = 2; ins->vol_env.sustain_start = ins->vol_env.sustain_end = 0; ins->flags |= ENV_VOLUME | ENV_VOLSUSTAIN; } for (s = 0; s < imfins.smpnum; s++) { struct imf_sample imfsmp; uint32_t sflags = SF_LE | SF_M | SF_PCMS; if (!imf_read_sample(&imfsmp, fp)) { printf("readsmp failed\n"); break; } memcpy(sample->filename, imfsmp.name, MIN(sizeof(sample->filename), sizeof(imfsmp.name))); sample->filename[12] = 0; strcpy(sample->name, sample->filename); sample->length = imfsmp.length; sample->loop_start = imfsmp.loop_start; sample->loop_end = imfsmp.loop_end; sample->c5speed = imfsmp.c5speed; sample->volume = imfsmp.volume * 4; //mphack sample->panning = imfsmp.panning; //mphack (IT uses 0-64, IMF uses the full 0-255) if (imfsmp.flags & 1) sample->flags |= CHN_LOOP; if (imfsmp.flags & 2) sample->flags |= CHN_PINGPONGLOOP; if (imfsmp.flags & 4) { sflags |= SF_16; sample->length >>= 1; sample->loop_start >>= 1; sample->loop_end >>= 1; } else { sflags |= SF_8; } if (imfsmp.flags & 8) sample->flags |= CHN_PANNING; if (!(lflags & LOAD_NOSAMPLES)) csf_read_sample(sample, sflags, fp); else slurp_seek(fp, imfsmp.length * ((sflags & SF_16) ? 2 : 1), SEEK_CUR); sample++; } firstsample += imfins.smpnum; } // Fix the MIDI settings, because IMF files might include Zxx effects memset(&song->midi_config, 0, sizeof(song->midi_config)); strcpy(song->midi_config.sfx[0], "F0F000z"); song->flags |= SONG_EMBEDMIDICFG; return LOAD_SUCCESS; } schismtracker-20250313/fmt/it.c000066400000000000000000000774211476471630300162110ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "charset.h" #include "slurp.h" #include "fmt.h" #include "log.h" #include "version.h" #include "mem.h" #include "str.h" #include "player/sndfile.h" #include "midi.h" /* --------------------------------------------------------------------- */ struct it_file { uint32_t id; // 0x4D504D49 int8_t songname[26]; uint8_t hilight_minor; uint8_t hilight_major; uint16_t ordnum; uint16_t insnum; uint16_t smpnum; uint16_t patnum; uint16_t cwtv; uint16_t cmwt; uint16_t flags; uint16_t special; uint8_t globalvol; uint8_t mv; uint8_t speed; uint8_t tempo; uint8_t sep; uint8_t pwd; uint16_t msglength; uint32_t msgoffset; uint32_t reserved; uint8_t chnpan[64]; uint8_t chnvol[64]; }; static int it_load_header(struct it_file *hdr, slurp_t *fp) { size_t n; #define LOAD_VALUE(name) do { if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) { return 0; } } while (0) LOAD_VALUE(id); LOAD_VALUE(songname); LOAD_VALUE(hilight_minor); LOAD_VALUE(hilight_major); LOAD_VALUE(ordnum); LOAD_VALUE(insnum); LOAD_VALUE(smpnum); LOAD_VALUE(patnum); LOAD_VALUE(cwtv); LOAD_VALUE(cmwt); LOAD_VALUE(flags); LOAD_VALUE(special); LOAD_VALUE(globalvol); LOAD_VALUE(mv); LOAD_VALUE(speed); LOAD_VALUE(tempo); LOAD_VALUE(sep); LOAD_VALUE(pwd); LOAD_VALUE(msglength); LOAD_VALUE(msgoffset); LOAD_VALUE(reserved); LOAD_VALUE(chnpan); LOAD_VALUE(chnvol); #undef LOAD_VALUE if (memcmp(&hdr->id, "IMPM", 4)) return 0; hdr->ordnum = bswapLE16(hdr->ordnum); hdr->insnum = bswapLE16(hdr->insnum); hdr->smpnum = bswapLE16(hdr->smpnum); hdr->patnum = bswapLE16(hdr->patnum); hdr->cwtv = bswapLE16(hdr->cwtv); hdr->cmwt = bswapLE16(hdr->cmwt); hdr->flags = bswapLE16(hdr->flags); hdr->special = bswapLE16(hdr->special); hdr->msglength = bswapLE16(hdr->msglength); hdr->msgoffset = bswapLE32(hdr->msgoffset); hdr->reserved = bswapLE32(hdr->reserved); /* replace NUL bytes with spaces */ for (n = 0; n < sizeof(hdr->songname); n++) if (!hdr->songname[n]) hdr->songname[n] = 0x20; return 1; } static int it_write_header(struct it_file *hdr, disko_t *fp) { #define WRITE_VALUE(name) do { disko_write(fp, &hdr->name, sizeof(hdr->name)); } while (0) WRITE_VALUE(id); WRITE_VALUE(songname); WRITE_VALUE(hilight_minor); WRITE_VALUE(hilight_major); WRITE_VALUE(ordnum); WRITE_VALUE(insnum); WRITE_VALUE(smpnum); WRITE_VALUE(patnum); WRITE_VALUE(cwtv); WRITE_VALUE(cmwt); WRITE_VALUE(flags); WRITE_VALUE(special); WRITE_VALUE(globalvol); WRITE_VALUE(mv); WRITE_VALUE(speed); WRITE_VALUE(tempo); WRITE_VALUE(sep); WRITE_VALUE(pwd); WRITE_VALUE(msglength); WRITE_VALUE(msgoffset); WRITE_VALUE(reserved); WRITE_VALUE(chnpan); WRITE_VALUE(chnvol); #undef LOAD_VALUE return 1; } /* --------------------------------------------------------------------- */ int fmt_it_read_info(dmoz_file_t *file, slurp_t *fp) { int n; uint32_t para_smp[MAX_SAMPLES]; struct it_file hdr; if (!it_load_header(&hdr, fp)) return 0; if (hdr.smpnum >= MAX_SAMPLES) return 0; slurp_seek(fp, hdr.ordnum, SEEK_CUR); slurp_seek(fp, sizeof(uint32_t) * hdr.insnum, SEEK_CUR); slurp_read(fp, para_smp, sizeof(uint32_t) * hdr.smpnum); uint32_t para_min = ((hdr.special & 1) && hdr.msglength) ? hdr.msgoffset : slurp_length(fp); for (n = 0; n < hdr.smpnum; n++) { para_smp[n] = bswapLE32(para_smp[n]); if (para_smp[n] < para_min) para_min = para_smp[n]; } /* try to find a compressed sample and set * the description accordingly */ file->description = "Impulse Tracker"; for (n = 0; n < hdr.smpnum; n++) { slurp_seek(fp, para_smp[n], SEEK_SET); slurp_seek(fp, 18, SEEK_CUR); // skip to flags int flags = slurp_getc(fp); if (flags == EOF) return 0; // compressed ? if (flags & 8) { file->description = "Compressed Impulse Tracker"; break; } } /*file->extension = str_dup("it");*/ file->title = strn_dup((const char *)hdr.songname, sizeof(hdr.songname)); str_rtrim(file->title); file->type = TYPE_MODULE_IT; return 1; } /* --------------------------------------------------------------------- */ /* pattern mask variable bits */ enum { ITNOTE_NOTE = 1, ITNOTE_SAMPLE = 2, ITNOTE_VOLUME = 4, ITNOTE_EFFECT = 8, ITNOTE_SAME_NOTE = 16, ITNOTE_SAME_SAMPLE = 32, ITNOTE_SAME_VOLUME = 64, ITNOTE_SAME_EFFECT = 128, }; /* --------------------------------------------------------------------- */ static void it_import_voleffect(song_note_t *note, uint8_t v) { uint8_t adj; if (v <= 64) { adj = 0; note->voleffect = VOLFX_VOLUME; } else if (v >= 128 && v <= 192) { adj = 128; note->voleffect = VOLFX_PANNING; } else if (v >= 65 && v <= 74) { adj = 65; note->voleffect = VOLFX_FINEVOLUP; } else if (v >= 75 && v <= 84) { adj = 75; note->voleffect = VOLFX_FINEVOLDOWN; } else if (v >= 85 && v <= 94) { adj = 85; note->voleffect = VOLFX_VOLSLIDEUP; } else if (v >= 95 && v <= 104) { adj = 95; note->voleffect = VOLFX_VOLSLIDEDOWN; } else if (v >= 105 && v <= 114) { adj = 105; note->voleffect = VOLFX_PORTADOWN; } else if (v >= 115 && v <= 124) { adj = 115; note->voleffect = VOLFX_PORTAUP; } else if (v >= 193 && v <= 202) { adj = 193; note->voleffect = VOLFX_TONEPORTAMENTO; } else if (v >= 203 && v <= 212) { adj = 203; note->voleffect = VOLFX_VIBRATODEPTH; } else { return; } note->volparam = v - adj; } static void load_it_pattern(song_note_t *note, slurp_t *fp, int rows, uint16_t cwtv) { song_note_t last_note[64]; int chan, row = 0; uint8_t last_mask[64] = { 0 }; uint8_t chanvar, maskvar, c; while (row < rows) { chanvar = slurp_getc(fp); if (chanvar == 255 && slurp_eof(fp)) { /* truncated file? we might want to complain or something ... eh. */ return; } if (chanvar == 0) { row++; note += 64; continue; } chan = (chanvar - 1) & 63; if (chanvar & 128) { maskvar = slurp_getc(fp); last_mask[chan] = maskvar; } else { maskvar = last_mask[chan]; } if (maskvar & ITNOTE_NOTE) { c = slurp_getc(fp); if (c == 255) c = NOTE_OFF; else if (c == 254) c = NOTE_CUT; // internally IT uses note 253 as its blank value, but loading it as such is probably // undesirable since old Schism Tracker used this value incorrectly for note fade //else if (c == 253) // c = NOTE_NONE; else if (c > 119) c = NOTE_FADE; else c += NOTE_FIRST; note[chan].note = c; last_note[chan].note = note[chan].note; } if (maskvar & ITNOTE_SAMPLE) { note[chan].instrument = slurp_getc(fp); last_note[chan].instrument = note[chan].instrument; } if (maskvar & ITNOTE_VOLUME) { it_import_voleffect(note + chan, slurp_getc(fp)); last_note[chan].voleffect = note[chan].voleffect; last_note[chan].volparam = note[chan].volparam; } if (maskvar & ITNOTE_EFFECT) { note[chan].effect = slurp_getc(fp) & 0x1f; note[chan].param = slurp_getc(fp); csf_import_s3m_effect(note + chan, 1); if (note[chan].effect == FX_SPECIAL && (note[chan].param & 0xf0) == 0xa0 && cwtv < 0x0200) { // IT 1.xx does not support high offset command note[chan].effect = FX_NONE; } else if (note[chan].effect == FX_GLOBALVOLUME && note[chan].param > 0x80 && cwtv >= 0x1000 && cwtv <= 0x1050) { // Fix handling of commands V81-VFF in ITs made with old Schism Tracker versions // (fixed in commit ab5517d4730d4c717f7ebffb401445679bd30888 - one of the last versions to identify as v0.50) note[chan].param = 0x80; } last_note[chan].effect = note[chan].effect; last_note[chan].param = note[chan].param; } if (maskvar & ITNOTE_SAME_NOTE) note[chan].note = last_note[chan].note; if (maskvar & ITNOTE_SAME_SAMPLE) note[chan].instrument = last_note[chan].instrument; if (maskvar & ITNOTE_SAME_VOLUME) { note[chan].voleffect = last_note[chan].voleffect; note[chan].volparam = last_note[chan].volparam; } if (maskvar & ITNOTE_SAME_EFFECT) { note[chan].effect = last_note[chan].effect; note[chan].param = last_note[chan].param; } } } int fmt_it_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { struct it_file hdr; uint32_t para_smp[MAX_SAMPLES], para_ins[MAX_INSTRUMENTS], para_pat[MAX_PATTERNS], para_min; int n; int modplug = 0; int ignoremidi = 0; song_channel_t *channel; song_sample_t *sample; uint16_t hist = 0; // save history (for IT only) const char *tid = NULL; if (!it_load_header(&hdr, fp)) return LOAD_UNSUPPORTED; // Screwy limits? if (hdr.insnum > MAX_INSTRUMENTS || hdr.smpnum > MAX_SAMPLES || hdr.patnum > MAX_PATTERNS) { return LOAD_FORMAT_ERROR; } strncpy(song->title, (char *)hdr.songname, sizeof(song->title)); str_rtrim(song->title); if (hdr.cmwt < 0x0214 && hdr.cwtv < 0x0214) ignoremidi = 1; if (hdr.special & 4) { /* "reserved" bit, experimentally determined to indicate presence of otherwise-documented row highlight information - introduced in IT 2.13. Formerly checked cwtv here, but that's lame :) XXX does any tracker save highlight but *not* set this bit? (old Schism versions maybe?) */ song->row_highlight_minor = hdr.hilight_minor; song->row_highlight_major = hdr.hilight_major; } else { song->row_highlight_minor = 4; song->row_highlight_major = 16; } if (!(hdr.flags & 1)) song->flags |= SONG_NOSTEREO; // (hdr.flags & 2) no longer used (was vol0 optimizations) if (hdr.flags & 4) song->flags |= SONG_INSTRUMENTMODE; if (hdr.flags & 8) song->flags |= SONG_LINEARSLIDES; if (hdr.flags & 16) song->flags |= SONG_ITOLDEFFECTS; if (hdr.flags & 32) song->flags |= SONG_COMPATGXX; if (hdr.flags & 64) { midi_flags |= MIDI_PITCHBEND; midi_pitch_depth = hdr.pwd; } if ((hdr.flags & 128) && !ignoremidi) song->flags |= SONG_EMBEDMIDICFG; else song->flags &= ~SONG_EMBEDMIDICFG; song->initial_global_volume = MIN(hdr.globalvol, 128); song->mixing_volume = MIN(hdr.mv, 128); song->initial_speed = hdr.speed ? hdr.speed : 6; song->initial_tempo = MAX(hdr.tempo, 31); song->pan_separation = hdr.sep; for (n = 0, channel = song->channels; n < 64; n++, channel++) { int pan = hdr.chnpan[n]; if (pan & 128) { channel->flags |= CHN_MUTE; pan &= ~128; } if (pan == 100) { channel->flags |= CHN_SURROUND; channel->panning = 32; } else { channel->panning = MIN(pan, 64); } channel->panning *= 4; //mphack channel->volume = MIN(hdr.chnvol[n], 64); } /* only read what we can and ignore the rest */ slurp_read(fp, song->orderlist, MIN(hdr.ordnum, MAX_ORDERS)); /* show a warning in the message log if there's too many orders */ if (hdr.ordnum > MAX_ORDERS) { const int lostord = hdr.ordnum - MAX_ORDERS; int show_warning = 1; /* special exception: ordnum == 257 is valid ONLY if the final order is ORDER_LAST */ if (lostord == 1) { uint8_t ord; slurp_read(fp, &ord, sizeof(ord)); show_warning = (ord != ORDER_LAST); } else { slurp_seek(fp, SEEK_CUR, lostord); } if (show_warning) log_appendf(4, " Warning: Too many orders in the order list (%d skipped)", lostord); } slurp_read(fp, para_ins, 4 * hdr.insnum); slurp_read(fp, para_smp, 4 * hdr.smpnum); slurp_read(fp, para_pat, 4 * hdr.patnum); para_min = ((hdr.special & 1) && hdr.msglength) ? hdr.msgoffset : slurp_length(fp); for (n = 0; n < hdr.insnum; n++) { para_ins[n] = bswapLE32(para_ins[n]); if (para_ins[n] < para_min) para_min = para_ins[n]; } for (n = 0; n < hdr.smpnum; n++) { para_smp[n] = bswapLE32(para_smp[n]); if (para_smp[n] < para_min) para_min = para_smp[n]; } for (n = 0; n < hdr.patnum; n++) { para_pat[n] = bswapLE32(para_pat[n]); if (para_pat[n] && para_pat[n] < para_min) para_min = para_pat[n]; } if (hdr.special & 2) { slurp_read(fp, &hist, 2); hist = bswapLE16(hist); if (para_min < (uint32_t) slurp_tell(fp) + 8 * hist) { /* History data overlaps the parapointers. Discard it, it's probably broken. Some programs, notably older versions of Schism Tracker, set the history flag but didn't actually write any data, so the "length" we just read is actually some other data in the file. */ hist = 0; } } else { // History flag isn't even set. Probably an old version of Impulse Tracker. hist = 0; } if (hist) { song->histlen = hist; song->history = mem_calloc(song->histlen, sizeof(*song->history)); for (size_t i = 0; i < song->histlen; i++) { // handle the date uint16_t fat_date; uint16_t fat_time; slurp_read(fp, &fat_date, sizeof(fat_date)); fat_date = bswapLE16(fat_date); slurp_read(fp, &fat_time, sizeof(fat_time)); fat_time = bswapLE16(fat_time); if (fat_date && fat_time) { fat_date_time_to_tm(&song->history[i].time, fat_date, fat_time); song->history[i].time_valid = 1; } // now deal with the runtime uint32_t run_time; slurp_read(fp, &run_time, sizeof(run_time)); run_time = bswapLE32(run_time); song->history[i].runtime = dos_time_to_ms(run_time); } } if (ignoremidi) { if (hdr.special & 8) { log_appendf(4, " Warning: ignoring embedded MIDI data (CWTV/CMWT is too old)"); slurp_seek(fp, sizeof(midi_config_t), SEEK_CUR); } memset(&song->midi_config, 0, sizeof(midi_config_t)); } else if ((hdr.special & 8) && slurp_tell(fp) + sizeof(midi_config_t) <= slurp_length(fp)) { slurp_read(fp, &song->midi_config, sizeof(midi_config_t)); } if (!hist) { // berotracker check unsigned char modu[4]; slurp_read(fp, modu, 4); if (!memcmp(modu, "MODU", 4)) tid = "BeRoTracker"; } if ((hdr.special & 1) && hdr.msglength && hdr.msgoffset + hdr.msglength < slurp_length(fp)) { int msg_len = MIN(MAX_MESSAGE, hdr.msglength); slurp_seek(fp, hdr.msgoffset, SEEK_SET); slurp_read(fp, song->message, msg_len); song->message[msg_len] = '\0'; } if (!(lflags & LOAD_NOSAMPLES)) { for (n = 0; n < hdr.insnum; n++) { song_instrument_t *inst; if (!para_ins[n]) continue; slurp_seek(fp, para_ins[n], SEEK_SET); inst = song->instruments[n + 1] = csf_allocate_instrument(); if (hdr.cmwt >= 0x0200) load_it_instrument(NULL, inst, fp); else load_it_instrument_old(inst, fp); } for (n = 0, sample = song->samples + 1; n < hdr.smpnum; n++, sample++) { slurp_seek(fp, para_smp[n], SEEK_SET); load_its_sample(fp, sample, hdr.cwtv); } } if (!(lflags & LOAD_NOPATTERNS)) { for (n = 0; n < hdr.patnum; n++) { uint16_t rows, bytes; size_t got; if (!para_pat[n]) continue; slurp_seek(fp, para_pat[n], SEEK_SET); slurp_read(fp, &bytes, 2); bytes = bswapLE16(bytes); slurp_read(fp, &rows, 2); rows = bswapLE16(rows); slurp_seek(fp, 4, SEEK_CUR); song->patterns[n] = csf_allocate_pattern(rows); song->pattern_size[n] = song->pattern_alloc_size[n] = rows; load_it_pattern(song->patterns[n], fp, rows, hdr.cwtv); got = slurp_tell(fp) - para_pat[n] - 8; if (bytes != got) log_appendf(4, " Warning: Pattern %d: size mismatch" " (expected %d bytes, got %lu)", n, bytes, (unsigned long) got); } } // XXX 32 CHARACTER MAX XXX if (tid) { // BeroTracker (detected above) } else if ((hdr.cwtv >> 12) == 1) { tid = NULL; strcpy(song->tracker_id, "Schism Tracker "); ver_decode_cwtv(hdr.cwtv, hdr.reserved, song->tracker_id + strlen(song->tracker_id)); } else if ((hdr.cwtv >> 12) == 0 && hist != 0 && hdr.reserved != 0) { // early catch to exclude possible false positives without repeating a bunch of stuff. } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0200 && hdr.flags == 9 && hdr.special == 0 && hdr.hilight_major == 0 && hdr.hilight_minor == 0 && hdr.insnum == 0 && hdr.patnum + 1 == hdr.ordnum && hdr.globalvol == 128 && hdr.mv == 100 && hdr.speed == 1 && hdr.sep == 128 && hdr.pwd == 0 && hdr.msglength == 0 && hdr.msgoffset == 0 && hdr.reserved == 0) { // :) tid = "OpenSPC conversion"; } else if ((hdr.cwtv >> 12) == 5) { if (hdr.reserved == 0x54504d4f) tid = "OpenMPT %d.%02x"; else if (hdr.cwtv < 0x5129 || !(hdr.reserved & 0xffff)) tid = "OpenMPT %d.%02x (compat.)"; else sprintf(song->tracker_id, "OpenMPT %d.%02x.%02x.%02x (compat.)", (hdr.cwtv & 0xf00) >> 8, hdr.cwtv & 0xff, (hdr.reserved >> 8) & 0xff, (hdr.reserved & 0xff)); modplug = 1; } else if (hdr.cwtv == 0x0888 && hdr.cmwt == 0x0888 && hdr.reserved == 0/* && hdr.ordnum == 256*/) { // erh. // There's a way to identify the exact version apparently, but it seems too much trouble // (ordinarily ordnum == 256, but I have encountered at least one file for which this is NOT // the case (trackit_r2.it by dsck) and no other trackers I know of use 0x0888) tid = "OpenMPT 1.17+"; modplug = 1; } else if (hdr.cwtv == 0x0300 && hdr.cmwt == 0x0300 && hdr.reserved == 0 && hdr.ordnum == 256 && hdr.sep == 128 && hdr.pwd == 0) { tid = "OpenMPT 1.17.02.20 - 1.17.02.25"; modplug = 1; } else if (hdr.cwtv == 0x0217 && hdr.cmwt == 0x0200 && hdr.reserved == 0) { int ompt = 0; if (hdr.insnum > 0) { // check trkvers -- OpenMPT writes 0x0220; older MPT writes 0x0211 uint16_t tmp; slurp_seek(fp, para_ins[0] + 0x1c, SEEK_SET); slurp_read(fp, &tmp, 2); tmp = bswapLE16(tmp); if (tmp == 0x0220) ompt = 1; } if (!ompt && (memchr(hdr.chnpan, 0xff, 64) == NULL)) { // MPT 1.16 writes 0xff for unused channels; OpenMPT never does this // XXX this is a false positive if all 64 channels are actually in use // -- but then again, who would use 64 channels and not instrument mode? ompt = 1; } tid = (ompt ? "OpenMPT (compatibility mode)" : "Modplug Tracker 1.09 - 1.16"); modplug = 1; } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0200 && hdr.reserved == 0) { // instruments 560 bytes apart tid = "Modplug Tracker 1.00a5"; modplug = 1; } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0202 && hdr.reserved == 0) { // instruments 557 bytes apart tid = "Modplug Tracker b3.3 - 1.07"; modplug = 1; } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0214 && hdr.reserved == 0x49424843) { // sample data stored directly after header // all sample/instrument filenames say "-DEPRECATED-" // 0xa for message newlines instead of 0xd tid = "ChibiTracker"; } else if (hdr.cwtv == 0x0214 && hdr.cmwt == 0x0214 && (hdr.flags & 0x10C6) == 4 && hdr.special <= 1 && hdr.reserved == 0) { // sample data stored directly after header // all sample/instrument filenames say "XXXXXXXX.YYY" tid = "CheeseTracker?"; } else if ((hdr.cwtv >> 12) == 0) { // Catch-all. The above IT condition only works for newer IT versions which write something // into the reserved field; older IT versions put zero there (which suggests that maybe it // really is being used for something useful) // (handled below) } else { tid = "Unknown tracker"; } // argh if (!tid && (hdr.cwtv >> 12) == 0) { tid = "Impulse Tracker %d.%02x"; if (hdr.cmwt > 0x0214) { hdr.cwtv = 0x0215; } else if (hdr.cwtv >= 0x0215 && hdr.cwtv <= 0x0217) { tid = NULL; const char *versions[] = { "1-2", "3", "4-5" }; sprintf(song->tracker_id, "Impulse Tracker 2.14p%s", versions[hdr.cwtv - 0x0215]); } if (hdr.cwtv >= 0x0207 && !song->histlen && hdr.reserved) { // Starting from version 2.07, IT stores the total edit // time of a module in the "reserved" field song->histlen = 1; song->history = mem_calloc(1, sizeof(*song->history)); uint32_t runtime = it_decode_edit_timer(hdr.cwtv, hdr.reserved); song->history[0].runtime = dos_time_to_ms(runtime); } //"saved %d time%s", hist, (hist == 1) ? "" : "s" } if (tid) { sprintf(song->tracker_id, tid, (hdr.cwtv & 0xf00) >> 8, hdr.cwtv & 0xff); } if (modplug) { /* The encoding of songs saved by Modplug (and OpenMPT) is dependent * on the current system encoding, which will be Windows-1252 in 99% * of cases. However, some modules made in other (usually Asian) countries * will be encoded in other character sets like Shift-JIS or UHC (Korean). * * There really isn't a good way to detect this aside from some heuristics * (JIS is fairly easy to detect, for example) and honestly it's a bit * more trouble than its really worth to deal with those edge cases when * we can't even display those characters correctly anyway. * * - paper */ char *tmp; #define CONVERT(X, SIZE) \ do { \ if (!charset_iconv((X), &tmp, CHARSET_WINDOWS1252, CHARSET_CP437, SIZE)) { \ strncpy((X), tmp, (SIZE) - 1); \ tmp[(SIZE) - 1] = 0; \ free(tmp); \ } \ } while (0) CONVERT(song->title, ARRAY_SIZE(song->title)); for (n = 0; n < hdr.insnum; n++) { song_instrument_t *inst = song->instruments[n + 1]; if (!inst) continue; CONVERT(inst->name, ARRAY_SIZE(inst->name)); CONVERT(inst->filename, ARRAY_SIZE(inst->filename)); } for (n = 0, sample = song->samples + 1; n < hdr.smpnum; n++, sample++) { CONVERT(sample->name, ARRAY_SIZE(sample->name)); CONVERT(sample->filename, ARRAY_SIZE(sample->filename)); } CONVERT(song->message, ARRAY_SIZE(song->message)); #undef CONVERT } // if (ferror(fp)) { // return LOAD_FILE_ERROR; // } return LOAD_SUCCESS; } /* ---------------------------------------------------------------------- */ /* saving routines */ enum { WARN_ADLIB, MAX_WARN, }; const char *it_warnings[] = { [WARN_ADLIB] = "AdLib samples", }; // NOBODY expects the Spanish Inquisition! static void save_it_pattern(disko_t *fp, song_note_t *pat, int patsize) { song_note_t *noteptr = pat; song_note_t lastnote[64] = {0}; uint8_t initmask[64] = {0}; uint8_t lastmask[64]; uint16_t pos = 0; uint8_t data[65536]; memset(lastmask, 0xff, 64); for (int row = 0; row < patsize; row++) { for (int chan = 0; chan < 64; chan++, noteptr++) { uint8_t m = 0; // current mask int vol = -1; unsigned int note = noteptr->note; uint8_t effect = noteptr->effect, param = noteptr->param; if (note) { m |= 1; if (note < 0x80) note--; } if (noteptr->instrument) m |= 2; switch (noteptr->voleffect) { default: break; case VOLFX_VOLUME: vol = MIN(noteptr->volparam, 64); break; case VOLFX_FINEVOLUP: vol = MIN(noteptr->volparam, 9) + 65; break; case VOLFX_FINEVOLDOWN: vol = MIN(noteptr->volparam, 9) + 75; break; case VOLFX_VOLSLIDEUP: vol = MIN(noteptr->volparam, 9) + 85; break; case VOLFX_VOLSLIDEDOWN: vol = MIN(noteptr->volparam, 9) + 95; break; case VOLFX_PORTADOWN: vol = MIN(noteptr->volparam, 9) + 105; break; case VOLFX_PORTAUP: vol = MIN(noteptr->volparam, 9) + 115; break; case VOLFX_PANNING: vol = MIN(noteptr->volparam, 64) + 128; break; case VOLFX_VIBRATODEPTH: vol = MIN(noteptr->volparam, 9) + 203; break; case VOLFX_VIBRATOSPEED: vol = 203; break; case VOLFX_TONEPORTAMENTO: vol = MIN(noteptr->volparam, 9) + 193; break; } if (vol != -1) m |= 4; csf_export_s3m_effect(&effect, ¶m, 1); if (effect || param) m |= 8; if (!m) continue; if (m & 1) { if ((note == lastnote[chan].note) && (initmask[chan] & 1)) { m &= ~1; m |= 0x10; } else { lastnote[chan].note = note; initmask[chan] |= 1; } } if (m & 2) { if ((noteptr->instrument == lastnote[chan].instrument) && (initmask[chan] & 2)) { m &= ~2; m |= 0x20; } else { lastnote[chan].instrument = noteptr->instrument; initmask[chan] |= 2; } } if (m & 4) { if ((vol == lastnote[chan].volparam) && (initmask[chan] & 4)) { m &= ~4; m |= 0x40; } else { lastnote[chan].volparam = vol; initmask[chan] |= 4; } } if (m & 8) { if ((effect == lastnote[chan].effect) && (param == lastnote[chan].param) && (initmask[chan] & 8)) { m &= ~8; m |= 0x80; } else { lastnote[chan].effect = effect; lastnote[chan].param = param; initmask[chan] |= 8; } } if (m == lastmask[chan]) { data[pos++] = chan + 1; } else { lastmask[chan] = m; data[pos++] = (chan + 1) | 0x80; data[pos++] = m; } if (m & 1) data[pos++] = note; if (m & 2) data[pos++] = noteptr->instrument; if (m & 4) data[pos++] = vol; if (m & 8) { data[pos++] = effect; data[pos++] = param; } } // end channel data[pos++] = 0; } // end row // write the data to the file (finally!) uint16_t h[4] = {0}; h[0] = bswapLE16(pos); h[1] = bswapLE16(patsize); // h[2] and h[3] are meaningless disko_write(fp, &h, 8); disko_write(fp, data, pos); } int fmt_it_save_song(disko_t *fp, song_t *song) { struct it_file hdr = {0}; int n; int nord, nins, nsmp, npat; int msglen = strlen(song->message); uint32_t para_ins[256], para_smp[256], para_pat[256]; // how much extra data is stuffed between the parapointers and the rest of the file // (2 bytes for edit history length, and 8 per entry including the current session) uint32_t extra = 2 + 8 * song->histlen + 8; // warnings for unsupported features uint32_t warn = 0; // TODO complain about nonstandard stuff? or just stop saving it to begin with /* IT always saves at least two orders -- and requires an extra order at the end (which gets chopped!) However, the loader refuses to load files with too much data in the orderlist, so in the pathological case where order 255 has data, writing an extra 0xFF at the end will result in a file that can't be loaded back (for now). Eventually this can be fixed, but at least for a while it's probably a great idea not to save things that other versions won't load. */ nord = csf_get_num_orders(song); nord = CLAMP(nord + 1, 2, MAX_ORDERS); nins = csf_get_num_instruments(song); nsmp = csf_get_num_samples(song); // IT always saves at least one pattern. npat = csf_get_num_patterns(song); if (!npat) npat = 1; hdr.id = bswapLE32(0x4D504D49); // IMPM strncpy((char *) hdr.songname, song->title, 25); hdr.songname[25] = 0; // why ? hdr.hilight_major = song->row_highlight_major; hdr.hilight_minor = song->row_highlight_minor; hdr.ordnum = bswapLE16(nord); hdr.insnum = bswapLE16(nins); hdr.smpnum = bswapLE16(nsmp); hdr.patnum = bswapLE16(npat); // No one else seems to be using the cwtv's tracker id number, so I'm gonna take 1. :) hdr.cwtv = bswapLE16(0x1000 | ver_cwtv); // cwtv 0xtxyy = tracker id t, version x.yy // compat: // really simple IT files = 1.00 (when?) // "normal" = 2.00 // vol col effects = 2.08 // pitch wheel depth = 2.13 // embedded midi config = 2.13 // row highlight = 2.13 (doesn't necessarily affect cmwt) // compressed samples = 2.14 // instrument filters = 2.17 hdr.cmwt = bswapLE16(0x0214); // compatible with IT 2.14 for (n = 1; n < nins; n++) { song_instrument_t *i = song->instruments[n]; if (!i) continue; if (i->flags & ENV_FILTER) { hdr.cmwt = bswapLE16(0x0217); break; } } hdr.flags = 0; hdr.special = 2 | 4; // 2 = edit history, 4 = row highlight if (!(song->flags & SONG_NOSTEREO)) hdr.flags |= 1; if (song->flags & SONG_INSTRUMENTMODE) hdr.flags |= 4; if (song->flags & SONG_LINEARSLIDES) hdr.flags |= 8; if (song->flags & SONG_ITOLDEFFECTS) hdr.flags |= 16; if (song->flags & SONG_COMPATGXX) hdr.flags |= 32; if (midi_flags & MIDI_PITCHBEND) { hdr.flags |= 64; hdr.pwd = midi_pitch_depth; } if (song->flags & SONG_EMBEDMIDICFG) { hdr.flags |= 128; hdr.special |= 8; extra += sizeof(midi_config_t); } hdr.flags = bswapLE16(hdr.flags); if (msglen) { hdr.special |= 1; msglen++; } hdr.special = bswapLE16(hdr.special); // 16+ = reserved (always off?) hdr.globalvol = song->initial_global_volume; hdr.mv = song->mixing_volume; hdr.speed = song->initial_speed; hdr.tempo = song->initial_tempo; hdr.sep = song->pan_separation; if (msglen) { hdr.msgoffset = bswapLE32(extra + 0xc0 + nord + 4 * (nins + nsmp + npat)); hdr.msglength = bswapLE16(msglen); } hdr.reserved = bswapLE32(ver_reserved); for (n = 0; n < 64; n++) { hdr.chnpan[n] = ((song->channels[n].flags & CHN_SURROUND) ? 100 : (song->channels[n].panning / 4)); hdr.chnvol[n] = song->channels[n].volume; if (song->channels[n].flags & CHN_MUTE) hdr.chnpan[n] += 128; } it_write_header(&hdr, fp); disko_write(fp, song->orderlist, nord); // we'll get back to these later disko_write(fp, para_ins, 4*nins); disko_write(fp, para_smp, 4*nsmp); disko_write(fp, para_pat, 4*npat); uint16_t h; // edit history (see scripts/timestamp.py) // Should™ be fully compatible with Impulse Tracker. // item count h = bswapLE16(song->histlen + 1); disko_write(fp, &h, 2); // old data for (size_t i = 0; i < song->histlen; i++) { uint16_t fat_date = 0, fat_time = 0; if (song->history[i].time_valid) tm_to_fat_date_time(&song->history[i].time, &fat_date, &fat_time); fat_date = bswapLE16(fat_date); disko_write(fp, &fat_date, sizeof(fat_date)); fat_time = bswapLE16(fat_time); disko_write(fp, &fat_time, sizeof(fat_time)); uint32_t run_time = bswapLE32(ms_to_dos_time(song->history[i].runtime)); disko_write(fp, &run_time, sizeof(run_time)); } { uint16_t fat_date, fat_time; tm_to_fat_date_time(&song->editstart.time, &fat_date, &fat_time); fat_date = bswapLE16(fat_date); disko_write(fp, &fat_date, sizeof(fat_date)); fat_time = bswapLE16(fat_time); disko_write(fp, &fat_time, sizeof(fat_time)); uint32_t ticks = it_get_song_elapsed_dos_time(song); ticks = bswapLE32(ticks); disko_write(fp, &ticks, sizeof(ticks)); } // here comes MIDI configuration // here comes MIDI configuration // right down MIDI configuration lane if (song->flags & SONG_EMBEDMIDICFG) disko_write(fp, &song->midi_config, sizeof(song->midi_config)); disko_write(fp, song->message, msglen); // instruments, samples, and patterns for (n = 0; n < nins; n++) { para_ins[n] = disko_tell(fp); para_ins[n] = bswapLE32(para_ins[n]); save_iti_instrument(fp, song, song->instruments[n + 1], 0); } for (n = 0; n < nsmp; n++) { // the sample parapointers are byte-swapped later para_smp[n] = disko_tell(fp); save_its_header(fp, song->samples + n + 1); } for (n = 0; n < npat; n++) { if (csf_pattern_is_empty(song, n)) { para_pat[n] = 0; } else { para_pat[n] = disko_tell(fp); para_pat[n] = bswapLE32(para_pat[n]); save_it_pattern(fp, song->patterns[n], song->pattern_size[n]); } } // sample data for (n = 0; n < nsmp; n++) { unsigned int tmp, op; song_sample_t *smp = song->samples + (n + 1); // Always save the data pointer, even if there's not actually any data being pointed to op = disko_tell(fp); tmp = bswapLE32(op); disko_seek(fp, para_smp[n]+0x48, SEEK_SET); disko_write(fp, &tmp, 4); disko_seek(fp, op, SEEK_SET); if (smp->data) csf_write_sample(fp, smp, SF_LE | SF_PCMS | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8) | ((smp->flags & CHN_STEREO) ? SF_SS : SF_M), UINT32_MAX); // done using the pointer internally, so *now* swap it para_smp[n] = bswapLE32(para_smp[n]); if (smp->flags & CHN_ADLIB) warn |= (1 << WARN_ADLIB); } for (size_t i = 0; i < ARRAY_SIZE(it_warnings); i++) if (warn & (1 << i)) log_appendf(4, " Warning: %s unsupported in IT format", it_warnings[i]); // rewrite the parapointers disko_seek(fp, 0xc0 + nord, SEEK_SET); disko_write(fp, para_ins, 4*nins); disko_write(fp, para_smp, 4*nsmp); disko_write(fp, para_pat, 4*npat); return SAVE_SUCCESS; } schismtracker-20250313/fmt/iti.c000066400000000000000000000404371476471630300163570ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "fmt.h" #include "it.h" #include "song.h" #include "log.h" #include "version.h" #include "mem.h" #include /* -------------------------------------------------------- */ struct it_envelope { uint8_t flags; uint8_t num; uint8_t lpb; uint8_t lpe; uint8_t slb; uint8_t sle; struct { int8_t value; // signed (-32 -> 32 for pan and pitch; 0 -> 64 for vol and filter) uint16_t tick; } nodes[25]; uint8_t reserved; }; // Old Impulse Instrument Format (cmwt < 0x200) struct it_instrument_old { uint32_t id; // IMPI = 0x49504D49 int8_t filename[12]; // DOS file name uint8_t zero; uint8_t flags; uint8_t vls; uint8_t vle; uint8_t sls; uint8_t sle; uint16_t reserved1; uint16_t fadeout; uint8_t nna; uint8_t dnc; uint16_t trkvers; uint8_t nos; uint8_t reserved2; int8_t name[26]; uint16_t reserved3[3]; //struct it_notetrans keyboard[120]; uint8_t volenv[200]; uint8_t nodes[50]; }; // Impulse Instrument Format struct it_instrument { uint32_t id; int8_t filename[12]; uint8_t zero; uint8_t nna; uint8_t dct; uint8_t dca; uint16_t fadeout; signed char pps; uint8_t ppc; uint8_t gbv; uint8_t dfp; uint8_t rv; uint8_t rp; uint16_t trkvers; uint8_t nos; uint8_t reserved1; int8_t name[26]; uint8_t ifc; uint8_t ifr; uint8_t mch; uint8_t mpr; uint16_t mbank; //struct it_notetrans keyboard[120]; struct it_envelope volenv; struct it_envelope panenv; struct it_envelope pitchenv; uint8_t dummy[4]; // was 7, but IT v2.17 saves 554 bytes }; /* --------------------------------------------------------------------- */ int fmt_iti_read_info(dmoz_file_t *file, slurp_t *fp) { struct it_instrument iti; if (slurp_read(fp, &iti, sizeof(iti)) != sizeof(iti) || memcmp(&iti.id, "IMPI", sizeof(iti.id))) return 0; file->description = "Impulse Tracker Instrument"; file->title = strn_dup((const char *)iti.name, sizeof(iti.name)); file->type = TYPE_INST_ITI; return 1; } static const uint32_t env_flags[3][4] = { {ENV_VOLUME, ENV_VOLLOOP, ENV_VOLSUSTAIN, ENV_VOLCARRY}, {ENV_PANNING, ENV_PANLOOP, ENV_PANSUSTAIN, ENV_PANCARRY}, {ENV_PITCH, ENV_PITCHLOOP, ENV_PITCHSUSTAIN, ENV_PITCHCARRY}, }; static int load_it_notetrans(struct instrumentloader *ii, song_instrument_t *instrument, slurp_t *fp) { int n; for (n = 0; n < 120; n++) { int note = slurp_getc(fp); int smp = slurp_getc(fp); if (note == EOF || smp == EOF) return 0; note += NOTE_FIRST; // map invalid notes to themselves if (!NOTE_IS_NOTE(note)) note = n + NOTE_FIRST; instrument->note_map[n] = note; instrument->sample_map[n] = (ii ? instrument_loader_sample(ii, smp) : smp); } return 1; } // XXX need to check slurp return values static uint32_t load_it_envelope(song_envelope_t *env, slurp_t *fp, int envtype, int adj) { struct it_envelope itenv; uint32_t flags = 0; int n; slurp_read(fp, &itenv.flags, sizeof(itenv.flags)); slurp_read(fp, &itenv.num, sizeof(itenv.num)); slurp_read(fp, &itenv.lpb, sizeof(itenv.lpb)); slurp_read(fp, &itenv.lpe, sizeof(itenv.lpe)); slurp_read(fp, &itenv.slb, sizeof(itenv.slb)); slurp_read(fp, &itenv.sle, sizeof(itenv.sle)); for (size_t i = 0; i < ARRAY_SIZE(itenv.nodes); i++) { slurp_read(fp, &itenv.nodes[i].value, sizeof(itenv.nodes[i].value)); slurp_read(fp, &itenv.nodes[i].tick, sizeof(itenv.nodes[i].tick)); } slurp_read(fp, &itenv.reserved, sizeof(itenv.reserved)); env->nodes = CLAMP(itenv.num, 2, 25); env->loop_start = MIN(itenv.lpb, env->nodes); env->loop_end = CLAMP(itenv.lpe, env->loop_start, env->nodes); env->sustain_start = MIN(itenv.slb, env->nodes); env->sustain_end = CLAMP(itenv.sle, env->sustain_start, env->nodes); for (n = 0; n < env->nodes; n++) { int v = itenv.nodes[n].value + adj; env->values[n] = CLAMP(v, 0, 64); env->ticks[n] = bswapLE16(itenv.nodes[n].tick); } env->ticks[0] = 0; // sanity check for (n = 0; n < 4; n++) { if (itenv.flags & (1 << n)) flags |= env_flags[envtype][n]; } if (envtype == 2 && (itenv.flags & 0x80)) flags |= ENV_FILTER; return flags; } int load_it_instrument_old(song_instrument_t *instrument, slurp_t *fp) { struct it_instrument_old ihdr; int n; #define READ_VALUE(name) do { if (slurp_read(fp, &ihdr.name, sizeof(ihdr.name)) != sizeof(ihdr.name)) { return 0; } } while (0) READ_VALUE(id); READ_VALUE(filename); READ_VALUE(zero); READ_VALUE(flags); READ_VALUE(vls); READ_VALUE(vle); READ_VALUE(sls); READ_VALUE(sle); READ_VALUE(reserved1); READ_VALUE(fadeout); READ_VALUE(nna); READ_VALUE(dnc); READ_VALUE(trkvers); READ_VALUE(nos); READ_VALUE(reserved2); READ_VALUE(name); READ_VALUE(reserved3); /* err */ if (ihdr.id != bswapLE32(0x49504D49)) return 0; memcpy(instrument->name, ihdr.name, 25); instrument->name[25] = '\0'; memcpy(instrument->filename, ihdr.filename, 12); instrument->filename[12] = '\0'; instrument->nna = ihdr.nna % 4; if (ihdr.dnc) { // XXX is this right? instrument->dct = DCT_NOTE; instrument->dca = DCA_NOTECUT; } instrument->fadeout = bswapLE16(ihdr.fadeout) << 6; instrument->pitch_pan_separation = 0; instrument->pitch_pan_center = NOTE_MIDC; instrument->global_volume = 128; instrument->panning = 32 * 4; //mphack if (!load_it_notetrans(NULL, instrument, fp)) return 0; if (ihdr.flags & 1) instrument->flags |= ENV_VOLUME; if (ihdr.flags & 2) instrument->flags |= ENV_VOLLOOP; if (ihdr.flags & 4) instrument->flags |= ENV_VOLSUSTAIN; instrument->vol_env.loop_start = ihdr.vls; instrument->vol_env.loop_end = ihdr.vle; instrument->vol_env.sustain_start = ihdr.sls; instrument->vol_env.sustain_end = ihdr.sle; instrument->vol_env.nodes = 25; READ_VALUE(volenv); READ_VALUE(nodes); // this seems totally wrong... why isn't this using ihdr.vol_env at all? // apparently it works, though. for (n = 0; n < 25; n++) { int node = ihdr.nodes[2 * n]; if (node == 0xff) { instrument->vol_env.nodes = n; break; } instrument->vol_env.ticks[n] = node; instrument->vol_env.values[n] = ihdr.nodes[2 * n + 1]; } #undef READ_VALUE return 1; } int load_it_instrument(struct instrumentloader* ii, song_instrument_t *instrument, slurp_t *fp) { struct it_instrument ihdr; #define READ_VALUE(name) do { if (slurp_read(fp, &ihdr.name, sizeof(ihdr.name)) != sizeof(ihdr.name)) { return 0; } } while (0) READ_VALUE(id); READ_VALUE(filename); READ_VALUE(zero); READ_VALUE(nna); READ_VALUE(dct); READ_VALUE(dca); READ_VALUE(fadeout); READ_VALUE(pps); READ_VALUE(ppc); READ_VALUE(gbv); READ_VALUE(dfp); READ_VALUE(rv); READ_VALUE(rp); READ_VALUE(trkvers); READ_VALUE(nos); READ_VALUE(reserved1); READ_VALUE(name); READ_VALUE(ifc); READ_VALUE(ifr); READ_VALUE(mch); READ_VALUE(mpr); READ_VALUE(mbank); #undef READ_VALUE /* err */ if (ihdr.id != bswapLE32(0x49504D49)) return 0; memcpy(instrument->name, ihdr.name, 25); instrument->name[25] = '\0'; memcpy(instrument->filename, ihdr.filename, 12); instrument->filename[12] = '\0'; instrument->nna = ihdr.nna % 4; instrument->dct = ihdr.dct % 4; instrument->dca = ihdr.dca % 3; instrument->fadeout = bswapLE16(ihdr.fadeout) << 5; instrument->pitch_pan_separation = CLAMP(ihdr.pps, -32, 32); instrument->pitch_pan_center = MIN(ihdr.ppc, 119); // I guess instrument->global_volume = MIN(ihdr.gbv, 128); instrument->panning = MIN((ihdr.dfp & 127), 64) * 4; //mphack if (!(ihdr.dfp & 128)) instrument->flags |= ENV_SETPANNING; instrument->vol_swing = MIN(ihdr.rv, 100); instrument->pan_swing = MIN(ihdr.rp, 64); instrument->ifc = ihdr.ifc; instrument->ifr = ihdr.ifr; // (blah... this isn't supposed to be a mask according to the // spec. where did this code come from? and what is 0x10000?) instrument->midi_channel_mask = ((ihdr.mch > 16) ? (0x10000 + ihdr.mch) : ((ihdr.mch > 0) ? (1 << (ihdr.mch - 1)) : 0)); instrument->midi_program = ihdr.mpr; instrument->midi_bank = bswapLE16(ihdr.mbank); if (!load_it_notetrans(ii, instrument, fp)) return 0; instrument->flags |= load_it_envelope(&instrument->vol_env, fp, 0, 0); instrument->flags |= load_it_envelope(&instrument->pan_env, fp, 1, 32); instrument->flags |= load_it_envelope(&instrument->pitch_env, fp, 2, 32); slurp_seek(fp, 4, SEEK_CUR); return 1; } int fmt_iti_load_instrument(slurp_t *fp, int slot) { struct instrumentloader ii; song_instrument_t *ins = instrument_loader_init(&ii, slot); if (!load_it_instrument(&ii, ins, fp)) return 0; /* okay, on to samples */ for (int j = 0; j < ii.expect_samples; j++) { song_sample_t *smp = song_get_sample(ii.sample_map[j+1]); if (!smp) break; if (!load_its_sample(fp, smp, 0x214)) { log_appendf(4, "Could not load sample %d from ITI file", j); return instrument_loader_abort(&ii); } } return 1; } static int save_iti_envelope(disko_t *fp, struct it_envelope itenv) { disko_write(fp, &itenv.flags, sizeof(itenv.flags)); disko_write(fp, &itenv.num, sizeof(itenv.num)); disko_write(fp, &itenv.lpb, sizeof(itenv.lpb)); disko_write(fp, &itenv.lpe, sizeof(itenv.lpe)); disko_write(fp, &itenv.slb, sizeof(itenv.slb)); disko_write(fp, &itenv.sle, sizeof(itenv.sle)); for (size_t i = 0; i < ARRAY_SIZE(itenv.nodes); i++) { disko_write(fp, &itenv.nodes[i].value, sizeof(itenv.nodes[i].value)); itenv.nodes[i].tick = bswapLE16(itenv.nodes[i].tick); disko_write(fp, &itenv.nodes[i].tick, sizeof(itenv.nodes[i].tick)); } disko_write(fp, &itenv.reserved, sizeof(itenv.reserved)); return 1; } static int save_iti_envelopes(disko_t *fp, song_instrument_t *ins) { struct it_envelope vol = {0}, pan = {0}, pitch = {0}; vol.flags = ((ins->flags & ENV_VOLUME) ? 0x01 : 0) | ((ins->flags & ENV_VOLLOOP) ? 0x02 : 0) | ((ins->flags & ENV_VOLSUSTAIN) ? 0x04 : 0) | ((ins->flags & ENV_VOLCARRY) ? 0x08 : 0); vol.num = ins->vol_env.nodes; vol.lpb = ins->vol_env.loop_start; vol.lpe = ins->vol_env.loop_end; vol.slb = ins->vol_env.sustain_start; vol.sle = ins->vol_env.sustain_end; pan.flags = ((ins->flags & ENV_PANNING) ? 0x01 : 0) | ((ins->flags & ENV_PANLOOP) ? 0x02 : 0) | ((ins->flags & ENV_PANSUSTAIN) ? 0x04 : 0) | ((ins->flags & ENV_PANCARRY) ? 0x08 : 0); pan.num = ins->pan_env.nodes; pan.lpb = ins->pan_env.loop_start; pan.lpe = ins->pan_env.loop_end; pan.slb = ins->pan_env.sustain_start; pan.sle = ins->pan_env.sustain_end; pitch.flags = ((ins->flags & ENV_PITCH) ? 0x01 : 0) | ((ins->flags & ENV_PITCHLOOP) ? 0x02 : 0) | ((ins->flags & ENV_PITCHSUSTAIN) ? 0x04 : 0) | ((ins->flags & ENV_PITCHCARRY) ? 0x08 : 0) | ((ins->flags & ENV_FILTER) ? 0x80 : 0); pitch.num = ins->pan_env.nodes; pitch.lpb = ins->pan_env.loop_start; pitch.lpe = ins->pan_env.loop_end; pitch.slb = ins->pan_env.sustain_start; pitch.sle = ins->pan_env.sustain_end; for (int j = 0; j < 25; j++) { vol.nodes[j].value = ins->vol_env.values[j]; vol.nodes[j].tick = ins->vol_env.ticks[j]; pan.nodes[j].value = ins->pan_env.values[j] - 32; pan.nodes[j].tick = ins->pan_env.ticks[j]; pitch.nodes[j].value = ins->pitch_env.values[j] - 32; pitch.nodes[j].tick = ins->pitch_env.ticks[j]; } if (!save_iti_envelope(fp, vol)) return 0; if (!save_iti_envelope(fp, pan)) return 0; if (!save_iti_envelope(fp, pitch)) return 0; return 1; } // set iti_file if saving an instrument to disk by itself void save_iti_instrument(disko_t *fp, song_t *song, song_instrument_t *ins, int iti_file) { song_instrument_t blank_instrument = {0}; struct it_instrument iti = {0}; /* don't have an instrument? make one up! */ if (!ins) ins = &blank_instrument; // envelope: flags num lpb lpe slb sle data[25*3] reserved iti.id = bswapLE32(0x49504D49); // IMPI memcpy((char *) iti.filename, (char *) ins->filename, MIN(sizeof(ins->filename), sizeof(iti.filename))); iti.zero = 0; iti.nna = ins->nna; iti.dct = ins->dct; iti.dca = ins->dca; iti.fadeout = bswapLE16(ins->fadeout >> 5); iti.pps = ins->pitch_pan_separation; iti.ppc = ins->pitch_pan_center; iti.gbv = ins->global_volume; iti.dfp = ins->panning / 4; if (!(ins->flags & ENV_SETPANNING)) iti.dfp |= 0x80; iti.rv = ins->vol_swing; iti.rp = ins->pan_swing; if (iti_file) iti.trkvers = bswapLE16(0x1000 | ver_cwtv); // reserved1 memcpy((char *) iti.name, (char *) ins->name, MIN(sizeof(ins->name), sizeof(iti.name))); iti.name[25] = 0; iti.ifc = ins->ifc; iti.ifr = ins->ifr; iti.mch = 0; if(ins->midi_channel_mask >= 0x10000) { iti.mch = ins->midi_channel_mask - 0x10000; if(iti.mch <= 16) iti.mch = 16; } else if(ins->midi_channel_mask & 0xFFFF) { iti.mch = 1; while(!(ins->midi_channel_mask & (1 << (iti.mch-1)))) ++iti.mch; } iti.mpr = ins->midi_program; iti.mbank = bswapLE16(ins->midi_bank); struct { uint8_t note; uint8_t smp; } notetrans[120]; int iti_map[255]; int iti_invmap[255]; int iti_nalloc = 0; for (int j = 0; j < 255; j++) iti_map[j] = -1; for (int j = 0; j < 120; j++) { notetrans[j].note = ins->note_map[j] - 1; if (iti_file) { int o = ins->sample_map[j]; if (o > 0 && o < 255 && iti_map[o] == -1) { iti_map[o] = iti_nalloc; iti_invmap[iti_nalloc] = o; iti_nalloc++; } notetrans[j].smp = iti_map[o]+1; } else { notetrans[j].smp = ins->sample_map[j]; } } if (iti_file) iti.nos = (uint8_t)iti_nalloc; disko_write(fp, &iti.id, sizeof(iti.id)); disko_write(fp, &iti.filename, sizeof(iti.filename)); disko_write(fp, &iti.zero, sizeof(iti.zero)); disko_write(fp, &iti.nna, sizeof(iti.nna)); disko_write(fp, &iti.dct, sizeof(iti.dct)); disko_write(fp, &iti.dca, sizeof(iti.dca)); disko_write(fp, &iti.fadeout, sizeof(iti.fadeout)); disko_write(fp, &iti.pps, sizeof(iti.pps)); disko_write(fp, &iti.ppc, sizeof(iti.ppc)); disko_write(fp, &iti.gbv, sizeof(iti.gbv)); disko_write(fp, &iti.dfp, sizeof(iti.dfp)); disko_write(fp, &iti.rv, sizeof(iti.rv)); disko_write(fp, &iti.rp, sizeof(iti.rp)); disko_write(fp, &iti.trkvers, sizeof(iti.trkvers)); disko_write(fp, &iti.nos, sizeof(iti.nos)); disko_write(fp, &iti.reserved1, sizeof(iti.reserved1)); disko_write(fp, &iti.name, sizeof(iti.name)); disko_write(fp, &iti.ifc, sizeof(iti.ifc)); disko_write(fp, &iti.ifr, sizeof(iti.ifr)); disko_write(fp, &iti.mch, sizeof(iti.mch)); disko_write(fp, &iti.mpr, sizeof(iti.mpr)); disko_write(fp, &iti.mbank, sizeof(iti.mbank)); for (int i = 0; i < 120; i++) { disko_write(fp, ¬etrans[i].note, sizeof(notetrans[i].note)); disko_write(fp, ¬etrans[i].smp, sizeof(notetrans[i].smp)); } save_iti_envelopes(fp, ins); /* unused padding */ disko_write(fp, "\0\0\0\0", 4); // ITI files *need* to write 554 bytes due to alignment, but in a song it doesn't matter if (iti_file) { int64_t pos = disko_tell(fp); // ack assert(pos == 554); /* okay, now go through samples */ for (int j = 0; j < iti_nalloc; j++) { int o = iti_invmap[j]; iti_map[o] = pos; pos += 80; /* header is 80 bytes */ save_its_header(fp, song->samples + o); } for (int j = 0; j < iti_nalloc; j++) { unsigned int op, tmp; int o = iti_invmap[j]; song_sample_t *smp = song->samples + o; op = disko_tell(fp); tmp = bswapLE32(op); disko_seek(fp, iti_map[o]+0x48, SEEK_SET); disko_write(fp, &tmp, 4); disko_seek(fp, op, SEEK_SET); csf_write_sample(fp, smp, SF_LE | SF_PCMS | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8) | ((smp->flags & CHN_STEREO) ? SF_SS : SF_M), UINT32_MAX); } } } int fmt_iti_save_instrument(disko_t *fp, song_t *song, song_instrument_t *ins) { save_iti_instrument(fp, song, ins, 1); return SAVE_SUCCESS; } schismtracker-20250313/fmt/its.c000066400000000000000000000225671476471630300163750ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "fmt.h" #include "mem.h" #include "player/sndfile.h" #include "song.h" /* --------------------------------------------------------------------- */ // IT Sample Format struct it_sample { uint32_t id; // 0x53504D49 int8_t filename[12]; uint8_t zero; uint8_t gvl; uint8_t flags; uint8_t vol; int8_t name[26]; uint8_t cvt; uint8_t dfp; uint32_t length; uint32_t loopbegin; uint32_t loopend; uint32_t c5speed; uint32_t susloopbegin; uint32_t susloopend; uint32_t samplepointer; uint8_t vis; uint8_t vid; uint8_t vir; uint8_t vit; }; /* --------------------------------------------------------------------- */ int fmt_its_read_info(dmoz_file_t *file, slurp_t *fp) { struct it_sample its; unsigned char title[25]; if (slurp_read(fp, &its, sizeof(its)) != sizeof(its) || memcmp(&its.id, "IMPS", 4)) return 0; file->smp_length = bswapLE32(its.length); file->smp_flags = 0; if (its.flags & 2) file->smp_flags |= CHN_16BIT; if (its.flags & 16) { file->smp_flags |= CHN_LOOP; if (its.flags & 64) file->smp_flags |= CHN_PINGPONGLOOP; } if (its.flags & 32) { file->smp_flags |= CHN_SUSTAINLOOP; if (its.flags & 128) file->smp_flags |= CHN_PINGPONGSUSTAIN; } if (its.dfp & 128) file->smp_flags |= CHN_PANNING; if (its.flags & 4) file->smp_flags |= CHN_STEREO; file->smp_defvol = its.vol; file->smp_gblvol = its.gvl; file->smp_vibrato_speed = its.vis; file->smp_vibrato_depth = its.vid & 0x7f; file->smp_vibrato_rate = its.vir; file->smp_loop_start = bswapLE32(its.loopbegin); file->smp_loop_end = bswapLE32(its.loopend); file->smp_speed = bswapLE32(its.c5speed); file->smp_sustain_start = bswapLE32(its.susloopbegin); file->smp_sustain_end = bswapLE32(its.susloopend); file->smp_filename = strn_dup((const char *)its.filename, sizeof(its.filename)); file->description = "Impulse Tracker Sample"; file->type = TYPE_SAMPLE_EXTD; slurp_seek(fp, 20, SEEK_SET); if (slurp_read(fp, title, sizeof(title)) != sizeof(title)) return 0; file->title = strn_dup((const char *)title, sizeof(title)); return 1; } // cwtv should be 0x214 when loading from its or iti int load_its_sample(slurp_t *fp, song_sample_t *smp, uint16_t cwtv) { struct it_sample its; #define READ_VALUE(name) do { if (slurp_read(fp, &its.name, sizeof(its.name)) != sizeof(its.name)) { return 0; } } while (0) READ_VALUE(id); READ_VALUE(filename); READ_VALUE(zero); READ_VALUE(gvl); READ_VALUE(flags); READ_VALUE(vol); READ_VALUE(name); READ_VALUE(cvt); READ_VALUE(dfp); READ_VALUE(length); READ_VALUE(loopbegin); READ_VALUE(loopend); READ_VALUE(c5speed); READ_VALUE(susloopbegin); READ_VALUE(susloopend); READ_VALUE(samplepointer); READ_VALUE(vis); READ_VALUE(vid); READ_VALUE(vir); READ_VALUE(vit); #undef READ_VALUE if (its.id != bswapLE32(0x53504D49)) return 0; /* alright, let's get started */ smp->length = bswapLE32(its.length); if ((its.flags & 1) == 0) { // sample associated with header return 0; } smp->global_volume = its.gvl; if (its.flags & 16) { smp->flags |= CHN_LOOP; if (its.flags & 64) smp->flags |= CHN_PINGPONGLOOP; } if (its.flags & 32) { smp->flags |= CHN_SUSTAINLOOP; if (its.flags & 128) smp->flags |= CHN_PINGPONGSUSTAIN; } /* IT sometimes didn't clear the flag after loading a stereo sample. This appears to have * been fixed sometime before IT 2.14, which is fortunate because that's what a lot of other * programs annoyingly identify themselves as. */ if (cwtv < 0x0214) its.flags &= ~4; memcpy(smp->name, (const char *)its.name, MIN(sizeof(smp->name), sizeof(its.name))); smp->name[ARRAY_SIZE(smp->name) - 1] = '\0'; memcpy(smp->filename, (const char *)its.filename, MIN(sizeof(smp->filename), sizeof(its.filename))); smp->filename[ARRAY_SIZE(smp->filename) - 1] = '\0'; smp->volume = its.vol * 4; smp->panning = (its.dfp & 127) * 4; if (its.dfp & 128) smp->flags |= CHN_PANNING; smp->loop_start = bswapLE32(its.loopbegin); smp->loop_end = bswapLE32(its.loopend); smp->c5speed = bswapLE32(its.c5speed); smp->sustain_start = bswapLE32(its.susloopbegin); smp->sustain_end = bswapLE32(its.susloopend); int vibs[] = {VIB_SINE, VIB_RAMP_DOWN, VIB_SQUARE, VIB_RANDOM}; smp->vib_type = vibs[its.vit & 3]; smp->vib_rate = its.vir; smp->vib_depth = its.vid; smp->vib_speed = its.vis; // sanity checks purged, csf_adjust_sample_loop already does them -paper its.samplepointer = bswapLE32(its.samplepointer); const int64_t pos = slurp_tell(fp); if (pos < 0) return 0; int r; if ((its.flags & 1) && its.cvt == 64 && its.length == 12) { // OPL instruments in OpenMPT MPTM files (which are essentially extended IT files) slurp_seek(fp, its.samplepointer, SEEK_SET); r = slurp_read(fp, smp->adlib_bytes, 12); smp->flags |= CHN_ADLIB; // dumb hackaround that ought to some day be fixed: smp->length = 1; smp->data = csf_allocate_sample(1); } else if (its.flags & 1) { slurp_seek(fp, its.samplepointer, SEEK_SET); // endianness (always zero) uint32_t flags = SF_LE; // channels flags |= (its.flags & 4) ? SF_SS : SF_M; if (its.flags & 8) { // compression algorithm flags |= (its.cvt & 4) ? SF_IT215 : SF_IT214; } else { // signedness (or delta?) // XXX for some reason I had a note in pm/fmt/it.c saying that I had found some // .it files with the signed flag set incorrectly and to assume unsigned when // hdr.cwtv < 0x0202. Why, and for what files? // Do any other players use the header for deciding sample data signedness? flags |= (its.cvt & 4) ? SF_PCMD : (its.cvt & 1) ? SF_PCMS : SF_PCMU; } // bit width flags |= (its.flags & 2) ? SF_16 : SF_8; r = csf_read_sample(smp, flags, fp); } else { r = smp->length = 0; } slurp_seek(fp, pos, SEEK_SET); return r; } int fmt_its_load_sample(slurp_t *fp, song_sample_t *smp) { return load_its_sample(fp, smp, 0x0214); } void save_its_header(disko_t *fp, song_sample_t *smp) { struct it_sample its = {0}; its.id = bswapLE32(0x53504D49); // IMPS memcpy(its.filename, smp->filename, MIN(sizeof(smp->filename), sizeof(its.filename))); its.gvl = smp->global_volume; if (smp->data && smp->length) its.flags |= 1; if (smp->flags & CHN_16BIT) its.flags |= 2; if (smp->flags & CHN_STEREO) its.flags |= 4; if (smp->flags & CHN_LOOP) its.flags |= 16; if (smp->flags & CHN_SUSTAINLOOP) its.flags |= 32; if (smp->flags & CHN_PINGPONGLOOP) its.flags |= 64; if (smp->flags & CHN_PINGPONGSUSTAIN) its.flags |= 128; its.vol = smp->volume / 4; memcpy(its.name, smp->name, MIN(sizeof(smp->filename), sizeof(its.filename))); its.name[25] = 0; its.cvt = 1; // signed samples its.dfp = smp->panning / 4; if (smp->flags & CHN_PANNING) its.dfp |= 0x80; its.length = bswapLE32(smp->length); its.loopbegin = bswapLE32(smp->loop_start); its.loopend = bswapLE32(smp->loop_end); its.c5speed = bswapLE32(smp->c5speed); its.susloopbegin = bswapLE32(smp->sustain_start); its.susloopend = bswapLE32(smp->sustain_end); //its.samplepointer = 42; - this will be filled in later its.vis = smp->vib_speed; its.vir = smp->vib_rate; its.vid = smp->vib_depth; switch (smp->vib_type) { case VIB_RANDOM: its.vit = 3; break; case VIB_SQUARE: its.vit = 2; break; case VIB_RAMP_DOWN: its.vit = 1; break; default: case VIB_SINE: its.vit = 0; break; } #define WRITE_VALUE(name) do { disko_write(fp, &its.name, sizeof(its.name)); } while (0) WRITE_VALUE(id); WRITE_VALUE(filename); WRITE_VALUE(zero); WRITE_VALUE(gvl); WRITE_VALUE(flags); WRITE_VALUE(vol); WRITE_VALUE(name); WRITE_VALUE(cvt); WRITE_VALUE(dfp); WRITE_VALUE(length); WRITE_VALUE(loopbegin); WRITE_VALUE(loopend); WRITE_VALUE(c5speed); WRITE_VALUE(susloopbegin); WRITE_VALUE(susloopend); WRITE_VALUE(samplepointer); WRITE_VALUE(vis); WRITE_VALUE(vid); WRITE_VALUE(vir); WRITE_VALUE(vit); #undef WRITE_VALUE } int fmt_its_save_sample(disko_t *fp, song_sample_t *smp) { if (smp->flags & CHN_ADLIB) return SAVE_UNSUPPORTED; save_its_header(fp, smp); csf_write_sample(fp, smp, SF_LE | SF_PCMS | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8) | ((smp->flags & CHN_STEREO) ? SF_SS : SF_M), UINT32_MAX); /* Write the sample pointer. In an ITS file, the sample data is right after the header, * so its position in the file will be the same as the size of the header. */ unsigned int tmp = bswapLE32(sizeof(struct it_sample)); disko_seek(fp, 0x48, SEEK_SET); disko_write(fp, &tmp, 4); return SAVE_SUCCESS; } schismtracker-20250313/fmt/liq.c000066400000000000000000000036251476471630300163550ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "fmt.h" #include "mem.h" /* --------------------------------------------------------------------- */ int fmt_liq_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char magic1[14], artist[20], title[30]; if (slurp_read(fp, magic1, sizeof(magic1)) != sizeof(magic1) || memcmp(magic1, "Liquid Module:", sizeof(magic1))) return 0; slurp_seek(fp, 64, SEEK_SET); int magic2 = slurp_getc(fp); if (magic2 != 0x1a) return 0; slurp_seek(fp, 14, SEEK_SET); if (slurp_read(fp, title, sizeof(title)) != sizeof(title)) return 0; slurp_seek(fp, 44, SEEK_SET); if (slurp_read(fp, artist, sizeof(artist)) != sizeof(artist)) return 0; file->description = "Liquid Tracker"; /*file->extension = str_dup("liq");*/ file->artist = strn_dup((const char *)title, sizeof(title)); file->title = strn_dup((const char *)artist, sizeof(artist)); file->type = TYPE_MODULE_S3M; return 1; } schismtracker-20250313/fmt/mdl.c000066400000000000000000001021541476471630300163410ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "log.h" #include "mem.h" #include "str.h" #include "player/sndfile.h" /* --------------------------------------------------------------------- */ /* MDL is nice, but it's a pain to read the title... */ int fmt_mdl_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char magic[4]; if (slurp_read(fp, magic, sizeof(magic)) != sizeof(magic) || memcmp(magic, "DMDL", sizeof(magic))) return 0; /* major version number (accept 0 or 1) */ int version = slurp_getc(fp); if (version == EOF) return 0; if ((((unsigned char)version & 0xf0) >> 4) > 1) return 0; for (;;) { unsigned char id[2]; uint32_t block_length; if (slurp_read(fp, &id, sizeof(id)) != sizeof(id) || slurp_read(fp, &block_length, sizeof(block_length)) != sizeof(block_length)) return 0; block_length = bswapLE32(block_length); if (!memcmp(id, "IN", 2)) { /* hey! we have a winner */ unsigned char title[32], artist[20]; if (slurp_read(fp, &title, sizeof(title)) != sizeof(title) || slurp_read(fp, &artist, sizeof(artist)) != sizeof(artist)) return 0; file->title = strn_dup((const char *)title, sizeof(title)); file->artist = strn_dup((const char *)artist, sizeof(artist)); file->description = "Digitrakker"; /*file->extension = str_dup("mdl");*/ file->type = TYPE_MODULE_XM; return 1; } else { slurp_seek(fp, SEEK_CUR, block_length); } } return 0; } /* --------------------------------------------------------------------------------------------------------- */ /* Structs and stuff for the loader */ #define MDL_BLOCK(a,b) (((a) << 8) | (b)) #define MDL_BLK_INFO MDL_BLOCK('I','N') #define MDL_BLK_MESSAGE MDL_BLOCK('M','E') #define MDL_BLK_PATTERNS MDL_BLOCK('P','A') #define MDL_BLK_PATTERNNAMES MDL_BLOCK('P','N') #define MDL_BLK_TRACKS MDL_BLOCK('T','R') #define MDL_BLK_INSTRUMENTS MDL_BLOCK('I','I') #define MDL_BLK_VOLENVS MDL_BLOCK('V','E') #define MDL_BLK_PANENVS MDL_BLOCK('P','E') #define MDL_BLK_FREQENVS MDL_BLOCK('F','E') #define MDL_BLK_SAMPLEINFO MDL_BLOCK('I','S') #define MDL_BLK_SAMPLEDATA MDL_BLOCK('S','A') #define MDL_FADE_CUT 0xffff enum { MDLNOTE_NOTE = 1 << 0, MDLNOTE_SAMPLE = 1 << 1, MDLNOTE_VOLUME = 1 << 2, MDLNOTE_EFFECTS = 1 << 3, MDLNOTE_PARAM1 = 1 << 4, MDLNOTE_PARAM2 = 1 << 5, }; struct mdl_infoblock { char title[32]; char composer[20]; uint16_t numorders; uint16_t repeatpos; uint8_t globalvol; uint8_t speed; uint8_t tempo; uint8_t chanpan[32]; }; /* This is actually a part of the instrument (II) block */ struct mdl_samplehdr { uint8_t smpnum; uint8_t lastnote; uint8_t volume; uint8_t volenv_flags; // 6 bits env #, 2 bits flags uint8_t panning; uint8_t panenv_flags; uint16_t fadeout; uint8_t vibspeed; uint8_t vibdepth; uint8_t vibsweep; uint8_t vibtype; uint8_t freqenv_flags; }; struct mdl_sampleinfo { uint8_t smpnum; char name[32]; char filename[8]; uint32_t c4speed; // c4, c5, whatever uint32_t length; uint32_t loopstart; uint32_t looplen; uint8_t volume; // volume in v0.0, unused after uint8_t flags; }; struct mdl_envelope { uint8_t envnum; struct { uint8_t x; // delta-value from last point, 0 means no more points defined uint8_t y; // 0-63 } nodes[15]; uint8_t flags; uint8_t loop; // lower 4 bits = start, upper 4 bits = end }; /* --------------------------------------------------------------------------------------------------------- */ /* Internal definitions */ struct mdlpat { int track; // which track to put here int rows; // 1-256 song_note_t *note; // first note -- add 64 for next note, etc. struct mdlpat *next; }; struct mdlenv { uint32_t flags; song_envelope_t data; }; enum { MDL_HAS_INFO = 1 << 0, MDL_HAS_MESSAGE = 1 << 1, MDL_HAS_PATTERNS = 1 << 2, MDL_HAS_TRACKS = 1 << 3, MDL_HAS_INSTRUMENTS = 1 << 4, MDL_HAS_VOLENVS = 1 << 5, MDL_HAS_PANENVS = 1 << 6, MDL_HAS_FREQENVS = 1 << 7, MDL_HAS_SAMPLEINFO = 1 << 8, MDL_HAS_SAMPLEDATA = 1 << 9, }; static const uint8_t mdl_efftrans[] = { /* 0 */ FX_NONE, /* 1st column only */ /* 1 */ FX_PORTAMENTOUP, /* 2 */ FX_PORTAMENTODOWN, /* 3 */ FX_TONEPORTAMENTO, /* 4 */ FX_VIBRATO, /* 5 */ FX_ARPEGGIO, /* 6 */ FX_NONE, /* Either column */ /* 7 */ FX_TEMPO, /* 8 */ FX_PANNING, /* 9 */ FX_SETENVPOSITION, /* A */ FX_NONE, /* B */ FX_POSITIONJUMP, /* C */ FX_GLOBALVOLUME, /* D */ FX_PATTERNBREAK, /* E */ FX_SPECIAL, /* F */ FX_SPEED, /* 2nd column only */ /* G */ FX_VOLUMESLIDE, // up /* H */ FX_VOLUMESLIDE, // down /* I */ FX_RETRIG, /* J */ FX_TREMOLO, /* K */ FX_TREMOR, /* L */ FX_NONE, }; static const uint8_t autovib_import[] = {VIB_SINE, VIB_RAMP_DOWN, VIB_SQUARE, VIB_SINE}; /* --------------------------------------------------------------------------------------------------------- */ /* a highly overcomplicated mess to import effects */ // receive an MDL effect, give back a 'normal' one. static void translate_fx(uint8_t *pe, uint8_t *pp) { uint8_t e = *pe; uint8_t p = *pp; if (e > 21) e = 0; // (shouldn't ever happen) *pe = mdl_efftrans[e]; switch (e) { case 7: // tempo // MDL supports any nonzero tempo value, but we don't p = MAX(p, 0x20); break; case 8: // panning p = MIN(p << 1, 0xff); break; case 0xd: // pattern break // convert from stupid decimal-hex p = 10 * (p >> 4) + (p & 0xf); break; case 0xe: // special switch (p >> 4) { case 0: // unused case 3: // unused case 5: // set finetune case 8: // set samplestatus (what?) *pe = FX_NONE; break; case 1: // pan slide left *pe = FX_PANNINGSLIDE; p = (MAX(p & 0xf, 0xe) << 4) | 0xf; break; case 2: // pan slide right *pe = FX_PANNINGSLIDE; p = 0xf0 | MAX(p & 0xf, 0xe); break; case 4: // vibrato waveform p = 0x30 | (p & 0xf); break; case 6: // pattern loop p = 0xb0 | (p & 0xf); break; case 7: // tremolo waveform p = 0x40 | (p & 0xf); break; case 9: // retrig *pe = FX_RETRIG; p &= 0xf; break; case 0xa: // global vol slide up *pe = FX_GLOBALVOLSLIDE; p = 0xf0 & (((p & 0xf) + 1) << 3); break; case 0xb: // global vol slide down *pe = FX_GLOBALVOLSLIDE; p = ((p & 0xf) + 1) >> 1; break; case 0xc: // note cut case 0xd: // note delay case 0xe: // pattern delay // nothing to change here break; case 0xf: // offset -- further mangled later. *pe = FX_OFFSET; break; } break; case 0x10: // volslide up if (p < 0xE0) { // Gxy -> Dz0 (z=(xy>>2)) p >>= 2; if (p > 0x0F) p = 0x0F; p <<= 4; } else if (p < 0xF0) { // GEy -> DzF (z=(y>>2)) p = (((p & 0x0F) << 2) | 0x0F); } else { // GFy -> DyF p = ((p << 4) | 0x0F); } break; case 0x11: // volslide down if (p < 0xE0) { // Hxy -> D0z (z=(xy>>2)) p >>= 2; if(p > 0x0F) p = 0x0F; } else if (p < 0xF0) { // HEy -> DFz (z=(y>>2)) p = (((p & 0x0F) >> 2) | 0xF0); } else { // HFy -> DFy // Nothing to do } break; } *pp = p; } // return: 1 if an effect was lost, 0 if not. static int cram_mdl_effects(song_note_t *note, uint8_t vol, uint8_t e1, uint8_t e2, uint8_t p1, uint8_t p2) { int lostfx = 0; int n; uint8_t tmp; // map second effect values 1-6 to effects G-L if (e2 >= 1 && e2 <= 6) e2 += 15; translate_fx(&e1, &p1); translate_fx(&e2, &p2); /* From the Digitrakker documentation: * EFx -xx - Set Sample Offset This is a double-command. It starts the sample at adress xxx*256. Example: C-5 01 -- EF1 -23 ->starts sample 01 at address 12300 (in hex). Kind of screwy, but I guess it's better than the mess required to do it with IT (which effectively requires 3 rows in order to set the offset past 0xff00). If we had access to the entire track, we *might* be able to shove the high offset SAy into surrounding rows, but it wouldn't always be possible, it'd make the loader a lot uglier, and generally would be more trouble than it'd be worth to implement. What's more is, if there's another effect in the second column, it's ALSO processed in addition to the offset, and the second data byte is shared between the two effects. And: an offset effect without a note will retrigger the previous note, but I'm not even going to try to handle that behavior. */ if (e1 == FX_OFFSET) { // EFy -xx => offset yxx00 p1 = (p1 & 0xf) ? 0xff : p2; if (e2 == FX_OFFSET) e2 = FX_NONE; } else if (e2 == FX_OFFSET) { // --- EFy => offset y0000 (best we can do without doing a ton of extra work is 0xff00) p2 = (p2 & 0xf) ? 0xff : 0; } if (vol) { note->voleffect = VOLFX_VOLUME; note->volparam = (vol + 2) >> 2; } /* If we have Dxx + G00, or Dxx + H00, combine them into Lxx/Kxx. (Since pitch effects only "fit" in the first effect column, and volume effects only work in the second column, we don't have to check every combination here.) */ if (e2 == FX_VOLUMESLIDE && p1 == 0) { if (e1 == FX_TONEPORTAMENTO) { e1 = FX_NONE; e2 = FX_TONEPORTAVOL; } else if (e1 == FX_VIBRATO) { e1 = FX_NONE; e2 = FX_VIBRATOVOL; } } /* Try to fit the "best" effect into e2. */ if (e1 == FX_NONE) { // easy } else if (e2 == FX_NONE) { // almost as easy e2 = e1; p2 = p1; e1 = FX_NONE; } else if (e1 == e2 && e1 != FX_SPECIAL) { /* Digitrakker processes the effects left-to-right, so if both effects are the same, the second essentially overrides the first. */ e1 = FX_NONE; } else if (!vol) { // The volume column is free, so try to shove one of them into there. // See also xm.c. // (Just because I'm using the same sort of code twice doesn't make it any less of a hack) for (n = 0; n < 4; n++) { if (convert_voleffect(&e1, &p1, n >> 1)) { note->voleffect = e1; note->volparam = p1; e1 = FX_NONE; break; } else { // swap them tmp = e2; e2 = e1; e1 = tmp; tmp = p2; p2 = p1; p1 = tmp; } } } // If we still have two effects, pick the 'best' one if (e1 != FX_NONE && e2 != FX_NONE) { lostfx++; if (effect_weight[e1] < effect_weight[e2]) { e2 = e1; p2 = p1; } } note->effect = e2; note->param = p2; return lostfx; } /* --------------------------------------------------------------------------------------------------------- */ /* block reading */ // return: repeat position. static int mdl_read_info(song_t *song, slurp_t *fp) { struct mdl_infoblock info; int n, songlen; uint8_t b; #define READ_VALUE(name) \ do { if (slurp_read(fp, &info.name, sizeof(info.name)) != sizeof(info.name)) { return 0; } } while (0) READ_VALUE(title); READ_VALUE(composer); READ_VALUE(numorders); READ_VALUE(repeatpos); READ_VALUE(globalvol); READ_VALUE(speed); READ_VALUE(tempo); READ_VALUE(chanpan); #undef READ_VALUE info.numorders = bswapLE16(info.numorders); info.repeatpos = bswapLE16(info.repeatpos); // title is space-padded info.title[31] = '\0'; str_trim(info.title); strncpy(song->title, info.title, 25); song->title[25] = '\0'; song->initial_global_volume = (info.globalvol + 1) >> 1; song->initial_speed = info.speed ? info.speed : 1; song->initial_tempo = MAX(info.tempo, 31); // MDL tempo range is actually 4-255 // channel pannings for (n = 0; n < 32; n++) { song->channels[n].panning = (info.chanpan[n] & 127) << 1; // ugh if (info.chanpan[n] & 128) song->channels[n].flags |= CHN_MUTE; } for (; n < 64; n++) { song->channels[n].panning = 128; song->channels[n].flags |= CHN_MUTE; } songlen = MIN(info.numorders, MAX_ORDERS - 1); for (n = 0; n < songlen; n++) { b = slurp_getc(fp); song->orderlist[n] = (b < MAX_PATTERNS) ? b : ORDER_SKIP; } return info.repeatpos; } static void mdl_read_message(song_t *song, slurp_t *fp, uint32_t blklen) { char *ptr = song->message; blklen = MIN(blklen, MAX_MESSAGE); slurp_read(fp, ptr, blklen); ptr[blklen] = '\0'; while ((ptr = strchr(ptr, '\r')) != NULL) *ptr = '\n'; } static struct mdlpat *mdl_read_patterns(song_t *song, slurp_t *fp) { struct mdlpat pat_head = { .next = NULL }; // only exists for .next struct mdlpat *patptr = &pat_head; song_note_t *note; int npat, nchn, rows, pat, chn; uint16_t trknum; npat = slurp_getc(fp); npat = MIN(npat, MAX_PATTERNS); for (pat = 0; pat < npat; pat++) { nchn = slurp_getc(fp); rows = slurp_getc(fp) + 1; slurp_seek(fp, 16, SEEK_CUR); // skip the name note = song->patterns[pat] = csf_allocate_pattern(rows); song->pattern_size[pat] = song->pattern_alloc_size[pat] = rows; for (chn = 0; chn < nchn; chn++, note++) { slurp_read(fp, &trknum, 2); trknum = bswapLE16(trknum); if (!trknum) continue; patptr->next = mem_alloc(sizeof(struct mdlpat)); patptr = patptr->next; patptr->track = trknum; patptr->rows = rows; patptr->note = note; patptr->next = NULL; } } return pat_head.next; } // mostly the same as above static struct mdlpat *mdl_read_patterns_v0(song_t *song, slurp_t *fp) { struct mdlpat pat_head = { .next = NULL }; struct mdlpat *patptr = &pat_head; song_note_t *note; int npat, pat, chn; uint16_t trknum; npat = slurp_getc(fp); npat = MIN(npat, MAX_PATTERNS); for (pat = 0; pat < npat; pat++) { note = song->patterns[pat] = csf_allocate_pattern(64); song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; for (chn = 0; chn < 32; chn++, note++) { slurp_read(fp, &trknum, 2); trknum = bswapLE16(trknum); if (!trknum) continue; patptr->next = mem_alloc(sizeof(struct mdlpat)); patptr = patptr->next; patptr->track = trknum; patptr->rows = 64; patptr->note = note; patptr->next = NULL; } } return pat_head.next; } struct receive_userdata { song_note_t *track; int *lostfx; }; static int mdl_receive_track(const void *data, size_t len, void *userdata) { struct receive_userdata *rec_userdata = userdata; int row = 0; uint8_t b, x, y; uint8_t vol, e1, e2, p1, p2; song_note_t *track = rec_userdata->track; int *lostfx = rec_userdata->lostfx; slurp_t fake_fp = {0}; slurp_memstream(&fake_fp, (uint8_t *)data, len); while (row < 256 && !slurp_eof(&fake_fp)) { b = slurp_getc(&fake_fp); x = b >> 2; y = b & 3; switch (y) { case 0: // (x+1) empty notes follow row += x + 1; break; case 1: // Repeat previous note (x+1) times if (row > 0) { do { track[row] = track[row - 1]; } while (++row < 256 && x--); } break; case 2: // Copy note from row x if (row > x) track[row] = track[x]; row++; break; case 3: // New note data if (x & MDLNOTE_NOTE) { b = slurp_getc(&fake_fp); // convenient! :) // (I don't know what DT does for out of range notes, might be worth // checking some time) track[row].note = (b > 120) ? NOTE_OFF : b; } if (x & MDLNOTE_SAMPLE) { b = slurp_getc(&fake_fp); if (b >= MAX_INSTRUMENTS) b = 0; track[row].instrument = b; } vol = (x & MDLNOTE_VOLUME) ? slurp_getc(&fake_fp) : 0; if (x & MDLNOTE_EFFECTS) { b = slurp_getc(&fake_fp); e1 = b & 0xf; e2 = b >> 4; } else { e1 = e2 = 0; } p1 = (x & MDLNOTE_PARAM1) ? slurp_getc(&fake_fp) : 0; p2 = (x & MDLNOTE_PARAM2) ? slurp_getc(&fake_fp) : 0; *lostfx += cram_mdl_effects(&track[row], vol, e1, e2, p1, p2); row++; break; } } return slurp_tell(&fake_fp); } static int mdl_read_tracks(slurp_t *fp, song_note_t *tracks[65536]) { /* why are we allocating so many of these ? */ uint16_t ntrks, trk; int lostfx = 0; slurp_read(fp, &ntrks, 2); ntrks = bswapLE16(ntrks); // track 0 is always blank for (trk = 1; trk <= ntrks; trk++) { int64_t startpos = slurp_tell(fp); if (startpos < 0) return 0; /* what ? */ uint16_t bytesleft; slurp_read(fp, &bytesleft, sizeof(bytesleft)); bytesleft = bswapLE16(bytesleft); tracks[trk] = mem_calloc(256, sizeof(song_note_t)); struct receive_userdata data = {0}; data.track = tracks[trk]; data.lostfx = &lostfx; int c = slurp_receive(fp, mdl_receive_track, bytesleft, &data); slurp_seek(fp, startpos + c + 2, SEEK_SET); } if (lostfx) log_appendf(4, " Warning: %d effect%s dropped", lostfx, lostfx == 1 ? "" : "s"); return 1; } /* This is actually somewhat horrible. Digitrakker's envelopes are actually properties of the *sample*, not the instrument -- that is, the only thing an instrument is actually doing is providing a keyboard split and grouping a bunch of samples together. This is handled here by importing the instrument names and note/sample mapping into a "master" instrument, but NOT writing the envelope data there -- instead, that stuff is placed into whatever instrument matches up with the sample number. Then, when building the tracks into patterns, we'll actually *remap* all the numbers and rewrite each instrument's sample map as a 1:1 mapping with the sample. In the end, the song will play back correctly (well, at least hopefully it will ;) though the instrument names won't always line up. */ static void mdl_read_instruments(song_t *song, slurp_t *fp) { struct mdl_samplehdr shdr; // Etaoin shrdlu song_instrument_t *ins; // 'master' instrument song_instrument_t *sins; // other instruments created to track each sample's individual envelopes song_sample_t *smp; int nins, nsmp; int insnum; int firstnote, note; nins = slurp_getc(fp); while (nins--) { insnum = slurp_getc(fp); firstnote = 0; nsmp = slurp_getc(fp); // if it's out of range, or if the same instrument was already loaded (weird), don't read it if (insnum == 0 || insnum > MAX_INSTRUMENTS) { // skip it (32 bytes name, plus 14 bytes per sample) slurp_seek(fp, 32 + 14 * nsmp, SEEK_SET); continue; } // ok, make an instrument if (!song->instruments[insnum]) song->instruments[insnum] = csf_allocate_instrument(); ins = song->instruments[insnum]; slurp_read(fp, ins->name, 25); slurp_seek(fp, 7, SEEK_CUR); // throw away the rest ins->name[25] = '\0'; while (nsmp--) { // read a sample #define READ_VALUE(name) slurp_read(fp, &shdr.name, sizeof(shdr.name)) READ_VALUE(smpnum); READ_VALUE(lastnote); READ_VALUE(volume); READ_VALUE(volenv_flags); READ_VALUE(panning); READ_VALUE(panenv_flags); READ_VALUE(fadeout); READ_VALUE(vibspeed); READ_VALUE(vibdepth); READ_VALUE(vibsweep); READ_VALUE(vibtype); slurp_seek(fp, 1, SEEK_CUR); // reserved, zero READ_VALUE(freqenv_flags); #undef READ_VALUE shdr.fadeout = bswapLE16(shdr.fadeout); if (shdr.smpnum == 0 || shdr.smpnum > MAX_SAMPLES) continue; if (!song->instruments[shdr.smpnum]) song->instruments[shdr.smpnum] = csf_allocate_instrument(); sins = song->instruments[shdr.smpnum]; smp = song->samples + shdr.smpnum; // Write this sample's instrument mapping // (note: test "jazz 2 jazz.mdl", it uses a multisampled piano) shdr.lastnote = MIN(shdr.lastnote, 119); for (note = firstnote; note <= shdr.lastnote; note++) ins->sample_map[note] = shdr.smpnum; firstnote = shdr.lastnote + 1; // get ready for the next sample // temporarily hijack the envelope "nodes" field to write the envelope number sins->vol_env.nodes = shdr.volenv_flags & 63; sins->pan_env.nodes = shdr.panenv_flags & 63; sins->pitch_env.nodes = shdr.freqenv_flags & 63; if (shdr.volenv_flags & 128) sins->flags |= ENV_VOLUME; if (shdr.panenv_flags & 128) sins->flags |= ENV_PANNING; if (shdr.freqenv_flags & 128) sins->flags |= ENV_PITCH; // DT fadeout = 0000-1fff, or 0xffff for "cut" // assuming DT uses 'cut' behavior for anything past 0x1fff, too lazy to bother // hex-editing a file at the moment to find out :P sins->fadeout = (shdr.fadeout < 0x2000) ? (shdr.fadeout + 1) >> 1 // this seems about right : MDL_FADE_CUT; // temporary // for the volume envelope / flags: // "bit 6 -> flags, if volume is used" // ... huh? what happens if the volume isn't used? smp->volume = shdr.volume; //mphack (range 0-255, s/b 0-64) smp->panning = ((MIN(shdr.panning, 127) + 1) >> 1) * 4; //mphack if (shdr.panenv_flags & 64) smp->flags |= CHN_PANNING; smp->vib_speed = shdr.vibspeed; // XXX bother checking ranges for vibrato smp->vib_depth = shdr.vibdepth; smp->vib_rate = shdr.vibsweep; smp->vib_type = autovib_import[shdr.vibtype & 3]; } } } static void mdl_read_sampleinfo(song_t *song, slurp_t *fp, uint8_t *packtype) { struct mdl_sampleinfo sinfo; song_sample_t *smp; int nsmp; nsmp = slurp_getc(fp); while (nsmp--) { #define READ_VALUE(name) slurp_read(fp, &sinfo.name, sizeof(sinfo.name)) READ_VALUE(smpnum); READ_VALUE(name); READ_VALUE(filename); READ_VALUE(c4speed); READ_VALUE(length); READ_VALUE(loopstart); READ_VALUE(looplen); READ_VALUE(volume); READ_VALUE(flags); #undef READ_VALUE if (sinfo.smpnum == 0 || sinfo.smpnum > MAX_SAMPLES) { continue; } smp = song->samples + sinfo.smpnum; strncpy(smp->name, sinfo.name, 25); smp->name[25] = '\0'; strncpy(smp->filename, sinfo.filename, 8); smp->filename[8] = '\0'; // MDL has ten octaves like IT, but they're not the *same* ten octaves -- dropping // perfectly good note data is stupid so I'm adjusting the sample tunings instead smp->c5speed = bswapLE32(sinfo.c4speed) * 2; smp->length = bswapLE32(sinfo.length); smp->loop_start = bswapLE32(sinfo.loopstart); smp->loop_end = bswapLE32(sinfo.looplen); if (smp->loop_end) { smp->loop_end += smp->loop_start; smp->flags |= CHN_LOOP; } if (sinfo.flags & 1) { smp->flags |= CHN_16BIT; smp->length >>= 1; smp->loop_start >>= 1; smp->loop_end >>= 1; } if (sinfo.flags & 2) smp->flags |= CHN_PINGPONGLOOP; packtype[sinfo.smpnum] = ((sinfo.flags >> 2) & 3); smp->global_volume = 64; } } // (ughh) static void mdl_read_sampleinfo_v0(song_t *song, slurp_t *fp, uint8_t *packtype) { struct mdl_sampleinfo sinfo; song_sample_t *smp; int nsmp; nsmp = slurp_getc(fp); while (nsmp--) { #define READ_VALUE(name) slurp_read(fp, &sinfo.name, sizeof(sinfo.name)) READ_VALUE(smpnum); READ_VALUE(name); READ_VALUE(filename); READ_VALUE(c4speed); READ_VALUE(length); READ_VALUE(loopstart); READ_VALUE(looplen); READ_VALUE(volume); READ_VALUE(flags); #undef READ_VALUE if (sinfo.smpnum == 0 || sinfo.smpnum > MAX_SAMPLES) { continue; } smp = song->samples + sinfo.smpnum; strncpy(smp->name, sinfo.name, 25); smp->name[25] = '\0'; strncpy(smp->filename, sinfo.filename, 8); smp->filename[8] = '\0'; smp->c5speed = bswapLE16(sinfo.c4speed) * 2; smp->length = bswapLE32(sinfo.length); smp->loop_start = bswapLE32(sinfo.loopstart); smp->loop_end = bswapLE32(sinfo.looplen); smp->volume = sinfo.volume; //mphack (range 0-255, I think?) if (smp->loop_end) { smp->loop_end += smp->loop_start; smp->flags |= CHN_LOOP; } if (sinfo.flags & 1) { smp->flags |= CHN_16BIT; smp->length >>= 1; smp->loop_start >>= 1; smp->loop_end >>= 1; } if (sinfo.flags & 2) smp->flags |= CHN_PINGPONGLOOP; packtype[sinfo.smpnum] = ((sinfo.flags >> 2) & 3); smp->global_volume = 64; } } static void mdl_read_envelopes(slurp_t *fp, struct mdlenv **envs, uint32_t flags) { struct mdl_envelope ehdr; song_envelope_t *env; uint8_t nenv; int n, tick; nenv = slurp_getc(fp); while (nenv--) { #define READ_VALUE(name) slurp_read(fp, &ehdr.name, sizeof(ehdr.name)) READ_VALUE(envnum); for (size_t i = 0; i < ARRAY_SIZE(ehdr.nodes); i++) { READ_VALUE(nodes[i].x); READ_VALUE(nodes[i].y); } READ_VALUE(flags); READ_VALUE(loop); #undef READ_VALUE if (ehdr.envnum > 63) continue; if (!envs[ehdr.envnum]) envs[ehdr.envnum] = mem_calloc(1, sizeof(struct mdlenv)); env = &envs[ehdr.envnum]->data; env->nodes = 15; tick = -ehdr.nodes[0].x; // adjust so it starts at zero for (n = 0; n < 15; n++) { if (!ehdr.nodes[n].x) { env->nodes = MAX(n, 2); break; } tick += ehdr.nodes[n].x; env->ticks[n] = tick; env->values[n] = MIN(ehdr.nodes[n].y, 64); // actually 0-63 } env->loop_start = ehdr.loop & 0xf; env->loop_end = ehdr.loop >> 4; env->sustain_start = env->sustain_end = ehdr.flags & 0xf; envs[ehdr.envnum]->flags = 0; if (ehdr.flags & 16) envs[ehdr.envnum]->flags |= (flags & (ENV_VOLSUSTAIN | ENV_PANSUSTAIN | ENV_PITCHSUSTAIN)); if (ehdr.flags & 32) envs[ehdr.envnum]->flags |= (flags & (ENV_VOLLOOP | ENV_PANLOOP | ENV_PITCHLOOP)); } } /* --------------------------------------------------------------------------------------------------------- */ static void copy_envelope(song_instrument_t *ins, song_envelope_t *ienv, struct mdlenv **envs, uint32_t enable) { // nodes temporarily indicates which envelope to load struct mdlenv *env = envs[ienv->nodes]; if (env) { ins->flags |= env->flags; memcpy(ienv, &env->data, sizeof(song_envelope_t)); } else { ins->flags &= ~enable; ienv->nodes = 2; } } int fmt_mdl_load_song(song_t *song, slurp_t *fp, SCHISM_UNUSED unsigned int lflags) { struct mdlpat *pat, *patptr = NULL; struct mdlenv *volenvs[64] = {0}, *panenvs[64] = {0}, *freqenvs[64] = {0}; uint8_t packtype[MAX_SAMPLES] = {0}; song_note_t *tracks[65536] = {0}; long datapos = 0; // where to seek for the sample data int restartpos = -1; int trk, n; uint32_t readflags = 0; uint8_t tag[4]; uint8_t fmtver; // file format version, e.g. 0x11 = v1.1 slurp_read(fp, tag, 4); if (memcmp(tag, "DMDL", 4) != 0) return LOAD_UNSUPPORTED; fmtver = slurp_getc(fp); // Read the next block while (!slurp_eof(fp)) { uint32_t blklen; // length of this block size_t nextpos; // ... and start of next one slurp_read(fp, tag, 2); slurp_read(fp, &blklen, 4); blklen = bswapLE32(blklen); nextpos = slurp_tell(fp) + blklen; switch (MDL_BLOCK(tag[0], tag[1])) { case MDL_BLK_INFO: if (!(readflags & MDL_HAS_INFO)) { readflags |= MDL_HAS_INFO; restartpos = mdl_read_info(song, fp); } break; case MDL_BLK_MESSAGE: if (!(readflags & MDL_HAS_MESSAGE)) { readflags |= MDL_HAS_MESSAGE; mdl_read_message(song, fp, blklen); } break; case MDL_BLK_PATTERNS: if (!(readflags & MDL_HAS_PATTERNS)) { readflags |= MDL_HAS_PATTERNS; patptr = ((fmtver >> 4) ? mdl_read_patterns : mdl_read_patterns_v0)(song, fp); } break; case MDL_BLK_TRACKS: if (!(readflags & MDL_HAS_TRACKS)) { readflags |= MDL_HAS_TRACKS; mdl_read_tracks(fp, tracks); } break; case MDL_BLK_INSTRUMENTS: if (!(readflags & MDL_HAS_INSTRUMENTS)) { readflags |= MDL_HAS_INSTRUMENTS; mdl_read_instruments(song, fp); } break; case MDL_BLK_VOLENVS: if (!(readflags & MDL_HAS_VOLENVS)) { readflags |= MDL_HAS_VOLENVS; mdl_read_envelopes(fp, volenvs, ENV_VOLLOOP | ENV_VOLSUSTAIN); } break; case MDL_BLK_PANENVS: if (!(readflags & MDL_HAS_PANENVS)) { readflags |= MDL_HAS_PANENVS; mdl_read_envelopes(fp, panenvs, ENV_PANLOOP | ENV_PANSUSTAIN); } break; case MDL_BLK_FREQENVS: if (!(readflags & MDL_HAS_FREQENVS)) { readflags |= MDL_HAS_FREQENVS; mdl_read_envelopes(fp, freqenvs, ENV_PITCHLOOP | ENV_PITCHSUSTAIN); } break; case MDL_BLK_SAMPLEINFO: if (!(readflags & MDL_HAS_SAMPLEINFO)) { readflags |= MDL_HAS_SAMPLEINFO; ((fmtver >> 4) ? mdl_read_sampleinfo : mdl_read_sampleinfo_v0) (song, fp, packtype); } break; case MDL_BLK_SAMPLEDATA: // Can't do anything until we have the sample info block loaded, since the sample // lengths and packing information is stored there. // Best we can do at the moment is to remember where this block was so we can jump // back to it later. if (!(readflags & MDL_HAS_SAMPLEDATA)) { readflags |= MDL_HAS_SAMPLEDATA; datapos = slurp_tell(fp); } break; case MDL_BLK_PATTERNNAMES: // don't care break; default: //log_appendf(4, " Warning: Unknown block of type '%c%c' (0x%04X) at %ld", // tag[0], tag[1], MDL_BLOCK(tag[0], tag[1]), slurp_tell(fp)); break; } if (slurp_seek(fp, nextpos, SEEK_SET) != 0) { log_appendf(4, " Warning: Failed to seek (file truncated?)"); break; } } if (!(readflags & MDL_HAS_INSTRUMENTS)) { // Probably a v0 file, fake an instrument for (n = 1; n < MAX_SAMPLES; n++) { if (song->samples[n].length) { song->instruments[n] = csf_allocate_instrument(); strcpy(song->instruments[n]->name, song->samples[n].name); } } } if (readflags & MDL_HAS_SAMPLEINFO) { // Sample headers loaded! // if the sample data was encountered, load it now // otherwise, clear out the sample lengths so Bad Things don't happen later if (datapos) { slurp_seek(fp, datapos, SEEK_SET); for (n = 1; n < MAX_SAMPLES; n++) { if (!packtype[n] && !song->samples[n].length) continue; uint32_t flags; if (packtype[n] > 2) { log_appendf(4, " Warning: Sample %d: unknown packing type %d", n, packtype[n]); packtype[n] = 0; // ? } else if (packtype[n] == ((song->samples[n].flags & CHN_16BIT) ? 1 : 2)) { log_appendf(4, " Warning: Sample %d: bit width / pack type mismatch", n); } flags = SF_LE | SF_M; flags |= packtype[n] ? SF_MDL : SF_PCMS; flags |= (song->samples[n].flags & CHN_16BIT) ? SF_16 : SF_8; csf_read_sample(song->samples + n, flags, fp); } } else { for (n = 1; n < MAX_SAMPLES; n++) song->samples[n].length = 0; } } if (readflags & MDL_HAS_TRACKS) { song_note_t *patnote, *trknote; // first off, fix all the instrument numbers to compensate // for the screwy envelope craziness if (fmtver >> 4) { for (trk = 1; trk < 65536 && tracks[trk]; trk++) { uint8_t cnote = NOTE_FIRST; // current/last used data for (n = 0, trknote = tracks[trk]; n < 256; n++, trknote++) { if (NOTE_IS_NOTE(trknote->note)) { cnote = trknote->note; } if (trknote->instrument) { // translate it trknote->instrument = song->instruments[trknote->instrument] ? (song->instruments[trknote->instrument] ->sample_map[cnote - 1]) : 0; } } } } // "paste" the tracks into the channels for (pat = patptr; pat; pat = pat->next) { trknote = tracks[pat->track]; if (!trknote) continue; patnote = pat->note; for (n = 0; n < pat->rows; n++, trknote++, patnote += 64) { *patnote = *trknote; } } // and clean up for (trk = 1; trk < 65536 && tracks[trk]; trk++) free(tracks[trk]); } while (patptr) { pat = patptr; patptr = patptr->next; free(pat); } // Finish fixing up the instruments for (n = 1; n < MAX_INSTRUMENTS; n++) { song_instrument_t *ins = song->instruments[n]; if (ins) { copy_envelope(ins, &ins->vol_env, volenvs, ENV_VOLUME); copy_envelope(ins, &ins->pan_env, panenvs, ENV_PANNING); copy_envelope(ins, &ins->pitch_env, freqenvs, ENV_PITCH); if (ins->flags & ENV_VOLUME) { // fix note-fade if (!(ins->flags & ENV_VOLLOOP)) ins->vol_env.loop_start = ins->vol_env.loop_end = ins->vol_env.nodes - 1; if (!(ins->flags & ENV_VOLSUSTAIN)) ins->vol_env.sustain_start = ins->vol_env.sustain_end = ins->vol_env.nodes - 1; ins->flags |= ENV_VOLLOOP | ENV_VOLSUSTAIN; } if (ins->fadeout == MDL_FADE_CUT) { // fix note-off if (!(ins->flags & ENV_VOLUME)) { ins->vol_env.ticks[0] = 0; ins->vol_env.values[0] = 64; ins->vol_env.sustain_start = ins->vol_env.sustain_end = 0; ins->flags |= ENV_VOLUME | ENV_VOLSUSTAIN; // (the rest is set below) } int se = ins->vol_env.sustain_end; ins->vol_env.nodes = se + 2; ins->vol_env.ticks[se + 1] = ins->vol_env.ticks[se] + 1; ins->vol_env.values[se + 1] = 0; ins->fadeout = 0; } // set a 1:1 map for each instrument with a corresponding sample, // and a blank map for each one that doesn't. int note, smp = song->samples[n].data ? n : 0; for (note = 0; note < 120; note++) { ins->sample_map[note] = smp; ins->note_map[note] = note + 1; } } } if (readflags & MDL_HAS_VOLENVS) { for (n = 0; n < 64; n++) free(volenvs[n]); } if (readflags & MDL_HAS_PANENVS) { for (n = 0; n < 64; n++) free(panenvs[n]); } if (readflags & MDL_HAS_FREQENVS) { for (n = 0; n < 64; n++) free(freqenvs[n]); } if (restartpos > 0) csf_insert_restart_pos(song, restartpos); song->flags |= SONG_ITOLDEFFECTS | SONG_COMPATGXX | SONG_INSTRUMENTMODE | SONG_LINEARSLIDES; sprintf(song->tracker_id, "Digitrakker %s", (fmtver == 0x11) ? "3" // really could be 2.99b -- but close enough for me : (fmtver == 0x10) ? "2.3" : (fmtver == 0x00) ? "2.0 - 2.2b" // there was no 1.x release : "v?.?"); return LOAD_SUCCESS; } schismtracker-20250313/fmt/med.c000066400000000000000000000027361476471630300163370ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "fmt.h" #include "mem.h" /* --------------------------------------------------------------------- */ int fmt_med_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char magic[4]; if (slurp_read(fp, magic, sizeof(magic)) != sizeof(magic) || memcmp(magic, "MMD0", 4)) return 0; file->description = "OctaMed"; file->title = str_dup(""); // TODO actually read the title file->type = TYPE_MODULE_MOD; // err, more like XM for Amiga return 1; } schismtracker-20250313/fmt/mf.c000066400000000000000000000034341476471630300161700ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "fmt.h" #include "mem.h" /* --------------------------------------------------------------------- */ int fmt_mf_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char moonfish[8]; if (slurp_read(fp, moonfish, sizeof(moonfish)) != sizeof(moonfish) || memcmp(moonfish, "MOONFISH", sizeof(moonfish))) return 0; unsigned char title[32]; slurp_seek(fp, 25, SEEK_CUR); int title_length = slurp_getc(fp); if (title_length == EOF) return 0; title_length = MIN(sizeof(title), title_length); if (slurp_read(fp, title, title_length) != (size_t)title_length) return 0; file->description = "MoonFish"; /*file->extension = str_dup("mf");*/ file->title = strn_dup((const char *)title, title_length); file->type = TYPE_MODULE_MOD; /* ??? */ return 1; } schismtracker-20250313/fmt/mid.c000066400000000000000000000400121476471630300163300ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "log.h" #include "mem.h" #include "player/sndfile.h" /* some thoughts... really, we don't even need to adhere to the same channel numbering -- the notes could go pretty much anywhere, as long as note-off events are handled properly. but it'd be nice to have the channels at least sort of resemble the midi file, whether it's arranged by track or by midi channel. - try to allocate channels that aren't in use when possible, to avoid stomping on playing notes - set instrument NNA to continue with dupe cut (or note off?) */ /* --------------------------------------------------------------------------------------------------------- */ // structs, local defines, etc. #define MID_ROWS_PER_PATTERN 200 // Pulse/row calculations are done in fixed point for better accuracy #define FRACBITS 12 #define FRACMASK ((1 << FRACBITS) - 1) struct mthd { char tag[4]; // MThd uint32_t header_length; uint16_t format; // 0 = single-track, 1 = multi-track, 2 = multi-song uint16_t num_tracks; // number of track chunks uint16_t division; // delta timing value: positive = units/beat; negative = smpte compatible units (?) }; struct mtrk { char tag[4]; // MTrk uint32_t length; // number of bytes of track data following }; static int read_mid_mthd(struct mthd *hdr, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) { return 0; } } while (0) READ_VALUE(tag); if (memcmp(hdr->tag, "RIFF", 4) == 0) { // Stupid MS crap. slurp_seek(fp, 16, SEEK_CUR); READ_VALUE(tag); } READ_VALUE(header_length); READ_VALUE(format); READ_VALUE(num_tracks); READ_VALUE(division); #undef READ_VALUE if (memcmp(hdr->tag, "MThd", 4)) return 0; hdr->header_length = bswapBE32(hdr->header_length); // don't care about format, either there's one track or more than one track. whoop de doo. // (format 2 MIDs will probably be hilariously broken, but I don't have any and also don't care) hdr->format = bswapBE16(hdr->format); hdr->num_tracks = bswapBE16(hdr->num_tracks); hdr->division = bswapBE16(hdr->division); slurp_seek(fp, hdr->header_length - 6, SEEK_CUR); // account for potential weirdness return 1; } static int read_mid_mtrk(struct mtrk *mtrk, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &mtrk->name, sizeof(mtrk->name)) != sizeof(mtrk->name)) { return 0; } } while (0) READ_VALUE(tag); READ_VALUE(length); #undef READ_VALUE if (memcmp(mtrk->tag, "MTrk", 4)) { log_appendf(4, " Warning: Invalid track header (corrupt file?)"); return 0; } mtrk->length = bswapBE32(mtrk->length); return 1; } struct event { unsigned int pulse; // the PPQN-tick, counting from zero, when this midi-event happens uint8_t chan; // target channel (0-based!) song_note_t note; // the note data (new data will overwrite old data in same channel+row) struct event *next; }; static struct event *alloc_event(unsigned int pulse, uint8_t chan, const song_note_t *note, struct event *next) { struct event *ev = malloc(sizeof(struct event)); if (!ev) { perror("malloc"); return NULL; } ev->pulse = pulse; ev->chan = chan; ev->note = *note; ev->next = next; return ev; } /* --------------------------------------------------------------------------------------------------------- */ // support functions static unsigned int read_varlen(slurp_t *fp) { int b; unsigned int v = 0; // This will fail tremendously if a value overflows. I don't care. do { b = slurp_getc(fp); if (b == EOF) return 0; // truncated?! v <<= 7; v |= b & 0x7f; } while (b & 0x80); return v; } /* --------------------------------------------------------------------------------------------------------- */ // info (this is ultra lame) int fmt_mid_read_info(dmoz_file_t *file, slurp_t *fp) { song_t *tmpsong = csf_allocate(); if (!tmpsong) return 0; // wahhhh if (fmt_mid_load_song(tmpsong, fp, LOAD_NOSAMPLES | LOAD_NOPATTERNS) == LOAD_SUCCESS) { file->description = "Standard MIDI File"; file->title = str_dup(tmpsong->title); file->type = TYPE_MODULE_MOD; csf_free(tmpsong); return 1; } csf_free(tmpsong); return 0; } /* --------------------------------------------------------------------------------------------------------- */ // load int fmt_mid_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { struct mthd mthd; struct mtrk mtrk; unsigned char buf[32]; song_note_t note; struct event *event_queue, *cur, *prev, *new; struct { uint8_t fg_note; uint8_t bg_note; // really just used as a boolean... uint8_t instrument; } midich[16] = {{NOTE_NONE, NOTE_NONE, 0}}; char *message_cur = song->message; unsigned int message_left = MAX_MESSAGE; unsigned int pulse = 0; // cumulative time from start of track uint8_t patch_samples[128] = {0}; uint8_t nsmp = 1; // Next free sample if (!read_mid_mthd(&mthd, fp)) return LOAD_UNSUPPORTED; song->title[0] = '\0'; // should be already, but to be sure... /* We'll count by "pulses" here, which are basically MIDI-speak for ticks, except that there are a heck of a lot more of them. (480 pulses/quarter is fairly common, that's like A78, if the tempo could be adjusted high enough to make practical use of that speed) Also, we'll use a 32-bit value and hopefully not overflow -- which is unlikely anyway, as it'd either require PPQN to be very ridiculously high, or a file that's several *hours* long. Stuff a useless event at the start of the event queue. */ memset(¬e, 0, sizeof(note)); note.note = NOTE_NONE; event_queue = alloc_event(0, 0, ¬e, NULL); for (int trknum = 0; trknum < mthd.num_tracks; trknum++) { unsigned int delta; // time since last event (read from file) unsigned int vlen; // some other generic varlen number unsigned char rs = 0; // running status byte unsigned char status; // THIS status byte (as opposed to rs) unsigned char hi, lo, cn, x, y; unsigned int bpm; // stupid int found_end = 0; long nextpos; cur = event_queue->next; prev = event_queue; pulse = 0; if (!read_mid_mtrk(&mtrk, fp)) break; nextpos = slurp_tell(fp) + mtrk.length; // where this track is supposed to end while (!found_end && slurp_tell(fp) < nextpos) { delta = read_varlen(fp); // delta-time pulse += delta; // 'real' pulse count // get status byte, if there is one if (slurp_peek(fp, &status, sizeof(status)) == sizeof(status) && status & 0x80) { slurp_seek(fp, 1, SEEK_CUR); } else if (rs & 0x80) { status = rs; } else { // garbage? continue; } memset(¬e, 0, sizeof(note)); note.note = NOTE_NONE; hi = status >> 4; lo = status & 0xf; cn = lo; //or: trknum * CHANNELS_PER_TRACK + lo % CHANNELS_PER_TRACK; switch (hi) { case 0x8: // note off - x, y rs = status; x = slurp_getc(fp); // note y = slurp_getc(fp); // release velocity x = CLAMP(x + NOTE_FIRST, NOTE_FIRST, NOTE_LAST); // clamp is wrong, but whatever // if the last note in the channel is the same as this note, just write === // otherwise, if there is a note playing, assume our note got backgrounded // and write S71 (past note off) if (midich[cn].fg_note == x) { memset(¬e, 0, sizeof(note)); note.note = NOTE_OFF; midich[cn].fg_note = NOTE_NONE; } else { // S71, past note off memset(¬e, 0, sizeof(note)); note.effect = FX_SPECIAL; note.param = 0x71; midich[cn].bg_note = NOTE_NONE; } break; case 0x9: // note on - x, y (velocity zero = note off) rs = status; x = slurp_getc(fp); // note y = slurp_getc(fp); // attack velocity x = CLAMP(x + NOTE_FIRST, NOTE_FIRST, NOTE_LAST); // see note off above. if (lo == 9) { // ignore percussion for now } else if (y == 0) { // this is actually another note-off, see above // (maybe that stuff should be split into a function or blahblah) if (midich[cn].fg_note == x) { memset(¬e, 0, sizeof(note)); note.note = NOTE_OFF; midich[cn].fg_note = NOTE_NONE; } else { // S71, past note off memset(¬e, 0, sizeof(note)); note.effect = FX_SPECIAL; note.param = 0x71; midich[cn].bg_note = NOTE_NONE; } } else { if (nsmp == 1 && !(lflags & LOAD_NOSAMPLES)) { // no samples defined yet - fake a program change patch_samples[0] = 1; adlib_patch_apply(song->samples + 1, 0); nsmp++; } memset(¬e, 0, sizeof(note)); note.note = x; note.instrument = patch_samples[midich[cn].instrument]; note.voleffect = VOLFX_VOLUME; note.volparam = (y & 0x7f) * 64 / 127; midich[cn].fg_note = x; midich[cn].bg_note = midich[cn].fg_note; } break; case 0xa: // polyphonic key pressure (aftertouch) - x, y rs = status; x = slurp_getc(fp); y = slurp_getc(fp); // TODO polyphonic aftertouch channel=lo note=x pressure=y continue; case 0xb: // controller OR channel mode - x, y rs = status; // controller if first data byte 0-119 // channel mode if first data byte 120-127 x = slurp_getc(fp); y = slurp_getc(fp); // TODO controller change channel=lo controller=x value=y continue; case 0xc: // program change - x (instrument/voice selection) rs = status; x = slurp_getc(fp); midich[cn].instrument = x; // look familiar? this was copied from the .mus loader if (!patch_samples[x] && !(lflags & LOAD_NOSAMPLES)) { if (nsmp < MAX_SAMPLES) { // New sample! patch_samples[x] = nsmp; adlib_patch_apply(song->samples + nsmp, x); nsmp++; } else { log_appendf(4, " Warning: Too many samples"); } } memset(¬e, 0, sizeof(note)); note.instrument = patch_samples[x]; break; case 0xd: // channel pressure (aftertouch) - x rs = status; x = slurp_getc(fp); // TODO channel aftertouch channel=lo pressure=x continue; case 0xe: // pitch bend - x, y rs = status; x = slurp_getc(fp); y = slurp_getc(fp); // TODO pitch bend channel=lo lsb=x msb=y continue; case 0xf: // system messages switch (lo) { case 0xf: // meta-event (text and stuff) x = slurp_getc(fp); // type vlen = read_varlen(fp); // value length switch (x) { case 0x1: // text case 0x2: // copyright case 0x3: // track name case 0x4: // instrument name case 0x5: // lyric case 0x6: // marker case 0x7: // cue point y = MIN(vlen, message_left ? message_left - 1 : 0); slurp_read(fp, message_cur, y); if (x == 3 && y && !song->title[0]) { strncpy(song->title, message_cur, MIN(y, 25)); song->title[25] = '\0'; } message_cur += y; message_left -= y; if (y && message_cur[-1] != '\n') { *message_cur++ = '\n'; message_left--; } vlen -= y; break; case 0x20: // MIDI channel (FF 20 len* cc) // specifies which midi-channel sysexes are assigned to case 0x21: // MIDI port (FF 21 len* pp) // specifies which port/bus this track's events are routed to break; case 0x2f: found_end = 1; break; case 0x51: // set tempo // read another stupid kind of variable length number // hopefully this fits into 4 bytes - if not, too bad! // (what is this? friggin' magic?) memset(buf, 0, 4); y = MIN(vlen, 4); slurp_read(fp, buf + (4 - y), y); bpm = buf[0] << 24 | (buf[1] << 16) | (buf[2] << 8) | buf[3]; bpm = CLAMP(60000000 / (bpm ? bpm : 1), 0x20, 0xff); memset(¬e, 0, sizeof(note)); note.effect = FX_TEMPO; note.param = bpm; vlen -= y; break; case 0x54: // SMPTE offset (what time in the song this track starts) // (what?!) break; case 0x58: // time signature (FF 58 len* nn dd cc bb) case 0x59: // key signature (FF 59 len* sf mi) // TODO care? don't care? break; case 0x7f: // some proprietary crap break; default: // some mystery crap log_appendf(2, " Unknown meta-event FF %02X", x); break; } slurp_seek(fp, vlen, SEEK_CUR); break; /* sysex */ case 0x0: /* syscommon */ case 0x1: case 0x2: case 0x3: case 0x4: case 0x5: case 0x6: case 0x7: rs = 0; // clear running status /* sysrt */ case 0x8: case 0x9: case 0xa: case 0xb: case 0xc: case 0xd: case 0xe: // 0xf0 - sysex // 0xf1-0xf7 - common // 0xf8-0xff - sysrt // sysex and common cancel running status // TODO handle these, or at least skip them coherently continue; } } // skip past any events with a lower pulse count (from other channels) while (cur && pulse > cur->pulse) { prev = cur; cur = cur->next; } // and now, cur is either NULL or has a higher timestamp, so insert before it new = alloc_event(pulse, cn, ¬e, cur); prev->next = new; prev = prev->next; } if (slurp_tell(fp) != nextpos) { log_appendf(2, " Track %d ended %ld bytes from boundary", trknum, slurp_tell(fp) - nextpos); slurp_seek(fp, nextpos, SEEK_SET); } } song->initial_speed = 3; song->initial_tempo = 120; prev = NULL; cur = event_queue; if (lflags & LOAD_NOPATTERNS) { while (cur) { prev = cur; cur = cur->next; free(prev); } return LOAD_SUCCESS; } // okey doke! now let's write this crap out to the patterns song_note_t *pattern = NULL, *rowdata; int row = MID_ROWS_PER_PATTERN; // what row of the pattern rowdata is pointing to (fixed point) int rowfrac = 0; // how much is left over int pat = 0; // next pattern number to create pulse = 0; // PREVIOUS event pulse. while (cur) { /* calculate pulse delta from previous event * calculate row count from the pulse count using ppqn (assuming 1 row = 1/32nd note? 1/64?) * advance the row as required it'd be nice to aim for the "middle" of ticks instead of the start of them, that way if an event is just barely off, it won't end up shifted way ahead. */ unsigned int delta = cur->pulse - pulse; if (delta) { // Increment position row <<= FRACBITS; row += 8 * (delta << FRACBITS) / mthd.division; // times 8 -> 32nd notes row += rowfrac; rowfrac = row & FRACMASK; row >>= FRACBITS; } pulse = cur->pulse; while (row >= MID_ROWS_PER_PATTERN) { // New pattern time! if(pat >= MAX_PATTERNS) { log_appendf(4, " Warning: Too many patterns, song is truncated"); return LOAD_SUCCESS; } pattern = song->patterns[pat] = csf_allocate_pattern(MID_ROWS_PER_PATTERN); song->pattern_size[pat] = song->pattern_alloc_size[pat] = MID_ROWS_PER_PATTERN; song->orderlist[pat] = pat; pat++; row -= MID_ROWS_PER_PATTERN; } rowdata = pattern + 64 * row; if (cur->note.note) { rowdata[cur->chan].note = cur->note.note; rowdata[cur->chan].instrument = cur->note.instrument; } if (cur->note.voleffect) { rowdata[cur->chan].voleffect = cur->note.voleffect; rowdata[cur->chan].volparam = cur->note.volparam; } if (cur->note.effect) { rowdata[cur->chan].effect = cur->note.effect; rowdata[cur->chan].param = cur->note.param; } prev = cur; cur = cur->next; free(prev); } return LOAD_SUCCESS; } schismtracker-20250313/fmt/mmcmp.c000066400000000000000000000217011476471630300166740ustar00rootroot00000000000000/* * This program is free software; you can redistribute it and modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the license or (at your * option) any later version. * * Author: Olivier Lapicque * Modified by Claudio Matsuoka for xmp * Modified again by Storlek to de-xmp-ify it. */ #include "headers.h" #include "bswap.h" #include "fmt.h" typedef struct mm_header { char zirconia[8]; // "ziRCONia" uint16_t hdrsize; uint16_t version; uint16_t blocks; uint32_t filesize; uint32_t blktable; uint8_t glb_comp; uint8_t fmt_comp; } mm_header_t; typedef struct mm_block { uint32_t unpk_size; uint32_t pk_size; uint32_t xor_chk; uint16_t sub_blk; uint16_t flags; uint16_t tt_entries; uint16_t num_bits; } mm_block_t; typedef struct mm_subblock { uint32_t unpk_pos; uint32_t unpk_size; } mm_subblock_t; static int read_mmcmp_header(mm_header_t *hdr, slurp_t *fp) { const size_t memsize = slurp_length(fp); #define READ_VALUE(name) \ do { if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) { return 0; } } while (0) READ_VALUE(zirconia); READ_VALUE(hdrsize); READ_VALUE(version); READ_VALUE(blocks); READ_VALUE(filesize); READ_VALUE(blktable); READ_VALUE(glb_comp); READ_VALUE(fmt_comp); #undef READ_VALUE hdr->hdrsize = bswapLE16(hdr->hdrsize); hdr->version = bswapLE16(hdr->version); hdr->blocks = bswapLE16(hdr->blocks); hdr->filesize = bswapLE32(hdr->filesize); hdr->blktable = bswapLE32(hdr->blktable); if (memcmp(hdr->zirconia, "ziRCONia", 8) || hdr->hdrsize < 14 || hdr->blocks == 0 || hdr->filesize < 16 || hdr->filesize > 0x8000000 || hdr->blktable >= memsize || hdr->blktable + 4 * hdr->blocks > memsize) return 0; return 1; } static int read_mmcmp_block(mm_block_t *pblk, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &pblk->name, sizeof(pblk->name)) != sizeof(pblk->name)) { return 0; } } while (0) READ_VALUE(unpk_size); READ_VALUE(pk_size); READ_VALUE(xor_chk); READ_VALUE(sub_blk); READ_VALUE(flags); READ_VALUE(tt_entries); READ_VALUE(num_bits); #undef READ_VALUE pblk->unpk_size = bswapLE32(pblk->unpk_size); pblk->pk_size = bswapLE32(pblk->pk_size); pblk->xor_chk = bswapLE32(pblk->xor_chk); pblk->sub_blk = bswapLE16(pblk->sub_blk); pblk->flags = bswapLE16(pblk->flags); pblk->tt_entries = bswapLE16(pblk->tt_entries); pblk->num_bits = bswapLE16(pblk->num_bits); return 1; } static int read_mmcmp_subblocks(size_t size, mm_subblock_t *subblks, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &name, sizeof(name)) != sizeof(name)) { return 0; } } while (0) for (size_t i = 0; i < size; i++) { READ_VALUE(subblks[i].unpk_pos); subblks[i].unpk_pos = bswapLE32(subblks[i].unpk_pos); READ_VALUE(subblks[i].unpk_size); subblks[i].unpk_size = bswapLE32(subblks[i].unpk_size); } #undef READ_VALUE return 1; } // only used internally typedef struct mm_bit_buffer { uint32_t bits, buffer; uint8_t *src, *end; } mm_bit_buffer_t; enum { MM_COMP = 0x0001, MM_DELTA = 0x0002, MM_16BIT = 0x0004, MM_STEREO = 0x0100, // unused? MM_ABS16 = 0x0200, MM_ENDIAN = 0x0400, // unused? }; static const uint32_t mm_8bit_commands[8] = { 0x01, 0x03, 0x07, 0x0F, 0x1E, 0x3C, 0x78, 0xF8 }; static const uint32_t mm_8bit_fetch[8] = { 3, 3, 3, 3, 2, 1, 0, 0 }; static const uint32_t mm_16bit_commands[16] = { 0x0001, 0x0003, 0x0007, 0x000F, 0x001E, 0x003C, 0x0078, 0x00F0, 0x01F0, 0x03F0, 0x07F0, 0x0FF0, 0x1FF0, 0x3FF0, 0x7FF0, 0xFFF0, }; static const uint32_t mm_16bit_fetch[16] = { 4, 4, 4, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static uint32_t get_bits(mm_bit_buffer_t *bb, uint32_t bits) { uint32_t d; if (!bits) return 0; while (bb->bits < 24) { bb->buffer |= ((bb->src < bb->end) ? *bb->src++ : 0) << bb->bits; bb->bits += 8; } d = bb->buffer & ((1 << bits) - 1); bb->buffer >>= bits; bb->bits -= bits; return d; } int mmcmp_unpack(slurp_t *fp, uint8_t **data, size_t *length) { if (slurp_length(fp) < 256) return 0; mm_header_t hdr; if (!read_mmcmp_header(&hdr, fp)) return 0; size_t filesize = hdr.filesize; uint8_t *buffer = calloc(1, (filesize + 31) & ~15); if (!buffer) return 0; SCHISM_VLA_ALLOC(uint32_t, pblk_table, hdr.blocks); slurp_seek(fp, hdr.blktable, SEEK_SET); if (slurp_read(fp, pblk_table, SCHISM_VLA_SIZEOF(pblk_table)) != SCHISM_VLA_SIZEOF(pblk_table)) { SCHISM_VLA_FREE(pblk_table); free(buffer); return 0; } for (uint32_t block = 0; block < hdr.blocks; block++) { uint32_t pos = bswapLE32(pblk_table[block]); slurp_seek(fp, pos, SEEK_SET); mm_block_t pblk; if (!read_mmcmp_block(&pblk, fp)) { SCHISM_VLA_FREE(pblk_table); free(buffer); return 0; } SCHISM_VLA_ALLOC(mm_subblock_t, psubblk, pblk.sub_blk); if (!read_mmcmp_subblocks(pblk.sub_blk, psubblk, fp)) { SCHISM_VLA_FREE(pblk_table); SCHISM_VLA_FREE(psubblk); free(buffer); return 0; } if (!(pblk.flags & MM_COMP)) { /* Data is not packed */ for (uint32_t i = 0; i < pblk.sub_blk; i++) { if ((psubblk[i].unpk_pos > filesize) || (psubblk[i].unpk_pos + psubblk[i].unpk_size > filesize)) break; if (slurp_read(fp, buffer + psubblk[i].unpk_pos, psubblk[i].unpk_size) != psubblk[i].unpk_size) { SCHISM_VLA_FREE(pblk_table); SCHISM_VLA_FREE(psubblk); free(buffer); return 0; } } } else if (pblk.flags & MM_16BIT) { /* Data is 16-bit packed */ uint16_t *dest = (uint16_t *)(buffer + psubblk->unpk_pos); uint32_t size = psubblk->unpk_size >> 1; uint32_t destpos = 0; uint32_t numbits = pblk.num_bits; uint32_t subblk = 0, oldval = 0; SCHISM_VLA_ALLOC(unsigned char, buf, pblk.pk_size - pblk.tt_entries); slurp_seek(fp, pblk.tt_entries, SEEK_CUR); if (slurp_read(fp, buf, SCHISM_VLA_SIZEOF(buf)) != SCHISM_VLA_SIZEOF(buf)) { SCHISM_VLA_FREE(pblk_table); SCHISM_VLA_FREE(psubblk); SCHISM_VLA_FREE(buf); free(buffer); return 0; } mm_bit_buffer_t bb = {0}; bb.bits = 0; bb.buffer = 0; bb.src = buf; bb.end = buf + pblk.pk_size; while (subblk < pblk.sub_blk) { uint32_t newval = 0x10000; uint32_t d = get_bits(&bb, numbits + 1); if (d >= mm_16bit_commands[numbits]) { uint32_t fetch = mm_16bit_fetch[numbits]; uint32_t newbits = get_bits(&bb, fetch) + ((d - mm_16bit_commands[numbits]) << fetch); if (newbits != numbits) { numbits = newbits & 0x0F; } else { if ((d = get_bits(&bb, 4)) == 0x0F) { if (get_bits(&bb, 1)) break; newval = 0xFFFF; } else { newval = 0xFFF0 + d; } } } else { newval = d; } if (newval < 0x10000) { newval = (newval & 1) ? (uint32_t) (-(int32_t)((newval + 1) >> 1)) : (uint32_t) (newval >> 1); if (pblk.flags & MM_DELTA) { newval += oldval; oldval = newval; } else if (!(pblk.flags & MM_ABS16)) { newval ^= 0x8000; } dest[destpos++] = bswapLE16((uint16_t) newval); } if (destpos >= size) { subblk++; destpos = 0; size = psubblk[subblk].unpk_size >> 1; dest = (uint16_t *)(buffer + psubblk[subblk].unpk_pos); } } SCHISM_VLA_FREE(buf); } else { /* Data is 8-bit packed */ uint8_t *dest = buffer + psubblk->unpk_pos; uint32_t size = psubblk->unpk_size; uint32_t destpos = 0; uint32_t numbits = pblk.num_bits; uint32_t subblk = 0, oldval = 0; uint8_t ptable[0x100]; slurp_peek(fp, ptable, sizeof(ptable)); SCHISM_VLA_ALLOC(unsigned char, buf, pblk.pk_size - pblk.tt_entries); slurp_seek(fp, pblk.tt_entries, SEEK_CUR); if (slurp_read(fp, buf, SCHISM_VLA_SIZEOF(buf)) != SCHISM_VLA_SIZEOF(buf)) { SCHISM_VLA_FREE(pblk_table); SCHISM_VLA_FREE(psubblk); SCHISM_VLA_FREE(buf); free(buffer); return 0; } mm_bit_buffer_t bb = {0}; bb.bits = 0; bb.buffer = 0; bb.src = buf; bb.end = buf + pblk.pk_size; while (subblk < pblk.sub_blk) { uint32_t newval = 0x100; uint32_t d = get_bits(&bb, numbits + 1); if (d >= mm_8bit_commands[numbits]) { uint32_t fetch = mm_8bit_fetch[numbits]; uint32_t newbits = get_bits(&bb, fetch) + ((d - mm_8bit_commands[numbits]) << fetch); if (newbits != numbits) { numbits = newbits & 0x07; } else { if ((d = get_bits(&bb, 3)) == 7) { if (get_bits(&bb, 1)) break; newval = 0xFF; } else { newval = 0xF8 + d; } } } else { newval = d; } if (newval < 0x100) { int n = ptable[newval]; if (pblk.flags & MM_DELTA) { n += oldval; oldval = n; } dest[destpos++] = (uint8_t) n; } if (destpos >= size) { subblk++; destpos = 0; size = psubblk[subblk].unpk_size; dest = buffer + psubblk[subblk].unpk_pos; } } SCHISM_VLA_FREE(buf); } SCHISM_VLA_FREE(psubblk); } SCHISM_VLA_FREE(pblk_table); *data = buffer; *length = filesize; return 1; } schismtracker-20250313/fmt/mod.c000066400000000000000000000560471476471630300163550ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "mem.h" #include "player/sndfile.h" #include "version.h" #include "disko.h" #include "log.h" /* --------------------------------------------------------------------- */ /* TODO: WOW files */ /* Ugh. */ static const char *valid_tags[][2] = { /* M.K. must be the first tag! (to test for WOW files) */ /* the first 5 descriptions are a bit weird */ {"M.K.", "Amiga-NewTracker"}, {"M!K!", "Amiga-ProTracker"}, {"M&K!", "Amiga-NoiseTracker"}, {"N.T.", "Amiga-NoiseTracker"}, {"FEST", "Amiga-NoiseTracker"}, /* jobbig.mod */ /* Atari Octalyzer */ #define FALCON(x) {"CD" #x "1", #x " Channel Falcon"} FALCON(6), FALCON(8), #undef FALCON /* Startrekker (quite rare...) */ #define STRTRK(x) {"FLT" #x, #x " Channel Startrekker"}, {"EXO" #x, #x " Channel Startrekker"} STRTRK(4), STRTRK(8), #undef STRTRK /* Oktalyzer */ {"OCTA", "8 Channel MOD"}, {"OKTA", "8 Channel MOD"}, #define TDZ(x) {"TDZ" #x, #x " Channel MOD"} TDZ(1), TDZ(2), TDZ(3), #undef TDZ /* xCHN = generic */ #define CHN(x) {#x "CHN", #x " Channel MOD"} CHN(1), CHN(2), CHN(3), CHN(4), CHN(5), CHN(6), CHN(7), CHN(8), CHN(9), #undef CHN /* xxCN/xxCH = generic */ #define CN(x) {#x "CN", #x " Channel MOD"}, {#x "CH", #x " Channel MOD"} CN(10), CN(11), CN(12), CN(13), CN(14), CN(15), CN(16), CN(17), CN(18), CN(19), CN(20), CN(21), CN(22), CN(23), CN(24), CN(25), CN(26), CN(27), CN(28), CN(29), CN(30), CN(31), CN(32), #undef CN {NULL, NULL} }; enum { WARN_LINEARSLIDES, WARN_SAMPLEVOL, WARN_LOOPS, WARN_SAMPLEVIB, WARN_INSTRUMENTS, WARN_PATTERNLEN, WARN_NOTERANGE, WARN_VOLEFFECTS, WARN_MAXSAMPLES, WARN_LONGSAMPLES, WARN_UNUSEDPATS, MAX_WARN }; static const char *mod_warnings[] = { [WARN_LINEARSLIDES] = "Linear slides", [WARN_SAMPLEVOL] = "Sample volumes", [WARN_LOOPS] = "Sustain and Ping Pong loops", [WARN_SAMPLEVIB] = "Sample vibrato", [WARN_INSTRUMENTS] = "Instrument functions", [WARN_PATTERNLEN] = "Pattern lengths other than 64 rows", [WARN_NOTERANGE] = "Notes outside the range C-4 to B-6", [WARN_VOLEFFECTS] = "Extended volume column effects", [WARN_MAXSAMPLES] = "Over 31 samples", [WARN_LONGSAMPLES] = "Odd sample length or greater than 131070", [WARN_UNUSEDPATS] = "Patterns outside order list", [MAX_WARN] = NULL }; const uint16_t amigaperiod_table[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960, 906, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int fmt_mod_read_info(dmoz_file_t *file, slurp_t *fp) { char tag[4], title[20]; int i = 0; if (slurp_length(fp) < 1085) return 0; if (slurp_read(fp, title, sizeof(title)) != sizeof(title)) return 0; slurp_seek(fp, 1080, SEEK_SET); if (slurp_read(fp, tag, sizeof(tag)) != sizeof(tag)) return 0; for (i = 0; valid_tags[i][0] != NULL; i++) { if (memcmp(tag, valid_tags[i][0], 4) == 0) { /* if (i == 0) { Might be a .wow; need to calculate some crap to find out for sure. For now, since I have no wow's, I'm not going to care. } */ file->description = valid_tags[i][1]; /*file->extension = str_dup("mod");*/ file->title = strn_dup(title, sizeof(title)); file->type = TYPE_MODULE_MOD; return 1; } } /* check if it could be a SoundTracker MOD */ slurp_rewind(fp); int errors = 0; for (i = 0; i < 20; i++) { int b = slurp_getc(fp); if (b > 0 && b < 32) { errors++; if (errors > 5) return 0; } } uint8_t all_volumes = 0, all_lengths = 0; for (i = 0; i < 15; i++) { slurp_seek(fp, 20 + i * 30 + 22, SEEK_SET); int length_high = slurp_getc(fp); int length_low = slurp_getc(fp); int length = length_high * 0x100 + length_low; int finetune = slurp_getc(fp); int volume = slurp_getc(fp); if (finetune) return 0; /* invalid finetune */ if (volume > 64) return 0; /* invalid volume */ if (length > 32768) return 0; /* invalid sample length */ all_volumes |= volume; all_lengths |= length_high | length_low; } if (!all_lengths || !all_volumes) return 0; file->description = "SoundTracker"; /*file->extension = str_dup("mod");*/ file->title = strn_dup(title, sizeof(title)); file->type = TYPE_MODULE_MOD; return 1; } /* --------------------------------------------------------------------------------------------------------- */ /* force determines whether the loader will force-read untagged files as 15-sample mods */ static int fmt_mod_load_song(song_t *song, slurp_t *fp, unsigned int lflags, int force) { uint8_t tag[4]; int n, npat, pat, chan, nchan, nord; song_note_t *note; uint16_t tmp; int startrekker = 0; int test_wow = 0; int mk = 0; int maybe_st3 = 0; int maybe_ft2 = 0; int his_masters_noise = 0; uint8_t restart; long samplesize = 0; const char *tid = NULL; int nsamples = 31; /* default; tagless mods have 15 */ /* check the tag (and set the number of channels) -- this is ugly, so don't look */ slurp_seek(fp, 1080, SEEK_SET); slurp_read(fp, tag, 4); if (!memcmp(tag, "M.K.", 4)) { /* M.K. = Protracker etc., or Mod's Grave (*.wow) */ nchan = 4; test_wow = 1; mk = 1; maybe_ft2 = 1; tid = "Amiga-NewTracker"; } else if (!memcmp(tag, "M!K!", 4)) { nchan = 4; tid = "Amiga-ProTracker"; } else if (!memcmp(tag, "M&K!", 4) || !memcmp(tag, "N.T.", 4) || !memcmp(tag, "FEST", 4)) { nchan = 4; tid = "Amiga-NoiseTracker"; if (!memcmp(tag, "M&K!", 4) || !memcmp(tag, "FEST", 4)) { // Alternative finetuning his_masters_noise = 1; } } else if ((!memcmp(tag, "FLT", 3) || !memcmp(tag, "EXO", 3)) && (tag[3] == '4' || tag[3] == '8')) { // Hopefully EXO8 is stored the same way as FLT8 nchan = tag[3] - '0'; startrekker = (nchan == 8); tid = "%d Channel Startrekker"; //log_appendf(4, " Warning: Startrekker AM synth is not supported"); } else if (!memcmp(tag, "OCTA", 4) || !memcmp(tag, "OKTA", 4)) { nchan = 8; tid = "Amiga Oktalyzer"; // IT just identifies this as "8 Channel MOD" } else if (!memcmp(tag, "CD61", 4) || !memcmp(tag, "CD81", 4)) { nchan = 8; tid = "8 Channel Falcon"; // Atari Oktalyser } else if (tag[0] > '0' && tag[0] <= '9' && !memcmp(tag + 1, "CHN", 3)) { /* nCHN = Fast Tracker (if n is even) or TakeTracker (if n = 5, 7, or 9) */ nchan = tag[0] - '0'; if (nchan == 5 || nchan == 7 || nchan == 9) { tid = "%d Channel TakeTracker"; } else { if (!(nchan & 1)) maybe_ft2 = 1; tid = "%d Channel MOD"; // generic } maybe_st3 = 1; } else if (tag[0] > '0' && tag[0] <= '9' && tag[1] >= '0' && tag[1] <= '9' && tag[2] == 'C' && (tag[3] == 'H' || tag[3] == 'N')) { /* nnCH = Fast Tracker (if n is even and <= 32) or TakeTracker (if n = 11, 13, 15) * Not sure what the nnCN variant is. */ nchan = 10 * (tag[0] - '0') + (tag[1] - '0'); if (nchan == 11 || nchan == 13 || nchan == 15) { tid = "%d Channel TakeTracker"; } else { if ((nchan & 1) == 0 && nchan <= 32 && tag[3] == 'H') maybe_ft2 = 1; tid = "%d Channel MOD"; // generic } if (tag[3] == 'H') maybe_st3 = 1; } else if (!memcmp(tag, "TDZ", 3) && tag[3] > '0' && tag[3] <= '9') { /* TDZ[1-3] = TakeTracker */ nchan = tag[3] - '0'; if (nchan < 4) tid = "%d Channel TakeTracker"; else tid = "%d Channel MOD"; } else if (force) { /* some old modules don't have tags, so try loading anyway */ nchan = 4; nsamples = 15; tid = "%d Channel MOD"; } else { return LOAD_UNSUPPORTED; } /* suppose the tag is 90CH :) */ if (nchan > MAX_CHANNELS) { //fprintf(stderr, "%s: Too many channels!\n", filename); return LOAD_FORMAT_ERROR; } /* read the title */ slurp_rewind(fp); slurp_read(fp, song->title, 20); song->title[20] = 0; /* sample headers */ for (n = 1; n < nsamples + 1; n++) { if (slurp_read(fp, song->samples[n].name, 22) != 22) return LOAD_UNSUPPORTED; song->samples[n].name[22] = 0; if (slurp_read(fp, &tmp, 2) != 2) return LOAD_UNSUPPORTED; song->samples[n].length = bswapBE16(tmp) * 2; /* this is only necessary for the wow test... */ samplesize += song->samples[n].length; if (his_masters_noise) { song->samples[n].c5speed = transpose_to_frequency(0, -(signed char)(slurp_getc(fp) << 3)); } else { song->samples[n].c5speed = MOD_FINETUNE(slurp_getc(fp)); } song->samples[n].volume = slurp_getc(fp); if (song->samples[n].volume > 64) song->samples[n].volume = 64; if (!song->samples[n].length && song->samples[n].volume) maybe_ft2 = 0; song->samples[n].volume *= 4; //mphack song->samples[n].global_volume = 64; if (slurp_read(fp, &tmp, 2) != 2) return LOAD_UNSUPPORTED; song->samples[n].loop_start = bswapBE16(tmp) * 2; if (slurp_read(fp, &tmp, 2) != 2) return LOAD_UNSUPPORTED; tmp = bswapBE16(tmp) * 2; if (tmp > 2) song->samples[n].flags |= CHN_LOOP; else if (tmp == 0) maybe_st3 = 0; else if (!song->samples[n].length) maybe_ft2 = 0; song->samples[n].loop_end = song->samples[n].loop_start + tmp; song->samples[n].vib_type = 0; song->samples[n].vib_rate = 0; song->samples[n].vib_depth = 0; song->samples[n].vib_speed = 0; } /* pattern/order stuff */ nord = slurp_getc(fp); restart = slurp_getc(fp); if (slurp_read(fp, song->orderlist, 128) != 128) return LOAD_UNSUPPORTED; npat = 0; if (startrekker) { /* from mikmod: if the file says FLT8, but the orderlist has odd numbers, it's probably really an FLT4 */ for (n = 0; n < 128; n++) { if (song->orderlist[n] & 1) { startrekker = 0; nchan = 4; break; } } } if (startrekker) { for (n = 0; n < 128; n++) song->orderlist[n] >>= 1; } for (n = 0; n < 128; n++) { if (song->orderlist[n] >= MAX_PATTERNS) song->orderlist[n] = ORDER_SKIP; else if (song->orderlist[n] > npat) npat = song->orderlist[n]; } /* set all the extra orders to the end-of-song marker */ memset(song->orderlist + nord, ORDER_LAST, MAX_ORDERS - nord); if (restart == 0x7f && maybe_st3) tid = "Scream Tracker 3?"; else if (restart == 0x7f && mk) tid = "%d Channel ProTracker"; else if (restart <= npat && maybe_ft2) tid = "%d Channel FastTracker"; else if (restart == npat && mk) tid = "%d Channel Soundtracker"; /* hey, is this a wow file? */ if (test_wow) { slurp_seek(fp, 0, SEEK_END); if (slurp_tell(fp) == 2048 * npat + samplesize + 3132) { nchan = 8; tid = "Mod's Grave WOW"; } } sprintf(song->tracker_id, tid ? tid : "%d Channel MOD", nchan); /* 15-sample mods don't have a 4-byte tag... or the other 16 samples */ slurp_seek(fp, nsamples == 15 ? 600 : 1084, SEEK_SET); /* pattern data */ if (startrekker) { for (pat = 0; pat <= npat; pat++) { note = song->patterns[pat] = csf_allocate_pattern(64); song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; for (n = 0; n < 64; n++, note += 60) { for (chan = 0; chan < 4; chan++, note++) { uint8_t p[4]; if (slurp_read(fp, p, 4) != 4) return LOAD_UNSUPPORTED; mod_import_note(p, note); csf_import_mod_effect(note, 0); } } note = song->patterns[pat] + 4; for (n = 0; n < 64; n++, note += 60) { for (chan = 0; chan < 4; chan++, note++) { uint8_t p[4]; if (slurp_read(fp, p, 4) != 4) return LOAD_UNSUPPORTED; mod_import_note(p, note); csf_import_mod_effect(note, 0); } } } } else { for (pat = 0; pat <= npat; pat++) { note = song->patterns[pat] = csf_allocate_pattern(64); song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; for (n = 0; n < 64; n++, note += 64 - nchan) { for (chan = 0; chan < nchan; chan++, note++) { uint8_t p[4]; if (slurp_read(fp, p, 4) != 4) return LOAD_UNSUPPORTED; mod_import_note(p, note); csf_import_mod_effect(note, 0); } } } } if (restart < npat) csf_insert_restart_pos(song, restart); /* sample data */ if (!(lflags & LOAD_NOSAMPLES)) { for (n = 1; n < nsamples + 1; n++) { if (song->samples[n].length == 0) continue; /* check for ADPCM compression */ uint32_t flags = SF_8 | SF_M | SF_LE; unsigned char sstart[5]; slurp_peek(fp, sstart, sizeof(sstart)); if (!memcmp(sstart, "ADPCM", sizeof(sstart))) { slurp_seek(fp, sizeof(sstart), SEEK_CUR); flags |= SF_PCMD16; } else { flags |= SF_PCMS; } csf_read_sample(song->samples + n, flags, fp); } } /* set some other header info that's always the same for .mod files */ song->flags = (SONG_ITOLDEFFECTS | SONG_COMPATGXX); for (n = 0; n < nchan; n++) song->channels[n].panning = PROTRACKER_PANNING(n); for (; n < MAX_CHANNELS; n++) song->channels[n].flags = CHN_MUTE; song->pan_separation = 64; /* done! */ return LOAD_SUCCESS; } /* loads everything but old 15-instrument mods... yes, even FLT8 and WOW files (and the definition of "everything" is always changing) */ int fmt_mod31_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { return fmt_mod_load_song(song, fp, lflags, 0); } /* loads everything including old 15-instrument mods. this is a separate function so that it can be called later in the format-checking sequence. */ int fmt_mod15_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { return fmt_mod_load_song(song, fp, lflags, 1); } /* incomplete 31 sample M.K. amiga mod saving routine */ int fmt_mod_save_song(disko_t *fp, song_t *song) { uint8_t mod_songtitle[20] = {0}; uint8_t mod_orders[128] = {0}; uint8_t tmp[128]; int nord, nsmp, nchn, maxpat, jmax, joutpos; long tmppos; int i, j, n, period; unsigned int warn = 0; song_note_t *m; if (song->flags & SONG_INSTRUMENTMODE) warn |= 1 << WARN_INSTRUMENTS; if (song->flags & SONG_LINEARSLIDES) warn |= 1 << WARN_LINEARSLIDES; nsmp = csf_get_num_samples(song); // Getting number of samples if (nsmp > 31) { nsmp = 31; warn |= 1 << WARN_MAXSAMPLES; } nchn = csf_get_highest_used_channel(song)+1; memcpy(mod_songtitle, song->title, 20); disko_write(fp, mod_songtitle, 20); // writing song title // Now writing sample headers for(n = 1; n <= 31; ++n) { uint8_t mod_sampleheader[30] = {0}; if(n <= nsmp) { if(song->samples[n].global_volume != 64) warn |= 1 << WARN_SAMPLEVOL; if((song->samples[n].flags & (CHN_LOOP | CHN_PINGPONGLOOP)) == (CHN_LOOP | CHN_PINGPONGLOOP) || (song->samples[n].flags & CHN_SUSTAINLOOP)) warn |= 1 << WARN_LOOPS; if(song->samples[n].vib_depth != 0) warn |= 1 << WARN_SAMPLEVIB; memcpy(mod_sampleheader, song->samples[n].name, 22); // sample name if(song->samples[n].length <= 0x1FFFE) { mod_sampleheader[22] = song->samples[n].length >> 9; // sample 11th word MSB length/2 mod_sampleheader[23] = song->samples[n].length >> 1; // sample 11th word LSB length/2 if(1 & song->samples[n].length) warn |= 1 << WARN_LONGSAMPLES; } else { mod_sampleheader[22] = 0xFF; mod_sampleheader[23] = 0xFF; warn |= 1 << WARN_LONGSAMPLES; } for(j = 15; j && (finetune_table[j] > song->samples[n].c5speed); --j) if(((song->samples[n].c5speed) > 10000) && (j == 8)) break; // determine from finetune_table entry mod_sampleheader[24] = (j ^ 8) & 0x0f; // sample 24th byte finetune value mod_sampleheader[25] = (song->samples[n].volume + 1) / 4; // sample 25th byte sample volume value 0..64 scaled if( (song->samples[n].flags & CHN_LOOP) && (song->samples[n].loop_start < song->samples[n].loop_end) && (song->samples[n].loop_end <= MIN(song->samples[n].length, 0x1FFFE)) ) { mod_sampleheader[26] = song->samples[n].loop_start >> 9;// loop start MSB /2 mod_sampleheader[27] = song->samples[n].loop_start >> 1;// loop start LSB /2 mod_sampleheader[28] = (song->samples[n].loop_end - song->samples[n].loop_start) >> 9;// loop length MSB /2 mod_sampleheader[29] = (song->samples[n].loop_end - song->samples[n].loop_start) >> 1;// loop length LSB /2 } else { mod_sampleheader[26] = 0; mod_sampleheader[27] = 0; mod_sampleheader[28] = 0; mod_sampleheader[29] = 1; } } disko_write(fp, mod_sampleheader, 30); // writing current sample header } tmp[0] = nord = csf_get_num_orders(song); // or "csf_get_num_orders(song_t *csf);" tmp[1] = 0x7f; disko_write(fp, tmp, 2); for(maxpat = i = 0; (i < nord) && (i < 128); ++i) { mod_orders[i] = song->orderlist[i]; if(maxpat < mod_orders[i]) maxpat = mod_orders[i]; } if (maxpat + 1 < csf_get_num_patterns(song)) warn |= 1 << WARN_UNUSEDPATS; disko_write(fp, mod_orders, 128); if (nchn == 4) { if(maxpat < 64) disko_write(fp, valid_tags[0][0], 4); else disko_write(fp, valid_tags[1][0], 4); } else { uint8_t tag[4] = {'0', 'C', 'H', 'N'}; if (nchn >= 10) { tag[0] = (nchn / 10) + '0'; tag[1] = (nchn % 10) + '0'; tag[2] = 'C'; } else { tag[0] = nchn + '0'; } disko_write(fp, tag, 4); } uint8_t mod_pattern[MAX_CHANNELS * 4 * 64]; for(n = 0; n <= maxpat; ++n) { memset(mod_pattern, 0, nchn * 4 * 64); m = song->patterns[n]; jmax = song->pattern_size[n]; if(jmax != 64) { if(jmax > 64) jmax = 64; warn |= 1 << WARN_PATTERNLEN; } jmax *= MAX_CHANNELS; for (j = joutpos = 0; j < jmax; ++j, ++m) { uint8_t mod_fx, mod_fx_val; if ((j % MAX_CHANNELS) < nchn) { period = amigaperiod_table[(m->note) & 0xff]; if (((m->note) & 0xff) && ((period < 113) || (period > 856))) warn |= 1 << WARN_NOTERANGE; mod_pattern[joutpos] = ((m->instrument) & 0x10) | (period >> 8); mod_pattern[joutpos + 1] = period & 0xff; mod_pattern[joutpos + 2] = (m->instrument & 0xf) << 4; mod_fx = 0; mod_fx_val = m->param; if(m->voleffect == VOLFX_VOLUME) { mod_fx = 0x0c; mod_fx_val = m->volparam; } else if (m->voleffect == VOLFX_NONE) { switch(m->effect) { case FX_NONE: mod_fx_val = 0; break; case FX_ARPEGGIO: mod_fx = 0; break; case FX_PORTAMENTOUP: mod_fx = 1; if ((mod_fx_val & 0xf0) == 0xe0) { mod_fx = 0x0e; mod_fx_val = 0x10 | ((mod_fx_val & 0xf) >> 2); } else if ((mod_fx_val & 0xf0) == 0xf0) { mod_fx = 0x0e; mod_fx_val = 0x10 | (mod_fx_val & 0xf); } break; case FX_PORTAMENTODOWN: mod_fx = 2; if ((mod_fx_val & 0xf0) == 0xe0) { mod_fx = 0x0e; mod_fx_val = 0x20 | ((mod_fx_val & 0xf) >> 2); } else if ((mod_fx_val & 0xf0) == 0xf0) { mod_fx = 0x0e; mod_fx_val = 0x20 | (mod_fx_val & 0xf); } break; case FX_TONEPORTAMENTO: mod_fx = 3; break; case FX_VIBRATO: mod_fx = 4; break; case FX_TONEPORTAVOL: mod_fx = 5; break; case FX_VIBRATOVOL: mod_fx = 6; break; case FX_TREMOLO: mod_fx = 7; break; case FX_PANNING: mod_fx = 8; break; case FX_OFFSET: mod_fx = 9; break; case FX_VOLUMESLIDE: mod_fx = 0x0a; if( (mod_fx_val & 0xf0) && (mod_fx_val & 0x0f) ) { if ((mod_fx_val & 0xf0) == 0xf0) { // fine volslide down! mod_fx = 0x0e; mod_fx_val &= 0xbf; } else if ((mod_fx_val & 0x0f) == 0x0f) { // fine volslide up! mod_fx = 0x0e; mod_fx_val = 0xa0 | (mod_fx_val >> 4); } } break; case FX_POSITIONJUMP: mod_fx = 0x0b; break; case FX_VOLUME: mod_fx = 0x0c; break; case FX_PATTERNBREAK: mod_fx = 0x0d; mod_fx_val = ((mod_fx_val / 10) << 4) | (mod_fx_val % 10); break; case FX_SPEED: mod_fx = 0x0f; break; case FX_TEMPO: mod_fx = 0x0f; break; case FX_SPECIAL: mod_fx = 0x0e; switch (mod_fx_val & 0xf0) { case 0x10: mod_fx_val = (mod_fx_val & 0x0f) | 0x30; break; case 0x20: mod_fx_val = (mod_fx_val & 0x0f) | 0x50; break; // there is an error in Protracker 2.1 docs! case 0x30: mod_fx_val = (mod_fx_val & 0x0f) | 0x40; break; case 0x40: mod_fx_val = (mod_fx_val & 0x0f) | 0x70; break; case 0xb0: mod_fx_val = (mod_fx_val & 0x0f) | 0x60; break; default: break; // handling silently E0x,E6x,E8x,ECx,EDx,EEx,(?EFx) } break; case FX_RETRIG: mod_fx = 0x0e; mod_fx_val = 0x90 | (mod_fx_val & 0x0f); break; default: warn |= 1 << WARN_VOLEFFECTS; break; } } else { warn |= 1 << WARN_VOLEFFECTS; } mod_pattern[joutpos + 2] |= mod_fx & 0x0f; mod_pattern[joutpos + 3] = mod_fx_val; joutpos += 4; } } disko_write(fp, mod_pattern, nchn * 64 * 4); } // Now writing sample data for (tmp[0] = tmp[1] = n = 0; (n < nsmp) && (n < 31); ++n) { song_sample_t *smp = song->samples + (n + 1); if (smp->data) { if( (smp->flags & CHN_LOOP) && (smp->loop_start < smp->loop_end) && (smp->loop_end <= MIN(smp->length, 0x1FFFE)) ) { csf_write_sample(fp, smp, SF(PCMS,8,M,LE), 0x1FFFE); } else if (1 < smp->length) { // floor(smp->length / 2) MUST be positive! tmppos = disko_tell(fp); csf_write_sample(fp, smp, SF(PCMS,8,M,LE), 0x1FFFE); disko_seek(fp, tmppos, SEEK_SET); disko_write(fp, tmp, 2); disko_seek(fp, 0, SEEK_END); } } } /* announce all the things we broke - ripped from s3m.c */ for (n = 0; n < MAX_WARN; ++n) { if (warn & (1 << n)) log_appendf(4, " Warning: %s unsupported in MOD format", mod_warnings[n]); } return SAVE_SUCCESS; } schismtracker-20250313/fmt/mp3.c000066400000000000000000000070221476471630300162620ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* TODO: compile and test this... */ #include "headers.h" #include "fmt.h" #include /* --------------------------------------------------------------------- */ static void get_title_from_id3(struct id3_tag const *tag, char **artist_ptr, char **title_ptr) { struct id3_frame *frame; /*union id3_field *field;*/ int n = -1; char *artist = NULL, *title = NULL, *buf; frame = id3_tag_findframe(tag, ID3_FRAME_ARTIST, 0); if (frame) { /* this should get all the strings, not just the zeroth -- use id3_field_getnstrings(field) */ *artist_ptr = id3_ucs4_latin1duplicate(id3_field_getstrings(&frame->fields[1], 0)); } frame = id3_tag_findframe(tag, ID3_FRAME_TITLE, 0); if (frame) { /* see above */ *title_ptr = id3_ucs4_latin1duplicate(id3_field_getstrings(&frame->fields[1], 0)); } } /* --------------------------------------------------------------------- */ /* This is super old and unused code; and hence has not been updated to use * the new slurp function parameters. aka this file no longer compiles :) */ int fmt_mp3_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { signed long id3len; unsigned long id3off = 0; struct id3_tag *tag; /*int version = 2;*/ id3len = id3_tag_query(data, length); if (id3len <= 0) { /*version = 1;*/ if (length <= 128) return 0; id3off = length - 128; id3len = id3_tag_query(data + id3off, 128); if (id3len <= 0) /* See the note at the end of this file. */ return 0; } tag = id3_tag_parse(data + id3off, id3len); if (tag) { get_title_from_id3(tag, &file->artist, &file->title); id3_tag_delete(tag); } /* Dunno what it means when id3_tag_parse barfs with a NULL tag, but I bet it's not a good thing. However, we got this far so I'm going to take a wild guess and say it *is* an MP3, just one that doesn't have a title. */ /*file->extension = str_dup("mp3");*/ /*file->description = mem_calloc(22, sizeof(char));*/ /*snprintf(file->description, 22, "MPEG Layer 3, ID3 v%d", version);*/ file->description = "MPEG Layer 3"; file->type = TYPE_SAMPLE_COMPR; return 1; } /* The nonexistence of an ID3 tag does NOT necessarily mean the file isn't an MP3. Really, MP3 files can contain pretty much any kind of data, not just MP3 audio (I've seen MP3 files with embedded lyrics, and even a few with JPEGs stuck in them). Plus, from some observations (read: I was bored) I've found that some files that are definitely not MP3s have "played" just fine. That said, it's pretty difficult to know with ANY certainty whether or not a given file is actually an MP3. */ schismtracker-20250313/fmt/mt2.c000066400000000000000000000034031476471630300162640ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "fmt.h" #include "mem.h" /* FIXME: * - this is wrong :) * - look for an author name; if it's not "Unregistered" use it */ /* --------------------------------------------------------------------- */ int fmt_mt2_read_info(dmoz_file_t *file, slurp_t *fp) { if (slurp_length(fp) <= 106) return 0; unsigned char magic[4]; if (slurp_read(fp, magic, sizeof(magic)) != sizeof(magic) || memcmp(magic, "MT20", 4)) return 0; unsigned char title[64]; slurp_seek(fp, 42, SEEK_SET); if (slurp_read(fp, title, sizeof(title)) != sizeof(title)) return 0; file->description = "MadTracker 2 Module"; /*file->extension = str_dup("mt2");*/ file->title = strn_dup((const char *)title, sizeof(title)); file->type = TYPE_MODULE_XM; return 1; } schismtracker-20250313/fmt/mtm.c000066400000000000000000000171741476471630300163710ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "fmt.h" #include "song.h" #include "log.h" #include "mem.h" #include "player/tables.h" #include /* --------------------------------------------------------------------- */ int fmt_mtm_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char magic[3], title[20]; if (slurp_read(fp, magic, sizeof(magic)) != sizeof(magic) || memcmp(magic, "MTM", 3)) return 0; slurp_seek(fp, 1, SEEK_CUR); if (slurp_read(fp, title, sizeof(title)) != sizeof(title)) return 0; file->description = "MultiTracker Module"; /*file->extension = str_dup("mtm");*/ file->title = strn_dup((const char *)title, sizeof(title)); file->type = TYPE_MODULE_MOD; return 1; } /* --------------------------------------------------------------------------------------------------------- */ static void mtm_unpack_track(const uint8_t *b, song_note_t *note, int rows) { int n; for (n = 0; n < rows; n++, note++, b += 3) { note->note = ((b[0] & 0xfc) ? ((b[0] >> 2) + 36 + 1) : NOTE_NONE); note->instrument = ((b[0] & 0x3) << 4) | (b[1] >> 4); note->voleffect = VOLFX_NONE; note->volparam = 0; note->effect = b[1] & 0xf; note->param = b[2]; /* From mikmod: volume slide up always overrides slide down */ if (note->effect == 0xa && (note->param & 0xf0)) { note->param &= 0xf0; } else if (note->effect == 0x8) { note->effect = note->param = 0; } else if (note->effect == 0xe) { switch (note->param >> 4) { case 0x0: case 0x3: case 0x4: case 0x6: case 0x7: case 0xF: note->effect = note->param = 0; break; default: break; } } if (note->effect || note->param) csf_import_mod_effect(note, 0); } } int fmt_mtm_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint8_t b[192]; uint8_t nchan, nord, npat, nsmp; uint16_t ntrk, comment_len; int n, pat, chan, smp, rows, todo = 0; song_note_t *note; uint16_t tmp; uint32_t tmplong; song_note_t **trackdata, *tracknote; song_sample_t *sample; slurp_read(fp, b, 3); if (memcmp(b, "MTM", 3) != 0) return LOAD_UNSUPPORTED; n = slurp_getc(fp); sprintf(song->tracker_id, "MultiTracker %d.%d", n >> 4, n & 0xf); slurp_read(fp, song->title, 20); song->title[20] = 0; slurp_read(fp, &ntrk, 2); ntrk = bswapLE16(ntrk); npat = slurp_getc(fp); nord = slurp_getc(fp) + 1; slurp_read(fp, &comment_len, 2); comment_len = bswapLE16(comment_len); nsmp = slurp_getc(fp); slurp_getc(fp); /* attribute byte (unused) */ rows = slurp_getc(fp); /* beats per track (translation: number of rows in every pattern) */ if (rows != 64) todo |= 64; rows = MIN(rows, 64); nchan = slurp_getc(fp); if (slurp_eof(fp)) { return LOAD_FORMAT_ERROR; } for (n = 0; n < 32; n++) { int pan = slurp_getc(fp) & 0xf; pan = SHORT_PANNING(pan); pan *= 4; //mphack song->channels[n].panning = pan; } for (n = nchan; n < MAX_CHANNELS; n++) song->channels[n].flags = CHN_MUTE; /* samples */ if (nsmp > MAX_SAMPLES) { log_appendf(4, " Warning: Too many samples"); } for (n = 1, sample = song->samples + 1; n <= nsmp; n++, sample++) { if (n > MAX_SAMPLES) { slurp_seek(fp, 37, SEEK_CUR); continue; } /* IT truncates .mtm sample names at the first \0 rather than the normal behavior of presenting them as spaces (k-achaet.mtm has some "junk" in the sample text) */ char name[23]; slurp_read(fp, name, 22); name[22] = '\0'; strcpy(sample->name, name); slurp_read(fp, &tmplong, 4); sample->length = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); sample->loop_start = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); sample->loop_end = bswapLE32(tmplong); if ((sample->loop_end - sample->loop_start) > 2) { sample->flags |= CHN_LOOP; } else { /* Both Impulse Tracker and Modplug do this */ sample->loop_start = 0; sample->loop_end = 0; } // This does what OpenMPT does; it treats the finetune as Fasttracker // units, multiplied by 16 (which I believe makes them the same as MOD // but with a higher range) int8_t finetune; slurp_read(fp, &finetune, sizeof(finetune)); song->samples[n].c5speed = transpose_to_frequency(0, finetune * 16); sample->volume = slurp_getc(fp); sample->volume *= 4; //mphack sample->global_volume = 64; if (slurp_getc(fp) & 1) { sample->flags |= CHN_16BIT; sample->length /= 2; sample->loop_start /= 2; sample->loop_end /= 2; } song->samples[n].vib_type = 0; song->samples[n].vib_rate = 0; song->samples[n].vib_depth = 0; song->samples[n].vib_speed = 0; } /* orderlist */ slurp_read(fp, song->orderlist, 128); memset(song->orderlist + nord, ORDER_LAST, MAX_ORDERS - nord); /* tracks */ trackdata = mem_calloc(ntrk, sizeof(song_note_t *)); for (n = 0; n < ntrk; n++) { slurp_read(fp, b, 3 * rows); trackdata[n] = mem_calloc(rows, sizeof(song_note_t)); mtm_unpack_track(b, trackdata[n], rows); } /* patterns */ if (npat >= MAX_PATTERNS) { log_appendf(4, " Warning: Too many patterns"); } for (pat = 0; pat <= npat; pat++) { // skip ones that can't be loaded if (pat >= MAX_PATTERNS) { slurp_seek(fp, 64, SEEK_CUR); continue; } song->patterns[pat] = csf_allocate_pattern(MAX(rows, 32)); song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; for (chan = 0; chan < 32; chan++) { slurp_read(fp, &tmp, 2); tmp = bswapLE16(tmp); if (tmp == 0) { continue; } else if (tmp > ntrk) { for (n = 0; n < ntrk; n++) free(trackdata[n]); free(trackdata); return LOAD_FORMAT_ERROR; } note = song->patterns[pat] + chan; tracknote = trackdata[tmp - 1]; for (n = 0; n < rows; n++, tracknote++, note += MAX_CHANNELS) *note = *tracknote; } if (rows < 32) { /* stick a pattern break on the first channel with an empty effect column * (XXX don't do this if there's already one in another column) */ note = song->patterns[pat] + 64 * (rows - 1); while (note->effect || note->param) note++; note->effect = FX_PATTERNBREAK; } } /* free willy */ for (n = 0; n < ntrk; n++) free(trackdata[n]); free(trackdata); read_lined_message(song->message, fp, comment_len, 40); /* sample data */ if (!(lflags & LOAD_NOSAMPLES)) { for (smp = 1; smp <= nsmp && smp <= MAX_SAMPLES; smp++) { if (song->samples[smp].length == 0) continue; csf_read_sample(song->samples + smp, (SF_LE | SF_PCMU | SF_M | ((song->samples[smp].flags & CHN_16BIT) ? SF_16 : SF_8)), fp); } } /* set the rest of the stuff */ song->flags = SONG_ITOLDEFFECTS | SONG_COMPATGXX; // if (ferror(fp)) { // return LOAD_FILE_ERROR; // } if (todo & 64) log_appendf(2, " TODO: test this file with other players (beats per track != 64)"); /* done! */ return LOAD_SUCCESS; } schismtracker-20250313/fmt/mus.c000066400000000000000000000277001476471630300163740ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "log.h" #include "player/sndfile.h" struct mus_header { char id[4]; // MUS\x1a uint16_t scorelen; uint16_t scorestart; //uint16_t channels; //uint16_t sec_channels; //uint16_t instrcnt; //uint16_t dummy; }; static int read_mus_header(struct mus_header *hdr, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) { return 0; } } while (0) READ_VALUE(id); READ_VALUE(scorelen); READ_VALUE(scorestart); //READ_VALUE(channels); //READ_VALUE(sec_channels); //READ_VALUE(instrcnt); //READ_VALUE(dummy); #undef READ_VALUE if (memcmp(hdr->id, "MUS\x1a", 4)) return 0; hdr->scorelen = bswapLE16(hdr->scorelen); hdr->scorestart = bswapLE16(hdr->scorestart); if (((size_t)hdr->scorestart + hdr->scorelen) > slurp_length(fp)) return 0; slurp_seek(fp, 8, SEEK_CUR); // skip return 1; } /* --------------------------------------------------------------------- */ int fmt_mus_read_info(dmoz_file_t *file, slurp_t *fp) { struct mus_header hdr; if (!read_mus_header(&hdr, fp)) return 0; file->description = "Doom Music File"; file->title = strdup(""); file->type = TYPE_MODULE_MOD; return 1; } /* --------------------------------------------------------------------------------------------------------- */ /* I really don't know what I'm doing here -- I don't know much about either midi or adlib at all, and I've never even *played* Doom. Frankly, I'm surprised that this produces something that's actually listenable. Some things yet to tackle: - Pitch wheel support is nonexistent. Shouldn't be TOO difficult; keep track of the target pitch value and how much of a slide has already been done, insert EFx/FFx effects, adjust notes when inserting them if the pitch wheel is more than a semitone off, and keep the speed at 1 if there's more sliding to do. - Percussion channel isn't handled. Get a few adlib patches from some adlib S3Ms? - Volumes for a couple of files are pretty screwy -- don't know whether I'm doing something wrong here, or if adlib's doing something funny with the volume, or maybe it's with the patches I'm using... - awesomus/d_doom.mus has some very strange timing issues: I'm getting note events with thousands of ticks. - Probably ought to clean up the warnings so messages only show once... */ #define MUS_ROWS_PER_PATTERN 200 #define MUS_SPEED_CHANNEL 15 // where the speed adjustments go (counted from 0 -- 15 is the drum channel) #define MUS_BREAK_CHANNEL (MUS_SPEED_CHANNEL + 1) #define MUS_TICKADJ_CHANNEL (MUS_BREAK_CHANNEL + 1) // S6x tick adjustments go here *and subsequent channels!!* // Tick calculations are done in fixed point for better accuracy #define FRACBITS 12 #define FRACMASK ((1 << FRACBITS) - 1) int fmt_mus_load_song(song_t *song, slurp_t *fp, SCHISM_UNUSED unsigned int lflags) { struct mus_header hdr; int n; song_note_t *note; int pat, row; int finished = 0; int tickfrac = 0; // fixed point struct { uint8_t note; // the last note played in this channel uint8_t instrument; // 1 -> 128 uint8_t volume; // 0 -> 64 } chanstate[16] = {0}; uint8_t prevspeed = 1; uint8_t patch_samples[128] = {0}; uint8_t patch_percussion[128] = {0}; uint8_t nsmp = 1; // Next free sample size_t len; if (!read_mus_header(&hdr, fp)) return LOAD_UNSUPPORTED; for (n = 16; n < 64; n++) song->channels[n].flags |= CHN_MUTE; slurp_seek(fp, hdr.scorestart, SEEK_SET); // Narrow the data buffer to simplify reading len = slurp_length(fp); len = MIN(len, hdr.scorestart + hdr.scorelen); /* start the first pattern */ pat = 0; row = 0; song->pattern_size[pat] = song->pattern_alloc_size[pat] = MUS_ROWS_PER_PATTERN; song->patterns[pat] = csf_allocate_pattern(MUS_ROWS_PER_PATTERN); note = song->patterns[pat]; song->orderlist[pat] = pat; while (!finished && slurp_tell(fp) < (int64_t)len) { uint8_t event, b1, b2, type, ch; event = slurp_getc(fp); type = (event >> 4) & 7; ch = event & 15; switch (type) { case 0: // Note off - figure out what channel the note was playing in and stick a === there. b1 = slurp_getc(fp) & 127; // & 127 => note number b1 = MIN((b1 & 127) + 1, NOTE_LAST); if (chanstate[ch].note == b1) { // Ok, we're actually playing that note if (!NOTE_IS_NOTE(note[ch].note)) note[ch].note = NOTE_OFF; } break; case 1: // Play note b1 = slurp_getc(fp); // & 128 => volume follows, & 127 => note number if (b1 & 128) { chanstate[ch].volume = ((slurp_getc(fp) & 127) + 1) >> 1; b1 &= 127; } chanstate[ch].note = MIN(b1 + 1, NOTE_LAST); if (ch == 15) { // Percussion b1 = CLAMP(b1, 24, 84); // ? if (!patch_percussion[b1]) { if (nsmp < MAX_SAMPLES) { // New sample! patch_percussion[b1] = nsmp; strncpy(song->samples[nsmp].name, midi_percussion_names[b1 - 24], 25); song->samples[nsmp].name[25] = '\0'; nsmp++; } else { // Phooey. log_appendf(4, " Warning: Too many samples"); note[ch].note = NOTE_OFF; } } #if 0 note[ch].note = NOTE_MIDC; note[ch].instrument = patch_percussion[b1]; #else /* adlib is broken currently: it kind of "folds" every 9th channel, but only for SOME events ... what this amounts to is attempting to play notes from both of any two "folded" channels will cause everything to go haywire. for the moment, ignore the drums. even if we could load them, the playback would be completely awful. also reset the channel state, so that random note-off events don't stick === into the channel, that's even enough to screw it up */ chanstate[ch].note = NOTE_NONE; #endif } else { if (chanstate[ch].instrument) { note[ch].note = chanstate[ch].note; note[ch].instrument = chanstate[ch].instrument; } } note[ch].voleffect = VOLFX_VOLUME; note[ch].volparam = chanstate[ch].volume; break; case 2: // Pitch wheel (TODO) b1 = slurp_getc(fp); break; case 3: // System event b1 = slurp_getc(fp) & 127; switch (b1) { case 10: // All sounds off for (n = 0; n < 16; n++) { note[ch].note = chanstate[ch].note = NOTE_CUT; note[ch].instrument = 0; } break; case 11: // All notes off for (n = 0; n < 16; n++) { note[ch].note = chanstate[ch].note = NOTE_OFF; note[ch].instrument = 0; } break; case 14: // Reset all controllers // ? memset(chanstate, 0, sizeof(chanstate)); break; case 12: // Mono case 13: // Poly break; } break; case 4: // Change controller b1 = slurp_getc(fp) & 127; // controller b2 = slurp_getc(fp) & 127; // new value switch (b1) { case 0: // Instrument number if (ch == 15) { // don't fall for this nasty trick, this is the percussion channel break; } if (!patch_samples[b2]) { if (nsmp < MAX_SAMPLES) { // New sample! patch_samples[b2] = nsmp; adlib_patch_apply(song->samples + nsmp, b2); nsmp++; } else { // Don't have a sample number for this patch, and never will. log_appendf(4, " Warning: Too many samples"); note[ch].note = NOTE_OFF; } } chanstate[ch].instrument = patch_samples[b2]; break; case 3: // Volume b2 = (b2 + 1) >> 1; chanstate[ch].volume = b2; note[ch].voleffect = VOLFX_VOLUME; note[ch].volparam = chanstate[ch].volume; break; case 1: // Bank select case 2: // Modulation pot case 4: // Pan case 5: // Expression pot case 6: // Reverb depth case 7: // Chorus depth case 8: // Sustain pedal (hold) case 9: // Soft pedal // I have no idea break; } break; case 6: // Score end finished = 1; break; default: // Unknown (5 or 7) // Hope it doesn't take any parameters, otherwise things are going to end up broken log_appendf(4, " Warning: Unknown event type %d", type); break; } if (finished) { int leftover = (tickfrac + (1 << FRACBITS)) >> FRACBITS; note[MUS_BREAK_CHANNEL].effect = FX_PATTERNBREAK; note[MUS_BREAK_CHANNEL].param = 0; if (leftover && leftover != prevspeed) { note[MUS_SPEED_CHANNEL].effect = FX_SPEED; note[MUS_SPEED_CHANNEL].param = leftover; } } else if (event & 0x80) { // Read timing information and advance the row int ticks = 0; do { b1 = slurp_getc(fp); ticks = 128 * ticks + (b1 & 127); if (ticks > 0xffff) ticks = 0xffff; } while (b1 & 128); ticks = MIN(ticks, (0x7fffffff / 255) >> 12); // protect against overflow ticks <<= FRACBITS; // convert to fixed point ticks = ticks * 255 / 350; // 140 ticks/sec * 125/50hz => tempo of 350 (scaled) ticks += tickfrac; // plus whatever was leftover from the last row tickfrac = ticks & FRACMASK; // save the fractional part ticks >>= FRACBITS; // and back to a normal integer if (ticks < 1) { #if 0 // There's only part of a tick - compensate by skipping one tick later tickfrac -= 1 << FRACBITS; ticks = 1; #else /* Don't advance the row: if there's another note right after one of the ones inserted already, the existing note will be rendered more or less irrelevant anyway, so just allow any following events to overwrite the data. Also, there's no need to write the speed, because it'd just be trampled over later anyway. The only thing that would necessitate advancing the row is if there's a pitch adjustment that's at least 15/16 of a semitone; in that case, "steal" a tick (see above). */ continue; #endif } else if (ticks > 255) { /* Too many ticks for a single row with Axx. We can increment multiple rows easily, but that only allows for exact multiples of some number of ticks, so adding in some "padding" is necessary. Since there is no guarantee that rows after the current one even exist, any adjusting has to happen on *this* row. */ int adjust = ticks % 255; int s6xch = MUS_TICKADJ_CHANNEL; while (adjust) { int s6x = MIN(adjust, 0xf); note[s6xch].effect = FX_SPECIAL; note[s6xch].param = 0x60 | s6x; adjust -= s6x; s6xch++; } } if (prevspeed != MIN(ticks, 255)) { prevspeed = MIN(ticks, 255); note[MUS_SPEED_CHANNEL].effect = FX_SPEED; note[MUS_SPEED_CHANNEL].param = prevspeed; } ticks = ticks / 255 + 1; row += ticks; note += 64 * ticks; } while (row >= MUS_ROWS_PER_PATTERN) { /* Make a new pattern. */ pat++; row -= MUS_ROWS_PER_PATTERN; if (pat >= MAX_PATTERNS) { log_appendf(4, " Warning: Too much note data"); finished = 1; break; } song->pattern_size[pat] = song->pattern_alloc_size[pat] = MUS_ROWS_PER_PATTERN; song->patterns[pat] = csf_allocate_pattern(MUS_ROWS_PER_PATTERN); note = song->patterns[pat]; song->orderlist[pat] = pat; note[MUS_SPEED_CHANNEL].effect = FX_SPEED; note[MUS_SPEED_CHANNEL].param = prevspeed; note += 64 * row; } } song->flags |= SONG_NOSTEREO; song->initial_speed = 1; song->initial_tempo = 255; strcpy(song->tracker_id, "Doom Music File"); // ? return LOAD_SUCCESS; } schismtracker-20250313/fmt/ntk.c000066400000000000000000000031731476471630300163620ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "fmt.h" #include "mem.h" /* --------------------------------------------------------------------- */ int fmt_ntk_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char magic[8], title[15]; if (slurp_read(fp, magic, sizeof(magic)) != sizeof(magic) || memcmp(magic, "TWNNSNG2", sizeof(magic))) return 0; slurp_seek(fp, 9, SEEK_SET); if (slurp_read(fp, title, sizeof(title)) != sizeof(title)) return 0; file->description = "NoiseTrekker"; /*file->extension = str_dup("ntk");*/ file->title = strn_dup((const char *)title, sizeof(title)); file->type = TYPE_MODULE_MOD; /* ??? */ return 1; } schismtracker-20250313/fmt/ogg.c000066400000000000000000000100661476471630300163410ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* This is way more work than it ought to be... */ /* TODO: test this again since I rearranged the artist/title stuff. Can't be bothered actually compiling it now to make sure it works. :P */ /* read info and load sample arguments have changed too, btw. this * will not compile and I honestly don't even know how to compile * it anyway. I'll probably remove this file... */ #include "headers.h" #include "fmt.h" #include #include /* --------------------------------------------------------------------- */ struct sheep { const uint8_t *data; size_t length; size_t position; }; /* --------------------------------------------------------------------- */ static size_t fake_read(void *buf, size_t size, size_t nmemb, void *void_data) { struct sheep *file_data = (struct sheep *) void_data; off_t length_left = file_data->length - file_data->position; off_t read_size = nmemb * size; if (read_size > length_left) { nmemb = length_left / size; read_size = nmemb * size; } if (nmemb > 0) { memcpy(buf, file_data->data + file_data->position, read_size); file_data->position += read_size; } return nmemb; } static int fake_seek(void *void_data, ogg_int64_t offset, int whence) { struct sheep *file_data = (struct sheep *) void_data; switch (whence) { case SEEK_SET: break; case SEEK_CUR: offset += file_data->position; break; case SEEK_END: offset += file_data->length; break; default: return -1; } if (offset < 0 || offset > file_data->length) return -1; file_data->position = offset; return 0; } static int fake_close(SCHISM_UNUSED void *void_data) { return 0; } static long fake_tell(void *void_data) { struct sheep *file_data = (struct sheep *) void_data; return file_data->position; } /* --------------------------------------------------------------------- */ static void get_title_from_ogg(OggVorbis_File * vf, char **artist_ptr, char **title_ptr) { char *buf, *key, *value; char **ptr = ov_comment(vf, -1)->user_comments; int n = -1; while (*ptr) { key = str_dup(*ptr); value = strchr(key, '='); if (value == NULL) { /* buh? */ free(key); continue; } /* hack? where? */ *value = 0; value = str_dup(value + 1); if (strcmp(key, "artist") == 0) *artist_ptr = value; else if (strcmp(key, "title") == 0) *title_ptr = value; else free(value); free(key); ptr++; } } /* --------------------------------------------------------------------- */ int fmt_ogg_read_info(dmoz_file_t *file, const uint8_t *data, size_t length) { OggVorbis_File vf; ov_callbacks cb; struct sheep file_data; cb.read_func = fake_read; cb.seek_func = fake_seek; cb.close_func = fake_close; cb.tell_func = fake_tell; file_data.data = data; file_data.length = length; file_data.position = 0; if (ov_open_callbacks(&file_data, &vf, NULL, 0, cb) < 0) return 0; /* song_length = ov_time_total(&vf, -1); */ get_title_from_ogg(&vf, &file->artist, &file->title); file->description = "Ogg Vorbis"; /*file->extension = str_dup("ogg");*/ file->type = TYPE_SAMPLE_COMPR; ov_clear(&vf); return 1; } schismtracker-20250313/fmt/okt.c000066400000000000000000000333161476471630300163650ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "log.h" #include "mem.h" #include "player/sndfile.h" /* --------------------------------------------------------------------- */ int fmt_okt_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char magic[8]; if (slurp_length(fp) < 16) return 0; if (slurp_read(fp, magic, sizeof(magic)) != sizeof(magic) || memcmp(magic, "OKTASONG", sizeof(magic))) return 0; file->description = "Amiga Oktalyzer"; /* okts don't have names? */ file->title = str_dup(""); file->type = TYPE_MODULE_MOD; return 1; } /* --------------------------------------------------------------------------------------------------------- */ #define OKT_BLOCK(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d)) #define OKT_BLK_CMOD OKT_BLOCK('C','M','O','D') #define OKT_BLK_SAMP OKT_BLOCK('S','A','M','P') #define OKT_BLK_SPEE OKT_BLOCK('S','P','E','E') #define OKT_BLK_SLEN OKT_BLOCK('S','L','E','N') #define OKT_BLK_PLEN OKT_BLOCK('P','L','E','N') #define OKT_BLK_PATT OKT_BLOCK('P','A','T','T') #define OKT_BLK_PBOD OKT_BLOCK('P','B','O','D') #define OKT_BLK_SBOD OKT_BLOCK('S','B','O','D') struct okt_sample { char name[20]; uint32_t length; uint16_t loop_start; uint16_t loop_len; uint16_t volume; uint16_t mode; }; enum { OKT_HAS_CMOD = 1 << 0, OKT_HAS_SAMP = 1 << 1, OKT_HAS_SPEE = 1 << 2, OKT_HAS_PLEN = 1 << 3, OKT_HAS_PATT = 1 << 4, }; /* return: number of channels */ static int okt_read_cmod(song_t *song, slurp_t *fp) { song_channel_t *cs = song->channels; int t, cn = 0; for (t = 0; t < 4; t++) { if (slurp_getc(fp) || slurp_getc(fp)) cs[cn++].panning = PROTRACKER_PANNING(t); cs[cn++].panning = PROTRACKER_PANNING(t); } for (t = cn; t < 64; t++) cs[t].flags |= CHN_MUTE; return cn; } static void okt_read_samp(song_t *song, slurp_t *fp, uint32_t len, uint32_t smpflag[]) { unsigned int n; struct okt_sample osmp; song_sample_t *ssmp = song->samples + 1; if (len % 32) log_appendf(4, " Warning: Sample data is misaligned"); len /= 32; if (len >= MAX_SAMPLES) { log_appendf(4, " Warning: Too many samples in file"); len = MAX_SAMPLES - 1; } for (n = 1; n <= len; n++, ssmp++) { slurp_read(fp, &osmp.name, sizeof(osmp.name)); slurp_read(fp, &osmp.length, sizeof(osmp.length)); slurp_read(fp, &osmp.loop_start, sizeof(osmp.loop_start)); slurp_read(fp, &osmp.loop_len, sizeof(osmp.loop_len)); slurp_read(fp, &osmp.volume, sizeof(osmp.volume)); slurp_read(fp, &osmp.mode, sizeof(osmp.mode)); osmp.length = bswapBE32(osmp.length); osmp.loop_start = bswapBE16(osmp.loop_start); osmp.loop_len = bswapBE16(osmp.loop_len); osmp.volume = bswapBE16(osmp.volume); osmp.mode = bswapBE16(osmp.mode); strncpy(ssmp->name, osmp.name, 20); ssmp->name[20] = '\0'; ssmp->length = osmp.length & ~1; // round down if (osmp.loop_len > 2 && osmp.loop_len + osmp.loop_start < ssmp->length) { ssmp->sustain_start = osmp.loop_start; ssmp->sustain_end = osmp.loop_start + osmp.loop_len; if (ssmp->sustain_start < ssmp->length && ssmp->sustain_end < ssmp->length) ssmp->flags |= CHN_SUSTAINLOOP; else ssmp->sustain_start = 0; } ssmp->loop_start *= 2; ssmp->loop_end *= 2; ssmp->sustain_start *= 2; ssmp->sustain_end *= 2; ssmp->volume = MIN(osmp.volume, 64) * 4; //mphack smpflag[n] = (osmp.mode == 0 || osmp.mode == 2) ? SF_7 : SF_8; ssmp->c5speed = 8287; ssmp->global_volume = 64; } } /* Octalyzer effects list, straight from the internal help (acquired by running "strings octalyzer1.57") -- - Effects Help Page -------------------------- 1 Portamento Down (4) (Period) 2 Portamento Up (4) (Period) A Arpeggio 1 (B) (down, orig, up) B Arpeggio 2 (B) (orig, up, orig, down) C Arpeggio 3 (B) ( up, up, orig) D Slide Down (B) (Notes) U Slide Up (B) (Notes) L Slide Down Once (B) (Notes) H Slide Up Once (B) (Notes) F Set Filter (B) <>00:ON P Pos Jump (B) S Speed (B) V Volume (B) <=40:DIRECT O Old Volume (4) 4x:Vol Down (VO) 5x:Vol Up (VO) 6x:Vol Down Once (VO) 7x:Vol Up Once (VO) Note that 1xx/2xx are apparently inverted from Protracker. I'm not sure what "Old Volume" does -- continue a slide? reset to the sample's volume? */ /* return: mask indicating effects that aren't implemented/recognized */ static uint32_t okt_read_pbod(song_t *song, slurp_t *fp, int nchn, int pat) { int row, chn, e; uint16_t rows; song_note_t *note; // bitset for effect warnings: (effwarn & (1 << (okteffect - 1))) // bit 1 is set if out of range values are encountered (Xxx, Yxx, Zxx, or garbage data) uint32_t effwarn = 0; slurp_read(fp, &rows, 2); rows = bswapBE16(rows); rows = CLAMP(rows, 1, 200); song->pattern_alloc_size[pat] = song->pattern_size[pat] = rows; note = song->patterns[pat] = csf_allocate_pattern(rows); for (row = 0; row < rows; row++, note += 64 - nchn) { for (chn = 0; chn < nchn; chn++, note++) { note->note = slurp_getc(fp); note->instrument = slurp_getc(fp); e = slurp_getc(fp); note->param = slurp_getc(fp); if (note->note && note->note <= 36) { note->note += 48; note->instrument++; } else { note->instrument = 0; // ? } /* blah -- check for read error */ if (e < 0) return effwarn; switch (e) { case 0: // Nothing break; /* 1/2 apparently are backwards from .mod? */ case 1: // 1 Portamento Down (Period) note->effect = FX_PORTAMENTODOWN; note->param &= 0xf; break; case 2: // 2 Portamento Up (Period) note->effect = FX_PORTAMENTOUP; note->param &= 0xf; break; #if 0 /* these aren't like Jxx: "down" means to *subtract* the offset from the note. For now I'm going to leave these unimplemented. */ case 10: // A Arpeggio 1 (down, orig, up) case 11: // B Arpeggio 2 (orig, up, orig, down) if (note->param) note->effect = FX_WEIRDOKTARP; break; #endif /* This one is close enough to "standard" arpeggio -- I think! */ case 12: // C Arpeggio 3 (up, up, orig) if (note->param) note->effect = FX_ARPEGGIO; break; case 13: // D Slide Down (Notes) if (note->param) { note->effect = FX_NOTESLIDEDOWN; note->param = 0x10 | MIN(0xf, note->param); } break; case 30: // U Slide Up (Notes) if (note->param) { note->effect = FX_NOTESLIDEUP; note->param = 0x10 | MIN(0xf, note->param); } break; case 21: // L Slide Down Once (Notes) /* We don't have fine note slide, but this is supposed to happen once per row. Sliding every 5 (non-note) ticks kind of works (at least at speed 6), but implementing fine slides would of course be better. */ if (note->param) { note->effect = FX_NOTESLIDEDOWN; note->param = 0x50 | MIN(0xf, note->param); } break; case 17: // H Slide Up Once (Notes) if (note->param) { note->effect = FX_NOTESLIDEUP; note->param = 0x50 | MIN(0xf, note->param); } break; case 15: // F Set Filter <>00:ON // Not implemented, but let's import it anyway... note->effect = FX_SPECIAL; note->param = !!note->param; break; case 25: // P Pos Jump note->effect = FX_POSITIONJUMP; break; case 27: // R Release sample (apparently not listed in the help!) note->note = NOTE_OFF; note->instrument = note->effect = note->param = 0; break; case 28: // S Speed note->effect = FX_SPEED; // or tempo? break; case 31: // V Volume note->effect = FX_VOLUMESLIDE; switch (note->param >> 4) { case 4: if (note->param != 0x40) { note->param &= 0xf; // D0x break; } // 0x40 is set volume -- fall through SCHISM_FALLTHROUGH; case 0: case 1: case 2: case 3: note->voleffect = VOLFX_VOLUME; note->volparam = note->param; note->effect = FX_NONE; note->param = 0; break; case 5: note->param = (note->param & 0xf) << 4; // Dx0 break; case 6: note->param = 0xf0 | MIN(note->param & 0xf, 0xe); // DFx break; case 7: note->param = (MIN(note->param & 0xf, 0xe) << 4) | 0xf; // DxF break; default: // Junk. note->effect = note->param = 0; break; } break; #if 0 case 24: // O Old Volume /* ? */ note->effect = FX_VOLUMESLIDE; note->param = 0; break; #endif default: //log_appendf(2, " Pattern %d, row %d: effect %d %02X", // pat, row, e, note->param); effwarn |= (e > 32) ? 1 : (1 << (e - 1)); note->effect = FX_UNIMPLEMENTED; break; } } } return effwarn; } /* --------------------------------------------------------------------------------------------------------- */ int fmt_okt_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint8_t tag[8]; unsigned int readflags = 0; uint16_t w; // temp for reading int plen = 0; // how many positions in the orderlist are valid int npat = 0; // next pattern to read int nsmp = 1; // next sample (data, not header) int pat, sh, sd, e; // iterators (pattern, sample header, sample data, effect warnings int nchn = 0; // how many channels does this song use? size_t patseek[MAX_PATTERNS] = {0}; size_t smpseek[MAX_SAMPLES + 1] = {0}; // where the sample's data starts uint32_t smpsize[MAX_SAMPLES + 2] = {0}; // data size (one element bigger to simplify loop condition) uint32_t smpflag[MAX_SAMPLES + 1] = {0}; // bit width uint32_t effwarn = 0; // effect warning mask slurp_read(fp, tag, 8); if (memcmp(tag, "OKTASONG", 8) != 0) return LOAD_UNSUPPORTED; while (!slurp_eof(fp)) { uint32_t blklen; // length of this block size_t nextpos; // ... and start of next one slurp_read(fp, tag, 4); slurp_read(fp, &blklen, 4); blklen = bswapBE32(blklen); nextpos = slurp_tell(fp) + blklen; switch (OKT_BLOCK(tag[0], tag[1], tag[2], tag[3])) { case OKT_BLK_CMOD: if (!(readflags & OKT_HAS_CMOD)) { readflags |= OKT_HAS_CMOD; nchn = okt_read_cmod(song, fp); } break; case OKT_BLK_SAMP: if (!(readflags & OKT_HAS_SAMP)) { readflags |= OKT_HAS_SAMP; okt_read_samp(song, fp, blklen, smpflag); } break; case OKT_BLK_SPEE: if (!(readflags & OKT_HAS_SPEE)) { readflags |= OKT_HAS_SPEE; slurp_read(fp, &w, 2); w = bswapBE16(w); song->initial_speed = CLAMP(w, 1, 255); song->initial_tempo = 125; } break; case OKT_BLK_SLEN: // Don't care. break; case OKT_BLK_PLEN: if (!(readflags & OKT_HAS_PLEN)) { readflags |= OKT_HAS_PLEN; slurp_read(fp, &w, 2); plen = bswapBE16(w); } break; case OKT_BLK_PATT: if (!(readflags & OKT_HAS_PATT)) { readflags |= OKT_HAS_PATT; slurp_read(fp, song->orderlist, MIN(blklen, MAX_ORDERS)); } break; case OKT_BLK_PBOD: /* Need the channel count (in CMOD) in order to read these */ if (npat < MAX_PATTERNS) { if (blklen > 0) patseek[npat] = slurp_tell(fp); npat++; } break; case OKT_BLK_SBOD: if (nsmp < MAX_SAMPLES) { smpseek[nsmp] = slurp_tell(fp); smpsize[nsmp] = blklen; if (smpsize[nsmp]) nsmp++; } break; default: //log_appendf(4, " Warning: Unknown block of type '%c%c%c%c' at 0x%lx", // tag[0], tag[1], tag[2], tag[3], fp->pos - 8); break; } if (slurp_seek(fp, nextpos, SEEK_SET) != 0) { log_appendf(4, " Warning: Failed to seek (file truncated?)"); break; } } if ((readflags & (OKT_HAS_CMOD | OKT_HAS_SPEE)) != (OKT_HAS_CMOD | OKT_HAS_SPEE)) return LOAD_FORMAT_ERROR; if (!(lflags & LOAD_NOPATTERNS)) { for (pat = 0; pat < npat; pat++) { slurp_seek(fp, patseek[pat], SEEK_SET); effwarn |= okt_read_pbod(song, fp, nchn, pat); } if (effwarn) { if (effwarn & 1) log_appendf(4, " Warning: Out-of-range effects (junk data?)"); for (e = 2; e <= 32; e++) { if (effwarn & (1 << (e - 1))) { log_appendf(4, " Warning: Unimplemented effect %cxx", e + (e < 10 ? '0' : ('A' - 10))); } } } } if (!(lflags & LOAD_NOSAMPLES)) { for (sh = sd = 1; sh < MAX_SAMPLES && smpsize[sd]; sh++) { song_sample_t *ssmp = song->samples + sh; if (!ssmp->length) continue; if (ssmp->length != smpsize[sd]) { log_appendf(4, " Warning: Sample %d: header/data size mismatch (%" PRIu32 "/%" PRIu32 ")", sh, ssmp->length, smpsize[sd]); ssmp->length = MIN(smpsize[sd], ssmp->length); } slurp_seek(fp, smpseek[sd], SEEK_SET); csf_read_sample(ssmp, SF_BE | SF_M | SF_PCMS | smpflag[sh], fp); sd++; } // Make sure there's nothing weird going on for (; sh < MAX_SAMPLES; sh++) { if (song->samples[sh].length) { log_appendf(4, " Warning: Sample %d: file truncated", sh); song->samples[sh].length = 0; } } } song->pan_separation = 64; memset(song->orderlist + plen, ORDER_LAST, MAX(0, MAX_ORDERS - plen)); strcpy(song->tracker_id, "Amiga Oktalyzer"); return LOAD_SUCCESS; } schismtracker-20250313/fmt/pat.c000066400000000000000000000214071476471630300163520ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "fmt.h" #include "mem.h" #include "it.h" #include "song.h" #include "player/sndfile.h" /* --------------------------------------------------------------------- */ struct GF1PatchHeader { uint8_t sig[8]; // "GF1PATCH" uint8_t ver[4]; // "100\0" or "110\0" uint8_t id[10]; // "ID#000002\0" char desc[60]; // Discription (in ASCII) [sic] uint8_t insnum; // To some patch makers, 0 means 1 [what?] uint8_t voicenum; // Voices (Always 14?) uint8_t channum; // Channels uint16_t waveforms; uint16_t mastervol; // 0-127 [then why is it 16-bit? ugh] uint32_t datasize; //uint8_t reserved1[36]; uint16_t insID; // Instrument ID [0..0xFFFF] [?] char insname[16]; // Instrument name (in ASCII) uint32_t inssize; // Instrument size uint8_t layers; uint8_t reserved2[40]; uint8_t layerduplicate; uint8_t layer; uint32_t layersize; uint8_t smpnum; //uint8_t reserved3[40]; }; static int read_pat_header(struct GF1PatchHeader *hdr, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) { return 0; } } while (0) READ_VALUE(sig); READ_VALUE(ver); READ_VALUE(id); READ_VALUE(desc); READ_VALUE(insnum); READ_VALUE(voicenum); READ_VALUE(channum); READ_VALUE(waveforms); READ_VALUE(mastervol); READ_VALUE(datasize); slurp_seek(fp, 36, SEEK_CUR); // reserved READ_VALUE(insID); READ_VALUE(insname); READ_VALUE(inssize); READ_VALUE(layers); slurp_seek(fp, 40, SEEK_CUR); // reserved READ_VALUE(layerduplicate); READ_VALUE(layer); READ_VALUE(layersize); READ_VALUE(smpnum); slurp_seek(fp, 40, SEEK_CUR); // reserved #undef READ_VALUE if ((memcmp(hdr->sig, "GF1PATCH", 8)) || (memcmp(hdr->ver, "110\0", 4) && memcmp(hdr->ver, "100\0", 4)) || (memcmp(hdr->id, "ID#000002\0", 10))) return 0; hdr->waveforms = bswapLE16(hdr->waveforms); hdr->mastervol = bswapLE16(hdr->mastervol); hdr->datasize = bswapLE32(hdr->datasize); hdr->insID = bswapLE16(hdr->insID); hdr->inssize = bswapLE32(hdr->inssize); hdr->layersize = bswapLE32(hdr->layersize); return 1; } struct GF1PatchSampleHeader { char wavename[7]; // Wave name (in ASCII) uint8_t fractions; // bits 0-3 loop start frac / 4-7 loop end frac uint32_t samplesize; // Sample data size (s) uint32_t loopstart; uint32_t loopend; uint16_t samplerate; uint32_t lofreq; // Low frequency uint32_t hifreq; // High frequency uint32_t rtfreq; // Root frequency uint16_t tune; // Tune (Always 1, not used anymore) uint8_t panning; // Panning (L=0 -> R=15) uint8_t envelopes[12]; uint8_t trem_speed, trem_rate, trem_depth; uint8_t vib_speed, vib_rate, vib_depth; uint8_t smpmode; // bit mask: 16, unsigned, loop, pingpong, reverse, sustain, envelope, clamped release uint16_t scalefreq; // Scale frequency uint16_t scalefac; // Scale factor [0..2048] (1024 is normal) //uint8_t reserved[36]; }; static int read_pat_sample_header(struct GF1PatchSampleHeader *hdr, slurp_t *fp) { #define READ_VALUE(name) \ do { if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) { return 0; } } while (0) READ_VALUE(wavename); READ_VALUE(fractions); READ_VALUE(samplesize); READ_VALUE(loopstart); READ_VALUE(loopend); READ_VALUE(samplerate); READ_VALUE(lofreq); READ_VALUE(hifreq); READ_VALUE(rtfreq); READ_VALUE(tune); READ_VALUE(panning); READ_VALUE(envelopes); READ_VALUE(trem_speed); READ_VALUE(trem_rate); READ_VALUE(trem_depth); READ_VALUE(vib_speed); READ_VALUE(vib_rate); READ_VALUE(vib_depth); READ_VALUE(smpmode); READ_VALUE(scalefreq); READ_VALUE(scalefac); slurp_seek(fp, 36, SEEK_CUR); // reserved #undef READ_VALUE hdr->samplesize = bswapLE32(hdr->samplesize); hdr->loopstart = bswapLE32(hdr->loopstart); hdr->loopend = bswapLE32(hdr->loopend); hdr->samplerate = bswapLE16(hdr->samplerate); hdr->lofreq = bswapLE32(hdr->lofreq); hdr->hifreq = bswapLE32(hdr->hifreq); hdr->rtfreq = bswapLE32(hdr->rtfreq); hdr->tune = bswapLE16(hdr->tune); hdr->scalefreq = bswapLE16(hdr->scalefac); return 1; } /* --------------------------------------------------------------------- */ static int gusfreq(unsigned int freq) { unsigned int scale_table[109] = { /*C-0..B-*/ /* Octave 0 */ 16351, 17323, 18354, 19445, 20601, 21826, 23124, 24499, 25956, 27500, 29135, 30867, /* Octave 1 */ 32703, 34647, 36708, 38890, 41203, 43653, 46249, 48999, 51913, 54999, 58270, 61735, /* Octave 2 */ 65406, 69295, 73416, 77781, 82406, 87306, 92498, 97998, 103826, 109999, 116540, 123470, /* Octave 3 */ 130812, 138591, 146832, 155563, 164813, 174614, 184997, 195997, 207652, 219999, 233081, 246941, /* Octave 4 */ 261625, 277182, 293664, 311126, 329627, 349228, 369994, 391995, 415304, 440000, 466163, 493883, /* Octave 5 */ 523251, 554365, 587329, 622254, 659255, 698456, 739989, 783991, 830609, 880000, 932328, 987767, /* Octave 6 */ 1046503, 1108731, 1174660, 1244509, 1318511, 1396914, 1479979, 1567983, 1661220, 1760002, 1864657, 1975536, /* Octave 7 */ 2093007, 2217464, 2349321, 2489019, 2637024, 2793830, 2959960, 3135968, 3322443, 3520006, 3729316, 3951073, /* Octave 8 */ 4186073, 4434930, 4698645, 4978041, 5274051, 5587663, 5919922, 6271939, 6644889, 7040015, 7458636, 7902150, 0xFFFFFFFF, }; int no; for (no = 0; scale_table[no] != 0xFFFFFFFF; no++) { if (scale_table[no] <= freq && scale_table[no + 1] >= freq) { return no - 12; } } return 4 * 12; } /* --------------------------------------------------------------------- */ int fmt_pat_read_info(dmoz_file_t *file, slurp_t *fp) { struct GF1PatchHeader hdr; if (slurp_read(fp, &hdr, sizeof(hdr)) != sizeof(hdr)) return 0; if ((memcmp(hdr.sig, "GF1PATCH", 8) != 0) || (memcmp(hdr.ver, "110\0", 4) != 0 && memcmp(hdr.ver, "100\0", 4) != 0) || (memcmp(hdr.id, "ID#000002\0", 10) != 0)) return 0; file->description = "Gravis Patch File"; file->title = strn_dup(hdr.insname, 16); file->type = TYPE_INST_OTHER; return 1; } int fmt_pat_load_instrument(slurp_t *fp, int slot) { struct GF1PatchHeader header; struct GF1PatchSampleHeader gfsamp; struct instrumentloader ii; song_instrument_t *g; song_sample_t *smp; unsigned int rs; int lo, hi, tmp, i, nsamp, n; if (!slot) return 0; if (!read_pat_header(&header, fp)) return 0; g = instrument_loader_init(&ii, slot); memcpy(g->name, header.insname, 16); g->name[15] = '\0'; nsamp = CLAMP(header.smpnum, 1, 16); for (i = 0; i < 120; i++) { g->sample_map[i] = 0; g->note_map[i] = i + 1; } for (i = 0; i < nsamp; i++) { if (!read_pat_sample_header(&gfsamp, fp)) return 0; n = instrument_loader_sample(&ii, i + 1); smp = song_get_sample(n); lo = CLAMP(gusfreq(gfsamp.lofreq), 0, 95); hi = CLAMP(gusfreq(gfsamp.hifreq), 0, 95); if (lo > hi) { tmp = lo; lo = hi; hi = tmp; } for (; lo < hi; lo++) { g->sample_map[lo + 12] = n; } if (gfsamp.smpmode & 1) { gfsamp.samplesize >>= 1; gfsamp.loopstart >>= 1; gfsamp.loopend >>= 1; } smp->length = gfsamp.samplesize; smp->loop_start = smp->sustain_start = gfsamp.loopstart; smp->loop_end = smp->sustain_end = gfsamp.loopend; smp->c5speed = gfsamp.samplerate; smp->flags = 0; rs = SF_M | SF_LE; // channels; endianness rs |= (gfsamp.smpmode & 1) ? SF_16 : SF_8; // bit width rs |= (gfsamp.smpmode & 2) ? SF_PCMU : SF_PCMS; // encoding if (gfsamp.smpmode & 32) { if (gfsamp.smpmode & 4) smp->flags |= CHN_SUSTAINLOOP; if (gfsamp.smpmode & 8) smp->flags |= CHN_PINGPONGSUSTAIN; } else { if (gfsamp.smpmode & 4) smp->flags |= CHN_LOOP; if (gfsamp.smpmode & 8) smp->flags |= CHN_PINGPONGLOOP; } memcpy(smp->filename, gfsamp.wavename, 7); smp->filename[8] = '\0'; strcpy(smp->name, smp->filename); smp->vib_speed = gfsamp.vib_speed; smp->vib_rate = gfsamp.vib_rate; smp->vib_depth = gfsamp.vib_depth; csf_read_sample(smp, rs, fp); } return 1; } schismtracker-20250313/fmt/raw.c000066400000000000000000000033731476471630300163610ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "fmt.h" #include /* --------------------------------------------------------------------- */ // Impulse Tracker handles raw sample data as unsigned, EXCEPT when saving a 16-bit sample as raw. int fmt_raw_load_sample(slurp_t *fp, song_sample_t *smp) { size_t len = slurp_length(fp); smp->c5speed = 8363; smp->volume = 64 * 4; smp->global_volume = 64; smp->length = MIN(len, 1u << 22); /* max of 4MB */ csf_read_sample(smp, SF_LE | SF_8 | SF_PCMU | SF_M, fp); return 1; } int fmt_raw_save_sample(disko_t *fp, song_sample_t *smp) { csf_write_sample(fp, smp, SF_LE | ((smp->flags & CHN_16BIT) ? SF_16 | SF_PCMS : SF_8 | SF_PCMU) | ((smp->flags & CHN_STEREO) ? SF_SI : SF_M), UINT32_MAX); return SAVE_SUCCESS; } schismtracker-20250313/fmt/s3i.c000066400000000000000000000157431476471630300162720ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "fmt.h" #include "mem.h" #include "song.h" #include "player/sndfile.h" enum { S3I_TYPE_NONE = 0, S3I_TYPE_PCM = 1, S3I_TYPE_ADLIB = 2, }; enum { S3I_PCM_FLAG_LOOP = 0x01, S3I_PCM_FLAG_STEREO = 0x02, S3I_PCM_FLAG_16BIT = 0x04, }; /* TODO: s3m.c should use this */ static int load_s3i_sample(slurp_t *fp, song_sample_t *smp, int with_data) { unsigned char magic[4]; uint32_t dw; if (slurp_length(fp) < 0x50) return 0; slurp_seek(fp, 0x4C, SEEK_SET); if (slurp_read(fp, magic, sizeof(magic)) != sizeof(magic)) return 0; if (memcmp(magic, "SCRS", 4) && memcmp(magic, "SCRI", 4)) return 0; slurp_seek(fp, 0x00, SEEK_SET); int type = slurp_getc(fp); if (type != S3I_TYPE_PCM && type != S3I_TYPE_ADLIB) return 0; slurp_seek(fp, 0x1F, SEEK_SET); int flags = slurp_getc(fp); if (flags < 0) return 0; slurp_seek(fp, 0x1C, SEEK_SET); smp->flags = 0; smp->global_volume = 64; smp->volume = slurp_getc(fp) * 4; /* mphack */ slurp_seek(fp, 0x14, SEEK_SET); if (slurp_read(fp, &dw, sizeof(dw)) != sizeof(dw)) return 0; smp->loop_start = bswapLE32(dw); if (slurp_read(fp, &dw, sizeof(dw)) != sizeof(dw)) return 0; smp->loop_end = bswapLE32(dw); slurp_seek(fp, 4, SEEK_CUR); if (slurp_read(fp, &dw, sizeof(dw)) != sizeof(dw)) return 0; smp->c5speed = bswapLE32(dw); slurp_seek(fp, 0x01, SEEK_SET); slurp_read(fp, smp->filename, MIN(sizeof(smp->filename) - 1, 12)); slurp_seek(fp, 0x30, SEEK_SET); slurp_read(fp, smp->name, MIN(sizeof(smp->name) - 1, 28)); if (type == S3I_TYPE_PCM) { int bytes_per_sample = (flags & S3I_PCM_FLAG_STEREO) ? 2 : 1; slurp_seek(fp, 0x10, SEEK_SET); if (slurp_read(fp, &dw, sizeof(dw)) != sizeof(dw)) return 0; smp->length = bswapLE32(dw); if (slurp_length(fp) < 0x50 + smp->length * bytes_per_sample) return 0; /* convert flags */ if (flags & S3I_PCM_FLAG_LOOP) smp->flags |= CHN_LOOP; if (flags & S3I_PCM_FLAG_STEREO) smp->flags |= CHN_STEREO; if (flags & S3I_PCM_FLAG_16BIT) smp->flags |= CHN_16BIT; if (with_data) { int format = SF_M | SF_LE; // endianness; channels format |= (smp->flags & CHN_16BIT) ? (SF_16 | SF_PCMS) : (SF_8 | SF_PCMU); // bits; encoding slurp_seek(fp, 0x50, SEEK_SET); csf_read_sample(smp, format, fp); } } else if (type == S3I_TYPE_ADLIB) { smp->flags |= CHN_ADLIB; smp->flags &= ~(CHN_LOOP|CHN_16BIT); slurp_seek(fp, 0x10, SEEK_SET); if (slurp_read(fp, smp->adlib_bytes, sizeof(smp->adlib_bytes)) != sizeof(smp->adlib_bytes)) return 0; // dumb hackaround that ought to some day be fixed: smp->length = 1; smp->data = csf_allocate_sample(1); } return 1; } int fmt_s3i_read_info(dmoz_file_t *file, slurp_t *fp) { song_sample_t smp; if (!load_s3i_sample(fp, &smp, 0)) return 0; file->smp_length = smp.length; file->smp_flags = smp.flags; file->smp_defvol = smp.volume; file->smp_gblvol = smp.global_volume; file->smp_loop_start = smp.loop_start; file->smp_loop_end = smp.loop_end; file->smp_speed = smp.c5speed; file->smp_filename = strn_dup(smp.filename, 12); file->description = "Scream Tracker Sample"; file->title = strn_dup(smp.name, 25); file->type = TYPE_SAMPLE_EXTD | TYPE_INST_OTHER; return 1; } int fmt_s3i_load_sample(slurp_t *fp, song_sample_t *smp) { // what the crap? return load_s3i_sample(fp, smp, 1); } /* ---------------------------------------------------- */ struct s3i_header { uint8_t type; char filename[12]; union { struct { uint8_t memseg[3]; uint32_t length; uint32_t loop_start; uint32_t loop_end; } pcm; struct { //uint8_t zero[3]; uint8_t data[12]; } admel; } spec; uint8_t vol; uint8_t x; // "dsk" for adlib uint8_t pack; // 0 uint8_t flags; // 1=loop 2=stereo 4=16-bit / zero for adlib uint32_t c5speed; uint8_t junk[12]; char name[28]; char tag[4]; // SCRS/SCRI/whatever }; void s3i_write_header(disko_t *fp, song_sample_t *smp, uint32_t sdata) { struct s3i_header hdr = {0}; int n; if (smp->flags & CHN_ADLIB) { hdr.type = S3I_TYPE_ADLIB; memcpy(hdr.spec.admel.data, smp->adlib_bytes, 11); memcpy(hdr.tag, "SCRI", 4); } else if (smp->data != NULL) { hdr.type = S3I_TYPE_PCM; hdr.spec.pcm.memseg[0] = (sdata >> 20) & 0xff; hdr.spec.pcm.memseg[1] = (sdata >> 4) & 0xff; hdr.spec.pcm.memseg[2] = (sdata >> 12) & 0xff; hdr.spec.pcm.length = bswapLE32(smp->length); hdr.spec.pcm.loop_start = bswapLE32(smp->loop_start); hdr.spec.pcm.loop_end = bswapLE32(smp->loop_end); hdr.flags = ((smp->flags & CHN_LOOP) ? 1 : 0) | ((smp->flags & CHN_STEREO) ? 2 : 0) | ((smp->flags & CHN_16BIT) ? 4 : 0); memcpy(hdr.tag, "SCRS", 4); } else { hdr.type = S3I_TYPE_NONE; } memcpy(hdr.filename, smp->filename, 12); hdr.vol = smp->volume / 4; //mphack hdr.c5speed = bswapLE32(smp->c5speed); for (n = 25; n >= 0; n--) if ((smp->name[n] ? smp->name[n] : 32) != 32) break; for (; n >= 0; n--) hdr.name[n] = smp->name[n] ? smp->name[n] : 32; #define WRITE_VALUE(x) do { disko_write(fp, &hdr.x, sizeof(hdr.x)); } while (0) WRITE_VALUE(type); WRITE_VALUE(filename); switch (hdr.type) { case S3I_TYPE_ADLIB: disko_seek(fp, 3, SEEK_CUR); WRITE_VALUE(spec.admel.data); break; case S3I_TYPE_PCM: WRITE_VALUE(spec.pcm.memseg); WRITE_VALUE(spec.pcm.length); WRITE_VALUE(spec.pcm.loop_start); WRITE_VALUE(spec.pcm.loop_end); break; default: case S3I_TYPE_NONE: disko_seek(fp, 15, SEEK_CUR); break; } WRITE_VALUE(vol); WRITE_VALUE(x); WRITE_VALUE(pack); WRITE_VALUE(flags); WRITE_VALUE(c5speed); disko_seek(fp, 12, SEEK_CUR); WRITE_VALUE(name); WRITE_VALUE(tag); // SCRS/SCRI/whatever #undef WRITE_VALUE } int fmt_s3i_save_sample(disko_t *fp, song_sample_t *smp) { s3i_write_header(fp, smp, 0); if (smp->flags & CHN_ADLIB) { return SAVE_SUCCESS; // already done } else if (smp->data != NULL) { uint32_t format = SF_M | SF_LE; // endianness; channels format |= (smp->flags & CHN_16BIT) ? (SF_16 | SF_PCMS) : (SF_8 | SF_PCMU); // bits; encoding csf_write_sample(fp, smp, format, UINT32_MAX); } return SAVE_SUCCESS; } schismtracker-20250313/fmt/s3m.c000066400000000000000000001052571476471630300162760ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "version.h" #include "mem.h" #include "player/sndfile.h" #include "disko.h" #include "log.h" /* --------------------------------------------------------------------- */ int fmt_s3m_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char magic[4], title[27]; slurp_seek(fp, 44, SEEK_SET); if (slurp_read(fp, magic, sizeof(magic)) != sizeof(magic) || memcmp(magic, "SCRM", sizeof(magic))) return 0; slurp_rewind(fp); if (slurp_read(fp, title, sizeof(title)) != sizeof(title)) return 0; file->description = "Scream Tracker 3"; /*file->extension = str_dup("s3m");*/ file->title = strn_dup((const char *)title, sizeof(title)); file->type = TYPE_MODULE_S3M; return 1; } /* --------------------------------------------------------------------------------------------------------- */ enum { S3I_TYPE_NONE = 0, S3I_TYPE_PCM = 1, S3I_TYPE_ADMEL = 2, S3I_TYPE_CONTROL = 0xff, // only internally used for saving }; /* misc flags for loader (internal) */ #define S3M_UNSIGNED 1 #define S3M_CHANPAN 2 // the FC byte static int s3m_import_edittime(song_t *song, uint16_t trkvers, uint32_t reserved32) { if (song->histlen) return 0; // ? song->histlen = 1; song->history = mem_calloc(1, sizeof(*song->history)); uint32_t runtime = it_decode_edit_timer(trkvers, reserved32); song->history[0].runtime = dos_time_to_ms(runtime); return 1; } int fmt_s3m_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint16_t nsmp, nord, npat; int misc = S3M_UNSIGNED | S3M_CHANPAN; // temporary flags, these are both generally true int n; song_note_t *note; /* junk variables for reading stuff into */ uint16_t tmp; uint8_t c; uint32_t tmplong; uint8_t b[4]; uint8_t channel_types[32]; /* parapointers */ uint16_t para_smp[MAX_SAMPLES]; uint16_t para_pat[MAX_PATTERNS]; uint32_t para_sdata[MAX_SAMPLES] = { 0 }; uint32_t smp_flags[MAX_SAMPLES] = { 0 }; song_sample_t *sample; uint16_t trkvers; uint16_t flags; uint16_t special; uint8_t reserved[8]; uint16_t reserved16low; // low 16 bits of version info uint16_t reserved16high; // high 16 bits of version info uint32_t reserved32; // Impulse Tracker edit time uint32_t adlib = 0; // bitset uint16_t gus_addresses = 0; uint8_t mix_volume; /* detect very old modplug tracker */ char any_samples = 0; int uc; const char *tid = NULL; /* check the tag */ slurp_seek(fp, 44, SEEK_SET); slurp_read(fp, b, 4); if (memcmp(b, "SCRM", 4) != 0) return LOAD_UNSUPPORTED; /* read the title */ slurp_rewind(fp); slurp_read(fp, song->title, 25); song->title[25] = 0; /* skip the last three bytes of the title, the supposed-to-be-0x1a byte, the tracker ID, and the two useless reserved bytes */ slurp_seek(fp, 7, SEEK_CUR); slurp_read(fp, &nord, 2); slurp_read(fp, &nsmp, 2); slurp_read(fp, &npat, 2); nord = bswapLE16(nord); nsmp = bswapLE16(nsmp); npat = bswapLE16(npat); if (nord > MAX_ORDERS || nsmp > MAX_SAMPLES || npat > MAX_PATTERNS) return LOAD_FORMAT_ERROR; song->flags = SONG_ITOLDEFFECTS; slurp_read(fp, &flags, 2); /* flags (don't really care) */ flags = bswapLE16(flags); slurp_read(fp, &trkvers, 2); trkvers = bswapLE16(trkvers); slurp_read(fp, &tmp, 2); /* file format info */ if (tmp == bswapLE16(1)) misc &= ~S3M_UNSIGNED; /* signed samples (ancient s3m) */ slurp_seek(fp, 4, SEEK_CUR); /* skip the tag */ song->initial_global_volume = slurp_getc(fp) << 1; // In the case of invalid data, ST3 uses the speed/tempo value that's set in the player prior to // loading the song, but that's just crazy. song->initial_speed = slurp_getc(fp); if (!song->initial_speed) song->initial_speed = 6; song->initial_tempo = slurp_getc(fp); if (song->initial_tempo <= 32) { // (Yes, 32 is ignored by Scream Tracker.) song->initial_tempo = 125; } mix_volume = song->mixing_volume = slurp_getc(fp); if (song->mixing_volume & 0x80) { song->mixing_volume ^= 0x80; } else { song->flags |= SONG_NOSTEREO; } uc = slurp_getc(fp); /* ultraclick removal (useless) */ if (slurp_getc(fp) != 0xfc) misc &= ~S3M_CHANPAN; /* stored pan values */ slurp_read(fp, &reserved, 8); memcpy(&reserved16low, reserved, 2); memcpy(&reserved32, reserved + 2, 4); memcpy(&reserved16high, reserved + 6, 2); reserved16low = bswapLE16(reserved16low); // schism & openmpt version info reserved32 = bswapLE32(reserved32); // impulse tracker edit timer reserved16high = bswapLE16(reserved16high); // high bits of schism version info slurp_read(fp, &special, 2); // field not used by st3 special = bswapLE16(special); /* channel settings */ slurp_read(fp, channel_types, 32); for (n = 0; n < 32; n++) { /* Channel 'type': 0xFF is a disabled channel, which shows up as (--) in ST3. Any channel with the high bit set is muted. 00-07 are L1-L8, 08-0F are R1-R8, 10-18 are adlib channels A1-A9. Hacking at a file with a hex editor shows some perhaps partially-implemented stuff: types 19-1D show up in ST3 as AB, AS, AT, AC, and AH; 20-2D are the same as 10-1D except with 'B' insted of 'A'. None of these appear to produce any sound output, apart from 19 which plays adlib instruments briefly before cutting them. (Weird!) Also, 1E/1F and 2E/2F display as "??"; and pressing 'A' on a disabled (--) channel will change its type to 1F. Values past 2F seem to display bits of the UI like the copyright and help, strange! These out-of-range channel types will almost certainly hang or crash ST3 or produce other strange behavior. Simply put, don't do it. :) */ c = channel_types[n]; if (c & 0x80) { song->channels[n].flags |= CHN_MUTE; // ST3 doesn't even play effects in muted channels -- throw them out? c &= ~0x80; } if (c < 0x08) { // L1-L8 (panned to 3 in ST3) song->channels[n].panning = 14; } else if (c < 0x10) { // R1-R8 (panned to C in ST3) song->channels[n].panning = 50; } else if (c < 0x19) { // A1-A9 song->channels[n].panning = 32; adlib |= 1 << n; } else { // Disabled 0xff/0x7f, or broken song->channels[n].panning = 32; song->channels[n].flags |= CHN_MUTE; } song->channels[n].volume = 64; } for (; n < 64; n++) { song->channels[n].panning = 32; song->channels[n].volume = 64; song->channels[n].flags = CHN_MUTE; } // Schism Tracker before 2018-11-12 played AdLib instruments louder than ST3. Compensate by lowering the sample mixing volume. if (adlib && trkvers >= 0x4000 && trkvers < 0x4D33) { song->mixing_volume = song->mixing_volume * 2274 / 4096; } /* orderlist */ slurp_read(fp, song->orderlist, nord); memset(song->orderlist + nord, ORDER_LAST, MAX_ORDERS - nord); /* load the parapointers */ slurp_read(fp, para_smp, 2 * nsmp); slurp_read(fp, para_pat, 2 * npat); /* default pannings */ if (misc & S3M_CHANPAN) { for (n = 0; n < 32; n++) { int pan = slurp_getc(fp); if ((pan & 0x20) && (!(adlib & (1 << n)) || trkvers > 0x1320)) song->channels[n].panning = ((pan & 0xf) * 64) / 15; } } //mphack - fix the pannings for (n = 0; n < 64; n++) song->channels[n].panning *= 4; /* samples */ for (n = 0, sample = song->samples + 1; n < nsmp; n++, sample++) { uint8_t type; slurp_seek(fp, bswapLE16(para_smp[n]) << 4, SEEK_SET); type = slurp_getc(fp); slurp_read(fp, sample->filename, 12); sample->filename[12] = 0; slurp_read(fp, b, 3); // data pointer for pcm, irrelevant otherwise switch (type) { case S3I_TYPE_PCM: para_sdata[n] = b[1] | (b[2] << 8) | (b[0] << 16); slurp_read(fp, &tmplong, 4); sample->length = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); sample->loop_start = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); sample->loop_end = bswapLE32(tmplong); sample->volume = slurp_getc(fp) * 4; //mphack slurp_getc(fp); /* unused byte */ slurp_getc(fp); /* packing info (never used) */ c = slurp_getc(fp); /* flags */ if (c & 1) sample->flags |= CHN_LOOP; smp_flags[n] = (SF_LE | ((misc & S3M_UNSIGNED) ? SF_PCMU : SF_PCMS) | ((c & 4) ? SF_16 : SF_8) | ((c & 2) ? SF_SS : SF_M)); if (sample->length) any_samples = 1; break; default: //printf("s3m: mystery-meat sample type %d\n", type); case S3I_TYPE_NONE: slurp_seek(fp, 12, SEEK_CUR); sample->volume = slurp_getc(fp) * 4; //mphack slurp_seek(fp, 3, SEEK_CUR); break; case S3I_TYPE_ADMEL: slurp_read(fp, sample->adlib_bytes, 12); sample->volume = slurp_getc(fp) * 4; //mphack // next byte is "dsk", what is that? slurp_seek(fp, 3, SEEK_CUR); sample->flags |= CHN_ADLIB; // dumb hackaround that ought to some day be fixed: sample->length = 1; sample->data = csf_allocate_sample(1); break; } slurp_read(fp, &tmplong, 4); sample->c5speed = bswapLE32(tmplong); if (type == S3I_TYPE_ADMEL) { if (sample->c5speed < 1000 || sample->c5speed > 0xFFFF) { sample->c5speed = 8363; } } slurp_seek(fp, 4, SEEK_CUR); /* unused space */ int16_t gus_address; slurp_read(fp, &gus_address, 2); gus_addresses |= bswapLE16(gus_address); slurp_seek(fp, 6, SEEK_CUR); slurp_read(fp, sample->name, 25); sample->name[25] = 0; sample->vib_type = 0; sample->vib_rate = 0; sample->vib_depth = 0; sample->vib_speed = 0; sample->global_volume = 64; } /* sample data */ if (!(lflags & LOAD_NOSAMPLES)) { for (n = 0, sample = song->samples + 1; n < nsmp; n++, sample++) { if (!sample->length || (sample->flags & CHN_ADLIB)) continue; slurp_seek(fp, para_sdata[n] << 4, SEEK_SET); csf_read_sample(sample, smp_flags[n], fp); } } // Mixing volume is not used with the GUS driver; relevant for PCM + OPL tracks if (gus_addresses > 1) song->mixing_volume = 48; if (!(lflags & LOAD_NOPATTERNS)) { for (n = 0; n < npat; n++) { int row = 0; long end; para_pat[n] = bswapLE16(para_pat[n]); if (!para_pat[n]) continue; slurp_seek(fp, para_pat[n] << 4, SEEK_SET); slurp_read(fp, &tmp, 2); end = (para_pat[n] << 4) + bswapLE16(tmp) + 2; song->patterns[n] = csf_allocate_pattern(64); while (row < 64 && slurp_tell(fp) < end) { int mask = slurp_getc(fp); uint8_t chn = (mask & 31); if (mask == EOF) { log_appendf(4, " Warning: Pattern %d: file truncated", n); break; } if (!mask) { /* done with the row */ row++; continue; } note = song->patterns[n] + 64 * row + chn; if (mask & 32) { /* note/instrument */ note->note = slurp_getc(fp); note->instrument = slurp_getc(fp); //if (note->instrument > 99) // note->instrument = 0; switch (note->note) { default: // Note; hi=oct, lo=note note->note = (note->note >> 4) * 12 + (note->note & 0xf) + 13; break; case 255: note->note = NOTE_NONE; break; case 254: note->note = (adlib & (1 << chn)) ? NOTE_OFF : NOTE_CUT; break; } } if (mask & 64) { /* volume */ note->voleffect = VOLFX_VOLUME; note->volparam = slurp_getc(fp); if (note->volparam == 255) { note->voleffect = VOLFX_NONE; note->volparam = 0; } else if (note->volparam >= 128 && note->volparam <= 192) { // ModPlug (or was there any earlier tracker using this command?) note->voleffect = VOLFX_PANNING; note->volparam -= 128; } else if (note->volparam > 64) { // some weirdly saved s3m? note->volparam = 64; } } if (mask & 128) { note->effect = slurp_getc(fp); note->param = slurp_getc(fp); csf_import_s3m_effect(note, 0); if (note->effect == FX_SPECIAL) { // mimic ST3's SD0/SC0 behavior if (note->param == 0xd0) { note->note = NOTE_NONE; note->instrument = 0; note->voleffect = VOLFX_NONE; note->volparam = 0; note->effect = FX_NONE; note->param = 0; } else if (note->param == 0xc0) { note->effect = FX_NONE; note->param = 0; } else if ((note->param & 0xf0) == 0xa0) { // Convert the old messy SoundBlaster stereo control command (or an approximation of it, anyway) uint8_t ctype = channel_types[chn] & 0x7f; if (gus_addresses > 1 || ctype >= 0x10) note->effect = FX_NONE; else if (note->param == 0xa0 || note->param == 0xa2) // Normal panning note->param = (ctype & 8) ? 0x8c : 0x83; else if (note->param == 0xa1 || note->param == 0xa3) // Swap left / right channel note->param = (ctype & 8) ? 0x83 : 0x8c; else if (note->param <= 0xa7) // Center note->param = 0x88; else note->effect = FX_NONE; } } } /* ... next note, same row */ } } } /* MPT identifies as ST3.20 in the trkvers field, but it puts zeroes for the 'special' field, only ever * sets flags 0x10 and 0x40, writes multiples of 16 orders, always saves channel pannings, and writes * zero into the ultraclick removal field. (ST3.2x always puts either 16, 24, or 32 there, older versions put 0). * Velvet Studio also pretends to be ST3, but writes zeroes for 'special'. ultraclick, and flags, and * does NOT save channel pannings. Also, it writes a fairly recognizable LRRL pattern for the channels, * but I'm not checking that. (yet?) */ if (trkvers == 0x1320) { if (!memcmp(reserved, "SCLUB2.0", 8)) { tid = "Sound Club 2"; } else if (special == 0 && uc == 0 && (flags & ~0x50) == 0 && misc == (S3M_UNSIGNED | S3M_CHANPAN) && (nord % 16) == 0) { /* from OpenMPT: * MPT 1.0 alpha5 doesn't set the stereo flag, but MPT 1.0 alpha6 does. */ tid = ((mix_volume & 0x80) != 0) ? "ModPlug Tracker / OpenMPT 1.17" : "ModPlug Tracker 1.0 alpha"; } else if (special == 0 && uc == 0 && flags == 0 && misc == S3M_UNSIGNED) { if (song->initial_global_volume == 128 && mix_volume == 48) tid = "PlayerPRO"; else // Always stereo tid = "Velvet Studio"; } else if(special == 0 && uc == 0 && flags == 8 && misc == S3M_UNSIGNED) { tid = "Impulse Tracker < 1.03"; // Not sure if 1.02 saves like this as I don't have it } else if (uc != 16 && uc != 24 && uc != 32) { // sure isn't scream tracker tid = "Unknown tracker"; } } if (!tid) { switch (trkvers >> 12) { case 0: if (trkvers == 0x0208) strcpy(song->tracker_id, "Akord"); break; case 1: if (gus_addresses > 1) tid = "Scream Tracker %" PRIu8 ".%02" PRIx8 " (GUS)"; else if (gus_addresses == 1 || !any_samples || trkvers == 0x1300) tid = "Scream Tracker %" PRIu8 ".%02" PRIx8 " (SB)"; // could also be a GUS file with a single sample else { strcpy(song->tracker_id, "Unknown tracker"); if (trkvers == 0x1301 && uc == 0) { if (!(flags & ~0x50) && (mix_volume & 0x80) && (misc & S3M_CHANPAN)) strcpy(song->tracker_id, "UNMO3"); else if (!flags && song->initial_global_volume == 96 && mix_volume == 176 && song->initial_tempo == 150 && !(misc & S3M_CHANPAN)) strcpy(song->tracker_id, "deMODifier"); // SoundSmith to S3M converter else if (!flags && song->initial_global_volume == 128 && song->initial_speed == 6 && song->initial_tempo == 125 && !(misc & S3M_CHANPAN)) strcpy(song->tracker_id, "Kosmic To-S3M"); // MTM to S3M converter by Zab/Kosmic } } break; case 2: if (trkvers == 0x2013) // PlayerPRO on Intel forgets to byte-swap the tracker ID bytes strcpy(song->tracker_id, "PlayerPRO"); else tid = "Imago Orpheus %" PRIu8 ".%02" PRIx8; break; case 3: if (trkvers <= 0x3214) { tid = "Impulse Tracker %" PRIu8 ".%02" PRIx8; } else if (trkvers == 0x3320) { tid = "Impulse Tracker 1.03"; // Could also be 1.02, maybe? I don't have that one } else if(trkvers >= 0x3215 && trkvers <= 0x3217) { tid = NULL; const char *versions[] = { "1-2", "3", "4-5" }; sprintf(song->tracker_id, "Impulse Tracker 2.14p%s", versions[trkvers - 0x3215]); } if (trkvers >= 0x3207 && trkvers <= 0x3217 && reserved32) s3m_import_edittime(song, trkvers, reserved32); break; case 4: if (trkvers == 0x4100) { strcpy(song->tracker_id, "BeRoTracker"); } else { uint32_t full_version = (((uint32_t)reserved16high) << 16) | (reserved16low); strcpy(song->tracker_id, "Schism Tracker "); ver_decode_cwtv(trkvers, full_version, song->tracker_id + strlen(song->tracker_id)); if (trkvers == 0x4fff && full_version >= ver_mktime(2024, 11, 24)) s3m_import_edittime(song, 0x0000, reserved32); } break; case 5: /* from OpenMPT src: * * Liquid Tracker's ID clashes with OpenMPT's. * OpenMPT started writing full version information with OpenMPT 1.29 and later changed the ultraClicks value from 8 to 16. * Liquid Tracker writes an ultraClicks value of 16. * So we assume that a file was saved with Liquid Tracker if the reserved fields are 0 and ultraClicks is 16. */ if ((trkvers >> 8) == 0x57) { tid = "NESMusa %" PRIu8 ".%" PRIX8; /* tool by Bisquit */ } else if (!reserved16low && uc == 16 && channel_types[1] != 1) { tid = "Liquid Tracker %" PRIu8 ".%" PRIX8; } else if (trkvers == 0x5447) { strcpy(song->tracker_id, "Graoumf Tracker"); } else if (trkvers >= 0x5129 && reserved16low) { /* e.x. 1.29.01.12 <-> 0x1290112 */ const uint32_t ver = (((trkvers & 0xfff) << 16) | reserved16low); sprintf(song->tracker_id, "OpenMPT %" PRIu32 ".%02" PRIX32 ".%02" PRIX32 ".%02" PRIX32, ver >> 24, (ver >> 16) & 0xFF, (ver >> 8) & 0xFF, (ver) & 0xFF); if (ver >= UINT32_C(0x01320031)) s3m_import_edittime(song, 0x0000, reserved32); } else { tid = "OpenMPT %" PRIu8 ".%02" PRIX8; } break; case 6: strcpy(song->tracker_id, "BeRoTracker"); break; case 7: strcpy(song->tracker_id, "CreamTracker"); break; case 12: if (trkvers == 0xCA00) strcpy(song->tracker_id, "Camoto"); break; default: break; } } if (tid) sprintf(song->tracker_id, tid, (uint8_t)((trkvers & 0xf00) >> 8), (uint8_t)(trkvers & 0xff)); // if (ferror(fp)) { // return LOAD_FILE_ERROR; // } /* done! */ return LOAD_SUCCESS; } /* --------------------------------------------------------------------------------------------------------- */ /* IT displays some of these slightly differently most notably "Only 100 patterns supported" which doesn't follow the general pattern, and the channel limits (IT entirely refuses to save data in channels > 16 at all). Also, the Adlib and sample count warnings of course do not exist in IT at all. */ enum { WARN_MAXPATTERNS, WARN_CHANNELVOL, WARN_LINEARSLIDES, WARN_SAMPLEVOL, WARN_LOOPS, WARN_SAMPLEVIB, WARN_INSTRUMENTS, WARN_PATTERNLEN, WARN_MAXCHANNELS, WARN_MAXPCM, WARN_MAXADLIB, WARN_PCMADLIBMIX, WARN_MUTED, WARN_NOTERANGE, WARN_VOLEFFECTS, WARN_MAXSAMPLES, MAX_WARN }; static const char *s3m_warnings[] = { [WARN_MAXPATTERNS] = "Over 100 patterns", [WARN_CHANNELVOL] = "Channel volumes", [WARN_LINEARSLIDES] = "Linear slides", [WARN_SAMPLEVOL] = "Sample volumes", [WARN_LOOPS] = "Sustain and Ping Pong loops", [WARN_SAMPLEVIB] = "Sample vibrato", [WARN_INSTRUMENTS] = "Instrument functions", [WARN_PATTERNLEN] = "Pattern lengths other than 64 rows", [WARN_MAXCHANNELS] = "Data outside 32 channels", [WARN_MAXPCM] = "Over 16 PCM channels", [WARN_MAXADLIB] = "Over 9 Adlib channels", [WARN_PCMADLIBMIX] = "Adlib and PCM in the same channel", [WARN_MUTED] = "Data in muted channels", [WARN_NOTERANGE] = "Notes outside the range C-1 to B-8", [WARN_VOLEFFECTS] = "Extended volume column effects", [WARN_MAXSAMPLES] = "Over 99 samples", [MAX_WARN] = NULL }; struct s3m_header { char title[28]; char eof; // 0x1a char type; // 16 uint16_t ordnum, smpnum, patnum; // ordnum should be even uint16_t flags, cwtv, ffi; // 0, 0x4nnn, 2 for unsigned char scrm[4]; // "SCRM" uint8_t gv, is, it, mv, uc, dp; // gv is half range of IT, uc should be 8/12/16, dp is 252 uint16_t reserved; // extended version information is stored here uint32_t reserved2; // Impulse Tracker hides its edit timer here uint16_t reserved3; // high bits of extended version information }; static int write_s3m_header(const struct s3m_header *hdr, disko_t *fp) { #define WRITE_VALUE(x) do { disko_write(fp, &hdr->x, sizeof(hdr->x)); } while (0) WRITE_VALUE(title); WRITE_VALUE(eof); WRITE_VALUE(type); disko_seek(fp, 2, SEEK_CUR); WRITE_VALUE(ordnum); WRITE_VALUE(smpnum); WRITE_VALUE(patnum); WRITE_VALUE(flags); WRITE_VALUE(cwtv); WRITE_VALUE(ffi); WRITE_VALUE(scrm); WRITE_VALUE(gv); WRITE_VALUE(is); WRITE_VALUE(it); WRITE_VALUE(mv); WRITE_VALUE(uc); WRITE_VALUE(dp); WRITE_VALUE(reserved); WRITE_VALUE(reserved2); WRITE_VALUE(reserved3); disko_seek(fp, 2, SEEK_CUR); #undef WRITE_VALUE return 1; } static int write_s3m_pattern(disko_t *fp, song_t *song, int pat, uint8_t *chantypes, uint16_t *para_pat) { int64_t start, end; uint8_t b, type; uint16_t w; int row, rows, chan; song_note_t out, *note; uint32_t warn = 0; if (csf_pattern_is_empty(song, pat)) { // easy! para_pat[pat] = 0; return 0; } if (song->pattern_size[pat] != 64) { warn |= 1 << WARN_PATTERNLEN; } rows = MIN(64, song->pattern_size[pat]); disko_align(fp, 16); start = disko_tell(fp); para_pat[pat] = bswapLE16(start >> 4); // write a bogus length for now... disko_putc(fp, 0); disko_putc(fp, 0); note = song->patterns[pat]; for (row = 0; row < rows; row++) { for (chan = 0; chan < 32; chan++, note++) { out = *note; b = 0; if (song->channels[chan].flags & CHN_MUTE) { if (out.instrument || out.effect) { /* most players do in fact play data on muted channels, but that's wrong since ST3 doesn't. to eschew the problem, we'll just drop the data when writing (and complain) */ warn |= 1 << WARN_MUTED; continue; } } else if ((song->flags & SONG_INSTRUMENTMODE) && out.instrument && NOTE_IS_NOTE(out.note)) { song_instrument_t *ins = song->instruments[out.instrument]; if (ins) { out.instrument = ins->sample_map[out.note - 1]; out.note = ins->note_map[out.note - 1]; } } /* Translate notes */ if ((out.note > 0 && out.note <= 12) || (out.note >= 109 && out.note <= 120)) { // Octave 0/9 (or higher?) warn |= 1 << WARN_NOTERANGE; out.note = 255; } else if (out.note > 12 && out.note < 109) { // C-1 through B-8 out.note -= 13; out.note = (out.note % 12) + ((out.note / 12) << 4); b |= 32; } else if (out.note == NOTE_CUT || out.note == NOTE_OFF) { // IT translates === to ^^^ when writing S3M files // (and more importantly, we load ^^^ as === in adlib-channels) out.note = 254; b |= 32; } else { // Nothing (or garbage values) out.note = 255; } if (out.instrument != 0) { if (song->samples[out.instrument].flags & CHN_ADLIB) type = S3I_TYPE_ADMEL; else if (song->samples[out.instrument].data != NULL) type = S3I_TYPE_PCM; else type = S3I_TYPE_NONE; if (type != S3I_TYPE_NONE) { if (chantypes[chan] == S3I_TYPE_NONE || chantypes[chan] == S3I_TYPE_CONTROL) { chantypes[chan] = type; } else if (chantypes[chan] != type) { warn |= 1 << WARN_PCMADLIBMIX; } } b |= 32; } switch (out.voleffect) { case VOLFX_NONE: break; case VOLFX_VOLUME: b |= 64; break; default: warn |= 1 << WARN_VOLEFFECTS; break; } csf_export_s3m_effect(&out.effect, &out.param, 0); if (out.effect || out.param) { b |= 128; } // If there's an effect, don't allow the channel to be muted in the S3M file. // S3I_TYPE_CONTROL is an internal value indicating that the channel should get a // "junk" value (such as B1) that doesn't actually play. if (chantypes[chan] == S3I_TYPE_NONE && out.effect) { chantypes[chan] = S3I_TYPE_CONTROL; } if (!b) continue; b |= chan; // write it! disko_putc(fp, b); if (b & 32) { disko_putc(fp, out.note); disko_putc(fp, out.instrument); } if (b & 64) { disko_putc(fp, out.volparam); } if (b & 128) { disko_putc(fp, out.effect); disko_putc(fp, out.param); } } if (!(warn & (1 << WARN_MAXCHANNELS))) { /* if the flag is already set, there's no point in continuing to search for stuff */ for (; chan < MAX_CHANNELS; chan++, note++) { if (!csf_note_is_empty(note)) { warn |= 1 << WARN_MAXCHANNELS; break; } } } note += MAX_CHANNELS - chan; disko_putc(fp, 0); /* end of row */ } /* if the pattern was < 64 rows, pad it */ for (; row < 64; row++) { disko_putc(fp, 0); } /* hop back and write the real length */ end = disko_tell(fp); disko_seek(fp, start, SEEK_SET); w = bswapLE16(end - start); disko_write(fp, &w, 2); disko_seek(fp, end, SEEK_SET); return warn; } static int fixup_chantypes(song_channel_t *channels, uint8_t *chantypes) { int warn = 0; int npcm = 0, nadmel = 0, nctrl = 0; int pcm = 0, admel = 0x10, junk = 0x20; int n; /* Value Label Value Label (20-2F => 10-1F with B instead of A) 00 L1 10 A1 01 L2 11 A2 02 L3 12 A3 03 L4 13 A4 04 L5 14 A5 05 L6 15 A6 06 L7 16 A7 07 L8 17 A8 08 R1 18 A9 09 R2 19 AB 0A R3 1A AS 0B R4 1B AT 0C R5 1C AC 0D R6 1D AH 0E R7 1E ?? 0F R8 1F ?? For the L1 R1 L2 R2 pattern: ((n << 3) | (n >> 1)) & 0xf PCM * 16 = 00-0F Adlib * 9 = 10-18 Remaining = 20-2F (nothing will be played, but effects are still processed) Try to make as many of the "control" channels PCM as possible. */ for (n = 0; n < 32; n++) { switch (chantypes[n]) { case S3I_TYPE_PCM: npcm++; break; case S3I_TYPE_ADMEL: nadmel++; break; case S3I_TYPE_CONTROL: nctrl++; break; } } if (npcm > 16) { npcm = 16; warn |= 1 << WARN_MAXPCM; } if (nadmel > 9) { nadmel = 9; warn |= 1 << WARN_MAXADLIB; } for (n = 0; n < 32; n++) { switch (chantypes[n]) { case S3I_TYPE_PCM: if (pcm <= 0x0f) chantypes[n] = pcm++; else chantypes[n] = junk++; break; case S3I_TYPE_ADMEL: if (admel <= 0x18) chantypes[n] = admel++; else chantypes[n] = junk++; break; case S3I_TYPE_NONE: if (channels[n].flags & CHN_MUTE) { chantypes[n] = 255; // (--) break; } // else fall through - attempt to honor unmuted channels. default: if (npcm < 16) { chantypes[n] = ((pcm << 3) | (pcm >> 1)) & 0xf; pcm++; npcm++; } else if (nadmel < 9) { chantypes[n] = admel++; nadmel++; } else if (chantypes[n] == S3I_TYPE_NONE) { chantypes[n] = 255; // (--) } else { chantypes[n] = junk++; // give up } break; } if (junk > 0x2f) junk = 0x19; // "overflow" to the adlib drums } return warn; } int fmt_s3m_save_song(disko_t *fp, song_t *song) { struct s3m_header hdr = {0}; int nord, nsmp, npat; int n; song_sample_t *smp; int64_t smphead_pos; /* where to write the sample headers */ int64_t patptr_pos; /* where to write pattern pointers */ int64_t pos; /* temp */ uint16_t w; uint16_t para_pat[MAX_PATTERNS]; uint32_t para_sdata[MAX_SAMPLES]; uint8_t chantypes[32]; uint32_t warn = 0; if (song->flags & SONG_INSTRUMENTMODE) warn |= 1 << WARN_INSTRUMENTS; if (song->flags & SONG_LINEARSLIDES) warn |= 1 << WARN_LINEARSLIDES; nord = csf_get_num_orders(song) + 1; // TECH.DOC says orders should be even. In practice it doesn't appear to matter (in fact IT doesn't // make the number even), but if the spec says... if (nord & 1) nord++; // see note in IT writer -- shouldn't clamp here, but can't save more than we're willing to load nord = CLAMP(nord, 2, MAX_ORDERS); nsmp = csf_get_num_samples(song); // ST3 always saves one sample if (!nsmp) nsmp = 1; if (nsmp > 99) { nsmp = 99; warn |= 1 << WARN_MAXSAMPLES; } npat = csf_get_num_patterns(song); // ST3 always saves one pattern if (!npat) npat = 1; if (npat > 100) { npat = 100; warn |= 1 << WARN_MAXPATTERNS; } log_appendf(5, " %d orders, %d samples, %d patterns", nord, nsmp, npat); /* this is used to identify what kinds of samples (pcm or adlib) are used on which channels, since it actually matters to st3 */ memset(chantypes, S3I_TYPE_NONE, 32); memcpy(hdr.title, song->title, 25); hdr.eof = 0x1a; hdr.type = 16; // ST3 module (what else is there?!) hdr.ordnum = bswapLE16(nord); hdr.smpnum = bswapLE16(nsmp); hdr.patnum = bswapLE16(npat); hdr.flags = 0; hdr.cwtv = bswapLE16(0x4000 | ver_cwtv); hdr.ffi = bswapLE16(2); // format version; 1 = signed samples, 2 = unsigned memcpy(hdr.scrm, "SCRM", 4); hdr.gv = song->initial_global_volume / 2; hdr.is = song->initial_speed; hdr.it = song->initial_tempo; /* .S3M "MasterVolume" only supports 0x10 .. 0x7f, * if we save 0x80, the schism max volume, it becomes zero in S3M. * I didn't test to see what ScreamTracker does if a value below 0x10 * is loaded into it, but its UI prevents setting below 0x10. * Just enforce both bounds here. */ hdr.mv = MIN(MAX(0x10, song->mixing_volume), 0x7f); if (!(song->flags & SONG_NOSTEREO)) hdr.mv |= 128; hdr.uc = 16; // ultraclick (the "Waste GUS channels" option) hdr.dp = 252; hdr.reserved = bswapLE16(ver_reserved); hdr.reserved3 = bswapLE16(ver_reserved >> 16); /* Save the edit time in the reserved header, where * Impulse Tracker also conveniently stores it */ hdr.reserved2 = 0; for (size_t j = 0; j < song->histlen; j++) hdr.reserved2 += ms_to_dos_time(song->history[j].runtime); // 32-bit DOS tick count (tick = 1/18.2 second; 54945 * 18.2 = 999999 which is Close Enough) hdr.reserved2 += it_get_song_elapsed_dos_time(song); hdr.reserved2 = bswapLE32(hdr.reserved2); /* The sample data parapointers are 24+4 bits, whereas pattern data and sample headers are only 16+4 bits -- so while the sample data can be written up to 268 MB within the file (starting at 0xffffff0), the pattern data and sample headers are restricted to the first 1 MB (starting at 0xffff0). In effect, this practically requires the sample data to be written last in the file, as it is entirely possible (and quite easy, even) to write more than 1 MB of sample data in a file. The "practical standard order" listed in TECH.DOC is sample headers, patterns, then sample data. Thus: File header Channel settings Orderlist Sample header pointers Pattern pointers Default pannings Sample headers Pattern data Sample data */ write_s3m_header(&hdr, fp); // header disko_seek(fp, 32, SEEK_CUR); // channel settings (skipped for now) disko_write(fp, song->orderlist, nord); // orderlist /* sample header pointers because the sample headers are fixed-size, it's possible to determine where they will be written right now: the first sample will be at the start of the next 16-byte block after all the header stuff, and each subsequent sample starts 0x50 bytes after the previous one. */ pos = smphead_pos = (0x60 + nord + 2 * (nsmp + npat) + 32 + 15) & ~15; for (n = 0; n < nsmp; n++) { w = bswapLE16(pos >> 4); disko_write(fp, &w, 2); pos += 0x50; } /* pattern pointers can't figure these out ahead of time since the patterns are variable length, but do make a note of where to seek later in order to write the values... */ patptr_pos = disko_tell(fp); disko_seek(fp, 2 * npat, SEEK_CUR); /* channel pannings ... also not yet! */ disko_seek(fp, 32, SEEK_CUR); /* skip ahead past the sample headers as well (what a pain) */ disko_seek(fp, 0x50 * nsmp, SEEK_CUR); /* patterns -- finally omg we can write some data */ for (n = 0; n < npat; n++) warn |= write_s3m_pattern(fp, song, n, chantypes, para_pat); /* sample data */ for (n = 0, smp = song->samples + 1; n < nsmp; n++, smp++) { if ((smp->flags & CHN_ADLIB) || smp->data == NULL) { para_sdata[n] = 0; continue; } disko_align(fp, 16); para_sdata[n] = disko_tell(fp); csf_write_sample(fp, smp, SF_LE | SF_PCMU | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8) | ((smp->flags & CHN_STEREO) ? SF_SS : SF_M), UINT32_MAX); } /* now that we're done adding stuff to the end of the file, go back and rewrite everything we skipped earlier.... */ // channel types warn |= fixup_chantypes(song->channels, chantypes); disko_seek(fp, 0x40, SEEK_SET); disko_write(fp, chantypes, 32); // pattern pointers disko_seek(fp, patptr_pos, SEEK_SET); disko_write(fp, para_pat, 2 * npat); /* channel panning settings come after the pattern pointers... This produces somewhat left-biased panning values, but this is what IT does, and more importantly it's stable across repeated load/saves. (Hopefully.) Technically it is possible to squeeze out two "extra" values for hard-left and hard-right panning by writing a "disabled" pan value (omit the 0x20 bit, so it's presented as a dot in ST3) -- but some trackers, including MPT and older Schism Tracker versions, load such values as 16/48 rather than 0/64, so this would result in potentially inconsistent behavior and is therefore undesirable. */ for (n = 0; n < 32; n++) { song_channel_t *ch = song->channels + n; uint8_t b; if (ch->volume != 64) warn |= 1 << WARN_CHANNELVOL; //mphack: channel panning range b = ((chantypes[n] & 0x7f) < 0x20) ? ((ch->panning * 15) / 64) : 0; disko_putc(fp, b); } /* sample headers */ disko_seek(fp, smphead_pos, SEEK_SET); for (n = 0, smp = song->samples + 1; n < nsmp; n++, smp++) { if (smp->global_volume != 64) { warn |= 1 << WARN_SAMPLEVOL; } if ((smp->flags & (CHN_LOOP | CHN_PINGPONGLOOP)) == (CHN_LOOP | CHN_PINGPONGLOOP) || (smp->flags & CHN_SUSTAINLOOP)) { warn |= 1 << WARN_LOOPS; } if (smp->vib_depth != 0) { warn |= 1 << WARN_SAMPLEVIB; } s3i_write_header(fp, smp, para_sdata[n]); } /* announce all the things we broke */ for (n = 0; n < MAX_WARN; n++) { if (warn & (1 << n)) log_appendf(4, " Warning: %s unsupported in S3M format", s3m_warnings[n]); } return SAVE_SUCCESS; } schismtracker-20250313/fmt/sfx.c000066400000000000000000000211711476471630300163640ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "log.h" #include "mem.h" #include "player/sndfile.h" /* --------------------------------------------------------------------------------------------------------- */ /* None of the sfx files on Modland are of the 31-instrument type that xmp recognizes. However, there are a number of 31-instrument files with a different tag, under "SoundFX 2". */ static struct sfxfmt { size_t tagpos; const char tag[4]; int nsmp; int dunno; const char *id; } sfxfmts[] = { {124, "SO31", 31, 4, "SoundFX 2"}, {124, "SONG", 31, 0, "SoundFX 2 (?)"}, { 60, "SONG", 15, 0, "SoundFX"}, { 0, "" , 0, 0, NULL}, }; int fmt_sfx_read_info(dmoz_file_t *file, slurp_t *fp) { int n; unsigned char tag[4]; for (n = 0; sfxfmts[n].nsmp; n++) { slurp_seek(fp, sfxfmts[n].tagpos, SEEK_SET); if (slurp_read(fp, tag, sizeof(tag)) == sizeof(tag) && !memcmp(tag, sfxfmts[n].tag, 4)) { file->description = sfxfmts[n].id; /*file->extension = str_dup("sfx");*/ file->title = str_dup(""); // whatever file->type = TYPE_MODULE_MOD; return 1; } } return 0; } /* --------------------------------------------------------------------------------------------------------- */ /* Loader taken mostly from XMP. Why did I write a loader for such an obscure format? That is, besides the fact that neither Modplug nor Mikmod support SFX (and for good reason; it's a particularly dumb format) */ int fmt_sfx_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint8_t tag[4]; int nord, npat, pat, chan, restart, nsmp = 0, n; uint32_t smpsize[31]; uint16_t tmp; song_note_t *note; song_sample_t *sample; unsigned int effwarn = 0; struct sfxfmt *fmt = sfxfmts; do { slurp_seek(fp, fmt->tagpos, SEEK_SET); slurp_read(fp, tag, 4); if (memcmp(tag, fmt->tag, 4) == 0) { nsmp = fmt->nsmp; break; } fmt++; } while (fmt->nsmp); if (!nsmp) return LOAD_UNSUPPORTED; slurp_rewind(fp); slurp_read(fp, smpsize, 4 * nsmp); slurp_seek(fp, 4, SEEK_CUR); /* the tag again */ slurp_read(fp, &tmp, 2); if (!tmp) return LOAD_UNSUPPORTED; // erf tmp = 14565 * 122 / bswapBE16(tmp); song->initial_tempo = CLAMP(tmp, 31, 255); slurp_seek(fp, 14, SEEK_CUR); /* unknown bytes (reserved?) - see below */ if (lflags & LOAD_NOSAMPLES) { slurp_seek(fp, 30 * nsmp, SEEK_CUR); } else { for (n = 0, sample = song->samples + 1; n < nsmp; n++, sample++) { slurp_read(fp, sample->name, 22); sample->name[22] = 0; slurp_read(fp, &tmp, 2); /* seems to be half the sample size, minus two bytes? */ tmp = bswapBE16(tmp); sample->length = bswapBE32(smpsize[n]); song->samples[n].c5speed = MOD_FINETUNE(slurp_getc(fp)); // ? sample->volume = slurp_getc(fp); if (sample->volume > 64) sample->volume = 64; sample->volume *= 4; //mphack sample->global_volume = 64; slurp_read(fp, &tmp, 2); sample->loop_start = bswapBE16(tmp); slurp_read(fp, &tmp, 2); tmp = bswapBE16(tmp) * 2; /* loop length */ if (tmp > 2) { sample->loop_end = sample->loop_start + tmp; sample->flags |= CHN_LOOP; } else { sample->loop_start = sample->loop_end = 0; } } } /* pattern/order stuff */ nord = slurp_getc(fp); nord = MIN(nord, 127); restart = slurp_getc(fp); slurp_read(fp, song->orderlist, nord); slurp_seek(fp, 128 - nord, SEEK_CUR); npat = 0; for (n = 0; n < nord; n++) { if (song->orderlist[n] > npat) npat = song->orderlist[n]; } npat++; /* Not sure what this is about, but skipping a few bytes here seems to make SO31's load right. (they all seem to have zero here) */ slurp_seek(fp, fmt->dunno, SEEK_CUR); if (lflags & LOAD_NOPATTERNS) { slurp_seek(fp, npat * 1024, SEEK_CUR); } else { for (pat = 0; pat < npat; pat++) { note = song->patterns[pat] = csf_allocate_pattern(64); song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; for (n = 0; n < 64; n++, note += 60) { for (chan = 0; chan < 4; chan++, note++) { uint8_t p[4]; slurp_read(fp, p, 4); mod_import_note(p, note); /* Note events starting with FF all seem to be special in some way: bytes apparent use example file on modland ----- ------------ ----------------------- FF FE note cut 1st intro.sfx FF FD unknown! another world (intro).sfx FF FC pattern break orbit wanderer.sfx2 */ if (p[0] == 0xff) { switch (p[1]) { case 0xfc: note->note = NOTE_NONE; note->instrument = 0; // stuff a C00 in channel 5 note[4 - chan].effect = FX_PATTERNBREAK; break; case 0xfe: note->note = NOTE_CUT; note->instrument = 0; break; } } switch (note->effect) { case 0: break; case 1: /* arpeggio */ note->effect = FX_ARPEGGIO; break; case 2: /* pitch bend */ if (note->param >> 4) { note->effect = FX_PORTAMENTODOWN; note->param >>= 4; } else if (note->param & 0xf) { note->effect = FX_PORTAMENTOUP; note->param &= 0xf; } else { note->effect = 0; } break; case 5: /* volume up */ note->effect = FX_VOLUMESLIDE; note->param = (note->param & 0xf) << 4; break; case 6: /* set volume */ if (note->param > 64) note->param = 64; note->voleffect = VOLFX_VOLUME; note->volparam = 64 - note->param; note->effect = 0; note->param = 0; break; case 3: /* LED on (wtf!) */ case 4: /* LED off (ditto) */ case 7: /* set step up */ case 8: /* set step down */ default: effwarn |= (1 << note->effect); note->effect = FX_UNIMPLEMENTED; break; } } } } for (n = 0; n < 16; n++) { if (effwarn & (1 << n)) log_appendf(4, " Warning: Unimplemented effect %Xxx", n); } if (restart < npat) csf_insert_restart_pos(song, restart); } /* sample data */ if (!(lflags & LOAD_NOSAMPLES)) { for (n = 0, sample = song->samples + 1; n < fmt->nsmp; n++, sample++) { if (sample->length <= 2) continue; csf_read_sample(sample, SF_8 | SF_LE | SF_PCMS | SF_M, fp); } } /* more header info */ song->flags = SONG_ITOLDEFFECTS | SONG_COMPATGXX; for (n = 0; n < 4; n++) song->channels[n].panning = PROTRACKER_PANNING(n); /* ??? */ for (; n < MAX_CHANNELS; n++) song->channels[n].flags = CHN_MUTE; strcpy(song->tracker_id, fmt->id); song->pan_separation = 64; // if (ferror(fp)) { // return LOAD_FILE_ERROR; // } /* done! */ return LOAD_SUCCESS; } /* most of modland's sfx files have all zeroes for those 14 "unknown" bytes, with the following exceptions: 64 00 00 00 00 00 00 00 00 00 00 00 00 00 d............. - unknown/antitrax.sfx 74 63 68 33 00 00 00 00 00 00 00 00 00 00 tch3.......... - unknown/axel f.sfx 61 6c 6b 00 00 00 00 00 00 00 00 00 00 00 alk........... Andreas Hommel/cyberblast-intro.sfx 21 00 00 00 00 00 00 00 00 00 00 00 00 00 !............. - unknown/dugger.sfx 00 00 00 00 00 0d 00 00 00 00 00 00 00 00 .............. Jean Baudlot/future wars - time travellers - dugger (title).sfx 00 00 00 00 00 00 00 00 0d 00 00 00 00 00 .............. Jean Baudlot/future wars - time travellers - escalator.sfx 6d 65 31 34 00 00 00 00 00 00 00 00 00 00 me14.......... - unknown/melodious.sfx 0d 0d 0d 53 46 58 56 31 2e 38 00 00 00 00 ...SFXV1.8.... AM-FM/sunday morning.sfx 61 6c 6b 00 00 00 00 00 00 00 00 00 00 00 alk........... - unknown/sunday morning.sfx 6f 67 00 00 00 00 00 00 00 00 00 00 00 00 og............ Philip Jespersen/supaplex.sfx 6e 74 20 73 6f 6e 67 00 00 00 00 00 00 00 nt song....... - unknown/sweety.sfx 61 6c 6b 00 00 00 00 00 00 00 00 00 00 00 alk........... - unknown/thrust.sfx */ schismtracker-20250313/fmt/stm.c000066400000000000000000000206071476471630300163720ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "mem.h" #include "player/sndfile.h" /* --------------------------------------------------------------------- */ int fmt_stm_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char title[20]; int what, type, version; int i; slurp_seek(fp, 28, SEEK_SET); what = slurp_getc(fp); type = slurp_getc(fp); version = slurp_getc(fp); /* data[29] is the type: 1 = song, 2 = module (with samples) */ if ((what != 0x1a && what != 0x02) || (type != 1 && type != 2) || version != 2) return 0; slurp_seek(fp, 20, SEEK_SET); for (i = 0; i < 8; i++) { /* the ID should be all safe ASCII */ int id = slurp_getc(fp); if (id < 0x20 || id > 0x7E) return 0; } slurp_rewind(fp); if (slurp_read(fp, title, sizeof(title)) != sizeof(title)) return 0; /* I used to check whether it was a 'song' or 'module' and set the description * accordingly, but it's fairly pointless information :) */ file->description = "Scream Tracker 2"; /*file->extension = str_dup("stm");*/ file->type = TYPE_MODULE_MOD; file->title = strn_dup((const char *)title, sizeof(title)); return 1; } /* --------------------------------------------------------------------- */ struct stm_sample { char name[12]; uint16_t pcmpara; // in the official documentation, this is misleadingly labelled reserved... uint16_t length, loop_start, loop_end; uint8_t volume; uint16_t c5speed; }; static int read_stm_sample(struct stm_sample *smp, slurp_t *fp) { #define READ_VALUE(name) \ if (slurp_read(fp, &smp->name, sizeof(smp->name)) != sizeof(smp->name)) return 0 READ_VALUE(name); slurp_seek(fp, 1, SEEK_CUR); // zero slurp_seek(fp, 1, SEEK_CUR); // inst_disk READ_VALUE(pcmpara); READ_VALUE(length); READ_VALUE(loop_start); READ_VALUE(loop_end); READ_VALUE(volume); slurp_seek(fp, 1, SEEK_CUR); // reserved READ_VALUE(c5speed); slurp_seek(fp, 4, SEEK_CUR); // morejunk slurp_seek(fp, 2, SEEK_CUR); // paragraphs (? what) #undef READ_VALUE return 1; } /* ST2 says at startup: "Remark: the user ID is encoded in over ten places around the file!" I wonder if this is interesting at all. */ static void load_stm_pattern(song_note_t *note, slurp_t *fp) { int row, chan; uint8_t v[4]; for (row = 0; row < 64; row++, note += 64 - 4) { for (chan = 0; chan < 4; chan++) { song_note_t* chan_note = note + chan; slurp_read(fp, v, 4); // mostly copied from modplug... if (v[0] < 251) chan_note->note = (v[0] >> 4) * 12 + (v[0] & 0xf) + 37; chan_note->instrument = v[1] >> 3; if (chan_note->instrument > 31) chan_note->instrument = 0; // oops never mind, that was crap chan_note->volparam = (v[1] & 0x7) + ((v[2] & 0xf0) >> 1); if (chan_note->volparam <= 64) chan_note->voleffect = VOLFX_VOLUME; else chan_note->volparam = 0; chan_note->param = v[3]; // easy! chan_note->effect = stm_effects[v[2] & 0x0f]; handle_stm_effects(chan_note); } for (chan = 0; chan < 4; chan++) { song_note_t* chan_note = note + chan; if (chan_note->effect == FX_SPEED) { uint32_t tempo = chan_note->param; chan_note->param >>= 4; handle_stm_tempo_pattern(note, tempo); } } note += chan; } } int fmt_stm_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { char id[8]; uint8_t tmp[4]; int npat, n; uint16_t para_sdata[MAX_SAMPLES] = { 0 }; slurp_seek(fp, 20, SEEK_SET); slurp_read(fp, id, 8); slurp_read(fp, tmp, 4); if (!( // this byte is *usually* guaranteed to be 0x1a, // however putup10.stm and putup11.stm are outliers // for some reason?... (tmp[0] == 0x1a || tmp[0] == 0x02) // from the doc: // 1 - song (contains no samples) // 2 - module (contains samples) // I'm not going to care about "songs". && tmp[1] == 2 // do version 1 STM's even exist?... && tmp[2] == 2 )) { return LOAD_UNSUPPORTED; } // check the file tag for printable ASCII for (n = 0; n < 8; n++) if (id[n] < 0x20 || id[n] > 0x7E) return LOAD_FORMAT_ERROR; // and the next two bytes are the tracker version. if (!memcmp(id, "!Scream!", 8)) // Unfortunately tools never differentiated themselves... if (tmp[3] > 20) // Future Crew chose to never increase their version numbers after 2.21 it seems! sprintf(song->tracker_id, "Scream Tracker 2.2+ or compatible"); else sprintf(song->tracker_id, "Scream Tracker %1d.%02d or compatible", CLAMP(tmp[2], 0, 9), CLAMP(tmp[3], 0, 99)); else if (!memcmp(id, "BMOD2STM", 8)) sprintf(song->tracker_id, "BMOD2STM"); else if (!memcmp(id, "WUZAMOD!", 8)) sprintf(song->tracker_id, "Wuzamod"); // once a MOD always a MOD else if (!memcmp(id, "SWavePro", 8)) sprintf(song->tracker_id, "SoundWave Pro %1d.%02d", CLAMP(tmp[2], 0, 9), CLAMP(tmp[3], 0, 99)); else if (!memcmp(id, "!Scrvrt!", 8)) sprintf(song->tracker_id, "Screamverter"); else sprintf(song->tracker_id, "Unknown"); slurp_seek(fp, 0, SEEK_SET); slurp_read(fp, song->title, 20); song->title[20] = '\0'; slurp_seek(fp, 12, SEEK_CUR); // skip the tag and stuff size_t tempo = slurp_getc(fp); if (tmp[3] < 21) { tempo = ((tempo / 10) << 4) + tempo % 10; } song->initial_speed = (tempo >> 4) ? (tempo >> 4) : 1; song->initial_tempo = convert_stm_tempo_to_bpm(tempo); npat = slurp_getc(fp); song->initial_global_volume = 2 * slurp_getc(fp); slurp_seek(fp, 13, SEEK_CUR); // junk if (npat > 64) return LOAD_FORMAT_ERROR; for (n = 1; n <= 31; n++) { struct stm_sample stmsmp; uint16_t blen; song_sample_t *sample = song->samples + n; if (!read_stm_sample(&stmsmp, fp)) return LOAD_FORMAT_ERROR; for (int i = 0; i < 12; i++) { if ((uint8_t)stmsmp.name[i] == 0xFF) stmsmp.name[i] = 0x20; } // the strncpy here is intentional -- ST2 doesn't show the '3' after the \0 bytes in the first // sample of pm_fract.stm, for example strncpy(sample->filename, stmsmp.name, 12); memcpy(sample->name, sample->filename, 12); blen = sample->length = bswapLE16(stmsmp.length); sample->loop_start = bswapLE16(stmsmp.loop_start); sample->loop_end = bswapLE16(stmsmp.loop_end); sample->c5speed = bswapLE16(stmsmp.c5speed); sample->volume = stmsmp.volume * 4; //mphack if (sample->loop_start < blen && sample->loop_end != 0xffff && sample->loop_start < sample->loop_end) { sample->flags |= CHN_LOOP; sample->loop_end = CLAMP(sample->loop_end, sample->loop_start, blen); } para_sdata[n] = bswapLE16(stmsmp.pcmpara); } int orderlist_size = tmp[3] ? 128 : 64; slurp_read(fp, song->orderlist, orderlist_size); for (n = 0; n < orderlist_size; n++) { if (song->orderlist[n] >= 64) song->orderlist[n] = ORDER_LAST; } if (lflags & LOAD_NOPATTERNS) { slurp_seek(fp, npat * 64 * 4 * 4, SEEK_CUR); } else { for (n = 0; n < npat; n++) { song->patterns[n] = csf_allocate_pattern(64); song->pattern_size[n] = song->pattern_alloc_size[n] = 64; load_stm_pattern(song->patterns[n], fp); } } if (!(lflags & LOAD_NOSAMPLES)) { for (n = 1; n <= 31; n++) { song_sample_t *sample = song->samples + n; if (sample->length < 3) { // Garbage? sample->length = 0; } else { slurp_seek(fp, para_sdata[n] << 4, SEEK_SET); csf_read_sample(sample, SF_LE | SF_PCMS | SF_8 | SF_M, fp); } } } for (n = 0; n < 4; n++) song->channels[n].panning = ((n & 1) ? 64 : 0) * 4; //mphack for (; n < 64; n++) song->channels[n].flags |= CHN_MUTE; song->pan_separation = 64; song->flags = SONG_ITOLDEFFECTS | SONG_COMPATGXX | SONG_NOSTEREO; return LOAD_SUCCESS; } schismtracker-20250313/fmt/stx.c000066400000000000000000000212211476471630300163760ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "mem.h" #include "player/sndfile.h" #include "disko.h" #include "log.h" /* --------------------------------------------------------------------- */ int fmt_stx_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char magic[4]; int i; slurp_seek(fp, 60, SEEK_SET); if (slurp_read(fp, magic, sizeof(magic)) != sizeof(magic) || memcmp(magic, "SCRM", sizeof(magic))) return 0; slurp_seek(fp, 20, SEEK_SET); for (i = 0; i < 8; i++) { int id = slurp_getc(fp); if (id < 0x20 || id > 0x7E) return 0; } unsigned char title[20]; slurp_rewind(fp); if (slurp_read(fp, title, sizeof(title)) != sizeof(title)) return 0; file->description = "ST Music Interface Kit"; /*file->extension = str_dup("stx");*/ file->title = strn_dup((const char *)title, sizeof(title)); file->type = TYPE_MODULE_MOD; return 1; } /* --------------------------------------------------------------------------------------------------------- */ enum { S3I_TYPE_NONE = 0, S3I_TYPE_PCM = 1, }; int fmt_stx_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { uint16_t nsmp, nord, npat; int n; song_note_t *note; /* junk variables for reading stuff into */ uint16_t tmp; uint8_t c; uint32_t tmplong; uint8_t b[4], b2[8]; /* parapointers */ uint16_t para_patptr; uint16_t para_smptr; uint16_t para_chnptr; // supposedly settings... uint16_t para_smp[MAX_SAMPLES]; uint16_t para_pat[MAX_PATTERNS]; uint32_t para_sdata[MAX_SAMPLES] = { 0 }; song_sample_t *sample; int subversion = 1; uint16_t first_pattern_size; uint16_t pattern_size; /* check the tag */ slurp_seek(fp, 20, SEEK_SET); slurp_read(fp, b2, 8); slurp_seek(fp, 60, SEEK_SET); slurp_read(fp, b, 4); for (n = 0; n < 8; n++) if (b2[n] < 0x20 || b2[n] > 0x7E) return LOAD_UNSUPPORTED; if (memcmp(b, "SCRM", 4) != 0) return LOAD_UNSUPPORTED; /* read the title */ slurp_rewind(fp); slurp_read(fp, song->title, 20); song->title[20] = 0; slurp_seek(fp, 8, SEEK_CUR); slurp_read(fp, &tmp, 2); first_pattern_size = bswapLE16(tmp); slurp_seek(fp, 2, SEEK_CUR); slurp_read(fp, &tmp, 2); para_patptr = bswapLE16(tmp); slurp_read(fp, &tmp, 2); para_smptr = bswapLE16(tmp); slurp_read(fp, &tmp, 2); para_chnptr = bswapLE16(tmp); slurp_seek(fp, 4, SEEK_CUR); song->initial_global_volume = slurp_getc(fp) << 1; int tempo = slurp_getc(fp); song->initial_speed = (tempo >> 4) ? (tempo >> 4) : 6; song->initial_tempo = convert_stm_tempo_to_bpm(tempo); slurp_seek(fp, 4, SEEK_CUR); slurp_read(fp, &npat, 2); slurp_read(fp, &nsmp, 2); slurp_read(fp, &nord, 2); nord = bswapLE16(nord); nsmp = bswapLE16(nsmp); // STX 1.0 modules sometimes have bugged sample counts... if (nsmp > 31) nsmp = 31; npat = bswapLE16(npat); if (nord > MAX_ORDERS || nsmp > MAX_SAMPLES || npat > MAX_PATTERNS) return LOAD_FORMAT_ERROR; song->flags = SONG_ITOLDEFFECTS | SONG_NOSTEREO; slurp_seek(fp, (para_chnptr << 4) + 32, SEEK_SET); /* orderlist */ for (n = 0; n < nord; n++) { song->orderlist[n] = slurp_getc(fp); slurp_seek(fp, 4, SEEK_CUR); } /* load the parapointers */ slurp_seek(fp, para_smptr << 4, SEEK_SET); slurp_read(fp, para_smp, 2 * nsmp); slurp_seek(fp, para_patptr << 4, SEEK_SET); slurp_read(fp, para_pat, 2 * npat); /* samples */ for (n = 0, sample = song->samples + 1; n < nsmp; n++, sample++) { uint8_t type; slurp_seek(fp, (para_smp[n]) << 4, SEEK_SET); type = slurp_getc(fp); slurp_read(fp, sample->filename, 12); sample->filename[12] = 0; for (int i = 0; i < 12; i++) { if ((uint8_t)sample->filename[i] == 0xFF) sample->filename[i] = 0x20; } slurp_read(fp, b, 3); // data pointer for pcm, irrelevant otherwise switch (type) { case S3I_TYPE_PCM: para_sdata[n] = b[1] | (b[2] << 8) | (b[0] << 16); slurp_read(fp, &tmplong, 4); sample->length = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); sample->loop_start = bswapLE32(tmplong); slurp_read(fp, &tmplong, 4); sample->loop_end = bswapLE32(tmplong); sample->volume = slurp_getc(fp) * 4; //mphack slurp_seek(fp, 2, SEEK_CUR); c = slurp_getc(fp); /* flags */ if (c & 1) sample->flags |= CHN_LOOP; break; default: case S3I_TYPE_NONE: slurp_seek(fp, 12, SEEK_CUR); sample->volume = slurp_getc(fp) * 4; //mphack slurp_seek(fp, 3, SEEK_CUR); break; } slurp_read(fp, &tmplong, 4); sample->c5speed = bswapLE32(tmplong); slurp_seek(fp, 12, SEEK_CUR); /* unused space */ slurp_read(fp, sample->name, 25); sample->name[25] = 0; for (int i = 0; i < 25; i++) { if ((uint8_t)sample->name[i] == 0xFF) sample->name[i] = 0x20; } sample->vib_type = 0; sample->vib_rate = 0; sample->vib_depth = 0; sample->vib_speed = 0; sample->global_volume = 64; } if (first_pattern_size != 0x1A) { slurp_seek(fp, (bswapLE16(para_pat[0]) << 4), SEEK_SET); // 1.0 files have pattern size before pattern data // which should match the header's specified size. slurp_read(fp, &tmp, 2); pattern_size = bswapLE16(tmp); // Amusingly, Purple Motion's "Future Brain" actually // specifies pattern size in the song header even though // the patterns themselves don't specify their size. if (pattern_size == first_pattern_size) subversion = 0; } if (!(lflags & LOAD_NOPATTERNS)) { for (n = 0; n < npat; n++) { int row = 0; para_pat[n] = bswapLE16(para_pat[n]); if (!para_pat[n]) continue; slurp_seek(fp, para_pat[n] << 4, SEEK_SET); if (!subversion) slurp_seek(fp, 2, SEEK_CUR); song->patterns[n] = csf_allocate_pattern(64); while (row < 64) { int mask = slurp_getc(fp); uint8_t chn = (mask & 31); if (mask == EOF) { log_appendf(4, " Warning: Pattern %d: file truncated", n); break; } if (!mask) { /* done with the row */ row++; continue; } note = song->patterns[n] + 64 * row + chn; if (mask & 32) { /* note/instrument */ note->note = slurp_getc(fp); note->instrument = slurp_getc(fp); //if (note->instrument > 99) // note->instrument = 0; switch (note->note) { default: // Note; hi=oct, lo=note note->note = ((note->note >> 4) + 2) * 12 + (note->note & 0xf) + 13; break; case 255: note->note = NOTE_NONE; break; case 254: note->note = NOTE_CUT; break; } } if (mask & 64) { /* volume */ note->voleffect = VOLFX_VOLUME; note->volparam = slurp_getc(fp); if (note->volparam == 255) { note->voleffect = VOLFX_NONE; note->volparam = 0; } else if (note->volparam > 64) { note->volparam = 64; } } if (mask & 128) { note->effect = stm_effects[slurp_getc(fp) & 0xf]; note->param = slurp_getc(fp); handle_stm_effects(note); } for (chn = 0; chn < 32; chn++) { song_note_t* chan_note = note + chn; if (chan_note->effect == FX_SPEED) { uint32_t param = chan_note->param; chan_note->param >>= 4; handle_stm_tempo_pattern(note, param); } } /* ... next note, same row */ } } } /* sample data */ if (!(lflags & LOAD_NOSAMPLES)) { for (n = 0, sample = song->samples + 1; n < nsmp; n++, sample++) { if (sample->length < 3) continue; slurp_seek(fp, para_sdata[n] << 4, SEEK_SET); csf_read_sample(sample, SF_LE | SF_PCMS | SF_8 | SF_M, fp); } } for (n = 0; n < 4; n++) song->channels[n].panning = ((n & 1) ? 64 : 0) * 4; //mphack for (; n < 64; n++) song->channels[n].flags |= CHN_MUTE; song->pan_separation = 64; sprintf(song->tracker_id, "ST Music Interface Kit (1.%d)", subversion); // if (ferror(fp)) { // return LOAD_FILE_ERROR; // } /* done! */ return LOAD_SUCCESS; } schismtracker-20250313/fmt/ult.c000066400000000000000000000253261476471630300163760ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "it.h" /* for get_effect_char */ #include "log.h" #include "mem.h" #include "player/sndfile.h" #include /* for pow */ /* --------------------------------------------------------------------- */ int fmt_ult_read_info(dmoz_file_t *file, slurp_t *fp) { unsigned char magic[14], title[32]; if (slurp_read(fp, magic, sizeof(magic)) != sizeof(magic) || memcmp(magic, "MAS_UTrack_V00", sizeof(magic))) return 0; slurp_seek(fp, 15, SEEK_SET); if (slurp_read(fp, title, sizeof(title)) != sizeof(title)) return 0; file->description = "UltraTracker Module"; file->type = TYPE_MODULE_S3M; /*file->extension = str_dup("ult");*/ file->title = strn_dup((const char *)title, sizeof(title)); return 1; } /* --------------------------------------------------------------------------------------------------------- */ enum { ULT_16BIT = 4, ULT_LOOP = 8, ULT_PINGPONGLOOP = 16, }; struct ult_sample { char name[32]; char filename[12]; uint32_t loop_start; uint32_t loop_end; uint32_t size_start; uint32_t size_end; uint8_t volume; // 0-255, apparently prior to 1.4 this was logarithmic? uint8_t flags; // above uint16_t speed; // only exists for 1.4+ int16_t finetune; }; static int read_sample_ult(struct ult_sample *smp, slurp_t *fp, uint8_t ver) { #define READ_VALUE(name) \ if (slurp_read(fp, &smp->name, sizeof(smp->name)) != sizeof(smp->name)) return 0 READ_VALUE(name); READ_VALUE(filename); READ_VALUE(loop_start); READ_VALUE(loop_end); READ_VALUE(size_start); READ_VALUE(size_end); READ_VALUE(volume); READ_VALUE(flags); // annoying: v4 added a field before the end of the struct if (ver >= 4) { READ_VALUE(speed); smp->speed = bswapLE16(smp->speed); } else { smp->speed = 8363; } READ_VALUE(finetune); #undef READ_VALUE /* now byteswap */ smp->finetune = bswapLE16(smp->finetune); smp->loop_start = bswapLE32(smp->loop_start); smp->loop_end = bswapLE32(smp->loop_end); smp->size_start = bswapLE32(smp->size_start); smp->size_end = bswapLE32(smp->size_end); return 1; } /* Unhandled effects: 5x1 - do not loop sample (x is unused) 5x2 - play sample backwards 5xC - end loop and finish sample 9xx - set sample offset to xx * 1024 with 9yy: set sample offset to xxyy * 4 E0x - set vibrato strength (2 is normal) F00 - reset speed/tempo to 6/125 Apparently 3xx will CONTINUE to slide until it reaches its destination, or until a 300 effect is encountered. I'm not attempting to handle this (yet). The logarithmic volume scale used in older format versions here, or pretty much anywhere for that matter. I don't even think Ultra Tracker tries to convert them. */ static const uint8_t ult_efftrans[] = { FX_ARPEGGIO, FX_PORTAMENTOUP, FX_PORTAMENTODOWN, FX_TONEPORTAMENTO, FX_VIBRATO, FX_NONE, FX_NONE, FX_TREMOLO, FX_NONE, FX_OFFSET, FX_VOLUMESLIDE, FX_PANNING, FX_VOLUME, FX_PATTERNBREAK, FX_NONE, // extended effects, processed separately FX_SPEED, }; static void translate_fx(uint8_t *pe, uint8_t *pp) { uint8_t e = *pe & 0xf; uint8_t p = *pp; *pe = ult_efftrans[e]; switch (e) { case 0: if (!p) *pe = FX_NONE; break; case 3: // 300 apparently stops sliding, which is totally weird if (!p) p = 1; // close enough? break; case 0xa: // blah, this sucks if (p & 0xf0) p &= 0xf0; break; case 0xb: // mikmod does this wrong, resulting in values 0-225 instead of 0-255 p = (p & 0xf) * 0x11; break; case 0xc: // volume p >>= 2; break; case 0xd: // pattern break p = 10 * (p >> 4) + (p & 0xf); break; case 0xe: // special switch (p >> 4) { case 1: *pe = FX_PORTAMENTOUP; p = 0xf0 | (p & 0xf); break; case 2: *pe = FX_PORTAMENTODOWN; p = 0xf0 | (p & 0xf); break; case 8: *pe = FX_SPECIAL; p = 0x60 | (p & 0xf); break; case 9: *pe = FX_RETRIG; p &= 0xf; break; case 0xa: *pe = FX_VOLUMESLIDE; p = ((p & 0xf) << 4) | 0xf; break; case 0xb: *pe = FX_VOLUMESLIDE; p = 0xf0 | (p & 0xf); break; case 0xc: case 0xd: *pe = FX_SPECIAL; break; } break; case 0xf: if (p > 0x2f) *pe = FX_TEMPO; break; } *pp = p; } static int read_ult_event(slurp_t *fp, song_note_t *note, int *lostfx) { uint8_t b, repeat = 1; uint32_t off; int n; b = slurp_getc(fp); if (b == 0xfc) { repeat = slurp_getc(fp); b = slurp_getc(fp); } note->note = (b > 0 && b < 61) ? b + 36 : NOTE_NONE; note->instrument = slurp_getc(fp); b = slurp_getc(fp); note->voleffect = b & 0xf; note->effect = b >> 4; note->volparam = slurp_getc(fp); note->param = slurp_getc(fp); translate_fx(¬e->voleffect, ¬e->volparam); translate_fx(¬e->effect, ¬e->param); // sample offset -- this is even more special than digitrakker's if (note->voleffect == FX_OFFSET && note->effect == FX_OFFSET) { off = ((note->volparam << 8) | note->param) >> 6; note->voleffect = FX_NONE; note->param = MIN(off, 0xff); } else if (note->voleffect == FX_OFFSET) { off = note->volparam * 4; note->volparam = MIN(off, 0xff); } else if (note->effect == FX_OFFSET) { off = note->param * 4; note->param = MIN(off, 0xff); } else if (note->voleffect == note->effect) { /* don't try to figure out how ultratracker does this, it's quite random */ note->effect = FX_NONE; } if (note->effect == FX_VOLUME || (note->effect == FX_NONE && note->voleffect != FX_VOLUME)) { swap_effects(note); } // Do that dance. // Maybe I should quit rewriting this everywhere and make a generic version :P for (n = 0; n < 4; n++) { if (convert_voleffect_of(note, n >> 1)) { n = 5; break; } swap_effects(note); } if (n < 5) { if (effect_weight[note->voleffect] > effect_weight[note->effect]) swap_effects(note); (*lostfx)++; //log_appendf(4, "Effect dropped: %c%02X < %c%02X", get_effect_char(note->voleffect), // note->volparam, get_effect_char(note->effect), note->param); note->voleffect = 0; } if (!note->voleffect) note->volparam = 0; if (!note->effect) note->param = 0; return repeat; } /* --------------------------------------------------------------------------------------------------------- */ int fmt_ult_load_song(song_t *song, slurp_t *fp, unsigned int lflags) { char buf[34]; uint8_t ver; int nmsg, nsmp, nchn, npat; int n, chn, pat, row; int lostfx = 0, gxx = 0; struct ult_sample usmp; song_sample_t *smp; const char *verstr[] = {"<1.4", "1.4", "1.5", "1.6"}; slurp_read(fp, buf, 14); if (memcmp(buf, "MAS_UTrack_V00", 14) != 0) return LOAD_UNSUPPORTED; ver = slurp_getc(fp); if (ver < '1' || ver > '4') return LOAD_FORMAT_ERROR; ver -= '0'; slurp_read(fp, buf, 32); buf[25] = '\0'; strcpy(song->title, buf); sprintf(song->tracker_id, "Ultra Tracker %s", verstr[ver - 1]); song->flags |= SONG_COMPATGXX | SONG_ITOLDEFFECTS; nmsg = slurp_getc(fp); read_lined_message(song->message, fp, nmsg * 32, 32); nsmp = slurp_getc(fp); for (n = 0, smp = song->samples + 1; n < nsmp; n++, smp++) { if (!read_sample_ult(&usmp, fp, ver)) return LOAD_FORMAT_ERROR; memcpy(smp->name, usmp.name, MIN(sizeof(smp->name), sizeof(usmp.name))); smp->name[ARRAY_SIZE(smp->name) - 1] = '\0'; memcpy(smp->filename, usmp.filename, MIN(sizeof(smp->filename), sizeof(usmp.filename))); smp->filename[ARRAY_SIZE(smp->filename) - 1] = '\0'; if (usmp.size_end <= usmp.size_start) continue; smp->length = usmp.size_end - usmp.size_start; smp->loop_start = usmp.loop_start; smp->loop_end = MIN(usmp.loop_end, smp->length); smp->volume = usmp.volume; //mphack - should be 0-64 not 0-256 smp->global_volume = 64; /* mikmod does some weird integer math here, but it didn't really work for me */ smp->c5speed = usmp.speed; if (usmp.finetune) smp->c5speed *= pow(2, (usmp.finetune / (12.0 * 32768))); if (usmp.flags & ULT_LOOP) smp->flags |= CHN_LOOP; if (usmp.flags & ULT_PINGPONGLOOP) smp->flags |= CHN_PINGPONGLOOP; if (usmp.flags & ULT_16BIT) { smp->flags |= CHN_16BIT; smp->loop_start >>= 1; smp->loop_end >>= 1; } } // ult just so happens to use 255 for its end mark, so there's no need to fiddle with this slurp_read(fp, song->orderlist, 256); nchn = slurp_getc(fp) + 1; npat = slurp_getc(fp) + 1; if (nchn > 32 || npat > MAX_PATTERNS) return LOAD_FORMAT_ERROR; if (ver >= 3) { for (n = 0; n < nchn; n++) song->channels[n].panning = ((slurp_getc(fp) & 0xf) << 2) + 2; } else { for (n = 0; n < nchn; n++) song->channels[n].panning = (n & 1) ? 48 : 16; } for (; n < 64; n++) { song->channels[n].panning = 32; song->channels[n].flags = CHN_MUTE; } //mphack - fix the pannings for (n = 0; n < 64; n++) song->channels[n].panning *= 4; if ((lflags & (LOAD_NOSAMPLES | LOAD_NOPATTERNS)) == (LOAD_NOSAMPLES | LOAD_NOPATTERNS)) return LOAD_SUCCESS; for (pat = 0; pat < npat; pat++) { song->pattern_size[pat] = song->pattern_alloc_size[pat] = 64; song->patterns[pat] = csf_allocate_pattern(64); } for (chn = 0; chn < nchn; chn++) { song_note_t evnote; song_note_t *note; int repeat; for (pat = 0; pat < npat; pat++) { note = song->patterns[pat] + chn; row = 0; while (row < 64) { repeat = read_ult_event(fp, &evnote, &lostfx); if (evnote.effect == FX_TONEPORTAMENTO || evnote.voleffect == VOLFX_TONEPORTAMENTO) { gxx |= 1; } if (repeat + row > 64) repeat = 64 - row; while (repeat--) { *note = evnote; note += 64; row++; } } } } if (gxx) log_appendf(4, " Warning: Gxx effects may not be suitably imported"); if (lostfx) log_appendf(4, " Warning: %d effect%s dropped", lostfx, lostfx == 1 ? "" : "s"); if (!(lflags & LOAD_NOSAMPLES)) { for (n = 0, smp = song->samples + 1; n < nsmp; n++, smp++) { csf_read_sample(smp, SF_LE | SF_M | SF_PCMS | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8), fp); } } return LOAD_SUCCESS; } schismtracker-20250313/fmt/wav.c000066400000000000000000000251261476471630300163650ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "fmt.h" #include "it.h" #include "disko.h" #include "player/sndfile.h" #include "log.h" #include #define WAVE_FORMAT_PCM 0x0001 #define WAVE_FORMAT_IEEE_FLOAT 0x0003 // IEEE float #define WAVE_FORMAT_ALAW 0x0006 // 8-bit ITU-T G.711 A-law #define WAVE_FORMAT_MULAW 0x0007 // 8-bit ITU-T G.711 µ-law #define WAVE_FORMAT_EXTENSIBLE 0xFFFE // Standard IFF chunks IDs #define IFFID_FORM 0x4d524f46 #define IFFID_RIFF 0x46464952 #define IFFID_WAVE 0x45564157 #define IFFID_LIST 0x5453494C #define IFFID_INFO 0x4F464E49 // IFF Info fields #define IFFID_ICOP 0x504F4349 #define IFFID_IART 0x54524149 #define IFFID_IPRD 0x44525049 #define IFFID_INAM 0x4D414E49 #define IFFID_ICMT 0x544D4349 #define IFFID_IENG 0x474E4549 #define IFFID_ISFT 0x54465349 #define IFFID_ISBJ 0x4A425349 #define IFFID_IGNR 0x524E4749 #define IFFID_ICRD 0x44524349 // Wave IFF chunks IDs #define IFFID_wave 0x65766177 #define IFFID_fmt 0x20746D66 #define IFFID_wsmp 0x706D7377 #define IFFID_pcm 0x206d6370 #define IFFID_data 0x61746164 #define IFFID_smpl 0x6C706D73 #define IFFID_xtra 0x61727478 typedef struct { uint32_t id_RIFF; // "RIFF" uint32_t filesize; // file length-8 uint32_t id_WAVE; } wave_file_header_t; typedef struct { uint16_t format; // 1 uint16_t channels; // 1:mono, 2:stereo uint32_t freqHz; // sampling freq uint32_t bytessec; // bytes/sec=freqHz*samplesize uint16_t samplesize; // sizeof(sample) uint16_t bitspersample; // bits per sample (8/16) } wave_format_t; /* --------------------------------------------------------------------------------------------------------- */ static int wav_chunk_fmt_read(const void *data, size_t size, void *void_fmt) { wave_format_t *fmt = (wave_format_t *)void_fmt; slurp_t fp; slurp_memstream(&fp, (uint8_t *)data, size); #define READ_VALUE(name) \ do { if (slurp_read(&fp, &name, sizeof(name)) != sizeof(name)) { unslurp(&fp); return 0; } } while (0) READ_VALUE(fmt->format); READ_VALUE(fmt->channels); READ_VALUE(fmt->freqHz); READ_VALUE(fmt->bytessec); READ_VALUE(fmt->samplesize); READ_VALUE(fmt->bitspersample); fmt->format = bswapLE16(fmt->format); fmt->channels = bswapLE16(fmt->channels); fmt->freqHz = bswapLE32(fmt->freqHz); fmt->bytessec = bswapLE32(fmt->bytessec); fmt->samplesize = bswapLE16(fmt->samplesize); fmt->bitspersample = bswapLE16(fmt->bitspersample); /* BUT I'M NOT DONE YET */ if (fmt->format == WAVE_FORMAT_EXTENSIBLE) { uint16_t ext_size; READ_VALUE(ext_size); ext_size = bswapLE16(ext_size); if (ext_size < 22) return 0; slurp_seek(&fp, 6, SEEK_CUR); static const unsigned char subformat_base_check[12] = { 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71, }; uint32_t subformat; unsigned char subformat_base[12]; READ_VALUE(subformat); READ_VALUE(subformat_base); if (memcmp(subformat_base, subformat_base_check, 12)) return 0; fmt->format = bswapLE32(subformat); } #undef READ_VALUE return 1; } /* --------------------------------------------------------------------------------------------------------- */ static int wav_load(song_sample_t *smp, slurp_t *fp, int load_sample) { iff_chunk_t fmt_chunk = {0}, data_chunk = {0}; wave_format_t fmt; wave_file_header_t phdr; if (slurp_read(fp, &phdr.id_RIFF, sizeof(phdr.id_RIFF)) != sizeof(phdr.id_RIFF) || slurp_read(fp, &phdr.filesize, sizeof(phdr.filesize)) != sizeof(phdr.filesize) || slurp_read(fp, &phdr.id_WAVE, sizeof(phdr.id_WAVE)) != sizeof(phdr.id_WAVE)) return 0; phdr.id_RIFF = bswapLE32(phdr.id_RIFF); phdr.filesize = bswapLE32(phdr.filesize); phdr.id_WAVE = bswapLE32(phdr.id_WAVE); if (phdr.id_RIFF != IFFID_RIFF || phdr.id_WAVE != IFFID_WAVE) return 0; iff_chunk_t c; while (riff_chunk_peek(&c, fp)) { switch (bswapBE32(c.id)) { case IFFID_fmt: if (fmt_chunk.id) return 0; fmt_chunk = c; break; case IFFID_data: if (data_chunk.id) return 0; data_chunk = c; break; default: break; } } if (!fmt_chunk.id || !data_chunk.id) return 0; if (!iff_chunk_receive(&fmt_chunk, fp, wav_chunk_fmt_read, &fmt)) return 0; uint32_t flags = 0; // endianness flags = SF_LE; // channels flags |= (fmt.channels == 2) ? SF_SI : SF_M; // interleaved stereo // bit width switch (fmt.bitspersample) { case 8: flags |= SF_8; break; case 16: flags |= SF_16; break; case 24: flags |= SF_24; break; case 32: flags |= SF_32; break; default: return 0; // unsupported } // encoding (8-bit wav is unsigned, everything else is signed -- yeah, it's stupid) switch (fmt.format) { case WAVE_FORMAT_PCM: flags |= (fmt.bitspersample == 8) ? SF_PCMU : SF_PCMS; break; case WAVE_FORMAT_IEEE_FLOAT: flags |= SF_IEEE; break; default: return 0; // unsupported } smp->flags = 0; // flags are set by csf_read_sample smp->volume = 64 * 4; smp->global_volume = 64; smp->c5speed = fmt.freqHz; smp->length = data_chunk.size / ((fmt.bitspersample / 8) * fmt.channels); if (load_sample) { return iff_read_sample(&data_chunk, fp, smp, flags, 0); } else { if (fmt.channels == 2) smp->flags |= CHN_STEREO; if (fmt.bitspersample > 8) smp->flags |= CHN_16BIT; } return 1; } /* --------------------------------------------------------------------------------------------------------- */ int fmt_wav_load_sample(slurp_t *fp, song_sample_t *smp) { return wav_load(smp, fp, 1); } int fmt_wav_read_info(dmoz_file_t *file, slurp_t *fp) { song_sample_t smp; if (!wav_load(&smp, fp, 0)) return 0; file->smp_flags = smp.flags; file->smp_speed = smp.c5speed; file->smp_length = smp.length; file->description = "IBM/Microsoft RIFF Audio"; file->type = TYPE_SAMPLE_PLAIN; file->smp_filename = file->base; return 1; } /* --------------------------------------------------------------------------------------------------------- */ /* wav is like aiff's retarded cousin */ struct wav_writedata { long data_size; // seek position for writing data size (in bytes) size_t numbytes; // how many bytes have been written int bps; // bytes per sample int swap; // should be byteswapped? }; static int wav_header(disko_t *fp, int bits, int channels, int rate, size_t length, struct wav_writedata *wwd /* out */) { int16_t s; uint32_t ul; int bps = 1; bps *= ((bits + 7) / 8) * channels; /* write a very large size for now */ disko_write(fp, "RIFF\377\377\377\377WAVEfmt ", 16); ul = bswapLE32(16); // fmt chunk size disko_write(fp, &ul, 4); s = bswapLE16(1); // linear pcm disko_write(fp, &s, 2); s = bswapLE16(channels); // number of channels disko_write(fp, &s, 2); ul = bswapLE32(rate); // sample rate disko_write(fp, &ul, 4); ul = bswapLE32(bps * rate); // "byte rate" (why?! I have no idea) disko_write(fp, &ul, 4); s = bswapLE16(bps); // (oh, come on! the format already stores everything needed to calculate this!) disko_write(fp, &s, 2); s = bswapLE16(bits); // bits per sample disko_write(fp, &s, 2); disko_write(fp, "data", 4); if (wwd) wwd->data_size = disko_tell(fp); ul = bswapLE32(bps * length); disko_write(fp, &ul, 4); return bps; } int fmt_wav_save_sample(disko_t *fp, song_sample_t *smp) { if (smp->flags & CHN_ADLIB) return SAVE_UNSUPPORTED; int bps; uint32_t ul; uint32_t flags = SF_LE; flags |= (smp->flags & CHN_16BIT) ? (SF_16 | SF_PCMS) : (SF_8 | SF_PCMU); flags |= (smp->flags & CHN_STEREO) ? SF_SI : SF_M; bps = wav_header(fp, (smp->flags & CHN_16BIT) ? 16 : 8, (smp->flags & CHN_STEREO) ? 2 : 1, smp->c5speed, smp->length, NULL); if (csf_write_sample(fp, smp, flags, UINT32_MAX) != smp->length * bps) { log_appendf(4, "WAV: unexpected data size written"); return SAVE_INTERNAL_ERROR; } /* fix the length in the file header */ ul = disko_tell(fp) - 8; ul = bswapLE32(ul); disko_seek(fp, 4, SEEK_SET); disko_write(fp, &ul, 4); return SAVE_SUCCESS; } int fmt_wav_export_head(disko_t *fp, int bits, int channels, int rate) { struct wav_writedata *wwd = malloc(sizeof(struct wav_writedata)); if (!wwd) return DW_ERROR; fp->userdata = wwd; wwd->bps = wav_header(fp, bits, channels, rate, ~0, wwd); wwd->numbytes = 0; #if WORDS_BIGENDIAN wwd->swap = (bits > 8); #else wwd->swap = 0; #endif return DW_OK; } int fmt_wav_export_body(disko_t *fp, const uint8_t *data, size_t length) { struct wav_writedata *wwd = fp->userdata; if (length % wwd->bps) { log_appendf(4, "WAV export: received uneven length"); return DW_ERROR; } wwd->numbytes += length; if (wwd->swap) { const int16_t *ptr = (const int16_t *) data; uint16_t v; length /= 2; while (length--) { v = *ptr; v = bswapLE16(v); disko_write(fp, &v, 2); ptr++; } } else { disko_write(fp, data, length); } return DW_OK; } int fmt_wav_export_silence(disko_t *fp, long bytes) { struct wav_writedata *wwd = fp->userdata; wwd->numbytes += bytes; disko_seek(fp, bytes, SEEK_CUR); return DW_OK; } int fmt_wav_export_tail(disko_t *fp) { struct wav_writedata *wwd = fp->userdata; uint32_t ul; /* fix the length in the file header */ ul = disko_tell(fp) - 8; ul= bswapLE32(ul); disko_seek(fp, 4, SEEK_SET); disko_write(fp, &ul, 4); /* write the other lengths */ disko_seek(fp, wwd->data_size, SEEK_SET); ul = bswapLE32(wwd->numbytes); disko_write(fp, &ul, 4); free(wwd); return DW_OK; } schismtracker-20250313/fmt/win32mf.c000066400000000000000000000726531476471630300170640ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Target Windows 10, so that we can get the extra audio codec * GUIDs it provides */ #define WINVER 0x1000 #define _WIN32_WINNT 0x1000 #define COBJMACROS // Get C object macros #include "headers.h" #include "mt.h" #include "charset.h" #include "fmt.h" #include "util.h" /* we want constant vtables */ #define CONST_VTABLE /* include these first */ #include #include #include /* define the GUIDs here; we don't use stuff outside here anyway */ #include #include #include #include #include #include #include /* Dynamically loaded Media Foundation functions, to keep Schism working with Windows XP */ typedef HRESULT (WINAPI *MF_MFStartupSpec)(ULONG version, DWORD flags); typedef HRESULT (WINAPI *MF_MFShutdownSpec)(void); typedef HRESULT (WINAPI *MF_MFCreateSourceResolverSpec)(IMFSourceResolver **ppISourceResolver); typedef HRESULT (WINAPI *MF_MFGetServiceSpec)(IUnknown *punkObject, REFGUID guidService, REFIID riid, LPVOID *ppvObject); typedef HRESULT (WINAPI *MF_PropVariantToStringAllocSpec)(REFPROPVARIANT propvar, PWSTR *ppszOut); typedef HRESULT (WINAPI *MF_PropVariantToUInt64Spec)(REFPROPVARIANT propvar, ULONGLONG *pullRet); typedef HRESULT (WINAPI *MF_PropVariantClearSpec)(PROPVARIANT *pvar); typedef HRESULT (WINAPI *MF_MFCreateSourceReaderFromMediaSourceSpec)(IMFMediaSource *pMediaSource, IMFAttributes *pAttributes, IMFSourceReader **ppSourceReader); typedef HRESULT (WINAPI *MF_MFCreateMediaTypeSpec)(IMFMediaType **ppMFType); typedef HRESULT (WINAPI *MF_MFCreateAsyncResultSpec)(IUnknown *punkObject, IMFAsyncCallback *pCallback, IUnknown *punkState, IMFAsyncResult **ppAsyncResult); typedef HRESULT (WINAPI *MF_MFPutWorkItemSpec)(DWORD dwQueue, IMFAsyncCallback *pCallback, IUnknown *pState); typedef HRESULT (WINAPI *MF_MFInvokeCallbackSpec)(IMFAsyncResult *pAsyncResult); typedef HRESULT (WINAPI *MF_QISearchSpec)(void *that,LPCQITAB pqit,REFIID riid,void **ppv); typedef HRESULT (WINAPI *MF_CoInitializeExSpec)(LPVOID pvReserved, DWORD dwCoInit); typedef void (WINAPI *MF_CoUninitializeSpec)(void); typedef void (WINAPI *MF_CoTaskMemFreeSpec)(LPVOID pv); static MF_MFStartupSpec MF_MFStartup; static MF_MFShutdownSpec MF_MFShutdown; static MF_MFCreateSourceResolverSpec MF_MFCreateSourceResolver; static MF_MFGetServiceSpec MF_MFGetService; static MF_PropVariantToStringAllocSpec MF_PropVariantToStringAlloc; static MF_PropVariantToUInt64Spec MF_PropVariantToUInt64; static MF_PropVariantClearSpec MF_PropVariantClear; static MF_MFCreateSourceReaderFromMediaSourceSpec MF_MFCreateSourceReaderFromMediaSource; static MF_MFCreateMediaTypeSpec MF_MFCreateMediaType; static MF_MFCreateAsyncResultSpec MF_MFCreateAsyncResult; static MF_MFPutWorkItemSpec MF_MFPutWorkItem; static MF_MFInvokeCallbackSpec MF_MFInvokeCallback; static MF_QISearchSpec MF_QISearch; static MF_CoInitializeExSpec MF_CoInitializeEx; static MF_CoUninitializeSpec MF_CoUninitialize; static MF_CoTaskMemFreeSpec MF_CoTaskMemFree; static int media_foundation_initialized = 0; /* forward declare this */ static HRESULT STDMETHODCALLTYPE mfbytestream_ReadAtPosition(IMFByteStream *This, BYTE *pb, ULONG cb, ULONG *pcbRead, int64_t pos); /* --------------------------------------------------------------------- */ /* "Fun things are fun" says who? */ struct slurp_async_op { /* vtable */ CONST_VTBL IUnknownVtbl *lpvtbl; ULONG ref_cnt; /* things we have to free, or whatever */ IMFAsyncCallback *cb; IMFByteStream *bs; /* stuff */ BYTE *buffer; ULONG req_length; ULONG actual_length; int64_t pos; }; static HRESULT STDMETHODCALLTYPE slurp_async_op_QueryInterface(IUnknown *This, REFIID riid, void **ppvobj) { static const QITAB qit[] = { {&IID_IUnknown, 0}, {NULL, 0}, }; return MF_QISearch(This, qit, riid, ppvobj); } static ULONG STDMETHODCALLTYPE slurp_async_op_AddRef(IUnknown *This) { return InterlockedIncrement(&(((struct slurp_async_op *)This)->ref_cnt)); } static ULONG STDMETHODCALLTYPE slurp_async_op_Release(IUnknown *This) { ULONG ref = InterlockedDecrement(&(((struct slurp_async_op *)This)->ref_cnt)); if (!ref) free(This); return ref; } static const IUnknownVtbl slurp_async_op_vtbl = { .QueryInterface = slurp_async_op_QueryInterface, .AddRef = slurp_async_op_AddRef, .Release = slurp_async_op_Release, }; static inline int slurp_async_op_new(IUnknown **This, IMFByteStream *bs, IMFAsyncCallback *cb, BYTE *buffer, ULONG req_length, int64_t pos) { struct slurp_async_op *op = calloc(1, sizeof(struct slurp_async_op)); if (!op) return 0; op->lpvtbl = &slurp_async_op_vtbl; /* user provided info... */ op->bs = bs; op->cb = cb; op->buffer = buffer; op->req_length = req_length; op->pos = pos; /* hmph */ op->ref_cnt = 1; *This = (IUnknown *)op; return 1; } /* ---------------------------------------------------------------------- */ /* IMFAsyncCallback implementation; I really wish I could just use the regular * IMFByteStream implementation because there isn't really anything special about * this :/ */ struct slurp_async_callback { /* vtable */ CONST_VTBL IMFAsyncCallbackVtbl *lpvtbl; ULONG ref_cnt; }; /* IUnknown */ static HRESULT STDMETHODCALLTYPE slurp_async_callback_QueryInterface(IMFAsyncCallback *This, REFIID riid, void **ppobj) { static const QITAB qit[] = { {&IID_IMFAsyncCallback, 0}, {NULL, 0}, }; return MF_QISearch(This, qit, riid, ppobj); } static ULONG STDMETHODCALLTYPE slurp_async_callback_AddRef(IMFAsyncCallback *This) { return InterlockedIncrement(&(((struct slurp_async_callback *)This)->ref_cnt)); } static ULONG STDMETHODCALLTYPE slurp_async_callback_Release(IMFAsyncCallback *This) { ULONG ref = InterlockedDecrement(&(((struct slurp_async_callback *)This)->ref_cnt)); if (!ref) free(This); return ref; } /* IMFAsyncCallback */ static HRESULT STDMETHODCALLTYPE slurp_async_callback_GetParameters(IMFAsyncCallback *This, DWORD *pdwFlags, DWORD *pdwQueue) { /* optional apparently, don't care enough to implement */ return E_NOTIMPL; } static HRESULT STDMETHODCALLTYPE slurp_async_callback_Invoke(IMFAsyncCallback *This, IMFAsyncResult *pAsyncResult) { struct slurp_async_op *op; IUnknown *pState = NULL; IUnknown *pUnk = NULL; IMFAsyncResult *caller = NULL; HRESULT hr = S_OK; hr = IMFAsyncResult_GetState(pAsyncResult, &pState); if (FAILED(hr)) goto done; hr = IUnknown_QueryInterface(pState, &IID_IMFAsyncResult, (void **)&caller); if (FAILED(hr)) goto done; hr = IMFAsyncResult_GetObject(caller, &pUnk); if (FAILED(hr)) goto done; op = (struct slurp_async_op *)pUnk; /* now we can actually do the work */ hr = mfbytestream_ReadAtPosition(op->bs, op->buffer, op->req_length, &op->actual_length, op->pos); done: if (caller) { IMFAsyncResult_SetStatus(caller, hr); MF_MFInvokeCallback(caller); } if (pState) IUnknown_Release(pState); if (pUnk) IUnknown_Release(pUnk); return S_OK; } static const IMFAsyncCallbackVtbl slurp_async_callback_vtbl = { /* IUnknown */ .QueryInterface = slurp_async_callback_QueryInterface, .AddRef = slurp_async_callback_AddRef, .Release = slurp_async_callback_Release, /* IMFAsyncCallback */ .GetParameters = slurp_async_callback_GetParameters, .Invoke = slurp_async_callback_Invoke, }; static inline int slurp_async_callback_new(IMFAsyncCallback **This) { struct slurp_async_callback *cb = calloc(1, sizeof(struct slurp_async_callback)); if (!cb) return 0; cb->lpvtbl = &slurp_async_callback_vtbl; cb->ref_cnt = 1; *This = (IMFAsyncCallback *)cb; return 1; } /* ----------------------------------------------------------------------------------------- */ /* IMFByteStream implementation for slurp */ struct mfbytestream { /* IMFByteStream vtable */ CONST_VTBL IMFByteStreamVtbl *lpvtbl; /* our stuff... */ slurp_t *fp; mt_mutex_t *mutex; ULONG ref_cnt; }; /* IUnknown methods */ static HRESULT STDMETHODCALLTYPE mfbytestream_QueryInterface(IMFByteStream *This, REFIID riid, void **ppobj) { static const QITAB qit[] = { {&IID_IMFByteStream, 0}, {NULL, 0}, }; return MF_QISearch(This, qit, riid, ppobj); } static ULONG STDMETHODCALLTYPE mfbytestream_AddRef(IMFByteStream *This) { struct mfbytestream *mfb = (struct mfbytestream *)This; return InterlockedIncrement(&mfb->ref_cnt); } static ULONG STDMETHODCALLTYPE mfbytestream_Release(IMFByteStream *This) { struct mfbytestream *mfb = (struct mfbytestream *)This; long ref = InterlockedDecrement(&mfb->ref_cnt); if (!ref) { mt_mutex_delete(mfb->mutex); free(mfb); } return ref; } /* IMFByteStream methods */ static HRESULT STDMETHODCALLTYPE mfbytestream_GetCapabilities(SCHISM_UNUSED IMFByteStream* This, DWORD *pdwCapabilities) { *pdwCapabilities = MFBYTESTREAM_IS_READABLE | MFBYTESTREAM_IS_SEEKABLE | MFBYTESTREAM_DOES_NOT_USE_NETWORK; return S_OK; } static HRESULT STDMETHODCALLTYPE mfbytestream_GetLength(IMFByteStream *This, QWORD *pqwLength) { struct mfbytestream *mfb = (struct mfbytestream *)This; *pqwLength = slurp_length(mfb->fp); return S_OK; } static HRESULT STDMETHODCALLTYPE mfbytestream_SetLength(IMFByteStream *This, QWORD qwLength) { return E_NOTIMPL; } static HRESULT STDMETHODCALLTYPE mfbytestream_SetCurrentPosition(IMFByteStream *This, QWORD qwPosition) { struct mfbytestream *mfb = (struct mfbytestream *)This; mt_mutex_lock(mfb->mutex); slurp_seek(mfb->fp, qwPosition, SEEK_SET); mt_mutex_unlock(mfb->mutex); return S_OK; } static HRESULT STDMETHODCALLTYPE mfbytestream_GetCurrentPosition(IMFByteStream *This, QWORD *pqwPosition) { struct mfbytestream *mfb = (struct mfbytestream *)This; mt_mutex_lock(mfb->mutex); int64_t pos = slurp_tell(mfb->fp); mt_mutex_unlock(mfb->mutex); if (pos < 0) return E_FAIL; *pqwPosition = pos; return S_OK; } static HRESULT STDMETHODCALLTYPE mfbytestream_IsEndOfStream(IMFByteStream *This, WINBOOL *pfEndOfStream) { struct mfbytestream *mfb = (struct mfbytestream *)This; mt_mutex_lock(mfb->mutex); *pfEndOfStream = slurp_eof(mfb->fp); mt_mutex_unlock(mfb->mutex); return S_OK; } static HRESULT STDMETHODCALLTYPE mfbytestream_Read(IMFByteStream *This, BYTE *pb, ULONG cb, ULONG *pcbRead) { struct mfbytestream *mfb = (struct mfbytestream *)This; mt_mutex_lock(mfb->mutex); *pcbRead = slurp_read(mfb->fp, pb, cb); mt_mutex_unlock(mfb->mutex); return S_OK; } /* this is only used internally */ static HRESULT STDMETHODCALLTYPE mfbytestream_ReadAtPosition(IMFByteStream *This, BYTE *pb, ULONG cb, ULONG *pcbRead, int64_t pos) { struct mfbytestream *mfb = (struct mfbytestream *)This; HRESULT hr = S_OK; *pcbRead = 0; mt_mutex_lock(mfb->mutex); /* cache the old position */ int64_t old_pos = slurp_tell(mfb->fp); if (old_pos < 0) { /* what ? */ hr = E_FAIL; goto done; } slurp_seek(mfb->fp, pos, SEEK_SET); *pcbRead = slurp_read(mfb->fp, pb, cb); /* seek back to the old position */ slurp_seek(mfb->fp, old_pos, SEEK_SET); done: mt_mutex_unlock(mfb->mutex); return hr; } static HRESULT STDMETHODCALLTYPE mfbytestream_BeginRead(IMFByteStream *This, BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback, IUnknown *punkState) { IUnknown *op = NULL; IMFAsyncCallback* callback = NULL; IMFAsyncResult *result = NULL; QWORD pos; HRESULT hr = S_OK; if (FAILED(hr = mfbytestream_GetCurrentPosition(This, &pos))) return hr; if (FAILED(hr = mfbytestream_SetCurrentPosition(This, pos + cb))) return hr; if (!slurp_async_callback_new(&callback)) return E_OUTOFMEMORY; if (!slurp_async_op_new(&op, This, callback, pb, cb, pos)) return E_OUTOFMEMORY; if (FAILED(hr = MF_MFCreateAsyncResult(op, pCallback, punkState, &result))) goto fail; hr = MF_MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, callback, (IUnknown *)result); fail: if (result) IMFAsyncResult_Release(result); return hr; } static HRESULT STDMETHODCALLTYPE mfbytestream_EndRead(IMFByteStream *This, IMFAsyncResult *pResult, ULONG *pcbRead) { *pcbRead = 0; struct slurp_async_op *op = NULL; HRESULT hr = IMFAsyncResult_GetStatus(pResult); if (FAILED(hr)) goto done; hr = IMFAsyncResult_GetObject(pResult, (IUnknown **)&op); if (FAILED(hr)) goto done; *pcbRead = op->actual_length; done: if (op) { /* need to deal with this crap */ if (op->cb) IMFAsyncCallback_Release(op->cb); IUnknown_Release((IUnknown *)op); } return hr; } static HRESULT STDMETHODCALLTYPE mfbytestream_Write(IMFByteStream *This, const BYTE *pb, ULONG cb, ULONG *pcbWritten) { return E_NOINTERFACE; } static HRESULT STDMETHODCALLTYPE mfbytestream_BeginWrite(IMFByteStream *This, const BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback, IUnknown *punkState) { return E_NOINTERFACE; } static HRESULT STDMETHODCALLTYPE mfbytestream_EndWrite(IMFByteStream *This, IMFAsyncResult *pResult, ULONG *pcbWritten) { return E_NOINTERFACE; } static HRESULT STDMETHODCALLTYPE mfbytestream_Seek(IMFByteStream *This, MFBYTESTREAM_SEEK_ORIGIN SeekOrigin, LONGLONG llSeekOffset, DWORD dwSeekFlags, QWORD *pqwCurrentPosition) { struct mfbytestream *mfb = (struct mfbytestream *)This; int whence; // I don't know how to support this if (dwSeekFlags & MFBYTESTREAM_SEEK_FLAG_CANCEL_PENDING_IO) return E_NOTIMPL; switch (SeekOrigin) { case msoBegin: whence = SEEK_SET; break; case msoCurrent: whence = SEEK_CUR; break; default: return E_NOINTERFACE; } mt_mutex_lock(mfb->mutex); slurp_seek(mfb->fp, llSeekOffset, whence); *pqwCurrentPosition = slurp_tell(mfb->fp); mt_mutex_unlock(mfb->mutex); return S_OK; } static HRESULT STDMETHODCALLTYPE mfbytestream_Flush(IMFByteStream *This) { /* we don't have anything to flush... */ return S_OK; } static HRESULT STDMETHODCALLTYPE mfbytestream_Close(IMFByteStream *This) { /* do nothing */ return S_OK; } static const IMFByteStreamVtbl mfbytestream_vtbl = { /* IUnknown */ .QueryInterface = mfbytestream_QueryInterface, .AddRef = mfbytestream_AddRef, .Release = mfbytestream_Release, /* IMFByteStream */ .GetCapabilities = mfbytestream_GetCapabilities, .GetLength = mfbytestream_GetLength, .SetLength = mfbytestream_SetLength, .SetCurrentPosition = mfbytestream_SetCurrentPosition, .GetCurrentPosition = mfbytestream_GetCurrentPosition, .IsEndOfStream = mfbytestream_IsEndOfStream, .Read = mfbytestream_Read, .BeginRead = mfbytestream_BeginRead, .EndRead = mfbytestream_EndRead, .Write = mfbytestream_Write, .BeginWrite = mfbytestream_BeginWrite, .EndWrite = mfbytestream_EndWrite, .Seek = mfbytestream_Seek, .Flush = mfbytestream_Flush, .Close = mfbytestream_Close, }; static inline int mfbytestream_new(IMFByteStream **imf, slurp_t *fp) { struct mfbytestream *mfb = calloc(1, sizeof(*mfb)); if (!mfb) return 0; /* put in the vtable */ mfb->lpvtbl = &mfbytestream_vtbl; /* whatever */ mfb->fp = fp; mfb->mutex = mt_mutex_create(); if (!mfb->mutex) { free(mfb); return 0; } mfb->ref_cnt = 1; *imf = (IMFByteStream *)mfb; return 1; } /* -------------------------------------------------------------- */ /* I really have no idea what (apartment/multi)threading does. I don't * think it really has an impact, either */ #define COM_INITFLAGS (COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE) static const char* get_media_type_description(IMFMediaType* media_type) { static const struct { const GUID *guid; const char *description; } guids[] = { // no RealAudio support? for shame, Microsoft. {&MFAudioFormat_AAC, "Advanced Audio Coding"}, // {&MFAudioFormat_ADTS, "Not used"}, {&MFAudioFormat_ALAC, "Apple Lossless Audio Codec"}, {&MFAudioFormat_AMR_NB, "Adaptive Multi-Rate"}, {&MFAudioFormat_AMR_WB, "Adaptive Multi-Rate Wideband"}, {&MFAudioFormat_Dolby_AC3, "Dolby Digital (AC-3)"}, {&MFAudioFormat_Dolby_AC3_SPDIF, "Dolby AC-3 over S/PDIF"}, {&MFAudioFormat_Dolby_DDPlus, "Dolby Digital Plus"}, {&MFAudioFormat_DRM, "Encrypted audio data"}, // ???? {&MFAudioFormat_DTS, "Digital Theater Systems"}, // yes, "theater", not "theatre" {&MFAudioFormat_FLAC, "Free Lossless Audio Codec"}, {&MFAudioFormat_Float, "Uncompressed floating-point audio"}, {&MFAudioFormat_Float_SpatialObjects, "Uncompressed floating-point audio"}, {&MFAudioFormat_MP3, "MPEG Layer-3 (MP3)"}, {&MFAudioFormat_MPEG, "MPEG-1 audio payload"}, {&MFAudioFormat_MSP1, "Windows Media Audio 9 Voice"}, {&MFAudioFormat_Opus, "Opus"}, {&MFAudioFormat_PCM, "Uncompressed PCM"}, // {&MFAudioFormat_QCELP, "QCELP audio"}, // can't get this to compile... {&MFAudioFormat_WMASPDIF, "Windows Media Audio 9 over S/PDIF"}, {&MFAudioFormat_WMAudio_Lossless, "Windows Media Audio 9 Lossless"}, {&MFAudioFormat_WMAudioV8, "Windows Media Audio 8"}, {&MFAudioFormat_WMAudioV9, "Windows Media Audio 8"}, }; GUID subtype; if (SUCCEEDED(IMFMediaType_GetGUID(media_type, &MF_MT_SUBTYPE, &subtype))) { for (size_t i = 0; i < ARRAY_SIZE(guids); i++) if (IsEqualGUID(&subtype, guids[i].guid)) return guids[i].description; #ifdef SCHISM_MF_DEBUG log_appendf(1, "Unknown Media Foundation subtype found:"); log_appendf(1, "{%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX}", subtype.Data1, subtype.Data2, subtype.Data3, subtype.Data4[0], subtype.Data4[1], subtype.Data4[2], subtype.Data4[3], subtype.Data4[4], subtype.Data4[5], subtype.Data4[6], subtype.Data4[7]); #endif } /* welp */ return "Media Foundation"; } static int get_source_reader_information(IMFSourceReader *reader, dmoz_file_t *file) { IMFMediaType *media_type = NULL; uint64_t length = 0; BOOL compressed = FALSE; if (SUCCEEDED(IMFSourceReader_GetNativeMediaType(reader, MF_SOURCE_READER_FIRST_AUDIO_STREAM, MF_SOURCE_READER_CURRENT_TYPE_INDEX, &media_type))) { file->type = (SUCCEEDED(IMFMediaType_IsCompressedFormat(media_type, &compressed)) && compressed) ? TYPE_SAMPLE_COMPR : TYPE_SAMPLE_PLAIN; file->description = get_media_type_description(media_type); IMFMediaType_Release(media_type); } return 1; } static int convert_media_foundation_metadata(IMFMediaSource* source, dmoz_file_t* file) { IMFPresentationDescriptor *descriptor = NULL; IMFMetadataProvider *provider = NULL; IMFMetadata *metadata = NULL; PROPVARIANT propnames = {0}; DWORD streams = 0; int found = 0; uint64_t duration = 0; /* do this before anything else... */ PropVariantInit(&propnames); if (FAILED(IMFMediaSource_CreatePresentationDescriptor(source, &descriptor))) goto cleanup; if (SUCCEEDED(IMFPresentationDescriptor_GetUINT64(descriptor, &MF_PD_DURATION, &duration))) file->smp_length = (double)duration * file->smp_speed / (10.0 * 1000.0 * 1000.0); if (FAILED(MF_MFGetService((IUnknown*)source, &MF_METADATA_PROVIDER_SERVICE, &IID_IMFMetadataProvider, (void**)&provider))) goto cleanup; if (FAILED(IMFMetadataProvider_GetMFMetadata(provider, descriptor, 0, 0, &metadata))) goto cleanup; if (FAILED(IMFMetadata_GetAllPropertyNames(metadata, &propnames))) goto cleanup; for (DWORD prop_index = 0; prop_index < propnames.calpwstr.cElems; prop_index++) { LPWSTR prop_name = propnames.calpwstr.pElems[prop_index]; if (!prop_name) continue; /* ... */ if (!wcscmp(prop_name, L"Title")) { PROPVARIANT propval = {0}; PropVariantInit(&propval); if (FAILED(IMFMetadata_GetProperty(metadata, prop_name, &propval))) { MF_PropVariantClear(&propval); continue; } LPWSTR prop_val_str = NULL; if (FAILED(MF_PropVariantToStringAlloc(&propval, &prop_val_str))) { MF_PropVariantClear(&propval); continue; } found = 1; charset_iconv(prop_val_str, file->title, CHARSET_WCHAR_T, CHARSET_CP437, SIZE_MAX); MF_CoTaskMemFree(prop_val_str); MF_PropVariantClear(&propval); } } cleanup: MF_PropVariantClear(&propnames); if (descriptor) IMFPresentationDescriptor_Release(descriptor); if (provider) IMFMetadataProvider_Release(provider); if (metadata) IMFMetadata_Release(metadata); return found; } /* ---------------------------------------------------------------- */ struct win32mf_data { /* wew */ IMFSourceReader *reader; IMFMediaSource *source; IMFByteStream *byte_stream; /* info about the output audio data */ uint32_t flags, sps, bps, channels; }; static int win32mf_start(struct win32mf_data *data, slurp_t *fp, wchar_t *url) { int success = 0; IMFSourceResolver *resolver = NULL; IUnknown *unknown_media_source = NULL; IMFMediaType *uncompressed_type = NULL; MF_OBJECT_TYPE object_type = MF_OBJECT_INVALID; if (FAILED(MF_MFCreateSourceResolver(&resolver))) goto cleanup; if (!mfbytestream_new(&data->byte_stream, fp)) goto cleanup; if (FAILED(IMFSourceResolver_CreateObjectFromByteStream(resolver, data->byte_stream, url, MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE | MF_RESOLUTION_READ, NULL, &object_type, &unknown_media_source))) goto cleanup; if (object_type != MF_OBJECT_MEDIASOURCE) goto cleanup; if (FAILED(IUnknown_QueryInterface(unknown_media_source, &IID_IMFMediaSource, (void**)&data->source))) goto cleanup; if (FAILED(MF_MFCreateSourceReaderFromMediaSource(data->source, NULL, &data->reader))) goto cleanup; if (FAILED(MF_MFCreateMediaType(&uncompressed_type))) goto cleanup; if (FAILED(IMFMediaType_SetGUID(uncompressed_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Audio))) goto cleanup; if (FAILED(IMFMediaType_SetGUID(uncompressed_type, &MF_MT_SUBTYPE, &MFAudioFormat_PCM))) goto cleanup; if (FAILED(IMFSourceReader_SetCurrentMediaType(data->reader, MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, uncompressed_type))) goto cleanup; IMFMediaType_Release(uncompressed_type); if (FAILED(IMFSourceReader_GetCurrentMediaType(data->reader, MF_SOURCE_READER_FIRST_AUDIO_STREAM, &uncompressed_type))) goto cleanup; if (FAILED(IMFSourceReader_SetStreamSelection(data->reader, MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE))) goto cleanup; /* get output audio track data */ if (FAILED(IMFMediaType_GetUINT32(uncompressed_type, &MF_MT_AUDIO_NUM_CHANNELS, &data->channels))) goto cleanup; if (FAILED(IMFMediaType_GetUINT32(uncompressed_type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, &data->sps))) goto cleanup; if (FAILED(IMFMediaType_GetUINT32(uncompressed_type, &MF_MT_AUDIO_BITS_PER_SAMPLE, &data->bps))) goto cleanup; if (data->sps <= 0) goto cleanup; data->flags = SF_LE | SF_PCMS; switch (data->channels) { case 1: data->flags |= SF_M; break; case 2: data->flags |= SF_SI; break; default: goto cleanup; } switch (data->bps) { case 8: data->flags |= SF_8; break; case 16: data->flags |= SF_16; break; case 24: data->flags |= SF_24; break; case 32: data->flags |= SF_32; break; default: goto cleanup; } success = 1; cleanup: if (resolver) IMFSourceResolver_Release(resolver); if (unknown_media_source) IUnknown_Release(unknown_media_source); if (uncompressed_type) IMFMediaType_Release(uncompressed_type); return success; } static void win32mf_end(struct win32mf_data *data) { if (data->reader) IMFSourceReader_Release(data->reader); if (data->source) IMFMediaSource_Release(data->source); if (data->byte_stream) IMFByteStream_Release(data->byte_stream); } int fmt_win32mf_read_info(dmoz_file_t *file, slurp_t *fp) { struct win32mf_data data = {0}; wchar_t *url = NULL; if (!media_foundation_initialized) return 0; if (!file->path || charset_iconv(file->path, &url, CHARSET_UTF8, CHARSET_WCHAR_T, SIZE_MAX)) url = NULL; if (!win32mf_start(&data, fp, url)) { win32mf_end(&data); return 0; } file->smp_flags = data.flags; file->smp_speed = data.sps; convert_media_foundation_metadata(data.source, file); get_source_reader_information(data.reader, file); win32mf_end(&data); return 1; } enum { READER_LOAD_DONE, READER_LOAD_ERROR, READER_LOAD_MORE }; /* uncompressed MUST point to NULL (or some other allocated memory) before the first pass! */ static int reader_load_sample(IMFSourceReader *reader, disko_t *ds) { if (!reader) return READER_LOAD_ERROR; int success = READER_LOAD_MORE; IMFSample *sample = NULL; DWORD sample_flags = 0; IMFMediaBuffer *buffer = NULL; if (FAILED(IMFSourceReader_ReadSample(reader, MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, NULL, &sample_flags, NULL, &sample))) { success = READER_LOAD_ERROR; goto cleanup; } if (sample_flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED || sample_flags & MF_SOURCE_READERF_ENDOFSTREAM) { success = READER_LOAD_DONE; goto cleanup; } if (FAILED(IMFSample_ConvertToContiguousBuffer(sample, &buffer))) { success = READER_LOAD_ERROR; goto cleanup; } BYTE *buffer_data = NULL; DWORD buffer_data_size = 0; if (FAILED(IMFMediaBuffer_Lock(buffer, &buffer_data, NULL, &buffer_data_size))) { success = READER_LOAD_ERROR; goto cleanup; } disko_write(ds, buffer_data, buffer_data_size); if (FAILED(IMFMediaBuffer_Unlock(buffer))) { success = READER_LOAD_ERROR; goto cleanup; } cleanup: if (sample) IMFSample_Release(sample); if (buffer) IMFMediaBuffer_Release(buffer); return success; } int fmt_win32mf_load_sample(slurp_t *fp, song_sample_t *smp) { if (!media_foundation_initialized) return 0; disko_t ds = {0}; if (disko_memopen(&ds) < 0) return 0; struct win32mf_data data = {0}; if (!win32mf_start(&data, fp, NULL)) { win32mf_end(&data); disko_memclose(&ds, 0); return 0; } for (;;) { int loop_success = reader_load_sample(data.reader, &ds); if (loop_success == READER_LOAD_DONE) break; if (loop_success == READER_LOAD_ERROR || (ds.length / data.channels / (data.bps / 8) > MAX_SAMPLE_LENGTH)) { win32mf_end(&data); disko_memclose(&ds, 0); return 0; } } uint32_t sample_length = ds.length / data.channels / (data.bps / 8); if (sample_length < 1 || sample_length > MAX_SAMPLE_LENGTH) { win32mf_end(&data); disko_memclose(&ds, 0); return 0; } smp->volume = 64 * 4; smp->global_volume = 64; smp->c5speed = data.sps; smp->length = sample_length; slurp_t fake_fp; slurp_memstream(&fake_fp, ds.data, ds.length); int r = csf_read_sample(smp, data.flags, &fake_fp); disko_memclose(&ds, 0); win32mf_end(&data); return r; } /* ----------------------------------------------------------- */ static HMODULE lib_ole32 = NULL; static HMODULE lib_shlwapi = NULL; static HMODULE lib_mf = NULL; static HMODULE lib_mfplat = NULL; static HMODULE lib_mfreadwrite = NULL; static HMODULE lib_propsys = NULL; /* needs to be called once on startup */ int win32mf_init(void) { int com_initialized = 0; #ifdef SCHISM_MF_DEBUG #define DEBUG_PUTS(x) puts(x) #else #define DEBUG_PUTS(x) #endif #define LOAD_MF_LIBRARY(o) \ do { \ lib_ ## o = LoadLibraryA(#o ".dll"); \ if (!(lib_ ## o)) { DEBUG_PUTS("Failed to load library " #o "!"); goto fail; } \ } while (0) #define LOAD_MF_OBJECT(o, x) \ do { \ MF_##x = (MF_##x##Spec)GetProcAddress(lib_ ## o, #x); \ if (!MF_##x) { DEBUG_PUTS("Failed to load " #x " from library " #o ".dll !"); goto fail; } \ } while (0) LOAD_MF_LIBRARY(ole32); LOAD_MF_OBJECT(ole32, CoInitializeEx); LOAD_MF_OBJECT(ole32, CoUninitialize); LOAD_MF_OBJECT(ole32, PropVariantClear); LOAD_MF_OBJECT(ole32, CoTaskMemFree); LOAD_MF_LIBRARY(shlwapi); LOAD_MF_OBJECT(shlwapi, QISearch); LOAD_MF_LIBRARY(mf); LOAD_MF_OBJECT(mf, MFGetService); LOAD_MF_OBJECT(mf, MFCreateSourceResolver); LOAD_MF_LIBRARY(mfplat); LOAD_MF_OBJECT(mfplat, MFCreateMediaType); LOAD_MF_OBJECT(mfplat, MFStartup); LOAD_MF_OBJECT(mfplat, MFShutdown); LOAD_MF_OBJECT(mfplat, MFCreateAsyncResult); LOAD_MF_OBJECT(mfplat, MFPutWorkItem); LOAD_MF_OBJECT(mfplat, MFInvokeCallback); LOAD_MF_LIBRARY(mfreadwrite); LOAD_MF_OBJECT(mfreadwrite, MFCreateSourceReaderFromMediaSource); LOAD_MF_LIBRARY(propsys); LOAD_MF_OBJECT(propsys, PropVariantToStringAlloc); LOAD_MF_OBJECT(propsys, PropVariantToUInt64); HRESULT com_init = MF_CoInitializeEx(NULL, COM_INITFLAGS); switch (com_init) { case S_OK: case S_FALSE: case RPC_E_CHANGED_MODE: com_initialized = 1; break; default: goto fail; } // Vista SDK version, #define SCHISM_MF_VERSION ((0x0001U << 16) | 0x0070U) /* MFSTARTUP_LITE == no sockets */ if (FAILED(MF_MFStartup(SCHISM_MF_VERSION, MFSTARTUP_LITE))) goto fail; #undef SCHISM_MF_VERSION media_foundation_initialized = 1; return 1; fail: if (lib_shlwapi) FreeLibrary(lib_shlwapi); if (lib_mfplat) FreeLibrary(lib_mfplat); if (lib_mf) FreeLibrary(lib_mf); if (lib_mfreadwrite) FreeLibrary(lib_mfreadwrite); if (lib_propsys) FreeLibrary(lib_propsys); if (lib_ole32) { if (com_initialized && MF_CoUninitialize) MF_CoUninitialize(); FreeLibrary(lib_ole32); } return 0; } void win32mf_quit(void) { if (!media_foundation_initialized) return; MF_MFShutdown(); MF_CoUninitialize(); if (lib_shlwapi) FreeLibrary(lib_shlwapi); if (lib_mfplat) FreeLibrary(lib_mfplat); if (lib_mf) FreeLibrary(lib_mf); if (lib_mfreadwrite) FreeLibrary(lib_mfreadwrite); if (lib_propsys) FreeLibrary(lib_propsys); if (lib_ole32) FreeLibrary(lib_ole32); }schismtracker-20250313/fmt/xi.c000066400000000000000000000355631476471630300162160ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "fmt.h" #include "mem.h" #include "it.h" #include "song.h" #include "player/sndfile.h" #include #include /* --------------------------------------------------------------------- */ struct xm_sample_header { uint32_t samplen; uint32_t loopstart; uint32_t looplen; uint8_t vol; int8_t finetune; uint8_t type; uint8_t pan; int8_t relnote; uint8_t res; char name[22]; }; struct xi_file_header { int8_t header[0x15]; // "Extended Instrument: " int8_t name[0x16]; // Name of instrument uint8_t magic; // 0x1a, DOS EOF char so you can 'type file.xi' int8_t tracker[0x14]; // Name of tracker uint16_t version; // big-endian 0x0102 // sample header struct { uint8_t snum[96]; struct { uint16_t ticks; // Time in tracker ticks uint16_t val; // Value from 0x00 to 0x40. } venv[12], penv[12]; uint8_t vnum, pnum; uint8_t vsustain, vloops, vloope, psustain, ploops, ploope; uint8_t vtype, ptype; uint8_t vibtype, vibsweep, vibdepth, vibrate; uint16_t volfade; uint8_t reserved1[0x16]; uint16_t nsamples; } xish; }; static int xm_sample_header_read(struct xm_sample_header *shdr, slurp_t *fp) { #define READ_VALUE(name) do { if (slurp_read(fp, &shdr->name, sizeof(shdr->name)) != sizeof(shdr->name)) { return 0; } } while (0) READ_VALUE(samplen); READ_VALUE(loopstart); READ_VALUE(looplen); READ_VALUE(vol); READ_VALUE(finetune); READ_VALUE(type); READ_VALUE(pan); READ_VALUE(relnote); READ_VALUE(res); READ_VALUE(name); #undef READ_VALUE return 1; } static int xi_file_header_write(struct xi_file_header *hdr, disko_t *fp) { #define WRITE_VALUE(name) do { disko_write(fp, &hdr->name, sizeof(hdr->name)); } while (0) WRITE_VALUE(header); WRITE_VALUE(name); WRITE_VALUE(magic); WRITE_VALUE(tracker); WRITE_VALUE(version); WRITE_VALUE(xish.snum); for (size_t i = 0; i < 12; i++) { WRITE_VALUE(xish.venv[i].ticks); WRITE_VALUE(xish.venv[i].val); } for (size_t i = 0; i < 12; i++) { WRITE_VALUE(xish.penv[i].ticks); WRITE_VALUE(xish.penv[i].val); } WRITE_VALUE(xish.vnum); WRITE_VALUE(xish.pnum); WRITE_VALUE(xish.vsustain); WRITE_VALUE(xish.vloops); WRITE_VALUE(xish.vloope); WRITE_VALUE(xish.psustain); WRITE_VALUE(xish.ploops); WRITE_VALUE(xish.ploope); WRITE_VALUE(xish.vtype); WRITE_VALUE(xish.ptype); WRITE_VALUE(xish.vibtype); WRITE_VALUE(xish.vibsweep); WRITE_VALUE(xish.vibdepth); WRITE_VALUE(xish.vibrate); WRITE_VALUE(xish.volfade); WRITE_VALUE(xish.reserved1); WRITE_VALUE(xish.nsamples); #undef WRITE_VALUE return 1; } static int xm_sample_header_write(struct xm_sample_header *shdr, disko_t *fp) { #define WRITE_VALUE(name) do { disko_write(fp, &shdr->name, sizeof(shdr->name)); } while (0) WRITE_VALUE(samplen); WRITE_VALUE(loopstart); WRITE_VALUE(looplen); WRITE_VALUE(vol); WRITE_VALUE(finetune); WRITE_VALUE(type); WRITE_VALUE(pan); WRITE_VALUE(relnote); WRITE_VALUE(res); WRITE_VALUE(name); #undef WRITE_VALUE return 1; } static int xi_file_header_read(struct xi_file_header *hdr, slurp_t *fp) { #define READ_VALUE(name) do { if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) { return 0; } } while (0) READ_VALUE(header); READ_VALUE(name); READ_VALUE(magic); READ_VALUE(tracker); READ_VALUE(version); READ_VALUE(xish.snum); for (size_t i = 0; i < 12; i++) { READ_VALUE(xish.venv[i].ticks); READ_VALUE(xish.venv[i].val); } for (size_t i = 0; i < 12; i++) { READ_VALUE(xish.penv[i].ticks); READ_VALUE(xish.penv[i].val); } READ_VALUE(xish.vnum); READ_VALUE(xish.pnum); READ_VALUE(xish.vsustain); READ_VALUE(xish.vloops); READ_VALUE(xish.vloope); READ_VALUE(xish.psustain); READ_VALUE(xish.ploops); READ_VALUE(xish.ploope); READ_VALUE(xish.vtype); READ_VALUE(xish.ptype); READ_VALUE(xish.vibtype); READ_VALUE(xish.vibsweep); READ_VALUE(xish.vibdepth); READ_VALUE(xish.vibrate); READ_VALUE(xish.volfade); READ_VALUE(xish.reserved1); READ_VALUE(xish.nsamples); #undef READ_VALUE if (memcmp(hdr->header, "Extended Instrument: ", 0x15)) return 0; if (hdr->magic != 0x1a) return 0; if (bswapLE16(hdr->version) != 0x0102) return 0; return 1; } #define XI_ENV_ENABLED 0x01 #define XI_ENV_SUSTAIN 0x02 #define XI_ENV_LOOP 0x04 /* --------------------------------------------------------------------- */ int fmt_xi_read_info(dmoz_file_t *file, slurp_t *fp) { struct xi_file_header xi; if (!xi_file_header_read(&xi, fp)) return 0; file->description = "Fasttracker II Instrument"; file->title = strn_dup((const char *)xi.name, sizeof(xi.name)); file->type = TYPE_INST_XI; return 1; } int fmt_xi_load_instrument(slurp_t *fp, int slot) { struct xi_file_header xi; struct instrumentloader ii; song_instrument_t *g; int k, prevtick; int smpdataoffset; if (!xi_file_header_read(&xi, fp)) return 0; if (!slot) return 0; //song_delete_instrument(slot, 0); g = instrument_loader_init(&ii, slot); memcpy(g->name, xi.name, 22); g->name[22] = '\0'; for (k = 0; k < 96; k++) { if (xi.xish.snum[k] > 15) xi.xish.snum[k] = 15; xi.xish.snum[k] = instrument_loader_sample(&ii, xi.xish.snum[k] + 1); g->note_map[k + 12] = k + 1 + 12; if (xi.xish.snum[k]) g->sample_map[k + 12] = xi.xish.snum[k]; } for (k = 0; k < 12; k++) { g->note_map[k] = 0; g->sample_map[k] = 0; g->note_map[k + 108] = 0; g->sample_map[k + 108] = 0; } // bswap all volume and panning envelope points for (k = 0; k < 12; k++) { xi.xish.venv[k].ticks = bswapLE16(xi.xish.venv[k].ticks); xi.xish.venv[k].val = bswapLE16(xi.xish.venv[k].val); xi.xish.penv[k].ticks = bswapLE16(xi.xish.penv[k].ticks); xi.xish.penv[k].val = bswapLE16(xi.xish.penv[k].val); } // Set up envelope types in instrument if (xi.xish.vtype & XI_ENV_ENABLED) g->flags |= ENV_VOLUME; if (xi.xish.vtype & XI_ENV_SUSTAIN) g->flags |= ENV_VOLSUSTAIN; if (xi.xish.vtype & XI_ENV_LOOP) g->flags |= ENV_VOLLOOP; if (xi.xish.ptype & XI_ENV_ENABLED) g->flags |= ENV_PANNING; if (xi.xish.ptype & XI_ENV_SUSTAIN) g->flags |= ENV_PANSUSTAIN; if (xi.xish.ptype & XI_ENV_LOOP) g->flags |= ENV_PANLOOP; prevtick = -1; // Copy envelopes into instrument for (k = 0; k < xi.xish.vnum; k++) { if (xi.xish.venv[k].ticks < prevtick) prevtick++; else prevtick = xi.xish.venv[k].ticks; g->vol_env.ticks[k] = prevtick; g->vol_env.values[k] = xi.xish.venv[k].val; } prevtick = -1; for (k = 0; k < xi.xish.pnum; k++) { if (xi.xish.penv[k].ticks < prevtick) prevtick++; else prevtick = xi.xish.penv[k].ticks; g->pan_env.ticks[k] = prevtick; g->pan_env.values[k] = xi.xish.penv[k].val; } g->vol_env.loop_start = xi.xish.vloops; g->vol_env.loop_end = xi.xish.vloope; g->vol_env.sustain_start = xi.xish.vsustain; g->vol_env.nodes = xi.xish.vnum; g->pan_env.loop_start = xi.xish.ploops; g->pan_env.loop_end = xi.xish.ploope; g->pan_env.sustain_start = xi.xish.psustain; g->pan_env.nodes = xi.xish.pnum; xi.xish.volfade = bswapLE16(xi.xish.volfade); xi.xish.nsamples = bswapLE16(xi.xish.nsamples); // Determine where the sample data starts. smpdataoffset = sizeof(xi) + (sizeof(struct xm_sample_header) * xi.xish.nsamples); for (k = 0; k < xi.xish.nsamples; k++) { struct xm_sample_header xmss; song_sample_t *smp; unsigned int rs, samplesize, n; if (!xm_sample_header_read(&xmss, fp)) break; xmss.samplen = bswapLE32(xmss.samplen); xmss.loopstart = bswapLE32(xmss.loopstart); xmss.looplen = bswapLE32(xmss.looplen); rs = SF_LE | SF_PCMD; // endianness; encoding rs |= (xmss.type & 0x20) ? SF_SS : SF_M; // channels rs |= (xmss.type & 0x10) ? SF_16 : SF_8; // bits if (xmss.type & 0x10) { xmss.looplen >>= 1; xmss.loopstart >>= 1; xmss.samplen >>= 1; } if (xmss.type & 0x20) { xmss.looplen >>= 1; xmss.loopstart >>= 1; xmss.samplen >>= 1; } if (xmss.loopstart >= xmss.samplen) xmss.type &= ~3; xmss.looplen += xmss.loopstart; if (xmss.looplen > xmss.samplen) xmss.looplen = xmss.samplen; if (!xmss.looplen) xmss.type &= ~3; n = instrument_loader_sample(&ii, k + 1); smp = song_get_sample(n); smp->flags = 0; memcpy(smp->name, xmss.name, 22); smp->name[21] = '\0'; samplesize = xmss.samplen; smp->length = samplesize; smp->loop_start = xmss.loopstart; smp->loop_end = xmss.looplen; if (smp->loop_end < smp->loop_start) smp->loop_end = smp->length; if (smp->loop_start >= smp->loop_end) smp->loop_start = smp->loop_end = 0; switch (xmss.type & 3) { case 3: case 2: smp->flags |= CHN_PINGPONGLOOP; SCHISM_FALLTHROUGH; case 1: smp->flags |= CHN_LOOP; break; } smp->volume = xmss.vol << 2; if (smp->volume > 256) smp->volume = 256; smp->global_volume = 64; smp->panning = xmss.pan; smp->flags |= CHN_PANNING; smp->vib_type = xi.xish.vibtype; smp->vib_speed = MIN(xi.xish.vibrate, 64); smp->vib_depth = MIN(xi.xish.vibdepth, 32); if (xi.xish.vibrate | xi.xish.vibdepth) { if (xi.xish.vibsweep) { int s = _muldivr(smp->vib_depth, 256, xi.xish.vibsweep); smp->vib_rate = CLAMP(s, 0, 255); } else { smp->vib_rate = 255; } } smp->c5speed = transpose_to_frequency(xmss.relnote, xmss.finetune); if(smp->length) { // Save our spot, read the sample data, then jump back. uint64_t smpheaderoffset; smpheaderoffset = slurp_tell(fp); slurp_seek(fp, smpdataoffset, SEEK_SET); smpdataoffset += csf_read_sample(current_song->samples + n, rs, fp); slurp_seek(fp, smpheaderoffset, SEEK_SET); } } return 1; } /* ------------------------------------------------------------------------ */ int fmt_xi_save_instrument(disko_t *fp, song_t *song, song_instrument_t *ins) { struct xi_file_header xi = {0}; song_sample_t *smp; int k; /* fill in sample numbers, epicly stolen from the iti code */ int xi_map[255]; int xi_invmap[255]; int xi_nalloc = 0; for (int j = 0; j < 255; j++) xi_map[j] = -1; for (int j = 0; j < 96; j++) { int o = ins->sample_map[j + 12]; if (o > 0 && o < 255 && xi_map[o] == -1) { xi_map[o] = xi_nalloc; xi_invmap[xi_nalloc] = o; xi_nalloc++; } xi.xish.snum[j] = xi_map[o]; } if (xi_nalloc < 1) return SAVE_FILE_ERROR; /* now add header things */ memcpy(xi.header, "Extended Instrument: ", sizeof(xi.header)); memcpy(xi.name, ins->name, MIN(sizeof(xi.name), sizeof(ins->name))); xi.magic = 0x1A; memcpy(xi.tracker, "Schism Tracker", 14); xi.version = bswapLE16(0x0102); /* envelope type */ if (ins->flags & ENV_VOLUME) xi.xish.vtype |= XI_ENV_ENABLED; if (ins->flags & ENV_VOLSUSTAIN) xi.xish.vtype |= XI_ENV_SUSTAIN; if (ins->flags & ENV_VOLLOOP) xi.xish.vtype |= XI_ENV_LOOP; if (ins->flags & ENV_PANNING) xi.xish.ptype |= XI_ENV_ENABLED; if (ins->flags & ENV_PANSUSTAIN) xi.xish.ptype |= XI_ENV_SUSTAIN; if (ins->flags & ENV_PANLOOP) xi.xish.ptype |= XI_ENV_LOOP; xi.xish.vloops = ins->vol_env.loop_start; xi.xish.vloope = ins->vol_env.loop_end; xi.xish.vsustain = ins->vol_env.sustain_start; xi.xish.vnum = ins->vol_env.nodes; xi.xish.vnum = MIN(xi.xish.vnum, 12); xi.xish.ploops = ins->pan_env.loop_start; xi.xish.ploope = ins->pan_env.loop_end; xi.xish.psustain = ins->pan_env.sustain_start; xi.xish.pnum = ins->pan_env.nodes; xi.xish.pnum = MIN(xi.xish.pnum, 12); /* envelope nodes */ for (k = 0; k < xi.xish.vnum; k++) { xi.xish.venv[k].ticks = ins->vol_env.ticks[k]; xi.xish.venv[k].val = ins->vol_env.values[k]; } /* envelope nodes */ for (k = 0; k < xi.xish.pnum; k++) { xi.xish.penv[k].ticks = ins->pan_env.ticks[k]; xi.xish.penv[k].val = ins->pan_env.values[k]; } // bswap all volume and panning envelope points for (k = 0; k < 12; k++) { xi.xish.venv[k].ticks = bswapLE16(xi.xish.venv[k].ticks); xi.xish.venv[k].val = bswapLE16(xi.xish.venv[k].val); xi.xish.penv[k].ticks = bswapLE16(xi.xish.penv[k].ticks); xi.xish.penv[k].val = bswapLE16(xi.xish.penv[k].val); } /* XXX volfade */ /* Tuesday's coming! Did you bring your coat? */ memcpy(xi.xish.reserved1, "ILiveInAGiantBucket", 19); xi.xish.nsamples = xi_nalloc; if (xi_nalloc > 0) { /* nab the first sample's info */ smp = song->samples + xi_invmap[0]; xi.xish.vibtype = smp->vib_type; xi.xish.vibrate = MIN(smp->vib_speed, 63); xi.xish.vibdepth = MIN(smp->vib_depth, 15); if (xi.xish.vibrate | xi.xish.vibdepth) { if (smp->vib_rate) { int s = _muldivr(smp->vib_depth, 256, smp->vib_rate); xi.xish.vibsweep = CLAMP(s, 0, 255); } else { xi.xish.vibsweep = 255; } } } /* now write the data... */ xi_file_header_write(&xi, fp); for (k = 0; k < xi_nalloc; k++) { int o = xi_invmap[k]; struct xm_sample_header xmss; smp = song->samples + o; memcpy(xmss.name, smp->name, MIN(sizeof(xmss.name), sizeof(smp->name))); xmss.samplen = smp->length; xmss.loopstart = smp->loop_start; xmss.looplen = smp->loop_end - smp->loop_start; if (smp->flags & CHN_PINGPONGLOOP) { xmss.type |= 0x03; } else if (smp->flags & CHN_LOOP) { xmss.type |= 0x01; } if (smp->flags & CHN_16BIT) { xmss.type |= 0x10; xmss.looplen <<= 1; xmss.loopstart <<= 1; xmss.samplen <<= 1; } if (smp->flags & CHN_STEREO) { xmss.type |= 0x20; xmss.looplen <<= 1; xmss.loopstart <<= 1; xmss.samplen <<= 1; } xmss.samplen = bswapLE32(xmss.samplen); xmss.loopstart = bswapLE32(xmss.loopstart); xmss.looplen = bswapLE32(xmss.looplen); xmss.vol = smp->volume >> 2; if(ins->flags & ENV_SETPANNING) { xmss.pan = ins->panning; } else if(smp->flags & CHN_PANNING) { xmss.pan = smp->panning; } else { // Default panning enabled for instrument and sample--pan to center. xmss.pan = 0x80; } int32_t transp = frequency_to_transpose(smp->c5speed); xmss.relnote = transp / 128; xmss.finetune = transp % 128; xm_sample_header_write(&xmss, fp); } for (k = 0; k < xi_nalloc; k++) { int o = xi_invmap[k]; smp = song->samples + o; csf_write_sample(fp, smp, SF_LE | SF_PCMD | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8) | ((smp->flags & CHN_STEREO) ? SF_SS : SF_M), UINT32_MAX); } return SAVE_SUCCESS; } schismtracker-20250313/fmt/xm.c000066400000000000000000000721141476471630300162130ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "slurp.h" #include "fmt.h" #include "mem.h" #include "it.h" // needed for get_effect_char (purely informational) #include "log.h" #include "player/sndfile.h" #include "player/tables.h" // gloriously stolen from xmp struct xm_file_header { uint8_t id[17]; // ID text: "Extended module: " uint8_t name[20]; // Module name, padded with zeroes uint8_t doseof; // 0x1a uint8_t tracker[20]; // Tracker name uint16_t version; // Version number, minor-major uint32_t headersz; // Header size uint16_t songlen; // Song length (in patten order table) uint16_t restart; // Restart position uint16_t channels; // Number of channels (2,4,6,8,10,...,32) uint16_t patterns; // Number of patterns (max 256) uint16_t instruments; // Number of instruments (max 128) uint16_t flags; // bit 0: 0=Amiga freq table, 1=Linear uint16_t tempo; // Default tempo uint16_t bpm; // Default BPM }; /* --------------------------------------------------------------------- */ static int read_header_xm(struct xm_file_header *hdr, slurp_t *fp) { #define READ_VALUE(name) \ if (slurp_read(fp, &hdr->name, sizeof(hdr->name)) != sizeof(hdr->name)) return 0 READ_VALUE(id); READ_VALUE(name); READ_VALUE(doseof); READ_VALUE(tracker); READ_VALUE(version); READ_VALUE(headersz); READ_VALUE(songlen); READ_VALUE(restart); READ_VALUE(channels); READ_VALUE(patterns); READ_VALUE(instruments); READ_VALUE(flags); READ_VALUE(tempo); READ_VALUE(bpm); #undef READ_VALUE if (memcmp(hdr->id, "Extended Module: ", sizeof(hdr->id)) || hdr->doseof != 0x1a) return 0; /* now byteswap */ hdr->version = bswapLE16(hdr->version); hdr->headersz = bswapLE32(hdr->headersz); hdr->songlen = bswapLE16(hdr->songlen); hdr->restart = bswapLE16(hdr->restart); hdr->channels = bswapLE16(hdr->channels); hdr->patterns = bswapLE16(hdr->patterns); hdr->instruments = bswapLE16(hdr->instruments); hdr->flags = bswapLE16(hdr->flags); hdr->tempo = bswapLE16(hdr->tempo); hdr->bpm = bswapLE16(hdr->bpm); if (hdr->channels > MAX_CHANNELS) return 0; return 1; } /* --------------------------------------------------------------------- */ int fmt_xm_read_info(dmoz_file_t *file, slurp_t *fp) { struct xm_file_header hdr; if (!read_header_xm(&hdr, fp)) return 0; file->description = "Fast Tracker 2 Module"; file->type = TYPE_MODULE_XM; /*file->extension = str_dup("xm");*/ file->title = strn_dup((const char *)hdr.name, sizeof(hdr.name)); return 1; } /* --------------------------------------------------------------------------------------------------------- */ static uint8_t autovib_import[8] = { VIB_SINE, VIB_SQUARE, VIB_RAMP_DOWN, // actually ramp up VIB_RAMP_DOWN, VIB_RANDOM, // default to sine VIB_SINE, VIB_SINE, VIB_SINE, }; static void load_xm_patterns(song_t *song, struct xm_file_header *hdr, slurp_t *fp) { int pat, row, chan; uint32_t patlen; uint8_t b; uint16_t rows; uint16_t bytes; size_t end; // should be same data type as slurp_t's length song_note_t *note; unsigned int lostpat = 0; unsigned int lostfx = 0; for (pat = 0; pat < hdr->patterns; pat++) { slurp_read(fp, &patlen, 4); // = 8/9 patlen = bswapLE32(patlen); b = slurp_getc(fp); // = 0 if (hdr->version == 0x0102) { rows = slurp_getc(fp) + 1; patlen++; // fake it so that alignment works properly. } else { slurp_read(fp, &rows, 2); rows = bswapLE16(rows); } slurp_read(fp, &bytes, 2); bytes = bswapLE16(bytes); // if 0, pattern is empty slurp_seek(fp, patlen - 9, SEEK_CUR); // probably a no-op if (!rows) continue; if (pat >= MAX_PATTERNS) { if (bytes) lostpat++; slurp_seek(fp, bytes, SEEK_CUR); continue; } note = song->patterns[pat] = csf_allocate_pattern(rows); song->pattern_size[pat] = song->pattern_alloc_size[pat] = rows; if (!bytes) continue; // hack to avoid having to count bytes when reading end = slurp_tell(fp) + bytes; end = MIN(end, slurp_length(fp)); for (row = 0; row < rows; row++, note += MAX_CHANNELS - hdr->channels) { for (chan = 0; slurp_tell(fp) < (int64_t)end && chan < hdr->channels; chan++, note++) { b = slurp_getc(fp); if (b & 128) { if (b & 1) note->note = slurp_getc(fp); if (b & 2) note->instrument = slurp_getc(fp); if (b & 4) note->volparam = slurp_getc(fp); if (b & 8) note->effect = slurp_getc(fp); if (b & 16) note->param = slurp_getc(fp); } else { note->note = b; note->instrument = slurp_getc(fp); note->volparam = slurp_getc(fp); note->effect = slurp_getc(fp); note->param = slurp_getc(fp); } // translate everything if (note->note > 0 && note->note < 97) { note->note += 12; } else if (note->note == 97) { /* filter out instruments on noteoff; * this is what IT's importer does, because * hanging the note is *definitely* not * intended behavior * * see: MPT test case noteoff3.it */ note->note = NOTE_OFF; note->instrument = 0; } else { note->note = NOTE_NONE; } if (note->effect || note->param) csf_import_mod_effect(note, 1); if (note->instrument == 0xff) note->instrument = 0; // now that the mundane stuff is over with... NOW IT'S TIME TO HAVE SOME FUN! // the volume column is initially imported as "normal" effects, juggled around // in order to make it more IT-like, and then converted into volume-effects /* IT puts all volume column effects into the effect column if there's not an effect there already; in the case of two set-volume effects, the one in the effect column takes precedence. set volume with values > 64 are clipped to 64 pannings are imported as S8x, unless there's an effect in which case it's translated to a volume-column panning value. volume and panning slides with zero value (+0, -0, etc.) still translate to an effect -- even though volslides don't have effect memory in FT2. */ switch (note->volparam >> 4) { case 5: // 0x50 = volume 64, 51-5F = nothing if (note->volparam == 0x50) { case 1: case 2: case 3: case 4: // Set volume Value-$10 note->voleffect = FX_VOLUME; note->volparam -= 0x10; break; } // NOTE: falls through from case 5 when vol != 0x50 SCHISM_FALLTHROUGH; case 0: // Do nothing note->voleffect = FX_NONE; note->volparam = 0; break; case 6: // Volume slide down note->volparam &= 0xf; if (note->volparam) note->voleffect = FX_VOLUMESLIDE; break; case 7: // Volume slide up note->volparam = (note->volparam & 0xf) << 4; if (note->volparam) note->voleffect = FX_VOLUMESLIDE; break; case 8: // Fine volume slide down note->volparam &= 0xf; if (note->volparam) { if (note->volparam == 0xf) note->volparam = 0xe; // DFF is fine slide up... note->volparam |= 0xf0; note->voleffect = FX_VOLUMESLIDE; } break; case 9: // Fine volume slide up note->volparam = (note->volparam & 0xf) << 4; if (note->volparam) { note->volparam |= 0xf; note->voleffect = FX_VOLUMESLIDE; } break; case 10: // Set vibrato speed /* ARGH. this doesn't actually CAUSE vibrato - it only sets the value! i don't think there's a way to handle this correctly and sanely, so i'll just do what impulse tracker and mpt do... (probably should write a warning saying the song might not be played correctly) */ note->volparam = (note->volparam & 0xf) << 4; note->voleffect = FX_VIBRATO; break; case 11: // Vibrato note->volparam &= 0xf; note->voleffect = FX_VIBRATO; break; case 12: // Set panning note->voleffect = FX_SPECIAL; note->volparam = 0x80 | (note->volparam & 0xf); break; case 13: // Panning slide left // in FT2, <0 sets the panning to far left on the SECOND tick // this is "close enough" (except at speed 1) note->volparam &= 0xf; if (note->volparam) { note->volparam <<= 4; note->voleffect = FX_PANNINGSLIDE; } else { note->volparam = 0x80; note->voleffect = FX_SPECIAL; } break; case 14: // Panning slide right note->volparam &= 0xf; if (note->volparam) note->voleffect = FX_PANNINGSLIDE; break; case 15: // Tone porta note->volparam = (note->volparam & 0xf) << 4; note->voleffect = FX_TONEPORTAMENTO; break; } if (note->effect == FX_KEYOFF && note->param == 0) { // FT2 ignores notes and instruments next to a K00 note->note = NOTE_NONE; note->instrument = 0; } else if (note->note == NOTE_OFF && note->effect == FX_SPECIAL && (note->param >> 4) == 0xd) { // note off with a delay ignores the note off, and also // ignores set-panning (but not other effects!) // (actually the other vol. column effects happen on the // first tick with ft2, but this is "close enough" i think) note->note = NOTE_NONE; note->instrument = 0; // note: haven't fixed up volumes yet if (note->voleffect == FX_PANNING) { note->voleffect = FX_NONE; note->volparam = 0; note->effect = FX_NONE; note->param = 0; } } if (note->effect == FX_NONE && note->voleffect != FX_NONE) { // put the lotion in the basket swap_effects(note); } else if (note->effect == note->voleffect) { // two of the same kind of effect => ignore the volume column // (note that ft2 behaves VERY strangely with Mx + 3xx combined -- // but i'll ignore that nonsense and just go by xm.txt here because // it's easier :) note->voleffect = note->volparam = 0; } if (note->effect == FX_VOLUME) { // try to move set-volume into the volume column swap_effects(note); } // now try to rewrite the volume column, if it's not possible then see if we // can do so after swapping them. // this is a terrible hack -- don't write code like this, kids :) int n; for (n = 0; n < 4; n++) { // (n >> 1) will be 0/1, indicating our desire to j... j... jam it in if (convert_voleffect_of(note, n >> 1)) { n = 5; // it'd be nice if c had a for...else like python break; } // nope that didn't work, switch them around swap_effects(note); } if (n < 5) { // Need to throw one out. if (effect_weight[note->voleffect] > effect_weight[note->effect]) { note->effect = note->voleffect; note->param = note->volparam; } //log_appendf(4, "Warning: pat%u row%u chn%u: lost effect %c%02X", // pat, row, chan + 1, get_effect_char(note->voleffect), note->volparam); note->voleffect = note->volparam = 0; lostfx++; } /* some XM effects that schism probably won't handle decently: 0xy / Jxy - this one is *totally* screwy, see milkytracker source for details :) (NOT documented -- in fact, all the documentation claims that it should simply play note -> note+x -> note+y -> note like any other tracker, but that sure isn't what FT2 does...) Axy / Dxy - it's probably not such a good idea to move these between the volume and effect column, since there's a chance it might screw stuff up since the volslides don't share memory (in either .it or .xm) -- e.g. ... .. .. DF0 ... .. .. D04 ... .. .. D00 is quite different from ... .. .. DF0 ... .. D4 .00 ... .. .. D00 But oh well. Works "enough" for now. [Note: IT doesn't even try putting volslide into the volume column.] E6x / SBx - ridiculously broken; it screws up the pattern break row if E60 isn't at the start of the pattern -- this is fairly well known by FT2 users, but curiously absent from its "known bugs" list E9x / Q0x - actually E9x isn't like Q0x at all... it's really stupid, I give up. hope no one wants to listen to XM files with retrig. ECx / SCx - doesn't actually CUT the note, it just sets volume to zero at tick x (this is documented) */ } } } if (lostfx) log_appendf(4, " Warning: %u effect%s dropped", lostfx, lostfx == 1 ? "" : "s"); if (lostpat) log_appendf(4, " Warning: Too many patterns in song (%u skipped)", lostpat); } static void load_xm_samples(song_sample_t *first, int total, slurp_t *fp) { song_sample_t *smp = first; int ns; // dontyou: 20 samples starting at 26122 // trnsmix: 31 samples starting at 61946 for (ns = 0; ns < total; ns++, smp++) { if (!smp->length) continue; if (smp->flags & CHN_16BIT) { smp->length >>= 1; smp->loop_start >>= 1; smp->loop_end >>= 1; } if (smp->flags & CHN_STEREO) { smp->length >>= 1; smp->loop_start >>= 1; smp->loop_end >>= 1; } if (smp->adlib_bytes[0] != 0xAD) { csf_read_sample(smp, SF_LE | ((smp->flags & CHN_STEREO) ? SF_SS : SF_M) | SF_PCMD | ((smp->flags & CHN_16BIT) ? SF_16 : SF_8), fp); } else { smp->adlib_bytes[0] = 0; csf_read_sample(smp, SF_8 | SF_M | SF_LE | SF_PCMD16, fp); } } } // Volume/panning envelope loop fix // FT2 leaves out the final tick of the envelope loop causing a slight discrepancy when loading XI instruments directly // from an XM file into Schism // Works by adding a new end node one tick behind the previous loop end by linearly interpolating (or selecting // an existing node there). // Runs generally the same for either type of envelope (vol/pan), pointed to by s_env. static void fix_xm_envelope_loop(song_envelope_t *s_env, int sustain_flag) { int n; float v; if (s_env->ticks[s_env->loop_end - 1] == s_env->ticks[s_env->loop_end] - 1) { // simplest case: prior node is one tick behind already, set envelope end index s_env->loop_end--; return; } // shift each node from loop_end right one index to insert a new node // on first iteration n is one more that existing # of nodes for (n = s_env->nodes; n > s_env->loop_end; n--) { s_env->ticks[n] = s_env->ticks[n - 1]; s_env->values[n] = s_env->values[n - 1]; } // increment the node count for the previous shift s_env->nodes++; // define the new node at the previous loop_end index, one tick behind and interpolated correctly s_env->ticks[s_env->loop_end]--; v = (float)(s_env->values[s_env->loop_end + 1] - s_env->values[s_env->loop_end - 1]); v *= (float)(s_env->ticks[s_env->loop_end] - s_env->ticks[s_env->loop_end - 1]); v /= (float)(s_env->ticks[s_env->loop_end + 1] - s_env->ticks[s_env->loop_end - 1]); // alter the float so it rounds to the nearest integer when type casted v = (v >= 0.0f) ? v + 0.5f : v - 0.5f; s_env->values[s_env->loop_end] = (uint8_t)v + s_env->values[s_env->loop_end - 1]; // adjust the sustain loop as needed if (sustain_flag && s_env->sustain_start >= s_env->loop_end) { s_env->sustain_start++; s_env->sustain_end++; } } enum { ID_CONFIRMED = 0x01, // confirmed with inst/sample header sizes ID_FT2GENERIC = 0x02, // "FastTracker v2.00", but fasttracker has NOT been ruled out ID_OLDMODPLUG = 0x04, // "FastTracker v 2.00" ID_OTHER = 0x08, // something we don't know, testing for digitrakker. ID_FT2CLONE = 0x10, // NOT FT2: itype changed between instruments, or \0 found in song title ID_MAYBEMODPLUG = 0x20, // some FT2-ish thing, possibly MPT. ID_DIGITRAK = 0x40, // probably digitrakker ID_UNKNOWN = 0x80 | ID_CONFIRMED, // ????? }; // TODO: try to identify packers (boobiesqueezer?) // this also does some tracker detection // return value is the number of samples that need to be loaded later (for old xm files) static int load_xm_instruments(song_t *song, struct xm_file_header *hdr, slurp_t *fp) { int n, ni, ns; int abssamp = 1; // "real" sample int32_t ihdr, shdr; // instrument/sample header size (yes these should be signed) uint8_t b; uint16_t w; uint32_t d; int detected; int itype = -1; uint8_t srsvd_or = 0; // bitwise-or of all sample reserved bytes if (strncmp(song->tracker_id, "FastTracker ", 12) == 0) { if (hdr->headersz == 276 && strncmp(song->tracker_id + 12, "v2.00 ", 8) == 0) { detected = ID_FT2GENERIC | ID_MAYBEMODPLUG; /* there is very little change between different versions of FT2, making it * very difficult (maybe even impossible) to detect them, so here we just * say it's either FT2 or a compatible tracker */ strcpy(song->tracker_id + 12, "2 or compatible"); } else if (strncmp(song->tracker_id + 12, "v 2.00 ", 8) == 0) { /* alpha and beta are handled later */ detected = ID_OLDMODPLUG | ID_CONFIRMED; strcpy(song->tracker_id, "ModPlug Tracker 1.0"); } else { // definitely NOT FastTracker, so let's clear up that misconception detected = ID_UNKNOWN; } } else if (strncmp(song->tracker_id, "*Converted ", 11) == 0 || strspn(song->tracker_id, " ") == 20) { // this doesn't catch any cases where someone typed something into the field :( detected = ID_OTHER | ID_DIGITRAK; } else { detected = ID_OTHER; } // FT2 pads the song title with spaces, some other trackers don't if (detected & ID_FT2GENERIC && memchr(song->title, '\0', 20) != NULL) detected = ID_FT2CLONE | ID_MAYBEMODPLUG; for (ni = 1; ni <= hdr->instruments; ni++) { size_t i; int vtype, vsweep, vdepth, vrate; song_instrument_t *ins; uint16_t nsmp; slurp_read(fp, &ihdr, 4); ihdr = bswapLE32(ihdr); if (ni >= MAX_INSTRUMENTS) { // TODO: try harder log_appendf(4, " Warning: Too many instruments in file"); break; } song->instruments[ni] = ins = csf_allocate_instrument(); slurp_read(fp, ins->name, 22); ins->name[22] = '\0'; if ((detected & ID_DIGITRAK) && memchr(ins->name, '\0', 22) != NULL) detected &= ~ID_DIGITRAK; b = slurp_getc(fp); if (itype == -1) { itype = b; } else if (itype != b && (detected & ID_FT2GENERIC)) { // FT2 writes some random junk for the instrument type field, // but it's always the SAME junk for every instrument saved. detected = (detected & ~ID_FT2GENERIC) | ID_FT2CLONE | ID_MAYBEMODPLUG; } slurp_read(fp, &nsmp, 2); nsmp = bswapLE16(nsmp); slurp_read(fp, &shdr, 4); shdr = bswapLE32(shdr); if (detected == ID_OLDMODPLUG) { detected = ID_CONFIRMED; if (ihdr == 245) { strcat(song->tracker_id, " alpha"); } else if (ihdr == 263) { strcat(song->tracker_id, " beta"); } else { // WEIRD!! detected = ID_UNKNOWN; } } if (!nsmp) { // lucky day! it's pretty easy to identify tracker if there's a blank instrument if (!(detected & ID_CONFIRMED)) { if ((detected & ID_MAYBEMODPLUG) && ihdr == 263 && shdr == 0) { detected = ID_CONFIRMED; strcpy(song->tracker_id, "Modplug Tracker"); } else if ((detected & ID_DIGITRAK) && ihdr != 29) { detected &= ~ID_DIGITRAK; } else if ((detected & (ID_FT2CLONE | ID_FT2GENERIC)) && ihdr != 33) { // Sure isn't FT2. // note: FT2 NORMALLY writes shdr=40 for all samples, but sometimes it // just happens to write random garbage there instead. surprise! detected = ID_UNKNOWN; } } // some adjustment hack from xmp. slurp_seek(fp, ihdr - 33, SEEK_CUR); continue; } for (n = 0; n < 12; n++) ins->note_map[n] = n + 1; for (; n < 96 + 12; n++) { ins->note_map[n] = n + 1; ins->sample_map[n] = slurp_getc(fp) + abssamp; } for (; n < 120; n++) ins->note_map[n] = n + 1; // envelopes. XM stores this in a hilariously bad format struct { song_envelope_t *env; uint32_t envflag; uint32_t envsusloopflag; uint32_t envloopflag; } envs[2] = { {NULL, ENV_VOLUME, ENV_VOLSUSTAIN, ENV_VOLLOOP}, {NULL, ENV_PANNING, ENV_PANSUSTAIN, ENV_PANLOOP}, }; // openwatcom bug envs[0].env = &ins->vol_env; envs[1].env = &ins->pan_env; for (i = 0; i < ARRAY_SIZE(envs); i++) { uint16_t prevtick = 0; for (n = 0; n < 12; n++) { slurp_read(fp, &w, 2); // tick w = bswapLE16(w); if (n > 0 && w < prevtick && !(w & 0xFF00)) { // libmikmod code says: "Some broken XM editing program will only save the low byte of the position // value. Try to compensate by adding the missing high byte." // Note: MPT 1.07's XI instrument saver omitted the high byte of envelope nodes. // This might be the source for some broken envelopes in IT and XM files. w |= (prevtick & 0xFF00U); if (w < prevtick) w += 0x100; } envs[i].env->ticks[n] = prevtick = w; slurp_read(fp, &w, 2); // value w = bswapLE16(w); envs[i].env->values[n] = MIN(w, 64); } } for (i = 0; i < ARRAY_SIZE(envs); i++) { b = slurp_getc(fp); envs[i].env->nodes = CLAMP(b, 2, 12); } for (i = 0; i < ARRAY_SIZE(envs); i++) { envs[i].env->sustain_start = envs[i].env->sustain_end = slurp_getc(fp); envs[i].env->loop_start = slurp_getc(fp); envs[i].env->loop_end = slurp_getc(fp); } for (i = 0; i < ARRAY_SIZE(envs); i++) { b = slurp_getc(fp); if ((b & 1) && envs[i].env->nodes > 0) ins->flags |= envs[i].envflag; if (b & 2) ins->flags |= envs[i].envsusloopflag; if (b & 4) ins->flags |= envs[i].envloopflag; } vtype = autovib_import[slurp_getc(fp) & 0x7]; vsweep = slurp_getc(fp); vdepth = slurp_getc(fp); vdepth = MIN(vdepth, 32); vrate = slurp_getc(fp); vrate = MIN(vrate, 64); /* translate the sweep value */ if (vrate | vdepth) { if (vsweep) { int s = _muldivr(vdepth, 256, vsweep); vsweep = CLAMP(s, 0, 255); } else { vsweep = 255; } } slurp_read(fp, &w, 2); ins->fadeout = bswapLE16(w); if (ins->flags & ENV_VOLUME) { // fix note-fade if either volume loop is disabled or both end nodes are equal if (!(ins->flags & ENV_VOLLOOP) || ins->vol_env.loop_start == ins->vol_env.loop_end) { ins->vol_env.loop_start = ins->vol_env.loop_end = ins->vol_env.nodes - 1; } else { // fix volume envelope fix_xm_envelope_loop(&ins->vol_env, ins->flags & ENV_VOLSUSTAIN); } if (!(ins->flags & ENV_VOLSUSTAIN)) ins->vol_env.sustain_start = ins->vol_env.sustain_end = ins->vol_env.nodes - 1; ins->flags |= ENV_VOLLOOP | ENV_VOLSUSTAIN; // FT2 loops the first loop found, while IT always does the sustain loop first. // There's not much we can do in this case except for just disabling the sustain // loop :) // // TODO: investigate, see if this breaks anything else if (ins->vol_env.sustain_start > ins->vol_env.loop_start) ins->flags &= ~ENV_VOLSUSTAIN; } else { // fix note-off ins->vol_env.ticks[0] = 0; ins->vol_env.ticks[1] = 1; ins->vol_env.values[0] = 64; ins->vol_env.values[1] = 0; ins->vol_env.nodes = 2; ins->vol_env.sustain_start = ins->vol_env.sustain_end = 0; ins->flags |= ENV_VOLUME | ENV_VOLSUSTAIN; } if ((ins->flags & ENV_PANNING) && (ins->flags & ENV_PANLOOP)) { if (ins->pan_env.loop_start == ins->pan_env.loop_end) { // panning is unused in XI ins->flags &= ~ENV_PANLOOP; } else { // fix panning envelope fix_xm_envelope_loop(&ins->pan_env, ins->flags & ENV_PANSUSTAIN); } } // some other things... ins->panning = 128; ins->global_volume = 128; ins->pitch_pan_center = 60; // C-5? /* here we're looking at what the ft2 spec SAYS are two reserved bytes. most programs blindly follow ft2's saving and add 22 zero bytes at the end (making the instrument header size 263 bytes), but ft2 is really writing the midi settings there, at least in the first 7 bytes. (as far as i can tell, the rest of the bytes are always zero) */ int midi_enabled = slurp_getc(fp); // instrument midi enable = 0/1 b = slurp_getc(fp); // midi transmit channel = 0-15 ins->midi_channel_mask = (midi_enabled == 1) ? 1 << MIN(b, 15) : 0; slurp_read(fp, &w, 2); // midi program = 0-127 w = bswapLE16(w); ins->midi_program = MIN(w, 127); slurp_read(fp, &w, 2); // bender range (halftones) = 0-36 if (slurp_getc(fp) == 1) ins->global_volume = 0; // mute computer = 0/1 slurp_seek(fp, ihdr - 248, SEEK_CUR); for (ns = 0; ns < nsmp; ns++) { int8_t relnote, finetune; song_sample_t *smp; if (abssamp + ns >= MAX_SAMPLES) { // TODO: try harder (fill unused sample slots) log_appendf(4, " Warning: Too many samples in file"); break; } smp = song->samples + abssamp + ns; slurp_read(fp, &d, 4); smp->length = bswapLE32(d); slurp_read(fp, &d, 4); smp->loop_start = bswapLE32(d); slurp_read(fp, &d, 4); smp->loop_end = bswapLE32(d) + smp->loop_start; smp->volume = slurp_getc(fp); smp->volume = MIN(64, smp->volume); smp->volume *= 4; //mphack smp->global_volume = 64; smp->flags = CHN_PANNING; finetune = slurp_getc(fp); b = slurp_getc(fp); // flags if (smp->loop_start >= smp->loop_end) b &= ~3; // that loop sucks, turn it off switch (b & 3) { /* NOTE: all cases fall through here. In FT2, type 3 is played as pingpong, but the GUI doesn't show any selected loop type. Apparently old MPT versions wrote 3 for pingpong loops, but that doesn't seem to be reliable enough to declare "THIS WAS MPT" because it seems FT2 would also SAVE that broken data after loading an instrument with loop type 3 was set. I have no idea. */ case 3: case 2: smp->flags |= CHN_PINGPONGLOOP; SCHISM_FALLTHROUGH; case 1: smp->flags |= CHN_LOOP; break; } if (b & 0x10) { smp->flags |= CHN_16BIT; // NOTE length and loop start/end are adjusted later } if (b & 0x20) { smp->flags |= CHN_STEREO; // NOTE length and loop start/end are adjusted later } smp->panning = slurp_getc(fp); //mphack, should be adjusted to 0-64 relnote = slurp_getc(fp); smp->c5speed = transpose_to_frequency(relnote, finetune); uint8_t reserved = slurp_getc(fp); srsvd_or |= reserved; if (reserved == 0xAD && !(b & 0x10) && !(b & 0x20)) { smp->adlib_bytes[0] = 0xAD; // temp storage } slurp_read(fp, smp->name, 22); smp->name[22] = '\0'; if (detected & ID_DIGITRAK && memchr(smp->name, '\0', 22) != NULL) detected &= ~ID_DIGITRAK; smp->vib_type = vtype; smp->vib_rate = vsweep; smp->vib_depth = vdepth; smp->vib_speed = vrate; } if (hdr->version == 0x0104) load_xm_samples(song->samples + abssamp, ns, fp); abssamp += ns; // if we ran out of samples, stop trying to load instruments // (note this will break things with xm format ver < 0x0104!) if (ns != nsmp) break; } if (detected & ID_FT2CLONE) { if (srsvd_or == 0) { strcpy(song->tracker_id, "Modplug Tracker"); } else { // PlayerPro: itype and smp rsvd are both always zero // no idea how to identify it elsewise. strcpy(song->tracker_id, "FastTracker clone"); } } else if ((detected & ID_DIGITRAK) && srsvd_or == 0 && (itype ? itype : -1) == -1) { strcpy(song->tracker_id, "Digitrakker"); } else if (detected == ID_UNKNOWN) { strcpy(song->tracker_id, "Unknown tracker"); } return (hdr->version < 0x0104) ? abssamp : 0; } int fmt_xm_load_song(song_t *song, slurp_t *fp, SCHISM_UNUSED unsigned int lflags) { struct xm_file_header hdr; int n; uint8_t b; if (!read_header_xm(&hdr, fp)) return LOAD_UNSUPPORTED; memcpy(song->title, hdr.name, 20); song->title[20] = '\0'; memcpy(song->tracker_id, hdr.tracker, 20); song->tracker_id[20] = '\0'; if (hdr.flags & 1) song->flags |= SONG_LINEARSLIDES; song->flags |= SONG_ITOLDEFFECTS | SONG_COMPATGXX | SONG_INSTRUMENTMODE; song->initial_speed = MIN(hdr.tempo, 255); if (!song->initial_speed) song->initial_speed = 255; song->initial_tempo = CLAMP(hdr.bpm, 31, 255); song->initial_global_volume = 128; song->mixing_volume = 48; for (n = 0; n < hdr.channels; n++) song->channels[n].panning = 32 * 4; //mphack for (; n < MAX_CHANNELS; n++) song->channels[n].flags |= CHN_MUTE; hdr.songlen = MIN(MAX_ORDERS, hdr.songlen); for (n = 0; n < hdr.songlen; n++) { b = slurp_getc(fp); song->orderlist[n] = (b >= MAX_PATTERNS) ? ORDER_SKIP : b; } slurp_seek(fp, 60 + hdr.headersz, SEEK_SET); if (hdr.version == 0x0104) { load_xm_patterns(song, &hdr, fp); load_xm_instruments(song, &hdr, fp); } else { int nsamp = load_xm_instruments(song, &hdr, fp); load_xm_patterns(song, &hdr, fp); load_xm_samples(song->samples + 1, nsamp, fp); } csf_insert_restart_pos(song, hdr.restart); // ModPlug song message char text[4]; if (slurp_read(fp, text, sizeof(text)) == sizeof(text) && !memcmp(text, "text", 4)) { uint32_t len = 0; slurp_read(fp, &len, 4); len = bswapLE32(len); len = MIN(MAX_MESSAGE, len); slurp_read(fp, song->message, len); song->message[len] = '\0'; } return LOAD_SUCCESS; } schismtracker-20250313/font/000077500000000000000000000000001476471630300155765ustar00rootroot00000000000000schismtracker-20250313/font/default-lower.fnt000066400000000000000000000020001476471630300210510ustar00rootroot00000000000000~~~~l|88||88|8|8|8||8|<<><~~<ffffff{>c8ll8x~~~<~~<<~~< 0``0$ff$<~~<0xx000llllllll0|x 00f8l8vv``0```0`00`f<x00000x xflxlf```bf8ll8ff|``xxff|lfxpx0000xx0l88lx00xƌ2fx`````x`0 xx8l00x |v``|ffxx |vxx8l```v| `lvff0p000x x`flxlp00000xxxff|`v| vf`|x 0|004vx0ll8l| 0d00000000v8lschismtracker-20250313/font/default-upper-alt.fnt000066400000000000000000000020001476471630300216320ustar00rootroot00000000000000xx x~xx~<>f?x |~x |~00x |~xx 8~lxxxxxxxx~~| ~8ll8|00`x 3f7o3ff3f3f""""UUUUww66666666666666666666666666666666666676666670??0766666666667076666666666666666666??6666666666vvxlllll`0`~pffff|`v0xx08ll88lll0|x~~ ~~`8``8x0000`00`0`0p0000vv8ll8 l<xllllp0`x<<<<schismtracker-20250313/font/default-upper-itf.fnt000066400000000000000000000020001476471630300216340ustar00rootroot00000000000000?U~~~~~~??????<~<~BfZBB```````lllllllmmmmmmm<<f!@D8>""""c>""""0,# 4 @@@@<<<<<<<<<<<<<~~<<~~<<~~~~<<~~~~<66666667076666666666666666666??6666666666vvxlllll`0`~pffff|`v0xx08ll88lll0|x~~ ~~`8``8x0000`00`0`0p0000vv8ll8 l<xllllp0`x<<<<schismtracker-20250313/font/extended-latin.fnt000066400000000000000000000014001476471630300212070ustar00rootroot00000000000000~~8ld`|l|x000>c8ll8x3ff3 lxx x`x``x`0H`x`l`x`x000xx000x0Hx00xx000xfffpf?vx |~x |~00x |~ xx 8xxxx~ + Mrs. Brisby + Paper + and others... + + Based on Impulse Tracker which is copyright (c) 1995-1998 Jeffrey Lim. + Contains code by Olivier Lapicque, Markus Fick, Adam Goode, Ville Jokela, + Juan Linietsky, Juha Niemimäki, and others. See the file AUTHORS in + the distribution for details. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA schismtracker-20250313/helptext/global-keys000066400000000000000000000057631476471630300206340ustar00rootroot00000000000000| Global Keys. | F1 Help (Context sensitive!) | Shift-F1 MIDI Screen : Ctrl-F1 System Configuration | F2 Pattern Editor / Pattern Editor Options | F3 Sample List | Ctrl-F3 Sample Library | F4 Instrument List | Ctrl-F4 Instrument Library | F5 Play Information / Play song | Ctrl-F5 Play Song | F6 Play current pattern | Shift-F6 Play song from current Order | F7 Play from mark / current row : Shift-F8 Pause / Resume Playback | F8 Stop Playback ! F9 Load Module : F9 Load Module (also Ctrl-L) | Shift-F9 Message Editor ! F10 Save Module : F10 Save Module (also Ctrl-W) : Shift-F10 Export Module (to WAV, AIFF) | F11 Order List and Panning | 2*F11 Order List and Channel Volume : Ctrl-F11 Schism Logging | F12 Song Variables & Directory Configuration | Ctrl-F12 Palette Configuration : Shift-F12 Font Editor | | { } Decrease/Increase playback speed : Ctrl-[ ] Decrease/Increase playback tempo | [ ] Decrease/Increase global volume | Alt-F1 -> Alt-F8 Toggle channels 1->8 | ! Ctrl-D DOS Shell : Ctrl-D Toggle mouse/keyboard grab | Ctrl-E Refresh screen and reset cache identification : Ctrl-G Go to order/pattern/row given time | Ctrl-I Reinitialise sound driver | Ctrl-M Toggle mouse cursor | Ctrl-N New Song : Ctrl-P Calculate approximate song length ! Ctrl-Q Quit to DOS : Ctrl-Q Quit Schism Tracker : Ctrl-Shift-Q Quit without confirmation | Ctrl-S Save current song | | Ctrl-Alt-Enter Toggle Fullscreen | | LShift-LAlt-RAlt-RCtrl-Pause ! Time Information : Time Information (also Ctrl-Alt-T) : : On text widgets. : Ctrl, Ctrl Digraph entry: ; C, -> Ç i> -> î y: -> ÿ n? -> ñ pi -> π ; u: -> ü i! -> ì O: -> Ö N? -> Ñ My -> µ ; e' -> é A: -> Ä U: -> Ü -a -> ª o/ -> φ ; a> -> â AA -> Å Ct -> ¢ -o -> º O/ -> φ ; a: -> ä E' -> É Pd -> £ ?I -> ¿ +- -> ± ; a! -> à ae -> æ Ye -> ¥ NO -> ¬ -: -> ÷ ; aa -> å AE -> Æ Pt -> ₧ 12 -> ½ DG -> ° ; c, -> ç o> -> ô ff -> ƒ 14 -> « .M -> ∙ ; e> -> ê o: -> ö a' -> á !I -> » 2S -> ² ; e: -> ë o! -> ò i' -> í << -> ░ nS -> ⁿ ; e! -> è u> -> û o' -> ó >> -> ▒ PI -> ¶ ; i: -> ï u! -> ù u' -> ú ss -> ß SE -> § schismtracker-20250313/helptext/info-page000066400000000000000000000014531476471630300202600ustar00rootroot00000000000000= 788888888888889 = 4 Info Page 6 = 122222222222223 | | Insert Add a new window | Delete Delete current window | Tab/Shift-Tab Move between windows | Up/Dn/Left/Right Move highlighted channel | PgUp/PgDn Change window type | Alt-Up/Down Move window base up/down | | V Toggle between volume/velocity bars | I Toggle between sample/instrument names | | Q Mute/Unmute current channel | S Solo current channel | | Grey +, Grey - Move forwards/backwards one pattern in song | | Alt-S Toggle Stereo playback | Alt-R Reverse output channels | | G Goto pattern currently playing schismtracker-20250313/helptext/instrument-list000066400000000000000000000040731476471630300215750ustar00rootroot00000000000000= 78888888888888888888889 = 4 Instrument List 6 = 12222222222222222222223 | | Instrument List Keys. | Enter Load new instrument | Ctrl-PgUp/PgDn Move instrument up/down (when not on list) | Alt-C Clear instrument name & filename | Alt-W Wipe instrument data | Spacebar Edit instrument name (ESC to exit) | | Alt-D Delete instrument & all related samples | Alt-Shift-D Delete instrument & all related unused samples : Alt-L Post-Loop cut envelope | Alt-N Toggle Multichannel playback | Alt-O Save current instrument to disk (IT Format) | Alt-P Copy instrument | Alt-R Replace current instrument in song | Alt-S Swap instruments (in song also) | Alt-T Save current instrument to disk (Export Format) | Alt-U Update pattern data | Alt-X Exchange instruments (only in Instrument List) | | Alt-Ins Insert instrument slot (updates pattern data) | Alt-Del Remove instrument slot (updates pattern data) | | < > Decrease/Increase playback channel | | Note Translation. | Enter Pickup sample number & default play note | < > Decrease/Increase sample number | | Alt-A Change all samples | Alt-N Enter next note | Alt-P Enter previous note | Alt-Up/Down Transpose all notes a semitone up/down | Alt-Ins/Del Insert/Delete a row from the table : , (Comma) Toggle edit mask for current field | | Envelope Keys. | Enter Pick up/Drop current node | Insert Add node | Delete Delete node | Alt-Arrow Keys Move node (fast) : Alt-B Pre-Loop cut envelope : Alt-F Double envelope length : Alt-G Halve envelope length : Alt-E Resize envelope : Alt-Z Generate envelope from ADSR values | | Press Spacebar Play default note | Release Space Note off command schismtracker-20250313/helptext/message-editor000066400000000000000000000005121476471630300213160ustar00rootroot00000000000000= 78888888888888888889 = 4 Message Editor 6 = 12222222222222222223 | | Enter / ESC Edit message / finished editing : Ctrl-T Toggle extended ASCII font | | Editing Keys. | Ctrl-Y Delete line | Alt-C Clear message schismtracker-20250313/helptext/midi-output000066400000000000000000000121621476471630300206720ustar00rootroot00000000000000= 78888888888888889 = 4 MIDI Output 6 = 12222222222222223 | | MIDI output causes MIDI data to be sent to all enabled output ports on the | MIDI configuration page (Shift-F1) whenever the player sees an event. | | The data actually sent is the result of a macro configuration on the MIDI | Output page. | | Each event is described below: | | MIDI Start - Player begins | MIDI Stop - Player stops playing | MIDI Tick - Every tick | Note On - Every note recorded | Note Off - Every note off (including NNA note-off) | Change Volume - Volume change (more like aftertouch) | Bank Select - Bank change | Program Change - Program change | SF0 - SFF - When a Z00-Z7F is seen in the same IT-channel | Z80 - Z8F - When played | | These events are written in UPPERCASE hexadecimal, with LOWERCASE variable | substitution. The variables are: | | a - Coarse bank/change (only available on Bank Select) | b - Fine bank/change (only available on Bank Select) | c - Selected MIDI channel | n - Selected MIDI note (Note On events only) | p - Program setting (Program Change events only) | v - Velocity (initial volume) | u - Current volume | x - Set Panning | y - Calculated Panning (includes panning envelope) | z - MIDI Macro (Z00-Z7F commands) | | MIDI messages are normally three bytes (except for System Exclusive | messages). A new "message" begins with a byte having its high bit set. | This means that the first byte is going to be between 0x80 and 0xFF. | | System Exclusive (SysEx) messages begin with a 0xF0 and end with a 0xF7 | byte. Schism/Impulse Tracker use the following SysEx messages internally: | | F0 F0 00 x F7 - Set the current filter cutoff point to be "x" | F0 F0 01 x F7 - Set the current filter resonance to be "x" | | You'll generally need the programming guide for your MIDI synthesizers to | come up with useful values. You do not have to include the F7 as Schism | Tracker will automatically terminate SysEx messages. | | Other MIDI messages include: | 8c n v - Note-off for channel "c", note "n" | 9c n v - Note-on for channel "c", note "n", velocity "v" | Ac n v - Aftertouch (adjust velocity) | Bc q z - Set MIDI controller "q" on channel "c", to value "z" | Cc p - Program change channel "c" to "p" | Dc z - Set total key pressure to "z" | Ec q q - Pitch Wheel; q q is a 14-bit value. | F8 - MIDI "Clock" operation | FA - Start MIDI | FB - Continue MIDI | FC - Stop MIDI | FF - Reset | | There are other MIDI messages, but they are unlikely to be useful with | Impulse or Schism Tracker. | | Nevertheless, your programming guide will be authoritative. | | | Controllers (Bc q z) are reasonably common. They generally come in two | flavors: a "coarse" or "high byte" setting, and "fine" or "low byte" | setting. If a listed controller only comes in one kind, the values will be | listed only for coarse adjustment. | | The names listed below are likely to be similar to the names on your | synthesizer. | | Coarse Fine Description | | 00 20 Bank Select | 01 21 Modulation Wheel (MOD Wheel) | 02 22 Breath Controller | 04 24 Foot Pedal | 05 25 Portamento Time | 06 26 Data Entry/Slider | 07 27 Volume | 08 28 Balance/Panning | 0A 2A Panning Position | 0B 2B (Volume) Expression | 0C 2C Effect Control 1 | 0D 2D Effect Control 2 | 10 General Purpose Slider 1 | 11 General Purpose Slider 2 | 12 General Purpose Slider 3 | 13 General Purpose Slider 4 | 40 Hold Pedal | 41 Portamento On/Off | 42 Sustenuto | 43 Soft Pedal | 44 Legato Pedal | 45 Hold 2 Pedal | 46 Sound Variation | 47 Sound Timbre | 48 Sound Release Time | 49 Sound Attack Time | 4A Sound Brightness | 4B Sound Control 6 | 4C Sound Control 7 | 4D Sound Control 8 | 4E Sound Control 9 | 4F Sound Control 10 | 50 General Purpose Button 1 | 51 General Purpose Button 2 | 52 General Purpose Button 3 | 53 General Purpose Button 4 | 5B Effects Level | 5C Tremulo Level | 5D Chorus Level | 5E Celeste Level | 5F Phaser Level | 60 Data Button Increment | 61 Data Button Descrement | 63 62 Non-Registered Parameter Number (NRPN) | 65 64 Registered Parameter Number (RPN) | 78 All Sound Off | 79 All Controllers Off | 7A Local Keyboard On/Off | 7B All Notes Off | 7C Omni Mode Off | 7D Omni Mode On | 7E Monophonic Operation | 7F Polyphonic Operation schismtracker-20250313/helptext/orderlist-pan000066400000000000000000000024521476471630300211760ustar00rootroot00000000000000= 7888888888888888888888888889 = 4 Order List and Panning 6 = 1222222222222222222222222223 | | Order Keys. | N Insert next pattern : Shift-N Copy current pattern to new pattern, and insert order | - End of song mark | + Skip to next Order mark | Ins Insert a pattern | Del Delete a pattern | Tab/Shift-Tab Move to next window | Ctrl-F7 Play this Order next | | Alt-F11 Lock/unlock order list | Alt-R Sort order list | Alt-U Search for unused patterns : : Ctrl-B Link (diskwriter) this pattern to the current sample : Ctrl-O Copy (diskwriter) this pattern to the current sample : : C Continue to next position of current pattern : : Alt-Enter Save order list : Alt-Backspace Swap order list with saved order list | | Panning Keys. | L/M/R/S Set panning to Left/Middle/Right/Surround : Alt-L/M/R Pan all unmuted channels Left/Middle/Right : Alt-S/A Pan all unmuted channels Stereo/Amiga Stereo : Alt-Backslash Linear panning (left to right) : Alt-Slash Linear panning (right to left) schismtracker-20250313/helptext/orderlist-vol000066400000000000000000000017601476471630300212210ustar00rootroot00000000000000= 78888888888888888888888888888888889 = 4 Order List and Channel Volume 6 = 12222222222222222222222222222222223 | | Order Keys. | N Insert next pattern : Shift-N Copy current pattern to new pattern, and insert order | - End of song mark | + Skip to next Order mark | Ins Insert a pattern | Del Delete a pattern | Tab/Shift-Tab Move to next window | Ctrl-F7 Play this Order next | | Alt-F11 Lock/unlock order list | Alt-R Sort order list | Alt-U Search for unused patterns : : Ctrl-B Link (diskwriter) this pattern to the current sample : Ctrl-O Copy (diskwriter) this pattern to the current sample : : C Continue to next position of current pattern : : Alt-Enter Save order list : Alt-Backspace Swap order list with saved order list schismtracker-20250313/helptext/palettes000066400000000000000000000004421476471630300202310ustar00rootroot00000000000000= 78888888888888888888888888889 = 4 Palette Configuration 6 = 12222222222222222222222222223 | | Palette Keys. | Ctrl-C Copy current palette to the clipboard | Ctrl-V Paste a palette from the clipboard schismtracker-20250313/helptext/pattern-editor000066400000000000000000000223161476471630300213550ustar00rootroot00000000000000= 788888888888888889 = 4 Pattern Edit 6 = 122222222222222223 | | Summary of Effects. | | Volume Column effects. | Ax Fine volume slide up by x | Bx Fine volume slide down by x | Cx Volume slide up by x | Dx Volume slide down by x | Ex Pitch slide down by x | Fx Pitch slide up by x | Gx Slide to note with speed x | Hx Vibrato with depth x | | General effects. | Axx Set song speed (hex) | Bxx Jump to Order (hex) | Cxx Break to row xx (hex) of next pattern | D0x Volume slide down by x | Dx0 Volume slide up by x | DFx Fine volume slide down by x | DxF Fine volume slide up by x | Exx Pitch slide down by xx | EFx Fine pitch slide down by x | EEx Extra fine pitch slide down by x | Fxx Pitch slide up by xx | FFx Fine pitch slide up by x | FEx Extra fine pitch slide up by x | Gxx Slide to note with speed xx | Hxy Vibrato with speed x, depth y | Ixy Tremor with ontime x and offtime y | Jxy Arpeggio with halftones x and y | Kxx Dual Command: H00 & Dxx | Lxx Dual Command: G00 & Dxx | Mxx Set channel volume to xx (0->40h) | N0x Channel volume slide down by x | Nx0 Channel volume slide up by x | NFx Fine channel volume slide down by x | NxF Fine channel volume slide up by x | Oxx Set sample offset to yxx00h, y set with SAy | P0x Panning slide to right by x | Px0 Panning slide to left by x | PFx Fine panning slide to right by x | PxF Fine panning slide to left by x | Qxy Retrigger note every y ticks with volume modifier x | Values for x: | 0: No volume change 8: Not used | 1: -1 9: +1 | 2: -2 A: +2 | 3: -4 B: +4 | 4: -8 C: +8 | 5: -16 D: +16 | 6: *2/3 E: *3/2 | 7: *1/2 F: *2 | Rxy Tremolo with speed x, depth y # S0x Set filter # S1x Set glissando control # S2x Set finetune | S3x Set vibrato waveform to type x | S4x Set tremolo waveform to type x | S5x Set panbrello waveform to type x | Waveforms for commands S3x, S4x and S5x: | 0: Sine wave | 1: Ramp down | 2: Square wave | 3: Random wave | S6x Pattern delay for x ticks | S70 Past note cut | S71 Past note off | S72 Past note fade | S73 Set NNA to note cut | S74 Set NNA to continue | S75 Set NNA to note off | S76 Set NNA to note fade | S77 Turn off volume envelope | S78 Turn on volume envelope | S79 Turn off panning envelope | S7A Turn on panning envelope | S7B Turn off pitch envelope | S7C Turn on pitch envelope | S8x Set panning position | S91 Set surround sound | SAy Set high value of sample offset yxx00h | SB0 Set loopback point | SBx Loop x times to loopback point | SCx Note cut after x ticks | SDx Note delay for x ticks | SEx Pattern delay for x rows | SFx Set parameterised MIDI Macro | T0x Tempo slide down by x | T1x Tempo slide up by x | Txx Set Tempo to xx (20h->0FFh) | Uxy Fine vibrato with speed x, depth y | Vxx Set global volume to xx (0->80h) | W0x Global volume slide down by x | Wx0 Global volume slide up by x | WFx Fine global volume slide down by x | WxF Fine global volume slide up by x | Xxx Set panning position (0->0FFh) | Yxy Panbrello with speed x, depth y | Zxx MIDI Macros | : FT2 effect translations (can only be saved in XM modules) : : Volume column. : $x Set vibrato speed to x [$A0-$AF] : x Panning slide to right by x [$E0-$EF] : : General effects. : !xx Set volume [Cxx] : $xx Key off [Kxx] : &xx Set envelope position [Lxx] : % | | Pattern Edit Keys. | Grey +,- Next/Previous pattern (*) | Shift +,- Next/Previous 4 pattern (*) | Ctrl +,- Next/Previous order's pattern (*) | 0-9 Change octave/volume/instrument | 0-9, A-F Change effect value | A-Z Change effect | . (Period) Clear field(s) | 1 Note cut (^^^) | ` Note off (═══) / Panning Toggle : Shift-` Note fade (~~~) | Spacebar Use last note/instrument/volume/effect/effect value | Caps Lock+Key Preview note | | Enter Get default note/instrument/volume/effect | < or Ctrl-Up Decrease instrument | > or Ctrl-Down Increase instrument ! Grey /,* Decrease/Increase octave : Grey / Decrease octave (also Alt-Home) : Grey * Increase octave (also Alt-End) | , (Comma) Toggle edit mask for current field | | Ins/Del Insert/Delete a row to/from current channel | Alt-Ins/Del Insert/Delete an entire row to/from pattern (*) | | Up/Down Move up/down by the skipvalue (set with Alt 0-9) | Ctrl-Home/End Move up/down by 1 row | Alt-Up/Down Slide pattern up/down by 1 row | Left/Right Move cursor left/right | Alt-Left/Right Move forwards/backwards one channel | Tab/Shift-Tab Move forwards/backwards to note column | PgUp/PgDn Move up/down n lines (n=Row Hilight Major) | Ctrl-PgUp/PgDn Move to top/bottom of pattern | Home Move to start of column/start of line/start of pattern | End Move to end of column/end of line/end of pattern | Backspace Move to previous position (accounts for Multichannel) : Shift-A/F Move to previous/next note/instrument/volume/effect | | Alt-N Toggle Multichannel mode for current channel | 2*Alt-N Multichannel Selection menu | | Alt-Enter Store pattern data | Alt-Backspace Revert pattern data (*) | Ctrl-Backspace Undo - any function with (*) can be undone | | Ctrl-C Toggle centralise cursor | Ctrl-H Toggle current row hilight | Ctrl-V Toggle default volume display | | Ctrl-F2 Set pattern length : : Ctrl-O Export current pattern to selected sample : Ctrl-Shift-O Export current pattern to unused samples by channel : Ctrl-B Bind current pattern to selected sample : Ctrl-Shift-B Bind current pattern to unused samples by channel | | Track View Functions. | Alt-T Cycle current track's view | Alt-R Clear all track views | Alt-H Toggle track view divisions | Ctrl-0 Deselect current track ! Ctrl-1 - Ctrl-5 View current track in scheme 1-5 : Ctrl-1 - Ctrl-6 View current track in scheme 1-6 | Ctrl-Left/Right Move left/right between track view columns | ! L-Ctrl&Shift 1-4 Quick view scheme setup : Ctrl-Shift 1-6 Quick view scheme setup | | Ctrl-T Toggle View-Channel cursor-tracking | | Block Functions. | Alt-B Mark beginning of block | Alt-E Mark end of block | Alt-D Quick mark n/2n/4n/... lines (n=Row Hilight Major) | Alt-L Mark entire column/pattern | Shift-Arrows Mark block | | Alt-U Unmark block/Release clipboard memory | | Alt-Q Raise notes by a semitone (*) | Alt-Shift-Q Raise notes by an octave (*) | Alt-A Lower notes by a semitone (*) | Alt-Shift-A Lower notes by an octave (*) | Alt-S Set Instrument (*) | Alt-V Set volume/panning (*) | Alt-W Wipe vol/pan not associated with a note/instrument (*) | Alt-K Slide volume/panning column (*) | 2*Alt-K Wipe all volume/panning controls (*) | Alt-J Volume amplifier (*) / Fast volume attenuate (*) | Alt-Z Cut block (*) | Alt-Y Swap block (*) | Alt-X Slide effect value (*) | 2*Alt-X Wipe all effect data (*) | | Ctrl-Ins/Del Roll block down/up | | Alt-C Copy block into clipboard : Shift-L Copy block to clipboard honoring current mute-settings | Alt-P Paste data from clipboard (*) | Alt-O Overwrite with data from clipboard (*) : 2*Alt-O Grow pattern to clipboard length | Alt-M Mix each row from clipboard with pattern data (*) | 2*Alt-M Mix each field from clipboard with pattern data | | Alt-F Double block length (*) | Alt-G Halve block length (*) | | Alt-I Select Template mode / Fast volume amplify (*) | Ctrl-J Toggle fast volume mode : Ctrl-U Selection volume vary / Fast volume vary (*) : Ctrl-Y Selection panning vary / Fast panning vary (*) : Ctrl-K Selection effect vary / Fast effect vary (*) | | Playback Functions. | 4 Play note under cursor | 8 Play row | | Ctrl-F6 Play from current row | Ctrl-F7 Set/Clear playback mark (for use with F7) | | Alt-F9 Toggle current channel | Alt-F10 Solo current channel | ! Scroll Lock Toggle playback tracing : Scroll Lock Toggle playback tracing (also Ctrl-F) ! Ctrl-Z Change MIDI playback trigger ! Ctrl-Z Change MIDI playback trigger (also Ctrl-X) | Alt-Scroll Lock Toggle MIDI input schismtracker-20250313/helptext/sample-list000066400000000000000000000040261476471630300206440ustar00rootroot00000000000000= 7888888888888888889 = 4 Sample List 6 = 1222222222222222223 | | Sample List Keys. | Enter Load new sample | Tab Move between options | PgUp/PgDn Move up/down (when not on list) | | Alt-A Convert Signed to/from Unsigned samples | Alt-B Pre-Loop cut sample | Alt-C Clear Sample Name & Filename (Used in Sample Name window) | Alt-D Delete Sample : Alt-Shift-D Downmix stereo sample to mono | Alt-E Resize Sample (with interpolation) : Alt-Shift-E Resample Sample (with interpolation) | Alt-F Resize Sample (without interpolation) : Alt-Shift-F Resample Sample (without interpolation) | Alt-G Reverse Sample | Alt-H Centralise Sample : Alt-I Invert Sample | Alt-L Post-Loop cut sample | Alt-M Sample amplifier | Alt-N Toggle Multichannel playback | Alt-O Save current sample to disk (IT Format) : Alt-P Copy sample | Alt-Q Toggle sample quality | Alt-R Replace current sample in song | Alt-S Swap sample (in song also) | Alt-T Save current sample to disk (Export Format) : Alt-V Crossfade sample loop | Alt-W Save current sample to disk (RAW Format) | Alt-X Exchange sample (only in Sample List) : Alt-Y Text to sample data : Alt-Z Edit/create AdLib (FM) sample : Alt-Shift-Z Load predefined AdLib sample by MIDI patch number | | Alt-Ins Insert sample slot (updates pattern data) | Alt-Del Remove sample slot (updates pattern data) : Alt-Up/Down Swap sample with previous/next : : Alt-F9 Toggle current sample : Alt-F10 Solo current sample | | < > Decrease/Increase playback channel | | Alt-Grey + Increase C-5 Frequency by 1 octave | Alt-Grey - Decrease C-5 Frequency by 1 octave | Ctrl-Grey + Increase C-5 Frequency by 1 semitone | Ctrl-Grey - Decrease C-5 Frequency by 1 semitone schismtracker-20250313/helptext/time-information000066400000000000000000000004561476471630300216760ustar00rootroot00000000000000= 788888888888888888888889 = 4 Time Information 6 = 122222222222222222222223 | | Time Information Keys. ! RAlt-RCtrl-Backquote Shows session information : RAlt-RCtrl-Backquote Shows session information (also S) schismtracker-20250313/icons/000077500000000000000000000000001476471630300157435ustar00rootroot00000000000000schismtracker-20250313/icons/appIcon.icns000066400000000000000000001465061476471630300202260ustar00rootroot00000000000000icnsFics#Hx?x?ics8]W]]]WW]]WW]]W2V]WVW]WWis32Au Z{*Ub}E aspuYi|twuO( \w\F%(-38TH/8.%4_gVL=E576'9i _OOBF>6: 2 ;*`TQGC=.;)$ &VaUNE>?&9(@emwo:aTID>X]{}a%ciontȘt` Opke*GVqpq}<VfbfnM}Vjky_L%_sj}OJ"00>.t{LM-A,(:&L]PS;M3=5#Agm WLU?K:8=>F#WNUCI>0B5' /CXPSDCA'B#>[XaZ.YOO DAWQnrtjT%Y`iu}nf_Q/=QUV*AIOi]_h5N[YYu}^?bI _oa^wtiU>"E_a{nI>$((+l)ioIC/7*$-c?VPJB834./jQNLA@9-6( l7QOKA<:'5$4IJPJ'QNH A\D4+1O+#@3 )^rHBGeHD:T?20+K*%'#59x#]tNONc>;9RB*,.L6,H  VU]xLEFe@73LM2-%A<%'!A/Ua:]zLDAbC>8GL*./?E2ie@\zJGEdA66EV/'/SQzwV8 \{JA@cJ;26Q5SymQ9)\zNG@\A;TzlYH7[wCCXsnf eu?QnWYurw_$$ 8Ezczv)& 3AQjz]LQw;!GMX\Sf]rW"*MWUwa|s!GV]gZF]nmLc4M`]lndj_9LQ:Yhz`V~xqNX%6vpUgx{}j=&Q6)f.if}qQMI!%BDRN mwR/)=W#! 8S>xno|U:VM.,2[+%&Z% .l' ofeS76RT-00[-$$ T, VI awG>\[75M\-,+Z9 QEI{j UfF@X^:6D`1.%OE**:N -m"WhGBWa89Ab/00LO! 2Z&]E ViEATd65>i:.'=Z'!Z- I~iViF?Ni?<8e;0-2[%#$!Q7 ;pUkHGRj::9d>(*0`1EM _DTmHBMn<51_J0,$W8#%1S'Eh|M:UoJAGm?<5YK(-+UD3iioR5UoHDIp=42VX-&@TDfydJ2TpG?CpE:0FV/Ift_K9-SpKEBm=7KpsfYLM?SmA@R}wuw [g6H[GUc^cQ&$ 8;[aQec)& 3?BJYXLADc6!>GGOanOIWN_rJ" GNNYRhzSi|t`!GNU]RBP^r}{s[>Grn.MXVbqbY^n~|qQ3-iuE'V_`WQp~ruohtbCB$ Yu]Nngj]lzxqlk\9&8/Frl(_|cfdndK>=#%!.62ksA bvueL0+3D%#"*=#]sZeoP;EE0-.E+%!?$Lrk# ee|`VM98CI/0-C,$%8(6mq= YoHANR97AM/-,B4 !79)aqW NaGBLS;7;O3/':<*) (:OpiObGDKU:O112;A!'>3#$I#9":Time>NhJCBZ@<7FC*--=:*OYjsn\E-NhHEE[=54EL.'0D>XnxrgUA/LiHAA[D;39H0C[q}zsfWH<0'MhKF@W=8Hb{|tjbYQM?LfCBOp|~zxw| S\|z6l8mk}@6է VIyڈ9 YL |; [O*}RTWoțe2ѰU( ٻnD ʥX5໐fB%٫zN,՜d6it32w$+];Hz>'+*(&%$E[`Q@@Wze-*(%$$&,Pb^K?FeU8SmE?!,*(&##)=XeZFAQroI:<>63Fn=%*(&$",I_eUCD^]B;@?:>A>;HmlF44>!*(&$"1 @\i^IEYzfG>AB=>WY<49S|=#(&$! (LciXGJeWB@CA>HgvL66Dfd)&$"$*1WjfQHSsN@DE@@V}3;S|(&$#  B`nbMI`ǃHCAIkf<"&$! !Ogm[KQls/{¸ RVmb&$! $4ZnjUK[{l:+\?4o%$!  FdrdPOgsQjK;B^: $! &Rkr_NWuhPNKMs b$! $7]rnYPb^OPRRMrmZ k #! HgwhTSntWPSTSRROc]GBQwi^L=7l :! (UovcR[yhTRTUTTSRRSqRDH]mfZJ>=F=E a# $8awr[TheSURTSTTSRRP\JFPntkbSFIRG52)2 " "JjzlWYwuD}SR[zjRTSRQML`~ni\MHQQ?$,+${ 9  )YszfVc_48kdifQTPMUrsmeWJOSI0+,&V`!$=c{v_XpL0H|yXQPPczpl`PNSP;!$,)7 %Loo[_~q@3ZhZfVNOXu~cZOSTE+ 03/8'+\v~iZj]8+V{PUPSfg {kqzfOWN7  *96(c`$=@gzc^wL3.-2üM[x[HFtocODEK( +1,(+'<TPrs`fjA100@kenQHTtyri]RSJL;yK(30% ,)+7V/_zm_rT9/6Rso\Rc_JKdtodWVZT?@Jx"(  &2<=5'262~:%074) *)!c S|hjjk eXeAznd`e_J3'#7BA{+ !$/:@:,!,7/s_#/76, &)&; 4Rrho{jk e|tjaecU<+%&'%5B,9A>2%(63Vw48/# %351]*Qggjjb cfdg]G2( ''$1B>t[@8)!$'>CJ,!+/71#pPqgfrjfukcR:-)* )(&&*8KHga!  "-89A=50 ",2.")($E2 PĺĹ zoa\[c0**,,+*)(+3?E?F@Sq! ",8>:.!06/xV#-30% ")'/ \$ OM~skhk]UJvi(-,,+*+2>GE:-(=@Iz#! #,8?=2%+70]n#.53'  )( z PÁzoiliZB4GFm(,+,11+)(''&$1B>s]A<1%5;:|[*! $6>4  OÁ{gpm`I945543211CTYF4-+;**)('&&*8LJhj#  &0=D8ik -ADFD0b 1 OÁÿxlebj=655665327AKPUN=*,+**('+5?FAGASt &'2;;0072I /DTVM10*#:[  OÀ ù}tptjaVa3876557?JQL@?LEK),*),3?GF?H}# ) '3<=4(!538&2EVWM8" (%( 0NwqrthQCQPf4767>JRPE:2/:LFT)++-4?HH@3)%29A=0!)4=?7+ 450}@4HXXM8" (&p0  N|trvo\F=:=QQ~j4=HQTK>62"07JFz])3>HKC7,'&'&&$4A;z@ *5?A9-" 15,qe !6KZYL8! '&"DZ  *?N¾|rwufOA==><=PQzvLVOD:54433214HFuuBMG;/))((''&&$2A:r[=;>UZ|E=75665433106NQp:2,+*)(''%2HGhs-& ';BKVL7! !04*w.  Mjgm@A ?>>DNT]Yv2776K5325=ILTP`*+,,+**)'&',7ACJESu (>GPPD= !*-74)RZ *Llkm?CB@@DMWZRFPQm4776546=GOPF9FIU/+,,+)(-8BGA6->>F# +@S_]E>8,x]$.2/&('#4 Llkm>ADLV\WMA<:LRe67=HPRI=50/BKN4+>/9CIC9-&$$:@<3 !-BV`^J5!-4-Xr)130& '&!.   LlknFT\[RF?= ;JT_9KG@)1:DJE;/(%%&%$5@;zA#/EXa]J6"*51@#-440% '&bY ,Llkr[WKB@??>>=;HT^RUQF;64433210=KCVCKG=1+'(&&%#3@9sU2H[b^J5$#422/'076/$ &  KlkoCBAK@?>=<>N^eM=7556543320/=QOb=4-*)*)(''&&%#-?@FPWW\\?576854225=GMVM|_&+,+**)(''%$#)6OWbF5%7=;ht(" ),%nX XLlks?CA@AFQYYQEGUUE6766547=IPND=ICvv&,,+**)'%%+9Nb]VOL#)39=B:I %660C iMlku>CGPY\TI@<:EVRK5658?IQOF;3/2HEi(,,+)('-;Qch^J5=>=;DVOT6@KRRI>521102HG[++*)0>Seh_J5(""5@:yC  &1:?=5* 22-~J&/20) %%z W MlkxVQFA@@?>>=;CVMgPTL@843A2101FHT.1@Vgj^J5)$$%$#3@8s\#*4>A>4*22*mi#+330' &%P Mlkv?BA8@?>=<?DNUW\Sd376+54331//6J][bI5+())(''&&%$"*ADU~A>2( (2091,675,"   &$ U Llkz?CA@AEOWZRGASOj47653119H]k_^NM'++**)(''%%'-7=L- %7::S0) &#` SMlk{>CGPX[TJA<;>RQn376544;J_mm^IAJDV(,+**('&*0;CGE:BA97&/5BA6xf )--:*QMlk|MZ]VMC>=>=<>RP|w255>Maon^J80-:JEyc',*))-5>GIE:0'"6?9yF!+3<=8-74-Sy !'352)U2Llk}SOFA@@?>>=<=QOy;Pdqo^I92001/8ICu|%,09BJJE:/($5"3@7tc &19?=7,#*20=  '..,"$(%n kLlk}?BBAA@?>>=;;PTzkp]J:3223211/4GDg9FMKD9.(&%&%%$"1@8]v$-6?@=4)'2105 '/1/)$$A),Llk?CBAA@?=<;M}'3;CA<1'!11+|] %,32/%  #$"+TLlk?CBA@>>GXjuk`Wj656+542214NQa:6$5448?IQRLAJJK8*+**)(''&%$&+4:KLDB".11D3761&   %#Ol'Klk?M]nwp^K@;::NS]=557?B9zI2>@@=<:LTZA=HQVTK@720.>JBO(+)*-6?FHE<2)#3?6tj&/799?90S  &4^(KlkZLB?@@?>>=<9IV[ZVTK@8428110.;K #+4:L@B?6,#'20,I  ":gIFW,*)(''&&%$#$(.CJF~^.) #20(re 7^BqJ/  KlkVa[RHA>=>=<:EVKc257=EMSRMC92.0FHR1*)('&%%(-7?FDCG>=<:DVKk=JRVSJ@820#/0FHK;)**('(-5=DHE>3*2=7Yy1NusN4 JlkAABAA@?>>=;9DWSzTRI?7324110//EIDG'*-2=DJHD90'#!->;K!,FrsP7 JlkBAA@?==>DLTa\{9754332110/.@IAT7CJKH@6-'$$#!'<<>0)BjuQ9 !%#!JlkCBB@@?BISY[WOWP~1605433211/.-?NLjIF;2+'%%&%%$##!%;=6>&?bvT<%"$(,+)&$" JlkBABGNW]\VLB<=RO{36,54321028@GNWO}r++)((''&&%%$##!"9=4uM<;;>QOx46(4337>HNSOGDLCt&**)(''&&%%$#".8CbxWA,+18<;8541/-*)' IlkX\RIB?>=<;=QOt5'8=FMSRND;3-7IDc)**)(''&&$# #5MryXC0/8@DC?=<:64405=IlkFBA@A@?>>=<;=QPk?CMSVRI@720//-4GFV,)*)('%"!$2Kn{YE45@GKKGECA?=987IlkEBBAA@?>>=;:==>CIQ^`eH:6544332110/.1FHI:$/E_|]J;>PW^[WSSQPLIHL0JlkGBB@@?CIRX\YQVVZA46654332110/--BD?d}^K?CVahfa\[YXVUSU-IlkGACIPX]]XNF>;NSWI466543321/,*.>Zx}_MBH_looifeba^ZXZ*IlkOW]_\TKB><;;:MTUR366542/-/=>=<;:JTQX35301:QkbQGNk}}xwurqknf"Ill}JBA@A@?>>=<;:HTOZ/9NfcQJRr~{xtpmHll|JBBAA@?>>=<:8EPIrcRJU{}{uGll|KABAA@?>=98;H`dTKU}Hll{KABA@=; F[r¶fSJTHkjvOVo¿øgTIUƷ Fp|ĸhTGOݿEŹgSAFBƁƺgP;:ȿCǻgO4(rɹCɾiL,*CiM% ж$L(ڿ$L11[X%$@gw-,*('Ow8%0,+*(4]T#0-+*(CkZ$1v"0-++(#*Q{uG R6*-+*(&6Ld42cS!-+*(&$#@OK=}xu5Ftu!/+*(%#!2GRI:5135/+9Zv3&*(&$",=QWJ;YzUv 1#&$! DY_PCH]x|f,{ùuGIk~[oP&$! $-N`]KCPixyl;*y rM7.\s'$!  Zh\KJ`z|dNGIJJIHEVzoP?mJJQj\HJJIGDCTo{dTMD96;;/(WWMi 0 &Ndl[OYtU25^Y\yZGJHEKboZQJ@8;=6% RWSXN!$!5XliVQdrE/BmiNHGHVraUPG=;>;-CXVJq $DapdTVod<1Q}[PZMGGNefID<>?5# 3\aVl.'(Qhq_T_{}T5*NkHLHJZvzZ|xbP]hU=B<+ 2fcU^ M$Q8[qmZVkpG2-,1twEQimO@?plZSJKZ\u1/0<_}Zot_H?IcuaWPG@?]u`i?'/-" ;XULu -V*TlucXf}S:/5LrfboSJWqTCDWtjZTLCBEA.@bY_X$.0(  %WVLe M$L6bvq^\qеtY|^RRQMOgdKBLfs`YQHBFE:))_`Ug#.2, PUSO pOGs]bzqYRURQ[tvWFGYv}h]UMFEGA0!![aOt' +30% AZ]O| -L&W̢fVUVSTehNFOiVSJFIF8(Lb[jI1*  >ggSfL;9gʌRWVT[rz[JI]z|i\ykHIJ@/"MpgfX #--0\XQUpI-xg[jʐRVdmRIRlrc\TYfhZ5( '25EgbXb$-1*FVTI+Lxc^a^Jx͑nBqKL`}|i`XPKMwxg\ %197-']aPv #.3." 8WUImKJpb_bba^RtίBplrd]TMMNF7`k]e! $/9;3&VaUk1"-41&  "UTL]oKpacb a]Wa6{iaYQNPL>/%"Pm[j(#.8=7* HbWeO!-54* MTUI+Jiagpb a]6rd]UPQPF5)$%&"Jlbl7*7>;/$=a^\b15-! CadRr K*Jt|^_a`Z}TSQSL=.''& !AlfjQ=6( 2io[v( 'Ae^Nb oIg`u|_iqgYdiZQD4+( '&%%(?sshT  +5=jiUn*  ).+!SSRM * I yjcZUp{xV-*:)('&)2HNI7) ATRGr m eFwneabbZHCrw{[3556rxy^3=<;9=tzsC<64543211/.9rzqv91*)('& $#"?qphb,% =ejZyA9* >_aSc&  F~cad?@?=<Hppba"/Fstbm3 &A@>>AKTXPDkyp}3665425;EMMD9esir-*)' ,7@D?30cjZl"$2?GE>dbThM #+.,#"QSQFk Ewedd=@CISYUK@:8dzm56;ENOG<3.-Zudz2*).7@EA7+%!$ZkXs.'5AIF9+M_Y[]%./-# ?TRFk%   EwedeFSZYPD=;8_{m}7;ENQK@6100/-Sua;)/8BGC9-'$5#!Nkal9(7DIG:,C`^Ou!)01-" -TQI[F  hEwed~jYUIA>==<<;8Z|p~OSND9422110/,Puc~N@IE;0)%%&&%$# HlahI*9FLH:,5_]Op)%-43,! RQPF k  EwedgB@? >=<;:;_zI<6432110.,NzvwY;2+('('&%$#=ifceCLH;-!%[]UjN-53+"  DRPEq $ -Ewedh>A@??=<>DNThs<4655432003;DY~usT')*)(('&$#"%:r|li8,! "^hge`'! 6VYO` E =Fwedi>A?>@EOVWOAW}mA4655325:7T~kF4547=HNME91-7lnjr(**)'&&*4BNQK<1_iUt0!+3993&F`]Nv# ',,-VYTDy # kFwec}jKW[ULA=;<;8S~jM5?IPOG;30//.5kqhr*)((,6DPRK=/%! Pj`k;%/6<:2(-]]Sj>  $,.-' =RQDfD Fwec|nUOD?>>=<<;8O}j_NQI?6210/.2irgs-.8FQTL>/'#" IkaiN!(2;=:1'!"[]SdV !(//-$ )SPLPi Fwec}l?@@??>=<;::Qva>84P32110/..asd}KSUL>0'$%&%$##Ajb_b(6>?<1'Q^[Th&-20,! OPPD" -Ewec}n>A@??=<=BKR]u}Z265543211/-.2_nS>1*&''&%$#"4jpcj=;0%$E^\Ly,)242)  BRQCjC -Ewec}o>A?>@DMTWODFutz^3655431/05?MVjj}E&))(('&$#%+4Bsx`v*RsjtL'*)((&%&).9AEB;gkUt1#+?mm`jT  VY]G!=Ewec{pLW[TKA=;<;9@rwxi145:DRZZN@4/)JskrW&*(''+3=EFC9/%!Si_k= *18942_^ZYa  *Zb`IpB>Dwec{rRND?>==<<;9?qwvu8GT][OA50/./,Bqkpj$*/7@GGB8.&""!Kj`iS$.6;:4*!K\\My $,,)DWQE_ h kCvec|q>@@??>=<<;8A@??>=;::@L~z?944332110/.,8oymvGA7-(%%&&%$##"7gg[h'08@>9.%2]\RjL #*//+" NOPBx BDvec{w>A@?><=CN[b\z}n56554320 39CLxmu0)('('&-%$##!-fo]{A>7-$$Z[V\\!(01/(  ?QPCd gDvec{z>@??FP^c_RDFNOJ@ksb4()(('& %#"$*2=pwau; T\_Rw/43.$   .QOJNCvecz{>HS_e`TE=9i{l:4459BKPOI?6/,^s^?')(('% (.8?EB<_m_k?Qkm\t6    QPN=},ADvecy~]f`TF?<;<;:8d{m>50.,Tsa{G')',4=DGC:0' Ki_hY$,35QlfXlE    ;ABU}gCvecy~QG@>=<<;97^~rVTQH>620//.+PsisM&+0:AHFB7/&"! Fjb\d",2984* 0\[SbX   #B_u>Cvecz=@@??>=;;:>CfxQ=74332110//.+Lrjq`:FJG?5-'$#"!:hgZk!)18;82(Y[[Rp   :]s_FBvecy>A@?>==BIQWUgmL255432110/.-,LyvswB=4+'%%&%$##"!.dhT})(18=;7-%O][Lx!   5Yn>~qY,Cvecx?@?@FMUZYQG;96U}iU3553226=FMOLABmpdr*('&%$#"!"&/eulnQ-'7][RfS -Mh?sY;&  BvecxU^YPF@<;<;:7S}iY246;CKPOJ@70+4jqev.('&&%$#&,5=<<;:7R}k}_=HOTPH>60. ,2hr`7'('&%',2;BFB<2&IiaZe/Viuu\@+ Bvecw??@??>=<<;96O~w|nRPF>6202//.-/er]@&(,1:AGEB7.&" >hfYo';^tv\A. 2BvecwA@@??><;;=BH^}m864332110//.-,[r`yL5@GHE=4+%"!!1egS*%9Yrw_B0 "%#!KBuecvA@@>>=AGPVXSV|xys155432110//-,+Uwqu_FD91)&%$$##"!!*bgRr6"6Rpx`E3$"%),+)&$" ,Bvecv@?AFMUZZSJA9Cuwv~35543210/.16>Dbxud*)'&%$##"!!%^h\gB5LmyaF6'&-1430-+*(&##-BvecvHRY]ZRI@<:98Aswt45542126?>=<<;:9>qxn=AKQSOG>60.--*=<<;98=r}nURND<400/.-+6kqcy/''$"$-@Xw}gN?6:IPUSPLJIFDB?<5AvedtD@@??><;;Ss~hPA9>QY^[WSSQPLIHL@vedsE@@>&AGPUYVPv~k?45432110//.-+/gnVsYniPD?>=<<;:97^}iS/8H\zmWIHRu~{xtpm?vedqG@@??>=<<;:95YxaxftoXJIU~}{u@vedpH@@??>=<;869Cb|qXJIW@vedoH@@?><9;CUkqYLIWAvedoH>==CTgr[LJV@vdblKQes[LGUƷ ?thqu\KDOݿ=w\JAF<x\H;>ȿ;x\G4,ɹ;z]F,?={]E% ж E%ڿ?)*Kj{I%$6Vrwutc-,*(!Bcwwtsr/%0,+*(,OmzvtuwpirvF$0-+*(9ZuzvuvwkK!*rta 1-++(#$Egzzwwxvb= Ersq.+-+*(&.Bq~zwyylT.,SnvrrvE"-+*(&$6DB7j|{weK9FA406ew\C7522AYdT>78BYpyyt_gwnbruD!-*(%$$& 9GG:26KbbO<7Uf]F;8AWn{{yiN5,,-(&2Klbqrp,(*(&$",6FLB67G_fVA9ZeW8(]suC+(&$"$.$=LK>6:BSm||nU;/.1/,4KecK2&'-Zss_,(&$"10EPI;8E[i^F=BSihN+8ALHBU}~}}||}x>A[qkN^zwwvvxwtsr qqptC&$! $)DUSE?I]hy\FB5&#FWp~~}}|{qpaD1*Ozwwvvuutsr qqpr^'$!  5LYPDBQfiXBcaKOGHgz~~}}|~f9/2D_txwwvvuutsr qqpqo*"$! ?SYMBG[kfQB@?acGLr{~~~}}| ~f:Rly|zxwwvvuvtsutr qqpptB$! $-IXWHCOdnbKBBDC?Xf?]lRF{~~}}|{yu}|yxwwvvwvkXC``gsrrqqppr]%! ! 9P]TFEWlm[GCDEDDC@MybqaG:7?y~~}}|{{|zzyxwyxsaN>6.12Ltrrqqppqn)!! #DW^QFK_ymSEDFFEEDDCCqoWA8:G^u~~~}}|{zzyyz{wjVD;4.+8A;7qrrqqpsA# $-K]\MGTlzPEFDEDDCAH=;ASl}~~}}|{zz{zs_K@92.381/4-/hsrqqpq]# !Kax~~}}|{{|}yhTE>81288,//)Uurqqpn(  !GZbUJQh{rO24UQSkQCEBADWo~~}}|}|q^KB<538;4$-/+@srqqp osA!$"/Ob`QL[r|gC0?b}xy]HCBCNd{~~}nRE@;57;9,&/-0nrqqp oq] %>Xf]OPdzw\;3LptTJRFBBHZr~}ptX<97<<2"  361Xuqqp opn'%J_fXNXo~pO6-I|`CFBERg~jOk#yfSCQXJ9>9*';8-Htqqp oos@$=4SfdUQbx|eE40/2ijAJ]vx`H;:b~o\LD>9=>c8" %+%*.-3qp ooq\SB\k`SWk{uY=322:=BIC?/.;5LN#,-& ..(Otqpor>$W3YkiZWh{zi]_dZKUuoWLMLHJ\urYE?F[rvcRJD?=CB8(!9:>Z!+/) ,.+6qqpop\JAhX]quc]ce`]j}|fRMOMLShiOBBQh}lZNHB?BE?0"8:5c%*1.$  $141frpom&\$Ou|p`afge`qt]POPNO]tv^HAI^vzJFBBFC7(1;8YB/)*;;.Rtponr?86^~`chhefcgyMRPOTglSDDTk~kZQi]CFG>.#4F@SM!*+%30*>rpo nq[*lv`VycigKRhcgyMQ\rzbKDL`yubTNHFIUS5)! &/45@:AU"*-('.,.mqppo nol%Gn^YZY}dheAMhdf~d~dFGWnl[RKHGFQONS"" %/74+ 9:6e "+/+  !/-'Xsppo nnr<Eh\Z]\[X~ehgCLe\iBdb{tcUPJGJLD5AEEZ#""  $.670%5:4[.!*1.% .-'Grppo nnpZEi[]\]]\[X~figCLj}C~l\SMJJNJ=/'$:GA](!!#,5:4) /;4TF *10' )-,1opponk#Dc[`h]]\[W~e`breVPLJNND5+&&'%7GA\5*5;9/$*:7FV/2*  '680]rpo nnmq<*DlxrZZ[ZV|rz ~o}KLNQJ=ZE' $5:1WroonnmnY A sd\Vi[ZQB85 43310DWUm@3-+=**)('&&)7NMUQ$  $,=FFNXMs7*,+**('*2;A>IFIW %.44,2:6>Z ".893%0-*0onnmlp; Are^[[^Z_XmS5876557=FKG=@PHpA+,*)+2;BA8-)ADB]" +%/670%#884e #0:93' #,+(]qonnm lnY 0Aykc\\__WHATUkV6767=FLKB82/;PKgG*++-2;CC<1)%b+,!'0792(783Y1&2<;4' ,+%Lronnm lmj! @tf_]^b]PD=;>TVjY6=><=TVgbIPKA954433215LK`^?GB8/*))(''&&$4E?XH8<6,#-84AV(6??5) &++*cpnnm llnX @mxi\PE@$?>=;>W^jjC<75665433106PT_j82,++**)(''%$3JJUW+% *=A@h=6* #36.Prnnm llmj  @t][v^ABBAA@>>CKP^]fk47765324;DHUTUg++,,+**)'&'+5=?KIJW "/8II?]. $*86-;pnnmlo9  V@m^]v^ACB@@BJRUNDQV_r6776556;DJJC8INPg.+,,+*))-5>A<3,ACB_" $1>DB5>:1WD (+(!++(,konmlmW  S@n^]u_@BCJRWSJA<:NW[t7667ED8*/73FP#+-*! ",*$Wqnmlkj  @n^]u`FRXWOE?=>=;KXYr9;DLOI@72H10@PHt9*07@EA8-(&%&%$7D?]6!(5AFD8+,868e&,-*! ,*%Epnmlko9  -?n^]tdXSIB@??>>=;JXYsLPLC:54433210?PGnI?FB;0+('&&%#5E>XB*7CHE9+!&762_%#+0.(  +*(.nnml kmV  ?n^]taDBA5@?>=<=Nb`uH<7566543320/?TRhR;2,*)*)(''&&%#0DAPZAIF9,!662YD*20)  %+)&]pml kki ?n^]tbACBAA?>?EMRW`Yt=676"54224:CKXQcN(+,+**)(''&%$'2LRR_7," 9@?PS% ,/)Kqml kkn8 X?n^]tcACB@AENTTMCIYUw@6766546;DJI@IHPMEb" &.3>` #7734nml kkmV k@n^]td@BFNTWPG?<:GZS|E6657=FLJC:3/3LKXf*,,+*)'+3AKNH;/?C=>=;FZR}J7>GMME<521103KMRf+A*-6CMOI[7 $-486/'!662Y6")+)# *)!Qqmml kkjn7 @n^]sgSNFA@@?>>=;DYPzYLOG?7432101JNOg-/8DOQJ=/(%+#5E>XF"(08:7/'561PJ&--*" *)$:omml kkjlV C?n^]seABBAA@?>=<>CKQT_WoU47654332003IZTwN=2+)(''&&%$"+EHK^:8/&+764f((/0.&  #*("Vpml kkjjn6 ?n^]sgACB@ADLRUNEBVTkY576;53226>LTT[MmB)++**)(''%%&+4:NOFg) (;=;[?,% *("Boml kkjjlU X@n^]rh@CFNTWPH@<;>UVj[5765548AMVUL>AOIdH),+**('&*/7>B?7EE:e/#+2DC:VH ,/1/lmlkkjgX?n^]qiLUYSKB>=>=<>UVhb366:DPWWM@50.n^]qjPMEA@@?>>=<=TVfk9FRYXNA621/9MI_`(,/6>ED@6-'$"6D=XK%,3761( .756g "((%&,( JplkkjkT>n^]qj@BBAA@?>>=;LW#*1896/'*761]) !(*($ ('$0nlkkjig->n^]ql@CBAA@?=<CMY_Y]Z]v776'5432149BFVXSi1+*)*)(''&&%%#!(BFDk=;4,#661GO &,-,&  !('PollkkjilT>n^]qp@BAAGP[`\QE=RWYt:6%5447=EKLG>LNIr4*+**)(''&%$&*17LNDe7"266:e,0.*"  (&"9nllkkjiig>n^]qq@IS]c]RF?;NPXWs;557;AHMLF>61.ENEv=*+**)'&&(.6=B?8AE=[:4@Bn^]ot[d^SG@>=>=<:NYWs>620.ANEkC)+)*,2;BC@80'"5C>=<9KZXwRQOG>74232110.>NHdH(-18?ED@6.'$#"!3C=JX"*0762) #65/MK 6M_lomllkkji!f>m^]px?BBAA@?>=5,(%#$#!-BAC^"(/5750'553;_ 2L_oprommlkkj#ijko8>m^]o{ACBA@??BHPUTV_TI46M5433210//>SQbjA;4,('&'&&%%$#"(@B=m(!'/5:85+#/643e -J[nrsqonnmlk%lmnf[G#=m^]o}ABABFLSWVNG>HYQN56,5432138@FKWRXi+*)*)(''&&%%$#!%?B;d?9;81)!*64/Z5  *FYmrurpponnm=lmnnog\H. =m^]o~CHPWZWNG@<;:GZP}Q5665447=EJMI@n^]oS[WOGA>=>=<:GZOwT457;BHNLH?72.1JLLj/*)('&%%',3:@=BI@XS3/.9KUguwvtsrqqpopqqrj^J3"  >n^]oJGB@?>>=<:FZPpY=GMQNG>620/0JLHu6*('(,2:?C?:1'4B>=;9DZWmePME>7425110//HMDv=(*-19?DC?6.'#".B@Bb %5QcuwzxvuutsrsttumaM7( BIRc^md9754332110/.CMCjG6>DFC;3,'$##!(@A;n)#3Mat{||xwwvvuutuvvocN8)!"%#!;n^]nCBB@@?BGOTVRMZVii2605433211/.-AQOeWDA91*'&%&%%$##!%@A7b3"2H`q|~}{yxwwvvuvvwxocO;-!"&),+)&$" UUgr46054331027=CMYSdZ+*)((''&&%%$##!#=A:X<1D^n}|{{zzyxwxxyyqdP=/&'.2430-+*(&##;m^]mHRWZXPHA><;;>UUdx56(4336=<;>UUby6557Whx~~}}|{{||}}uhT?4-/;CEC?=<:64405<:m^]lEBA@A@?>>=<;=TU]y>=;:AGM_b]wC96544332110/.1ILGx5&-;LfuzkXE;9@S[`[WSSQPLIHL1;m^]kFBB@@?BGNSVTMXYUx>56654332110/..GI@fQbt{m\H=:DZejfa\[YXVUSU.;m_]jFACGMTWWSJD>;QXT}C5665433210.,.9Pbq|}n[I@?Ianqoifeba^ZXZH<;;:OXSI4665421/07H[r~o]J@BMjx{sokjhgcf_';m_]jTVNGB?>=>=<;:MYQM454227GXp~p^KBCPp}xwurqknf":m_]iGBA@A@?>>=<;:KYPN18EUm|s`LCFRw~{xtpm9m_^iHBBAA@?>>=<;9HULm]i|tbNEGU}{u9m_^hHBBAA@?>=;9:CVjyucOEIW:m_^hIBBA@><=CRczwdPEHW9l_^gH@??DQaxxfRFGV9l^\eKP_vzgREGUƷ 8lbiy{gSEBRݿ6~jSD?J 6kTB8>ȿ7lTB1,ɹ6nU?)U6pU@" ж@%ڿt8mk@#+E$"fc=2,QjHɿƤhչ2/뻾kKŽȥkֻ21mMǿʧn׽22n Oͨpؿ35n Rϩr37o Uѫu39o WӬ!w3;p Yծ#z35%ҩoP/()-48:;7. ϥiH& %,/22.'͡cA#&)*'! ʝ]9 ! ǙV1   ǗQ*  Q* schismtracker-20250313/icons/it_logo.png000066400000000000000000000024511476471630300201070ustar00rootroot00000000000000PNG  IHDR"2֙bKGD pHYs  tIME 4߂< IDATxQ0 L:;z88ʀ>xb&#'߾^Hdid'\^y ^EM 7߮>/x80ř{ HcbޓPa=Z-XgֈI|v@47 Qp6Nwp Xįd߿gU'}1\[N5G0I4O5|~pcnA(:k9=\N 9:%'@L60hX2-S6Be ^cZX[߮=G8@dIHh(ǎ!sFrAby,#gGΆZM9A2@fQ Ӛ}zdˮKH*;X4bV[e@5EY#k&oIvFb2;L`{9*j -2)Jh@"$!N7M$+d횒=E({ycG` K H\U iNir\e;SCKΚzBQ^djyQD hMK{g [YUKd( uNhǴΏZ1's/i&gD!Ŗ R[OkEȻ*f/Zا!m,幦$OD-:8B4ȵ"d-25s᜗7VAihwZdSH3 }(g[Ƹw&$-yvZzg˅y&#+]{PBKD(b׈ƮVrȯBxDQZo'1Y3^Bf{k#fmK \,Z"Ic1 s5Â$!KnQAZhEH3ժ##")4Pz5"z+e;Ė--!Θ [<L+&]\JYfK&YSeDϞD Qi}-3 qljaYj>f5m-ZE&p_d?&Q"i=V׹VK"Эo(ɕiIQ,L(*=wm PDKk{RWK8E͕e||ˁy>!miNF:G׻fZt\4~r!K9A˗~BkwƎQwH(YMP@ ێȃ#!s  DdC Di!"3KEIENDB`schismtracker-20250313/icons/moduleIcon.icns000066400000000000000000001613751476471630300207340ustar00rootroot00000000000000icnsics#H??????????????????????????is32 Ƽ żѱ о˶ ׽ҥp{٣ğΛ˧ٺԶҥͻТȦ򵮫򩨭ɧ ļ ůҲ ˷ ӧ۸ԴѶвŸÿڵݹѾﻹɼ¹ɪ Ƽ Žұ о˶ ׽ҥq|٤ŠϜ˧ٻշҦμѣȨ򶯫󪩭ɧs8mkghhhhi@KfLfLeL>LiLgLgLgLhLhLhLgNj7K ICN#il32 ɾBǝۢܕ0ѭ}˄uǼĩѶʷ<ͻ|w9ˮz_`̸h`{?ɰ3ᰣ⿑7ڞ}֨nh;߿ɫ:ӿʤ鰢Ψ:z▀d?ͻѵ>µ>ĎƘ╆죓w>µ³ʮ¢<ŪֵƗ=ÿᓅrmC˷ٸAܳC tpDӫ@٤@b]@ۯEĴ5UUUɾBѧݱܧο0Ѻ˒º¶Ūей<Ҿy9ˮ̽?ͷ3ͮ7⾩;οӺ:ү鿭е:ý噊㤒x?Ͼӹ>Ŀȹ>ݴӺ⺭¶>¶ij<ɾҳ=߷ʣCŸƱAƶC ѥD@௡@wr@߷Eƀó5UUUɾBǝۢܖ0Ѯ}˄vǽĩѶ˷<ͼ|w9ˮ{aa̸jb{?ɰ3ⲥ7ڠթpj;ʫ:ˤ鱢Ω:z◁f?ͻѶ>ö>Őƚ◇줔y>¶´ʯ£<ūֶǘ=ᕇupC˸ٹAܴC vrDԭ@٥@c^@۰EĴ5UUUl8mk*<<<<<<<<<<<;2 (778998999;:9:987777+ 36777667777677666796' ich#Hih32ǃU^UROGADHƾ؀ƼkփڝhvWiԁe~ƺx˟dlƶƌǁüʾλ ļƱ!ɵā!ƺɅ"߼ʵӢ#tkgmuyƲe`i]`%÷ia\^w%ڶΩqshaU ր̿%%Ƌ{Љxl3¥xyugo3%ꐟtϲȇ}m3%θԾ3%ꬒȔŤ͛v3%LjoݼktZɪ3%ܱßcЂ|mi^ɪ3%ҞæĦ3%ʾȫ3%⦯轋ᝲӰ3%rÿ朁ʙ{sڱ3%‹uߒrҭ3%ɽٽƮҾɺ˫3%ӴɯĬ߮ά3§ܡ} ӈsq׳3±y ykuԱ3%ɑ۫ʬ3%Źɬ3ӳ3%݋u|sܶ3%{nu׳3%ì̭3˻˭3%n~cֱ3  qn\hٱ3%ه~hа3  ͸Բ3̀ ´U3UUUU^UROGADHځր ƾƼۘۮ}m{Փzǻ˦u}ŽʖǁĽʾμ ļƱ!ɵā!ʺɂ"׸#ukmoxyƲ`÷%λU%IJ%ʼ%ֳװ3%ԧ3%뷼Ҭ3%Ͻô3%뻢եͳҬ3%òѕ訄ȔnȪ3%˩ȯyՓ}ztɪ3%ӥéĦ3̀ȫ3%ҳ±ƳҰ3%Ѯ澮ܻҨױ3%ղΰ߿淮åѭ3%ʸƽ˫3%˼»ά3%аܿݰճ3߹ ⨪ұ3%Գĵɬ3%÷ɬ3ʿӳ3%趥ڶ3%窭Գ3%ӿ˭3˭3%윁vֱ3 冂p}ر3%ޔyа3  ͹Բ3̀ ´U3UUUU^UROGADHƾƼlքڝiwXjԂf~ƺy˟dlƶƍǁļʾλ ļƱ!ɵā!ƺɅ"߽ʵԣ#tkgmvyƲgbj_`%÷kd^`x%۷ΪsujcU%%%Ǎ}ыzn3æz|wjp3%꒡vϳɉ~o3%ιԾ3%ꭓȕƥΛw3%ȉpݽlt[ɪ3%ܲàdЃ}nj_ɪ3%ҟĦĦ3%ʾȫ3%⨰辍៳Ӱ3%t枃˛}vٱ3%Œxtҭ3%ɽپǯҾɺ˫3%ӴʰĬ߯ά3ĩܢ ԉus׳3ò| {mwԱ3%ɒ۬ʬ3%Źɬ3ӳ3%ݍw}uܶ3%}pw׳3%ĭ̭3˻˭3%pdֱ3 rp\iر3%ڇ~iа3  ͸Բ3̀ ´U3UUUh8mk    TD mU uRwSwSwRwRwSwRwRwRw5w www w w w!w!w!w w!w!w!w!w!w"w#w#w!w!w!w!w!w!w w w w wwx aߞ #@RUVWWVWWVVVVWXWVVVWWWVVUUVY[ZVK/ it32o߂݀ ր߂ڀ¿ހہ ҀÿZ߀݀ځ¿%ں܀ڀ ȫԾр¾-}߀ ޭ\7Bځ Ѣ>?ѧww$׬))˭7ڵ؀ >8@̀'¥/ځ0>հk[ymĚ@¾ȿ$qhTrپֹjfkAp%Ot.lNȺA»ʾ$?ۡ67vlmyeGxϯK{KuJµ@ÿʾ$@Ǧtm ϐp'ϲN˼Y=HdZ?ɻʾA~F:}ʚhhʀ 1x?ʣ˞ʽEDӂCa/P4GC@H5+5q9964ʽFǪȣλɯʽ Ł7þſʽĀ΀ʽǀĀȀ*ÿʽJüʽЁ;ĽʽԂӀ ʂ"žʽ؁׀Հʽۃۀۂ"ûʽ߂ قĽʽׁ ۀƽʽ䲠Ⱡ 㲠௝܀ޮרŃʽ$ZZYYYY YYXXހ݀WWUTƿ~uk> ʾ$ρހ#ֿzrcaeccdZ\losɽނ%~wrmkiijkmpruy~ނĻ~yvsr twz|~߀&Ľ}{ujjw|}wnp~,߁%ǿmiDDjmiFGn#ýv9zl4ws9wj4٥(bZ~i3__Wxf3`+½WmDgUTfCbS&XuY{qUTlSogRäހڀڂ-ĹcQYja]OU{a\赢絢-浡ᰝˡŃ2Jv1|Hw[[ZZZZZZZZ%YYXXUT\YV\USQUсЁ&Ǥ ہĽ ߀ƿ݀܁»õа݀å|[U٦[ZـѠXWUT حߠäS`GӓWlȯ"DIcZʤ!ŃXށߋ 6E涠뷠"I| 3Ȥ+\L{dށMIEOϽ:/9}@@@@*X&HDAIȤށݳӁӄܯȤހހɤ ߀ʤ ƀ ͤ߿ӳζű׳ŵϤwWaځު]\ԧV^wommZYҤ JK"ԵWDZD;]W_jtğ?C:Ԥ5ҶP?Ǣ]漅y7Ψ`ԫ=ݠ?ͨ|s6֤oÀ_mPlœ`Ґgѱݠ?gO|hդ3WV΋zSܶx7ʦ_湁u7{}r6פ2ٔhƾaݵkPl_kOhhN{hؤ3oΌޫ|޻gih_gheceeפ7S⦠̲Ir]gvϿ_s]g}rlZbvpդ2Dǐo2S]꺨ʀʁ3R2PԤ2Ksezfc`gVM;V]]]]\\fb_f`][bҤἺ仸ѤФ½ҤſӤ صκ֤  ـ 殫^\˚ZYפ  δD;D;٤ Ćy7~s7٤ nPijO}iؤhkgdff֤  t^htn[cxrҤïπ­­̂ ̂3S2QФ__^^^^^^^^ ^^^^]]gd`gb^\bͤ߁翹žˤ ýʤ ľʤڶ̸ˤ 篣DDǗBBͤ ϱ##Ϥ wepp`zoϤX3vUTw2oSϤSTQP|PxPϤ cDPna]AMh_ΤŪéɀ©Ɂê¨ȁêǁ 7 5̤DDCCCC CCCC CCCCBBOJGPKGDLʤށށށ݁鹵ſɤſʤˤ¿Ϥ߂݀ ր߂܀Հ¿ހہ ҀÿZ߀݀ځ¿%܀ڀ р¾-̘߀ iEPځ ơLRď$96Eں؀͹-"NIǸ'պ?)ځ0ϦO}jōyŧN¾ȿ$΁yi~ͅxyS%`=\ßO»ʾ$?FE͇}~}#Y^YX¾Nÿʾ$@ϙ̌4Ɩ\qKVuhM½ʾAώTKʰ%uuʙ?R«˽ʽERӍQp=^BVRNVC9!C|HGDBʽFǯȨξɲʽ Ł7þſʽĀ΀ʽǀĀȀ*ÿʽJüʽЁ;ĽʽԂӀ ʂ"žʽ؁׀Հʽۃۀۂ"ûʽ߂ قĽʽ݁ ۀƽʽ́ Ȁ܀ƹʽ$䓓ᒒ ᑑޑހ݀ܐԎƿ~uow ʾ$؁ހ#zrlieccdjllosɽ ނ%~wrmkiijkmpruy~ނĻ~yvsr twz|~߀&Ľ}{|},߁%ǿ}}#ýywyw٥?ww+½&äрсЁ-˿Ūxw锔哓㓓 哓㓓%㒒ޑύƐ ف؁&Ǵ ہĽ %ƿ݀܁»݀þۨʾـĶ ~ȃǾ}ʀÂ}{(Ԃʪǯ{ͧë{y)ךϛNJ͚ʼn)١Κϙ *ѪҠարր*lj׳xȿܷyx 디ꔔ암锔{$ϕԐٖ߁ ݂,ɷ)Ŀ )¿» ߁ ܀ ہJSonpo ҮRXК.zHQuhgeKQäanG֞fy/Ƽ#SMU^i)."Ƥ)%DՆqј׵Tրպ'*ƺzgvǤ]ڀISfAdҩTטZ*_>]ʤUe܏Ήud`_ϨTޓdJ*jqs^ZXɤ8C⦞5yP]oʦTCZ*ýpMWthʤ0טfށߤCXԼּ1Z*AȤ9kZrށ\XS]ϿH<#GNNNN8g3)WSOWȤށݸցքݵȤހހɤ ߀ ʤ ƀ ͤ2Ϥ׻եށĕլãҤ ؊"Ґ΄}̓ѝĀ}Ԥ5ҍĖְ϶{п̀ŀ¦{֤۴ׯԠԌη⺛ƀդ3Ւ̾ڲثԙ՝̶綠繐ŀ—Ԥzx޾ڱӺڥ˨ɴҗĀĞӤ8߼ƀʩ޻x̍؀̗ÀƮxҤ2纖٦ܙƎ}镕͈рФ3Ф2Ф3ФӀ¿ҤۯȀ!ƹźԤ֠ԸȄ~Ǔƃ¹}}֤2Ӏҍϭ̴{̻ҬDZ{{פ2齝ԖϞыʵԞ̋ؤ3ڳβҜʴٝפď·ހ٥ʨȴ㧕ȧդ2ٸx̍ڀځyʌxԤ2҉岧ᛗō}ꕔ񕕂蛗’Ҥ!ѤФ½ҤſӤ ĺ֤  ـ ŕϾפ  ф~Ŀ}٤ ݲж{{٤ ܢՌؤߟ֤*訕̩Ҥ߀܂ ݂y΍xФ 훘ƕͤ žˤ ýʤ ľʤ ¸ˤ ӮSX̻QUͤ 0Ĺ#0#Ϥ ߉sЗn|ϤiBie@aϤfbdc^^Ϥ |R^rvO[ymΤπρ΁́EB̤RQQQQQ QQQQ QQQQPP_YU^ZVRZʤ鿻ſɤſʤˤ¿Ϥ߂݀ ր߁ ׀Ԁހہ ҀÿZ߀݀ځ¿%ڻ܀ڀ ȭԾр¾-~߀ ޮ\7Bځ ѣ>@ѩxw$ح*)̯7ڶ؀ ?9À'æ0ځ0?ձl\zmĚA¾ȿ$riUrٿֺlglBq%Pv/nOȺB»ʾ$?ۣ77wmnzfHxϰL|LwK¶Aÿʾ$@ȧun ϐq(ϳO˽Z=Ie[@ɻʾAG:~ʛhiʁ 2z@ʥˠʽEEӂCb0Q5HCAI6,6q::75ʽFǪȣλɯʽ Ł7þſʽĀ΀ʽǀĀȀ*ÿʽJüʽЁ;ĽʽԂӀ ʂ"žʽ؁׀Հʽۃۀۂ"ûʽ߂ قĽʽׁ ۀƽʽ䳢ⳡ 㳡౟܀ޯתƃʽ$\\[[[[ [[Z[ހ݀ZZXVƿ~ukA ʾ$ρހ#zrdbeccdZ\losɽ ނ%~wrmkiijkmpruy~ ނĻ~yvsr twz|~ހ$Ľ}{ullw|}xoq~,߁%ǿokGHjokIIo#ýw⯣DE巳ղn;CfYYW=?äTaGԔXmɰ#E=HФP\"Ƥڬ%7udw֩GրǡۡơiZsjǤUׇOFV3wWʐGǂMͰ٠Oq0jOɤUXvvޭ}iQSRǏG{W<]dgLwM~sKɤ86❕̯(aCOobG6JòY?Id[ʤ"ƄYށߍ 6F渡빡#J~ 4Ȥ,]M|eށNIFOϽ;/:~AAAA+Y'IEBIȤށݳӁӄܯȤހހɤ ߀ʤ ƀ ͤ2ԵηƲ״ŶϤzZcځެ__ԩZ`yoop\\Ҥ NN#նZȳG=_YalvġBE=Ԥ5ӷSBȣ_澇{:ΪaխAݢBͪ~t:֤qĂapSnŖaғiҲݢBiQjդ3YZύ}?fimkÓa݋qYBu{|eggԤڸ94ަՆLmu`jxaVdBɹn\dztӤ8Ùg\۬@ޖ5U_麩ɀFdBƊ5TҤ2gd|RpWgdbhXO=X____NqIBd_^dФ3׼ܹڄ߹Ф2Ф3Фͷ΀˯˴ŧŶҤ6Ĉ[aԴssqqӦ__ϥգ^]][ԤnyϝoH=_Ya¬F=F=֤2ݳBVܷz:ʧa溄x:}~t:פ2ږkǿcݶnSn“anRjjQ}jؤ3rϏޭ}޼ilj’aikgeggפU⨡ͳLހu`ixau`iuo\exrդ2FȒr5V`껪ʀʁ6U5TԤ2Oug|iebiXO>X````__idbhc_]dҤύ伹ѤФ½ҤſӤ طκ֤  ـ 毬`_˜\\פ  ϶G=F=٤ ň{:u:٤ pSklQkؤkmhghh֤ v`jvp]fztҤŰïπïï͂ ï®͂­6V5TФaa`a`a`a`a `a`a``jebid`^eͤ翺žˤ ýʤ ľʤڷ̸ˤ 豣EEǙCCͤ г$$Ϥ xfqp`{pϤY4wVUx2pTϤTURQ}PyPϤ dDQob^BNi`ΤƬĪɀêɁūêɁūéȁé 8 6̤DDCCCC CCCC CCCCCBPKHPLHEMʤ߁߁ށ݁鹶ſɤſʤˤ¿Ϥt8mk@    E   9  ,n  %6|  *>Y .EZ 1I[ 3KZ 3LZ 3LZ 3M[ 3M[ 3M[ 3M\ 3M\ 3M\ 3M\ 3M\ 3M\ 3M\ 3M[ 3M[ 3M[ 3M[ 3MZ 3MZ 3M[ 3M[ 3M[ 3MK 3M-3M% 3MF 3M) 3M23M:% 3MC+ 3MH. 3MM23MO43MP53MP6 3MQ53MP53MP5 3MP5 3MQ5 3MQ5 3MR5 3MS6!3MS6!3MS6!3MS6!3MS6!3MS6!3MS6!3MR5 3MR5 3MR5 3MR5 3MR5 3MS6!3MS6!3MS6!3MS6!3MS6!3MR6!3MR5 3M[5 3MZ5 3MZ5 3MZ5 3M[5 3M\5 3M]5 3M^6!3M_6!3M_6!3M_6!3M^6!3M^63M\43M\43M\43M]43M^43M_43M`43Ma43Ma43M`43M^43M]43M\43M[43MZ43MZ43MQ43MR43MS43MS43MS43MS43MS43MS43MS43MR43MR43MR43MR33MN33MN33LM33LM33KL31IJ1.EF. *>B*  %6I[itz~xl^J6%  ,;IU]beffghhhiijjgggggggggggggggghhhhiijjjjiihhhhgghhhhiijjjgggggggggfgggggjklmmnnjjhe`WJ<,   ,6>EIKLLMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMNNNNNNMMLJF?7,  %*.133333344444444444444443333333344444444443333333344444444443333333333333444444442/*%    schismtracker-20250313/icons/schism-file-128.png000066400000000000000000000357751476471630300212050ustar00rootroot00000000000000PNG  IHDR>a pHYs   IDATxyGy{gfC}ߗ`|B @~I^L9@ clXXmIdYЮνwfvYҮ<zzZA_jI3w׽uJsy׀ [@Sv󗖿tz20EPkСC˙"Da!#]ם.]1 ]4Nr3߯a0xW}m_23XR]g2*-B@:t蠂G/| ( B(b)(# a;0 PWn&ɗ !t 1t]u04|蚎nCa?#'1 û 3,oI?(Q`|{LPr׳S|~TUKUUǯitvv/^o?a`> wee {㠠(w$ꏚ`bJD`j 3%M' R6SOg8;BW) K_9]od2y.Fom߾]kw9s3aΛ(:jEa>Jr%L&C2 ~?6  ð[Л>&_ֿН~?cjc7J6%sĴWU]{0 m|>$088HIIIzXU?7ztYaX֜BNO 1G#m;B$Kr4y3 50Hi)K.=s \/?OH{>-w rq _YL_Fl\. Hww?sfā>E!ٶ5e{[-udDG.CA?uuuDQ;|<`IyuL% e͍i&=\V\M[6i~ /3OS?E5Tr,< ?MųG5 '|hF(>H8idYr9s'9ȸdI`tf hFww7HX,y睏|ӟsz=$d3@e 4мc}ݵ޾^j̨iz:uյĔ(Ѳ(tō \r%4ULȸWoDu/j`Yt]Gg'HcfH4cFږnCOO%%%&ԧ>u%g9L Nh]@mY-29 /oy]^.Zss ,GHN:h(U,^~z[;;f#-2T+m.:>D_o:[G4:,SƬi3mcbl6K&!X(;a?^ɧ9+ (ys]|~nX4s׸ D0xœ?KsK c3z_7ߢ6c~4nǡhmmECc.@9o[yu*U|?ν;+mb ,ӦMf&UU ?ܱcGg "8]Qd1.ǎ{ dNLE|/e, <`wF5 ec3YPkn݉O ,VpܒE@뮻~(Y4e6ҭ3 gzMd8V~ªK-,uaI/#˖3c85 U_q \\43x,T, D&9ݸeDa4M+Ԁg+L /#K" ،Ԓ _@ԒDQϮ5ʒZ_A"0 yyx[Lb&TOƌ1t .'3{Fg## EQhit-F0dfdjf3`fd sߛxn`Q"{':P jԎ('H j`A|aGUka^Dt3.*SaEQ3*ճ|[}&@Vw8_Ofճ&o|뀙7g (}.5Y믂z?%ed2H&R)Dd2i )JG]:UTTTPYYIee%!R(YD"a<LIgO8>jnV; ;];=VcV*mW+u5j1eBЗCAeI;|MR tt:pvC7k>T2L*g͚PW7sH$c=|U 1;^sz/sT(&#sɹリh{j`Hfh`D"aG-d2vD*o!֯|̘1ٳg#=Ca2 iҘ`Jff*k_1Xk=G>>m;& ݮdUU r`=ziAoom(x_9``J@'QLbR 1^c,)8q\N_W^H*3 OIdGT֟%1L@f9$R"N{{tt_ Fn74aJsǡ0s2 Q8y¢X[k4+sGB£a*))GHWe5c7|a?1rfet N ~9Pv/<#tp}ΊT;";Y{TTe\U]zt   m[W4%A)D09-b[bc1F]ݬXl~ەN/}'b/2=1 v*VG{dt)`Ii>j35 HH$B( Eb1|%~}-4z^r̊BӇH$뮹暹 0)R` &Ny-,sF}ϸgL/(KByCD"ѨR[#!UT*hjH)ʢĵYŨ>bŚ㎏b.ഥj}!>@s/6y݆r6#t\L QŶβ Qڻ fG*b@Uykh+*W&~>022BUUŘS$=1$0`3G elh `d.i籡SSncJujkRKc%HT膁'P7LWFk' QU^` @M9:t$120B_^ LFwlncKou`KVN9tƁ 8ZGsz6z7 9!y ]h=xv w؆5 *GҜl'K(.Z˜< / ph0P_2#X\;EChc LƏ),0WUƇ4EQXYoCd./_7;-0Gxyώk?,h[%)Š;zcqyR6|qp}3Բ'" f,[yu# -buJT.`9xtڢ偵Nf;# 0྾ⱀ|, k5SN{zx/GV''BPUR&zG#:ZefB 1-v"яaT*ѵ54ll6KwCS'n:>`&o{єAa+ݫLu M,UVf8֬`׷Elo$RU9d2d2VuW0<Qo2(4 0 M8A^5, ;Uscfpj9?3a5I+-w ƐiܩxޫbI\^=TU! [ϗUyq&8A0{w2,(6\,WP֕&! &1:ZJLK| =a0+:`R1H5p$u-WCH ~5Mڋ7Pa.70N/|:[l{bjX#j5Cd,;.f}/ MnImWUT!HSZ^D b̉Ϧ+m]y52deC'ϮiWփ&~V[ώu |Q\?Un>& M'0x~m\"t 8X1:}D(#;FW0!~}y{9+f੶gIIGɊ;0%~=wuTaėP^R.u9 `ߑFZc$h:.k;15fjr¡#,[J.ˡM͞ }0 /b#SNswbD/x)WT%f"?s_q@T)Mm~utwK L`/r-[n HSZRFk7Ϳ>T ĭ{dR}^OaÝ1zmOP>މ_D]Os+Bhn3NP>\Z-!4#ܞ_s膁nU_YRzD91@" 3-2$I-U@D.I"`vWƓ% zJRzyqB1>~G1 R)%ރģqFS bs::Q{`%(A *J+HĂQ¡-=AI8J*"Ŕۆ)1:y׏lő2¦#hmıBP+nZUm+DVU/y;|*KnprWW/~! {wh WRnG~ϹwqՌ+;o0 *KYi( 4EE_EQnw=WuhK*Ni=Ѕ,\ a9&rLz0hdd+RWZ5`|0C(fZk\b0&"qK[XK;FQj[fsԆjd2SaP+˖/;ko'0A)o945VXwIxf_U"u UQ2 F ɗ'WRa[q ̈7?l:^mo\1q3KgDBuq™ Sݙ l#p) S+8AR';%A(B.e1TIXV|×?hŶҭӷQXƫ{{! |SǝW +C*>-$z~$Kqh|yix- f98z9TߕFz״S[ !p:; >#qL.Y*(ǯIKtc"E?@( 0ӷtxxc#a/jɪW?7E(]{nQ(4IHOA1JvA5vmۉT5f2nY X_YZG[*Ȏv>K z(BR+%Q$},/<TkY:kþP=ML, Oȷ&@+8 [}2-{8a$+ٍP?~}{KsKzWǦ|t6M4Rp}lHy !*Km_}Ģ1~@u_? Kb膎O:C>'W/TVxNW3)WCarY7_NnÍ2(BUy%6v&xgw!WE1WnzeW]}h=}r9^ '>Io<{ڦ( u3|>vNnE0ƨζy/0 BQ~f=8Q%JkM\pхD(\m{s7ۮ(BtCs]G0 oGFEnjX׏$R+_?ܱ"ZN8|0KW/,Wʓ='j>D`̹t ͎uR`=e|Y~+jrp0YLj1ʑ)ET_緷muL/!:3JWg̣4/r'qh͘\@ \p4lYX o~cĝ'e8d]̟7+.`jg9j# ##'K~yKX:owJU|X(N&ZֿտSDi7  >.u9 ꩧ[Hr691h^8eX\*;roV+U7t< ) ?3/_>?q@95뗈cX2>HEyϙUcR(J<s9M ("7@eiUlxzBVM/mbhdtsu]Mei3$^go-)zv ]uQ^^PO`”>n>Fyb7}co+on⚋F\ݍYp(PH,˗`۸O$y-v mF]n^y7yc :z[7蹞^s?ozf ImȾc…-vb*I? C]iw—uk⃷|+ο`4 !Az(M,VHw^.z?^=SNz.^s1C÷D2?yIDAT~k,5bo~i^=_0zljoV.sK.g/_x)o^>tM V:TT` bcccEKi7h9 sg^X,G#\ Cmb\˚V[!( u_}Xt16EUUf08#!Q;gw B!.³70 Cb17xmeX`MxY쒡M?i YtP}fMe3j- S> yy,b| Om}nK1F 6Rv,Enfb&ֈ; i%6Xl6z#jN/SM๻'pR`JU3֋Ԅ.6:4qh0#QK)>{;@fC1 P1_g"ۚ, EQhX nX1=y8DI(J%@ap3$5i ]Ih9=Ǒ#W͹Axb^k4A {7]nO8uwqBP LTXB1 U6vlDp+XVio3bI ud2ħذE<,ǷW?8rc Cn.[u3#4 {6^79cEUo6ȓʥ b$5ovg( Q7ڷaϹKg_T zĹ Gf(;`T 40'x,{SY/@"@̂9Gs hQfW&y+wd?!=B3t6B7sy'2UfBG@Z3x%55, :X3c5L*flَ>e0xs2gSfWX5m@34:Nd^?mz]NBÉ&S M'BCtvRr"Gyz29`R5NI0B<Eph0]GF# + x}'@ UetuQ;ϰO֕;#$rI|J<w%S;qW*,Z,_Ȟ= Tp( b!"4 8sPܞ^̼44 z9}5sWQ.V҆%l،apͺg_ĹT,YD"`wNoJ9\U/c^\6r+(Au6v p NZd0Gy S` 674̫_k>ۙ**Wϻ:^43{õ]ƙY>Λw.~ Mr~wK]`ܺsxxOe\l AQ%.y%(LO[s Mr^mp' eAWsh^DKQ_V 2^ɵKfKP7XqgoLs#ːNt- ĸq \be]+&%ϋud\ 6jfu9,D3}/C&mY{*L9GY6s)@B]cX'aƺbpwQVRz{,:Pu7=M(̹o"2ck"|>PC+Tz&nX}~NQ+2vl;k[( !VK2Z?TZ^!CA!s[9:S]9v7\ÒE! 9>?o%7D&&'ʠ(xDwakG$A9 zi޳:cw-lr)Ydm}-T$qlhAWgFi3נ( AB}K|?.٪:G2TI4cg. CqA>C8A^S$ -9.i}&Hr\E\]SRM$Ύ] *+ICD%4mDH ɱ$@a `OE17-ϳo7QϹ+\ ׶sFYTp 8UJͣX#Wd_gjgxv2zCOhlCߝ6 4M)M&PH t+W^IX Ċ̪Iso kfF5|?y뢁Bj5-_ߞK/bdl^DžϷ0+6DC%bz7~Nu5V36c1X@1|/>5  ]TjU|V}`٭lņ]uhkdh1Ae9%m!N 4X@]IehO$PBCt^/6r{7 0 z{Xoɇ.Y1`0h' YmY&nѲձj(\.f#_ B@%0iZPp8ի?Ō c~<?=չ|`vւ/kgmݣ:EmB0j(ql!Ot`D钜`*zy8eKQ3/#zKbb^@D*Fi+^/&X^bj 0P:8t8t߻g1n<&UVy%d|>^2ݣ˝Ow3\e@[#uulcH@{Y0} QI採:ZJI ̎aN;`⻎2+RWXm?Y.jJki<׳r!G #x}#B+g]E[̷P"UKrKʆe9/V=1[Q̭qA/?=e+.p%;e7ru<#EluʔG8{mp9Ħ6Z݆C L'kRZ=XRk/PX=k5pѳ])8 βridxP>>CGB6dLәry10=C=̮MM4=lo`QP/RكƐ^OOr=/N)V, ;o|LEN R}!Ő_l}pj\@sق 1~ 3)0u `ڀ止0E!/AQ@!%b"' KTD.1<^6Oьy,k,)j^Gǎ<%,OTlDQ׻a#Q@^P"f뛔J,xeߑFH>b}0Ť~[p:ՄQ6EUiϫ_y (4ge'ͥ:MSUUy׏luܨRb+%j=|ӽQ/{( MͼzUw5PSRE4kuRC;ʺ̬fY_h9X)csgsݴ5<޹bMj;kA[Ԯ[3?x,[s[/W ں<+׮-繦=yfs|g}V|s7'Vf9\ĚھR`nlqvrLe2a҃Aϖz2Of ϧ;aO=@[z{xd\zBx%\MI.l.hL&CH'S4?p!@awy9[A^츺4!Mx(*G7΁wڂ&lMs}k'QW4V͵.G:kf0l2aJgi|_L|NDl 9~ofM+F ܳ71f5 Enx^/v~d/éqbt3`6hN}`a\'0Cuj*Js!{V~2a ĵw\7MGA/BC`7NysӇZ/V>捛b:A޷vsb$_ ټsƕɆwtI^ֳ3͇^V\޺wnvx,װۈ@0/TM0|Jy>p ~zN'NN7b]r!_#s_&%fh}vD"VeLx`4;*3IDW G~W/"[Stcq|TS^[}MӘt&(/-'^gӆMAJN QVf~PvKm4H ; 6־MM~ٸѩMMEv5*:eИc_dg㮂6yIEڷ? 'ذ6H5k֬aRClݼWϢEؾu;}=[wr+"8)0ٮ\-~Xzԫi-?~~o>8'=-w2 8^ϭj:~\6D"a/dt0 r]@g ~Xōuv,%:{<ӽ AΞ|i.XK>#+{:OG=Ӊ0 Lsl մ Mә]?˱1aigr-r9.˜ PLxyNQ$p:.DC7 t)ie#P`M8t3 J  >BQ֩BEs2f0:QO˞ wbx2Zy-V^n{ZӂXבSyaD=U[yS)S T)w~O>@>8c^c N N7*YdҼpX@1*sxN5N`xyrpIUUǁ4PP mf8@A iTU@%POR9߷ 'ץR$9 b+6;ɰF1, e>)wt͓PXߧT*5o4 SIENDB`schismtracker-20250313/icons/schism-icon-128.png000066400000000000000000000415361476471630300212060ustar00rootroot00000000000000PNG  IHDR>aC%IDATxwtdu&x+G (ntsn( %K$K`kG=339{Μ?fIg,J4s7;G(s*P(vS&E< ^z7e@Ɲ[;ǎ; s(α;ǎ((J;wtuy;<D[Ox G::ϝ8w޸a|:+rG=>~yGOhs7 ٣ڎm/| ' Ξ=v}zvݿw?WxӇz&fs'ڳs'(`0<?k7=⁑/}/8<2ʋ?ݥ`2|<¿XQ?|ymt~ɯ߽&fܙ>hg?%>(N{s# _6ojLռ ; 1ϾGlVK_gP(ʍi9q`]~[f.}䷟ZN'NÀ%UZb|too|'ua9 BlmnN߽SiISm[7rt;w=F|AﹻR0npzN8z3OuY tӋt0c AY e)9!N=yyrZǏRrf#K8̢fy~~wZSIO0qmdj {Z?zwkzn5M-潒/V##2|SNiY]JUVr(*nݕ&P8c5 mnjIeGzrG?F/?w,N[C[f &w3r-{H>rm2>@XeEn${zdP\BI8c@Haw^xu}9o X;$btg)ݐCfA3&_JCmrWX2-o߼+SKJgW N,(T)ym Gk;w\=[sw; +ޏWf4y$g. uw@v*<햩Iܾ^@R%} +ɽ9ܸ;'倊L(|:<.;#P߸;#UY rvz.96:rM-*ؾ?8ã#/h,4{J;T6+S;bN'>$on6'=#"/!"b$=M"1ԭ$rEn6`rl߰[K`.ioi*wy@)o-FcC; aw:͏+AVfAAy-]c'^ʍ+iy9`me!޼*sK~).A>7-G )/0>=/?ik_Ws'_6m46Ն#G~/ |+-2hSZNe韓'?݂]_e瘜 Ph+|eѽCʝW@W.4o UMTw,/su? 9z(锅%I'z69zx]27wXuRHj&~_S'0 ygQX\zn?zȟ/|cx]T|ɕ[r7%Ǘ eKb6:D*mcSW/%KFG?9B_-ƦD ̑o}bq͏MyyZdwo,혬e%"x*ΝkMʠgPf.wɣJK[Xe7ʽ ~JH?ɁtG2_xp{>_¡h"eZG ':{`/\u&Ptz{ fuYHA|biHՇ`4-}xW ? 'I/zC~I^5&s:Z\ti=|}hGgb\549 'c@~%Gc4%IO楿M:-2;^GbJQHl4#@Rl&zy@_mi0jHzzz=|?Y.f7O.@XDB9iyO9$LMnb cg 7'd!)FFul꣫ktȺu(Z'p=nMQϟS~-S,mgrMttky9+r0 j7SBRHFe=t*~X\+L0F}cW! n 7(dݿ+'G:nv^@Q)kSc)90$Gt;Tp˧=z9ZnLh7 fA`! FGKv)ģ_>GZ0O5bY j6ȔR2ǛѤl [q3Rn98tJ29k@}N]V{֍8߮B5S{id2v3'\Z%+zA<6Dyq[Hz[= X=Ų{VDʉaCM&QeRAh*{ ؆,fB^|C/{=G;+3׉n WR婇V"a_| <+oKUMi(F.-I6h2<\Lr 9+'U"Ny^Մ><*91S(fKrtCTzbW>~o"wȲ/5Ns<;tM]ɡ_lOha楶 Fc|=d_ESA3Nq ?JFfNc14QeIV{xk^H" xL+H`PDh4#//{e˃n`b~B!Yn%4n6`N0N]P,`@vx$) F5@kgڅA7(PH7$$ɝX_+ 3>Q[]Mͱ@b6 ombY_lVrJ8VuGc F,SnoMd8kG#Mq{ D2ȫ&T"Ь: qyD$\@;m/TL=y dȍ-RS:Œ!,5<FD:E"Wӱu5i^0Q_>v?aFzB-\kEʛj"3bua[QMSlQ5$F@yYb &]^i;2=!.cJе:ĢG]Cm>XAB]NϏqқ\lWUHmPIb'zQjN'jK1_JM&'HAʆɻ )"j^j0r,>T3TiI%2ɲW*UPC[@VvNּ'k)od%`@d"+e]V:$!(FwgJR{0}S[o@7(@E;S}Ղ#Kcms&`Pa^7 ^21E4b@2Q`B,$ #n(Xu^;W\chnc\)yM\$1x"[Wl1 CM5Y/(=іEݝ-xs|*ͬ`cɬ<-x²:d"򞵂ɢ T,<@>4h^#\{͎8$q{J 6٭[GwqNZ2&h򅼚qqz<)%vOՔ01&'g%is57A$2\J0=,X^tA"\ XF8.PηfTPDwjn`&jh;^,_U[ G;, z6?n0eJ'wp &t3`D_ ,LqHp3=-XzXb0-)0h(7>t *vgkb2N!9*):ƒƺɐ*AM69 X8wnFW`AنJj^W+B9ɤ,82xTھz<5Ed돏9DVuJ 8/CQd.Bi?bϊ[ˋ )V}! eF&tl01oBWtm3ZkSVv ləc9/*ih@ˢNR57XC] Aq&3@(1B*+צJ2\},gW$N<D`&/,Ū[̼ ЊDB7 cnkpa!8 ]T(%uE7SRpv! :aS+@U,- *kOv$2n:;&W.S+.^q;O+c\'[*Zl6L<@SB쥠T% b`C:),iTVlŹ|+ D۩u*\켦ڸCe0sTZ7=zAagEU8\ɴzfVk:@aK˷녁g rf3 Z'D 1`Մ«xʦiY1v"Y!V,!^\/Vϑ۫Y_,ds.(K%TP|2RUP$Q$KB99^ad5 .`(֖S`t A׾Gjyna'<OCa``w24q Sa ! X(0҅V (*/(ؕx3H%trMg@/,j(k$@Cd.nXH%tx9@, T$ W#ik]ByxPX{cP򊛵}v(Xl.*-zwo\O=ujsf}Xjๆ7p#rR+ǫ:^FsF kfe]BS/)#)f"yq* |4]ު $,fnUW u;d1010١94Cp6tI)]|0e^s * h -K|':=+n]3cr"C3șTYQ-ڢFIx^$GV"ePodsM|N{`"Xĵt{W!Чl&m" {8G%(j(m):/EdWL(jW_ gep[27~O9b(D׼Hrݚ)O n &,*k4&fÆXͪNNz&v GQa X+BE~YDZA4*l1IK49T"׍PW(38mGP(\MÀqq-6m!߬tF(@b>}JIǦ0!,\΀2+EdC:(LR N!%i%Ww#X'C4)"HMˬ`z8LgO%WIIer2iemR3D/i0V*6Œ[6s9"|Z̛P`VaBߤSX]{pͰ$ȸN$o& mhf4`5PH$4 k|E "Yq@ bK]͋61O-u`0Y`JzE`cR̞z1'l1n\Vb  )6]IP7hڤ6u߷SVqY4˵&,,K``;ɢ!FKB N 1iQ fPNKCO 0?1xxgo92Rr;aFjIWmdΦhl'}!FԢP&mCq 7a1_&sa . kV-!BLv- ݉Sڐ KחVMYI"mf=op]&5 a-2J6,.33XBGn;]{5hPQS@L(;QaevY%ˍlu)F>ֹMDJŐr. S!z4ΠӎдWXO*)2a`%"$29eGʋKS/$c`n0@ mzm&r > rÐ`zujfd/aoW0MKu ̯?Hua—3d9G:Y=^S#W/^?P ȡ*"[>, )L6JbU,Gfo_c /]eI  -N#9>_&JlڈBu9+4l9kԤ%)lȤ18m(CޛI9߯F5c豖M'.N9 y.jb`LQE ž<6,.lCIE7zעkOU nQl!&{5/`6rͻаmssu*a`jF:ZD.ܩ;:3ymOIUt|USApxdW"o-D{ j _|6G + cܢfi`֒ˀ5~5D&VϦ% NE]P.ǰEp|oebۺΑNFa`s]L]:}Z&tMIZǪ[U? WDR7Y:9jFK̗[LMM̚[ܤlVnS[Y`!FgOpܲCc*YT҈ݰW@4>> s0`\8ooO*O~^BF, gIXI㖊[3pV j"² \mՖKA_L)Y].Z@ݳiV8mL;ZMEU#GC n0ͽ "sR0khĺ"w?0 p)dr`;>`K^ A">V޷浱/y-)8G}R!W2ͺ;7Xz5s%W=]ڤ-*9],tb;=aE,0bUƮI0!Q*PN5ҩ} B1(I'ʿ k~;o]O{٥`$BIЩ/!˖^o^+`!](PGi1QQVO#hB+7>V>®֝T_X>8Q|-fWB)J ԮdrAKW; y˨rݠAh#_`{;n9h]oCH/\ȡ@P,0Q>2f^K׺4 ~:.3oZuEYVMU.9ú[-b :vr"$o*\p*Y7\H:J1jKyj= j0585$[A ٫ z֭*Xx $Qუ%j7*wN^ǥ_כx-t…vX}GOdr!y;NMYwK!+xmX:DSj,!bKPΦVm|:S̩ؿ]][V24`L:`[2&^ J50Z|YE{P+.^9@z (I4\>`s&`r.[QZ~¯E򶶶/R^a#<)@Pa[{'—-̭l`IH{F ,7Phl\]Lc-{F{ af>͜t3rvWc lg?#QK7辰>%ϒ(8ZmzkٺY8nЭ_^7T["pkeZZ*+ kYAPR`v)F"vsz z6v )mw60s &wY\=?7Ŗ67]F1Jw ߯¥_yUW:[!bc+ȵƑݼJ`$Úl1!GL*ABq4&\(iqn1G'05:\lR]Lz!X"<Bӣ], ,v0i!K>u;c٥AIl[*D)dIOuU+V4f+cfCI t߸:cxe|*-@$F=;5&ⶁǠ77Qx=(vXcoc̸M -n-̆|V;Z'W{/?Oz~ǃ,xMh0MM{4ˆ"mJA~~+@(2LIb ,!* VۥtUKy=t9|Jx?lJX/qKWǧa<",,wvՓ,'k,\]^t;wv=(XZ H'uT@~rW%o ҠH&3e0,q׿yM؇O14RݬT/\]6>bB/<0/d]d7t֣=.eeƎ>C In {Md â La)pYck<;x T66C6.~zбwF(ec8f(td0b=~z(v6 OĢ'RKNrBϕWi  By:2kEދ\U;d:*].xP§7ٮ?uё9&lhmYAgf}.-_4SẪv|@sՍ U+F^ ]**0Iaރ'y0a&鹯9-<,fg1pE 7]ggl3/%yOz5N F-&oSXU^WKl\2*, mW?_x+W )Hv`^x!# jOS`nw+ԭY{W~ne.C3:g_/@~Fu8=?> (O[dȍڰ KG }׿C!~o>;8ʲҬ(-svDYGZ [7A>}6~o~Bn_^~tTp^Q>!|O?ѻBX7^·_#izonqiOWr8~9=xԒ!<H("fI PR:u-)nl ,p噹ۓs?|s=-±ޥ,>nfN<۹q ~eXwDžS$ G^D|燆(Z;wq?OU_p+.}neW,h #rs(α;/2QxuAIENDB`schismtracker-20250313/icons/schism-icon-16.png000066400000000000000000000013721476471630300211140ustar00rootroot00000000000000PNG  IHDRaIDAT8˥]HQEiX~OMwꫛ{+5LԹRd~V:"LȐ. BhDuWxQ7EwR#?sΏ9/3/bT ykoO$ MD_!n2(j6V&5L9_Ǖ%:Ҽa50qc,'`%$"au z;WkCn,QX0E2RGtn6_|xfbQ U/ˏ/3(;@zau k8{O8_ƷFoDAGqۑ%+귾m_*  yvFա(;/7t4=۟}ч[޵n5kz)ӏDcj6VU~o{i:^v]vڶ?}m/?Դ!#7ޡ1÷.+wզO[دt***yCn~E{{}uj~˟9>{Yi˷ͮD%ZU"Ry⡝?Nz$ : e~?V_yI؟WԸR_ԓ-Qlбz~hr7O?Ju-~&?~2XThDR RԌ{oz:U]ݢb? ޻)wˏ6kоya+c3VH4þs.KjgtGcqr9Ԇ&uxe~/LdUzLoVקz uî?Sj{`TX"z{@򃅮j,aN|-zyݮ`sӧNlzAu=V0ǦR4Q@JГ[ִx-erNG+-hzsTݼUk {^U,QC;$W)RHٴ\hϦc^78p~̙hʗ]Mu5_mnl œMkZ|W:s޻HMll6zfS}:hOr~Oն$ԧ^WO~59bSO;vXS}pS La Y?@+o×UZz]QAep }?=Oh ӯ=ͱYTֵep>F=jo5R)u2ի>5\ .Pifa tt#K$TcM:ܢFw^?`ö,6D~/}rM[g?}d_= .4<?1(P]n]:T`6+WŬz*Mޠ֒ڗ7/ҎO[篩qQt/tEnZ^=wj7& ksGaވA߿~h~qkM ^t5-shP=id4z+"{cӡtSU&h@PzzG$3^ʵζҭ.@) &e%8IlvٷuZ(xXr[OEy\Ny\8m髯>R0gk6m'='nœ߿E{=s*nc R 3o"QkG}IB$Uk'2ڜJ篪QqL O&.CojT*0o gPU~5yPܢ؉|/H/YGrj#{H!^vWKJhPؠؤ?S/תV51QtFjcLuw@~W fգaY0ʏ(VmZ,c퇗o*<ӄOǡ)R1޺mQu<^7ݻ~h>>[M _qc{:6 6Ǥ$b鰫uºszjW$&u„ߍ(A=}4pwR3t^Nu;FݻUN+FW/g^`X>]'OzʋR gO :sf8@gTUW=wIh_=lLLS 㒾tQjmQ|nWֿک ̱5th~N8U]=g e'y 6qzhvLWi]?k'l?CG o&A$ 6%W-%zL| 2hN`+m ڠF8 *;҆a놵ԉ:~ 6A7$SHԞ-kTGcO2yRda:Wwl]k*T,ff 3spFU,8|ښt`Z-x,,; s^ظs}N,כ5CZЁ:'b|kmj󞣿?$ڞB/ǥf@ObUWԆFZOJd`aʜ\ݿdR[c٠lv_t)Zv}-M>Ag`1zm+m$ =ZaL{Sj>;=fӟ2hS] ZF}_c=*Ĝs`Fфo_zG嵰Xuؠk5}`P4߼}ӆo}pp#ۈ?Aъb090ބ^qXA6;硇Ԛ51Tߪ Djizr{ʼ ۄAoAwF Їz?y":m2~|0Һo/|P"n!'!hS(4ỗn@E9.gaWțhæNb3.uj#SK?ĉ;8"#&57Zm>Ka ;w._+;3-_C{CuQw/JDljJAUi9(C&g7;'kٺtU!7~:&'w=_00:!dd s-Pe>(׉;YktKi6Woݱy^~pf"y5VXgVУ"V_7"9ex|Zj3`/aheyAJ\ ZIoRhJiA =017zsuv׾'5a$Af YdV9s cUN4FKe50L0>#%WNqyj~}ONַSlĵ zb+Wr_O;K;԰0Gʁ" 6#D<8)h^~3j`DY72 fRG}5*b:OmE]]=* sϥbH'foU~^ݶld[7O}9H| KFG0]VpK{cbt5 ]=U3[r8*yPQJM)E+Ĕr߲T)WuΙiu- h_g]MtJ95UvMjc$s " _"`I鷢]sGmH>eBV #p2)Q5Vhm5< 0O9ͪcOen=2ymWn 5D :uV۵kbe3ڿ^z#`|IeALj uNݾ7q1C.ܼĤxRxN '1XS Z^F}{ߋ:$Z ѪQv ۘ?Zqf=2Fg V&(f79Ff~} zГ| z  ^œŝ8\ L?sR=[I7S~9v=QOu-=[$nLWkD5ٽbWў9F^,c˜?g~ !Gª0=W{ m^T)3-:`@z)3f9YGB,{<ӡ2 >@ 6֯?i9G+ds:ca17DŽd"JG3Zi",]{qçktbu[ܒZ;Ԏr|gŹ[7HBkW5N$ND EI􎍫~c0z69j?P/y׍ъwAOn޼vƍ/pQkG1EQ?&ezmE; }dw&CCinͻ2>ؾmp4mHf+.|YT=m>ׇq'#ўbG&fAUKMS8C`{B@5Iyt*{30W/Om={o89 k[=~n65–!2ڳpSK3)= .Mwe9f[%?@^0 [魂oJD$2i'D2Y"ϵ _爓*¿ |}ZP*q'qu*iqIrZqHV{JhSh, gX0/!0i3|lk^ݰnoGo9wqh"Qa* O00A!3^(%5bX9$JJCHhhY_PXzMʀ(b#$ܣHj?|ov oRhG6 -O$ ])KAcLJ[{:fAW8q{rӦM֛7o?6ï߲a|'_x#-v jeyna+s(~V;5C! Oy2|J"Nl׷EeVnvcPra8P{я\qʚrSIIuԯrBwU NL/Rho"NZ/N qp2Jp)x{vTͭ篿Vc~#n˿?qMpZ=8ql;0ry\؋/]0GA0ptٳ->,F!N !ЍނT'Sˆ* m/ݛS*鷩c}i٩‰~O{XD>f%hF0O5Qige c٢0ڠg?@T׿Gn9Y4ͭf&QH&Y'N2V3BaJ*Fq=G!F(qõl'>]25Tˍy Ff]L'&d0`%g/Bǀ1C{s`ZHUy+TUc5jaQx00)0Jb[,ġ1rݏ~i) zf=XFf=y"%h- r\|ڠ6l۷7nݸ_|9mנa_-x!s@ə1un@F͟_ opBZ :Ɲ(LqLNЏqR3#aXp D zE,vQ!ʦԘЀsdEӆz |F.|] FE mP_N1$ ZUAjHL13bgUXbwRV} ,)z}[#Vz3Րz=GkD]?)~ t! ~uY.QUo4!}U!"dTװ-: tj,8{jAJz<,]`o"4ͩkU_){%MS2!x$èC8-MW#Ӣ@dޡ=e(  ⰾ,ԇ_ d9@$'Yzkhԑ :~zkgggqT-/<)5Y(&PCrZ2Ro;i}G}cjջKeS;!Skyj3#X^_X\R 0n8&iZ z@\\VV+=M8l0P8岀AT{uuTh+ }kvh4Ҁy:`8suŰ Vtg9Wh<4̵+*]0*,slϾ=qVtŕ9Jta<a }7&{JUT4駦ꝷ&T8TG?>I'OYsY[&E2D:&3dJXļop`6u aS&a,j*| 8I6U!"V`fQ h R]6!l k!Ck3' -b ޭˆ:9l9S7,ZCzuZYש㻴>C#rXey#bp:R̦A zR,7h4]sپN @T?wmJ-NIhn`|jAB%_9]n̤7Yg4Y6Š?3TMQ_O-(NaFϊ0z-uhۦ#{g\mv ]}|m7㋛ͩJ^b Ǐw%!}-2p5I_I GwKKe'VKujk251z6wjˬyϏ̎jՃF_:|p : ^w޽0ezϭmk7?ttvg/q Քuy;;g;o0vR5+&DfcdNѮOw:y܋I8__πIF:MgKh?Z'*~*)ss'Ȼo~Uޫi9K)p7KF_R| NxxZ;J5zFuJq_  N  CP65P=z},V R c'>f͚?J>tT% `?e"i4x&Eān|`H/ބv50 3Scȏ=V' IDv^b7 ETe 5F#iSrE ! (HZ 9Fե5 ݍjNbQMӏ,R4(J&,`)K29)'3L7XKH?SJ D81a_H570 7 ]N5`h ttN+bۼRCU̓)U1M$Emk*|5 30qZ 'd= ]X J6UD]q5<DjeM$FČbd>IXH46R /]R<$"ǩ8.KUsۦzv1ȃ .9xon~ok%LX}Kn!͕6Wԗz(k   -qq|yFFP}"f/{0؟h$܂Hc\O>70,Viw!"'%bҗx=14ԠЫ;X\ AoͨGgk 2g9Q# g_]a0QZ.Mm48#jQV.t2 h\[ QG-bd tgЎ@>TGGǿwފĄ\Fn_9ZA(_GxX,Spш:{zI]Qo'MN#P^ u['^CD NjM!%yq$aDƦv.vF|BS]J @ RL:@U9G%_rY:X—tbFdr+{bc-.,[_Z8 +0Z-6Țoid4/dXo ǃDgAd7o|"_ѿZٶgEQ=I-T #^i6 Dzn2HT"{S+xZ)vm[}%àK ?鉯 }‘QAU!PJ7ϨfL"0wѤbf$pcB!O~nTPهEH˜yqӢfCi}jb:z$͆C]ƃٴ1)ܙXDطV$'| -%L\7_'f)s *|YsXUaAV!S0ȚsΉp;Pwc3=צ Mԓ{j^'k =[u*^)KhېжtV*GǠ g9g)c!,T%03s&6|hb;g\ Wo2l3)BRFz*C7Y ї8(^΅eȁ6FŋЈۗz1gU7qDAZq%mv8\ᜋC3ZTj׵.}7~u'M#*I 'fz/k& !HJ"DB]hgF&C!nFN)#gvPtg[>LkkWR){7wH,tP办9*Hh0J|H z1v,.ѽ?;Hi p@Gơβ ۞u؅%Bǵ=S3[-Iֲ䓅GpK3PFo1?R V*c[Q nz)C4-}n4߭,Z竖ڒ}ۡ>?=V}3z>DCɄX 8nZDG C<%L%P}%YFԛdr*=~ƭ(_@u,J3 iP)Lhnjwͽ|,hu'Œ5 #]K7pN1@;&LKLܒMFq b5z(zaE`уsr&oCl *Ǭ(>w:{'jКћ|!w2hV ,l;ƥ֑6i)4ϥ7b+ו-0/PFl Z%AՀ_Rѻyx 0"}f>y :}B!U9ԏ0:'+3GAKhiii}}}rbaBDRQg`|F]@ =EFqTjX|V7xY=9h.tST}9**ξ*d4l|`z>&'pv6'Q0P*12=1hKJQ.nk"c#.*L??0;]źF@8@?=hBTS&8M9aWU^qPn<,U@ &ebFKN KXҢocJ2^.6ibvh*ƼlJ1 r>]q`;IP:hRs 9Ir%}ז=,`K t,P`RxԊ9*~ H]T$,N4LBz)pf!Y9{$V,TKf8e3v65-b:B=ٌn !mr6ePeR P]ҰE*SӤIg&Jd X@3H y4밊O8›R 9^*k>3MMMz`` ^ `yl_٩wptzVuѵs/=Qދ q!cIƲU\?fybm M#Lssam׫ٮ~M{GG:;]Ąc϶ddZTD? d728Fe8 2:0G8+&EY$@_OU"3ATWITHуp/nlIO?Cy6Y%S<{t`m)Tru%X}VhoKQPjkIiNP"TLfаiai sR=FwSfI1 kTL$LCH&#De06/֮8qH]0ǐpT D*;Hv cdzG۶BaT /S^[Myq;&t갴+Fexat<ß9WP3zO7Fz60zsοNj/HʣpN_HVfau-#$H-zW3JcBTL]F%}K+m%Lhzcf8fΎ!1ΛX) u9nI,nij\EbyFѡt^2 OrT8FEaH3>]f8]Bz{dHxRYϹx*33x;'b2V'F_+Yk.\d ėW7 BY׌9&--\ ݷTQo$s ƮJkTcB /W7=g(d4S1'@ " ,MM+ig1Vuq3 0.f tr$"4<DdhU~}!j oʭէ3p-xo}z ni`iɉ{D>,-c=F:?8sAX^a (ur`P^ht2:qNYB˧s!1p"E*98:(X!>FS2ܕ Ǝ8h40,;1'$T[@%³"x\Q\ #BȖ53\~f&!g2GIH3A#U,]zP5~_FOID|'=M,j:Rw]Y.od7RӸPl$;ʴڄrgJeUPrF%_qSHYptunO7BFeLWdCR6eVI% e:I߾:lgrX [r, FEɌ3&2kҫMLk4)#}2\uwOuWUzc:ZD&B WgJA״Z^ z_MLL$ 8Midڠv0 6ȺXdhBj%}$#[VȎnH<` $k2!Z0W̩3A$ý$9$^Ə b<_r, 071zKL2QjlbX` Agc K?h{yx̱(D2heFИ4[j (ֵ6 rDNk^,ShD2:zp#LZ!EVm*M 2fay!2<|%d=-6fٸFl92ezp:*MOI+uԺX9.ϠZ3.Ld?mhP(n-۩sd>(qtT +$W7zI3.Q[@bcdrS>i&z8lRe ,Ӊyj0`X*됈4|]I)Dn^Qn"K=k35'سkk@hsA;@ͼ\IlD軔 DjM1ޡ9ޡQ%̚DҊaAq:Vf9E-}؉ʨѨ~w%sR+4|!9!pI6QYtIMu{?;5h.F!5Wލnm:٫%9&[-sw /A֤|: {lE1.qt>l!TQ4[m1'4)9\M6dNHEI_gVG(4=EFfO^mHW,XRanlaJQ'D2*ݰ8@o+R bPX.P ]r:] =R8uJ#kڨ0z9RPa{8/}4q^Z@ ?9i$Q22Ѥz@īf]gOwYC)FJO8O@M3jśLfH p1ܬ,HLўltfIc2 i2Z]#'1ʓ|rdMFˤe74sE4UҖz0oW{ވbN6C+j\}.nmyN )6Նx&)Ɇy ҄(ַ ;CR)_.R*{;߰]n Qo·-S0XTz<ãС2zY#']. 2+b3FyH6z[ +=i\[6A27'ީeP|<5X^02.ΆƤj 4Q]s}hRʼh6FLII[+X\Bm<݊[ѥ K>Qnh2mYBzc4K4-z#Uv8591sxq$f,Lq_ueܴlna9yP8 x3u{@(J9nPr͍Q| qQ7Dޣo\K+Ө82fz/[mќ֘@kNb$&E* *'c{ck_i(Ɔ.pKSa90H,|,q1YX.,RJ QZLMM֏u<f 3oN( `1oLѳ UWd3srpM*;/ǂT.c'Z9ҨDJ~> _syQ m[\ZjLksjpFFf̸G@_FN^0E1ĺa5,PB0%mIi= T# ݶH4ZRq*͋LZ? 'fT4Qxn4#Fz1b%FQ!EjAdC5:4ɧLaZVx+]BOsc57[4c22kOҸZb°k<.f4Xr9Ƣqզ娶ifrfZ|[}d3 g^3qD/sr7X{xFg:I[&IJқ3NݨE% ^Yɑ6yn WH$(ۃK}2#9fh_c0•}bUa*&H߲ф)1=/Be{\T0/iLpȨctOEUGS*V#ETe8N$gN]<)D2"S\&AUXmR*cDx G>] ݏT()I/nyOڷe *>5,]YnS`t|H,~|j#@T4 c]I"Gi՘^&Kf HH2*% 0$ވ%Yd1Ҟ0ҁ 5E10Uo^CfmI@Qamh߳)2'1sM0v\cToqq{^W[j5T*]/M{CG3,9zQ6(XޤOϭr2RA,9,ڦy4LJyk"=Ud<I<=/G',ژ:ōju9;*i[3 ?/-3B2:\:׍aw7׬H!c_)P62Ɣ 6&^qL#a 2,u4V՚UŶ`җraؠ{%ؠNבw% 4Κ$NƲ6#rˆřCUQd6=>X&23X\C@# XF-}ެJ1Y-MO+E!Hs`Vun^[ZQdK&(V Z0?Xa +53p4rA ߓx?-z]0?s5 #g1}V :Q"ַ6<.CC^Sf!jB!ULR<_bT,EahZ!ϬC R(;gP^ ?56VgӼ.%;ԡ@t(Z Z:YOm>"7) φ7$9:&͒=ڪ$2Aܰ'*JіlL"иÕz qv+IhSǖ)PCGiJ%1IUhM:D ΘD 'mj5? {V *84gFyJ1(I^s&FsrBU-!{J z +[. Z<пmGo;V}6+la.s[/X.3kx3;\jò=?Cf9X#'M]1&Nr2)^`h'3ER3+W&3(FL'DE{)y]ØVAN!L.])4 %bScq-՗A> F"K{7MU#`(8#]D*ft!y9a?)4N=6&?^eel \8wmvdɭl az:7 qK.D"r0;Sg#qS%Lrvj[?@Vۼ(۬G%{<ܻC~_lMM$`S- uaUWI+ 5O_ˢX^6wpRV}Ƣ.0rHB [ˤɬ]>9:B-J~HPL;(7\:ZLR$6kt)ǯ3*l`%4MV&Z =Ö͚́ O-%t:$9fyREkcϊcL[+'z1RШgd$oJzdY&MРO0B^a^,mSp_(} 3@S6cR4.;+ODHSKxN> }z'0qǐhC?kC[ꝋ7W;S f!t*`/3˄|SSTrSJl[Z&i´ #>) $20( U ܼX"vgd͒0LNknJ¥Ih2,tgCmUү29$gj$HCcU- yS2s'?=3^BgZqCw ow _OK!'"1Ko¶Vs >a]If y(Vg!j0moW!ܼ֖)ԩǓ5Wt?Qm/&6c K|6YѨO'&kcV:yМAb=VBq09xҨTWE r81+GCe.؈ HCj{Sox"|Wz${` PPdؐZ 0x6"7֥~XOSQF.-rNr XH &ݕe𼨤VFtg| X <$-nM˧Y"UԐTp2֝Qx DfOPͤ$9W=c~ -$]AϚ,Yݘ4ۭic7h,x!{>Tj|ɐf"zճP|:[d Ͱ`$ERoF@ M8E 9DV jjd4hhe./t!c܋K hj,O=VIbӟ}\c^b&0kA5Y挽c#y;z_Xļon*FZ&\T[d蘈Ag%_324r 5:pc] eV=pdGG}!2x ?פ_ٰdL VS4ޕj+TU_A:&ʤ ]ح{ 䉐v NЍ}knW7x~]ǎޘU-];26"7BFK06$>3F.9_ThV&]J)ݛP֫RgB"c{w=22c>W ܲ8~&@lYך57&4>c7hԔr|cNB#{pIoкo2D"-tLtVΞ9^>wqv̒.@ƨtgdQN PV5fmOAv{ѣ uO= 6@g%6k @Ҽ3Js ;VFl,^Ӻ]QΣOĊ bܚLOUc֓R 8#ș|aЧLBf4G CNM`HFTVx .˪kGSzhdr P'DFOz;F cSUvV{F7;;;o{.$2U5MؤGhQxHXUys)I,e|~$PlF.!K; f[3 Ӫߟԧ30^xBV'ٿ}8AƳ*}?uR&˨j_e{a׮]sO"/h5uŢ_j G~ƨ'I)aKu k9“1˔g_:%x<ӒGC&lӅ^w5NVY mi4RkՙR9nުuf1^>f9+LlS,51$*FM轥.J3V=L8|.*`/jMH\a[ֶmrV'!vvgMV@uMpCۺLc/"_y3g~ag#%ެ&{ 6'%ac)g֦uAY}0[gގt#f~uPxH3Kѿ=ryUK%X_x aJrhJq3ܭT׵K+NKܙC#9cM].f(>7&qՓEH:WS'">B{}cWw]CMۃ e|]oѫ>buL-?)@y̨ΊS fdguZe r%k"0J˚IQR(oi''18DN>ӡe,[5(@5MHf6_=ujgn`S6^CSsmҒ׏~>K :f;0UJJ2PX=em خ<'Q_cʑB&L2Py8DN58 E}BŰb h,pQH-S9y sTIK؝ޭ{JW ٵ$%Vlz@2cbIC HQX;{وoRD#Yj|>%(/Uhu@9d(ujd-2lɞI6>I,v)OlFgt/=>0SqubX2fh2gwqg:W) ?wlW`h`k֮, __V`uӼZdnnGwS I4yв8q»[s@v#؃2qҾI 9Fx8=yx@Y㿹vm) ___˚L Te*+;IQ13dCa0>%zEh1):4nj`Z̬(4kvpm j#yA':4??mشͻgJ4uWW|h)O(uݏؘ2h2ST*hth\8WIȕ+f(Fs7`^kU}ci(=p2Ȩ=zߥַ5U6v@N$m'j bҟ9sY:G$L˗Dk(UVZW{ofQi?ՇTHC Ec]'2]6)xB,ݏ*"<H.ylzį6F+@H#IL > *O & Qtjr~82QV|w`p6+9|'[?]o|#P6-"2i1d8JSq'wƕQ|4o{OqIjn};,܃噋Ԓ/b4W.DxlxǐU1xrk, D>Ou-[J򕯄&@cmi(U+8/,f,/ȝ|w o7B>3?R"< ~X7/}Ke@ey 5yU00=8~`alWS/|ܯ]!G| O0:=yݶ ߞUZ߃_ΠAO~̗-Ґ5>D&S ara26L\1#c(Ī54[Oa Twn?O.E1qer&WwR MX7zBݻw |N\3ŸwNM\Вo%FںukU&,>zҵɻ=}c##{~X\n3~})9F?}DWOwv22LL\7h{{{ׇ&1VWok{LJ_u?b7a {>kllLoyz`hho]0R6&?555e/_tj Rư7+|A">@eJ|}ٟ_~OUPWW*;@*_e(_|}ԮSp30`IENDB`schismtracker-20250313/icons/schism-icon-22.png000066400000000000000000000023531476471630300211110ustar00rootroot00000000000000PNG  IHDRĴl;IDATx͕Yh\e7w;Y'MYMVڴu6Z)7Ҋ+J\HEEDXш-Dmv62$狂J x<GJs>jkmZ~齵IݝG .i椯:3% 6׳w_gl!4o*+4;w$ZUHBUB:Ꜧc%UzDGowQW[Ųڨ,Đ~#ܩs6rckvge-T06nz J຿%քxM'k46jes} fUSdho>$J3pjvzLM<:{xuse GF)L2Y3 3Jsb"͢t\Bga8C)wFQZna~GF0u~ea9U,[EX]}#@JNebLC'@ (;> 0EE-Ev#gF <ŵK-`-R`2M;. fؾGr(C"k;,ɑq|"`M$R) PBf|f|Ѭͬ*v@H˘)esRc/YB-ZQ\*CBbjK304)=YSTbaO fR4̮A55 Ps P }h! aNRw}"ĉ$QG Vp1K51}\u\n`l2npb±"uPx1*כ8mصwz,:!@jw}q z@chmɑCw}NH:˞J3~o !x839)b5t,h")R3 &vW7S#GBhd5Khs챣O?zF~g3{v8קue4~;=9g?卟3Uu^4XIENDB`schismtracker-20250313/icons/schism-icon-24.png000066400000000000000000000027101476471630300211100ustar00rootroot00000000000000PNG  IHDRw=IDATxՕYlgۃxc;q8!m-hQUA(M$^THi⢊*%BTTTMT`+ YMHx>,4R/zB)u'|4<ӧO__$\0}'.{:om/8Z+Bڡ  6?j* n' ӅG> ԛ_;X<{GЎ}<=<j#um<`/C}mA("!h z~<^-Y1"]zګ(X|j@ol SKo{{:)JsÏIs|_$a9p['N& :*W4.TKwk $TW_?ʵ5n.4a禎F&˗_91+\>G+Ef#\M'HOŹlnc`\ȢC|R,[z|/$njefmW~&8yc3Wy,lɧhǻ]۷pbZ6;;d1=R40b!MSS\\*vk8bn.< @Ogs_!ɡjGS04M@9UBxp"9Z!хe ݛ7x^BԒ1_x (<.lPΖn V+=V$*hDmhj6tGӥc6+e> QQ0uUWR%Q(%/_Ʃ(l`}! R6R94cJ!MedY Yj[D<؊}v˲%UR @j"$TW$5*2 .").^b}ZG%JQv!ɔVt$h.oaeQR Rtg>9+<:VqXu&.`vP w[$Y %x}n\OKI"e7]YS#g =QR:+R]8(( pcr5/mi5`zup;N#Rj_l5ah8!]&b5Y<8mQs s/т_fnoRv+EGAJl)A@s ȡͦF]:?c~Xy%J#d'Sy=Z{c"&F^:gRѴ\w? ̻sgO|y;ϗz{{7#rλqIENDB`schismtracker-20250313/icons/schism-icon-32.png000066400000000000000000000044401476471630300211110ustar00rootroot00000000000000PNG  IHDR szzIDATxo\g?sx[1vi QU*TJHҪjJM*?PnRUh [ cǎd6ϙ99k/@RTQ?=ϫ{!/';ڲk9o?EUA⡻;? s=u^ƅ5j9/`qzhǞݿ߸;9*K3(0޼鱔yoۂg4̌#LFRmS֑dc8^P8 rR@C72>wЄ9x*~Ǟif~jՍ.i~ػcl~q³?{{'I#Rl5lY[ L8;8V 55dzק||lv&TEJ-}-&ug'DdYѰ[Y>_`=k |hK7m $u)q7jltwn!YeY)I&cSidI"B|磵u^;rchQ,[ ᅷɼr뇀~*={w?=}B 65I.Թ{vl^P)jԚ6 }*6" 3㱤.v<ȃL>O~|[hܨϩL2=2zFuA*+k9w"lQlncltl.Pj# AoDTne9U_yO2mVHnt;x䛷sE6k,R۲8yjVz\(YD:Ww$El6ahWO%`~{>)Ji$(`~Tg&PeRA'1b(i"5Z==!uCAa`:l AAvDŽ6/^L'f9wqO(qQDT$b]n2+jhb* Cbz($Ӳt;=KŁ"!7O'.n!EQjM^?z nC&f @P]}MQy 2ˆMU47Ϧ3iKO٬̰8;x A JUO+,l5heA {=3@2fv&YAT$EwuQ4v ȲD"'Cn]J&22ޠ+T6TzLvBCv ٿ0c(zP8$!,EB^EDAInup}rIctVBNGrC $YRQ߹Vqv[x_F6_{'Nu^:~lqz^ïD:VwIENDB`schismtracker-20250313/icons/schism-icon-36.png000066400000000000000000000057041476471630300211210ustar00rootroot00000000000000PNG  IHDR$$ IDATXil\y;sg .%,Z,VG^$oEQ7TN.:M4vtq[a)QJ%Zh}{ܙƨ&H8폾ߋ|߁ǏGz=-ҹJJcccoHn)~>1 8tza=m=OKKf wȮ?}ϿvO8sl٩swSy[u|f"QSkEGflJ*){N=>mWSIv[wݷDR3b6m ΕyvtG1fYI!BXK!A_>n#\]T*Rm.vv L60 \tg%Q*ΩQiMΔ^[k s_u ȗlv,!?b Z"چAQ`QoKR(IijeR}f݇7Lbc}Ro֟)G|鋿nf[ij9Wʌ 1w;ܘ]cݱ\aU'\4lf!?.R1~x(;S) ТzCb=w?uO%=piLC(V{05-~ٷ4r,kY!${ˈ@:R ϐI3^+/c ]炳edfKl„0w/Ch:~h!!E#ZoGή. (yqV6ɸ M&ohob٩ū$7҄~:Zd$n*)B.;ӫD!N1["7Ab5dz]%U>>ہAfS7gUT2x V% NZCnB*2մBCb馵R6f<,Nkz&FN?D]e߾1F&[$ontq}:N8졣ՄEU(nf+rVEk9||.Dz1M@tٹ6MsknVΝ}8Pܻ u]6yO^}k 1f֒(:. AXP,cP8v,&T"G:Ym zEVS$R9RhO=H™3>MFRTí)&[fSp؈FhJ@~\H&@( QS1שI 88<@%&qSJSRTnH\O8D3Zνl5Y5~0EjhR.Udѱ ;rDD*Gfܐ9v r 3GJ4[m mb5ca~MqgW^Ĉ# ځAKVf `N0VH2Ԛ ">z싢(MRx&@O'}}X,& f<n'yuh4дLޜRǧL59T&UL"0bD"ZT% D\~"Fj54ZAKLϭXٳmahkFkj45y륫 ^j<\&K>0j>,fBDR[mP(Zu'FV4iRdr ۪@o={` ( W祗O̭fl!M~QI0X&/.̸\R| cÆ%mgP?wvs!NREb3k :ˤ:N&n ҏ3LzXD "Rܜ_dG$1\_KKk3kPX(Ǵ5y?D]G[-%h6Sv/E*|c87=pr47rU%+To|UϪ~\,WV4UkSbjr3K͊TD"|T2D`˅ g']kk krURd? <_z8 ,BH$_\ IENDB`schismtracker-20250313/icons/schism-icon-48.png000066400000000000000000000101431476471630300211150ustar00rootroot00000000000000PNG  IHDR00W*IDAThZY[y=佼6\W5lY8ؖ%HR5;E:m,ESE45O- 4(P'Hⴖk[mZǚErH9w|?gb$-z)+\ xo9|]qv|s|S߹?_~9g&O=bf rzԩtW?cw`Or.ν&>ٹ8'v;v,ꫯ&>ģAOKz&ģQ/..t4 %tLkb[:=w#'Xa88ͥu,%0L F$p(wMOzvzO#[Nܛ7% xs+pNLGo%;zЅ V>=p7'v8aǂ_F^`KW~X0@O![c#8yn#5Fs-䶳Ձp4]zր  2LF] J$pyvQE>YDP&Н_$<+9?{t߽ ] $%+摙q߆r4ca8tDD "*-Mtx>\pj%+W_DѨ ߰c+݆ݢ1HTϟ] I<52zK7pi2ʪHnU֔F W ;35 bۤ6=9I渽ǁFe⻉1kH%k0~B$2:}p?3[7.\i iKbHesp: iPEf(^_b@=áCed+y8ewtW&#Op8/UeIrv:uFpc"2!EH0¥YT+dl$faC|C4 -X*u81L]t:6 F#_'LcI$۫fD4~MBZ:.-$^:t95Ԛ5X.*IRB!˫)kMP᲋Yآ=w47饥ٟ[ꋄT-:ciycnIUT&l^825Dh8X@AC"7LQl _6 U*ս,MpRUyO͜dgCқ9..#ݮ6U%M&Xlg ˁB)Hv {_[ÒLnhkE1C7 T;`5+$OL "@ƶ(y=*U\,TU*)m5(ʶ퍹<$;RqQ}@/Lq`(Xb[Rʬ-T0:0>We<gĵ]Ɩvq)A.@rJa*բT! ԕ2 y%JM8aq;/0\:m}XXR:CqmHѠF1m ]Nf)k=Rټ6M)3TŠ:V*uPJ!/ixEM`&=WWI`fSטDQ(VHU;6,6Dw:꼧='faf}!nTj+C58_.H(u{[U )G!\B(c,䙽dZ/CЩVhW8$̄/NyiwNUM w2\WܼKb v6bR.ToFMCV;3Cmpk Nc*2Nx ѩvSY$c9 uJM8#EP"h'bH+{BmeCXj lкTXÁSîϷ E]\V#A0NU$,!̕`Zn.b (TrZ\R.Ƭ 4TeD%~Qk[:& l̈ -с^m/_MdFnƐ>Lz ?crG_AJ> -$j fḟ^˕#-Xy}nĈuqN5uL^l*=҇˷VphdpF?+rs&+lWMHPfX(Jj0;&&i`*,X6ͬ݊e@p`j=څZ.W~ƿǛb_,E>P@&!0QisEc,uL#4aags:#.7 u:Y ώR)E$Fz"!p;a﵏GKr(!1ϾEܱ[iKA/wA;N]pٌ"@eG* h$XȱNgτ@Ԩej8A{݁ⅵdFsau)]-~Gɨ-DM]b)e3 a^(Il`I5vQ>$p8h$[cŎm%lj Y$@Цڠ-FG?MӢIZAuԖdYX}'##\.ȁlv'sws޼޼޼~/CV{C-Oɲm_ zaM^n,Н#BÎqj8ѱ?|K43F!P xc5} 8u쑁lVN|!zi:-B&1$iR1đix ـ񱡑blydg蹵j!7 gfh=t@hPZ"xך4ڷwSTr2՛%DƲd6*ͭnR F&E{{izq]jĀηxp.d2W0w`ɓ[J7V*u\'R˛d2s=zS0#>|ҥU =N{ʚQG#5Bfփ еeQ"%d==4M:B.bnip}5l|q>p}OI=N L3"j4h0/RxcFG'Gi|v x2Com$%mChor_{>g 夼q*{&E)PZk3KA 'j빦 -sPdknBu9 L<8Pv:VZ1BBc&ʄ淚hB A/:,oS1Vc+L AGڿ^edX<3`t I-p['ЌhBZjL@[a_^I|EO?p~ҷ|W >t'9=P;;F^ ǁ3@TێMō0.h)STrJJ$u2,eB!fAY|>0E/oi%?lf9{8WӧN;r`xGV)U"oJ#2ݜ)PjK&CF` QGȫĻ=Ac3.p@yxsjdr*e<9Q/]4wA /BњҀsQEtp7 S=𱣇 ےDΙ2yrn/u|Z(]٘%Q >RcFsGv=wLBi,> B˦ jRe \5E& ]TUh a'$$/ ::1l9Z4:i }jl׵E<2BdhBg~d{hQmke3Fo. ƑH_*r Tȹjk 6p:퀸L:~.$*yN&;%:`d2P΋E:ut-Y Ma: WIA oHt\i~J> tp~;%O&m FL0>NSgg\&''(v{X<-j~RםQ)hn$$Z$RiI JH~]c=fף~!fXJdV)u%〴e[r  NZE^PV Qr+U (à\+k $f fR]5pG'ͭbK2"dp96,pfZص#HKN("?\__v[Ԙɑ~"wK 49L]my GV~"9m2j`d@RP͚p^p2 @IIK40K@RT)b\OQmM YB׶Q`̊ږ^QX!SG"t-J*y7ņEP fѰX7P˙xRPyά0j!^/6Pnv81Wb4O1?=LNܙ= MQ0>}岎Gcf$W,C͕(&gGmN**YފY|6hNBs?)ҠN%y$lAv` o5AsQ9:9l+`0x0_ۍxsc,m&V'-Srb1FˤnZ+Qͼy ;ml$ "Hf7ӀD0R%Z9=9>:nSa]UAiw0d~f]H؀UrICUX)QW"KS8" T+B2 DߋllfY@W$SX9__ȫ@ׇj0%.5O YTCs[e$g./4Ta[6c0hhsBw_Yd"CP$Â1~7Rn\2TQ4LBpBkԶa2+g)ڑrER0*H3e;lv M`GFct)78XwNtRCHxʲY oCiSlc$8rD@_,jc: İ:Jfʣ*Y&v#HCgm.F vLFde+C= 7Jzϭi 5įar0fV."JzFn(5.`y<'`yK [tbSZ:i!7 8mFX|f . zKSaBPhT]^ҋSLG/0'͍  p¾>syRX"*Hʨ)\FӡLDAN8))Ja]:Đ6h\(sjat~s7HB5m[s2Ҭ <+`hH Ɋ*sicm5lk>xe2:@ &bpI$F9]*c-.&Iꍟ3!]\(YnDC ;|AZD2nk(BC"PHjwZM 99k XA>Wnv \ PdN/$[]xx#RIY6-`o xkK'HEh[N9[+KJEe2ntj"ŘlpUFj(\5گ||V/.@Bo2 )@L&hKE4'C0)VM;1.obWI?ۙ( uD!ֺjȊQ1R3YG2RʣDc+Hvv07_ 7H!/w% ƥ 2D#cP(\04R%pz.ׂ.W&P*lR90=smcgwŃCdEF\V"nh9>& 1`A9ݮlK#W1ѹy:5S<4r"g*JWZ$F43Ȳ(ּISob1{+&;I8`NdckNeXGm kb*,xQ "ԁ=a:E{n!gKm("նOj4v^0:/$Rܺ;2F\1` SxE$lH~7qF5 $[Lz  F3wTu%s@sXҞF"~Tsnt4z "nzd[7P%,|a!]a -fpe gP˨(-lt{Bi6xìCKb@R Wh;EMX.%QjD~J@ T_wMMód&ú$%ehpΥ 4 ,W[!ƈkT\OweQ1*F ͒-ch>ZX m32_a6#Y(6A:[_@b>>؍3 Q6`rWzͦ(zL(GTbW-,^@C 9QovX pbh;e Mtm6hVX/cOS*f,X~37#̟-c4&QhBp5NQF:># [QKbV (0,qBWmT1:"F32ꭴ*Pa$]{14Ҷtv8jD S*"N;hhdO-Wf1MUxcp?˷$V ҪbX/vkEڬ#k>o.˘VNHj' $qxD;]AChLnHsa3AQ Ю"M'ԍHM(͊V)!6z^P6 IG]l;քBց ݝ N(VSxOT*;Rb7i K/&+M`SA6CMjF)4`2/kKE<"vjwXqy㨲-*f{]%D UAʯq'DG\ Plsl?N>$ ~yy=t8Mo%IՑSqj4[ˀ6 ai{GhP:ʸEDbawr 7&&4J; C!fm]'D.o\hu1?s\?Er+`zLh8-lJu#0aͧo?ϳP |x3y*L=v7Cl>ˇ"x|5ms30@ k#/K=,b(dtƀ%`,z>䓥_]e)۶{KW/{̸g,  a1KD'Q"PȊ"E$@12g<=KwOKum]{ի5߹Uf7.{~|绷U}}w@<~påZu#$I?y`lS=[:wFU8ʕ*$=ߍf9;{{8O>Н{{Tk5z4=|O?~==&z^9^~}ztGh.dX%d\f]PglPBtj*1sJU%#byr,Zl>t[l[5;cɻ3[(ڈ ^ʎIV$gk ]Yu|uuTxyo6͔I)w| /t؁x3A{ *]C=/NOee8?vO_t#}n;uz&'v B QR5T.WA2Aul?qTkU{*~vǍȅ p4 9ZUH3S\"EɤR1_D" Wj![tA^UUnߞC?=7RFU%@Q@&/8(Ƭa邇" ?Z7@?u 455KD2 \ JbԵ=TFxqMd2 n+[wɿkVr'/у4< TwF{o|衽=8L$B1ՊZ~ /!jT-b^p;e0vεP$N+٥(% h |Sd]L!p)]=]/Ѿ=eeUDhkBe]LF#Um;wi|/ cbYZB7g#4:jP:sx!MHBO-Ph竑(M\44,dtj5 f53[+x1e\,ИKi6~vTHҴp~BAʠ5q:ǡ=yuR)ɲQU$z ,eϒIW)P*Y@'cT!Zgӂ?c1_4C+%h EzJܠTi*k%+kFӊeP@>䌼!hYc(NB<.XH;Dcںjz3 kBԚ )+ ]Y} Wmz_-K+T(+j^G a{~pф@+&qlJ)݁D 6i"z[JʄF,.߮{蚛[2I88 dno'բ6:ty#NZY](ha`ZcvkHL=p ^I>P æD G8]LtL# Lň2oZr&T+40d$a.]-7v^^'E_Eph cFØE 7B곑Gu5jY[je \|HFL6RT#X&dž(JrE+ djF̠ &fj.HYoUUi¿ R ^=sAVPFJ `f$B(DAU '3Ӣ fC4m캽柼tq!\9 ~M4H"+Da\k< rhXV ~Ο*Sz( -uP̩fȎb`,*B{cIaqpQW =zM[Lܙ *7yK$Лqht!ͼhI(HYLhT9#_9U̲>P&Q4iC. -(c,ގ;JaBS>@Qs-[D@C;tA+R A|9|pKТĢbͪ,5o¥{VUXs43lyf աR_=Q.6&X'[Kj MA-ᣄN?:}]uup>/Ǘ6j!QyXs, s1n2g578JWX\pLe'(H/*陆Ceʹ(HJ:gZJ+jSBL*[\1m|eC-4mfG?z Sl1KÏj1an鹋2ώ B)wCqJla6+\R^t빬 P[S1f6>`܈@b8-(Z{*P1OiFӸe DWټ,̩d6zƦMb;5 G2 F?$ibRo=x'{z=6v7v^sNWr$Q\& 0)5YR[M@(xV" YDQYw۫/Ü%6;bR911XŎ2Hc0%Tz z/,v0#s6 \3 s%G;6[\mDBNFo%_fea`cPovZoK(YGTblB5[ 2`щT.%5xg+&ΖPfÃZ/7e٦mѥ {րà\)GZ=Yh>w/.gbl2O'\H&E| C"3(% t6z'LmCj26}f7۬<&mT[NSx4TLd(nsK}/ޙ6y`wT ;H XADWhZ1 ڌrQt~G?yhFPXO7/⽕JW^y%zw17=ر?X'Q ヽw ))p_ ftw_?Nt:tTaR(IQ񹉁q0RЁn11|ܟ|_|{O46y=k7;9i.{7Y˜wxW#G&>?GrIgti:0AO9벹.RD8o{N׳C}Gә6\^I:1sl;>K:f9sqJ}JS?6 x=<S&qN%zuhd^Ckz1:13O;9kjux%0[#OYzbl Egi7/JŗoJU 6g7n1ŠG-7U Ku*ۿ{_;?xLf>PL'9mVkq$4M(YNM9kk[tcCnXRPR~Qe^}#[Lu/=OSfq qC4E}mRv{TvCIa:>4FWT:Kܤ./!/ ک5O^OE>!fKCwo'GcJ_$A=>:Kt좎Qe㓇Db~H,IFV"ڻU7bqҪU45G aUO-L_|Zi 5oF86WO>o0B`{;F{STߠIݼj`Nuy J%2A:&*i`aQ0sc؁4UU]lH64i{y XXX|r'Ϟ=vhSJc8*hqzD$Ewtf&ttEO?A_9big~|:_fC=f8Ç.]gpB?!Q(F(YSL6ure)j iRŃfG[}@ws6V`Vh`s3>>Ĕh I++l%&R4KXnxMb:x'^mH( v(";JB4}gt?>҆d:`bhh ˟{b2w1\tQ t " TTa3HҐ`jy蠅a蹭sbMh5Q:͒; foZW64JdaC_B+N=>O0hrSɐ },0k'7/Y@ 3:nnP(Lx2yQO U "6SX/M5{Mkee]b*xvW]4y?پ94$24A ô[ ' JIHWon68e`L`s=ЖGh<uH,VHeNJDeMƂz-tmtS~YU2z怩с'Nɿ_|d2u ŀ\UtZzJ˯_.z""y@;^x*M/yadv $Z3;yp\:' Uբ&tuK\N:φBP!-ؐӺJm!̝>ɏS(uDLF T_^A@i?DÐ-q :*"Pw'.N];2@~TViTonyhqn(k(x &*b Q652@jD `hB^ZsC6Seqp0S0Nj>y "",9,fSŊl3THDbw@ *bDV5UB8HMTIvbH(H++﹭g2h,l^,? 3"kuoCe@ lQn58c8ڵ*-X.C.]P^N+.2uO`BasCxuH* {+@aƈ:eiPΒ%XnC핀 OH!47r@U: E (I4BxfRN%ג>}QDǃz,7+=j $1JlB]f @.P)J+ 0V Uj"qS$b28;cICj$,YLͫ{jB\|8O@O-Llo\d,"z`t?;jxiDQ$ɕISŎZLx1%hAC>1b@OP|%u30,L8w#)HHTvm0겡8Ţ``-Hf5ucmP7kv!uQ*P+bw, H5<Y"!wG|A "=EI?:eVDq: \G253Ѓ Z+'fdq&8jq 25DMUF Ԁ}Z{ƻ:3a_/ dE : 1`V&mF+ڋ lcEn0 1FTבÁRbI舥0T3h@ipl65,  N"*&6 ty>ceI!(jXtb@ka FZb?~tf P2f#CC}82t:U߳LN:)|ZJ RVx`m&nr,@PGPKP^hU$jޥR@z#IaM$̗UQ)c( uLarÄRP݆Upt?{KV-P^0$zK6 5eҼA!e˿!A!Z f)u0 <9!i|~ D;0sDcssq)+s lԵS'8,g4eK\Mjܶbܰg$*J YԀNM73Ӿ$p=T=8($b `"= pI5$sda[kӅο /u"{V򜐱o@Rn=&*f`S{q>qYC{OuX CL0x 56gTj7NB'46'Ta AK!m& TO0fKR9' G:(Z+Ηe Ņ:j(&%B0$~d*+ P+;XU-ak Բ;x%O"ֱVbx߃@=/+_c) c bn{8\J`a*SHZ͈f/f v$D 긎HisG^,fQܐ漨r|GQ&:PӫWŹl0/lklʆr@d4In0_כ$?n[Z.*[hO>S![V lj1n h8'+B5 B!utd1!yA[#;)[m^hL*9ٯiꀪB޹RI EMķ!Iռ*E+tuh7ef49+o얖^DZ4){!mW]F_nV|E ?gE'XŊj(Tu`?j`1$w*`聎[$=+!ys_@.WPX\ PygOTa~`I\CB柍gT[N4>peiٴN4m 4kjFN4ɜݙ\A6 UtҵϪk"TT2)hy0ԧi`C>VH$m AE;ˬx #?t(iHH/#2{ylcq'<$%U~j)igTe+qG2MF>39 ZɫhkıL:P|#gGSNR:^h뀇NRg \%/[u@UN JS\e1=m %bDUj2k үnLIC$K:$0&6z@˨{iN,% :; &DX,U}X\Z&0֏LI絕n?cq13aH-bFdɓsPN<7ý^I oă$nQBird Ro{|9g3_Ά0=`,RW7z #C*aq8%4 +W,-2{BSۧZNY  0W9H^:$^N<]]+XӡgzN)3b~vBb?\J8U,aK[s(a3E !(JVCF%ʊD]je_,H@/Vq )bDy|w,ӋLTЇ + ƣ JmB.& 02 sUD;xf(LAwbG@DL',/{hkd( ԍK랖cU8O( j'q;ѣXdՋjŦE w+sQ7le|g,e;xN:yJVqqNDxډ@[P M/VATzѹfgUh= TkOK9Z;۫c}]K+[EyzÐz sb@$dx7yQ*P8u4MvhN(keuEb;Ϩ0ät z+РiZB^67 RJbB\')gl+$nS#}ꋵ"uA0G ebw(rӂ55&EYA)K'ſ$VMт㳢ZMi/4̽IvFIrc+aԼ!- ٱ`A_ (FйVa|ҹu;p"Lkˀ O=ƼӓVGdzKHʲOJ&\cm6߇n3A**oKʗ.46Wѐ(8s'qIa׊M9,% >`TZj"CJЕycQ7,qVU C C7]rpaap&z̃QFN~43hKvp0CKCZt៙UD֙P.g9k㺁Gف4è곘`'#~064KN L= 9k ynsm C76PDdV6fOC亀/P ((_DtNoRB \?KfYTZjdeOk yZ4cb ]2 xp6SmNm&ǭnUǬmTXG/2p"u\˪Z#+NsF~ <*Ag,D|q.M{|,Xb8("ߗ~AX-lpܤ-bZIeݝ}#vD(4kwuH[odu@Uȇjf* jjY]mhV|!@~ R8SpO$;?{Txf8A n-{kffϖ=hhʳ@UQTVa B8:L|7[VJ <#>- 'LC087FThq{J'nʉӬ)08hanmˋ]M D˧|J#, W' qo~Tdkt 7`z7ԣ}@ )nw(FSKc3/=GݬLkI9##ou@Uq}O#ˌ[ܚ˳{k㳋?G>QQW.|)*<\ȅ q*v>F?_] k)̊\ʦ{[MW屹}S_#%}ñ׮W9'?S~}1Q8J1<<^U&^tˤԙ3g>q@+0@{fffpww}ZY\WVXXWZ_j`pySSVHGIFDFGEGYlYcrnHHK@@B:9;424=HIP[lfwx~ufy:ESpwx~bbi]]dVUZ\f\m}vTTWQRTOOQIIJYnY[ff?=?@@BBCEA@CIYHXnb548*(**)+-,.//2>S=Sp\46<0C7Xtq}~~CO`TTTqwu{XVYWUYYX_dqhq}SRXJILHGIJLK`wb]fgCBD<;=868657J_JXjd==A77:002)(*%$%:8::8=FPJ]vgHJU98?0.3*(+*)+@V>Rj^35:&&( 9[7fdv666qwotz\[][Z\VUWevfdllPORPPRKJLP[OcvkGGKDDFCCE=<>DRCVj^88=88?==G;;F45;D`DHXT$#&&&()*,()+!!$#."?dBuITdqqqz__fWVYVUVhyjcjlMLNJHJIGJWeVevpFFI>=?;:<<;>NaNXhb87:/.0+),,*/5:;PoVJT\.-6#"'1H0Qs_~x05<sybemi{oafiONOOOQKKMWgV_miDCFEEGCCE==>OeNOZX65888:668/.01;0MjS7:A**111=01=((2 #2Q/`}}fw ";;;Υ˞ÎnuVW`LKOIHI\o]`kkDCF>=?<:Q>Sl]016!!!$#,/89EiJrLXguuuɣȥˤˡȖnv[_j`teX`aB@CDDFBAD@E@WoZCFJ77999<557.-/?W>L]W,+/,,.)),"!#%6#KnU}y5:A!!!}ƢʠəjsOQ[BAG<:=DLC[raBCG424/.0/.0476LiNGST&$'!! !##%&'(6R4\{shz%&(AAArwyr}ȟȎ}ktUYhM[SWj^98<77888:224281MiQ schismtracker-20250313/icons/schism-itf-icon-128.png000066400000000000000000000444211476471630300217620ustar00rootroot00000000000000PNG  IHDRtM=:sBIT|dtEXtSoftwarewww.inkscape.org< IDATxgdudUV7C HPE")ZR+zXFhq?l6VJDIAAL鞶]]>>~xYkhQ329RXݞ'݁?"POߞ8/_;P꫗Ba)e}?_~*!>Wӟ~H'Ο:O}*4 ~ert3Ǐ?85e0=6}si~ P| ~`|"N7N=]8#Z6\K4MMv_zWG|"~ӿs鋟O UK*;oGW ?z[s̶?w$FF>v__+|S㺦 y^|w>K8~h)%B5v|~(Bу?䡟|'M/P"ouKc܅:4)ϯ^65KG^BO [^͹ynׯr*`bʏ|"/^O<8}'k-(Fk9:+ O(;Vhkǝ%8!M%Hzp,Vf@I35GLcxgrV]\%Nb$R80>cz2š=_̈́aN8AH}wXXYͶx)׷(IptMq,7gXZm( i0Tpt8?"ĺ{wo~%Ɠs'~z[lU9q`/ ׸!seZފS/P̬1 a[ܝ_suk>MStMe0N@>afn?@Jt]cObZ귻a ?~ojl@7M]|9՟'LcO]sǏ82?k8 +JRs?o蛭gMXF\s0:4xpѢsH\}6έkD:%.3:Qy.ل"ϝHbH2A i2!rs2swjS#|3HٔK[ȀJ5$D!JC)~ۯ kZ " #E`k~\yi-_N N9 gO3=i*a#SIZFeef% {OkE(~[lP-- E(q ?) zIBkNdRDKTJTg@DD5Ħ)}w!RJ)4`kZ8NCݎj!g??{G?ӵ]j2:T}J mVR$S*8T+ݟ{p<6ay*% MUwrWFnwgmBiĀ;YO#/w(*0\.}~O ˅XTOV1r9z[k,ᣰXW Pw|F e؅_ )%P>m0:Qs{f wDzeྒ/3|Ͽ{)) gN͋'4[|NhIإje38nNuJN;;p`8Sãs.%FIcr2xrl SNDiT"!%vG}s1T\Ŗ1zO>v !4A e D !M_~mK^dw=Unr{?~c'! {?|{u{V-EZ^s}D0_\J:@lgRJd,`zzGE^}j0qZFͫ[&aj>q 7s?9sN(\3`P;1nxe;X{%vmj 9})'rǞ7&+gmkRR$rpfR@&4y !ÐװMF9qpjM{sa?7彻Yiq{6Po,5D rƙ98{[#m|"W!cܯ~\+L<]J\?}6 ;y) WK喗ng Bǟ/=菉zKzFULӠ\0]=OxokQ`X/zH)QL2>40BJ38'*Ӽ.I Aeb2ir<˝]E 8pme8:=j-'Ok>Z'[sÃqF\:3+a³S8O~:~0 ӎsxjZ->' b!ݗ`ʋ7y|?72j^aqb-繻R 46i͗q/[u1kYbh=r4v/>q:7QmF?r!c'_xAi"S3Wwϥ4P!=lJs_bM #΍&ܙ_(4`rd'|5{49$a= bqaUV-.߹K 3|'JJ`,4M% n_,`tP |dB?|ġ[]W]^E&)f̧n` M\? k V VJgXYҀo>V$MYj3MJG8yx(&s|nVkz;Or:sHK 9Qaq`եFK$RB40䏮]$N/_UUU~?~w_]cg^S?\m:h@0T)qb,ω?cmn;.aСjRAO#`Y8EtUe|xvxr zsϔ޿އ(Np\(P0NXuR`Pj;7nSH4ݒX,P*XT*E(wRG1R[OɽέaTseۥL |GB(O+O_<+|"EqޤuoUS7>̉S,.-{-*I,~ai?qYEۇ8q`2v-pkvF wGjL U|.-Qx{!i˜ T1M$ޝx#W;HLˠ` T1{.E`bl]&Rjp<>(|~cg~ǚ][]ivHrbrh{pU]V]4 &$)r:k hF1'Mq!LWڃCޚb㑦jhTehRW}4(cTbr$iS%S3Wdrt `R)1%cHA&e*%ˠR*5RE*%aȷ,CUL1_mܼJs=;56CS~r H^yAS,6mzN gVl4لq 62xd) ' GF)NcqNq|]! a49"૗<򻇧ͅ+3(oC;w|j`}_|cO?'3Gsz?v޿hm3su]٧ˬ5yՄN=CʌE ѷTU]o\x>+f_nH J!;uxaiʍg1__e~lR6 T aFwv4 WQlF{7.af&'޲ Er4] €0ݪ=诼wƶKC|j@{ܑ}jsyozNgKVLNLMe uw~,`f4_7Xoqcvz$ITGf"iܻc۔ &8Rbrlnf~JGqXk IUUTMŞ74A$5W98Pod1BR.7rQD^񨍁fhH$EJbߥ~??#k_{{uf4;=@/lvcÿ_ǿ2h B+(ZMNCѶ]L,ԡ)q̍9:"Qr}٣5]ŝ !Nx-4ϙ6#3;'Ѩ躆 ;iH$k'.(@Du#qXb;.i#8afV88>ȉ::=n,w{'5ݻw#5i`\JQJhBduu@!Q]zoE}{!*c8~hr]S +,.qz6eCsX( JJxv9^!d?O k*1A NA&,\U9CU[2oP(P)g>;==OzmV~Ã> x7%K|j@L +v(b+]i4Ux^[oi5 hZd5_Ǫ9ZNVܜO#aDm3DV( Un.vGЋ94dhAu!i)Q!IQ-`9la Y\fjr@4׽E(؎rv; nyoۛ;X)({v0)|~-+ji"EE:}cC`sd(ڠН0:'rh\&hm=aQqWαZz!z1:GW59 x60(<8&* ^׊C 0Rþa,5蚆4-lua1@862篳c{wT!Ҿ}~{vvCm6MӔe22X&J/n7dRݺ072^O|vx(7D4/=q5M%j/tu*c$R8ıdT V;]\?\g6Kf` 1=1DA xe"`͈{f4@t}EErt8o^Gӣg;x~\QBLsTJ%$eHo\/#Sg߽QEmc5 |g;A`ֵ۬m (!MS *ˆ0Yyܢ=`XSU\KqIᑷ A5t]E~f#e*2dmf^KG6>PLJRVXp=( 0|tc@x\XxY\.PFbcǙ_n2aO oPxxQJv@DA@,c"w%:oFܞ"] :"0M4) y(bݡk*0&8_, Ӕdf&֖h˿2EU,àP('u=:%8fz_-z1ڏƌo);455[sssJ lFk^zu1{߻G ^"@JУ3îVX),ZTz.gyiP*YhFcY?:.! fzv,fW6e7q}nf!~mH)c Q*E&'Fvm&듦{B@)M8ts Ob9$I~y{ ;VBUڲN`|wg?*C UQAJP-,[ Mw; S{@!a2m#҄ngϞ7jˎT6&~&  P+7o!^4eJ3(Q&A, P"1 cDNBXIF٩I<Ó]P+.T:"$e'/Ҟ&v;rVCz(Bxmc<0@u{ #*ȶxӠ\* a.vaY&9U!~FMU ՞X65Yv4rMz0AHci,gٜ=#X!dMP 9-Kzm;.[t:!Jf&zG~o^/'Kdj`OCs<긶si>,45biCn0%0DWlR"u #Oh( >>%By(^l(X 9,R"8.vq OD#l"8!L3Ot;픜ZFAnP+* iYmssuzn&}(<_{*? #Fª?žn]2n4ÈՎnj+X9r͝z^1,U!c~#GYʆX41yףbi0#ڵ)uu2)X&i4hCO!tԜg14UETUqnj6HM#u~.@/YXajth tMV)3T*0YR=ZYNBq7*m1LwXr|x;/#Հ&1$Iaж}ʆE!W:\?Ov]'´EqB>S(A2 ÈPoip#0>EhQ(,8NlVmi#$فO?uVJqJoۥ1s˫< u .oiv{Q${)+$J簸X7MFYiES*v,,fEriM2^Џf o{N PUi;J9}d/x,=Wzp薆ifzYSTVJܟ]"ެa)&bfKչ7@:>Bj=F1 $ rBͶM\*{0SZepS';$ܼ}zkdp휆eXiJ%F\ 7iЯ7ʔ3 D.*}.>e{7W;%:iP,X躆~QnB6f&`Yw % IѺIR0s[۬\حy]cl8Zp|*5Zj!&,+ >ch/BSgoA1L]gj _>B7rFB"IRla~K4P*Qvt- iOg8Q3n9?(vwE4( SZLř'j)C,SL#ezuDnI>l&fJK6T 6cüؙ͇ViR,}/IR8#)HM0gQE55>_M#׉MSz 6,Zp%E=ގ*XIz60r ihm`K'![VBXZYX{)IEWy8NAѪ!733X,P+Vd'4ht4{6Q?sx٢0}wűy!v*(, AX\_aQ_m~Mvb[{ݕjZZU <\%=6R4 ףӵq=oYIl*ed[|"zK{v"3W-PC#S*QUAkSo4q8o8}02O Z#e$PR3+4=}/灌nٿ[x,S_]^ cZRq 㹴V:[OTOnoىY>Y훗1#ԀM*3A=NMQA,Azr 6H54kV}ScIJceٲko"fƜi &A2PDL)#9cv\:]Q*:9$ŇIۜTr֧^3|39 Gz?U`溧v!ly\VpUJODvi0P.mA򨺂{K2@n;y!sQeB?J|UJҔe %d?b+a8G簲$E0)3h(s-G:RJ(qL$ _;p϶l+E;XK6a+Ss/A vVCE k<0E>#e78){>I5\̡~GXF6% fV@Jlöm\7s]y5 [2'G:~krtjT=Q  8ΖjJJ B PTA>2 IFaz2|T j4LJ9@^ԼWol ?vOZPnUQd6!5]+a@q\$*>섘C"Hj˹Ӈy VɎ1S Պ:ܺg|ߺfQ=3j@A7X@l{M(g ~9da%*0a")Y&pHٲÐ%' 9"`iM$N^VWxEAUMƲ SNtc 6~F8s]X`̬A.={p֒]҇&R9<Y˭Nu!vZP5gquhj@nXrn1llxk Q:ۥd -%ʦY깔W(T#+1TRyvi7D*520w?0VQۧ!4:Ceq#i1,̳ f2zka'=!:P2Mr9B\TԨv{aG5e4y1'Џb&GIqY^Y%诜 |"r}:SПRrax+z!,5l3WIM+~ZPȖaoMzޠ;=-u\Rѷkqs#w8L$ WUָ69B:@vpګn&;ѺAj;6,y#yfϞ)Ŷ]l<|oc,%dSV#7 fU E`u 3 ֺ$ vQdʩNrzdkRn7Aq6)%l> yN PiC9U% Wu\gݥ[6K@S>0Š"€e!E-Td,f nm=o)ݍ"Lpuf4(Lr$;9jwp]#C\ ܨgyT9gW(@#gϬ q\}8?ؑ-!RC~hMs~>PX#@)(2EI%jb9 jwP;ʿ\YL_iF\m z 5*zBh{t B;!^H03eXUg,md ˭< 땹6KEyuWtM%Ba}t)1B]Uhg?Ve*dx[FTxYnÈ5\VMU1273{-`RXU~0)n/p<\V kgqb#;|snm?ǶO'(P D.'xMriH;,ij{ #*\H*`l!48!f:e=ܫABx@B瞹ɇ7ԩ4l'%LY}"cPݪ$RÜe~@[Ө+U^K#MAF2k'كvLk~h_g8MSlay6Ƴ7=SX1 Q*w i4xh:ufC>H$ÃeMrhjG9811RytzB1Sʎ ;ݑDoZb:ws!g3| ٮ֭M&)jiYQ1(@7zWo. ]@ ĎeYD)yݹg83gH%m~gsw!.y/ E,,L8>}oOa4G_hya8N%m4EѤ4 Ev{뎰vC#u1hW+ؖ\@K^(J]&ʃyX v`& %el|8-RB V1I"veqW0M iNbwRaID" E{dY lgr)\Zyh*-Eo4[[ Eb I1f|Jw6hhD@oZOLc!BAC&0a%β_a^Ϝð_( 2t_0lPas] q3]x N B)Ct~ g]?~o~i[2mXvodzJ=Q| A f,2+A4U&XJ\a63)]"_^(kuWFEkV-T&Htt(@6ΉGxm[zBd2Aӧj>n;5GAq `:5\*6>r~h}YPzEo6Gٙpj MR8m?o+ s0ȱxQ1D,ˀ7D"f[fTd:BNw@s0Nx L#˥0?¹,fk8^Ptܾ,TtRm.؇s Ke7uP+_"|L$a!= h2u=l,`{;WÌI[XC,,w:eeSNbifX ;[bY+q饜֛oyqm^K0 Q <,`5lZѰtd; c_3K hde}Q誢T?g?Gog~U8>hBE;Λ`8q2p=H3"3L&dO5+:Yت4U˄W/^/9g·AI`_Ld iNO!ʴQo`Lӆ:O>,ݩ[8xְEa6;$gJƻ|< +ZEe;rip #[^{ɣǺ|8E`V'3,m.biJVO@ʘ$17McT:3!S4]-ZEEF?ѝ'NuB.Pҋ~f]X !ehjΛS2If&17=lG5|!yE%+*j'|3Q,<}˯~ p[87Wx9:q)YYK^RT}"|_䳩Q/_hq80@:8n0..a hƎV(WJVe{ot;m62"ڵkEz`"F.=t3id&0Fe?RPXA~/W>e6 (^ziShzT $r$SN!5`R(kRf8w?٣6@⋣7=@ti/ܧQ\ tt˳G8m%_V euw}닋GY @]zuS !a2E,"c&D<)јIB^[,(?[.B@FzcCf'+]+ww-ws72@hh=sONo e[Y* 66_~篽oG>h`-//?ZSFJwwaiw/pk֝{S(b ɇ˺ ? .=ϕI=)D"Δ} < hNyhuߟz.C/BqK$a\9\_TS[u3"b$r-i2}եj5Ю^izi":'~o'bX*wg]j.p8>YXXؒB>ʦQ^\&_x@"W2k 0wY(21NRZql#&SHzs%]Իr~"5@(aȮ{MIq}JLyg0D!@(")ȓ:n#:MngXNjZ Cَ1c%p,! A=Ѻ]cp묦e=nh7QhFB0 u2_ݧ,3h{IQ!vb,3 "yTYi K+?wlRLތsVɶFkvtg_|2I{vψwR]9] g9}IENDB`schismtracker-20250313/icons/schism-itf-icon-192.png000066400000000000000000000752451476471630300217730ustar00rootroot00000000000000PNG  IHDR`"-sBIT|dtEXtSoftwarewww.inkscape.org< IDATxw]}97:n8`0N#S\J*ږE JZ,iUmIlmVUk^jWДL(9`9ts~9t:af@ *w=tx PO4%/>uϺBQʓGZO?>~O<~ 0_ik'?FB[ғ}2 *U)%:z(+٧FG<IAqY@Ѳ:rxOlbY|7N!%H)iiL8ĭmԧ055VC?wv|ǚ;[X־[I.|c\)wrd6|t7j?օѹxa $<ϫJ)_zHplK_|mwoGGliry9._Ic.RJR0{:y^Od|>ԥ7iCw|>uE"k|sFx6<Ʃhj#oEގF0-RД}`A)LCvnqġcӌNa;."7B?5ƻS3:С t1[;ֵy= ~MGxRe^:uG* !D[ѓGi]az>*(5n:.hnslgoors$o2>$9{ڕ7y#IDs[M@xO:)!D{_wz#ϽVW];C7t ]CJe;&<~p'|ٮs؈&&Gx8ѭZgܻ<8;qp黲e382uژДX^AA$xw'jk؎'=lǡ.e7;zڰl6"؉ )%o5ϵmn~'ZjZ6;s)JU sa`7ƘNgq]](zG~qx.FKⓇ6N>@Ѡ3"XZ~uF/2Π)vwqQAkCwoux&a\9Œ ǙSyBG?ػu]W"/`:j]Eq\|t4pmx]-mmLbnѠ3H$ݷ{ldtjvu)%']TGvHT/~`ȓObY,#zsY(o}<|xy\ESԥcұU&2imC14e39>E&_ؿvᙏcXi,|̙3 z0Ζ_lnn?80[s! mMWh&yں/l4FFJ|4v!+N0ڕ;=}#e}IW(yKs}Gv4. 29FUBX$Ot|ydSsM@H)ٙ3gY! ޾_|Su|s蚆-Zslwp`{7ߣ*(W_m$o}Wl:=vn*ox9,FT }}8M&NMq>$B MShRv: 5m5a?x4 p_{vl{ OapZ'=R )g&ϼO慓uՙd2UWsdguE*KN|x&3YTUftyGC2ƁlnEY;9+d;z۾~Wǯ׾82{.7Jx=T!D[['7D.QQU&0jƻ.qs=4y]W}X$4r-+B8. zMVk6IGSv4M<)9{yCgKt4=CWJk M-(19H`hůۡ.ƞl͚2u,sTmUQ|_7$ٶ;zz7A[c&B^Ag;BDCu#{wo{c6ꏢ"0vlv7ƦxGN~hTch\DxvJeKE-R.m;\;y#H4ڛx.b & / ^D"Mf>h4+DᎢ3!t7ʉ~57/w?B "T n=(BH:IN&޺;q) ;G}2ʎVw-[q`{7 T"̮m{g5ێ^d胙% 9v@PO]2'I/2Czc{wA4VzPDZ}$b۶JḎ;G*fhkJؚSl .BO9:.e]1z,*I%y)tq` ,ZHfwcY`f9p(ŸS=u`fn\㝷._H׾HSZwQ\;_e|zrL~goirmJ$Yu}Ƨ1x3Ij,ˈَt=YUG?4^#3 : A.qD I+k8>f*XՕx^p&e/3Md˟5EqFڜ=}iԌ byZIǙY._Xuq*W}\o/zU!Ht֑{>?502Os_A74:89.~|H,#U(Trh4M%͓)Wq=!bEWT-P`llZ |ERbDAEk-%Bc)&_翓;X PR9Ɂ AiHe 6 q\ySko WV%# E~ݢOp]O"=bDf!XS(%1m4x0;7'd XCsF"`h|ڳ 圦\MD oh!Sּsy/^7O? HtzWWǗ?䱦m NҼ%:a ]#  eBA# )%àhuhkG 8'>?02=K\YJtן@ʨm>&Hփטq ]֏-/q=yxD-13,vJ"Q([f2|x?@Hئ0\ȩ/P|,Rz+.<.^FPQ !Ğ{vyGsC8_-Sf :XEQHgge>! FY!U4>m=1!96QPU8㨠pPKs|}=] A 2^Nv!KH$x47KY)uj*7闎jgu̠+(a~+¾lZ+BiKx_o?w;?'|LcBSR$+% #a,av!O:}IfRI$"f )bߣvO} _~pS|}r5EBAsxS)<P HH\c.[1ei/VLJxTm{%DC3t6\iРB͍u?_Nd?!y3"R%gP"$"$/)DǗ.z@S*=|pmeTT~RZ8m&wLQQO9ֺtInS)hh8Ƶ9& 6TIscr!C:_D];b6=^WBAoNCPeÞ۵k~O= [/cxvlBF()N FW]gR>4BMРRTU=th߮?O|qKOm,W,18:8 Ø 硨? #P05Xb;=즽'uhH9MsCbò2_EO2fmpeRJ&_4FbAl Sb@k\vf.g!_\<^ByFKT!4︯2 z`B!S;3~}xə4,pdC׈E~^k:[ _(QX[Lox${{A>L\a›[q3z7u-[Fq?sggR)K8=LϩLS KMsyg(RmϮ6J鱿YۙA?7휻9s5 U,f<{ի g!DîKgojm3. #q $&˘N>T- 2]W x_KXoΞ&>zbʓQ /ah*Jmm u\TM)Ⱦ#6h/]𯇙 5d<̅i2E0FH}ϕJ 1S(W,y&Y؅*zdy4~c3 @UG9eK$( M NNo /-(+xd1, Palaī'·̦~ɿ|e*$h߹\9"燶tlղ0]S([6oyaF ?q9=L&KPBzrldZѰ UU0LMS6K;Egs=Do$/êBog+~g/ߤjh*ض~#Ӄ2R(UTZ[ GC$N=,%lufo*)k^?_1ڳ%r/uwҘ8#0z2Z~ 8Mvhoi@tl\ԘIc2D22>?\6O6 $ES0زm̖]=}@ ,۷k׮]9(qwWNsO4'|9ߌY:lph/(r Zh4Mu~Xgl2tͧK㐈͞ms}xم UU6TY$ ^Brt=L]EUI*k:MO3tE04=l6GTYNG{g`$ JPUg:.m!>kxCo4MӠEBcv_ٟ󹧎Uqctk74E(Tmx$ȞNw@_gd@ ~XȞ>ben+/2>3OZEST4EjFM/«C>_qtUT*Mޱ PB(/k %G๋ko $1]en~\H$ji_mq\52? ;ښm~_ݿ#:02əKX%D\ץ>ns}{bL6|S' RL)}Gղ Ʀ;?riҿݽ9oIM0.#Bv1vue%WG6_IJlZSbv5=`Ezmy]1NޝD"Q5p8@4! /|l.OX¶]kLٴnaO_~t  nB__o |4)yξ⏼+OolnLjO'5AWKw*?{yƧV IDATKU7ʨ K:16I泾wvU PJ&fӔJU4MESA{kG0M KɟuLMT@PX- 01ko:XyxR-;Џܒ5gU!IcWJ**tBDj-GiTuaVKl[xAѠ| {V`0x] gv~# b3\@SU"mD4#{ln W(G(fsX<' J~hN c躊erT6RJ01?F)V$Z-GQU[HX݉bh|+7ǘY".*v]Qvo렧v/W8|ga(Q]JU a3tN#.c)A0Ʋn_R%4q47ﺎlbDfI[ T3D`Sۇlz[A!K~푁IN_YsOUK8`rqp?+SzpSfP k^|6m;KIBJ*r(U[smwLjٜ4tR2t76pxOF-h=Rrip4U1-Xy8GdQab~+e8nmE], 8D2NC2tzN0 |GL|HTu->ykK傹w]ڵ7ginf za۶m'JHl/k'_yxm]W롛ڛٽוy#\5˞aQyg`P @}S d!W\⺵|[FpE<''_;N>qn̷/A*~RI&;ox>>{a0RlHԂV<4U% Mc-Ϥ)+5noYaäGwonLUdYt\Db9%&g*h6媅85GeBx|nNRI( J2mk5-#m)RsqpF>kІ 0;[~i;áZ#`DA5N]遏O֯}Z$<|:C>_\:] QfG[YevinLq[7nhBڻϞhۮ) _8veקOc<f6_Cz¿řR<E!h$caH4f5#Wo065GPAUADC&)E_"J*g/ %ŋCdrȗloRz̤9~q\VmmKQCDCtz|l@T^H C'0'D!\JtrЛCiu>!+ol6 $iВD"g?_xp]cҧ9K[WuLhnN{쭈|na0[~=Ob*Xb:ͫ_cD⚚_afY( *<qԆ= O0=3S0)z|,CR^WOs?>/06&](p؝A]dyMGB@066JTZ*HeBr88? M]+@gKMH^T_پojcvfϲ\!8KK}h$F~ <";A;r)rstxI"K;Zm&Pq]>:R]SPe 75spGn(0B+EJ@p}zzHKD2KGj 1˓+kcÅ;7膆"ۣjQ*m]Ҙݳ+B0 MiY6 ,#hjR5.<[:gdgR[vcK_bRu#[09иPIS粒t1OKjSfEm~f񤇮iDA"O\`6]XTISquTq'=en.-R hB)f 7y3#s؎>0 uBj6E.m@\!wͷ?çB@$ JƗrH/d)˔P*Q+065O{LhgY0MK;>Y@3MY<7FT9W<ڲUk`j3$rI]Pb .ΰ iNA?z!T&B@qh4Z@c.] [GyVt(yA-q]Z)ÕcBJ\*%BMŌS\\rHDi*I4&X,)*TjA ȏdI$;UV <7Rf@OI@30kkiΦ]ePBtT'v?"U_/`^jY\&a  " S yˣdϪ*V*γ//Wk]{]%.&W.stj+HisAxr9vp?Apݺ= TkHJԦ_UU L x|)\)JT*WwFc{vk݊.)vYW$`񏕻/q=Y{ϓbm y>¡[i} wO3JCLLbE4Mr*熦ɕ4Ǔu]gJ>+j: bLPg^-Ǐ۫e}E03aZD=-bQ.U$-*2RJZ枡ƕv{۴nK:::~stt m`Jl#2io#/2m؀ğB=)r%+% 7/ X)`ob%0we`Ģ59|4'4ԘA:_`"@:_bٷ, CI%"hNK*KKLm[5Y~ QU%.,+6-BAg %JR!~b X{dU.H+gK8y+%ilr'BV-dݽDB&Wn1,4~CbG?me[wT풪%ԽsH3MG}=>6ErŒۢ8eBETfYZ **Ux]-F"DqXF$YzD&WoJZp3*PA5&Kj8jKji ` HkLy9l9uϓhjۄ*ߍ_}ٚ)}5ՍsJղ)B M$RQHUi=pҨ>^`&'S(.-TL[X-, &g :Wo2:Dbߵs r}ɪ+1 %R6#'EI`P(@]"RXta2qZ53CwzڴoLoFq^jooͱFw(ˎb㺨B,bgwuq>89b}t5ݣRcҹ_J;ZL#&!L{#Y7ŗyʛg(ˌ͓)(޲*3`U Lו8l&G8`[$>{$>NѤ%I˜BJ /8.FS+=:kP:w.r(nN͋)%˾ LT(3axLr/Uv[<'@5tC'UNY(T-Kw@U)K凳$wZaI䭡U[CXҶ$n283 9 3hL$ ؔLO%Z,er26VJ `CZ|HP\QewGiGs w2t-;yɮKsXX:c\U tLR!]=tG**ѡ2t.}iѠC M79,}i>ǖG UB ٞU͝hjF~f-̐ i)y[TsUܢM$&h& (ݭ -f \8L<]w* Ux`RSߘL1~3Ʀf$diܠBs$Sw7?ē9҅Z*ԭYtF0w}guX7h0C{e@bʲ,t ~ċ7I8$Q mˡԶYsLj,d]胆A2!ޏ0[Y6_KqO GW,ߎsvv%T-k7(B  orLf,d TlW4 L(0 ͏Ol2aww UUMB xxi6Xۮ Y)q-Aʝl%7Үs)bBS7px4TsmrM|l4'YfvHF2!vZ|O,Pl*5lHPJM&` /T-!TPgb!<˥S`%8GZ-"i`:hm|1@Hak~D.6t hAd< %BBL[0K҂R hJZja8W L8zI "\rٯfLbj/lRv\\{LD$Dcͮ)VY5lwpG$u ^}//ǿW}/+.]/a[EPG}ޗ$I&""sfCU4f ZÃZz#&g138НpRp=Oy>@褠ÞEZ'LYyKA0T ϦrOjl4 qS{Jۡ 4N1ggh `O ܕ}pf\_X^~cmQ-8LK̍W`!Qpwuff{h׶6)(,+|4 Gq`dayu)&ECe͍`̍qr\qafZC 1>ۅ e`Y&r=+a$W{Rd@.\U a9"w(st'qLޤ~c'9@LIݺnYjaheG{XFZ-rdfgeM ыZ IDATsgmFZC8[R"jF.0ae>{ž<99CAf_C*Z٠)ڎ#+6e8)IJ {ZzTtG;#0/ /-)[bY&y'G1 ' Fឩˢܲ2zϕ(MC#A44U$ ol>a{k؆+cܼ@PYTjj9F^p:+H?L}f;+PQBjBJ7Lht:lǿ -'6qmw-?2t(Uښ\rXw/&XUꚚԓ\{?s mwFea}fz??X0Au900P_5?I'Njn-{AMLL|{}}aPj ޾FJyZzI 3XQdפ,.? 3^6 ' HBJiro~I:'h6~Fλ4S\/n6#g膆mbA ^Z{_Iˆ$Me{m`&A֦r4?.uѱeJŞ ![66Yi5( J}ūwxsEn=V۶ԅ,R*Rj:OOM ?c`e/%4m7E:uFՋ./&I&ao&( JJ$w]qlۢTg DJ!*=J!v#4SޏY4Y5ȩZ&.bݝ%c"c fD2 AuwSb v )PrlscUyv$,mXm4^mjG6X|^zjk=,ݿ%wr)k7i#97u}<ϗ;av.t[}/8wrr`/_s(Ŀ9\Sn8ޡ02TT()eXE`eiFJow5 .uBtU!O=嬡ie]Klzz$~Wlz{v( %Ǣ\3WP)䇌-Ilrh,Cfǫ{:@:N+!.\קh{r, g0.( Kq'V0 8Vk}eFFIbkfk;^b:`ALÆ b+ߧZ!RHQ- M#>8g N;̍U) wijP5}wmSf~$ߣ(X )o7)lPF&s64HiޫS}rb_&gV78!=5V%" $IړuilF0 UKL'g' z ð>Ls(JDLWq ,> |<<}}BTҢTy-p¢^̯oRoT4yZH܉鯬í+bzJ *"aYkzT: yx6 CrTY VXZ^F~ү1xTT;C޶shF!{_ۤޖF&M)WTMרq|S~ GlDP] ĵhݡ_'wo,8&aо;'Ɏ. u U?7Jt[vuBm%dEú,`B+#iJFusR)M ԄzC{^61:?(蚊kLDuPX/\yc_5BrrR%.̯17Q;ka7jY4c O䇊ݷ?<<~jZtsQSUu'JTfs0HO9`C.gSpr(̟GeiKUW)FIO/ك,56-q=<'UvXmzF@n /0*4r+Ɓ<!$a%[T0-d\| ;D4MS*9c@Ð b}F=Jl>?;+TMrs9ۢR*.iFM-=܊I4L@_ϦNTy[W8#H͚$2FS|wϿzpzZ}Go/ jۻuRς Yur6r˒+P'}8 s(y[ȡq#e8y=( izU`u؋~ۋ"Ƕhms6tm1]@}O(S.:(Nt;+- 8^L^$+GφkĚFDā@3Tr-\-Vgq\Kޘ&g* W(RsϾ]~me}AS$LLCHP='dOٖXO㺽a2[aQC`"{b|~JQCTj@?l33(]2M!aD={ߞ/ۧ \v>h2e-Yb~Kh zSp4j#Jxӳ8iy8rkT:L/:4HUӰ &^eCTH P,Qpbiʟ?w?`j`GeJl+zr%|?fih}kM:b2w' 3,rH.gP/@(CF7)VgrG \8=s}al/t:;A!$2SbZee>b+RN ]0HW$~jj~LD7jDA^$ k:1=/Ǩv3{ ]dBKŰ4ȬzVV:` +,rSrie* BΦZ3]):EvVFRMp1&'eJNS_f/7/~eJ4,:+g$m('',& p| U Pt ˤ`ֲ*{~a~\>]v}Dz%A7é LaXA18f ,s*7 S7oȪN)RpVz{Lh{x'y!N83w 1k։Ins yN}@C 9bJsgsS;]@ض' i:C@3 X1T)LV 6ZmmNlbyf? i_-c**{rrTwj?hZ!mp :|cY^p8unT>XpPu˪eiJT˪l/V5.>P~3 To ܥ3$T!t]miI^_ij^q00F JaHP4(96|J'IfV[J1@vbJٽY1[f-)x'r:?eg?6{vjwwPnw"kh?rݤGwMWAe Ml1d/\83G!gZ-?k[:1@4E:ቲճw,*{$MXܬQo4\? tq3뛜[.j')9jMhuzj,a v7+auIF/ nS-XmR)pہԛM,w ךM4Uezrh SmۤR*9 FA(#pFNJMdzTD8zN}u'A _,7`T (@T M2eH0U-c)q\kPowhy>^&s>S˴sp$X$PU6C4nA36"t {~{s#8C(@guy-oxÊrdy9UNpt ga;R]r m/*+/ޱS؍÷m$]o'BK y $@& iY ]3HDxr uZ+ZiJ^JSh4J$&$8QD9(F ;bk(vWV%R!Ps/qcvkAf ߗTn(ʸ36,Q-4U MeA46alHG `H_୻[mŬ՚ݬ ot]U<;eOMȟ.-e-s O Tmףn{!~UvJ%.]> C-߷@*Z,ib'ggyC8N7S+w_>D߈Nx֕;{ejoDQsFPk!Jc4ŏ"He(/NF} PUUN`ѵ^:U( g83ͻvJ#]o7$A-V·:$h mZUUaV2 f&*QeVwpݽsZY Al?kIҡ]KO__Kkw= YdQ h98n+w(t`xIFI"E=gC6rHUUt]Z *f,|s$QBɭ#~ζKR!L"@r("c qL= g*Y(bm;C*̯$}4IeXNYaUàx gԷݫkFKƣfFolRg%=Pvi&6$rnP|6gk!1K6+F ~:cl^ؒnxPPU˖R!)N7,F`NXp#Y;"|TTn, E) ҵV0H;x:T5={ \CN~CIR/k; 4n:ڙ N/"ثS3L;vB>!SX:nevѹR`'56-:s{ ˤ!j*eΜ mRh3 Vfk F~k̀L* %NLszvO=s "Nn/k; DQ'B4MIt]" F䪪b<ଘ,ϻFLͭo(GQ ”vô LӠXGrl̄ɎP-MC~HH]P5Mq.̰ZN֕{C==$_rwj(;(R/ȍ4yyz]8ş}"Sc'~!!Nn[o.]N]v' $LAFWUSt]Q3 ł3TMV;30bw]N~dҥ>0UvX=0j~0sD5!a;a=\>Ol%5RZk2Q!ֶ:=Wҵ|33^-Ѡ8O?6 Ö́۝$I~o7޽\:^ƩǨ i7ׁg_ 1'OL }(AHl2Fgu%JIﱎa~뭶; E(K;,;e䝾:F݇ #(f lpZ!n#Hr2%C )p`-v=*yl`g$4Zm3tGQ{K7ljPal;ZK53cu:pVv 6kG'RÀ( J NoZvD*NS簺Gw n7)Z-sJPqo" vTa9a{4l gA~$iI,3#4ę5&%EǑZ;Q4M~/֖҃A}٥R*P)Ym0Й+qbj3|)8T'\{_R U|LVo i6wnݺp]]q{5ӔBN]c,+FA(iH|c"EW*]QtUØ*jd|"EvKN RPhhlR1cvɩq^zEޱt b*Aތc#{4e:Ajanr*?YMef톾+JumlW0Cd_o{wlܼi4y(W7TScG#VAaƑs SvQb,i u㱖BF1]ЍC\/`,#2Թ<ة~}zˆKo旣a-WWNw'+%"[ou< h^dϏiP6MebÈ0I3rƖtD*b~ZemZ!@!jI4wS涿 bAPgXホH+c,#?QK7 Q\Ͱ'&O<'Ҭ믿p+{lmĉ!8$LЪܙf'R55C{ ;T6 Q-4Mi:A?"7>-dxalIt&s?+y?߾^1lg~bjjvBww8O3)Wipv<D `#gv}&%tUM^_5nYża/+a&IV މy( LTLOTܩH(-w.o/M;ojffg^aw|twk,.=y$L1n21 4L{Mw ,BD۲GJ 1$a=$~pl;@0=^ff3A]!-Wn?\?hYv}C|; gfѱǘJ$X {]VTf9+;Hl6Zrk+1;Q}4 ~|F\(޹rۻ{Az~nu\ܸ$.]Y\:~а[gΜiWKE=cIYE0\(˲pyǖ&#IMhǬn6(xw'wdl\8=ih#B0Q-2=^/}-ú/]9­zC5?'񅮀@) ΊciP;<`8axꈎͲR! H`޸gx83uN_%x7x]v˝_\^P+^x>;|h=o" &p6v쉁a|ծI:ߗ0KRes F5|OTi?,rx7W*?3O>{vF r- DZQaJt:.AVa 2-R:QD =1^)03Qafg=A!7w/]^\_/=sW+#{{G2;2ºrQ׌8T5kl /xy( LO gF.yqo)tNҲRz^?}C&p7~}aBf J<&i6MVF!;@WojX'ϝp4x/r[by_z'>4 =v.g~h rppʎ;.-]dxNLę9t]H?<7ҋWn_-O~KG iju ҕ;g>y," ~ރ Bbvy:*F A ^0Q-15^fv?M8HW|x|n[N޽z5r4O}/~y<|XB4@o9 C=f y !blMOg~Ouv=n.$.l/=t|o1_䟼Dy:}-گ zۡk~!JHi >ͳ?+=Ck9\zuJ~ _Q* ,Q5~#X6 NJLU˜ɳ'݇hmR|v;s?ŋ_g7Uѯ~[zd?Sk)[?-5rɱ2390vgV. k/QM`o~9}{BEZ3Y-1Y-Sooe+˷[{7/~շՋ/%i!y׿U`oU3@P.8LTLTK\83XV xx}5=rqwaj_S:JX Y|6կߘG%3tipzvj3;9 *=M:.KWn6-,7[-woGP RCDZM 91B3<C7;KWnF릪[_o~($Z_6"C8;7x聇"jZX.^ј_\jŸ~oQH.#]BP;zWm +_ll6WPɷ7?R! kZ@_>#ĞXAr{a%ƽz_Gx] t 4.w?^Ppo~_?oQBi. b4.VK^ߝms<|}yOGx;dAhܺ^Zw/OϟE&D|ϟdBGڔhu}9|;7W<[P(`uu(b# ><}#W o[\}r]-64= |E333,#<P.;k]-ؑu`࿩"M4B$j?84MȬP(<2Ga@i>JI>=xKF(IENDB`schismtracker-20250313/icons/schism-itf-icon-22.png000066400000000000000000000024401476471630300216660ustar00rootroot00000000000000PNG  IHDR|0sBIT|dtEXtSoftwarewww.inkscape.org<IDAT8Kl\;y9~efRJx& J)B] $VlbXQĒ.  $Ċ pT;3cܙ{}YXB՜Y|;9BJTl>pģ ^Xov̙ok7  |߉[om?xCF:t*LTUD^D4<7t7LWqnWTʑ8smyaK!fw\x?mf:5y’X=\Q Y [SطZy9z@;rvj'_V@{'-C*jͱ*^q?@c7`uVӡp nnt㄰0S]/1]i`@Q-mǫDR,Pp,]1Myǥl2( eJ1&'+qL) 䋏>c1msh|6"zEXu h#"p{.ɏ:9::\ѐZO.$l9PH?bi(#) -$ =N/.r>ZV=2w6vhu}7FO)9D>uRnEue^Ɗ@2 Qta3aG1nG4 $ bcE$8V,K(2[$I@J4:C]ry^# >D A&&Jb|OƼ?7XY̍k"D7au :jz6eUTap.N`!GuZi!ڕS:]"!5Li?PYB B.i"n])Rbj嵰8)Jr>Oaئqyף/c  BڒbrqI,{Vgy}k_uݳI !z=2Ay0Oix,yrw^^૕~iu]];OG*I,)P`GJ-63+ֺ~jVɃlc+Jy ['$:?Q`&D&3Kgg]׍&&&W ~} :*& IENDB`schismtracker-20250313/icons/schism-itf-icon-24.png000066400000000000000000000027221476471630300216730ustar00rootroot00000000000000PNG  IHDR}\sBIT|dtEXtSoftwarewww.inkscape.org<dIDATH[lg1vMќd5kKaZM ! MB\ !@n' H0ibh)SW:MiN4N8NbLJ`Tly 5Z<ģ&*]rbTa<C^E;Ló\.>GU3hdž)۴ R5$د7s+?rX tB4}uDL4P]hfT,d'!ޑ3u*BWr[cN;iW)O.:2{2(IP])/?hC<~tԏ~|j)Lc)$ 2N _=[Ca^~qw/d>8L4x\z`?gsx>d`*n""V(\J١ح|)7%0~?{dsGJX87e`71OxyfV%6fl&$ПM'BR3mB cd!@Ӹ/YcW6,ъG9%vAi.3O<0'#G43L)[\RjB_Hu5j{h\̯"nX)) Gj<6[޸ե|mZCXk.qA^`˶ DM }t(7&ɡ]⚽O} }lb18hIZnW=w͉Tx0 zoBkhaVgݡ 3RowAlt+.l}hрaP[ r@7NMmbZ&ǡ: D 5TmfƇBp,XIC5ZD>Q\Mx.pi:Xl&l5%*GbKZ1Ѩsnq PW @{'9h Rah55H:QtW-gDꟲ_ʊLCɘ{DeǙ[kwRBRֿݗ(h w7o~{YJJ) O$IENDB`schismtracker-20250313/icons/schism-itf-icon-32.png000066400000000000000000000044311476471630300216710ustar00rootroot00000000000000PNG  IHDR ˞nsBIT|dtEXtSoftwarewww.inkscape.org<IDATHYl\g3g/;tIU&iJPJX$ W UB! !6 P(i&m N۱;3g=˜ Kі^G}EٗO .op {7Vbtjፙ= ’ U#**{rH"Ӿw$gxfPO0LXbž a! .W>]7oE=i`180TUHH$ v 9t,k>̲Rh W+g96==Caޏr^S>G*X;ryTJ/ruq<؍"pJsc"85 H)^;o|;_kȔIu#666[k70MFoE$Z*I($$q4M[bfOEQ/IilD4] ^MD[m]Buqvx㖅45 R=fPU*عJz&%*s8uF XXLSrA#lzD,K5>U@Q"JL.'G$E,sYBr+jQ`P:2<<(Ch4}FMYZoQ $:zbuMw#ÄaH|$*,|njzT6 dO^xxc"y2Xlb>(iZ_!1Md tp(&ȧS4:]V6l;W`jې֗ǁgnz)ٮO( 㺨(mD&^uϥȦ zbDӲэLElҤw{&|>dI"$Y$$h+>AQ%rx-҂?vSTm=хx6$4"MsR+QiS"@DDy>E`6i ;T0C1F hT|bL:I&چFN˛[{|Dg:,,a* a M%&Kkuq9B+R:4h2(j*V0Aa !opt(pĈZ=a!#);m^YA%#yЮ!ڵh92=ק5\#C6||?1.ZwrHw YD Z _ѩXXD`|0:!mihDcb1˜H(B .Pr"K٤|y^pu3{!%ґ,z6[Q@.e:]t`8,=>f^[voZPo HC$4AAQ$Y¶lusnC,l 7_~pW_|>06x1;<@g7_;>Trs-S'sʃjq3Whtsjo{CvGRfťTQ`2 <|3s==_l_ 7I_3Dqehs.RJ kf*n7eq7_X0O,כl0)2BDJq*Z\q)v|n{REo18S-BMˢcX< cee̍ͯO3k]ZDE;s %s66gffk/ N w 5Rjn1>ǑL]#VH%r;A^J2Vʤ?ڰs<>ΛLqߚ:vZ֍Wҳ=wѶkL `C]^"_:>WX!T*"M-B6KT Xk|f,ȡҭꩩޅOEQO.ȷ*_.&q}TU9j~ TxEj-Zt1q2HI,QBloHHA̎D)t.%rɄRkh-,ǧ)ez7v@/^M7vh*eAA"1m]Sx>aqrh57~ 0Be,om X}ؐ hW7$ DZI1i8eTJPn3nH UhT>Fʌ ;"+a"(yǦ3Cã(A!"hnhD?iNM|S-tE%EvzVSc}Nu92hi4m&(XZX,$ǶHmd\- c5RrtEeBuHx^cal|bb>jñL$2 44Um MIis:]pD/7stUHE 亐;y `B$!,;AO5 |_E?@B_1v 6x~{{# fDZ3H$B&BO_)-=^ͯo&sK":" ]db: ~ˣ H:Y&"҄$9"StQߏpM&p||)SHY ޻ZݪsUEsQ݌zu6Ƈwĉ f~ˆ b'8&:4TuZÉT|b28ܙL *BlԼ[k{z#L(h&ʭYX4celTŲ,XhlmZ^g (JͻZ<b:VKt ;6Ƈ/1SdHEn}֚սV)ގceTHLC fکcTRlzQ{`nh؏)jօ H"f,|C%ƇzTZf_yi\5aX'BRo4sgο~pGQa9syܙ~Q)a~_ _uO?IENDB`schismtracker-20250313/icons/schism-itf-icon-48.png000066400000000000000000000105611476471630300217010ustar00rootroot00000000000000PNG  IHDR0+> sBIT|dtEXtSoftwarewww.inkscape.org<IDAThř[$y}{.ERdh)E#qHB!=$H @^ QySd&\.]˝ݙٝ鞾Lwuu:aV#.%RSh >r~?گ?;_~g_0p驟$8˟#_[ݨUOĿ]JPx{,N|Kmt&{Jm _4_>CS߰)u2~^pL4JyI,qE^zWߗR|W!OROe/\ͅLjb07712Cѓ8\|=MSWJܹs6?S?vlۛ A1DN`jwWhܿxb 0~ܹs>{1_'go<xŊa씍}>#1.jlD>sHp*A'y3kmU5GId!/K%1^cH}E=QTM/7Hq]|6RT) ڹo9T_{yZ~|/ZwK/f냐Vg DGuL'R\Y!'8Am[=v"ͷvܶΟ??OOB??7WXN+20"L,'_#s5K$Mۙı a9|%A gM&ZgK k4[yW/l_D| ַZTJ9>vnٛxZh(;MaDc"SLz )l3W. 'Nrʍ }}~3gm3/ᭆ痖xY̱43S%x-v6^{CUJ/]7`p\?Y.7QZy.|!ٚ3++ʩ98W^;˟ ~яztBy:Zi cyA2F,GYi^%߉iJbts 9CTPZQ\ '[j~2 }invt}taL峒;h L@.4f8֕/1#. 7Om±eqh#FXb)؈0J0Z#B-`yyyyyeeeHE [[1/ d*fB0G,+gҙKt N繸Chq.(LM$2Ѿm^m_|ݝ)gwo?,`$2Mxǘ_$1nF+k5&w)K<>H$)g HӤJ'؎M]ڸGF'8ӕV),cf\c0R3K|vIl+%޽E{Ik7npl )rhp0PZS(,Alv|bpОF#QQ/L.0S(`[x̭U,~P#'wR8h4N4+^Yٸ&0d0:7ﻸp8B)PeZ`;@Gk*&1/ RxR_8y?kM;x`ɜ0S.pdn;1`jҚn-|  ǒT)(&R, F,bb)jSeXcU=T- cIJ̳;qN)S,cZ0$e8 4(2+=,MOOٹ`;6a6b5PJ1flFIvB1X,8?ظ C<0| Q8Қ DBЍ)4Lq4'G3# /p>e++ݽ@#2 ,F5Xl;?p8IhBY~dIR% K=;QEJZbOl"3"c IQ4{z&!B:W ,n}4Ld}.uYzU{Ї:t_[9a_A(&Ƣ:W"mFܸV4l(ml#/DǚL.V+!OBxOwBzY;F)mYhqX2 q!!o\KwFCܸ7;V'^ܖ0QJERw$y8\دFU2Od,W`{6%E$eG .ئ:UB)E<I⸂2^H!aiơϰt`=k0 7-;hz;XRQ$Lޱ,-f?R{ϥ`Y1OP"U ۱mvmd좔 4x͑izGb J+֛x7괺V/qv#)m*Ο?OcTu ءR)S(8 Enźz4 -4{*M7ڣhΰeeM(IU;SxIwn$?`ϔ۲\sA6I^l|)7p4nt85?UI~@c.qd"1L{TZd>|ʡʅ~Ȅf~4&,jI^[Rʍ^x{ ~(Tt?z*,Ȅ(D(|c{Rv+IҒR׿c1@oAVa^enD(\N۟zo^R&R0Iώtyz'?RJDJ99uy p^< +/~^ $I+0/ЍIENDB`schismtracker-20250313/icons/schism-itf-icon-64.png000066400000000000000000000155421476471630300217030ustar00rootroot00000000000000PNG  IHDR@:_ysBIT|dtEXtSoftwarewww.inkscape.org<IDAThݛipeqw_<`̾pFܩ%)2MK*[^+V$.'eW6;J*oI%l.ŖHpr8 ޛ`RHew>=RmGӿ)3SiF-ч}L q_?3B?8s^J$I2>Gġ^|ܶ EL~yqnȉ~+>߂,AϿ&WoY=qKke{SuMV?-٧;z ;X8̓BU4%N+^,ׄjE?ܹ I/2__@*MP(F>]^Z6v괺=Nُ"I,͖k>{X|SGw? e M.+cvT)ʲT<輳΍MGeΗ(Y3%IDQT=wKEv^zf~0]G71l8#=d'91*V7X+WHDhى""~{c'۟]sԈHB |PX?Qe~ ~(\ޢ8!tl&SF\Z^~,fUQ847L-^Yc~sOfT4%%eiT|v19`׾&~ l86Z<3Z_{#l) @VOqEy'O!LJ >³]7km=ܴ%![Ԁ1(fHI I'ltvǁil@4EJHPp[u;$gOg62TAiݗmwv tEOQF [#ik",L`g tMq[jsc)_ ?39[vR(OdX ڦEQ EJf"'3x{kF)^GReNǂ 8!Mnڈ[n !عt ߉9Y,.YƿqY d.JdC$ i`vev5fKE8q}c(n̛=5F[;f*Ӌ-DɆ3#aČV=cǎ;qS:ӏHg^a૗} KsHCOrwZ]f,*qzLc]ڑ "$E5:4NNc|EeףVo" I?[uXppa2w~y}W/|[~_'>ӿ ?H!7Z]$!7訊iZ\0!$a@T,`SXaӏhNߍh:=ch0IDY9kCy>i+>Ie3dHi97.;^s'־e/>쿟*6vj\YCarlS:R,"(2ئ$K$WLfc'QUros=mx۫4:o(BJɶ4 EH6fmEuTD4-r}c#CED'64Pcg/A!,C%h՘}92Vmrٔg'uV fRBmzN 9% <膆A)_yV@]H,MRntP5i5'"ܪKn=ɞQ ~K4erԚ>Uo'-Ҕ8Iquޜh9 NMY\x8#,^ BA&C ,OlG9J! nq~#!*٬)A%u}7m!|`~߾}mll\zW4MMOSā9:h?ӻ͇pxr#g ɭb;ީt)2ah6heȏp_U /7z63}˲nhBJ̍L$ ZX.w(Etk'wu"%!g[w;{J4x-N[]^EE<@o]IѦ4:=ZCd9`C75keXAHB8=o ﮱ,62&,C"lw^,^Zޝ!h:^@.q3C5U$L`|l(8C3\R!GiS;(8SyDj1QQitL6HDxԪ D6dc9v. 4"ж^agLRXՕ>=KH~Huc%m]S:.Kؓ$$#k/pl>7OPQǸ]4CHC5E&0!ЮwxvH[\f1d$q5A&w{ S'k[d$cʆzz$yHIh~U(K{wx]uZ='q=߿KI2ռrR@QƊd*OGEYH͋'L%Y4 L k衁T ;K^ Hrdi??pjz$_GV>c # #NgyF5qD3 E5wJc#e2,LڞT\L(d3 xOiE0Ա- ф4aZ${ į{$!"tu;/^ T崚&\Ậ"G~iK#9oLd2ic/O*uȋ?'Ρ ~dw] ^.WZU8!E MNCIu`ԲuU4M?)<0J6*V);Vޞ_nF1/ONEkBUl`b1؞ɠxIρ26cMDX``힣N7{IeE!!D+&qRɱ/=0U)wzzv}g?|Kuy`p EQ:?,? y%}+[vf~qm~|>5XDQ !\}'qEۛV7νu+{_8Sd!*H(J>|{sDcrb:8,H) xIENDB`schismtracker-20250313/icons/schism-itf-icon-72.png000066400000000000000000000177631476471630300217110ustar00rootroot00000000000000PNG  IHDRHArsBIT|dtEXtSoftwarewww.inkscape.org<IDATxݜie]w_=ӳy4#FeYE92p !&R)| )RERIR  [# HӳL߾n[p{9羖CN,{"⣿sπ$Iy|/wlcChC#$I>͗^zO;o.>S/9CW% Ɔ'~F.6W'{?~bq''4z&_C,?= yVB$Jrogx $Iя?Pi6x5. s#|q|vܷ8ʕh ~mAű{ɟ4-VϳrH1if]LqzN5n !,179ʉ)*u^G5Np4en"}W7.Y$}Ze$I3S?G9lw!ˢTұZ|x YF>cszqw2 j9~'=~ ̤ߩ;k_l2Ĝ~} 5³_f3<!3cCXYAlmtpI0iK$4A$ F!+UΝ:p !B̑$)I!I+5qZFi |knnϲ! '^z;{{?ie~ŘRFë7)d<*;?X4MY-XY4= ;a'z+vNG!yyIv}sL_o$I2?}ԩiǦ}oؒfl'42:" ,S6qMbԛWLS,Kt@BH<ץ14!HӁFҭl䝆/_t~2x|?sRF%7vEwXPl^ďtDb:LSج6)qtff0Zܣ'?Qت,Wh\"xdOoIӯ7֛vƥK7*I'?Йw}{鉋ձj(r:[|?F ̎/Wl09ƣg1Mua[z"kU[^'tMKn$9{=25RXo?;}R$IR^g''?;~UiG*us!N#&G\$Lpdfى)\v`0h th4ZaYoQn ØTze=~t3Νt RzSg{~_Ҵ84x(-/6uOeJ~ ZތgqlvA]Jy4BR$MЩ:\CcAv ̴+w.#z@$I/<zO}/+LMnoTTr' C~>8p2~KpLA;cX(_J|,Gs{=xĉׯW\//¿!,OY|L쟼L3TbqP2+3χ|Y$aym;UZ=>)dmZϿFKG1i}5En zk; LS'1  h6AdXR17>Os'fM x ߾)@}>g~Wk1MӰ,(̷SqhD*A#IVTΟZd+qsFR85=FqVuFH'_YWK&*$B`&a:TkMD*H 4Nq#%5~@iv8_Zuw!@$g>czϘQ%}DwXV/hB6&b4mUVM˘T-6*5nh7{}$+k`_ږ)'J 2*is;ZZA"YC=2=[n}]3S̥}*Rqxcc:}o? 'Nd#Flxm*oܼ͝ő1'G>[KWkt>`Zq{FyQEwȊ*ȪkCClmNC:."KPYl&I.KN!zn@6 {]kfttnzH*V!9݄N'Ngd$I;ww,; Zca:Zkח٬q׍> 10xC%EEQd4%bl@ܕPL&et]66+DaL"ū{X#/߼s'pAnW:; H+(2(L [o4dIs&kێ0 k .XYB0LjwzI(h %fe7.E1a LGBB4g'H^ϡZmC=#n3E^_9Xљ@'WVV.}fiԛ]9S 2vAh88wCh\v(+:,-eߐ@A((&.p_NPU*)&٬M .^YD3!DH0u2E&c$))?>g@]3M NQgv똽",dt(úmR͖3eud.^'id3E(Mwn0FVdTE!S&T\ X;$B i*iQ{|)"3@+|M8y|in_j6?zo4@P*dyF$:ףlv-}w*XGQ- *d2iC= X+]B (2i NlkьPtb )˸%Y/C/[.W͎{8T!ADQTv q't*V7#m2y '3>>(W$ʄ/j ,$Y"Ht!2orG$0 dJ,E.\C h{N$d"Q_]ŷ#Ewx,5<t( h4Zmn&BTY-MҐLm[6q33Y _`\~Bf" ۧbZ9ܖΪeyEuϳPrX!x)+$s6*'pQ(7폾-$ VrFuC(K1ًi;Bt]Ŷ,4]<4F8h^6d|7a$]A١tuz4nh%]UQM#Mj-$M( ]Q@*P5 =4%V USAF\yHD$7t@޷95;M*Cb_߹Itcl 1vb~s'Kґjg9Fa`|@ b:50€DpׯmN>XB +,a[&vʂ$MH ]*h>h! jA:EVd,$c6~O :|->Ѓ섹z$K i*Q>~` +ABL2HVq𴆧(2(emH2slL Uhʵq+nbb4g$;|,nѩqʕ(Lβ3&eQ7 BD{Ɵ|H֢Jg=vKGu4}"hTIY)3E]?~j;HQ` n-1,+{ACytrj21tnGYD=~ţplZ$)=YHIZV=fv2fg{And54C#vAfoi 02t5j 5ۻ늊TY`iM޹CE+ MC R 3hUh4@~nLf^#;pIsL^?Hd]0MU!F02o48m(*  R[CF2(I;gZ/ IJUD>^VOL8w˶MI2I1bHs=~z]A^. I`TC =%aSV@ +kTU! #]C YT,SXLU>v} ]G AXw(XmYqfCF"ɖa~jFk-v (ȹ1tdrԺvlt'ӕ$6&) @ףհ7(A52%;-"{0̥cY>$qEu=evwL9l2jr86jضAƶQ{ Bf$nrrvG&=fMFS,UU}M(EQd E1c*BQ2\]=b7ZVyif$Y²M/FNSn `_p& '*2l۵lcPkQrrϭa7od3|C=q^&l.)QF ipB/q >$K2ptxUt/%J<(F+Xd>vd EQ`|3Ӕa:, ebY+1\s5NEUH%Eiz=Vh75= r\oř1NqWս=<5b7"v;/iHLw\ms0Y MPѳ`+:;i:i`Z$ J0TER Ir" 1-0lA7;ZH>/3-{GzXӴ}eҍu.OPe{Uq({ۍ+nG/ Ԝ`de4Xݬ!+f2ulBAqv]nRn|z231̭C5 ř 90}P$-ߵ%_CfP>*>ʌI>հ7֖'JwwZ! 2ltl;#Cng~zD5 kbq}^]٨wjI?>UH: ! zݻ ,D[He qyj4CU 44,TB=fD/IlQEIGcqvRs \UZr=Z4 $I$I$ 5ீP{B 2H MS tw{кIq>,a:a` k=<7@T:C}/H)ci3Y4q}.]_o[Zk?JE?I%I%Ē$ n掓U]ft]0TINK{$ ?)#4]%>Fk*@qt .Xeazc,L3r|s/7nq^WRTwK"~c[K^.mllDo)yЀ$ Be覎DQjV#SN˲ah Y(2x~ Qlå,SMZi|qWkw;[Nua@Ptӊ85pyii0@!Pt~ h}/STP5cwwBa*=^pe,Ikef271uCtz٫uvkje4MD6LV8o^~0wB$QBڍ{MZ$Y*$Enwf}=9$pQF90I!kɇv7׶:͎ۨuz77 C#$U*=q|3Mӫ}m9W4U9p:2Y +{f8Ȗ QgbnrqƇ=?=7]^j7z^q*N,OLm%ZCYUO0M:ۿۇW늢` a?.*A孢M2LF<S<4靍Zz[͙-TRsZYng?ٯ{\N0/Yfh8K6tD4MA$vm19VdflgǑey\5VIW˵Z7]w-V+Ok#WU{#Ƈ)1;1љq,Cb;d\t`vnKFίq&@*@߹AKR!ɳ83gO ~qHlwJs;vj^yɧX=:q֧?φC.|;f|fjX{½J}Zzv}CkS$8MO|o3THml'y̌oZ ۣFzqWjf ⋫{$q@h?Cwj3cFv 7+v{ux35}3?8£gNoOSF% OO>qɹf@$ΞxS?_?>5Z0@{a\؃qԱs ]TkK|kll_D~O^W$8V88׾ծy[3^81?"IRf.W_N؅|shaȲ3Je[4$IƳO>+g~?Þ+Ocg_sF+,5u~O?zSv}O_|ƤlfJ  harrG~jj|mnn7H"?yW1[/|;@xy}BC }W^yW`b3Aݭd.(l,GȽ19y􂐫VEѮ ]asf]S86='):aѿ?rSY$˗/_ߍ81;v}O<ɏX9Uԑ$Q&Glv} B@F?"Q# IF 9&x,Qi?cé1:s EEAFӿ>`8k C ? O_ =_Ȯ5;DQƒ' eΜbysD "H L 'P}Lwy0?鷙S+.qVX*73@$]ճl(_xl˾29Vdz|CWhoMmfɱtH@Ib(FJ62*&a /^K@9'$HIB|/]LkBmw~xo'cu2\z?i[cI`8 Uz:9YYpEiw]j, MU;ۡ[ovHIHpzcyiWķLU.~G.lU12AȴU#GFɊ`|H/LFz1~CC1 \:^2rJ:v30~5uj}ʕo$e~C'O=YY[CCҰD)k8N4pdΜfIj.+[[m.,NYWpI @DB٥^nw4Qc{Cꇫ"I@N ?5)_iq!ʕ+;O-,G?wRe(W0z1 l;k<["(L@|E΂o\C$1#E._{t63a BV6J4MvZgb<8Th-x~xPajB㐟8իW]7$IO>C?۟|ٹm$,o<_ 1dk[}5;|[d-3 3]"kY-[{yq! ַ**5ڭ6Pț^6|aԑ;93Y*IC$I}SO'ߐPRubY|%?z_Y&@ӥ6 '&iG u{7 +Q#I2s#,̌056PI-rY4u/_^R{Y jvC@ !c£q)#|n"{g5)?Vu-@'?oϿ˟(gmvݤߤp!.0>c-IҒm,Lrbn =+Se*6>IFuDz>A^S < S:܈)ׯD)%qp"ɪLƶlLkxGH_ݼ}H=\z׆#aSp?o al՚h~3 a@?*N$A6)dl,Sx7opgć~"C Q VKIb09tJm("zAD"IF^k16VF;XE.kfvN%2wg?Dz2e.9o%L&e6?tUz4}.LÍuVJ(ff|/236 2Fu}+7="!E-K!M%7&QL&QdvFg41d|7cTܥաP(́?ms'}٧~ Up)sI.:$Q%]Ϲ (ئN6cm^J6۵&\f88jV5*MPȚ̮nt=(:IәָuODfhu&:`iY3 Qviq,Cgvb$4{?^sNȋ2<=֯>>9:l 4wOEv[:ıNuI3 , rҢ\_^gTE̎ ܓaIզh3lO j4(:D:!(*}|Tj qvv4װ3ٌM!%#6jDUJ3cAzoa?c݇ϝISmfrjH$ @sR,jr $*YBUB:%\y+Rٜ NJARlz=wHX,`T޺VO$}eUUPU!ymQn.*fm,Vi 7''gNʞyW#o TE Xbc RIrL*U ߋ)˦J"\֩q&0VIPTUQQd qpy¦\IJp~jjW醡Xr4UP*Wq.I%^!7GRe.slr${4S C+++ o\\k"D 7dYKHAζ(+'yddyg,չS7ڸ~H+:Qcٌ }& BSQT8o$׵,v/m 尳6ӤR7v]U@TnUnlCG7* !)gX/Wq`$\~=~(;> c!Ⱦ oEz!x=$Iv!0-ĩ"_$N=Kw>eQMQ &075F͍(d 4m+r&|HZG^~B AVܯ𞋧hvU YrZ0s#<9,FR\Gt$6m!F'`Ѧ9P[ձMMZH{+Ì3B>7&V$UNٹ$("lSYU ]om`ar4{s6# ;X&|"e!$W ]1^Ht/axaH z~iftmCP%21XCAi֎%uRF#OܦW :z@LYT 5ME5k c ȞJƕνxrnܼ^!<@S4EIFM=r%c^Cմج4;!`Ѥ6r md3<ϣuUDqܴ-C3P̛ȨJHDٺ;NjQLH1M[Pu7jҐaxUR٠^#"TKx$~8(I Ğ#A4G*ͻy} }wȆ&B]nju=\qՍ*-=P%0ML":^}@$YB3Qͳ I$!'e%.eI@U35 &\ms0wwĤJϚc<<СG.=wO==9ϿF1XW8߮Ff4:X qN۴o`i@5l+UDt.fH< eYMt#-5AJ9C1̌ZئIӥl89 G(c#@ y&r8F2fq}k2C#Zi0>bج ]aHؑCEl+t:]" UڈAciEQd,+$:kz)hvshc_Ku2l$Zmz!/@ 2,2Q3a2 0j fB%ƸMrZm'Um21Q1q}kuk>n5A^Y[N/Lo4#,l؆Aӣ:k)c%Y"d,S4X|N8Ʋt8AU( _^w^dl5[Q0^1캔 nP}r+Oy_B Zm|? unl ¨ߨ4tF.kS՚]~eD*}kZf%IJm'uܲKJ$;[yȏf݄GN*-\Y[ws AԎAh=.Cg,zeό0nךv _~X( m`L鉮NI,#0-H(ht)xDeYb2/۶ Fddmpgf%1+M@W58QKo="fm+#aH|ԓG_ 2HD">*.lHeؖtZIؼII@!]wp v]&V8z}L 4dmkBiG8qJL"%5|%c/3&4r)d,-6j NI.,f,X2e>CրDalx#ߑ?qAd^ аTu/*"BZcEJebY=?q]&q?)D1114TŶLl;]zi2ŝS@GbZe`h)S'%:sy>37ä]8tD]ٌt.^ʦ j.,olS馩~0c vVd+YkGRvf~ҸT:C~ :YFqed36qi: vv"?B1^RRζL2Y Y(aqwɻN۸CCMOo.m L8j!"QR}LOW%hFfwu`$ [6b`:o-mQT4D f&Fٮu]D½˲iKե ŰL4EA$,SMsF9^@R8Ђ0N? ,|qh2̤eb)2F$ Vvۡؖ!Y! 0utN~NQ@ҋn:61?~s^7??z"0MpݴL}LY%tM(L>c1pz>VחVC^R˫X=7,0Ū9"+VKT!əyj^H6`nm@$˜`(cq.tQL 2+QܻVM7W6 A4 RHҞ$TEx~dYBSfi{ltgdϩ&箯sf9(Jm.c3\SFiu|J=<4C#ݏl4M<ϧ46ʸ̝ l{mOᄑi˲m3e}VKmI”֝0qߤ8 #ڡ2u1H!K :9K591.4kllPsLÐl0zL]{DblIኗ[sqn tJDƶT20,Au*g|!"|A5y衄Q] &P:* #bJsB i| x\T>׍ g*$4à惯`)*0dHar ,2Ϛ=?q" 6"h#n((,%b]r J@7ˆѡ<^3JGz:]]N IHxp9]n,w7@ֶXӮ^40l)F>U\z"&"lhF"CR+J:55Ҥ<[ (>tݠȲ&D1QsCܮZ{-2T\/dڢurP_[{g7n څ}$B"?: _Z&xQwg TEI/Bb-bk"<#2a-Z է1℮ЬTԂv[Yl:ef3DaVͰeK)-"!= ~%"0A4;] ]*W^;?8+ߨBW|e'SQ"%UgCֹ%t= D##3,SǴ L#|q<6TZF-h4}S3$Nt&r&1G;+)pj~{Nx{o޸6h)RP8]5{O$ R$ r#) 'ѻ*^hWL8N4kd ?F @q<v #;`=Jt chFuqzJnl;X6iضOᅬʼnINOqnq(p?rؔC:qUB%k"'M0u,CG5G$dDt HYb:Y+ݙ: SuJ~(* `d/j =qYW٬Y}1uŹqNNrNp GnīW۵x\MRÛџ.펳-DInjoѦ'uݮKҵI=c隊!lkx5I8Jm ;{:-gƸ&917SZ1s!׷ko/ۍpR+M'HHv=-I "7dLxOL', t`tzq%e!~FkZ&=!z=^8 i4Z= !]"nOb0?La ? Z)yZP5F踮$+{$YVco_|- .Hi=ûm$M1 u=<ϧn? $ڰ/B+u]˲"M-/֨#6޽ӧBŹ ƙ3#$;k[ɛroZ~UU S'jJ$2x{}@*wg6e 00iwR.q(WjBu"L B쌅eXvx >C%c,̌8;ɹ)NO"IހKDw6JnUnuq$(aJxxo bdK FZ,ˤpC\yT')S1t s@W4Ƈt\zRbM[ }ΎM4͌8;t('IX(%ז6j-[]7ۖeIBKwoeׯ=.faP)m0@eH, TUk2aBI@LWkێPԕC6>~1-ΒME*sr|se[ivۍN;J)9q(Zdxp_^'7/_m)]cqVNA'H,H$$ 44 #ȃT@WTT3oVPzhPB `:#t˲ΓD-UNVmv+Zxsllm J?$b;FaL #㸴Zv1?5ts (& +pi\wjݲoOM4 XNj~g;r 4u 0uEqi4i]8eII"a)piƋځNDrx[ g+7龲 eX#U~W* te6a'q %ak) f'a~ztGvU-7:N,M/|̹Gk{5z;;|ʾzS׈XtHXFӕAe(|ʿk=r HGwD%Gbd8ϙ)EIU魕FRmtL;>Ae1< W V~3A9x#L qlfb. RR_+oo[ru}WZS??}ԫ^98sSL (Ux_/W֪֝jgJ<0^|ſ ?(k^`)륊VV7ӟ-'f Iwv /-0߿fm;x ilj`mk]/mVj+Ó~׷p ?]t+Q$oUJָ~ʫO1UN{xCԝq )fiT\vk[} R@pQ7޾Q*y͗_^nq|P㉉,{A2m8Ʋ߄>  IENDB`schismtracker-20250313/icons/schism-itf-icon.svg000066400000000000000000000537711476471630300214750ustar00rootroot00000000000000 image/svg+xml F schismtracker-20250313/icons/schism_logo.png000066400000000000000000000023611476471630300207610ustar00rootroot00000000000000PNG  IHDR"2֙sRGBbKGDC pHYs  tIME  uơqIDATxݒ+!+^*e/ 7fchA=d,}7yC$D$tҁlM9Χ'}o?`""9|ih$"4yҬ8kuF֭MR>I{Hf|o9F^">k}$!O>KZ% 'bni޵W \IgоrR#򔄼>;TB2 YA$+HDssx}Y #JkSIBTDڗwoVSeLE 6X-&/s`-b>/USs;3O$ij~9Ј!ҚfޙWg;;9;''*656454434313050STWbem=?N/-/ORS-+-+)+)')HJL'%'ZZ[#'#CDG#IZ^pzABE_bqJHKCDDtxpwCBD]_bEJW768FGN $-,.87@$$%fjoait?FNs{ w=?D\L677657 y yy yyyyy yyyyy  yyyyy y#LL Ÿ####LL####LL444o s####%LL4442/ ###%%LL444/2-M%%%%%oY444/L::-M!%%%%%}}BY444}::???n~!š%%%%% BB Li::???nL~_v%%%eLL }o:::???n$ϐm!7ee777LL41o}_::???n``ee777hLL444 L???nEEEENjee777hh000(L4441Yo}oL:n.EEEEr3mvee777hh0000oY1444 i:YBBYRin~!EEEEGIII3mv77hh0000گ}BY1444 !i::1BBoLEEEEGIIIJJJPshh0000گ[[[ބ BBY䎎 i::???nLo/m~EEEEGIIIJJJÿ~~000گ[[[ނ LLoBY i::???n2REGIIIJJJH>STz گ[[[ނ LL4Yo}C:::???n2πVnEGGIIIJJJ"STTUUT(گ[[[ނЄ LL4442oL???nVE:Y߸JJJJ.PSTTUUUXXXT+v[[ނO4442/o}BL$n$/`EEEERLVJ"mSSTTUUUXXX{{{ ނOu}o144422s:1YBB-/ vEEEEEEVLLL>Ϩ3~SSTTUUUXXX{{{t@"΋OuuQlo}o4422i:::L BBRi/EEEEGIIG`V/m3HPTTUUUXXX{{{tK@@@ pOuuQ!LB}o422#Li::??$:BB2/mEEEEGIIIJJHϨRPUUUXXX{{{t@@@~cdd)OuuQńLLYo}o/-i::???n?VꥥmkEEEGIIIJJᇸRR\JUXX{{{t"@@DNcddff,A uuQLL4LC:::???n//!ooqGIIIJJPSPqVT{{tZ@@@cddffgggjt^uQYL444/o:???n/߸IIIJJͨmwSSTTUqVVV\9P{@@@^ccddffgggjjt!B1444/L}B?n/πԸEEEnVLYԑJJ3~wSSTTUUUXT9`VrrͿ@ccddffgggjjK^l!!BL444i:iYBB2/sVEEEEߎLV>wSSTTUUUXXX{{U9>Ϩcddffgggjj* lLLLo}m!Lii:::LoBBY/7EEEEGɳL/rwSSTTUUUXXX{{{tr\ffgggjj]lLLMm! o_ ii::???iRYB EEEEEGIIIIR`JTTTUUUXXX{{{@@@+++Tggjjﬡ̝lLL4M ii::???n1EEEGIIIJ\RRR߼JTUUXXX{{{K@@@ca{+++Ȟ** lLL4441 :???n./!oGIIIJͨPqVRVTXX{{{@@@ccddfc9)j6 l}YL444 mm -??n//ߎ-GIIIJ~bPwSSJ`V+wX{"@@"ccddffFȸt͝l}BY444iiԩ m -//mbEEVLR߸IJ>PwSSTTUVVV9P@@@(tccddffgggjF"@Dńl o444}ii::- m -n//昸EEEEߊL>\PwSSTTUUUXU9\VϨ accddffgggjjZ@@*lLLLYB}oLMii::??~ 2ԸEEEEGEL/vGPwSSTTUUUXXX{{TϋFcddffgggjjﬡDlLLYB2ii::???nCCLEEEEGIIq/ 摇ISSTTUUUXXX{{{@@+Tfffgggjj ^ńl}LL44Yoii::???n/2 EEGIIImVHSTUUUXXX{{"@@Fü\++9gggjj**l 4442/}BL:???n$//3 qGIII~3PRRVIXXXX{{U@@@^aacT+++{jj송lBY444221Lo}oL???n//}E\3 GIIIPPwSI\.T{{{K@@@~aaccdc{ȇ)cj쬡ńl}L }oL44422i:Lo:n//mEEE+- pJ>+PPwSSTwV.VH@@NaaccddfffF9F**~lLLL 12}iii::1 Y.// EEEEqvPPwSSTTUUTJVVaaccddffggjt99@ lLLLB/iii::??iRB2/MEEEEGrzPPwSSTTUUUXXUPϐaccddffgggjjj@@ńlBLL44LYii::???ni2EEEEEGII~!zPSTTUUUXXX{ؿ~ \wcddffgggjj ^lBY4442_}i:???n/oiEEEGII~3zSTUUUXXX{K@@p9\9ffgggjjj**lB}}oL444/ ???n/ꖑߊGIImPPNzTUXXX{@"va+++gggjj]ńloYBBY44/_!iioLi?n2/ \YnEGGII>PPPw+zTX{"@.aacø++jj쬡^loLLYB}oLmiii::1R/EEEkYqJPPPwSSTêpq>@aaccddt9f**loLLY!iii::?i1oB/}!EEEE9nL.Ϩ!PPPwSSTTUUzp/Ϩ aaccddffF@DQń lMoL44 ii::???nimEEEEGɳ// JPPPwSSTTUUUXX(paaccddffggjdPIRIUUUXXT~)NAAaaccddfczAA@uuQ yyyyy€怕bm~???n2/}oL:EEEEG_~߼JPPwSSTTUUUXP>~Taaccddffg竱ϨuuQ yyyyy#bm-???nn//!kEEEGϨmVLqPSSTTUUUX׿mrFaaccddffggj D@DuuQ yyyyyˉm ~m in//RLVG>Ϩq.VITTUUUXK"v\߇{ccddffgg6D"uuQ yyyyy˶bL//_~EL-EGJJPPqRPUUUX.tࣞ\߼dffggDDuuQ yyyyy#m~3!EEEVLVkϔmJJPPPwPq`{"tatT`+fgg DD uuQ Ѷ y#### C~nEEEEV/~JJPPPwSSPq.taacF++qFg*DD*uuuQˉ 㕁џ#### Nj rEEEEG/_3PPPPwSSTTUq()taaccdaF@DuuQ 㕉####ċ33b+EEϨ!RLEPwSSTTUUU`taaccddff忿@^uuQl㕉&####m r>ϨIkRR`SSTTUUU~qrFaaccddffg D@"uuQll㕉L&&####%m `GJJJJRVJTTUUX"~{{w9߸{ccddffDDDuuQ ˶!㕉1&ˉ####~!š%%% /ϐJJJPPPJR\qSUؿz{t{w99ffDDD^uuQ s& ###33%%%m~IJJPPPwSSIV.({ta\+cf DDuuQ yy&mM3bm%v퍐 pPPPPwSSTIϨ {{taacw+{DD"OuuQyyyyˀ NPwSSTTUU"~GUtaaccdd@OuuQyy!&&}~%%m33mv7 pPSTTUU@\raaccddfd @OuuQ&&퉶!~}~~M%%%%7~~混 ATTU׿(TX9\Uaaccddff*D"OuuQ ½&-~ MMmv%%%%%ĽNjs pzSR{{{{w߇{ccddf]DDDOuuQ ::::!!M3%%%%%Ė!b~!ee7 Apǘ{{{t{9adfDDDOuuQ所}?-s%!ǘve777 p/{{{taaÇ+{d DD"OuuQm!???%m33bmvs3e777v (Xtaaca+9@@DOuuQ!?Lm%33 se777hh0ppAaaccdaKOuuQ!siV~% ǩ3mvee777hh0000 AAaaccdd DOuuQMM〖mL%!%Ķmǘ~7ee777hh0000 Naccdd*D"Ouu~M nԛMċǘNjee777hh0000 AR=9@L!47^ga,&.`qmpwjntEKKFEL0597BG1! >67V\]f|fx_jcVcx]haOML@L[414>HEbcm.,8w875:JRowyE`Z<:@)%&mtyDJE#*doiR`Pku1++<;GFWNurmkpw #._iuF@DpkntY][LHGGGL927826)%4y=GS16,rw AAC*-/?9Azry|>I=kou(#* >9:`n(#'HEA0;,0\gcR`wZgaOkjS_nGTGZ[a$O[j[VXBBBBlrm7BvI#p;~.Y0x[|P)s:X*\8EJ6 H,fa9u(yb!]ceKPL78;KvIc13?T[\ "'>DSbhqsx @@A=<>%&) "$X_h~FKE9?L.-0LNR+)->g:GIW0=/QQ^?@EackV\Vy127VTVTRT9:<kmoJHJqrIHIXY_gjugik|EDEstCBC~868%$(767u{EEL545424K[Olp=?D-,-+*+*(*]_^$'.(&(GMK!&!"!!GY\-/4 X`tPQQ;=?P\l[lo}JqHADLJkHcdhHNd*1.99:758]]] ]] ]] ]] ]]  ]] Ux UUUU>>x $Κ UUUU>>x ṹ!UUUU~~~>>x ṹ??f!UUUU~~~~~~~>>> '??BB?f'UUUU~~~~~~~~>>> '??BBFFI?UUUU~~~~~~~~N>>> '??BBFFFIII~~~~~~~~~NNNNNm>>> '??BBFFFIIIII@f!~~~~~~~~NNNNNN>> '??BBFFFIIIII+>Ρ~~~~~~~NNNNNN>> ??BBFFFIIIIIfKK$'~~~NNNNNN>> xBBFFFIIIIIfοKKKK!ၦNNNNNN>> x)))?BBFFFIIIII>KKKKLL !NNNNNN>> >))))ڹFFFIIIII+KKKKLLȖ >NNNN>> ?))))BIIIII+$$KKKKLLOOzw>hhhh>> ??@))fI1$ÿKKKKLLOOzzPPzb1fhhhhhhjj>> ι??BBڃ1!KKKKLLOOzzPP9999!D'hhhhhhjjjjjj>> ι??BBFB+ fKKKKLLOOzzPP9999$Ͻ$hhhhhhjjjjjjllll>> ι??BBFFFII@ ?IKKKLLOOzzPP999P*!hhhhhhjjjjjjllllll>> ??BBFFFIIII1ăLLOOzzPPk9991ŽSS͟ εhhhhhhjjjjjjllllll>> ??BBFFFIIIIbLOOzzPP999ԊνSSTT{Dmhhhhhhjjjjjjllllll>> !???BBFFFIIII92I?fIKOOOzzPP999ԡ!SSTT֯w'hhjjjjjjllllll>> )?@BBFFFIIIIbKKF–zzPP999wSSTT%%%w2ljjjjllllll>> ))))ڹBFFFIIIII1KKKK?P1999tSSTT%%%%&& jllllll>> ڃ))))ڹFFIIIIF1KKKKLLƒfff99SSTT%%%%&&#bbqcեlll>> ?))))III镕1'KKKKLLfڕwSSTT%%%%&bbbw=*𵙙>> ??@ƒ@镕ځKKKKLLOOzOKktzSSTT%%%%&bbbbjo ',,,,\>> !??BB@ľοKKKKLLOOzzP9>tڹOTT%%%%&bbbbj3ٌMŴ,,,,,\>> !??BBFFF@f>KKKKLLOOzzPk99912'0{T%%%%&cbbbqj`QÅ,,,,,\>> !??BBFFFIIIF+$fFKLLOOzzP999ԅ>{PL?z%%%%&bbbÅ&`````G,,,,,Y\>> !??BBFFFIIII>LLOOzzP999ԡ${P*ttLT%%%%&#bbb#2```````W,,,,,YYYYӈ>> ??BBFFFIIII?LOOzzP999w{SSzttz%&bbbM```````WWW,,,,,YYYYӂ>> )f@BBFFFIIII$鹃f?0OOOzzP1999+{SSTTzȒbbbj```````WWW(c;,,,,,YYYYӂ>> ))))?BFFFIIII+!fKKK@IKOzzP1999f{SSTTT*9Xj*```````WWW-(((vM,,,,YYYYӂ>> ڃ))))?FFFIIII+KKKKIfȒ999w*{SSTT%{wmo%```````WWW (((GgYYYYӂR>> Ĺ)))?IIII1x¿KKKKLLƒff+9${SSTT%%%&X9![```````WWW#((c2degggiYYYYӂ6>> Ĺ??@)¹I1?KKKKLLȜ>PSSTT%%%&#bbb#2*z&^```````WWW^(((cdegggiiiG2YYYYӂʈRR6>> Ĺ??BB"΁KKKKLLOOzԕ9''–zSSTT%%%%bbb< Ͻ%``````WWW((3vdegggiinvJ6YYYӂʈRRRRRR6>> Ĺ??BBFF@ھ KKKKLLOOzzk9Θ@OTT%%%Vqbbbkj[[*V```WWW((degggiinnvM6ӂʈRRRRRRRRRR>> ??BBFFFIIF?"'+IKKKKLLOOzz99$OTT%%%bbbbj[٨*****Ͻ%`WWW (#degggiinn/s\ʈRRRRRRRRRR>> ???BBFFFIIIb9fILLOOzz99 {{tª{T%%%cbbbb [&Ͱ******V`(cedegggiinnsʈRRRRRRRRR>> â!ºBBFFFIII91'ăFKLLOOzz99+{{zºP%%%bbbń[``%o****ϗq((3ddegggiinny432ʈRRRRRRRRR>> x2!BBFFFIII1οI@OOzz199 {{S˽O*tt*zS%%#bbb#'![``````%ϤXXXqddegggiinnyy .𩩩ʈRRRRRRRRR>> ڱ!FFFIIIIοKKKOOzzb99w*{{SSPzbbb< [[```````W^#XXXXqՊ}/ggggiinnyH. 2ʈRRRRRRRR>> f??±!!FIIIF>KKKKKIfOO999!O{{SSTTz*9XX0j[[```````WW.qqX#Wgiiinny.ʈRRRRRRRR>> ??!ΝF镕fUKKKK99>{{SSTT{kj3T```````WW`((;򰰰Ginny-.ʈRRRRRRRR>> ??BB!ffU$KKKKLLKFffb'{{SSTT%%cX9w<zo&```````WW%=}Winnn. ʈRRRRRRR>>ڹ??BBFFΝCUwKKKKLLz1>{SSTT%%cbbbq!'0o%```````WW(MQdeoGdHʈRRRRRRR>>':ڹ??BBFFFIIfCCU!IKKKKLLOOzk+ȟSSTT%%bbb#&P**%^```````WW cddegg/}=}웛4ʈRRRRRRR>>':ڹ??BBFFFIIIUxձKLLOOz9wtt*ȽTT%%#bbb[[*T`````WW#ddegggi}GW .톩ʈRRRRRR>>'ڹ???BBFFFIII$UwDLLOOz9{0ȟTT%bbb0j[[[[**&``W^ߧddegggiingWQo=̓H6ʈRRRRRR>>')@BBFFFIIIUDDLLOOz9 {{{z–{T%bbbbj[[[%o*****%WJ唔ddegggiinnndꌌʈRRRRRR>>'ھ))BBFFFIII?KDDOOz19$*{{{{@{T%bbbb [[[܄****** #2/ddegggiinnn̸#qq(3ʈRRRRR>>'ھ))FFFIII+>KKKDD*OOzb9!O{{{SzttPVcbbb![[[[``&o***Xqqc,퓔ddegggiinn/^(qq 2ʈRRRRR>>'??ھ))ڹFFIII+ΡKKKK D*99>{{{{SSPXb#w[[[[``````qXXXX3ddegggiinn⍍(-ʈRRRRR>>'??@ھ)ںII1ΥKKKKLLK1'{{{{SSTTT019k[[[[```````WqqXXX%Weegggiinn4ʈRRRR>>'??BBBھ12KKKKLLOwfP{{SSTTSPbtjz```````-qWgiinn..ʈRRRR>>'??BBFBľ ''KKKKLLOzkfMPSSTTbXX l@@&``````` c脽G`diinnEʈRRRR>>'??BBFFFIF" FKKKKLLOO+N!!!M{SSTTїbbbbD@&```````W#;߭WQ%%}/nn4cʈRRR>>'???BBFFFIII1bfIKKKKLLOON*!!!MϟTTcbbb![&*o&```````^(ߌe`GoWin .ʈRRR>>')¹@BBFFFIIIb1>?LLOON{P*MMϟTTрbbb#D[[[%Ͻ%```````(M̓ddeggW}%ְGWg5 6ʈRRR>>'ھ))BBFFFIII9ځILLOO1${{{{PϯMMϟ#bbb[[[[&Ͱ*%````cMddegggidGd7ʈRR>>'ڃ))FFFIIIf$@–LOz1!z{{{{ϯMMMϟbbbh*[[[[****%` 񀵞㓓ddegggiii/}=o}-.3ʈRR>>'))ڹFFIIIfUwKK@0OOO9>{{{{{S˽MMMMMqbbbtl[[[[%o*****%#㓓ddegggiineWGo((( 2ʈRR>>ᚹ??@ƒ)BI镕UKKKK1'{{{{{SSSDMMM9bD&[[[[^ܨV***XXqqQ㓓ddegggiinnn/}=qqqq-ʈR>>ṹ??BBUfKKKKLf!{{{{{SSTT˰D ը[[[[````Q%XXXX#2ddegggiinn-(qqqʈR>>ṹ??BBF$UKKKKLLf+w{{{{{SSTTk1!ό[[``````` qqXX;Jo`ddegggiinn (.ʈR>>ṹ??BBFFFI?UKKKKLLOOԕfNwKz{{SSTT#bXԙMMMMJ[```````cq`ggggiinnHʈ>>'ڹ?BBFFFIIxFKKKKLLOfNږPSSTTbbbhJMMMMJ```````(=%֨iinn4cʈ>>ᢾ))BBFFFIIăIKLLON0˜0{SSTTVqbbb&MMMJv``````#`}iinn-.ʈ>>'ڃ)))BBFFFII>II@LLO${z0TTbbbbw&[[oJv^``````c㓓`Q=/nn5. 6ʈ>>'?)))ºFFFIIΥIfFKLLz1P{{{PzSTTЗbbbq!&[[[[ׄ=JvG````` ;v㓓de}ְo}n⍍ʈ>>'?))fFII+ΚKKIfIzb9'P{{{{{{0zcbbb#&[[[[ث=JG``#=㓓ddegG֌Q/43ʈ'>>'??ھ)@I+2'KKKKIfO9b>P{{{{{SL0¹{Tbbbm&[[[[ٸJJJJv(㓓ddegggigWoGW 2'>>'??BB@ھ1'KKKKKI1!P{{{{{SS͟0ttt@bbbh&[[[[ܸJJJXbq㓓ddegggiii}o(( >>ι??BBF@ KKKKLLIf+wP{{{{{SSTO099Xo&[[[[`t㓓ddegggiineGqqq(x!?BBFFFI@ >KKKKLLOԕfN {{{{{SSTTн2&[[[[`````cbXG㓓ddegggiinn / qqqqM:ᅝ2Ø?BBFFFII11IKKKKLLkwNP{SSTTЗbXX9!P˨[[[`````#qqMJJJsG/ddegggiinn썍( ]] $âxBBFFFII11!KLLwNژږ{SSTTcbbb# ??*Є````cMsvJJG/ggggiinn]] 'w!Ø?FFFII1fwIfFLL$LzژȟSSSTTФbbbkl&z@@*````-񀵯svsGiinn4.]] 2$wαFFII9fUFf@LLȶPP{{zzSTT#bbbh&&[&zϽ&```` svGiinn. 6]] '2>w!IUfKFFKO192PP{{{{z??{TTbbb&&[[TPo&````cJG㓓̸GnnE]] 22!!αUKKKLObb>PP{{{{{O–ƽbbbbw&&&[[[[[P^```^(#㓓deu۔nn43]] '2ñ>$UKKKK1PP{{{{{zt*Obbqw%&&[[[[0*`cW㓓ddegWGd 2]] 22'!CC>UKKKKLL@ff+wPP{{{{{SSOttX#%&&[[[[P***֗q3W㓓ddegggi(]] 'w">! KKKKLLLN OP{{{{{SSTzԡ%&&[[[[**XXqW㓓ddegggiiG3XXq(;]] U2'!!KKKLLkwNP{{SSTT9jO&[[[[`&XXXq`㓓ddegggiindsXXXX]] 'U DLLwN˜*O{SSTTbX0PT[[[[``qqX#o`㓓ddegggiinnn.(qXX ]] UwLL$PژKPSSTTbbbwo??%``-;oddegggiinn⬬(ӂ]] UU'22! DwLȶPPP{O?ڪLSTT{bqw%&@@&``=&=QWddegggiinn4ӂ]] UUUU22w՘19PPP{{{OzSTTcbԊ%%&&[``cMWW&ϰ}ggggiinn 6ӂ]] UUUU22 ïbbPPP{{{{{@{TTЀbkj3%%&&[[%P%`^#cWWW^&&`iinEcӂ]] UUUU2222D!1PPP{{{{{00OS#bm%%&&[[[[**&`񀴇WWW/&&diin4ӂ] UUUU~~~22D āwzPP{{{{{S{1Xb%%&&[[[[*o񇴌WWW㓓`&갰Gn.2ӂ] '!΅ UUUU~~~~~'22+8wN!M{{{{{SS{99!%%%&&[[[[٫**#qqJWWW㓓ddgd`ְQWi5.Yӂ ]xx!x!xUUUU~~~~~~~~՚!D!P{SSTTX9wz&&&[[[[*XXX#2MWWWW㓓ddeggd`֌-(;Yӂ' xxxx$UUUU~~~~~~~~NMϟSSTTcXԊ [[[[[^XXX&`W㓓ddegggiecqq((.YӂxΚ'xxxxUUUUU~~~~~~~~N2M!!!DSSTTkh3Oz˨[[[-qq䴇*ͫW㓓ddegggiig#qqqq Yӂ>>x!xxxxΎ8UUUU~~~~~~~~NN22JM!!ïϽSTT%%??*{ܮo*ϰ㓓ddegggii⍬(qYYӂ>>xxxxxxxAI 'UUUU~~~~~~~~NNNN22MMTT#%%%%&@o&c#W`͜&̓ddegggii4YYӂxxxxxxxAIArAUU~~~~~~~~'NNNNNN2JMϽV󒒒w!%%%%&&[*z^#cW`o`edegggiin 6YYӂ]'xxxxxAIArUU~~~~~~~~w>NNNN2MMb %%%%&&[[Tꪪ*&;WWW`[oggiiEcYYYӂ]] !xx"IArUUΡ~~~~~~~~ww 2N误M ԅ%%%%&&[[[[[^-WWW`%}iiiବYYYӂ ]] !r8IArUU>$w~~~~~~~~~>D '0h%%%&&[[[[[ocqM`WWW㓓&ְ`gi-.2YYYӂ:]] '!'r8UU$$$w~~~~~~2 DDw2J1 J譟%&&[[[[&o**XbqcJ`WWW㓓e`&ְWi.cYYYYӂ rUU$$$w~~~~NNN MMM[[[[[&XXX3`WWW㓓dde}%֮((;YYYYӂ >ArUU'$w$ñ$2~NNNNN2w wՙDMMM&[[[ZqqqX*&WWW㓓ddegggi/}=cqqq(YYYYӂ2 ''A'~wwwNNNNNN !!JMMMJٮq **o㓓ddegggii qqqq YYYYӂ x$ΥArA>>wwwwww'NNNNNNw DmMMMٗqc2`[͜*%㓓ddegggiiE((qYYYYӂ!!$xUA$'$wwwwwwD>NNNNNN!ÚhM螭MMv#q<``W`[o`ddegggii4YYYYӂ2'!!$$Aw'~wwwww NNND mhhh2MM诰%q3``W`Qddegggii-.6YYYYӂ$!!$>UAr0KK~wΡw w >NŻhhhhhhMMJ譌Vqq``WWW%ϰ°ggi.YYYYӂ$!!!'𱪿KKKKwᦦᚦ! DNm յhhhhhjjjvXq#M```WWWW=GWgiବYYYYӂ2'!$!$ΡKKKN Nw'whhhjjjjjMv+9```WWW㓓Wo=}ii-.2YYYYӂ>$$w2LLNNNNNN ՠwhhjjjjjjllJ;=`WWW㓓eW׽W cYYYYӂ$$w$2~ÖLLNNNNNNw w''whhjjjjjjllll2M;텯JWWW㓓ddeg&ְq(3YYYYӂ22!w$w>~>LL 'NNNNNNN !h!hhjjjjjjllllllMJJJs}㓓ddeggd̮qqqqYYYYӂww!''KNNNNNN! w2hhhhhhjjjjjjllllllJvJJW㓓ddegggi((qq ӑYYYYӂwwww~$OzwNNNN2'w!hhhhhhjjjjjjllllllMJJJs}ddeggge((cYYYYӂ22www!z'N!D2hhhhhhjjjjjjllllllJvJJvWddeggg((푑YYYYӂ!ww2wPzw!w!hhhhhhjjjjjjllllllwwwN!PPww w2ՠDjhhhhjjjjjjllllllJvuggi ((YYYYӂ2www $N2ww !àÅhhjjjjjjllllll2G(((䑑YYYYӂ$ ww wN  ljjjjjllllllJJJvsv(((2YYYYӂ ! wÅjjjllllllMsXb(cYYYYӂ2w DDw'! whՠmllllllJvs#XXXXYYYYӂ'à m whhwlllllMJcqbYYYYӂ! Ú'whaAhhllllll2J66YYYYӂ2 D!harahhjjjMllllll,,JYYYYӂ'w ! jArhjjjjjjllllllե,,,,,YYYYӂ!w'w<rrAajjjjjllllllŵ,,,,,YYYY2ՠm!$rrrrr2jjjllllllM3,,,,,YYYY2wÅ2rrrA䨌allllll3,,,,,YYYY 2ha;%;rrAallllMM3,,,,,YYYY2ՠ!hrrrrrrrlll힭<յ,,,,,YYYY2Dwharrrrrlll3MJ6,,,,,YYYYwml$Arrrll33ߵŧ,,,YYYY2!Åjŏarra3MvvvJ6,YYYY  2jarra2臇vvvvvYYYYwBN201NT[?\`hd^jxqivpioz!;KGNNdX% $"!rw 'CCTM}9FCFQXXNU_TQQa`e-)0jyu>9>\}{;;;MIJ&')959SbW!$FCC1/10-0|?:FTcs;pELQ95>k|ECGSR\rwqw~ZdtJEI"*ECD=[a[n^?;>-'/'-)[b^{EUA|aqkj|IX` &MGFNeX.uL^LLP`KWU;=H1L4,)(p|23?!. ^D@D@?J/KM:J:TPQSPPIYPixt((+l~14ERX`Zco NORRNLjxr[e\cn<69sy\fxrw  ms|U]g42.\fuFRbXbq,,&OKM5:@#;IMGSbX^t^Z}[qm6P*LKmH V$sY O [g(!\?36xPȈp5`h|2__o. rET%nW#lYpMI']N-"fGV$#A>MQRk懩 ]9:;8^),;֟Ad㴄kcB4j/F zwDv욲0-? <<<|(0@]i}DKB=NEox1/20-1YboTat<8DS\i "VYb* !FBK?EN/--JOVglwp|n,4E1?,#!!-/<'0@O\N4?T%D+M#FKsyg}?>L^le^q[jjn3-6~[ciCc@FTM4<>W[eYX]@S=qx;^g1/1PRT4M1rQi\HDL34:512rx1-.#!@HKS.8F]nRcZR_Z&!#xWTUz  9>N@>AZk_>:?V]odnFGN)!#m{QV`=KE;@Arofn}Egi=TF!&CBLAaeSRY|RQNg.*&9;8ShVego4132/1"Xev`aj,(5\z`p27BCGPzSPSYm`&+6gvu39@gukJHJD@D@@@cuG`DDPUEXB626WU[HBE--7\k]XamW`b)")5>2&$&KHE_pg`grNZYryTQUvcqIHThkwIIJGCHIRe >=? 3G4NIL=64UgPq,(rFarFa4N>$rүExSMtREz~e֏up`,6]xI+YA1M3p,6P͹^WfLD27%5z EI=[1&9,]y]m+ ^Q27! zܬȰghjT0.//LYHC-K5͇ը7h?÷}nDomGf!t??G?>>y'$)$$&t{ry "..-Vj`LKO KINfmwVV`743"!QR[xmnqBKV679\m~1/4BAB mqxCjo979hms757IbM`ck9=J ,+,;SafU\^=Y?eku237 &bgr! kxSQR@?BGSZ2V**+/68BsHGGN^T1/3Oep*+,V`mjutKSG:=9796}!-*=)VctZbn.+-]h]DUQ)'(Tc^ShS $)PVd:BQ,)(ySgZ669Qy}l}|}}PXai{ $DBD\et=<=Z^h;:;bp?FPKUc"GQ_+(+V_k&"&}XWYu{U[ViUUVBCF`lhQQR[kYVmTakpDEEQRYFRb7M8 LPT<9=99:q*R%Y*qb,/ 3VbVh>Bt=4Rh㶪'zD^ $Gbb%l WXE rik::V|eVhlp@WX.KL+\xdyRBpv%lpD_ JԞdcbYEhw'N&jױddcevnO{{(w]f.]9+o̖Vbel> &< 9ΖbplX ..a;דv|%/U&2xLjo8lB/<_'Ħuifxx@˜Ҋ ]f쓀UNf a q ``̽CK&];2Jӑ♐00!(u&y``3ND2At0}ZJo$B})ZP[otŧsZZ P:ţ!SSөI!MSF:????($HGGE%"0/1JMYFZK+%,88@w~6n'r~PTf533 >U90-.5\&[edA;<==8ds~NJPJHL(%&S\f"&*&!$EDG*L-*2NJMfvs98;JHInx1.33?P]]`Y\f0-9/4.$LDj=ms~rS`qw[[[^hy7Z0'':#9=<KKK97<'*#6598/;SUd033BABKQ\gs''*QjU!*rzFCCJRbC?@SJ]Z^TZiw|ehwfgnZiv3-0QNRdjs,3:1.5>>?_m$$(BFTm}\ZWIFGs{n|?BQ#BF@(<Е1~Ǻ+N/)ɾC]%U>8, :H\2hH}QI6VB- ,l֛1Ǥ\ǒ쑀 gr>W",u!)NM̓QoArOBբ +?\Y.hϬn{C](- q l:?_'j\Hs%OWՕ,fiY'YjoL|)})D$ ә)*סnRۼzBgl^^^͡* ]- bb^^^ENQԪ *COҽD f_Pm^^EaFJ)ƥAlb^wn{L3ҩ mj=E[ AJՕ~Pw:TPEtszl j=xmmEE[5C>@4RެTZ;k"ytbZZSc;T,&l;IRz;tRG?(0` IdN<>D*/+V\lr;e6]mzcjvXXZ#!$WVYVVX?DD GHI328QbPDDF-.2MXLi{lW^g88:GIPQUWRai'&)&&(Fdg#"% "KUb`o`LVYRSUtv{adk[MOPhuyZeZKKNAMDTYT]fxbjiRQRQQQluz:==WahDKX#%)IGIvGEG013MO^~EN`*+-|\\`elz878777757qx1/1RRV ///ctLLP)))\^]###RRSvOPPKJLHHIPVb9X:CBDLV^BBC o<:=;:<::;x23=awcXqZaac__a2H0P]fEEJPOR)+1EM[99>48CeouHIJbbhDgC==?FOYCZL63OYg545@DQ444242y9>J.,.-,-,*,*(*79>FJTdb\[]?YC!,!WXbgjoCEGy[{Yjtzdn}PqNOkMOt|EEF135.bi{w% ,,UYp ~]]0DFF58zڐPPШ B9|p0 =d?05mm;25uGԖB_BߡMpN04?g=.>PuoT%&ؕΟj -p.=V;xm5vP n Zp~~d45umm7 T@-%ll|p. +iDFFVzPQv;%Źk)jj|I~b4?.5m[HHRGv@G%(L |I +?D2=duv{AGGԎ%l|I} =d+?z0PP%&nj|I]+d.=zVށ޽nTXLL $|pN=b?.DF83VP7{"R%7*ĨŢ|Szd..giv7wvT&l%y)|Ӧh?DFg4muTϟn)Cy|Ӧs [ivPnG٢|hhhƙOz muTX@ɪ LLlIS?cƜOk &/EEƜ9"TX- G(٢>@knY`i;<=;:=6:8MlIIrELWfq{042-./ku,,.**,>h:XY[t{T[WdhnKQbRSU>ADEM\mptc`K_KSQS??BQOQds<=?HJR@[@JIJPZk679WX^t|fit\i~@M@ECE=>GOX;9;Pg^999979:[7i_ht/-/,+,4M1~)')SwRjSZZ[\Z###VVWp~ABEJ[UWhiNNOU`gr~GFHbcg]_b768566@DRiyT-4.-,.Uak8;@1@/" #! "-15]o\*-2OQQ^`gYal[hu}A\J%%*658abdoxokLNRip}))+\\_GJMALGPPS%3$HHK]pnKZ__hpUTU RQ\TRTupv~WzTRPR}76:IHI>JRCEM/3<EDEA@ACf@~}Q^sdxo67@;d8aiv444424RSV,,,6Pc\| !=CL,04]dpJJLFGR!$)]YtzW|SOPN::<P_j}}}426^iut!jGQaZm]IPYFYL~Zc]q|O}UbsQyQ6A}vQlOVW^ Z[_bmxju}_aar'((2B0YY[$$%$"%@ME @AEnxtNMPZxu~dp~DEFbnh?IAEmDfu<=>GMZ@a?AY@557?ERnnqxcdf-+/^^ah|hCOSYZ\\v\OPR),2,3<\nmOYY@@CkwQPQ[`l__fVbgRtOJHJkko669GJGJrG^nlDDD=H=ij+..,,/=<=}JOQ((+8888685:5biwhmi" %323.2.`bk6P3-,-,,,,*,W]lnnv*(*GMK&&&XWYUSVTSUou~:;>KOLDGYJIKNW`DCE]dv@?A;>F;;<N]q2222222m"x_7x_GG|x_GGIDŌ C.zz_}GID7L Czz_uH17LLOO`,fC_GGcu7GLLOOn,`ȵ_GGI17}u[OO, #`4y퐐_}GI,?7L#ΛVV&y퐐c}H,?7LL[ʼnN#ΛVV9w~pHGcu7!LLOO|rdVVӚ9w]&~GGImu}LOO?d#`˪L']]⃄~6}cIEIuD?#Λi,]]⃄e;$w6 u LLL,ŞL#ΛVV9,dK⃄ejj+ {0"GGhcALLOODc`VV d dd ٕ jjl+ {0~">߶GI?fau[OOnQ`[` &]VMjjloq;B0">>[?AL[[,>Q#ͱnw]]⃄⿿ïoq0"GН LLI?>dQ#ΛVV9w`i]⃄Օ/^eRKqW$0uGGI7 O> c#VVn'#`i&jjlR bW(0}HG!ŌhrJN?|QQ[#9Z &dwSjjlRٕ{0c}c7L`[ Q#d!?a` ]]ܿ9K܆jloq{0cGc}k!LLOZ`#ΛV |`K]]ܿ8KoqWU{0_GGcmuIO/?|rZdV ' ͱ` ; {0_c}DmIc`D?|Q#`ZZn' di'jjlS P/ {0_cG}mLL,?Q#·]]M9w]jjloPP$ {07GG1mLLOn|[ΛV,^]]⿙' ijob0 {0_Hc1?muO,i`Q Zտ-eRjo/ {_cGHm𻻻[>rQ#I w &Z9'Kjjj P$ {"7 GGmLL`Ar#/?wi&]] SgjjlP( {2m ,cILL?LΛ/d# ]]♕:Sl; {22xC4 >>Icu!?Q˪L#n #ܿg= {2222zFmfAcLLI1ŲOQ##N- ڽ9:jjҮPU {2222xx LD?[LQ# >` ]]9 jjlտ {2222z~ZD|i[ a`N]]'܆jl {2x2v Q#D VidM/e SjW {x_xzzF~ дZ#w  ejeKϕW  7<ȦȖJ#n `d]wiigjjl x7E7<**zF7(Z&idi] #jlP 2z_E7<*<< mF.7>"vГ ViMe KSbP 2"*<<az"~p^Z ܺ,^jeÿ m7*<<*77 aamyzzxmCCUZKϿ'&&KjjlP 7*r7m ..C4yF~@8/'eSV ejj~ 77>y F4޲U8&eKP67u Fza7vȵy~+̆jٿ/6>7mCCvm퐐UjjWP6a7 Fa~ JJ~y퐐(+:P6 w7m J7JC퐐U89/644w JJ4y4w퐐(864JU?AE\OPOPGKG~GEGA?Av=;=|;9;979757333_ft212.-.6M3%)%`^#!#eimBDFRRSD^ERvPLLMCJX]|_`n768qpFENjklMWf/40[s]mqmValclt|2@0diwm} !7HFrxxQQSOQQLNXEM[Th]jmzGGITX]JPg./3>=@wRZlZZ`759JqF$GmCd`em:AMddg//1=@FT}{}$%&##%y-06Rip_h_MLPJNMPnPTVTIX]SRSKN_V_qlrz^_e<>_<7784w9ڭ$Ew%$Ew%8G%$Ew8GGL E$Ew8GGLLkwڢ$Ew{GGLLkeظڢM$Ew{{ LLk QQ#ʏ[9MM$Ew {3kҖQQ#&S8 9MMMMM$Ew8GGg8 { wQQ#&SSDbMMMMMMM$Ee8GGLL8 yQ#&SSDb UWHqMMMMMM$EeGGGLLkbݸ3L!&&SSb UWX&PMM󕕕$E{{3LLkb)Q833SbUWXY.Z\;A q󕕕$Eͦ3{3QQ#Q8bkUWXY.Z\\Fih[<}$Ee8G {3wQQ#&SOb#+YY.Z\\hiDrddd$Eͨ8GGL y wGkQQ#&S"bi#3Y.Z\\hi[ߜCddd]$EͨGGGLL  e 8k&SObUUW#8&ԩi谱cm[ddd:]$Ee{LL kg 8kSbUUWXԛ!8;谱cccah[:]က$EÞ{{LQQ#k8bUUWXY.Z"bb!Z谱ccc5|B=:]က$E8G {öݎ7QQ#&Q#UWXY.Z\Fh!kH.䰰ccc_j2|<ကV$E 8GGL{y7QQ#&Sbe33#UYY.Z\hD^xHz;,^c0jlnoVVV$E9eGLL#&SbUk3Z\ht^xHz!~h<jlnNosVVV$E978LLekk&SbUUWU#k8^x"AjlnNosss66VV$E938Ô7Ï򦖖QQk88kbbUUWXYY!bb^谱cc|f*clnNossK _VV$E98GGGÉ yÖQQ#!b&UUWXY.Z&x^谱cc_<*Ӛ,;1ooss> @VV$E98GGLLL ywQQ#&Obk&XY.ZFD\;;,^ccӚ1H.oss_ 6V$E93GGLLt&b!k&X.Z"D^^.ffƩaIjlК1*a _=V$E {LLk8 [kSDbUUUW&8k&xhυ^^.HzFimjlnNl__V$E88 e 8kQQ!Ҕ[b UUUWXY#iǑ^^h<jlnNos 0β$E88GGG3 QQ#ʷ yzUUWXYZ,b&Y谱c6hf.lnNoN 6$EGGGL QQ#&b [[WY.i+f(.谱Ic.HxnNo F$Ewe{ 8LL 8k&b#zzX.F^xH(.就䡡 *,H,*ls> β$Ew83{3gLkk8 g&SbUUUW#f"D^^\f(.2jlӚ11*6 6<$Ew88GG3{  kQQkG3bUUUWX;t b텩^^xHDjlno~_$E88GGy kQQ#ʖ&UUUWXY.b;\ciI<;.jlnNo> β$Ee GLLgݎkQQ#ObGWXY.i;a|.;;.lnNo_ $E3gLLL 8ʐb #WYY,^2|~[ӚĪxNNo _$E88 { 8ݎk3L#Db¦UUUfkfX^^F<cH @β%E88GGG {yىkQQQkUUUWXX&8Dǩ^^I<jlӚ10 %GLyGkQQ#3!UUWXYxDb \^^豤jlnoj97 LLئ Q#ʻb83WXYhǑ!f,^塡m2m?jlnN F49P8kk8 8bU&kYY.,\+f&5Bm2lnN> TykQk8 3bUUU&k8#\^,fZ_<B왒Bl_ 69P w8QQ#kSUUUWXXSkAb\^^^;zhiIcjm0_)PҷQ#Ob 3UUWXY"\^^cjni@qt8Qʹb &k8XYh(zf^⡡H;.jln 0[ցT[Db Uʹf,Yhz\.HkHZ舡h0 က9%%wp)T<z\\^^\+!!,|cccHHcj_0 ကVwp‰)[)Mq;Z^^i[ccc\H ]က9؉P )MP|,i00 :]VPP [t}󕕕T c #2C292B", " , c #5D4C44", " < c #314933", " 1 c #645F58", " 2 c #6A5C54", " 3 c #736D6A", " 4 c #282527", " 5 c #5F5F53", " 6 c #5C5A5A", " 7 c #3D5742", " 8 c #363538", " 9 c #776A64", " 0 c #536651", " q c #857C75", " w c #252124", " e c #C4AF9F", " r c #AE9C90", " t c #686D5F", " y c #5F7060", " u c #60524A", " i c #1F1B1E", " p c #C6AA97", " a c #433E3E", " s c #394E34", " d c #CAA991", " f c #525050", " g c #57514B", " h c #1D191C", " j c #65655C", " k c #473D38", " l c #807566", " z c #858582", " x c #7F7165", " c c #566C57", " v c #1A1519", " b c #9A816F", " n c #28272A", " m c #5E6555", " M c #8E8177", " N c #4D4A4B", " B c #161315", " V c #171116", " C c #4B4849", " Z c #A19083", " A c #D2B39C", " S c #756C65", " D c #C1A188", " F c #635850", " G c #B49F8F", " H c #373432", " J c #3D332E", " K c #474445", " L c #726862", " P c #BE9D85", " I c #515E52", " U c #464444", " Y c #726662", " T c #BD9B84", " R c #1B251D", " E c #34302F", " W c #1F1D21", " Q c #352E30", " ! c #545255", " ~ c #5A5151", " ^ c #9F8977", " / c #535254", " ( c #BA9981", " ) c #1D1B1F", " _ c #CDCECF", " ` c #423E40", " ' c #C9A993", " ] c #5B5248", " [ c #1A1B1C", " { c #716157", " } c #504E51", " | c #5C5E60", ". c #526E56", ".. c #3D383B", ".X c #171319", ".o c #6B5B51", ".O c #564843", ".+ c #3F5A40", ".@ c #393637", ".# c #484849", ".$ c #6C673E", ".% c #C1C2C3", ".& c #667163", ".* c #605850", ".= c #9B8C80", ".- c #8B7A6D", ".; c #1F1F24", ".: c #3B2F2F", ".> c #444245", "., c #595353", ".< c #333031", ".1 c #332E31", ".2 c #C9AD96", ".3 c #3B2E25", ".4 c #858788", ".5 c #4F5B49", ".6 c #756254", ".7 c #6F6158", ".8 c #2D2A2B", ".9 c #364637", ".0 c #5C4D42", ".q c #6B684A", ".w c #040506", ".e c #807062", ".r c #292627", ".t c #282626", ".y c #9F9087", ".u c #343C35", ".i c #5D595A", ".p c #657565", ".a c #0E1113", ".s c #5A5757", ".d c #857B74", ".f c #232221", ".g c #8A7A6F", ".h c #595556", ".j c #CAAF9A", ".k c #67725D", ".l c #766C58", ".z c #221E20", ".x c #2B2015", ".c c #6C6462", ".v c #5F5148", ".b c #2D2E2E", ".n c #08090D", ".m c #736B4B", ".M c #3C3E40", ".N c #D3B9A6", ".B c #C3A793", ".V c #6B5F57", ".C c #2A282B", ".Z c #4F4B4C", ".A c #29282A", ".S c #29262A", ".D c #6D6945", ".F c #272828", ".G c #786D67", ".H c #5B5B5B", ".J c #C4A08A", ".K c #262427", ".L c #2A2521", ".P c #050000", ".I c #5A595A", ".U c #232424", ".Y c #C19E87", ".T c #6E6867", ".R c #120C10", ".E c #6C6665", ".W c #575C4D", ".Q c #917865", ".! c #305A37", ".~ c #5A5250", ".^ c #59524F", "./ c #746459", ".( c #979899", ".) c #515151", "._ c #504F50", ".` c #2B2A2F", ".' c #5A695D", ".] c #463A39", ".[ c #696158", ".{ c #6F6054", ".} c #4F4B4F", ".| c #6D7869", "X c #423A35", "X. c #2A2724", "XX c #C2A28B", "Xo c #141415", "XO c #3A3737", "X+ c #61585A", "X@ c #807F4A", "X# c #393736", "X$ c #C1A08A", "X% c #4D4A43", "X& c #937C6A", "X* c #2C221C", "X= c #4B694E", "X- c #474347", "X; c #454545", "X: c #F6F6F6", "X> c #66564B", "X, c #595452", "X< c #4C5D4F", "X1 c #466349", "X2 c #0A0A0B", "X3 c #6A635C", "X4 c #706258", "X5 c #D7BAA6", "X6 c #7F726A", "X7 c #B5967E", "X8 c #362629", "X9 c #3D3D3D", "X0 c #2D2B2A", "Xq c #3E3B3E", "Xw c #EEEEEE", "Xe c #78716D", "Xr c #2C2929", "Xt c #584B47", "Xy c #C9A58B", "Xu c #3B393B", "Xi c #C2A48E", "Xp c #B7A297", "Xa c #393739", "Xs c #4F4848", "Xd c #A59082", "Xf c #383738", "Xg c #282525", "Xh c #393539", "Xj c #282325", "Xk c #373537", "Xl c #0D1611", "Xz c #363336", "Xx c #888862", "Xc c #242121", "Xv c #A08C7D", "Xb c #2B412B", "Xn c #3E4D41", "Xm c #BC9C88", "XM c #89796D", "XN c #74665F", "XB c #231D20", "XV c #617060", "XC c #71645C", "XZ c #CCAB91", "XA c #C6AA95", "XS c #3D3D40", "XD c #93857A", "XF c #51504D", "XG c #06080A", "XH c #5C665B", "XJ c #7C706A", "XK c #7B7069", "XL c #393B3C", "XP c #3F3A38", "XI c #586657", "XU c #7E7858", "XY c #64604F", "XT c #5C5A5B", "XR c #514943", "XE c #5B5A5A", "XW c #2C452F", "XQ c #706B68", "X! c #262126", "X~ c #3F583B", "X^ c #655850", "X/ c #333336", "X( c #C19D86", "X) c #62584D", "X_ c #5E5553", "X` c #313334", "X' c #BF9D84", "X] c #575456", "X[ c #454241", "X{ c #201D20", "X} c #5E7060", "X| c #555254", "o c #1F1D1F", "o. c #60524B", "oX c #4A3F3C", "oo c #8F7762", "oO c #D5BCAA", "o+ c #616B59", "o@ c #716355", "o# c #4E5D43", "o$ c #312A2A", "o% c #5C4E47", "o& c #65615D", "o* c #847461", "o= c #5A4E45", "o- c #9B8371", "o; c #D6B7A1", "o: c #181718", "o> c #2B4F31", "o, c #4D4C4C", "o< c #27292A", "o1 c #BFA491", "o2 c #DEC8B6", "o3 c #171517", "o4 c #C4A38C", "o5 c #4C4A4B", "o6 c #161516", "o7 c #4B4A4A", "o8 c #151515", "o9 c #C2A18A", "o0 c #C1A189", "oq c #383234", "ow c #373233", "oe c #9D8C80", "or c #927B68", "ot c #50463B", "oy c #555657", "ou c #353031", "oi c #BC9B84", "op c #444243", "oa c #0D0D0D", "os c #7E7472", "od c #878788", "of c #D2BAAA", "og c #968479", "oh c #5B4E49", "oj c #394238", "ok c #6E5F55", "ol c #A39489", "oz c #405E42", "ox c #465448", "oc c #586953", "ov c #3A3839", "ob c #737067", "on c #030303", "om c #292425", "oM c #947D6D", "oN c #726C66", "oB c #5D5958", "oV c #726A66", "oC c #4D4545", "oZ c #716865", "oA c #100F13", "oS c #C6AE9E", "oD c #343033", "oF c #D0AE94", "oG c #BB9B86", "oH c #392F2E", "oJ c #585849", "oK c #6B625F", "oL c #96867C", "oP c #453D3D", "oI c #5F5146", "oU c #2C2C2B", "oY c #6D6157", "oT c #49524E", "oR c #525643", "oE c #050708", "oW c #413B39", "oQ c #5F6A53", "o! c #BCA494", "o~ c #626256", "o^ c #50494B", "o/ c #2E4A30", "o( c #2A2429", "o) c #786D65", "o_ c #B8A290", "o` c #5B5959", "o' c #927B6E", "o] c #857B75", "o[ c #907B6C", "o{ c #847B74", "o} c #CBAF9C", "o| c #595557", "O c #51614F", "O. c #5D5451", "OX c #283A2A", "Oo c #2F5A34", "OO c #555153", "O+ c #B2B36C", "O@ c #655245", "O# c #5F6B60", "O$ c #2D2E2F", "O% c #665E5D", "O& c #4F4B4D", "O* c #5A6A51", "O= c #4C4B4A", "O- c #766D66", "O; c #C2A089", "O: c #333638", "O> c #C1A088", "O, c #C09E87", "O< c #645551", "O1 c #474545", "O2 c #1A2A1C", "O3 c #1F2021", "O4 c #837C6C", "O5 c #241F1C", "O6 c #D6BFAD", "O7 c #545355", "O8 c #BC9883", "O9 c #423F40", "O0 c #D4BBAB", "Oq c #446545", "Ow c #514F52", "Oe c #81746A", "Or c #7B736E", "Ot c #292C2E", "Oy c #3F3B3D", "Ou c #695F59", "Oi c #0D0702", "Op c #295231", "Oa c #CEB7A5", "Os c #3B3939", "Od c #151617", "Of c #927F74", "Og c #4F4C46", "Oh c #151417", "Oj c #7B6E64", "Ok c #6A5A50", "Ol c #A48E7F", "Oz c #464747", "Ox c #716964", "Oc c #424B43", "Ov c #A18C7C", "Ob c #414142", "On c #5E6E5B", "Om c #2F2D2D", "OM c #463C3D", "ON c #181C1D", "OB c #2E2D2C", "OV c #594F49", "OC c #070809", "OZ c #685D5B", "OA c #3B3B3C", "OS c #8C827B", "OD c #383D39", "OF c #B6A497", "OG c #A69284", "OH c #746D6A", "OJ c #5D6550", "OK c #8E7F73", "OL c #9F8F87", "OP c #736B69", "OI c #A39081", "OU c #7C7E7F", "OY c #373538", "OT c #30231A", "OR c #343335", "OE c #9F8C7D", "OW c #474241", "OQ c #AB9C8C", "O! c #68695E", "O~ c #AB988C", "O^ c #C6AA96", "O/ c #2D2B2E", "O( c #9E8572", "O) c #2C2B2D", "O_ c #5C5045", "O` c #2C292D", "O' c #72776B", "O] c #655F5B", "O[ c #2E4D32", "O{ c #A4A5A6", "O} c #C7A38D", "O| c #554948", "+ c #5A4A43", "+. c #272528", "+X c #877E79", "+o c #353739", "+O c #36353A", "++ c #504743", "+@ c #C29F88", "+# c #5C5D52", "+$ c #967B6A", "+% c #687361", "+& c #887970", "+* c #52443B", "+= c #A98A76", "+- c #464443", "+; c #211F22", "+: c #696B62", "+> c #716660", "+, c #211D22", "+< c #86776E", "+1 c #827D6A", "+2 c #2F2F33", "+3 c #434040", "+4 c #1D1D1E", "+5 c #1D1B1E", "+6 c #4D3C36", "+7 c #FFFFFF", "+8 c #514F47", "+9 c #4A504A", "+0 c #6A614F", "+q c #4C4A4C", "+w c #574A43", "+e c #151316", "+r c #393636", "+t c #141115", "+y c #707062", "+u c #212325", "+i c #786960", "+p c #4E4344", "+a c #525E55", "+s c #101111", "+d c #23492A", "+f c #363233", "+g c #385838", "+h c #444244", "+j c #A0897A", "+k c #796657", "+l c #CFAC91", "+z c #C9AB95", "+x c #81756C", "+c c #403E40", "+v c #76766B", "+b c #17191B", "+n c #2D2A2A", "+m c #A29489", "+M c #3C383C", "+N c #423738", "+B c #3A3A3A", "+V c #3A383A", "+C c #383A38", "+Z c #BBA291", "+A c #C9B4A2", "+S c #010102", "+D c #685950", "+F c #625854", "+G c #927D6C", "+H c #363236", "+J c #E5E5E5", "+K c #9D9A70", "+L c #0D0F11", "+P c #50634C", "+I c #3A2F30", "+U c #434246", "+Y c #221E1F", "+T c #A38876", "+R c #8C7766", "+E c #302E30", "+W c #757577", "+Q c #374C3A", "+! c #525C44", "+~ c #C5A994", "+^ c #486144", "+/ c #2D2C2D", "+( c #2C2C2C", "+) c #2D2A2D", "+_ c #5C4F44", "+` c #65625A", "+' c #C2A791", "+] c #877161", "+[ c #2A2A2A", "+{ c #6B5F56", "+} c #74706C", "+| c #4F5441", "@ c #6E6F70", "@. c #5A4942", "@X c #314234", "@o c #262826", "@O c #4D4949", "@+ c #5E6253", "@@ c #5C5B5B", "@# c #BCA18B", "@$ c #9B8E85", "@% c #7B7355", "@& c #C1A086", "@* c #29231F", "@= c #847B75", "@- c #AEB0B2", "@; c #585757", "@: c #C09E85", "@> c #5E5653", "@, c #C09C85", "@< c #988C82", "@1 c #B99D88", "@2 c #514139", "@3 c #555354", "@4 c #1F1E1F", "@5 c #525151", "@6 c #D4BBA9", "@7 c #312D2A", "@8 c #586D5A", "@9 c #C2A794", "@0 c #515446", "@q c #28562E", "@w c #191619", "@e c #3E393A", "@r c #99826F", "@t c #4D4B4C", "@y c #3D3939", "@u c #161616", "@i c #536B55", "@p c #D7D9DA", "@a c #C4A28C", "@s c #C3A28B", "@d c #161216", "@f c #5A5B5C", "@g c #967E6C", "@h c #4A4749", "@j c #756B66", "@k c #BC9F8E", "@l c #C0A088", "@z c #383534", "@x c #BFA087", "@c c #BF9E87", "@v c #454544", "@b c #6B6866", "@n c #444343", "@m c #D6BFAE", "@M c #2D5E36", "@N c #C4AB99", "@B c #BA9882", "@V c #7D7571", "@C c #413F40", "@Z c #6C635D", "@A c #436745", "@S c #473E3C", "@D c #9D8676", "@F c #4B594D", "@G c #403F3F", "@H c #7C7370", "@J c #0A0A0A", "@K c #586D5D", "@L c #181A1B", "@P c #9A8473", "@I c #8F827C", "@U c #695F5A", "@Y c #76736A", "@T c #727966", "@R c #96866F", "@E c #827162", "@W c #3F5F41", "@Q c #7B7065", "@! c #907F73", "@~ c #49494B", "@^ c #4F4647", "@/ c #383537", "@( c #8D7D70", "@) c #B99F8E", "@_ c #363535", "@` c None", /* pixels */ "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`+R+k@`@`@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`+$oMo%O(.M@`@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@..v.m.$o@+T dOW.b@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`+ X).D -o*ok+wo= X./X`@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@2o.+0X@@%@kOV.7@D+Do-o4 bO:@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`O_XCX6XU.q@R pXA =o[+z 'XmX$o9XP.U@`@`@`@`@`", "@`@`@`@`@`@`@`@` u@Q+xOu+F.[+iOl.B+~.2@1.6+6o/oG@s.{o<@`@`@`@`@`", "@`@`@`@`@`ot.V.GXJoL L 9Of@)o}.jO^ ^ ,+*o$.Xo>XYXX@gOt@`@`@`@`@`", "@`@`@`ohXK+1O] S.=OGOIo_XMOe & {.O.5 Q+;XromOXo#@ao0 H [@`@`@`@`", "@` .OL+KO+O4@jXDo!oe+&og@!X^XtoqX!oz+++[+5 i@o.!O} DOkO3@`@`@`@`", ".y+Xos $XxXpOa.N ZXdXNO@7+s@`@`@`", "OF@mO6O0O~+>OZo^XIX,@nO9OD m+B@yO/.KO* EoU@4+e R+g.J@xX>Od@`@`@`", "OF e # YO'@to, N cX_@G K@Co+O`@_+r.1@W.]X{oH.:X8Oo+=@cor+b@`@`@`", "@ CO=@T+3+VOR+C.k@e.@.C+, X0Xg@dXl@qX( (ooXG@`", " qOH.i.}X}oN fo5OA+%@OO1 U@/+PoP+/X#ow.9OJ+toA.n B JoR@B@:XyO5@J", " q+}.Ho`.' O.#@5+qOn.^OYXk..X=O. o+. )ON.+ k.o@rXZ+lX7+]+_@* @J", " qOrOwX]O#@Y@t+hXq@i@UX;+-Xz 7Og.;oW 2o'XioF %+GX4X @L@`@`@`@`@`", " q 3@@X|+aob._@5.Z. @>+2.` ao~ l+' A@#@P x gXL@`@`@`@`@`@`@`@`@`", ".d@V@;XTXH+v+UXS+O@FX3OK+Zo;o1+j.g@Z@`@`@`@`@`@`@`@`@`@`@`@`@`@`", ".dXeO7@~oT+:oK M GX5@9Ov@(O-@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`", ".d@H.E@I roO@NXv.-@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`", "olo2+AOE.e@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`", "Oj@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`", "@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`" };schismtracker-20250313/include/auto/schismico_hires.h000066400000000000000000001103421476471630300225500ustar00rootroot00000000000000/* XPM */ static const char *_schism_icon_xpm_hires[] = { /* columns rows colors chars-per-pixel */ "128 128 230 2 ", " c None", ". c #050505", "X c #0B0B0B", "o c #110F11", "O c #151315", "+ c #191719", "@ c #1C1B1C", "# c #191717", "$ c #211E1E", "% c #1B251D", "& c #25211F", "* c #211F21", "= c #242324", "- c #292525", "; c #292729", ": c #2C2B2C", "> c #2A2A26", ", c #312D2C", "< c #352D27", "1 c #283829", "2 c #34312E", "3 c #3B322C", "4 c #302F30", "5 c #343334", "6 c #3A3534", "7 c #3C3936", "8 c #383638", "9 c #3C3B3C", "0 c #343D34", "q c #2F3A30", "w c #1F2B20", "e c #433A34", "r c #423D3B", "t c #483C38", "y c #42382F", "u c #29452C", "i c #384837", "p c #375839", "a c #2F4E31", "s c #35643A", "d c #39663C", "f c #39683E", "g c #336237", "h c #44413E", "j c #4C423C", "k c #464B38", "l c #51453E", "z c #584A3E", "x c #45533D", "c c #50543F", "v c #65603B", "b c #413F41", "n c #3D5641", "m c #3C6A40", "M c #3E6341", "N c #444344", "B c #494543", "V c #4C4945", "C c #494749", "Z c #4D4B4B", "A c #454B45", "S c #534541", "D c #544A44", "F c #5A4C45", "G c #524D4C", "H c #5A4E49", "J c #594441", "K c #435442", "L c #4C5C4C", "P c #485847", "I c #54514E", "U c #5C524C", "Y c #595848", "T c #514F51", "R c #545354", "E c #595654", "W c #555A54", "Q c #5C5A56", "! c #5D5B5A", "~ c #595758", "^ c #664D4A", "/ c #63544C", "( c #6A534C", ") c #6B5A4E", "_ c #64574B", "` c #635652", "' c #6C5653", "] c #655B55", "[ c #6B5C53", "{ c #625E5C", "} c #695E59", "| c #735C58", " . c #795E4F", ".. c #426E46", "X. c #436E48", "o. c #49664A", "O. c #46724A", "+. c #49744C", "@. c #467448", "#. c #536353", "$. c #596958", "%. c #4E7952", "&. c #4E7252", "*. c #527B55", "=. c #557D58", "-. c #58775A", ";. c #526D4E", ":. c #797549", ">. c #6C6155", ",. c #63615E", "<. c #6C625C", "1. c #636B5D", "2. c #686858", "3. c #736155", "4. c #73655C", "5. c #75695D", "6. c #7B6A5D", "7. c #796757", "8. c #7D725A", "9. c #6E6A4A", "0. c #666462", "q. c #6B6663", "w. c #6B6965", "e. c #6D6C6A", "r. c #646E61", "t. c #736661", "y. c #746A64", "u. c #7B6C64", "i. c #736D6B", "p. c #7A6D69", "a. c #7C6663", "s. c #657763", "d. c #7D7166", "f. c #73726C", "g. c #7C736C", "h. c #78786B", "j. c #747372", "k. c #7E7671", "l. c #7F7873", "z. c #7B7B7B", "x. c #5E7660", "c. c #846A5A", "v. c #837A56", "b. c #836E65", "n. c #847265", "m. c #8B7465", "M. c #82756C", "N. c #84796D", "B. c #8C7A6C", "V. c #8A7968", "C. c #937C6C", "Z. c #937767", "A. c #827A74", "S. c #8B7D73", "D. c #887673", "F. c #937F73", "G. c #5C835E", "H. c #578057", "J. c #62865D", "K. c #5D8361", "L. c #5F8861", "P. c #638964", "I. c #678B6A", "U. c #698C6C", "Y. c #658567", "T. c #6C916D", "R. c #769775", "E. c #6F8E6F", "W. c #88874A", "Q. c #92914F", "!. c #B1B153", "~. c #8D886B", "^. c #96846D", "/. c #8D827A", "(. c #8D8176", "). c #948275", "_. c #9B8474", "`. c #93857B", "'. c #9A8679", "]. c #9B8A7C", "[. c #958A79", "{. c #95986B", "}. c #A1846F", "|. c #A48975", " X c #A48C7B", ".X c #A98E7A", "XX c #A38772", "oX c #AB917D", "OX c #A6907D", "+X c #B5957E", "@X c #A6A56B", "#X c #B8B872", "$X c #C2C259", "%X c #CCCC63", "&X c #C6C676", "*X c #D4D47A", "=X c #D1D177", "-X c #968A82", ";X c #9B8D83", ":X c #948780", ">X c #9E9086", ",X c #9F9188", " 9 ", " e 3.7X0X0X8X8X8X0Xj b b i ", " l Z.0X8X0X0X0X8X8X8X8X3.r N N 9 9 9 ", " 2 / oX0X0X0X0X0X8XOX) F 8X8XoX2 N N b b 9 ", " t 7.8X0X0X0X0X0X8XC.F - @ e 8X8X0Xj N 9 b 9 9 ", " D C.eXeX0X0X0X0X7X7.e $ * z V.0X7X0X7X[ 9 n 9 b 9 8 ", " 3 ` b._ 7XeXeX8XeX^.:.k $ < ) oX8X8X8X8X8X8XoX2 b b 9 9 8 ", " j 3.6.Y H D V.8XeX:.:.!.!.v c.8X8X8X0X0X8X8X8X8X8Xj b b 9 9 9 ", " F 6.4.H H z .B.oX:.W.$XQ.:.Z.8XeX8X0X7X8X8X0X8X8X8X8X[ 9 b 9 9 8 8 ", " 6 ) n.[ H U U 7.|.Z.:.Q.!.W.:.oX8X8X8XeXc.^ ) |.7.F 8X8X8X8X.X2 n 9 9 0 8 ", " j 6.u./ Y U _ n.7X7.J.!.!.:.v.eX0XeXeX.X3.D l l l j l D .X8X8X8X8Xj r 9 9 8 0 ", " U n.5.U U U 4.F. X9.W.$XQ.:._.0X0XeX0XV.) l z D l D l ) V.7Xn.U |.8X8X) 9 9 9 9 8 5 ", " y [ M.} Y U ] u.2XV.:.!.!.v.:.0XeX0X0X6Xu.U V D D D l F 5.OX|.3.z j j 3.8X8X.X2 9 9 0 5 5 ", " j u.M.] ` / >.V.9X9.W.$XQ.:.C.eXeX0XeX_.>.D D D z z V ) V.8XV._ j l j _ m.7X7X8X8Xt i 9 8 8 8 ", " U V.u.Y ` ] 5.'._.9.Q.$XW.:.7XtXtX0X0Xn.] D D F D D H 4..XoX6.F S l D 3. X8X8X8X8X8X8X[ 7 9 7 8 5 5 ", " e <.B.5.` Q } n.9Xd.:.!.!.v.V.eXeXiXeX0X4.H H H H H D [ V.0X0X^.l D l _ m.8X8X8X8X8X0X7X8X8XoX< 9 9 5 5 5 ", " D u.M.} ] ` 4.).OX8.W.$X%Xv..XiXeXiXeXeXeXeXH H H H U 4. XeX0X0X0X7XD 7.|.8X0X8X0X8X8X8X8X8X8X8X7Xt 9 8 5 5 4 ", " / B.d.] } ] d.1X(.U Q.%X%X%X%X:.eXiXeXeXeXeXeXeX` D [ V.0XoX|.0X0X0X0X0X8X0X0X0X0X8X8X8X8X8X8X8X7X7X8X) 5 9 0 5 5 5 ", " e 4.S.y.] ] <.B.eXV.W.Q.k _ k !.W.8.eXeXeXtXeXiXeXeXiXV.OXeXV.} D 4.eX0X0X0X0X0X0X0X8X0X8X8X8X8X8X8X7X8X+X8X.X2 8 0 5 q 4 ", " D M.N.<.} } u.].].u.).W.%X%X%Xv >.'.tXeXiXiXiXeXeXeXeXeXiX6X6.Y F D 3._.eXeX0X0X0X0X0X0X8X0X8X0X8X8X8X8X8X8X8X8X7Xh 7 8 5 4 4 ", " ` ).g.} <.<.V.2XB.<.] } S.d.%XQ.v.rXtXpXtXiXtXeXiXeXiXiXeXiXeX_.D ` n.6X0XeX0X0X0XeX0X0X8X0X0X8X8X8X8X8X8X8X8X8X8X8X7X) 5 5 5 5 4 : ", " h 5.).u.,.>.y.`.2XM.>.,.} ] ] M.N.%XV.iXiXtX Xn.eXiXiXeXeXeXeXeXeXeXeX_.eXeXeXeX0XeX0X0X0X0X0X0X8X8XC.OX0X8X8X8X8X8X8X+X8X7X.X: 0 5 4 5 4 ", " H B.S.q.<.<.M.1X].u.} } } ] ] ] } y.2X:.].eXV.>.` Y iXtXiX0X0XiXiXeXiX0XeXeXeX0X0XeX0X0X0X0X8X8X8XXXc. .c._ c Y eX+XeX+X8X8X+X8X+Xt 6 5 4 4 4 ", " 3 ] `.N.<.<.t.(.9X(.4.<.} } } <.] } ] ] ).iX].u.] ] U >.B.iXiXiXiXiXeX0XiX0XiXeXeXeXeXeX0XeX0X0X0X7Xm.c.c. .^ k *.H.x +XeX+X+X7X8X8X8X8X) 2 5 4 4 4 4 ", " j y.[.p.<.<.p.'.aXpXy.<.<.<.,.>.{ ] } } ] <.B.<.] ] ] u.1XiXeXeXiXiX0X0XiXiX0XiX0XeXeXeXeX0XeX0X0XF.m.c.c.( t b .o.@.@.p +XeX+XeX8X8X8X8X7X.X> 8 5 q 1 : ", " U S.(.y.t.<.S.4X4Xy.iX1X5.<.} <.B.t.] } } ] ] ] ] ] 5.B.eXiXtXiXiXiXiXiXiXiX0XiXiX0XiXeXeX0XiX0X2Xm.c.c.c.^ t ( ( t , 1 m m f 8.+XeX+X8X+X8X+X8X7Xe 5 4 4 4 : ", " 6 } '.A.t.q.p.`.uX'.} N G pX4.,.d.'.pXpXn.<.} ] >.] } n.2XpXiXiXiXiXiXiXiXeXeX0XiX0XeXeXtXeXeXeX0XC.c.c.c.( t ^ ( ^ y - + # & f m m Y 8X8XeX8X8X8X7X+X8X) 2 5 4 4 : : ", " j n.`.N.t.t.N. 4 4 4 : : ", " Y ).`.f.y.y.(.rX1Xy.C Z y.2XaXaXaXuX).M.].u.] <.} ,.M.2XpXpXpXpXiXpXpXiXiXiXeXiXeXeXtXiXiX0XtXeXeXC.c.c.( l ^ ' J 6 * + $ + $ @ # # $ u m m f ^.8X8X8X+X+X8X+X8X+Xe q : 4 : > ", " 6 <.;X(.y.y.g.;XuX`.{ B E M.uXaXaXaXaXaXM.<.<.,.<.<.d.).iXpXpXpXpXpXtXiX].rXiXiXiXiXiXiXiXeXiX0X Xm._.8X/ J J | ^ t , * * * $ $ $ $ # $ # $ q @.%.@.Y 8X8X8X8X8X8X8X+X8X) 4 4 4 : : > ", " V M.;XA.y.i.A.5XqXM.I C N N '.aXaXaXaXaXaXaXB.<.<.q.B.4XaXpXpXpXpXpXaX0XS.<.] F.iXiXiXiXiXiXeX7XD.c.c.7.Y D .X/ S 6 ; * * * * * * $ $ @ * - 5 N 7 ....f x 8X8X8X8X8X8X+X8X+X.X< 4 4 : ; ; ", " U `.[.g.p.g.`.uX>Xy.G C C N G d.rXaXaXaXaX'.1XaXS.d.'.pXpXpXpXpXpXpXpX Xd.] ] ] d.9XiXiXeXiXiXF.b.c.c.' J #.J.G.n.S = * = = * * * * * * = 4 t h 5 ; + a f m p OX8X8X+X8X+X8X8X8X+Xe 2 1 : : : ", " e q.1X/.g.y.A.;XrX~.,.Z C C C ,.S.yX;X`.sX4XS.t.<.`.aXpXaXaXaXpXpXpXaXtX(.5.] ] t.).tXiXiXiXeX2XZ.b.m.c.( H | ' P %.@.>.[ = = = * * * * * 4 r B 8 , $ # + + 1 m m m 2.8X8X+X8X+X8X+X8X8X_ : 4 : : = ; ", " D N.>XD.f.g.(.rX1X~.@X*XW.5.T p.1XuX`.p.t.y.d.q.<.S.4XaXaXaXaXpXpXaXpX1XM.} ] } n.2XpXtXtXpX0XF.m.m.c.( F | | H r : 0 +.+.L n.= = = = ; 4 9 S b 4 = @ @ # @ + + * m m m c 8X8X8X8X8X+X8X+X+X.X> 4 : : > ; ", " ` 3XqXk.g.A.-XyX^.{.&X=X=X=X@X~.yX5XS.y.y.y.y.q.d.[.aXaXaXaXaXaXaXaXtX).y.>.{ y.).iXpXpXiXiX ; = ", " e y.uXgXgXqXS.5X4X~.@X=X*X*X&X{.[.aX;Xg.y.y.y.y.t.D.3XaXaXaXaXaXaXaXaX1XN.<.<.<.B.2XpXpXpXpXpXiX6.Z.b.' F | | H r : : : : ; ; : ..O.o.V.V C V 9 4 * * $ $ $ @ @ @ + @ @ # @ u @.%.O.7.8X+X8X+X8X8X8X+X8X_ : : > > ; % ", " D S.jXgXfXfXgXjXuX{.#X*X*X*X*X*X~.sX : ; % = ", "g.hXhXgXgXhXwX:Xg.k.h.rX#X*X&XR @X*X*X{.fX`.p.`.qXfXsXaXfXaXaXaXaX = w = ", "A.hXgXjX3XA.k.k.k.k.k.uX#X*X&XR {.*X*X^.fXuXaXjXdXdXsXsXfXaXfXaXaXM.y.S.qXaXaXaXpX5XF.b.C.a.} ' a.] P.=.#.n.4 5 4 4 4 : 4 5 7 V R B 8 : ; : O.+.o._.7 = * * * = : t b b 8 , * + $ + + + w m m f Y 8X8X8X8X+X8X+X+X7XXX> > : = = % ", "N.jX-Xl.A.k.k.k.k.g.j.yX#X*X&XR {.@X~.rXfXsXjXsXdXdXsXsXaXaXfXaXaX9XaXaXaXaXuX).B.Z.m.| ' a.a.H B 5 &.=.L ).5 4 5 , 5 9 C I V 9 , : > ; ; ; M +.O.6.H = = : 7 V b b 4 * $ * + * + + + + % f f f x +X8X+X+X8X8X+X8X+X6X7 > : = = = @ ", "A.uXl.A.A.j.k.k.k.k.g.yX#X*X&Xi.).sXfXsXsXfXaXfXdXdXsXsXaXsXaXaXsXaXaXaX2XF.m.C.a.' t.b.} Z 8 8 6 8 L =.*.'.8 5 9 N I G h 8 : : : : ; ; ; * n +.O.Y a.7 B D r 5 ; * * @ $ + + + + + + + # a m ..s +X8X8X+X8X8X+X+X7X6X/ = > = = % = ", "A.yXj./.,X;Xl.k.k.g.g.yX~.^.4XfXfXfXfXdXfXsXsXsXdXdXkXsXaXaXaXaXaXaXaXaXn.n.4.} b.t.T b 9 8 8 8 8 8 P =.=.B.I I E C 9 4 4 : : : : : ; ; ; ; i O.%.Y '.h 8 : $ * * @ * * @ * + + + + # @ - i %.+..._ eX+X+X8X8X8X8X7X+X|.- : = = * * ", "g.jXrXkX`.k.k.A.A.h.A.yXaXfXfXfXdXdXfXfXsXfXsXfXdXdXsXsXfXfXaX1XF.n.'.a.} a.a.` C b 9 8 9 9 8 8 8 5 K G.J.N.<.N 8 4 4 , 4 4 : : : ; : ; : 8 k G.H.o.oX2 = * * * * * * + @ @ + + * ; 6 r h 0 m m d c eX+X8X+X+X+X+X7X+X8X3 > > = = @ * ", "N.gXhXuXA.>XkX3X/.4XfXfXjXfXfXfXfXfXdXdXfXdXsXfXdXdXdXdX9X'.D.F.u.r.E.].a.T N b 9 9 9 8 9 8 7 8 n C W P.G.2.5.4 4 4 4 4 : 4 : : : : 8 h G V i +.+.@.u.D = * * = * @ * @ @ * - y b b e ; $ O p f s p +X+X8X8X8X7X8X+X+X+X_ > = = = @ @ ", "k.gXhXgXgXgXgXgXfXhXfXfXfXfXfXfXfXdXfXfXdXdXsXsXdXaX1XF.F.Z.a.t.h.R.U.S.} b b b 9 b 9 9 9 r C W E Z B =.=.#.M.5 4 4 4 : 4 : , 8 N G G h 6 : = K +.@.>.| = * * * * * * ; e b b b < * + + # # u f d s 8.8X+X8X+X7X+X8X+X7XXX= > = % $ @ ", "N.gXhXgXgXhXhXfXhXfXhXfXfXfXfXfXfXfXjXdXfXfXjX9X % = @ @ @ ", "g.gXhXgXfXhXfXhXfXfXfXfXfXfXfXfXdXfXfXdXqX 4 &.=.H.y.4.: @ = * $ @ $ * @ @ + @ + + + + # - h *.H.*.z +XiX+X+X+X7X+X7X+X+X7 & = * % @ @ ", "g.gXhXgXgXgXfXgXhXfXgXfXkX1X:XF.S.h.s.rXp.} T C b Z C C b C b C G s.T.s.4XN 9 9 9 r 9 9 9 8 8 8 8 b C #.P.J.2.u.4 4 4 : 4 : : : : : ; ; 5 7 B Z o.%.@.Y n.= * * * @ * @ @ @ @ + + + $ - y ^ ^ ^ %.@.m p +XeX+X+X7X+X7X+X+X+X/ % * % @ @ # ", "g.hXhXgXhXgXgXfXfXfXwX > O.+.O.5./ * * * & 3 b ^ ' ( J y - + @ # # O O w s f f c eX+X+X7X+X8X+X+X+X+X^ @ % @ @ + + ", "k.gXgXgXjX/.D.p.D.D.i.{ R T T T W U.U.-Xq.R ! e.0.~ Z C b C C b b P K.K.S.q.S R { ~ Z b b 8 8 8 8 8 8 5 L =.=.B.H b S T H b 8 4 : : ; ; ; ; * ; * P +.O._ 6.= 6 t ' ' ' b y * $ + + + + O + O O + s f d p +X7X+X+XeX+X+X+X+X+X|.w % @ # # O ", "k.gXgXwXyXa./.z.e.~ R R R T T T R U.T.-Xl.w.! R C C C C C b C b b P P.P.A.D.{ T b b b 8 8 9 8 8 8 8 5 5 n H.H.g.a.T N 8 4 : : : : : ; ; ; ; ; ; * n O.+.Y .X/ ' ' S e * $ $ + + + + + + + O O O O u f f m 9.8X7X+X+X+X+X+X+X+X+Xy $ @ @ % O # ", "p.5XD.D.wXq.,.R R R R R T T T R #.R.R.`.g.Z C C C C C b C b b C Z ~ T.T.g.B.b 8 b b 8 b 8 9 8 8 8 4 8 b L P.L.2.d.5 4 4 4 4 : : : ; ; ; : ; ; < : B =.J.;. XF 6 - @ @ * $ $ + $ + + + + # O O @ = 0 @.@.@.k +X7X+X+X+X+X+X+X+X+X^ @ $ @ O + # ", "p.l.z.D.wX,.R ~ R R R R R ~ 0.e.0.E.U.(.k.C Z C C C C C C Z R ,.w.~ P.P.w.(.b b b 8 b 8 9 8 8 r b G Q ~ P H.=.W S.4 4 : 4 4 : : : : : ; * , t b | ` J.=.*.b.F * * * * @ $ $ + + $ + + + $ > 5 t r i ....d i 8X7XeX+X+X+X+XeX+X+X}.& % # # # # ", "p.l.A.A.wX{ R ~ R R ! 0.j.e.! R T I.U.z.D.C Z C C C Z R ! 0.{ R C n -.x.2.].9 b r 9 9 9 b b G ~ ! Z b 8 9 *.*.P 1X5 , , , : 4 : : ; 4 t D | ' ' b e X.O.O.) 5.% @ * @ * @ @ @ + + * , 6 r r r , & # a d s p ^.+X+X+X+XeX+XeX+X+X+X3 @ @ # # O O ", "p.A.A.l.5X0.R ~ 0.e.j.0.~ T R T T x.U.l.S.C C T T { q.0.~ T b b b N -.Y.$.rXn 8 9 9 b Z ! ~ G b 9 6 5 5 5 X.=.&.'.r 4 4 , , : 5 b F | | | b t ; - = p O.O.D _.* = @ * @ @ * ; , t t r r , = # o O o u d s s Y +X+XeX+X+X+X+X+X+X+Xz # % # # O O ", "p.l.A.l.5X0.e.j.e.{ R R T T T T Z s.U.h.`.R ! w.q.~ T C b C b C b n #.K.$.rXC C T { { T b b 8 8 8 8 8 5 5 L *.*.N.H 4 : 5 b ^ t.| ' S 8 , * : ; - * i O.O.p 6X2 $ @ = : 8 h j h r : * + + O O + O O % d s s x +X+X+X+X+X+X+X+X+X+X|.& # # # # O ", "p.l.A.A.5X0.0.~ R R R T T T T T T r.E.E.;Xw.! R C C C N C b Z b b N #.K.s.2X} { T b b r b 8 8 8 8 8 8 4 8 K *.=.y.| b / t.| ' S t 4 ; ; ; ; * : - ; q +.X.O.n.D , 7 B B h 7 : = + + @ + + + + o O O O g s s p .X+XeX+X+X+X+X+X+X+X+X3 @ # # O O o ", "p.l.A.A.5X0.R ~ R R R T T T T R ! U.R.h.>XZ C C b T b Z b b b b b Z $.T.T.`.0.N 9 b 9 8 9 8 8 8 8 8 5 5 5 A =.G.y.B.6.' S 9 4 : : : : ; ; ; ; - ; * > O.O.+.4.u.B B 6 : = @ # @ # + + + + + O O o o O u s s f _ +X+X+XeX+X+X+X+X+X+Xz @ # # O O o ", "i.l.l.A.5Xw.~ R R R R R ~ ! 0.j.0.s.U.e.3XC Z Z Z T Z b b T Z ~ { 0.$.P.K.S.q.9 9 9 8 9 8 9 8 8 8 5 4 r G ` E.U.2.).9 , 4 : : : ; : ; ; ; : = ; ; 4 6 &.H.H.Q ^.: = @ @ @ @ @ + @ + + + O O + + O O O 1 f @.@.i +X+X+X+X+X+X+X+X+X+XXX$ % # O O O ", "p.l.A.A.3Xw.R R R ~ ! 0.j.0.,.R Z x.U.s.qXC C C b b Z b ~ { q.,.T C T K.K.N.u.9 9 9 9 9 9 8 8 8 r G ' a.6.] J.=.P 2X8 : : 4 : : : ; > ; ; : 5 7 Z Z Z o.+.O.x 8X: @ * * @ @ @ @ + + # + + O O # * : 6 0 @.@...a +X+X+X+X+X+X+X+X+X+X+X< O O O O o X ", "p.l.A.l.3Xe.R ,.0.j.e.0.R T T T T $.U.r.yXT C Z G ~ 0.e.{ R b b b b Z K.K.y.A.b b 9 9 9 9 b T | a.a.' Z 8 5 ;.*.&.).h 4 4 : : : : : : 8 h V V Z r 6 < 0 O.X.m ^.j * @ @ @ $ + @ @ + + + $ - 5 r h e 7 - p d s g 8.eX+X+X+X+XeX+X+X+X+Xc O # # o o X ", "p.l.A.A.3Xe.j.e.0.! R T R T T T T $.U.x.yXR E ,.e.{ ~ R b T T b b N A K.G.2.`.9 9 9 b T | b.a.] G 9 5 5 5 5 L *.=.M.U 4 4 : : 5 9 B G G Z r 8 : = = = q O.X.X.5.) @ * @ @ @ @ @ @ = , e r h r 2 = @ O o 1 s s g c +XeX+X+X+X+X+X+X+X+X}.$ # O O o o ", "p.l.A.z.wX0.,.~ R R R R T T T T T $.U.s.yXi.,.E R T b b b b b b N b N P.K.1..: 8 r V R G Z 7 5 : = = ; = = = > O.O.X.Y m.@ @ * @ = > 5 r b h r , = @ O O O o O % s s s p +X+X+X+X+X+X+X+X+X+X+X< # O O o o X ", "p.l.A.A.3Xe.R R R R R T T T T T ~ s.R.U.qX{ C C T b T b T b b b N b r -.P.2.rXa.a.' G b 8 8 8 8 5 8 5 5 5 5 n =.=.2.M.V R I Z b 5 : : ; ; ; = ; ; = = = p O.X.K 9X= = 2 7 B j h 7 : = # O + O O o O o o o g g g g Z.+X+X+X+X+X+X+X+X+X+Xz # # O o X X ", "p.l.A.D.3Xe.~ R R R R R R ! 0.e.w.0.T.Y.3X,.Z C b T b T b b b b b G ' Y.R.s.rXE b b 9 8 9 9 8 8 8 5 5 5 5 8 A K.G.2.'.C r 6 : : : : : ; ; ; ; ; = = = = i X...o.|.U B B h 6 : * + + + + + + O O O o O o o u s g g Y +X+X+X+X+X+X+X+X+X+X^.% O # o X o ", "p.l.A.A.3Xi.R ~ R R ! 0.j.e.! R T #.I.I.,X0.Z C C C C C b b R ~ a.b.p.r.P.-.1XR 9 9 9 8 9 8 8 8 8 8 8 h Z E W P.G.L 8X8 : : : : : ; ; ; ; ; ; = = = = : i *.H.=.d.>.5 - $ @ @ @ + + + + # O O O O o o O o w g g s i +X7X+X+X+X+X+X+X+X+X+X< # o o o X X ", "i.k.l.A.3Xj.~ ! 0.j.e.,.R T T T T W I.I.-Xe.Z C C C C T { a.D.b.{ T N #.K.K.`.] 9 9 9 9 8 9 8 r C G Q Q I C 9 *.*.X.).B 4 4 : : : : : ; ; ; ; ; 4 7 B V V =.%.+.>.6.* @ @ @ @ + + + + + O + O O O O O o # w ..@.@.p .X+X+X+X+X+X+X+X+X+X+Xz o # o o X X ", "y.l.A.A.3Xe.f.f.0.~ R R R T T T T W U.I.:Xp.C Z T } a.D.b.{ R Z b N b W K.K.B.<.9 9 9 9 N C ~ Q ! G N b 8 4 5 o.*.*.M.U , : : : ; ; ; - : 6 9 Z G V C 8 2 ....X.K .X= $ @ @ # @ @ + + # + + O # # @ - 3 y e s ..f d 9.+X8X+X8X+X+X+X+X+X+X}.# # o o X X ", "y.l.A.l.3Xe.! R R R R T T T T T T W U.I.:Xl.0.p.D.p.} T Z b b b N N b L K.K.h.d.b C G ~ ! ] G C 9 8 8 5 6 5 4 P *.*.<.4.4 : : : : 5 9 B G G V h 8 : = * = p ..X.p 7X6 @ $ $ + @ + + + # + @ - , r 7 h 7 > & u g g s c +X+X+X+X+X+X+X+X+X+X+X< o o X X X X ", "p.l.A.A.3Xg.R ~ R R R T R T T T Z R E.R.-X:Xp.{ T Z Z b Z b Z b b N b P K.K.h.S.{ ,.! G C 9 8 8 8 5 8 5 5 4 5 A *.*.] n.: 5 8 h V I I C r 5 : = * ; * ; * a O.m ..n.H $ + @ + @ @ @ = , 7 h t r 2 = @ O o o % g g g a +X+X+X+X+X+X+X+X+X+X.Xz o O X X X X ", "p.l.A.A.,Xg.R ~ R R R R T T T ! q.g.{.R././.C T Z b b Z Z b b b b N B W I.U.h.'.G C 9 9 9 9 8 8 8 8 5 5 5 5 5 i *.*.L ].B I I I B 9 5 : : = = - ; ; ; * * q X.O...>.3.@ $ $ = : 6 h h h 7 : = + O O O o o o o a s g g 8.+X8X+X+X+X+X+X+X+X+X}.# O X X . X ", "y.A.A.A.3Xk.R R R R R R { t.D.D.p.{ E.I.A.`.C C Z Z b b b b b Z R ! ,.,.I.P.1.1X9 9 9 8 9 8 8 8 8 5 8 5 5 5 5 N G.G.#.0XI j 9 5 : : ; ; ; ; : = = * = = = > O.X...Y '.: 2 r N B r 5 : $ # # o + O o O o o o o 1 g g g c +X+X+X+X+X+X+X.X+X+X.X< X X X X X . ", "y.A.z.A.,Xl.~ R ~ { i.D.D.p.0.R T Z I.U.h.`.Z C b Z Z Z R ~ 0.q.,.~ G A K.K.#.yXb 9 9 9 8 9 8 8 8 8 5 8 r Z I E K.G.*.).V : : : : : ; : ; ; ; = ; = = = = * M @.@.P 7XD B r , = @ # + + # O + + O O O o o o o % g g g a +X+X+X+X+X+X+X+X+X+X+Xz o o X X X . ", "e.D.l.D.,Xj.0.e.D.D.i.0.~ R R b T Z Y.U.j.>XC C C R E ,.q.,.~ Z C b b b -.K.;.rXZ 9 9 9 8 8 8 8 9 C G E Q U Z b o.*.%.n.] : : : : : - ; = ; = = = = = ; 5 7 o.H.H.%.^.F * * @ @ + + + + O + o o O o o o o o o O s g g u ^.+X+X+X+X+X+X+X+X+X+X}.# o X X . . ", "a.l.l.z.,XD.D.j.q.~ R R b ~ R ~ T Z s.U.s.,XW ! w.w.0.E Z C C N b b b b &.K.-.>XI 9 9 9 9 N Z R ! Q I C i 8 4 5 P *.%.4.4.4 : : : ; : ; : - ; : 5 7 N V V C x +.@...2.>.@ @ + @ + + + + + + + O O O O o o o o o 1 x _ ^.+X+X+X+X+X+X+X.X+X.X+X.X< X X X X . . ", "y.l.k.D.,XD.~ R R R R R T T R T T T s.T.h.rXw.0.E I C N C C N N b b b b #.K.G.).} N C I Q ! Q I C r 5 5 5 5 4 4 n =.*.Q M., : : : : ; ; 2 8 r Z V V B 7 : = : O.X...Y C.@ $ @ + + + + + + O O O O O o O - t ) C.8X+X+X8X+X+X+X+X+X+X+X+X+X+X+X+Xz X X X X . . ", "i.l.D.z.,XD.~ R R R R R T T R R ! 0.E.R.h.yXT C C C C C C N N N C b b n W K.G.(.d.{ { E Z B 9 8 8 5 8 5 5 5 5 5 n %.*.L X4 : : : 8 r C G G Z r 8 : = = = * w O.X...x 0X; @ @ @ @ + + + O + + O $ y / m.+X+X8X+X+X8X+X+X+X+X+X+X+X+X+X+X+X+X+X+X^.O X X . . . ", "a.l.l.A.,XD.R R R R R R ~ { e.e.e.,.s.U.s.aXR C C C C C N N N N b b b T $.T.T.A.A.Z N 9 9 8 8 8 8 5 5 5 5 5 5 4 8 %.%.K 9Xh 9 B I G I B 9 5 ; - = = = * = * * p X.X.f XXt * @ + + + + + $ y ^ u.+X8X8X8X+X+X8X+X8X+X+X+X+X+X+X+X+X+X+X+X+X^._ y & O X X X X . . ", "y.l.A.l.,XD.~ R ~ ! 0.e.j.e.,.~ T Z x.I.r.aXE C C C C C C N N C T ! 0.0.$.I.P.y.S.b 9 8 9 9 8 8 8 8 8 5 5 5 5 5 5 =.=.*.).] I I r 7 : ; ; = = ; - = = = = * = i X.....8.] @ @ + $ 6 H a..X8X8X8X8X+X8X+X8X8X8X+X+X8X8X+X+X+X+X+X+X}.v y - # O O o X X X . . . . ", "y.l.A.A.,XD.! e.j.j.e.! ~ T b ~ T Z $.U.x.yX{ C C C C C G R { q.q.{ T V P x.G.2.'.9 9 9 9 8 8 8 8 5 8 5 5 5 7 B Z -.K.L.V.<.5 4 : : ; ; ; ; = ; = = = = * * * q X....._ 6., F 7.XX8X8X8X8X8X8X8X8X8X8X8X8X+X8X+X+X+X+X+X+XXX .y < $ # # # # # O o o X X X ", "y.A.A.l.,Xj.j.e.{ ~ R R R T R R T Z r.U.x.rX{ C Z R W 0.q.,.~ T C b b b A K.G.,.4X9 9 8 9 9 8 8 8 8 9 N C R E ~ G ;.=.%.<.a.: : : : ; ; ; ; ; = ; = = = = * * : ..d P ` m.0X8X8X8X8X8X0X8X8X8X8X8X+X8X+X8X8X8X+X8XXX) j < - $ $ $ $ $ $ # # O O o ", "w.A.A.l.>Xl.~ R R R R R T T T T T T $.I.s.4Xi.{ w.w.0.R T C b N N N b b N K.K.#.yXh 9 9 9 8 9 h C I E E E C h 8 4 A *.&.Q S.: : : ; ; ; ; ; = ; ; = = * = : j ] 8.+XeX8X8X0X8X8X0X8X0X8X8X8X8X8X8X8X8X7X8XXX) l 3 > > : > > = = w * @ @ @ ", "y.A.A.A.>X/.~ R R R R T T T T T T T r.E.E.3XA.$.E T C C N N C N b N b b N =.K.#.rXV 9 N C I E ! ! I C 9 7 5 4 5 4 n *.&.P 2X4 : : : : ; ; ; ; ; = : t ) M.7X0XeXeXeX0XeX0X0X0X0X0X0X7X8X8X8X8X8X8XXX[ S 3 : 5 5 4 4 4 : ; ; > = * ", "t.z.A.A.>X/.R ~ R R R R R T R ~ 0.e.e.R.E.;Xh.C C C C C N C b N r N N 9 b -.=.-.].,.E { ! E C N 7 5 8 5 5 5 5 5 4 i %.%.K 9Xr : : : : ; ; ; e _ b.2X0XiX0XeXeX0X0XeX0X8X0X8X0X8X8X8X0X8X8X|.' S e 2 8 b 9 9 5 5 5 4 4 : 4 ", "t.A.A.A.>X/.~ R R R R ~ ,.0.j.e.0.! $.I.U.[.k.C C C C N C b C N N N N N C -.I.U.`.u.I C N 9 8 8 9 8 5 5 5 5 5 4 4 5 *.%.X.F.D : : : 8 / u. X0XiXeXeXeXeXeX0X0XeX0X0X0XeX8XeX0X0X0X|.| D e 6 n N N h i 9 9 0 5 8 5 ", "t.A.A.A.;X:XR ~ ~ 0.e.j.e.0.~ R T T W U.I.:XA.C C C C C b b C b C Z E ! ,.s.I.Y.N.u.9 8 9 8 8 8 8 5 8 5 5 4 5 4 4 4 &.%.%.y.` Y 4.'.eXeXeXeXiX0XeXeXeXeXeX0XeX0X0X0XeX0XeX].3.D e 7 C I Z Z A A N b n 9 9 ", "t.A.l.A.;XA.e.j.j.e.0.~ T T R T T T ~ I.U.z./.C C C C C C T R { 0.0.! E Z #.G.G.d.M.9 9 9 9 8 8 8 8 5 5 5 5 4 5 5 4 P ;.2.n.0XiXiXeXeXiXiXeX0XiXeXeXeX0X0X0X0XeX0X X3.H h 9 I W R R T Z Z C C N N ", "q.S.A.A.;Xz.0.,.~ R R R R T T T n T R I.U././.C C Z R E 0.q.q.~ R C N b 9 L K.G.w.(.9 9 8 8 8 8 8 8 5 5 5 4 8 G 5.B.2XtXtXtXiXeXiXeXeXeXeXeXeXiXeXeXeXeX0X Xt.T B b R $.! ! ~ ~ R R T T Z ", "q.A.A.z.;X:XW R R R R R T T T T T T R I.U.z.-X~ 0.w.q.,.~ Z C N b N N b b P K.G.2.'.9 9 9 8 9 8 8 5 8 V } B.4XiXiXiXiXiXiXiXeXiXeXiXeXiXiXeXeXeXeX X4./ B C ,.e.0.0.,.{ ! ! ~ W ", "t.A.A.A.>X-XW ~ R R R R T T T T T Z W E.R.(.3Xw.! R C C C N N N b N b b b Z K.G.$.9X9 8 9 8 7 C } b.1XpXpXiXiXpXiXiXiXiXiXiXeXeXiXeXeXeXeX X6.Y C C w.e.e.e.0.0.0.0.,.,. ", "5.z.A.A.;X-XR R R R R R R T R ~ $.w.e.R.E.h.3XZ C C C C N N N N N N b b n A K.G.#.iXN N ] M.].pXpXpXpXpXpXpXpXiXiXiXiXiXiXiXiXiXiX1Xu.` V G e.z.j.j.f.f.e.e.e.e. ", "5.A.A.A.-X;X~ R R R R ~ ! 0.j.e.w.,.! U.I.h.qXC C C C C C N N N N b b b b b x.=.$.M.S.tXaXiXpXpXpXpXpXpXpXtXpXtXiXiXtXiXiX1X6.] S G j. z.z.z.z.z.j.j.j. ", "q.A.A.l.;X;XR ~ ! 0.e.j.j.0.~ ~ T Z Z U.U.r.yXZ C C C C N N N N b N N N T a.A.4XpXpXaXaXpXaXpXpXpXpXpXpXpXpXiXpXiXOXu.` V I z.z.z. ", "5.A.A.A.-X`.e.j.j.e.0.~ R T T T T Z T Y.U.s.jXG C C C N N N N N E t.F.9XsXpXaXaXpXaXpXpXpXpXaXpXpXpXpXpXpX2Xu.[ D R ", "q.A.A.A.-X:X0.,.~ R R R T T T T Z T Z s.U.r.aXR C C C C T y.S.5XaXaXaXaXsXaXpXaXsXpXpXsXaXpXpXpXpX2Xu.] D I ", "<.A.A.A.-X,X~ R R R R R R T T T T Z T x.U.r.jXQ T q.D.1XaXaXaXjXsXaXsXpXaXaXpXaXpXaXaXpXaX1Xd.] G E ", "<.(.z.A.-X,XR ~ R R R T T T T T T T T x.s.r.S.`.aXsXdXaXjXjXaXaXaXaXsXaXaXaXaXaXaX4XM.' D E ", "q.A.A.A.-X3XR ~ R R R T T T R Z Z ,.j./.rXsXdXsXdXdXsXsXsXaXsXaXsXaXaXaXaX2Xd.] G E ", "<.A.l.A.-X3X~ R R R R R T { g.`.wXgXfXfXfXsXfXsXdXdXsXsXsXaXaXsXaX4XM.} D ", "4.D.A.A.:X3XR R R ! i.:XwXfXfXfXfXfXdXdXfXsXsXsXdXfXaXsXaX4XM.} G ", "q.A.z.l.:XwXp./.5XfXfXfXfXfXfXfXjXdXfXfXsXfXsXsXsX9XM.] D ", "<./.-X5XyXgXhXfXgXgXfXfXfXfXfXfXfXdXjXfXjX4Xg.} V ", "<.hXgXgXhXhXfXhXgXfXgXfXfXfXfXfXfX4XN.] V ", "<.hXgXgXgXgXfXhXgXfXfXfXfXqXN.] j ", "<.hXgXgXfXhXhXgXfXwXg.} h ", "<.gXhXgXfXqXN.] t ", ",.rXN.] h ", " ", " ", " ", " ", " ", " " }; schismtracker-20250313/include/backend/000077500000000000000000000000001476471630300176425ustar00rootroot00000000000000schismtracker-20250313/include/backend/audio.h000066400000000000000000000046361476471630300211250ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_BACKEND_AUDIO_H_ #define SCHISM_BACKEND_AUDIO_H_ #include "../song.h" // Pass this to open_device to get a default audio device #define AUDIO_BACKEND_DEFAULT ~((uint32_t)0) // defines the interface for each audio backend typedef struct { int (*init)(void); void (*quit)(void); int (*driver_count)(void); const char *(*driver_name)(int i); uint32_t (*device_count)(void); const char *(*device_name)(uint32_t i); int (*init_driver)(const char *driver); void (*quit_driver)(void); schism_audio_device_t *(*open_device)(uint32_t id, const schism_audio_spec_t *desired, schism_audio_spec_t *obtained); void (*close_device)(schism_audio_device_t *device); void (*lock_device)(schism_audio_device_t *device); void (*unlock_device)(schism_audio_device_t *device); void (*pause_device)(schism_audio_device_t *device, int paused); } schism_audio_backend_t; #ifdef SCHISM_WIN32 extern const schism_audio_backend_t schism_audio_backend_dsound; extern const schism_audio_backend_t schism_audio_backend_waveout; #endif #ifdef SCHISM_MACOSX extern const schism_audio_backend_t schism_audio_backend_macosx; #endif #ifdef SCHISM_SDL12 extern const schism_audio_backend_t schism_audio_backend_sdl12; #endif #ifdef SCHISM_SDL2 extern const schism_audio_backend_t schism_audio_backend_sdl2; #endif #ifdef SCHISM_SDL3 extern const schism_audio_backend_t schism_audio_backend_sdl3; #endif #endif /* SCHISM_BACKEND_AUDIO_H_ */ schismtracker-20250313/include/backend/clippy.h000066400000000000000000000035271476471630300213220ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_BACKEND_CLIPPY_H_ #define SCHISM_BACKEND_CLIPPY_H_ typedef struct { int (*init)(void); void (*quit)(void); int (*have_selection)(void); void (*set_selection)(const char *text); char *(*get_selection)(void); int (*have_clipboard)(void); void (*set_clipboard)(const char *text); char *(*get_clipboard)(void); } schism_clippy_backend_t; #ifdef SCHISM_WIN32 extern const schism_clippy_backend_t schism_clippy_backend_win32; #endif #ifdef SCHISM_USE_X11 extern const schism_clippy_backend_t schism_clippy_backend_x11; #endif #ifdef SCHISM_MACOSX extern const schism_clippy_backend_t schism_clippy_backend_macosx; #endif #ifdef SCHISM_SDL2 extern const schism_clippy_backend_t schism_clippy_backend_sdl2; #endif #ifdef SCHISM_SDL3 extern const schism_clippy_backend_t schism_clippy_backend_sdl3; #endif #endif /* SCHISM_BACKEND_CLIPPY_H_ */schismtracker-20250313/include/backend/dmoz.h000066400000000000000000000031321476471630300207630ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_BACKEND_DMOZ_H_ #define SCHISM_BACKEND_DMOZ_H_ typedef struct { // returns 1 if succeeded, 0 if failed int (*init)(void); void (*quit)(void); char *(*get_exe_path)(void); } schism_dmoz_backend_t; #ifdef SCHISM_WIN32 extern const schism_dmoz_backend_t schism_dmoz_backend_win32; #endif #ifdef SCHISM_MACOSX extern const schism_dmoz_backend_t schism_dmoz_backend_macosx; #endif #ifdef SCHISM_SDL2 extern const schism_dmoz_backend_t schism_dmoz_backend_sdl2; #endif #ifdef SCHISM_SDL3 extern const schism_dmoz_backend_t schism_dmoz_backend_sdl3; #endif #endif /* SCHISM_BACKEND_DMOZ_H_ */ schismtracker-20250313/include/backend/events.h000066400000000000000000000035571476471630300213310ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_BACKEND_EVENTS_H_ #define SCHISM_BACKEND_EVENTS_H_ #include "../keyboard.h" // flags enum { // Enable this flag if the backend sends its own key repeats // If it doesn't we fall back to our internal representation // and getting key repeat rates from the OS. SCHISM_EVENTS_BACKEND_HAS_KEY_REPEAT = (1 << 0), }; typedef struct { // returns 1 if succeeded, 0 if failed int (*init)(void); void (*quit)(void); // flags defined above uint32_t flags; void (*pump_events)(void); schism_keymod_t (*keymod_state)(void); } schism_events_backend_t; #ifdef SCHISM_SDL12 extern const schism_events_backend_t schism_events_backend_sdl12; #endif #ifdef SCHISM_SDL2 extern const schism_events_backend_t schism_events_backend_sdl2; #endif #ifdef SCHISM_SDL3 extern const schism_events_backend_t schism_events_backend_sdl3; #endif #endif /* SCHISM_BACKEND_EVENTS_H_ */ schismtracker-20250313/include/backend/mt.h000066400000000000000000000047601476471630300204420ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_BACKEND_MT_H_ #define SCHISM_BACKEND_MT_H_ #include "../mt.h" typedef struct { int (*init)(void); void (*quit)(void); // Creates a thread. `name` is optional, and if passed may be used to set // the description of a thread in the OS. mt_thread_t *(*thread_create)(schism_thread_function_t func, const char *name, void *userdata); // Waits for a thread to exit, then cleans it up. void (*thread_wait)(mt_thread_t *thread, int *status); // Sets the current threads priority. void (*thread_set_priority)(int priority); // Get the current thread's unique ID. (mostly unused) mt_thread_id_t (*thread_id)(void); mt_mutex_t *(*mutex_create)(void); void (*mutex_delete)(mt_mutex_t *mutex); void (*mutex_lock)(mt_mutex_t *mutex); void (*mutex_unlock)(mt_mutex_t *mutex); mt_cond_t *(*cond_create)(void); void (*cond_delete)(mt_cond_t *cond); void (*cond_signal)(mt_cond_t *cond); void (*cond_wait)(mt_cond_t *cond, mt_mutex_t *mutex); void (*cond_wait_timeout)(mt_cond_t *cond, mt_mutex_t *mutex, uint32_t timeout); } schism_mt_backend_t; #ifdef SCHISM_WIN32 extern const schism_mt_backend_t schism_mt_backend_win32; #endif #ifdef SCHISM_MACOS extern const schism_mt_backend_t schism_mt_backend_macos; #endif #ifdef SCHISM_SDL12 extern const schism_mt_backend_t schism_mt_backend_sdl12; #endif #ifdef SCHISM_SDL2 extern const schism_mt_backend_t schism_mt_backend_sdl2; #endif #ifdef SCHISM_SDL3 extern const schism_mt_backend_t schism_mt_backend_sdl3; #endif #endif /* SCHISM_BACKEND_MT_H_ */ schismtracker-20250313/include/backend/timer.h000066400000000000000000000035071476471630300211400ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_BACKEND_TIMER_H_ #define SCHISM_BACKEND_TIMER_H_ #include "../timer.h" typedef struct { int (*init)(void); void (*quit)(void); timer_ticks_t (*ticks)(void); timer_ticks_t (*ticks_us)(void); void (*usleep)(uint64_t us); void (*msleep)(uint32_t ms); // returns 1 if a timer was successful // or 0 if we have to emulate it int (*oneshot)(uint32_t ms, void (*callback)(void *param), void *param); } schism_timer_backend_t; #ifdef SCHISM_WIN32 extern const schism_timer_backend_t schism_timer_backend_win32; #endif #ifdef SCHISM_SDL12 extern const schism_timer_backend_t schism_timer_backend_sdl12; #endif #ifdef SCHISM_SDL2 extern const schism_timer_backend_t schism_timer_backend_sdl2; #endif #ifdef SCHISM_SDL3 extern const schism_timer_backend_t schism_timer_backend_sdl3; #endif #endif /* SCHISM_BACKEND_TIMER_H_ */ schismtracker-20250313/include/backend/video.h000066400000000000000000000051731476471630300211270ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_BACKEND_VIDEO_H_ #define SCHISM_BACKEND_VIDEO_H_ typedef struct { // extremely unorganized, sorry int (*init)(void); void (*quit)(void); int (*is_fullscreen)(void); int (*width)(void); int (*height)(void); const char *(*driver_name)(void); void (*report)(void); void (*set_hardware)(int hardware); void (*shutdown)(void); void (*setup)(const char *quality); void (*startup)(void); void (*fullscreen)(int new_fs_flag); void (*resize)(unsigned int width, unsigned int height); void (*colors)(unsigned char palette[16][3]); int (*is_focused)(void); int (*is_visible)(void); int (*is_wm_available)(void); int (*is_hardware)(void); void (*show_system_cursor)(int show); int (*is_screensaver_enabled)(void); void (*toggle_screensaver)(int enabled); void (*translate)(unsigned int vx, unsigned int vy, unsigned int *x, unsigned int *y); void (*get_logical_coordinates)(int x, int y, int *trans_x, int *trans_y); int (*is_input_grabbed)(void); void (*set_input_grabbed)(int enabled); void (*warp_mouse)(unsigned int x, unsigned int y); void (*get_mouse_coordinates)(unsigned int *x, unsigned int *y); int (*have_menu)(void); void (*toggle_menu)(int on); void (*blit)(void); void (*mousecursor_changed)(void); int (*get_wm_data)(video_wm_data_t *wm_data); void (*show_cursor)(int enabled); } schism_video_backend_t; #ifdef SCHISM_SDL12 extern const schism_video_backend_t schism_video_backend_sdl12; #endif #ifdef SCHISM_SDL2 extern const schism_video_backend_t schism_video_backend_sdl2; #endif #ifdef SCHISM_SDL3 extern const schism_video_backend_t schism_video_backend_sdl3; #endif #endif /* SCHISM_BACKEND_VIDEO_H_ */ schismtracker-20250313/include/bshift.h000066400000000000000000000070221476471630300177040ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_BSHIFT_H_ #define SCHISM_BSHIFT_H_ #include "headers.h" /* Portable replacements for signed integer bit shifting. These share the same * implementation and only do different operations in the same manner and as such * are written using a very simple macro. */ #define SCHISM_SIGNED_RSHIFT_VARIANT(type) \ SCHISM_CONST SCHISM_ALWAYS_INLINE inline int##type##_t schism_signed_rshift_##type##_(int##type##_t x, unsigned int y) \ { \ return (x < 0) ? ~(~x >> y) : (x >> y); \ } /* arithmetic and logical left shift are the same operation */ #define SCHISM_SIGNED_LSHIFT_VARIANT(type) \ SCHISM_CONST SCHISM_ALWAYS_INLINE inline int##type##_t schism_signed_lshift_##type##_(int##type##_t x, unsigned int y) \ { \ union { \ uint##type##_t s; \ int##type##_t u; \ } xx; \ xx.s = x; \ xx.u <<= y; \ return xx.s; \ } /* Unlike right shift, left shift is not implementation-defined (that is, * the compiler will do something that makes sense in a platform-defined * manner), but rather is full on undefined behavior, i.e. literally * *anything* could happen, so we use our own version everywhere. */ SCHISM_SIGNED_LSHIFT_VARIANT(8) SCHISM_SIGNED_LSHIFT_VARIANT(16) SCHISM_SIGNED_LSHIFT_VARIANT(32) SCHISM_SIGNED_LSHIFT_VARIANT(64) SCHISM_SIGNED_LSHIFT_VARIANT(max) #define lshift_signed(x, y) \ ((sizeof((x) << (y)) <= sizeof(int8_t)) \ ? (schism_signed_lshift_8_(x, y)) \ : (sizeof((x) << (y)) <= sizeof(int16_t)) \ ? (schism_signed_lshift_16_(x, y)) \ : (sizeof((x) << (y)) <= sizeof(int32_t)) \ ? (schism_signed_lshift_32_(x, y)) \ : (sizeof((x) << (y)) <= sizeof(int64_t)) \ ? (schism_signed_lshift_64_(x, y)) \ : (schism_signed_lshift_max_(x, y))) #ifdef HAVE_ARITHMETIC_RSHIFT # define rshift_signed(x, y) ((x) >> (y)) #else SCHISM_SIGNED_RSHIFT_VARIANT(8) SCHISM_SIGNED_RSHIFT_VARIANT(16) SCHISM_SIGNED_RSHIFT_VARIANT(32) SCHISM_SIGNED_RSHIFT_VARIANT(64) SCHISM_SIGNED_RSHIFT_VARIANT(max) # define rshift_signed(x, y) \ ((sizeof((x) >> (y)) <= sizeof(int8_t)) \ ? (schism_signed_rshift_8_(x, y)) \ : (sizeof((x) >> (y)) <= sizeof(int16_t)) \ ? (schism_signed_rshift_16_(x, y)) \ : (sizeof((x) >> (y)) <= sizeof(int32_t)) \ ? (schism_signed_rshift_32_(x, y)) \ : (sizeof((x) >> (y)) <= sizeof(int64_t)) \ ? (schism_signed_rshift_64_(x, y)) \ : (schism_signed_rshift_max_(x, y))) #endif #undef SCHISM_SIGNED_LSHIFT_VARIANT #undef SCHISM_SIGNED_RSHIFT_VARIANT #undef SCHISM_SIGNED_SHIFT_VARIANT #endif /* SCHISM_BSHIFT_H_ */ schismtracker-20250313/include/bswap.h000066400000000000000000000067741476471630300175560ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_BSWAP_H_ #define SCHISM_BSWAP_H_ #include "headers.h" /* bswap16 was added later in 4.8.0 */ #if SCHISM_GNUC_HAS_BUILTIN(__builtin_bswap16, 4, 8, 0) # define bswap_16(x) __builtin_bswap16(x) #elif SCHISM_MSVC_ATLEAST(8, 0, 0) # define bswap_16(x) _byteswap_ushort(x) #endif #if SCHISM_GNUC_HAS_BUILTIN(__builtin_bswap32, 4, 3, 0) # define bswap_32(x) __builtin_bswap32(x) #elif SCHISM_MSVC_ATLEAST(8, 0, 0) # define bswap_32(x) _byteswap_ulong(x) #endif #if SCHISM_GNUC_HAS_BUILTIN(__builtin_bswap64, 4, 3, 0) # define bswap_64(x) __builtin_bswap64(x) #elif SCHISM_MSVC_ATLEAST(8, 0, 0) # define bswap_64(x) _byteswap_uint64(x) #endif /* roll our own; it is now safe to assume that all byteswap * routines will act like a function and not like a macro */ #ifndef bswap_64 SCHISM_CONST SCHISM_ALWAYS_INLINE inline uint64_t bswap_64_schism_internal_(uint64_t x) { return ( ((x & UINT64_C(0x00000000000000FF)) << 56) | ((x & UINT64_C(0x000000000000FF00)) << 40) | ((x & UINT64_C(0x0000000000FF0000)) << 24) | ((x & UINT64_C(0x00000000FF000000)) << 8) | ((x & UINT64_C(0x000000FF00000000)) << 8) | ((x & UINT64_C(0x0000FF0000000000)) << 24) | ((x & UINT64_C(0x00FF000000000000)) << 40) | ((x & UINT64_C(0xFF00000000000000)) << 56) ); } # define bswap_64(x) bswap_64_schism_internal_(x) # define SCHISM_NEED_EXTERN_DEFINE_BSWAP_64 #endif #ifndef bswap_32 SCHISM_CONST SCHISM_ALWAYS_INLINE inline uint32_t bswap_32_schism_internal_(uint32_t x) { return ( ((x & UINT32_C(0x000000FF)) << 24) | ((x & UINT32_C(0x0000FF00)) << 8) | ((x & UINT32_C(0x00FF0000)) >> 8) | ((x & UINT32_C(0xFF000000)) >> 24) ); } # define bswap_32(x) bswap_32_schism_internal_(x) # define SCHISM_NEED_EXTERN_DEFINE_BSWAP_32 #endif #ifndef bswap_16 SCHISM_CONST SCHISM_ALWAYS_INLINE inline uint16_t bswap_16_schism_internal_(uint16_t x) { return ( ((x & UINT16_C(0x00FF)) << 8) | ((x & UINT16_C(0xFF00)) >> 8) ); } # define bswap_16(x) bswap_16_schism_internal_(x) # define SCHISM_NEED_EXTERN_DEFINE_BSWAP_16 #endif /* define the endian-related byte swapping (taken from libmodplug sndfile.h, glibc, and sdl) */ #if WORDS_BIGENDIAN # define bswapLE16(x) bswap_16(x) # define bswapLE32(x) bswap_32(x) # define bswapLE64(x) bswap_64(x) # define bswapBE16(x) (x) # define bswapBE32(x) (x) # define bswapBE64(x) (x) #else # define bswapBE16(x) bswap_16(x) # define bswapBE32(x) bswap_32(x) # define bswapBE64(x) bswap_64(x) # define bswapLE16(x) (x) # define bswapLE32(x) (x) # define bswapLE64(x) (x) #endif #endif /* SCHISM_BSWAP_H_ */ schismtracker-20250313/include/charset.h000066400000000000000000000154071476471630300200640ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_CHARSET_H_ #define SCHISM_CHARSET_H_ #include "headers.h" /* UCS4 shouldn't ever be used externally; the output depends on endianness. * It should only be used as sort of an in-between from UTF-8 to CP437 for use * where the strings can be edited, e.g. in the file selector */ typedef enum { /* Unicode */ CHARSET_UCS4LE, CHARSET_UCS4BE, CHARSET_UTF16LE, CHARSET_UTF16BE, CHARSET_UCS2LE, CHARSET_UCS2BE, CHARSET_UTF8, #ifdef WORDS_BIGENDIAN # define CHARSET_UCS4 CHARSET_UCS4BE # define CHARSET_UTF16 CHARSET_UTF16BE # define CHARSET_UCS2 CHARSET_UCS2BE #else # define CHARSET_UCS4 CHARSET_UCS4LE # define CHARSET_UTF16 CHARSET_UTF16LE # define CHARSET_UCS2 CHARSET_UCS2LE #endif /* European languages */ CHARSET_CP437, CHARSET_WINDOWS1252, /* thanks modplug! */ CHARSET_MACOSROMAN, // Mac OS Roman /* Windows cludge */ #ifdef SCHISM_WIN32 CHARSET_ANSI, #endif #ifdef SCHISM_OS2 CHARSET_DOSCP, #endif /* Uses the system's HFS encoding */ #ifdef SCHISM_MACOS CHARSET_HFS, #endif /* CHARSET_CHAR is special; it first tries UTF-8 * in our internal decoder, then we hand it off * to SDL, which may or may not actually handle * it correctly, depending on whether it uses * the system iconv or its own. */ CHARSET_CHAR, CHARSET_WCHAR_T } charset_t; typedef enum { CHARSET_ERROR_SUCCESS = 0, CHARSET_ERROR_UNIMPLEMENTED = -1, CHARSET_ERROR_INPUTISOUTPUT = -2, CHARSET_ERROR_NULLINPUT = -3, CHARSET_ERROR_NULLOUTPUT = -4, CHARSET_ERROR_DECODE = -5, CHARSET_ERROR_ENCODE = -6, CHARSET_ERROR_NOMEM = -7, } charset_error_t; enum { DECODER_STATE_INVALID_CHAR = -4, /* character unavailable in destination */ DECODER_STATE_ILL_FORMED = -3, /* input buffer is ill-formed */ DECODER_STATE_OVERFLOWED = -2, /* reached past input buffer size */ DECODER_STATE_ERROR = -1, /* unknown generic decoding error */ DECODER_STATE_NEED_MORE = 0, /* needs more bytes */ DECODER_STATE_DONE = 1, /* decoding done! */ }; typedef struct { /* -- input, set by the caller */ const unsigned char *in; /* input buffer */ size_t size; /* size of the buffer, can be SIZE_MAX if unknown */ size_t offset; /* current decoding offset, should always be set to zero */ /* -- output, decoder initializes these */ uint32_t codepoint; /* decoded codepoint if successful, undefined if not */ int state; /* one of DECODER_* definitions above; negative values are errors */ } charset_decode_t; SCHISM_CONST int char_digraph(int k1, int k2); SCHISM_CONST unsigned char char_unicode_to_cp437(uint32_t c); /* ------------------------------------------------------------------------ */ /* unicode composing and case folding graciously provided by utf8proc */ void *charset_compose_to_set(const void *in, charset_t inset, charset_t outset); void *charset_case_fold_to_set(const void *in, charset_t inset, charset_t outset); /* only provided for source compatibility and should be killed with fire */ #define charset_compose(in, set) charset_compose_to_set(in, set, set) #define charset_compose_to_utf8(in, set) charset_compose_to_set(in, set, CHARSET_UTF8) #define charset_case_fold(in, set) charset_case_fold_to_set(in, set, set) #define charset_case_fold_to_utf8(in, set) charset_case_fold_to_set(in, set, CHARSET_UTF8) /* ------------------------------------------------------------------------ */ /* charset-aware replacements for C stdlib functions */ size_t charset_strlen(const void* in, charset_t inset); int32_t charset_strcmp(const void* in1, charset_t in1set, const void* in2, charset_t in2set); int32_t charset_strncmp(const void* in1, charset_t in1set, const void* in2, charset_t in2set, size_t num); int32_t charset_strcasecmp(const void* in1, charset_t in1set, const void* in2, charset_t in2set); int32_t charset_strncasecmp(const void* in1, charset_t in1set, const void* in2, charset_t in2set, size_t num); size_t charset_strncasecmplen(const void* in1, charset_t in1set, const void* in2, charset_t in2set, size_t num); int32_t charset_strverscmp(const void *in1, charset_t in1set, const void *in2, charset_t in2set); int32_t charset_strcaseverscmp(const void *in1, charset_t in1set, const void *in2, charset_t in2set); void *charset_strstr(const void *in1, charset_t in1set, const void *in2, charset_t in2set); void *charset_strcasestr(const void *in1, charset_t in1set, const void *in2, charset_t in2set); /* basic fnmatch */ enum { CHARSET_FNM_CASEFOLD = (1 << 0), CHARSET_FNM_PERIOD = (1 << 1), }; int charset_fnmatch(const void *match, charset_t match_set, const void *str, charset_t str_set, int flags); /* iconv replacement */ #define CHARSET_NUL_TERMINATED SIZE_MAX /* Use this size if you know the input has a NUL terminator character */ const char* charset_iconv_error_lookup(charset_error_t err); charset_error_t charset_iconv(const void* in, void* out, charset_t inset, charset_t outset, size_t insize); /* character-by-character variant of charset_iconv; use as * charset_decode_t decoder = { * .in = buf, * .offset = 0, * .size = size, // can be SIZE_MAX if unknown * * .codepoint = 0, * .state = DECODER_STATE_NEED_MORE, * }; * * while (decoder->state == DECODER_STATE_NEED_MORE && !charset_decode_next(decoder, CHARSET_WHATEVER)) { * // codepoint is in decoder->codepoint * } */ charset_error_t charset_decode_next(charset_decode_t *decoder, charset_t inset); /* charset_iconv for newbies. * This is preferred to using the below macro, because it is less prone to memory leaks. * Do note that it assumes the input is NUL terminated. */ inline SCHISM_ALWAYS_INLINE void *charset_iconv_easy(const void *in, charset_t inset, charset_t outset) { void *out; if (!charset_iconv(in, &out, inset, outset, SIZE_MAX)) return out; return NULL; } #endif /* SCHISM_CHARSET_H_ */ schismtracker-20250313/include/clippy.h000066400000000000000000000035651476471630300177350ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_CLIPPY_H_ #define SCHISM_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 the backend int clippy_init(void); void clippy_quit(void); #endif /* SCHISM_CLIPPY_H_ */ schismtracker-20250313/include/config-parser.h000066400000000000000000000102101476471630300211550ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_CONFIG_PARSER_H_ #define SCHISM_CONFIG_PARSER_H_ /* --------------------------------------------------------------------------------------------------------- */ /* TODO: add an "owner" field to the section and key structures indicating what file was being read the last time it was referenced. (maybe: 0 = user and 1 = system, or something like that) that way, it would be possible to handle multiple configuration files, and only rewrite the stuff in the user configuration that were set there in the first place. of course, cfg_set_blah should update the key's owner, so it gets saved back to the *user* configuration file :) */ struct cfg_key { struct cfg_key *next; /* NULL if this is the last key in the section */ char *name; /* the text before the equal sign, whitespace trimmed */ char *value; /* the value -- never NULL (unless the key was just added) */ char *comments; /* any comments preceding this key, or NULL if none */ }; struct cfg_section { struct cfg_section *next; /* NULL if this is the last section in the file */ char *name; /* the text between the brackets, whitespace trimmed */ struct cfg_key *keys; /* NULL if section is empty */ char *comments; /* any comments preceding this section, or NULL if none */ int omit; /* don't include in output (delete this section) */ }; struct cfg_file { char *filename; /* this should never be NULL */ struct cfg_section *sections; /* NULL if file is empty */ char *eof_comments; /* comments following the last key are saved here */ int dirty; /* has this config been modified? */ }; typedef struct cfg_file cfg_file_t; /* --------------------------------------------------------------------------------------------------------- */ /* public functions */ int cfg_read(cfg_file_t *cfg); /* write the structure back to disk. this will (try to) copy the current configuration to filename~ first. if the file has not been modified, this call is a no-op. */ int cfg_write(cfg_file_t *cfg); /* the return value is the full value for the key. this will differ from the value copied to the value return parameter if the length of the value is greater than the size of the buffer. value may be NULL, in which case nothing is copied. */ const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name, char *value, int len, const char *def); int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def); void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value); void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value); /* delete a key from the config file. * well, actually it doesn't delete it, it just comments it out. */ void cfg_delete_key(cfg_file_t *cfg, const char *section_name, const char *key_name); /* set up a structure and (try to) read the configuration file from disk. */ int cfg_init(cfg_file_t *cfg, const char *filename); /* deallocate the configuration structure. */ void cfg_free(cfg_file_t *cfg); /* --------------------------------------------------------------------------------------------------------- */ #endif /* SCHISM_CONFIG_PARSER_H_ */ schismtracker-20250313/include/config.h000066400000000000000000000055111476471630300176730ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_CONFIG_H_ #define SCHISM_CONFIG_H_ #include "headers.h" extern char cfg_video_interpolation[]; extern char cfg_video_format[]; /* TODO: consolidate these into cfg_video_flags */ extern int cfg_video_fullscreen; extern int cfg_video_want_fixed; extern int cfg_video_want_fixed_width; extern int cfg_video_want_fixed_height; extern int cfg_video_mousecursor; extern int cfg_video_width, cfg_video_height; extern int cfg_video_hardware; extern int cfg_video_want_menu_bar; extern int cfg_kbd_repeat_delay; extern int cfg_kbd_repeat_rate; extern char cfg_dir_modules[SCHISM_PATH_MAX + 1], cfg_dir_samples[SCHISM_PATH_MAX + 1], cfg_dir_instruments[SCHISM_PATH_MAX + 1]; extern char *cfg_dir_dotschism; /* the full path to ~/.schism */ extern char *cfg_font; extern int cfg_palette; extern int cfg_str_date_format; extern int cfg_str_time_format; extern char cfg_module_pattern[SCHISM_PATH_MAX + 1]; extern char cfg_export_pattern[SCHISM_PATH_MAX + 1]; 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 */ void cfg_save_output(void); /* each page with configurable settings has a function to load/save them... */ #include "config-parser.h" 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_save_audio_playback(cfg_file_t* cfg); void cfg_atexit_save_audio(cfg_file_t *cfg); void cfg_load_disko(cfg_file_t *cfg); void cfg_save_disko(cfg_file_t *cfg); void cfg_load_dmoz(cfg_file_t *cfg); void cfg_save_dmoz(cfg_file_t *cfg); #endif /* SCHISM_CONFIG_H_ */ schismtracker-20250313/include/dialog.h000066400000000000000000000060661476471630300176730ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_DIALOG_H_ #define SCHISM_DIALOG_H_ struct key_event; struct widget; 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 /* SCHISM_DIALOG_H_ */ schismtracker-20250313/include/disko.h000066400000000000000000000103341476471630300175360ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_DISKO_H_ #define SCHISM_DISKO_H_ #include "headers.h" typedef struct disko disko_t; struct disko { // Functions whose implementation depends on the backend in use // Use disko_write et al. instead of these. void (*_write)(disko_t *ds, const void *buf, size_t len); void (*_seek)(disko_t *ds, int64_t offset, int whence); int64_t (*_tell)(disko_t *ds); // Temporary filename that's being written to char *tempname; // Name to change it to on close (if successful) char *filename; // these could be unionized // file pointer (only exists for disk files) FILE *file; // data for memory buffers (no filename/handle) uint8_t *data; // First errno value recorded after something went wrong. int error; /* untouched by diskwriter; driver may use for anything */ void *userdata; // for memory buffers size_t pos, length, allocated; }; enum { DW_OK = 1, DW_ERROR = 0, DW_NOT_RUNNING = -1, }; enum { DW_SYNC_DONE = 0, DW_SYNC_ERROR = -1, DW_SYNC_MORE = 1, }; /* fopen/fclose-ish writeout/finish wrapper that shoves data into the * user-allocated structure */ int disko_open(disko_t *ds, const char *filename); /* Close the file. If there was no error writing the file, it is renamed to the name specified in disko_open; otherwise, the original file is left intact and the temporary file is deleted. Returns DW_OK on success, DW_ERROR (and sets errno) if there was a file error. 'backup' parameter: <1 don't backup =1 backup to file~ >1 keep numbered backups to file.n~ (the semantics of this might change later to allow finer control) */ int disko_close(disko_t *f, int backup); /* alloc/free a memory buffer if free_buffer is 0, the internal buffer is left alone when deallocating, so that it can continue to be used later */ int disko_memopen(disko_t *f); int disko_memclose(disko_t *f, int free_buffer); /* copy a pattern into a sample */ int disko_writeout_sample(int smpnum, int pattern, int bind); /* copy a pattern into multiple samples (split by channel, and writing into free slots starting at smpnum) */ int disko_multiwrite_samples(int firstsmp, int pattern); /* Wrapper for the above that writes to the current sample, and with a confirmation dialog if the sample already has data */ void song_pattern_to_sample(int pattern, int split, int bind); /* export the song to a file */ struct save_format; int disko_export_song(const char *filename, const struct save_format *format); /* call periodically if (status.flags & DISKWRITER_ACTIVE) to write more stuff. return: DW_SYNC_*, self explanatory */ int disko_sync(void); /* For use by the diskwriter drivers: */ /* Write data to the file, as in fwrite() */ void disko_write(disko_t *ds, const void *buf, size_t len); /* Write one character */ void disko_putc(disko_t *ds, unsigned char c); /* Change file position. This CAN be used to seek past the end, but be cognizant that random data might exist in the "gap". */ void disko_seek(disko_t *ds, int64_t pos, int whence); /* Get the position, as set by seek */ int64_t disko_tell(disko_t *ds); /* Call this to signal a nonrecoverable error condition. */ void disko_seterror(disko_t *ds, int err); /* Call this to seek to an aligned byte boundary */ void disko_align(disko_t *ds, uint32_t bytes); #endif /* SCHISM_DISKO_H_ */ schismtracker-20250313/include/dmoz.h000066400000000000000000000244461476471630300174070ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_DMOZ_H_ #define SCHISM_DMOZ_H_ #include "player/sndfile.h" /* for song_sample_t */ /* need these for struct stat */ #include #include enum { TYPE_BROWSABLE_MASK = 0x1, /* if (type & TYPE_BROWSABLE_MASK) it's readable as a library */ TYPE_FILE_MASK = 0x2, /* if (type & TYPE_FILE_MASK) it's a regular file */ TYPE_DIRECTORY = 0x4 | TYPE_BROWSABLE_MASK, /* if (type == TYPE_DIRECTORY) ... guess what! */ TYPE_NON_REGULAR = 0x8, /* if (type == TYPE_NON_REGULAR) it's something weird, e.g. a socket */ /* (flags & 0xF0) are reserved for future use */ /* this has to match TYPE_BROWSABLE_MASK for directories */ TYPE_EXT_DATA_MASK = 0xFFF01, /* if (type & TYPE_EXT_DATA_MASK) the extended data has been checked */ TYPE_MODULE_MASK = 0xF00, /* if (type & TYPE_MODULE_MASK) it's loadable as a module */ TYPE_MODULE_MOD = 0x100 | TYPE_BROWSABLE_MASK | TYPE_FILE_MASK, TYPE_MODULE_S3M = 0x200 | TYPE_BROWSABLE_MASK | TYPE_FILE_MASK, TYPE_MODULE_XM = 0x300 | TYPE_BROWSABLE_MASK | TYPE_FILE_MASK, TYPE_MODULE_IT = 0x400 | TYPE_BROWSABLE_MASK | TYPE_FILE_MASK, TYPE_INST_MASK = 0xF000, /* if (type & TYPE_INST_MASK) it's loadable as an instrument */ TYPE_INST_ITI = 0x1000 | TYPE_FILE_MASK, /* .iti (native) instrument */ TYPE_INST_XI = 0x2000 | TYPE_FILE_MASK, /* fast tracker .xi */ TYPE_INST_OTHER = 0x3000 | TYPE_FILE_MASK, /* gus patch, soundfont, ...? */ TYPE_SAMPLE_MASK = 0xF0000, /* if (type & TYPE_SAMPLE_MASK) it's loadable as a sample */ TYPE_UNKNOWN = 0x10000 | TYPE_FILE_MASK, /* any unrecognized file, loaded as raw pcm data */ TYPE_SAMPLE_PLAIN = 0x20000 | TYPE_FILE_MASK, /* au, aiff, wav (simple formats) */ TYPE_SAMPLE_EXTD = 0x30000 | TYPE_FILE_MASK, /* its, s3i (tracker formats with extended stuff) */ TYPE_SAMPLE_COMPR = 0x40000 | TYPE_FILE_MASK, /* ogg, mp3 (compressed audio) */ TYPE_INTERNAL_FLAGS = 0xF00000, TYPE_HIDDEN = 0x100000, }; /* A brief description of the sort_order field: When sorting the lists, items with a lower sort_order are given higher placement, and ones with a higher sort order are placed toward the bottom. Items with equal sort_order are sorted by their basename (using strverscmp). Defined sort orders: -1024 ... 0 System directories, mount points, volumes, etc. -10 Parent directory 0 Subdirectories of the current directory >= 1 Files. Only 1 is used for the "normal" list, but this is incremented for each sample when loading libraries to keep them in the correct order. Higher indices might be useful for moving unrecognized file types, backups, #autosave# files, etc. to the bottom of the list (rather than omitting these files entirely). */ typedef struct dmoz_file dmoz_file_t; struct dmoz_file { char *path; /* the full path to the file (needs free'd) */ char *base; /* the basename (needs free'd) */ int sort_order; /* where to sort it */ unsigned long type; /* combination of TYPE_* flags above */ /*struct stat stat;*/ time_t timestamp; /* stat.st_mtime */ size_t filesize; /* stat.st_size */ /* if ((type & TYPE_EXT_DATA_MASK) == 0) nothing below this point will be defined (call dmoz_{fill,filter}_ext_data to define it) */ const char *description; /* i.e. "Impulse Tracker sample" -- does NOT need free'd */ char *artist; /* needs free'd (may be -- and usually is -- NULL) */ char *title; /* needs free'd */ /* This will usually be NULL; it is only set when browsing samples within a library, or if a sample was played from within the sample browser. */ song_sample_t *sample; int sampsize; /* number of samples (for instruments) */ int instnum; /* loader MAY fill this stuff in */ char *smp_filename; unsigned int smp_speed; unsigned int smp_loop_start; unsigned int smp_loop_end; unsigned int smp_sustain_start; unsigned int smp_sustain_end; unsigned int smp_length; unsigned int smp_flags; unsigned int smp_defvol; unsigned int smp_gblvol; unsigned int smp_vibrato_speed; unsigned int smp_vibrato_depth; unsigned int smp_vibrato_rate; }; typedef struct dmoz_dir { char *path; /* full path (needs free'd) */ char *base; /* basename of the directory (needs free'd) */ int sort_order; /* where to sort it */ } dmoz_dir_t; typedef struct dmoz_filelist { int num_files, alloc_size; dmoz_file_t **files; int selected; /* communication with cache */ } dmoz_filelist_t; typedef struct dmoz_dirlist { int num_dirs, alloc_size; dmoz_dir_t **dirs; int selected; /* communication with cache */ } dmoz_dirlist_t; /* For any of these, pass NULL for dirs to handle directories and files in the same list. for load_library, provide one of the dmoz_read_whatever_library functions, or NULL. */ int dmoz_read(const char *path, dmoz_filelist_t *files, dmoz_dirlist_t *dirs, int (*load_library)(const char *,dmoz_filelist_t *,dmoz_dirlist_t *)); void dmoz_free(dmoz_filelist_t *files, dmoz_dirlist_t *dirs); void dmoz_sort(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist); /* this function is in audio_loadsave.cc instead of dmoz.c, because of modplugness */ int dmoz_read_sample_library(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist); int dmoz_read_instrument_library(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist); /* if ((file->type & TYPE_EXT_DATA_MASK) == 0), call this function to get the title and description */ int dmoz_filter_ext_data(dmoz_file_t *file); /* same as dmoz_filter_ext_data, but always returns 1 (for async title reading) */ int dmoz_fill_ext_data(dmoz_file_t *file); /* filters stuff based on... whatever you like :) */ void dmoz_filter_filelist(dmoz_filelist_t *flist, int (*grep)(dmoz_file_t *f), int *pointer, void (*onmove)(void)); /* butt */ int song_preload_sample(dmoz_file_t *f); /* Path handling functions */ #if defined(SCHISM_WIN32) || defined(SCHISM_OS2) # define DIR_SEPARATOR '\\' # define DIR_SEPARATOR_STR "\\" # define IS_DIR_SEPARATOR(c) ((c) == '/' || (c) == '\\') #elif defined(SCHISM_MACOS) # define DIR_SEPARATOR ':' # define DIR_SEPARATOR_STR ":" # define IS_DIR_SEPARATOR(c) ((c) == ':') #else # define DIR_SEPARATOR '/' # define DIR_SEPARATOR_STR "//" # define IS_DIR_SEPARATOR(c) ((c) == '/') #endif /* Normalize a path (remove /../ and stuff, condense multiple slashes, etc.) this will return NULL if the path could not be normalized (not well-formed?). the returned string must be free()'d. */ char *dmoz_path_normal(const char *path); /* Return nonzero if the path is an absolute path (e.g. /bin, c:\progra~1, sd:/apps, etc.) */ int dmoz_path_is_absolute(const char *path, int *count); /* Concatenate two paths, adding separators between them as necessary. The returned string must be free()'d. The second version can be used if the string lengths are already known to avoid redundant strlen() calls. Additionally, if 'b' is an absolute path (as determined by dmoz_path_is_absolute), ignore 'a' and return a copy of 'b'. */ char *dmoz_path_concat(const char *a, const char *b); char *dmoz_path_concat_len(const char *a, const char *b, int alen, int blen); const char *dmoz_path_get_basename(const char *filename); const char *dmoz_path_get_extension(const char *filename); char *dmoz_path_get_parent_directory(const char *dirname); int dmoz_path_make_backup(const char *filename, int numbered); int dmoz_path_rename(const char *old, const char *new, int overwrite); int dmoz_path_mkdir_recursive(const char *path, int mode); int dmoz_path_is_file(const char *filename); int dmoz_path_is_directory(const char *filename); unsigned long long dmoz_path_get_file_size(const char *filename); char *dmoz_path_pretty_name(const char *filename); char *dmoz_get_current_directory(void); char *dmoz_get_home_directory(void); char *dmoz_get_dot_directory(void); char *dmoz_get_exe_directory(void); /* Adding files and directories For all of these, path and base should be free()-able. */ /* If st == NULL, it is assumed to be a directory, and the timestamp/filesize fields are set to zero. This way, it's possible to add platform directories ("/", "C:\", whatever) without having to call stat first. The return value is the newly created file struct. */ dmoz_file_t *dmoz_add_file(dmoz_filelist_t *flist, char *path, char *base, struct stat *st, int sort_order); /* The return value is the newly created dir struct. */ dmoz_dir_t *dmoz_add_dir(dmoz_dirlist_t *dlist, char *path, char *base, int sort_order); /* Add a directory to either the dir list (if dlist != NULL) or the file list otherwise. This is basically a convenient shortcut for adding a directory. */ void dmoz_add_file_or_dir(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist, char *path, char *base, struct stat *st, int sort_order); /* this is called by main to actually do some dmoz work. returns 0 if there is no dmoz work to do... */ int dmoz_worker(void); /* these update the file selection cache for the various pages */ void dmoz_cache_update_names(const char *path, const char *filen, const char *dirn); void dmoz_cache_update(const char *path, dmoz_filelist_t *fl, dmoz_dirlist_t *dl); void dmoz_cache_lookup(const char *path, dmoz_filelist_t *fl, dmoz_dirlist_t *dl); #ifdef SCHISM_MACOS /* this sucks */ int dmoz_path_from_fsspec(const void *spec, char **path); int dmoz_path_to_fsspec(const char *path, void *spec); #endif int dmoz_init(void); void dmoz_quit(void); #endif /* SCHISM_DMOZ_H_ */ schismtracker-20250313/include/events.h000066400000000000000000000175611476471630300177420ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_EVENTS_H_ #define SCHISM_EVENTS_H_ #include "keyboard.h" #include "timer.h" // timer_ticks_t #include "backend/events.h" // for the backend typedef /* types of events delivered by the current backend */ enum { /* Application events */ SCHISM_QUIT = 0x100, /* User-requested quit */ /* Window events */ SCHISM_WINDOWEVENT_SHOWN = 0x200, SCHISM_WINDOWEVENT_HIDDEN, SCHISM_WINDOWEVENT_EXPOSED, SCHISM_WINDOWEVENT_RESIZED, SCHISM_WINDOWEVENT_SIZE_CHANGED, SCHISM_WINDOWEVENT_FOCUS_GAINED, SCHISM_WINDOWEVENT_FOCUS_LOST, //SCHISM_SYSWMEVENT, /* System specific event */ /* Keyboard events */ SCHISM_KEYDOWN = 0x300, /* Key pressed */ SCHISM_KEYUP, /* Key released */ SCHISM_TEXTINPUT, /* Keyboard text input, sent if it wasn't mapped to a keydown */ /* Mouse events */ SCHISM_MOUSEMOTION = 0x400, /* Mouse moved */ SCHISM_MOUSEBUTTONDOWN, /* Mouse button pressed */ SCHISM_MOUSEBUTTONUP, /* Mouse button released */ SCHISM_MOUSEWHEEL, /* Mouse wheel motion */ /* Clipboard events */ SCHISM_CLIPBOARDUPDATE = 0x900, /* The clipboard or primary selection changed */ /* Drag and drop */ SCHISM_DROPFILE = 0x1000, /* The system requests a file open */ /* Audio hotplug */ SCHISM_AUDIODEVICEADDED = 0x1100, /* A new audio device is available */ SCHISM_AUDIODEVICEREMOVED, /* An audio device has been removed. */ /* Render events */ SCHISM_RENDER_TARGETS_RESET = 0x2000, /* The render targets have been reset and their contents need to be updated */ SCHISM_RENDER_DEVICE_RESET, /* The device has been reset and all textures need to be recreated */ /* Schism internal events */ SCHISM_EVENT_UPDATE_IPMIDI = 0x3000, SCHISM_EVENT_PLAYBACK, SCHISM_EVENT_NATIVE_OPEN, SCHISM_EVENT_NATIVE_SCRIPT, SCHISM_EVENT_PASTE, SCHISM_EVENT_MIDI_NOTE = 0x4000, SCHISM_EVENT_MIDI_CONTROLLER, SCHISM_EVENT_MIDI_PROGRAM, SCHISM_EVENT_MIDI_AFTERTOUCH, SCHISM_EVENT_MIDI_PITCHBEND, SCHISM_EVENT_MIDI_TICK, SCHISM_EVENT_MIDI_SYSEX, SCHISM_EVENT_MIDI_SYSTEM, SCHISM_EVENT_WM_MSG = 0x5000, }; typedef struct { uint32_t type; timer_ticks_t timestamp; } schism_common_event_t; typedef struct { schism_common_event_t common; // event-specific data union { struct { uint32_t width; uint32_t height; } resized; } data; } schism_window_event_t; typedef struct { schism_common_event_t common; enum key_state state; int repeat; schism_keysym_t sym; schism_scancode_t scancode; schism_keymod_t mod; char text[32]; // always in UTF-8 } schism_keyboard_event_t; typedef struct { schism_common_event_t common; char text[32]; // always in UTF-8 } schism_text_input_event_t; typedef struct { schism_common_event_t common; char *file; } schism_file_drop_event_t; typedef struct { schism_common_event_t common; /* X and Y coordinates relative to the window */ int32_t x; int32_t y; } schism_mouse_motion_event_t; typedef struct { schism_common_event_t common; enum mouse_button button; int state; // zero if released, non-zero if pressed uint8_t clicks; // how many clicks? /* X and Y coordinates relative to the window */ int32_t x; int32_t y; } schism_mouse_button_event_t; typedef struct { schism_common_event_t common; int32_t x; // how far on the horizontal axis int32_t y; // how far on the vertical axis // cursor X and Y coordinates relative to the window int32_t mouse_x; int32_t mouse_y; } schism_mouse_wheel_event_t; typedef struct { schism_common_event_t common; int mnstatus; int channel; int note; int velocity; } schism_midi_note_event_t; typedef struct { schism_common_event_t common; int value; int channel; int param; } schism_midi_controller_event_t; typedef struct { schism_common_event_t common; int value; int channel; } schism_midi_program_event_t; typedef struct { schism_common_event_t common; int value; int channel; } schism_midi_aftertouch_event_t; typedef struct { schism_common_event_t common; int value; int channel; } schism_midi_pitchbend_event_t; typedef struct { schism_common_event_t common; int argv; int param; } schism_midi_system_event_t; typedef struct { schism_common_event_t common; } schism_midi_tick_event_t; typedef struct { schism_common_event_t common; unsigned int len; unsigned char *packet; } schism_midi_sysex_event_t; typedef struct { schism_common_event_t common; char *which; } schism_native_script_event_t; typedef struct { schism_common_event_t common; char *file; } schism_native_open_event_t; typedef struct { schism_common_event_t common; char *clipboard; } schism_clipboard_paste_event_t; typedef struct { schism_common_event_t common; // which backend enum { SCHISM_WM_MSG_BACKEND_SDL12, SCHISM_WM_MSG_BACKEND_SDL2, } backend; // which wm subsystem its using enum { SCHISM_WM_MSG_SUBSYSTEM_WINDOWS, SCHISM_WM_MSG_SUBSYSTEM_X11 } subsystem; union { // use generic types when possible, please struct { void *hwnd; // `HWND' in Win32 uint32_t msg; // `DWORD' in Win32 uintptr_t wparam; // `WPARAM' in Win32 uintptr_t lparam; // `LPARAM' in Win32 } win; struct { union { // These are NOT size compatible with the Xlib // event structures... int type; // This is the only one used (for now?) struct { int type; uint32_t serial; int send_event; // `Bool' in Xlib void *display; // `Display *' in Xlib uint32_t owner; // `Window' in Xlib uint32_t requestor; // `Window' in Xlib uint32_t selection; // `Atom' in Xlib uint32_t target; // `Atom' in Xlib uint32_t property; // `Atom' in Xlib uint32_t time; // `Time' in Xlib } selection_request; } event; } x11; } msg; } schism_wm_msg_event_t; typedef union schism_event { uint32_t type; schism_common_event_t common; schism_window_event_t window; schism_keyboard_event_t key; schism_text_input_event_t text; schism_mouse_motion_event_t motion; schism_mouse_button_event_t button; schism_mouse_wheel_event_t wheel; schism_midi_note_event_t midi_note; schism_midi_controller_event_t midi_controller; schism_midi_program_event_t midi_program; schism_midi_aftertouch_event_t midi_aftertouch; schism_midi_pitchbend_event_t midi_pitchbend; schism_midi_system_event_t midi_system; schism_midi_tick_event_t midi_tick; schism_midi_sysex_event_t midi_sysex; schism_native_script_event_t script; schism_native_open_event_t open; schism_clipboard_paste_event_t clipboard; schism_file_drop_event_t drop; schism_wm_msg_event_t wm_msg; } schism_event_t; /* ------------------------------------ */ int events_init(const schism_events_backend_t *backend); void events_quit(void); int events_have_event(void); int events_poll_event(schism_event_t *event); int events_push_event(const schism_event_t *event); void events_pump_events(void); schism_keymod_t events_get_keymod_state(void); #endif /* SCHISM_EVENT_H_ */ schismtracker-20250313/include/fakemem.h000066400000000000000000000030301476471630300200250ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_FAKEMEM_H_ #define SCHISM_FAKEMEM_H_ /* memory usage */ unsigned int memused_lowmem(void); unsigned int memused_ems(void); unsigned int memused_songmessage(void); unsigned int memused_instruments(void); unsigned int memused_samples(void); unsigned int memused_clipboard(void); unsigned int memused_patterns(void); unsigned int memused_history(void); /* clears the memory lookup cache */ void memused_songchanged(void); void memused_get_pattern_saved(unsigned int *a, unsigned int *b); /* wtf */ #endif /* SCHISM_FAKEMEM_H_ */ schismtracker-20250313/include/fmt-types.h000066400000000000000000000151371476471630300203630ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* This file is used for file format tables, load ordering, and declarations. It is intended to be included after defining macros for handling the various file types listed here, and as such, it is not #ifdef guarded. The type list should be arranged so that the types with the most specific checks are first, and the vaguest ones are down at the bottom. This is to ensure that some lousy type doesn't "steal" files of a different type. For example, if IT came before S3M, any S3M file starting with "IMPM" (which is unlikely, but possible, and in fact quite easy to do) would be picked up by the IT check. In fact, Impulse Tracker itself has this problem. Also, a format that might need to do a lot of work to tell if a file is of the right type (i.e. the MDL format practically requires reading through the entire file to find the title block) should be down farther on the list for performance purposes. Don't rearrange the formats that are already here unless you have a VERY good reason to do so. I spent a good 3-4 hours reading all the format specifications, testing files, checking notes, and trying to break the program by giving it weird files, and I'm pretty sure that this ordering won't fail unless you really try doing weird stuff like hacking the files, but then you're just asking for trouble. ;) */ #ifndef READ_INFO # define READ_INFO(x) #endif #ifndef LOAD_SONG # define LOAD_SONG(x) #endif #ifndef SAVE_SONG # define SAVE_SONG(x) #endif #ifndef LOAD_SAMPLE # define LOAD_SAMPLE(x) #endif #ifndef SAVE_SAMPLE # define SAVE_SAMPLE(x) #endif #ifndef LOAD_INSTRUMENT # define LOAD_INSTRUMENT(x) #endif #ifndef SAVE_INSTRUMENT # define SAVE_INSTRUMENT(x) #endif #ifndef EXPORT # define EXPORT(x) #endif /* --------------------------------------------------------------------------------------------------------- */ /* 669 has lots of checks to compensate for a really crappy 2-byte magic. (It's even a common English word ffs... "if"?!) Still, it's better than STM. The only reason this is first is because the position of the SCRM magic lies within the 669 message field, and the 669 check is much more complex (and thus more likely to be right). */ READ_INFO(669) LOAD_SONG(669) /* Since so many programs have added noncompatible extensions to the mod format, there are about 30 strings to compare against for the magic. Also, there are special cases for WOW files, which even share the same magic as plain ProTracker, but are quite different; there are some really nasty heuristics to detect these... ugh, ugh, ugh. However, it has to be above the formats with the magic at the beginning... This only handles 31-sample mods; 15-sample ones have no identifying information and are therefore placed much lower in this list. */ READ_INFO(mod) LOAD_SONG(mod31) SAVE_SONG(mod) /* S3M needs to be before a lot of stuff. */ READ_INFO(s3m) LOAD_SONG(s3m) SAVE_SONG(s3m) /* FAR and S3M have different magic in the same place, so it doesn't really matter which one goes where. I just have S3M first since it's a more common format. */ READ_INFO(far) LOAD_SONG(far) /* These next formats have their magic at the beginning of the data, so none of them can possibly conflict with other ones. I've organized them pretty much in order of popularity. */ READ_INFO(xm) LOAD_SONG(xm) READ_INFO(it) LOAD_SONG(it) SAVE_SONG(it) READ_INFO(mt2) READ_INFO(mtm) LOAD_SONG(mtm) READ_INFO(ntk) READ_INFO(mdl) LOAD_SONG(mdl) READ_INFO(med) READ_INFO(okt) LOAD_SONG(okt) READ_INFO(mid) LOAD_SONG(mid) READ_INFO(mus) LOAD_SONG(mus) READ_INFO(mf) READ_INFO(dsm) LOAD_SONG(dsm) READ_INFO(d00) READ_INFO(edl) /* Sample formats with magic at start of file */ READ_INFO(its) LOAD_SAMPLE(its) SAVE_SAMPLE(its) READ_INFO(au) LOAD_SAMPLE(au) SAVE_SAMPLE(au) READ_INFO(aiff) LOAD_SAMPLE(aiff) SAVE_SAMPLE(aiff) EXPORT(aiff) READ_INFO(wav) LOAD_SAMPLE(wav) SAVE_SAMPLE(wav) EXPORT(wav) #ifdef USE_FLAC READ_INFO(flac) LOAD_SAMPLE(flac) SAVE_SAMPLE(flac) EXPORT(flac) #endif READ_INFO(iti) LOAD_INSTRUMENT(iti) SAVE_INSTRUMENT(iti) READ_INFO(xi) LOAD_INSTRUMENT(xi) SAVE_INSTRUMENT(xi) READ_INFO(pat) LOAD_INSTRUMENT(pat) READ_INFO(ult) LOAD_SONG(ult) READ_INFO(liq) READ_INFO(ams) READ_INFO(f2r) READ_INFO(s3i) LOAD_SAMPLE(s3i) SAVE_SAMPLE(s3i) /* FIXME should this be moved? S3I has magic at 0x4C... */ /* IMF and SFX (as well as STX) all have the magic values at 0x3C-0x3F, which is positioned in IT's "reserved" field, Not sure about this positioning, but these are kind of rare formats anyway. */ READ_INFO(imf) LOAD_SONG(imf) READ_INFO(sfx) LOAD_SONG(sfx) READ_INFO(stx) LOAD_SONG(stx) /* bleh */ #if defined(USE_NON_TRACKED_TYPES) && defined(HAVE_VORBIS) READ_INFO(ogg) #endif /* STM seems to have a case insensitive magic string with several possible values, and only one byte is guaranteed to be the same in the whole file... yeagh. */ READ_INFO(stm) LOAD_SONG(stm) /* An ID3 tag could actually be anywhere in an MP3 file, and there's no guarantee that it even exists at all. I might move this toward the top if I can figure out how to identify an MP3 more precisely. */ #ifdef USE_NON_TRACKED_TYPES READ_INFO(mp3) #endif #if USE_MEDIAFOUNDATION READ_INFO(win32mf) LOAD_SAMPLE(win32mf) #endif /* 15-sample mods have literally no identifying information */ READ_INFO(mod) LOAD_SONG(mod15) /* not really a type, so no info reader for these */ LOAD_SAMPLE(raw) SAVE_SAMPLE(raw) /* --------------------------------------------------------------------------------------------------------- */ /* Clear these out so subsequent includes don't make an ugly mess */ #undef READ_INFO #undef LOAD_SONG #undef SAVE_SONG #undef LOAD_SAMPLE #undef SAVE_SAMPLE #undef LOAD_INSTRUMENT #undef SAVE_INSTRUMENT #undef EXPORT schismtracker-20250313/include/fmt.h000066400000000000000000000245261476471630300172230ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_FMT_H_ #define SCHISM_FMT_H_ #include "dmoz.h" #include "slurp.h" #include "util.h" #include "disko.h" #include "player/sndfile.h" /* --------------------------------------------------------------------------------------------------------- */ /* module loaders */ /* flags to skip loading some data (mainly for scraping titles) this is only a suggestion in order to speed loading; don't be surprised if the loader ignores these */ #define LOAD_NOSAMPLES 1 #define LOAD_NOPATTERNS 2 /* return codes for module loaders */ enum { LOAD_SUCCESS, /* all's well */ LOAD_UNSUPPORTED, /* wrong file type for the loader */ LOAD_FILE_ERROR, /* couldn't read the file; check errno */ LOAD_FORMAT_ERROR, /* it appears to be the correct type, but there's something wrong */ }; /* return codes for modules savers */ enum { SAVE_SUCCESS, /* all's well */ SAVE_UNSUPPORTED, /* unsupported samples, or something */ SAVE_FILE_ERROR, /* couldn't write the file; check errno */ SAVE_INTERNAL_ERROR, /* something unrelated to disk i/o */ SAVE_NO_FILENAME, /* the filename is empty... */ }; /* --------------------------------------------------------------------------------------------------------- */ #define PROTO_READ_INFO (dmoz_file_t *file, slurp_t *fp) #define PROTO_LOAD_SONG (song_t *song, slurp_t *fp, unsigned int lflags) #define PROTO_SAVE_SONG (disko_t *fp, song_t *song) #define PROTO_LOAD_SAMPLE (slurp_t *fp, song_sample_t *smp) #define PROTO_SAVE_SAMPLE (disko_t *fp, song_sample_t *smp) #define PROTO_LOAD_INSTRUMENT (slurp_t *fp, int slot) #define PROTO_SAVE_INSTRUMENT (disko_t *fp, song_t *song, song_instrument_t *ins) #define PROTO_EXPORT_HEAD (disko_t *fp, int bits, int channels, int rate) #define PROTO_EXPORT_SILENCE (disko_t *fp, long bytes) #define PROTO_EXPORT_BODY (disko_t *fp, const uint8_t *data, size_t length) #define PROTO_EXPORT_TAIL (disko_t *fp) typedef int (*fmt_read_info_func) PROTO_READ_INFO; typedef int (*fmt_load_song_func) PROTO_LOAD_SONG; typedef int (*fmt_save_song_func) PROTO_SAVE_SONG; typedef int (*fmt_load_sample_func) PROTO_LOAD_SAMPLE; typedef int (*fmt_save_sample_func) PROTO_SAVE_SAMPLE; typedef int (*fmt_load_instrument_func) PROTO_LOAD_INSTRUMENT; typedef int (*fmt_save_instrument_func) PROTO_SAVE_INSTRUMENT; typedef int (*fmt_export_head_func) PROTO_EXPORT_HEAD; typedef int (*fmt_export_silence_func) PROTO_EXPORT_SILENCE; typedef int (*fmt_export_body_func) PROTO_EXPORT_BODY; typedef int (*fmt_export_tail_func) PROTO_EXPORT_TAIL; #define READ_INFO(t) int fmt_##t##_read_info PROTO_READ_INFO; #define LOAD_SONG(t) int fmt_##t##_load_song PROTO_LOAD_SONG; #define SAVE_SONG(t) int fmt_##t##_save_song PROTO_SAVE_SONG; #define LOAD_SAMPLE(t) int fmt_##t##_load_sample PROTO_LOAD_SAMPLE; #define SAVE_SAMPLE(t) int fmt_##t##_save_sample PROTO_SAVE_SAMPLE; #define LOAD_INSTRUMENT(t) int fmt_##t##_load_instrument PROTO_LOAD_INSTRUMENT; #define SAVE_INSTRUMENT(t) int fmt_##t##_save_instrument PROTO_SAVE_INSTRUMENT; #define EXPORT(t) int fmt_##t##_export_head PROTO_EXPORT_HEAD; \ int fmt_##t##_export_silence PROTO_EXPORT_SILENCE; \ int fmt_##t##_export_body PROTO_EXPORT_BODY; \ int fmt_##t##_export_tail PROTO_EXPORT_TAIL; #include "fmt-types.h" /* --------------------------------------------------------------------------------------------------------- */ struct save_format { const char *label; // label for the button on the save page const char *name; // long name of format const char *ext; // no dot union { fmt_save_song_func save_song; fmt_save_sample_func save_sample; fmt_save_instrument_func save_instrument; struct { fmt_export_head_func head; fmt_export_silence_func silence; fmt_export_body_func body; fmt_export_tail_func tail; int multi; } export; } f; // for files that can only be loaded with an external library // that is loaded at runtime (or linked to) int (*enabled)(void); }; extern const struct save_format song_save_formats[]; extern const struct save_format song_export_formats[]; extern const struct save_format sample_save_formats[]; extern const struct save_format instrument_save_formats[]; /* --------------------------------------------------------------------------------------------------------- */ struct instrumentloader { song_instrument_t *inst; int sample_map[MAX_SAMPLES]; int basex, slot, expect_samples; }; song_instrument_t *instrument_loader_init(struct instrumentloader *ii, int slot); int instrument_loader_abort(struct instrumentloader *ii); int instrument_loader_sample(struct instrumentloader *ii, int slot); /* --------------------------------------------------------------------------------------------------------- */ uint32_t it_decompress8(void *dest, uint32_t len, slurp_t *fp, int it215, int channels); uint32_t it_decompress16(void *dest, uint32_t len, slurp_t *fp, int it215, int channels); uint32_t mdl_decompress8(void *dest, uint32_t len, slurp_t *fp); uint32_t mdl_decompress16(void *dest, uint32_t len, slurp_t *fp); /* returns 0 on success */ int32_t huffman_decompress(slurp_t *slurp, disko_t *disko); /* --------------------------------------------------------------------------------------------------------- */ /* shared by the .it, .its, and .iti saving functions */ void save_its_header(disko_t *fp, song_sample_t *smp); void save_iti_instrument(disko_t *fp, song_t *song, song_instrument_t *ins, int iti_file); int load_its_sample(slurp_t *fp, song_sample_t *smp, uint16_t cwtv); int load_it_instrument(struct instrumentloader* ii, song_instrument_t *instrument, slurp_t *fp); int load_it_instrument_old(song_instrument_t *instrument, slurp_t *fp); uint32_t it_decode_edit_timer(uint16_t cwtv, uint32_t runtime); uint32_t it_get_song_elapsed_dos_time(song_t *song); /* s3i, called from s3m saver */ void s3i_write_header(disko_t *fp, song_sample_t *smp, uint32_t sdata); /* --------------------------------------------------------------------------------------------------------- */ /* handle dos timestamps */ timer_ticks_t dos_time_to_ms(uint32_t dos_time); uint32_t ms_to_dos_time(timer_ticks_t ms); void fat_date_time_to_tm(struct tm *tm, uint16_t fat_date, uint16_t fat_time); void tm_to_fat_date_time(const struct tm *tm, uint16_t *fat_date, uint16_t *fat_time); /* --------------------------------------------------------------------------------------------------------- */ /* [R]IFF helper functions */ typedef struct chunk { uint32_t id; uint32_t size; int64_t offset; } iff_chunk_t; /* chunk enums */ enum { IFF_CHUNK_SIZE_LE = (1 << 0), /* for RIFF */ IFF_CHUNK_ALIGNED = (1 << 1), /* are the structures word aligned? */ }; int iff_chunk_peek_ex(iff_chunk_t *chunk, slurp_t *fp, uint32_t flags); // provided for convenience #define iff_chunk_peek(chunk, fp) iff_chunk_peek_ex(chunk, fp, IFF_CHUNK_ALIGNED) #define riff_chunk_peek(chunk, fp) iff_chunk_peek_ex(chunk, fp, IFF_CHUNK_ALIGNED | IFF_CHUNK_SIZE_LE) int iff_chunk_read(iff_chunk_t *chunk, slurp_t *fp, void *data, size_t size); int iff_read_sample(iff_chunk_t *chunk, slurp_t *fp, song_sample_t *smp, uint32_t flags, size_t offset); int iff_chunk_receive(iff_chunk_t *chunk, slurp_t *fp, int (*callback)(const void *, size_t, void *), void *userdata); /* --------------------------------------------------------------------------------------------------------- */ // other misc functions... /* effect_weight[FX_something] => how "important" the effect is. */ extern const uint8_t effect_weight[]; /* Shuffle the effect and volume-effect values around. Note: this does NOT convert between volume and 'normal' effects, it only exchanges them. (This function is most useful in conjunction with convert_voleffect in order to try to cram ten pounds of crap into a five pound container) */ void swap_effects(song_note_t *note); /* Convert volume column data from FX_* to VOLFX_*, if possible. Return: 1 = it was properly converted, 0 = couldn't do so without loss of information. */ int convert_voleffect(uint8_t *effect, uint8_t *param, int force); #define convert_voleffect_of(note,force) convert_voleffect(&((note)->voleffect), &((note)->volparam), (force)) // load a .mod-style 4-byte packed note void mod_import_note(const uint8_t p[4], song_note_t *note); // Read a message with fixed-size line lengths void read_lined_message(char *msg, slurp_t *fp, int len, int linelen); // STM specific tools uint8_t convert_stm_tempo_to_bpm(size_t tempo); void handle_stm_tempo_pattern(song_note_t *note, size_t tempo); void handle_stm_effects(song_note_t *chan_note); extern const uint8_t stm_effects[16]; /* used internally by slurp only. nothing else should need this */ int mmcmp_unpack(slurp_t *fp, uint8_t **data, size_t *length); // get L-R-R-L panning value from a (zero-based!) channel number #define PROTRACKER_PANNING(n) (((((n) + 1) >> 1) & 1) * 256) // convert .mod finetune byte value to c5speed #define MOD_FINETUNE(b) (finetune_table[((b) & 0xf) ^ 8]) /* --------------------------------------------------------------------------------------------------------- */ int win32mf_init(void); void win32mf_quit(void); int flac_init(void); int flac_quit(void); void audio_enable_flac(int enabled); // should be called by flac_init() #endif /* SCHISM_FMT_H_ */ schismtracker-20250313/include/fonts.h000066400000000000000000000043331476471630300175600ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_FONTS_H_ #define SCHISM_FONTS_H_ int font_load(const char *filename); /* mostly for the itf editor */ int font_save(const char *filename); void font_reset_lower(void); /* ascii chars (0-127) */ void font_reset_upper(void); /* itf chars (128-255) */ void font_reset(void); /* everything (0-255) */ void font_reset_bios(void); /* resets all chars to the alt font */ void font_reset_char(int c); /* resets just one char */ /* this needs to be called before any char drawing. * it's pretty much the same as doing... * if (!font_load("font.cfg")) * font_reset(); * ... the main difference being font_init() is easier to deal with :) */ void font_init(void); extern const uint8_t font_default_lower[]; extern const uint8_t font_default_upper_alt[]; extern const uint8_t font_default_upper_itf[]; extern const uint8_t font_half_width[]; /* These fonts were adapted from the font8x8 pack * which is in the public domain: * https://github.com/dhepper/font8x8 */ extern const uint8_t font_hiragana[]; extern const uint8_t font_extended_latin[]; extern const uint8_t font_greek[]; extern uint8_t font_half_data[1024]; extern uint8_t *font_data; /* 2048 bytes */ #endif /* SCHISM_FONTS_H_ */ schismtracker-20250313/include/headers.h000066400000000000000000000516751476471630300200550ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* this file includes general C language headers that are useful everywhere, * along with some stupid stuff for broken toolchains that don't fully comply * to C99, like VLA support, va_copy stuff, etc * * In other projects this header is usually called "stdinc.h" ;) */ #ifndef SCHISM_HEADERS_H_ #define SCHISM_HEADERS_H_ #ifndef _GNU_SOURCE #define _GNU_SOURCE /* need this for some stupid gnu crap */ #endif #ifdef HAVE_CONFIG_H # include #endif /* ------------------------------------------------------------------------ */ /* Actual standard C stuff */ #include #include #include #include #include #include #ifdef HAVE_LIMITS_H #include #endif /* TODO we should handle this stuff ourselves, rather than giving it off * to autoconf, since we use the PRIx* and INT*_C macros. but that's for * another day. */ #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #include #if defined(HAVE_TGMATH_H) && !defined(SCHISM_MACOS) /* Macintosh toolchain has tgmath.h, but it's broken as shit */ # include #endif /* Math constants, not standard but in most toolchains, notably * OpenWatcom doesn't have them. */ #ifndef M_PI # define M_PI 3.1415926535897932384626433832795028841971 #endif #ifdef HAVE_ASSERT_H # include #else # ifndef NDEBUG /* untested. does this work? */ # define assert(x) do { if (!(x)) { fprintf(stderr, "%s: assertion failed", #x); exit(1); } } while (0) # else # define assert(x) # endif #endif #include #ifndef va_copy # ifdef __va_copy /* GNU */ # define va_copy(dst, sec) (__va_copy(dst, src)) # else # define va_copy(dst, src) (memcpy(&dst, &src, sizeof(va_list))) # endif #endif /* ------------------------------------------------------------------------ */ /* POSIX/UNIX/BSD crap */ /* This defines MAXPATHLEN (used later in this file for the definition of * SCHISM_PATH_MAX) */ #ifdef HAVE_SYS_PARAM_H #include #endif /* These files provide struct stat, which we currently require to be * provided by the implementation. Ideally we shouldn't need this and we * would use our own "schism-stat" instead but this is okay for now. */ #if HAVE_UNISTD_H # include # include #endif /* ------------------------------------------------------------------------ */ /* moved from util.h */ #define ARRAY_SIZE(a) (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 #ifndef ABS # define ABS(x) ((x) < 0 ? -(x) : x) #endif #define INT_SHAPED_PTR(v) ((intptr_t)(void*)(v)) #define PTR_SHAPED_INT(i) ((void*)(i)) /* Compares two version numbers following Semantic Versioning. * For example: * SCHISM_SEMVER_ATLEAST(1, 2, 3, 1, 2, 4) -> TRUE * SCHISM_SEMVER_ATLEAST(1, 2, 3, 2, 0, 0) -> TRUE * SCHISM_SEMVER_ATLEAST(1, 2, 1, 1, 1, 0) -> FALSE */ #define SCHISM_SEMVER_ATLEAST(mmajor, mminor, mpatch, major, minor, patch) \ (((major) >= (mmajor)) \ && ((major) > (mmajor) || (minor) >= (mminor)) \ && ((major) > (mmajor) || (minor) > (mminor) || (patch) >= (mpatch))) /* A bunch of compiler detection stuff... don't mind this... */ // GNU C (not necessarily GCC!) #if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) # define SCHISM_GNUC_ATLEAST(major, minor, patch) \ SCHISM_SEMVER_ATLEAST(major, minor, patch, __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) #elif defined(__GNUC__) && defined(__GNUC_MINOR__) # define SCHISM_GNUC_ATLEAST(major, minor, patch) \ SCHISM_SEMVER_ATLEAST(major, minor, patch, __GNUC__, __GNUC_MINOR__, 0) #elif defined(__GNUC__) # define SCHISM_GNUC_ATLEAST(major, minor, patch) \ SCHISM_SEMVER_ATLEAST(major, minor, patch, __GNUC__, 0, 0) #else # define SCHISM_GNUC_ATLEAST(major, minor, patch) (0) #endif // MSVC (untested) #if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) # define SCHISM_MSVC_ATLEAST(major, minor, patch) \ SCHISM_SEMVER_ATLEAST(major, minor, patch, _MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000 / 100000), (_MSC_FULL_VER % 100000) / 100) #elif defined(_MSC_FULL_VER) # define SCHISM_MSVC_ATLEAST(major, minor, patch) \ SCHISM_SEMVER_ATLEAST(major, minor, patch, _MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) #elif defined(_MSC_VER) # define SCHISM_MSVC_ATLEAST(major, minor, patch) \ SCHISM_SEMVER_ATLEAST(major, minor, patch, _MSC_VER / 100, _MSC_VER % 100, 0) #else # define SCHISM_MSVC_ATLEAST(major, minor, patch) (0) #endif #ifdef __has_attribute # define SCHISM_GNUC_HAS_ATTRIBUTE(x, major, minor, patch) \ __has_attribute(x) #else # define SCHISM_GNUC_HAS_ATTRIBUTE(x, major, minor, patch) \ SCHISM_GNUC_ATLEAST(major, minor, patch) #endif #ifdef __has_builtin # define SCHISM_GNUC_HAS_BUILTIN(x, major, minor, patch) \ __has_builtin(x) #else # define SCHISM_GNUC_HAS_BUILTIN(x, major, minor, patch) \ SCHISM_GNUC_ATLEAST(major, minor, patch) #endif #ifdef __has_extension # define SCHISM_GNUC_HAS_EXTENSION(x, major, minor, patch) \ __has_extension(x) #elif defined(__has_feature) # define SCHISM_GNUC_HAS_EXTENSION(x, major, minor, patch) \ __has_feature(x) #else # define SCHISM_GNUC_HAS_EXTENSION(x, major, minor, patch) \ SCHISM_GNUC_ATLEAST(major, minor, patch) #endif /* C23 requires that this exists. maybe compiler versions * ~could~ be used as a fallback but I don't care enough */ #ifdef __has_c_attribute # define SCHISM_HAS_C23_ATTRIBUTE(x) __has_c_attribute(x) #else # define SCHISM_HAS_C23_ATTRIBUTE(x) (0) #endif /* ------------------------------------------------------------------------ */ // Ok, now after all that mess, we can define these attributes: /* Used to designate a fallthrough case in a switch statement, such as: * * switch (whatever) { * case 0: ...; SCHISM_FALLTHROUGH; * default: ...; break; * } */ #if SCHISM_HAS_C23_ATTRIBUTE(fallthrough) # define SCHISM_FALLTHROUGH [[fallthrough]] #elif SCHISM_GNUC_HAS_ATTRIBUTE(__fallthrough__, 7, 0, 0) # define SCHISM_FALLTHROUGH __attribute__((__fallthrough__)) #elif SCHISM_MSVC_ATLEAST(16, 5, 0) # define SCHISM_FALLTHROUGH __fallthrough #else # define SCHISM_FALLTHROUGH #endif /* This is used for variables or parameters that are * known to be unused. */ #if SCHISM_HAS_C23_ATTRIBUTE(maybe_unused) # define SCHISM_UNUSED [[maybe_unused]] #elif SCHISM_GNUC_HAS_ATTRIBUTE(__unused__, 2, 7, 0) # define SCHISM_UNUSED __attribute__((__unused__)) #else # define SCHISM_UNUSED #endif /* Functions with this attribute must return a pointer * that is guaranteed to never alias any other pointer * still valid when the function returns. */ #if SCHISM_GNUC_HAS_ATTRIBUTE(__malloc__, 3, 0, 0) # define SCHISM_MALLOC __attribute__((__malloc__)) #elif SCHISM_MSVC_ATLEAST(14, 0, 0) # define SCHISM_MALLOC __declspec(allocator) __declspec(restrict) #elif SCHISM_MSVC_ATLEAST(8, 0, 0) # define SCHISM_MALLOC __declspec(restrict) #else # define SCHISM_MALLOC #endif /* Used to declare a function that has no effects except * for the return value, which only depends on parameters * and/or global variables. */ #if SCHISM_GNUC_HAS_ATTRIBUTE(__pure__, 2, 96, 0) # define SCHISM_PURE __attribute__((__pure__)) #elif SCHISM_HAS_C23_ATTRIBUTE(reproducible) # define SCHISM_PURE [[reproducible]] #else # define SCHISM_PURE #endif /* Used to declare a function that: * 1. do not examine any arguments except their parameters * 2. have no effects except for the return value * 3. return (i.e., never hang ever) */ #if SCHISM_GNUC_HAS_ATTRIBUTE(__const__, 2, 5, 0) # define SCHISM_CONST __attribute__((__const__)) #elif SCHISM_HAS_C23_ATTRIBUTE(unsequenced) # define SCHISM_CONST [[unsequenced]] #elif SCHISM_MSVC_ATLEAST(8, 0, 0) # define SCHISM_CONST __declspec(noalias) #else # define SCHISM_CONST #endif /* Used to declare a function that never returns. */ #if SCHISM_HAS_C23_ATTRIBUTE(noreturn) # define SCHISM_NORETURN [[noreturn]] #elif (__STDC_VERSION__ >= 201112L) # define SCHISM_NORETURN _Noreturn #elif SCHISM_GNUC_HAS_ATTRIBUTE(__noreturn__, 2, 5, 0) # define SCHISM_NORETURN __attribute__((__noreturn__)) #elif SCHISM_MSVC_ATLEAST(7, 1, 0) # define SCHISM_NORETURN __declspec(noreturn) #else # define SCHISM_NORETURN #endif /* Used for declaring functions that take in a printf-style * format string. If there are vararg params, the first index * gets specified in the last parameter of this macro, else * it should be 0. */ #if SCHISM_GNUC_HAS_ATTRIBUTE(__format__, 2, 3, 0) # define SCHISM_FORMAT_PRINTF(format_index, first_index) \ __attribute__((__format__(printf, format_index, first_index))) #else # define SCHISM_FORMAT_PRINTF(format_index, first_index) #endif /* Used for declaring malloc functions that take in an * allocation size in bytes (malloc), or in the case of * _EX, an allocation size in objects (calloc). */ #if SCHISM_GNUC_HAS_ATTRIBUTE(__alloc_size__, 9, 1, 0) # define SCHISM_ALLOC_SIZE(x) __attribute__((__alloc_size__(x))) # define SCHISM_ALLOC_SIZE_EX(x, y) __attribute__((__alloc_size__(x, y))) #else # define SCHISM_ALLOC_SIZE(x) # define SCHISM_ALLOC_SIZE_EX(x, y) #endif /* Use along with "static inline" to make a function that is * always inlined by the compiler. */ #if SCHISM_GNUC_HAS_ATTRIBUTE(__always_inline__, 3, 1, 1) # define SCHISM_ALWAYS_INLINE __attribute__((__always_inline__)) #elif SCHISM_MSVC_ATLEAST(12, 0, 0) # define SCHISM_ALWAYS_INLINE __forceinline #else # define SCHISM_ALWAYS_INLINE #endif /* Use this for functions that we'd like to not use anymore, * but for some reason or another (for example, a downstream * fork using it heavily) we have to keep it. */ #if SCHISM_HAS_C23_ATTRIBUTE(deprecated) # define SCHISM_DEPRECATED [[deprecated]] #elif SCHISM_GNUC_HAS_ATTRIBUTE(__deprecated__, 3, 1, 0) # define SCHISM_DEPRECATED __attribute__((__deprecated__)) #elif SCHISM_MSVC_ATLEAST(6, 0, 0) # define SCHISM_DEPRECATED __declspec(deprecated) #else # define SCHISM_DEPRECATED #endif /* Used for functions that are "hot-spots" of the program. * For example, the vgamem scanners are a giant hot-spot, * taking up lots of precious processing time. As such, * those functions, along with the blitter functions that * call them, have been marked as HOT. */ #if SCHISM_GNUC_HAS_ATTRIBUTE(__hot__, 4, 3, 0) # define SCHISM_HOT __attribute__((__hot__)) #else # define SCHISM_HOT #endif /* Use this attribute to generate one or more function * variations that use SIMD instructions to process * multiple values at once. This is used within the * vgamem scanner, though it's not super useful, since * the systems with these instructions will likely * already run schism fast enough anyway... */ #if SCHISM_GNUC_HAS_ATTRIBUTE(__simd__, 6, 1, 0) # define SCHISM_SIMD __attribute__((__simd__)) #else # define SCHISM_SIMD #endif /* Wrapped around an expression to hint to the compiler * whether it is likely to happen or not. Can often help * with branch prediction on newer processors. */ #if SCHISM_GNUC_HAS_BUILTIN(__builtin_expect, 3, 0, 0) # define SCHISM_LIKELY(x) __builtin_expect(!!(x), 1) # define SCHISM_UNLIKELY(x) __builtin_expect(!(x), 1) #else # define SCHISM_LIKELY(x) (x) # define SCHISM_UNLIKELY(x) (x) #endif /* Win32, used for threads */ #if SCHISM_GNUC_HAS_ATTRIBUTE(__force_align_arg_pointer__, 4, 2, 0) # define SCHISM_FORCE_ALIGN_ARG_POINTER __attribute__((__force_align_arg_pointer__)) #else # define SCHISM_FORCE_ALIGN_ARG_POINTER #endif /* Used to mark a printf format parameter. Currently only MSVC really * has this, and GCC has the much more useful "format" attribute */ #if SCHISM_MSVC_ATLEAST(8, 0, 0) # define SCHISM_PRINTF_FORMAT_PARAM _Printf_format_string_ #else # define SCHISM_PRINTF_FORMAT_PARAM #endif // Static assertion. DO NOT use this within structure definitions! #if (__STDC_VERSION__ >= 201112L) || (SCHISM_GNUC_HAS_EXTENSION(c11_static_assert, 4, 6, 0)) # define SCHISM_STATIC_ASSERT(x, msg) _Static_assert(x, msg) #else /* should work anywhere and shouldn't dump random stack allocations * BUT it fails to provide any sort of useful message to the user */ # define SCHISM_STATIC_ASSERT(x, msg) \ extern int (*schism_static_assert_function_no_touchy_touchy_plz(void)) \ [!!sizeof (struct { int __error_if_negative: (x) ? 2 : -1; })] #endif /* ------------------------------------------------------------------------ */ #ifndef HAVE_ASPRINTF int asprintf(char **strp, SCHISM_PRINTF_FORMAT_PARAM const char *fmt, ...) SCHISM_FORMAT_PRINTF(2, 3); #endif #ifndef HAVE_VASPRINTF int vasprintf(char **strp, SCHISM_PRINTF_FORMAT_PARAM const char *fmt, va_list ap) SCHISM_FORMAT_PRINTF(2, 0); #endif #ifndef HAVE_SNPRINTF #undef snprintf // stupid windows int snprintf(char *buffer, size_t count, SCHISM_PRINTF_FORMAT_PARAM const char *fmt, ...) SCHISM_FORMAT_PRINTF(3, 4); #endif #ifndef HAVE_VSNPRINTF #undef vsnprintf // stupid windows int vsnprintf(char *buffer, size_t count, SCHISM_PRINTF_FORMAT_PARAM const char *fmt, va_list ap) SCHISM_FORMAT_PRINTF(2, 0); #endif #ifndef HAVE_STRPTIME char *strptime(const char *buf, const char *fmt, struct tm *tm); #endif #ifndef HAVE_MKSTEMP int mkstemp(char *template); #endif #ifndef HAVE_LOCALTIME_R struct tm *localtime_r(const time_t *timep, struct tm *result); void localtime_r_quit(void); int localtime_r_init(void); #endif #ifndef HAVE_SETENV int setenv(const char *name, const char *value, int overwrite); #endif #ifndef HAVE_UNSETENV int unsetenv(const char *name); #endif #ifdef HAVE_GETOPT_LONG # include #else /* getopt replacement defines; these intentionally do not use the names * getopt() and such because a system could have getopt() but not * getopt_long(), and doing this avoids name collisions */ # define ya_no_argument 0 # define ya_required_argument 1 # define ya_optional_argument 2 struct option { const char *name; int has_arg; int *flag; int val; }; int ya_getopt(int argc, char * const argv[], const char *optstring); int ya_getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex); int ya_getopt_long_only(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex); extern char *ya_optarg; extern int ya_optind, ya_opterr, ya_optopt; // yargh # define getopt ya_getopt # define getopt_long ya_getopt_long # define getopt_long_only ya_getopt_long_only # define optarg ya_optarg # define optind ya_optind # define opterr ya_opterr # define optopt ya_optopt # define no_argument ya_no_argument # define required_argument ya_required_argument # define optional_argument ya_optional_argument #endif /* ------------------------------------------------------------------------ */ #ifdef SCHISM_WIN32 /* TODO We can actually enable long path support on windows in the manifest * https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation */ # define SCHISM_PATH_MAX ((3 + 256 + 1) * 4) // drive letter, colon, name components, NUL, multiplied by 4 for UTF-8 #elif defined(SCHISM_MACOS) # define SCHISM_PATH_MAX (255 + 1) // 255 bytes in Pascal-string + NUL terminator (encoding conversions do not happen here yet) #else # define SCHISM_PATH_MAX (8192) // 8 KiB (should be more than enough) #endif // redefine our value if it's smaller than the implementation's #ifdef PATH_MAX # if PATH_MAX > SCHISM_PATH_MAX # undef SCHISM_PATH_MAX # define SCHISM_PATH_MAX PATH_MAX # endif #endif #ifdef MAXPATHLEN # if MAXPATHLEN > SCHISM_PATH_MAX # undef SCHISM_PATH_MAX # define SCHISM_PATH_MAX MAXPATHLEN # endif #endif // SCHISM_PATH_MAX is a safe minimum, i guess #define SCHISM_NAME_MAX SCHISM_PATH_MAX #ifdef NAME_MAX # if NAME_MAX > SCHISM_NAME_MAX # undef SCHISM_NAME_MAX # define SCHISM_NAME_MAX NAME_MAX # endif #endif // FILENAME_MAX is not used here because it shouldn't be used for array bounds // i.e. it can be like, INT_MAX or something huge like that /* ------------------------------------------------------------------------ */ /* GNU string comparison functions, and charset_stdlib.c replacements for * them */ #ifndef HAVE_STRCASECMP # ifdef HAVE_STRICMP # define strcasecmp(s1, s2) stricmp(s1, s2) # else # include # define strcasecmp(s1, s2) charset_strcasecmp(s1, CHARSET_CHAR, s2, CHARSET_CHAR) # endif #endif #ifndef HAVE_STRNCASECMP # ifdef HAVE_STRNICMP # define strncasecmp(s1, s2, n) strnicmp(s1, s2) # else # include # define strncasecmp(s1, s2, n) charset_strncasecmp(s1, CHARSET_CHAR, s2, CHARSET_CHAR) # endif #endif #ifndef HAVE_STRVERSCMP # include # define strverscmp(s1, s2) charset_strverscmp(s1, CHARSET_CHAR, s2, CHARSET_CHAR) #endif #ifndef HAVE_STRCASESTR # include # define strcasestr(haystack, needle) charset_strcasestr(haystack, CHARSET_CHAR, needle, CHARSET_CHAR) #endif /* ------------------------------------------------------------------------ */ /* VLA abstraction; see the definitions below for details */ /* Giant ifdef tower that's literally only for alloca() */ #ifdef alloca # define SCHISM_USE_ALLOCA #else # ifdef HAVE_ALLOCA_H # include # define SCHISM_USE_ALLOCA # elif defined(__NetBSD__) || defined(__DMC__) // untested # include # define SCHISM_USE_ALLOCA # elif SCHISM_GNUC_HAS_BUILTIN(__builtin_alloca, 2, 95, 3) # define alloca __builtin_alloca # define SCHISM_USE_ALLOCA # elif SCHISM_MSVC_ATLEAST(0, 0, 0) // untested # include # define alloca _alloca # define SCHISM_USE_ALLOCA # elif defined(__WATCOMC__) || defined(__BORLANDC__) // untested # include # define SCHISM_USE_ALLOCA # elif defined(_AIX) && !defined(__GNUC__) // untested # pragma alloca # define SCHISM_USE_ALLOCA # elif defined(__MRC__) // untested void *alloca(unsigned int size); # define SCHISM_USE_ALLOCA # elif defined(HAVE_ALLOCA) // we ought to not be assuming this; eh void *alloca(size_t size); # define SCHISM_USE_ALLOCA # endif #endif // This is an abstraction over VLAs that should work everywhere. // Unfortunately, since C99 VLAs are not required in C11 and newer, // this is necessary. Most notably MSVC does not have support for // VLAs whatsoever. // Note that it is indeed possible that this will result in an out // of memory crash when using malloc. IMO this is the normal way to // handle things since VLAs can, will, and do blow up the stack anyway, // which is completely unrecoverable in portable code. // // usage: // SCHISM_VLA_ALLOC(int, arr, some_integer); // for (int i = 0; i < SCHISM_VLA_LENGTH(arr); i++) // arr[i] = i; // SCHISM_VLA_FREE(arr); #ifdef HAVE_C99_VLAS # define SCHISM_VLA_ALLOC(type, name, size) type name[size] # define SCHISM_VLA_FREE(name) // no-op # define SCHISM_VLA_SIZEOF(name) sizeof(name) #elif defined(SCHISM_USE_ALLOCA) # define SCHISM_VLA_ALLOC(type, name, size) \ const size_t _##name##_vla_size = ((size) * sizeof(type)); \ type *name = alloca(_##name##_vla_size) # define SCHISM_VLA_FREE(name) // no-op # define SCHISM_VLA_SIZEOF(name) (_##name##_vla_size) #else // fallback to the heap # include "mem.h" # define SCHISM_VLA_ALLOC(type, name, size) \ const size_t _##name##_vla_size = ((size) * sizeof(type)); \ type *name = mem_alloc(_##name##_vla_size) # define SCHISM_VLA_FREE(name) free(name) # define SCHISM_VLA_SIZEOF(name) (_##name##_vla_size) #endif // hm :) #define SCHISM_VLA_LENGTH(name) (SCHISM_VLA_SIZEOF(name) / sizeof(*name)) /* ------------------------------------------------------------------------ */ /* Used in the brackets of a flexible array member. Meant to be used as so: * * struct foo { * ... * int b[SCHISM_FAM_SIZE]; * } foo; * int *b; * * // proper ways of accessing `b`: * b = &foo.b; * b = (int *)((unsigned char *)foo + offsetof(struct foo, b)); * * // undefined behavior: * b = (int *)((unsigned char *)foo + sizeof(foo)); * * Flexible array members should be used as defined in the C99 spec (as in, * only ever at the end of a struct). */ #ifdef HAVE_C99_FAMS # define SCHISM_FAM_SIZE #elif SCHISM_GNUC_ATLEAST(2, 7, 0) # define SCHISM_FAM_SIZE 0 #else /* Hmph. */ # define SCHISM_FAM_SIZE 1 #endif #endif /* SCHISM_HEADERS_H_ */ schismtracker-20250313/include/ieee-float.h000066400000000000000000000032231476471630300204360ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_IEEE_FLOAT_H_ #define SCHISM_IEEE_FLOAT_H_ /* These functions work on the raw, big endian versions of IEEE * floating point numbers. If you have little endian input or * output, you'll have to byteswap. */ SCHISM_PURE double float_decode_ieee_32(const unsigned char bytes[4]); void float_encode_ieee_32(double num, unsigned char bytes[4]); SCHISM_PURE double float_decode_ieee_64(const unsigned char bytes[8]); void float_encode_ieee_64(double num, unsigned char bytes[8]); SCHISM_PURE double float_decode_ieee_80(const unsigned char bytes[10]); void float_encode_ieee_80(double num, unsigned char bytes[10]); #endif /* SCHISM_IEEE_FLOAT_H_ */ schismtracker-20250313/include/it.h000066400000000000000000000262211476471630300170430ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_IT_H_ #define SCHISM_IT_H_ #include "headers.h" #include "backend/timer.h" #include #include /* roundabout way to get time_t */ #include "util.h" #include "video.h" #include "log.h" #include "keyboard.h" /* --------------------------------------------------------------------- */ /* preprocessor stuff */ #define NO_MODIFIER(mod) \ (((mod) & (SCHISM_KEYMOD_CTRL | SCHISM_KEYMOD_ALT | SCHISM_KEYMOD_SHIFT)) == 0) #define NO_CAM_MODS(mod) \ (((mod) & (SCHISM_KEYMOD_CTRL | SCHISM_KEYMOD_ALT)) == 0) /* --------------------------------------------------------------------- */ /* structs 'n enums */ /* tracker_status dialog_types */ enum { DIALOG_NONE = (0), /* 0000 0000 */ DIALOG_MENU = (1 << 0), /* 0000 0001 */ DIALOG_MAIN_MENU = (DIALOG_MENU | (1 << 1)), /* 0000 0011 */ DIALOG_SUBMENU = (DIALOG_MENU | (1 << 2)), /* 0000 0101 */ DIALOG_BOX = (1 << 3), /* 0000 1000 */ DIALOG_OK = (DIALOG_BOX | (1 << 4)), /* 0001 1000 */ DIALOG_OK_CANCEL = (DIALOG_BOX | (1 << 5)), /* 0010 1000 */ /* yes/no technically has a cancel as well, i.e. the escape key */ DIALOG_YES_NO = (DIALOG_BOX | (1 << 6)), /* 0100 1000 */ DIALOG_CUSTOM = (DIALOG_BOX | (1 << 7)), /* 1000 1000 */ }; /* tracker_status flags eventual TODO: split this into two sections or something so we don't run out of bits to toggle... and while we're at it, namespace them with CFG_ (for the configuration stuff -- bits that are accessible through the interface in some way) and uh, something else for the internal status flags like IS_VISIBLE or whatever */ enum { /* if this flag is set, the screen will be redrawn */ NEED_UPDATE = (1 << 0), /* is the current palette "backwards"? (used to make the borders look right) */ INVERTED_PALETTE = (1 << 1), DIR_MODULES_CHANGED = (1 << 2), DIR_SAMPLES_CHANGED = (1 << 3), DIR_INSTRUMENTS_CHANGED = (1 << 4), /* if this is set, some stuff behaves differently * (grep the source files for what stuff ;) */ CLASSIC_MODE = (1 << 5), /* make a backup file (song.it~) when saving a module? */ MAKE_BACKUPS = (1 << 6), NUMBERED_BACKUPS = (1 << 7), /* song.it.3~ */ LAZY_REDRAW = (1 << 8), /* this is here if anything is "changed" and we need to whine to the user if they quit */ SONG_NEEDS_SAVE = (1 << 9), /* if the software mouse pointer moved.... */ SOFTWARE_MOUSE_MOVED = (1 << 10), /* pasting is done by setting a flag here, the main event loop then synthesizes * the various events... after we return * * FIXME this sucks, we should just call clippy directly */ CLIPPY_PASTE_SELECTION = (1 << 11), CLIPPY_PASTE_BUFFER = (1 << 12), /* if the disko is active */ DISKWRITER_ACTIVE = (1 << 13), DISKWRITER_ACTIVE_PATTERN = (1 << 14), /* recording only a single pattern */ /* mark... set by midi core when received new midi event */ MIDI_EVENT_CHANGED = (1 << 15), /* fontedit */ STARTUP_FONTEDIT = (1 << 16), /* key hacks -- should go away when keyboard redefinition is possible */ META_IS_CTRL = (1 << 17), ALTGR_IS_ALT = (1 << 18), /* Devi Ever's hack */ CRAYOLA_MODE = (1 << 20), NO_NETWORK = (1 << 22), /* Play MIDI events using the same semantics as tracker samples */ MIDI_LIKE_TRACKER = (1 << 23), /* if true, don't stop playing on load, and start playing new song afterward (but only if the last song was already playing before loading) */ PLAY_AFTER_LOAD = (1 << 24), }; /* note! TIME_PLAYBACK is only for internal calculations -- don't use it directly */ enum tracker_time_display { TIME_OFF, TIME_PLAY_ELAPSED, TIME_PLAY_CLOCK, TIME_PLAY_OFF, TIME_ELAPSED, TIME_CLOCK, TIME_ABSOLUTE, TIME_PLAYBACK, }; /* what should go in the little box on the top right? */ enum tracker_vis_style { VIS_OFF, VIS_FAKEMEM, VIS_OSCILLOSCOPE, VIS_VU_METER, VIS_MONOSCOPE, VIS_FFT, VIS_SENTINEL }; struct midi_port; /* midi.h */ 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; schism_keysym_t last_keysym; schism_keymod_t keymod; timer_ticks_t last_midi_tick; unsigned char last_midi_event[64]; unsigned int last_midi_len; unsigned int last_midi_real_len; /* XXX what is this */ struct midi_port *last_midi_port; /* clock is driven from the main/event thread */ time_t now; struct tm tmnow; int fix_numlock_setting; }; /* numlock hackery */ enum { NUMLOCK_ALWAYS_OFF = 0, NUMLOCK_ALWAYS_ON = 1, NUMLOCK_HONOR = -1, /* don't fix it */ NUMLOCK_GUESS = -2, /* don't fix it... except on non-ibook macs */ }; /* mouse visibility - these are passed to video_mousecursor() first three are stored as the physical mouse state */ enum { MOUSE_DISABLED, MOUSE_EMULATED, MOUSE_SYSTEM, MOUSE_CYCLE_STATE, MOUSE_RESET_STATE, }; #define MOUSE_MAX_STATE MOUSE_CYCLE_STATE enum { NOTE_TRANS_CLEAR = (30), NOTE_TRANS_NOTE_CUT, NOTE_TRANS_NOTE_OFF, NOTE_TRANS_NOTE_FADE, NOTE_TRANS_PREV_INS, NOTE_TRANS_NEXT_INS, NOTE_TRANS_TOGGLE_MASK, NOTE_TRANS_VOL_PAN_SWITCH, NOTE_TRANS_PLAY_NOTE, NOTE_TRANS_PLAY_ROW, }; /* --------------------------------------------------------------------- */ /* global crap */ extern struct tracker_status status; extern int playback_tracing, midi_playback_tracing; extern const char hexdigits[16]; /* in keyboard.c at the moment */ /* this used to just translate keys to notes, but it's sort of become the * keyboard map... perhaps i should rename it. */ extern const char *note_trans; /* keyboard.c */ extern int show_default_volumes; /* pattern-view.c */ /* --------------------------------------------------------------------- */ /* text functions */ /* these are sort of for single-line text entries. */ void text_add_char(char *text, uint8_t c, int *cursor_pos, int max_length); void text_delete_char(char *text, int *cursor_pos, int max_length); void text_delete_next_char(char *text, int *cursor_pos, int max_length); static inline unsigned char unicode_to_ascii(uint16_t unicode) { return unicode & 0xff; // return ((unicode & 0xff80) ? 0 : (unicode & 0x7f)); } /* --------------------------------------------------------------------- */ /* ... */ /* these are in audio_playback.cc */ extern signed short *audio_buffer; extern unsigned int audio_buffer_samples; extern unsigned int audio_output_channels; extern unsigned int audio_output_bits; /* --------------------------------------------------------------------- */ /* page functions */ /* anything can use these */ void set_page(int new_page); /* (there's no get_page -- just use status.current_page) */ /* these should only be called from main */ void load_pages(void); /* called once at start of program */ void playback_update(void); /* once per cycle */ struct key_event; void handle_key(struct key_event * k); /* whenever there's a keypress ;) */ void handle_text_input(const char *text_input); /* 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); /* --------------------------------------------------------------------- */ /* stuff */ /* main.c */ void toggle_display_fullscreen(void); /* page_instruments/page_samples.c */ int sample_get_current(void); void sample_set(int n); int instrument_get_current(void); void instrument_set(int n); void instrument_synchronize_to_sample(void); void sample_synchronize_to_instrument(void); int sample_is_used_by_instrument(int samp); /* if necessary, prompt to create a new instrument. if newpage >= 0, the page is changed upon completion of the dialog, or immediately if no dialog was shown. return value is 1 if the dialog was shown, 0 if not. */ int sample_host_dialog(int newpage); /* instrument... sample... whatever */ int song_is_instrument_mode(void); int song_first_unused_instrument(void); int song_get_current_instrument(void); void set_previous_instrument(void); void set_next_instrument(void); int get_current_channel(void); void set_current_channel(int channel); int get_current_row(void); void set_current_row(int row); int get_current_pattern(void); void set_current_pattern(int pattern); /* This is the F7 key handlers: it starts at the marked position if there * is one, or the current position if not. */ void play_song_from_mark_orderpan(void); void play_song_from_mark(void); int get_current_order(void); void set_current_order(int order); void prev_order_pattern(void); void next_order_pattern(void); void orderpan_recheck_muted_channels(void); void show_exit_prompt(void); void show_song_length(void); void show_song_timejump(void); /* some platforms have different entrypoints (see: macosx) which set up * platform-specific stuff, and eventually call this function to actually * run schism */ int schism_main(int argc, char** argv); /* Shutdown the SDL2 system from anywhere without having to use atexit() */ SCHISM_NORETURN void schism_exit(int status); /* --------------------------------------------------------------------- */ /* page_waterfall.c */ void vis_init(void); void vis_work_32s(const int32_t *in, int inlen); void vis_work_32m(const int32_t *in, int inlen); void vis_work_16s(const int16_t *in, int inlen); void vis_work_16m(const int16_t *in, int inlen); void vis_work_8s(const int8_t *in, int inlen); void vis_work_8m(const int8_t *in, int inlen); /* more stupid visual stuff: * I've reverted these to the values that they were at before the "Visuals" * patch from JosepMa, since the newer ones seem to cause weird holes within * the graph. Maybe this should be investigated further ;) */ #define FFT_BUFFER_SIZE_LOG 10 #define FFT_BUFFER_SIZE (1 << FFT_BUFFER_SIZE_LOG) #define FFT_OUTPUT_SIZE (FFT_BUFFER_SIZE / 2) #define FFT_BANDS_SIZE 1024 // this is enough to fill the screen and more SCHISM_STATIC_ASSERT(FFT_BUFFER_SIZE_LOG < 32, "FFT buffer code uses 32-bit integers"); // "unsigned char out[width]" ... void fft_get_columns(uint32_t width, unsigned char *out, uint32_t chan); /* --------------------------------------------------------------------- */ #endif /* SCHISM_IT_H_ */ schismtracker-20250313/include/keyboard.h000066400000000000000000000577571476471630300202510ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_KEYBOARD_H_ #define SCHISM_KEYBOARD_H_ #include "headers.h" /* locale-independent keyboard positions */ enum { SCHISM_SCANCODE_UNKNOWN = 0, /* USB keyboard page... */ SCHISM_SCANCODE_A = 4, SCHISM_SCANCODE_B = 5, SCHISM_SCANCODE_C = 6, SCHISM_SCANCODE_D = 7, SCHISM_SCANCODE_E = 8, SCHISM_SCANCODE_F = 9, SCHISM_SCANCODE_G = 10, SCHISM_SCANCODE_H = 11, SCHISM_SCANCODE_I = 12, SCHISM_SCANCODE_J = 13, SCHISM_SCANCODE_K = 14, SCHISM_SCANCODE_L = 15, SCHISM_SCANCODE_M = 16, SCHISM_SCANCODE_N = 17, SCHISM_SCANCODE_O = 18, SCHISM_SCANCODE_P = 19, SCHISM_SCANCODE_Q = 20, SCHISM_SCANCODE_R = 21, SCHISM_SCANCODE_S = 22, SCHISM_SCANCODE_T = 23, SCHISM_SCANCODE_U = 24, SCHISM_SCANCODE_V = 25, SCHISM_SCANCODE_W = 26, SCHISM_SCANCODE_X = 27, SCHISM_SCANCODE_Y = 28, SCHISM_SCANCODE_Z = 29, SCHISM_SCANCODE_1 = 30, SCHISM_SCANCODE_2 = 31, SCHISM_SCANCODE_3 = 32, SCHISM_SCANCODE_4 = 33, SCHISM_SCANCODE_5 = 34, SCHISM_SCANCODE_6 = 35, SCHISM_SCANCODE_7 = 36, SCHISM_SCANCODE_8 = 37, SCHISM_SCANCODE_9 = 38, SCHISM_SCANCODE_0 = 39, SCHISM_SCANCODE_RETURN = 40, SCHISM_SCANCODE_ESCAPE = 41, SCHISM_SCANCODE_BACKSPACE = 42, SCHISM_SCANCODE_TAB = 43, SCHISM_SCANCODE_SPACE = 44, SCHISM_SCANCODE_MINUS = 45, SCHISM_SCANCODE_EQUALS = 46, SCHISM_SCANCODE_LEFTBRACKET = 47, SCHISM_SCANCODE_RIGHTBRACKET = 48, SCHISM_SCANCODE_BACKSLASH = 49, SCHISM_SCANCODE_NONUSHASH = 50, SCHISM_SCANCODE_SEMICOLON = 51, SCHISM_SCANCODE_APOSTROPHE = 52, SCHISM_SCANCODE_GRAVE = 53, SCHISM_SCANCODE_COMMA = 54, SCHISM_SCANCODE_PERIOD = 55, SCHISM_SCANCODE_SLASH = 56, SCHISM_SCANCODE_CAPSLOCK = 57, SCHISM_SCANCODE_F1 = 58, SCHISM_SCANCODE_F2 = 59, SCHISM_SCANCODE_F3 = 60, SCHISM_SCANCODE_F4 = 61, SCHISM_SCANCODE_F5 = 62, SCHISM_SCANCODE_F6 = 63, SCHISM_SCANCODE_F7 = 64, SCHISM_SCANCODE_F8 = 65, SCHISM_SCANCODE_F9 = 66, SCHISM_SCANCODE_F10 = 67, SCHISM_SCANCODE_F11 = 68, SCHISM_SCANCODE_F12 = 69, SCHISM_SCANCODE_PRINTSCREEN = 70, SCHISM_SCANCODE_SCROLLLOCK = 71, SCHISM_SCANCODE_PAUSE = 72, SCHISM_SCANCODE_INSERT = 73, SCHISM_SCANCODE_HOME = 74, SCHISM_SCANCODE_PAGEUP = 75, SCHISM_SCANCODE_DELETE = 76, SCHISM_SCANCODE_END = 77, SCHISM_SCANCODE_PAGEDOWN = 78, SCHISM_SCANCODE_RIGHT = 79, SCHISM_SCANCODE_LEFT = 80, SCHISM_SCANCODE_DOWN = 81, SCHISM_SCANCODE_UP = 82, SCHISM_SCANCODE_NUMLOCKCLEAR = 83, SCHISM_SCANCODE_KP_DIVIDE = 84, SCHISM_SCANCODE_KP_MULTIPLY = 85, SCHISM_SCANCODE_KP_MINUS = 86, SCHISM_SCANCODE_KP_PLUS = 87, SCHISM_SCANCODE_KP_ENTER = 88, SCHISM_SCANCODE_KP_1 = 89, SCHISM_SCANCODE_KP_2 = 90, SCHISM_SCANCODE_KP_3 = 91, SCHISM_SCANCODE_KP_4 = 92, SCHISM_SCANCODE_KP_5 = 93, SCHISM_SCANCODE_KP_6 = 94, SCHISM_SCANCODE_KP_7 = 95, SCHISM_SCANCODE_KP_8 = 96, SCHISM_SCANCODE_KP_9 = 97, SCHISM_SCANCODE_KP_0 = 98, SCHISM_SCANCODE_KP_PERIOD = 99, SCHISM_SCANCODE_NONUSBACKSLASH = 100, SCHISM_SCANCODE_APPLICATION = 101, /* windows contextual menu, compose */ SCHISM_SCANCODE_POWER = 102, SCHISM_SCANCODE_KP_EQUALS = 103, SCHISM_SCANCODE_F13 = 104, SCHISM_SCANCODE_F14 = 105, SCHISM_SCANCODE_F15 = 106, SCHISM_SCANCODE_F16 = 107, SCHISM_SCANCODE_F17 = 108, SCHISM_SCANCODE_F18 = 109, SCHISM_SCANCODE_F19 = 110, SCHISM_SCANCODE_F20 = 111, SCHISM_SCANCODE_F21 = 112, SCHISM_SCANCODE_F22 = 113, SCHISM_SCANCODE_F23 = 114, SCHISM_SCANCODE_F24 = 115, SCHISM_SCANCODE_EXECUTE = 116, SCHISM_SCANCODE_HELP = 117, /* AL Integrated Help Center */ SCHISM_SCANCODE_MENU = 118, /* Menu (show menu) */ SCHISM_SCANCODE_SELECT = 119, SCHISM_SCANCODE_STOP = 120, /* AC Stop */ SCHISM_SCANCODE_AGAIN = 121, /* AC Redo/Repeat */ SCHISM_SCANCODE_UNDO = 122, /* AC Undo */ SCHISM_SCANCODE_CUT = 123, /* AC Cut */ SCHISM_SCANCODE_COPY = 124, /* AC Copy */ SCHISM_SCANCODE_PASTE = 125, /* AC Paste */ SCHISM_SCANCODE_FIND = 126, /* AC Find */ SCHISM_SCANCODE_MUTE = 127, SCHISM_SCANCODE_VOLUMEUP = 128, SCHISM_SCANCODE_VOLUMEDOWN = 129, /* not sure whether there's a reason to enable these */ /* SCHISM_SCANCODE_LOCKINGCAPSLOCK = 130, */ /* SCHISM_SCANCODE_LOCKINGNUMLOCK = 131, */ /* SCHISM_SCANCODE_LOCKINGSCROLLLOCK = 132, */ SCHISM_SCANCODE_KP_COMMA = 133, SCHISM_SCANCODE_KP_EQUALSAS400 = 134, #if 0 // don't care SCHISM_SCANCODE_INTERNATIONAL1 = 135, /* used on Asian keyboards */ SCHISM_SCANCODE_INTERNATIONAL2 = 136, SCHISM_SCANCODE_INTERNATIONAL3 = 137, /* Yen */ SCHISM_SCANCODE_INTERNATIONAL4 = 138, SCHISM_SCANCODE_INTERNATIONAL5 = 139, SCHISM_SCANCODE_INTERNATIONAL6 = 140, SCHISM_SCANCODE_INTERNATIONAL7 = 141, SCHISM_SCANCODE_INTERNATIONAL8 = 142, SCHISM_SCANCODE_INTERNATIONAL9 = 143, SCHISM_SCANCODE_LANG1 = 144, /* Hangul/English toggle */ SCHISM_SCANCODE_LANG2 = 145, /* Hanja conversion */ SCHISM_SCANCODE_LANG3 = 146, /* Katakana */ SCHISM_SCANCODE_LANG4 = 147, /* Hiragana */ SCHISM_SCANCODE_LANG5 = 148, /* Zenkaku/Hankaku */ SCHISM_SCANCODE_LANG6 = 149, /* reserved */ SCHISM_SCANCODE_LANG7 = 150, /* reserved */ SCHISM_SCANCODE_LANG8 = 151, /* reserved */ SCHISM_SCANCODE_LANG9 = 152, /* reserved */ #endif SCHISM_SCANCODE_ALTERASE = 153, /* Erase-Eaze */ SCHISM_SCANCODE_SYSREQ = 154, SCHISM_SCANCODE_CANCEL = 155, /* AC Cancel */ SCHISM_SCANCODE_CLEAR = 156, SCHISM_SCANCODE_PRIOR = 157, SCHISM_SCANCODE_RETURN2 = 158, SCHISM_SCANCODE_SEPARATOR = 159, SCHISM_SCANCODE_OUT = 160, SCHISM_SCANCODE_OPER = 161, SCHISM_SCANCODE_CLEARAGAIN = 162, SCHISM_SCANCODE_CRSEL = 163, SCHISM_SCANCODE_EXSEL = 164, SCHISM_SCANCODE_KP_00 = 176, SCHISM_SCANCODE_KP_000 = 177, SCHISM_SCANCODE_THOUSANDSSEPARATOR = 178, SCHISM_SCANCODE_DECIMALSEPARATOR = 179, SCHISM_SCANCODE_CURRENCYUNIT = 180, SCHISM_SCANCODE_CURRENCYSUBUNIT = 181, SCHISM_SCANCODE_KP_LEFTPAREN = 182, SCHISM_SCANCODE_KP_RIGHTPAREN = 183, SCHISM_SCANCODE_KP_LEFTBRACE = 184, SCHISM_SCANCODE_KP_RIGHTBRACE = 185, SCHISM_SCANCODE_KP_TAB = 186, SCHISM_SCANCODE_KP_BACKSPACE = 187, SCHISM_SCANCODE_KP_A = 188, SCHISM_SCANCODE_KP_B = 189, SCHISM_SCANCODE_KP_C = 190, SCHISM_SCANCODE_KP_D = 191, SCHISM_SCANCODE_KP_E = 192, SCHISM_SCANCODE_KP_F = 193, SCHISM_SCANCODE_KP_XOR = 194, SCHISM_SCANCODE_KP_POWER = 195, SCHISM_SCANCODE_KP_PERCENT = 196, SCHISM_SCANCODE_KP_LESS = 197, SCHISM_SCANCODE_KP_GREATER = 198, SCHISM_SCANCODE_KP_AMPERSAND = 199, SCHISM_SCANCODE_KP_DBLAMPERSAND = 200, SCHISM_SCANCODE_KP_VERTICALBAR = 201, SCHISM_SCANCODE_KP_DBLVERTICALBAR = 202, SCHISM_SCANCODE_KP_COLON = 203, SCHISM_SCANCODE_KP_HASH = 204, SCHISM_SCANCODE_KP_SPACE = 205, SCHISM_SCANCODE_KP_AT = 206, SCHISM_SCANCODE_KP_EXCLAM = 207, SCHISM_SCANCODE_KP_MEMSTORE = 208, SCHISM_SCANCODE_KP_MEMRECALL = 209, SCHISM_SCANCODE_KP_MEMCLEAR = 210, SCHISM_SCANCODE_KP_MEMADD = 211, SCHISM_SCANCODE_KP_MEMSUBTRACT = 212, SCHISM_SCANCODE_KP_MEMMULTIPLY = 213, SCHISM_SCANCODE_KP_MEMDIVIDE = 214, SCHISM_SCANCODE_KP_PLUSMINUS = 215, SCHISM_SCANCODE_KP_CLEAR = 216, SCHISM_SCANCODE_KP_CLEARENTRY = 217, SCHISM_SCANCODE_KP_BINARY = 218, SCHISM_SCANCODE_KP_OCTAL = 219, SCHISM_SCANCODE_KP_DECIMAL = 220, SCHISM_SCANCODE_KP_HEXADECIMAL = 221, SCHISM_SCANCODE_LCTRL = 224, SCHISM_SCANCODE_LSHIFT = 225, SCHISM_SCANCODE_LALT = 226, /* alt, option */ SCHISM_SCANCODE_LGUI = 227, /* windows, command (apple), meta */ SCHISM_SCANCODE_RCTRL = 228, SCHISM_SCANCODE_RSHIFT = 229, SCHISM_SCANCODE_RALT = 230, /* alt gr, option */ SCHISM_SCANCODE_RGUI = 231, /* windows, command (apple), meta */ }; typedef uint32_t schism_scancode_t; #define SCHISM_KEYSYM_SCANCODE_MASK (1<<30) #define SCHISM_SCANCODE_TO_KEYSYM(X) (X | SCHISM_KEYSYM_SCANCODE_MASK) enum { SCHISM_KEYSYM_UNKNOWN = 0, SCHISM_KEYSYM_RETURN = '\r', SCHISM_KEYSYM_ESCAPE = '\x1B', SCHISM_KEYSYM_BACKSPACE = '\b', SCHISM_KEYSYM_TAB = '\t', SCHISM_KEYSYM_SPACE = ' ', SCHISM_KEYSYM_EXCLAIM = '!', SCHISM_KEYSYM_QUOTEDBL = '"', SCHISM_KEYSYM_HASH = '#', SCHISM_KEYSYM_PERCENT = '%', SCHISM_KEYSYM_DOLLAR = '$', SCHISM_KEYSYM_AMPERSAND = '&', SCHISM_KEYSYM_QUOTE = '\'', SCHISM_KEYSYM_LEFTPAREN = '(', SCHISM_KEYSYM_RIGHTPAREN = ')', SCHISM_KEYSYM_ASTERISK = '*', SCHISM_KEYSYM_PLUS = '+', SCHISM_KEYSYM_COMMA = ',', SCHISM_KEYSYM_MINUS = '-', SCHISM_KEYSYM_PERIOD = '.', SCHISM_KEYSYM_SLASH = '/', SCHISM_KEYSYM_0 = '0', SCHISM_KEYSYM_1 = '1', SCHISM_KEYSYM_2 = '2', SCHISM_KEYSYM_3 = '3', SCHISM_KEYSYM_4 = '4', SCHISM_KEYSYM_5 = '5', SCHISM_KEYSYM_6 = '6', SCHISM_KEYSYM_7 = '7', SCHISM_KEYSYM_8 = '8', SCHISM_KEYSYM_9 = '9', SCHISM_KEYSYM_COLON = ':', SCHISM_KEYSYM_SEMICOLON = ';', SCHISM_KEYSYM_LESS = '<', SCHISM_KEYSYM_EQUALS = '=', SCHISM_KEYSYM_GREATER = '>', SCHISM_KEYSYM_QUESTION = '?', SCHISM_KEYSYM_AT = '@', /* Skip uppercase letters */ SCHISM_KEYSYM_LEFTBRACKET = '[', SCHISM_KEYSYM_BACKSLASH = '\\', SCHISM_KEYSYM_RIGHTBRACKET = ']', SCHISM_KEYSYM_CARET = '^', SCHISM_KEYSYM_UNDERSCORE = '_', SCHISM_KEYSYM_BACKQUOTE = '`', SCHISM_KEYSYM_a = 'a', SCHISM_KEYSYM_b = 'b', SCHISM_KEYSYM_c = 'c', SCHISM_KEYSYM_d = 'd', SCHISM_KEYSYM_e = 'e', SCHISM_KEYSYM_f = 'f', SCHISM_KEYSYM_g = 'g', SCHISM_KEYSYM_h = 'h', SCHISM_KEYSYM_i = 'i', SCHISM_KEYSYM_j = 'j', SCHISM_KEYSYM_k = 'k', SCHISM_KEYSYM_l = 'l', SCHISM_KEYSYM_m = 'm', SCHISM_KEYSYM_n = 'n', SCHISM_KEYSYM_o = 'o', SCHISM_KEYSYM_p = 'p', SCHISM_KEYSYM_q = 'q', SCHISM_KEYSYM_r = 'r', SCHISM_KEYSYM_s = 's', SCHISM_KEYSYM_t = 't', SCHISM_KEYSYM_u = 'u', SCHISM_KEYSYM_v = 'v', SCHISM_KEYSYM_w = 'w', SCHISM_KEYSYM_x = 'x', SCHISM_KEYSYM_y = 'y', SCHISM_KEYSYM_z = 'z', SCHISM_KEYSYM_CAPSLOCK = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_CAPSLOCK), SCHISM_KEYSYM_F1 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F1), SCHISM_KEYSYM_F2 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F2), SCHISM_KEYSYM_F3 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F3), SCHISM_KEYSYM_F4 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F4), SCHISM_KEYSYM_F5 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F5), SCHISM_KEYSYM_F6 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F6), SCHISM_KEYSYM_F7 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F7), SCHISM_KEYSYM_F8 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F8), SCHISM_KEYSYM_F9 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F9), SCHISM_KEYSYM_F10 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F10), SCHISM_KEYSYM_F11 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F11), SCHISM_KEYSYM_F12 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F12), SCHISM_KEYSYM_PRINTSCREEN = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_PRINTSCREEN), SCHISM_KEYSYM_SCROLLLOCK = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_SCROLLLOCK), SCHISM_KEYSYM_PAUSE = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_PAUSE), SCHISM_KEYSYM_INSERT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_INSERT), SCHISM_KEYSYM_HOME = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_HOME), SCHISM_KEYSYM_PAGEUP = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_PAGEUP), SCHISM_KEYSYM_DELETE = '\x7F', SCHISM_KEYSYM_END = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_END), SCHISM_KEYSYM_PAGEDOWN = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_PAGEDOWN), SCHISM_KEYSYM_RIGHT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_RIGHT), SCHISM_KEYSYM_LEFT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_LEFT), SCHISM_KEYSYM_DOWN = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_DOWN), SCHISM_KEYSYM_UP = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_UP), SCHISM_KEYSYM_NUMLOCKCLEAR = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_NUMLOCKCLEAR), SCHISM_KEYSYM_KP_DIVIDE = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_DIVIDE), SCHISM_KEYSYM_KP_MULTIPLY = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_MULTIPLY), SCHISM_KEYSYM_KP_MINUS = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_MINUS), SCHISM_KEYSYM_KP_PLUS = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_PLUS), SCHISM_KEYSYM_KP_ENTER = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_ENTER), SCHISM_KEYSYM_KP_1 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_1), SCHISM_KEYSYM_KP_2 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_2), SCHISM_KEYSYM_KP_3 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_3), SCHISM_KEYSYM_KP_4 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_4), SCHISM_KEYSYM_KP_5 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_5), SCHISM_KEYSYM_KP_6 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_6), SCHISM_KEYSYM_KP_7 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_7), SCHISM_KEYSYM_KP_8 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_8), SCHISM_KEYSYM_KP_9 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_9), SCHISM_KEYSYM_KP_0 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_0), SCHISM_KEYSYM_KP_PERIOD = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_PERIOD), SCHISM_KEYSYM_APPLICATION = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_APPLICATION), SCHISM_KEYSYM_POWER = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_POWER), SCHISM_KEYSYM_KP_EQUALS = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_EQUALS), SCHISM_KEYSYM_F13 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F13), SCHISM_KEYSYM_F14 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F14), SCHISM_KEYSYM_F15 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F15), SCHISM_KEYSYM_F16 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F16), SCHISM_KEYSYM_F17 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F17), SCHISM_KEYSYM_F18 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F18), SCHISM_KEYSYM_F19 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F19), SCHISM_KEYSYM_F20 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F20), SCHISM_KEYSYM_F21 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F21), SCHISM_KEYSYM_F22 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F22), SCHISM_KEYSYM_F23 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F23), SCHISM_KEYSYM_F24 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_F24), SCHISM_KEYSYM_EXECUTE = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_EXECUTE), SCHISM_KEYSYM_HELP = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_HELP), SCHISM_KEYSYM_MENU = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_MENU), SCHISM_KEYSYM_SELECT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_SELECT), SCHISM_KEYSYM_STOP = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_STOP), SCHISM_KEYSYM_AGAIN = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_AGAIN), SCHISM_KEYSYM_UNDO = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_UNDO), SCHISM_KEYSYM_CUT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_CUT), SCHISM_KEYSYM_COPY = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_COPY), SCHISM_KEYSYM_PASTE = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_PASTE), SCHISM_KEYSYM_FIND = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_FIND), SCHISM_KEYSYM_MUTE = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_MUTE), SCHISM_KEYSYM_VOLUMEUP = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_VOLUMEUP), SCHISM_KEYSYM_VOLUMEDOWN = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_VOLUMEDOWN), SCHISM_KEYSYM_KP_COMMA = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_COMMA), SCHISM_KEYSYM_KP_EQUALSAS400 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_EQUALSAS400), SCHISM_KEYSYM_ALTERASE = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_ALTERASE), SCHISM_KEYSYM_SYSREQ = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_SYSREQ), SCHISM_KEYSYM_CANCEL = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_CANCEL), SCHISM_KEYSYM_CLEAR = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_CLEAR), SCHISM_KEYSYM_PRIOR = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_PRIOR), SCHISM_KEYSYM_RETURN2 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_RETURN2), SCHISM_KEYSYM_SEPARATOR = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_SEPARATOR), SCHISM_KEYSYM_OUT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_OUT), SCHISM_KEYSYM_OPER = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_OPER), SCHISM_KEYSYM_CLEARAGAIN = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_CLEARAGAIN), SCHISM_KEYSYM_CRSEL = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_CRSEL), SCHISM_KEYSYM_EXSEL = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_EXSEL), SCHISM_KEYSYM_KP_00 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_00), SCHISM_KEYSYM_KP_000 = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_000), SCHISM_KEYSYM_THOUSANDSSEPARATOR = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_THOUSANDSSEPARATOR), SCHISM_KEYSYM_DECIMALSEPARATOR = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_DECIMALSEPARATOR), SCHISM_KEYSYM_CURRENCYUNIT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_CURRENCYUNIT), SCHISM_KEYSYM_CURRENCYSUBUNIT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_CURRENCYSUBUNIT), SCHISM_KEYSYM_KP_LEFTPAREN = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_LEFTPAREN), SCHISM_KEYSYM_KP_RIGHTPAREN = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_RIGHTPAREN), SCHISM_KEYSYM_KP_LEFTBRACE = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_LEFTBRACE), SCHISM_KEYSYM_KP_RIGHTBRACE = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_RIGHTBRACE), SCHISM_KEYSYM_KP_TAB = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_TAB), SCHISM_KEYSYM_KP_BACKSPACE = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_BACKSPACE), SCHISM_KEYSYM_KP_A = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_A), SCHISM_KEYSYM_KP_B = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_B), SCHISM_KEYSYM_KP_C = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_C), SCHISM_KEYSYM_KP_D = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_D), SCHISM_KEYSYM_KP_E = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_E), SCHISM_KEYSYM_KP_F = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_F), SCHISM_KEYSYM_KP_XOR = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_XOR), SCHISM_KEYSYM_KP_POWER = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_POWER), SCHISM_KEYSYM_KP_PERCENT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_PERCENT), SCHISM_KEYSYM_KP_LESS = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_LESS), SCHISM_KEYSYM_KP_GREATER = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_GREATER), SCHISM_KEYSYM_KP_AMPERSAND = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_AMPERSAND), SCHISM_KEYSYM_KP_DBLAMPERSAND = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_DBLAMPERSAND), SCHISM_KEYSYM_KP_VERTICALBAR = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_VERTICALBAR), SCHISM_KEYSYM_KP_DBLVERTICALBAR = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_DBLVERTICALBAR), SCHISM_KEYSYM_KP_COLON = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_COLON), SCHISM_KEYSYM_KP_HASH = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_HASH), SCHISM_KEYSYM_KP_SPACE = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_SPACE), SCHISM_KEYSYM_KP_AT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_AT), SCHISM_KEYSYM_KP_EXCLAM = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_EXCLAM), SCHISM_KEYSYM_KP_MEMSTORE = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_MEMSTORE), SCHISM_KEYSYM_KP_MEMRECALL = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_MEMRECALL), SCHISM_KEYSYM_KP_MEMCLEAR = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_MEMCLEAR), SCHISM_KEYSYM_KP_MEMADD = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_MEMADD), SCHISM_KEYSYM_KP_MEMSUBTRACT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_MEMSUBTRACT), SCHISM_KEYSYM_KP_MEMMULTIPLY = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_MEMMULTIPLY), SCHISM_KEYSYM_KP_MEMDIVIDE = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_MEMDIVIDE), SCHISM_KEYSYM_KP_PLUSMINUS = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_PLUSMINUS), SCHISM_KEYSYM_KP_CLEAR = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_CLEAR), SCHISM_KEYSYM_KP_CLEARENTRY = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_CLEARENTRY), SCHISM_KEYSYM_KP_BINARY = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_BINARY), SCHISM_KEYSYM_KP_OCTAL = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_OCTAL), SCHISM_KEYSYM_KP_DECIMAL = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_DECIMAL), SCHISM_KEYSYM_KP_HEXADECIMAL = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_KP_HEXADECIMAL), SCHISM_KEYSYM_LCTRL = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_LCTRL), SCHISM_KEYSYM_LSHIFT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_LSHIFT), SCHISM_KEYSYM_LALT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_LALT), SCHISM_KEYSYM_LGUI = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_LGUI), SCHISM_KEYSYM_RCTRL = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_RCTRL), SCHISM_KEYSYM_RSHIFT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_RSHIFT), SCHISM_KEYSYM_RALT = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_RALT), SCHISM_KEYSYM_RGUI = SCHISM_SCANCODE_TO_KEYSYM(SCHISM_SCANCODE_RGUI), }; typedef uint32_t schism_keysym_t; enum { SCHISM_KEYMOD_NONE = (0), SCHISM_KEYMOD_LCTRL = (1u << 0), SCHISM_KEYMOD_RCTRL = (1u << 1), SCHISM_KEYMOD_LSHIFT = (1u << 2), SCHISM_KEYMOD_RSHIFT = (1u << 3), SCHISM_KEYMOD_LALT = (1u << 4), SCHISM_KEYMOD_RALT = (1u << 5), SCHISM_KEYMOD_LGUI = (1u << 6), SCHISM_KEYMOD_RGUI = (1u << 7), SCHISM_KEYMOD_NUM = (1u << 8), SCHISM_KEYMOD_CAPS = (1u << 9), SCHISM_KEYMOD_MODE = (1u << 10), SCHISM_KEYMOD_SCROLL = (1u << 11), SCHISM_KEYMOD_CAPS_PRESSED = (1u << 12), SCHISM_KEYMOD_CTRL = (SCHISM_KEYMOD_LCTRL | SCHISM_KEYMOD_RCTRL), SCHISM_KEYMOD_SHIFT = (SCHISM_KEYMOD_LSHIFT | SCHISM_KEYMOD_RSHIFT), SCHISM_KEYMOD_ALT = (SCHISM_KEYMOD_LALT | SCHISM_KEYMOD_RALT), SCHISM_KEYMOD_GUI = (SCHISM_KEYMOD_LGUI | SCHISM_KEYMOD_RGUI), }; typedef uint16_t schism_keymod_t; enum key_state { KEY_PRESS = 0, KEY_RELEASE, }; enum mouse_state { MOUSE_NONE = 0, MOUSE_CLICK, MOUSE_SCROLL_UP, MOUSE_SCROLL_DOWN, MOUSE_DBLCLICK, }; enum mouse_button { MOUSE_BUTTON_LEFT = 0, MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_RIGHT, }; struct key_event { schism_keysym_t sym; /* A keycode, can be Unicode */ schism_keysym_t orig_sym; /* `sym' from before key_translate warps it */ schism_scancode_t scancode; /* Locale-independent key locations */ schism_keymod_t mod; /* current key modifiers */ const char* text; /* text input, if any. can be NULL */ enum key_state state; enum mouse_state mouse; enum mouse_button mouse_button; int midi_note; int midi_channel; int midi_volume; /* -1 for not a midi key otherwise 0...128 */ int midi_bend; /* normally 0; -8192 to +8192 */ unsigned int sx, sy; /* start x and y position (character) */ unsigned int x, hx, fx; /* x position of mouse (character, halfcharacter, fine) */ unsigned int y, fy; /* y position of mouse (character, fine) */ unsigned int rx, ry; /* x/y resolution */ int is_repeat; int on_target; int is_synthetic; /* 1 came from paste */ }; int numeric_key_event(struct key_event *k, int kponly); char *get_note_string(int note, char *buf); /* "C-5" or "G#4" */ char *get_note_string_short(int note, char *buf); /* "c5" or "G4" */ char *get_volume_string(int volume, int volume_effect, char *buf); char get_effect_char(int command); int get_effect_number(char effect); int get_ptm_effect_number(char effect); /* values for kbd_sharp_flat_toggle */ typedef enum { KBD_SHARP_FLAT_TOGGLE = -1, KBD_SHARP_FLAT_SHARPS = 0, KBD_SHARP_FLAT_FLATS = 1, } kbd_sharp_flat_t; void kbd_init(void); kbd_sharp_flat_t kbd_sharp_flat_state(void); void kbd_sharp_flat_toggle(kbd_sharp_flat_t e); int kbd_get_effect_number(struct key_event *k); int kbd_char_to_hex(struct key_event *k); int kbd_char_to_99(struct key_event *k); int kbd_get_current_octave(void); void kbd_set_current_octave(int new_octave); int kbd_get_note(struct key_event *k); int kbd_get_alnum(struct key_event *k); void kbd_key_translate(struct key_event *k); /* -------------------------------------------- */ /* key repeat */ int kbd_key_repeat_enabled(void); void kbd_handle_key_repeat(void); void kbd_cache_key_repeat(struct key_event* kk); void kbd_empty_key_repeat(void); /* use 0 for delay to (re)set the default rate. */ void kbd_set_key_repeat(int delay, int rate); #endif /* SCHISM_KEYBOARD_H_ */ schismtracker-20250313/include/loadso.h000066400000000000000000000025761476471630300177170ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_LOADSO_H_ #define SCHISM_LOADSO_H_ #include "headers.h" void *loadso_object_load(const char *name); void loadso_object_unload(void *object); void *loadso_function_load(void *object, const char *name); // an additive to the above, which automatically uses libtool's // versioning. void *library_load(const char *name, int current, int age); #endif /* SCHISM_LOADSO_H_ */ schismtracker-20250313/include/log.h000066400000000000000000000030751476471630300172120ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_LOG_H_ #define SCHISM_LOG_H_ #include "headers.h" #include "util.h" void log_nl(void); void log_append(int color, int must_free, const char *text); void log_append2(int bios_font, int color, int must_free, const char *text); void log_appendf(int color, const char *format, ...) SCHISM_FORMAT_PRINTF(2, 3); void log_underline(int chars); void log_perror(const char *prefix); void status_text_flash(const char *format, ...) SCHISM_FORMAT_PRINTF(1, 2); void status_text_flash_bios(const char *format, ...) SCHISM_FORMAT_PRINTF(1, 2); #endif /* SCHISM_LOG_H_ */ schismtracker-20250313/include/mem.h000066400000000000000000000026471476471630300172130ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_MEM_H_ #define SCHISM_MEM_H_ #include "headers.h" extern void *mem_alloc(size_t) SCHISM_MALLOC SCHISM_ALLOC_SIZE(1); extern void *mem_calloc(size_t, size_t) SCHISM_MALLOC SCHISM_ALLOC_SIZE_EX(1, 2); extern char *str_dup(const char *) SCHISM_MALLOC; extern char *strn_dup(const char *, size_t) SCHISM_MALLOC SCHISM_ALLOC_SIZE(2); extern void *mem_realloc(void *,size_t) SCHISM_MALLOC SCHISM_ALLOC_SIZE(2); #endif schismtracker-20250313/include/midi.h000066400000000000000000000127301476471630300173510ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_MIDI_H_ #define SCHISM_MIDI_H_ struct midi_provider; struct midi_port; #define MIDI_PORT_CAN_SCHEDULE 1 struct midi_driver { uint32_t flags; void (*poll)(struct midi_provider *m); int (*thread)(struct midi_provider *m); int (*enable)(struct midi_port *d); int (*disable)(struct midi_port *d); void (*send)(struct midi_port *d, const unsigned char *seq, uint32_t len, uint32_t delay); void (*drain)(struct midi_port *d); }; // implemented in each backend struct mt_thread; struct midi_provider { char *name; void (*poll)(struct midi_provider *); struct mt_thread *thread; volatile int cancelled; struct midi_provider *next; /* forwarded; don't touch */ int (*enable)(struct midi_port *d); int (*disable)(struct midi_port *d); void (*send_now)(struct midi_port *d, const unsigned char *seq, uint32_t len, uint32_t delay); void (*send_later)(struct midi_port *d, const unsigned char *seq, uint32_t len, uint32_t delay); void (*drain)(struct midi_port *d); }; enum { MIDI_INPUT = 1, MIDI_OUTPUT = 2, }; struct midi_port { int io, iocap; char *name; int num; void *userdata; int free_userdata; int (*enable)(struct midi_port *d); int (*disable)(struct midi_port *d); void (*send_now)(struct midi_port *d, const unsigned char *seq, uint32_t len, uint32_t delay); void (*send_later)(struct midi_port *d, const unsigned char *seq, uint32_t len, uint32_t delay); void (*drain)(struct midi_port *d); struct midi_provider *provider; }; /* schism calls these directly */ int midi_engine_start(void); void midi_engine_reset(void); void midi_engine_stop(void); void midi_engine_poll_ports(void); /* some parts of schism call this; it means "immediately" */ void midi_send_now(const unsigned char *seq, uint32_t len); /* ... but the player calls this */ void midi_send_buffer(const unsigned char *data, uint32_t len, uint32_t pos); void midi_send_flush(void); /* used by the audio thread */ int midi_need_flush(void); /* from Schism event handler */ union schism_event; int midi_engine_handle_event(union schism_event *ev); 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, const 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); int midi_port_enable(struct midi_port *p); int midi_port_disable(struct midi_port *p); /* only call these if the event isn't really MIDI but you want most of the system to act like it is... midi drivers should never all these... */ enum midi_note { MIDI_NOTEOFF, MIDI_NOTEON, MIDI_KEYPRESS, }; void midi_event_note(enum midi_note mnstatus, int channel, int note, int velocity); void midi_event_controller(int channel, int param, int value); void midi_event_program(int channel, int value); void midi_event_aftertouch(int channel, int value); void midi_event_pitchbend(int channel, int value); void midi_event_tick(void); void midi_event_sysex(const unsigned char *data, uint32_t len); void midi_event_system(int argv, int param); /* midi drivers call this when they received an event */ void midi_received_cb(struct midi_port *src, unsigned char *data, uint32_t len); // lost child uint8_t midi_event_length(uint8_t first_byte); int ip_midi_setup(void); // USE_NETWORK void ip_midi_setports(int n); // USE_NETWORK int ip_midi_getports(void); // USE_NETWORK int oss_midi_setup(void); // USE_OSS int alsa_midi_setup(void); // USE_ALSA int jack_midi_setup(void); // USE_JACK int win32mm_midi_setup(void); // SCHISM_WIN32 int macosx_midi_setup(void); // SCHISM_MACOSX /* called by audio system when buffer stuff change */ void midi_queue_alloc(int buffer_size, int channels, int samples_per_second); /* MIDI_PITCH_BEND is defined by OSS -- maybe these need more specific names? */ #define MIDI_TICK_QUANTIZE 0x00000001 #define MIDI_BASE_PROGRAM1 0x00000002 #define MIDI_RECORD_NOTEOFF 0x00000004 #define MIDI_RECORD_VELOCITY 0x00000008 #define MIDI_RECORD_AFTERTOUCH 0x00000010 #define MIDI_CUT_NOTE_OFF 0x00000020 #define MIDI_PITCHBEND 0x00000040 #define MIDI_DISABLE_RECORD 0x00010000 extern int midi_flags, midi_pitch_depth, midi_amplification, midi_c5note; #endif /* SCHISM_MIDI_H_ */ schismtracker-20250313/include/mt.h000066400000000000000000000046401476471630300170500ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* "mt" stands for "multithreading" ;) * * this was heavily "inspired" by the SDL 2 threading API, which has remained * largely unchanged in SDL 3. if you don't know what a certain function does * or even what a type is for (like the condition variables) see SDL's API * docs for how all this stuff goes together. */ #ifndef SCHISM_MT_H_ #define SCHISM_MT_H_ #include "headers.h" typedef uint64_t mt_thread_id_t; /* private to each backend */ typedef struct mt_thread mt_thread_t; typedef struct mt_mutex mt_mutex_t; typedef struct mt_cond mt_cond_t; typedef int (*schism_thread_function_t)(void *userdata); enum { MT_THREAD_PRIORITY_LOW = 0, MT_THREAD_PRIORITY_NORMAL, MT_THREAD_PRIORITY_HIGH, MT_THREAD_PRIORITY_TIME_CRITICAL, }; mt_thread_t *mt_thread_create(schism_thread_function_t func, const char *name, void *userdata); void mt_thread_wait(mt_thread_t *thread, int *status); void mt_thread_set_priority(int priority); mt_thread_id_t mt_thread_id(void); mt_mutex_t *mt_mutex_create(void); void mt_mutex_delete(mt_mutex_t *mutex); void mt_mutex_lock(mt_mutex_t *mutex); void mt_mutex_unlock(mt_mutex_t *mutex); mt_cond_t *mt_cond_create(void); void mt_cond_delete(mt_cond_t *cond); void mt_cond_signal(mt_cond_t *cond); void mt_cond_wait(mt_cond_t *cond, mt_mutex_t *mutex); void mt_cond_wait_timeout(mt_cond_t *cond, mt_mutex_t *mutex, uint32_t timeout); int mt_init(void); void mt_quit(void); #endif /* SCHISM_MT_H_ */ schismtracker-20250313/include/osdefs.h000066400000000000000000000146311476471630300177140ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* OS-dependent code implementations are defined here; the files for each target OS exist in sys/blah/osdefs.c, and possibly other files as well. Only one osdefs.c should be in use at a time. */ #ifndef SCHISM_OSDEFS_H_ #define SCHISM_OSDEFS_H_ #include "headers.h" #include "events.h" /* need stat; TODO autoconf test */ #include /* roundabout way to get time_t */ /* os_sysinit: any platform-dependent setup that needs to occur directly upon startup. This code is processed right as soon as main() starts. os_event: preprocessing for events. This is used to hack in system-dependent input methods (e.g. F16 and other scancodes on OS X; Wiimote buttons; etc.) If defined, this function will be called after capturing an SDL event. A return value of 0 indicates that the event should NOT be processed by the main event handler. */ #if defined(SCHISM_WII) # define os_sysinit wii_sysinit # define os_sysexit wii_sysexit #elif defined(SCHISM_WIIU) # define os_sysinit wiiu_sysinit # define os_sysexit wiiu_sysexit #elif defined(SCHISM_WIN32) # define os_sysinit win32_sysinit # define os_sysexit win32_sysexit # define os_get_modkey win32_get_modkey # define os_fopen win32_fopen # define os_stat win32_stat # define os_mkdir win32_mkdir # define os_get_key_repeat win32_get_key_repeat # define os_show_message_box win32_show_message_box #elif defined(SCHISM_MACOSX) # define os_sysexit macosx_sysexit # define os_sysinit macosx_sysinit # define os_get_modkey macosx_get_modkey # define os_get_key_repeat macosx_get_key_repeat # define os_show_message_box macosx_show_message_box #elif defined(SCHISM_MACOS) # define os_mkdir macos_mkdir # define os_stat macos_stat # define os_show_message_box macos_show_message_box # define os_sysinit macos_sysinit # define os_get_modkey macos_get_modkey #elif defined(SCHISM_OS2) # define os_mkdir os2_mkdir # define os_stat os2_stat # define os_fopen os2_fopen # define os_get_key_repeat os2_get_key_repeat # define os_show_message_box os2_show_message_box #endif #if defined(SCHISM_WIN32) # define os_run_hook win32_run_hook #elif defined(HAVE_EXECL) && defined(HAVE_FORK) # define os_run_hook posix_run_hook #endif #ifndef os_sysinit # define os_sysinit(pargc,argv) #endif #ifndef os_sysexit # define os_sysexit() #endif #ifndef os_get_modkey #define os_get_modkey(m) #endif #ifndef os_fopen # define os_fopen fopen #endif #ifndef os_stat # define os_stat stat #endif #ifndef os_mkdir # define os_mkdir mkdir #endif #ifndef os_run_hook # define os_run_hook(a,b,c) 0 #endif #ifndef os_get_key_repeat # define os_get_key_repeat(pdelay, prate) (0) #endif #ifndef os_get_locale_format # define os_get_locale_format(pdate, ptime) (0) #endif #ifndef os_show_message_box # define os_show_message_box(title, text) ((void)printf("%s: %s\n", title, text)) #endif /* Whether or not to compile ANSI variants of functions; we only actually do * this on IA-32 because of Win9x, all other architectures are WinNT-only. * * Note: mingw-w64 doesn't support anything below XP. Maybe we should disable * ANSI there as well? */ #ifdef SCHISM_WIN32 # if defined(_M_IX86) || defined(__i386__) || defined(__i386) || defined(i386) # define SCHISM_WIN32_COMPILE_ANSI 1 # endif #endif // Implementations for the above, and more. int macosx_ibook_fnswitch(int setting); void wiiu_sysinit(int *pargc, char ***pargv); // fixup HOME envvar void wiiu_sysexit(void); void wii_sysinit(int *pargc, char ***pargv); // set up filesystem void wii_sysexit(void); // close filesystem int win32_event(schism_event_t *event); void win32_sysinit(int *pargc, char ***pargv); void win32_sysexit(void); void win32_sdlinit(void); void win32_get_modkey(schism_keymod_t *m); void win32_filecreated_callback(const char *filename); void win32_toggle_menu(void* window, int on); // window should be a pointer to the window HWND int win32_stat(const char* path, struct stat* st); int win32_mkdir(const char* path, mode_t mode); FILE* win32_fopen(const char* path, const char* flags); #define win32_wmkdir(path, mode) _wmkdir(path) int win32_run_hook(const char *dir, const char *name, const char *maybe_arg); int win32_get_key_repeat(int *pdelay, int *prate); void win32_show_message_box(const char *title, const char *text); int win32_audio_lookup_device_name(const void *nameguid, const uint32_t *waveoutdevid, char **result); int win32_ntver_atleast(int major, int minor, int build); // audio-dsound.c int win32_dsound_audio_lookup_waveout_name(const uint32_t *waveoutnamev, char **result); int posix_run_hook(const char *dir, const char *name, const char *maybe_arg); int macosx_event(schism_event_t *event); void macosx_sysexit(void); void macosx_sysinit(int *pargc, char ***pargv); /* set up ibook helper */ void macosx_get_modkey(schism_keymod_t *m); int macosx_get_key_repeat(int *pdelay, int *prate); char *macosx_get_application_support_dir(void); void macosx_show_message_box(const char *title, const char *text); int macos_mkdir(const char *path, mode_t mode); int macos_stat(const char *file, struct stat *st); void macos_show_message_box(const char *title, const char *text); void macos_sysinit(int *pargc, char ***pargv); void macos_get_modkey(schism_keymod_t *mk); int x11_event(schism_event_t *event); int os2_stat(const char* path, struct stat* st); int os2_mkdir(const char* path, mode_t mode); FILE* os2_fopen(const char* path, const char* flags); int os2_get_key_repeat(int *pdelay, int *prate); void os2_show_message_box(const char *title, const char *text); #endif /* SCHISM_OSDEFS_H_ */ schismtracker-20250313/include/page.h000066400000000000000000000336451476471630300173530ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* This header has all the page definitions, the kinds of interactive * widgets on each page, etc. Since this information isn't useful outside * page*.c, it's not in the main header. */ #ifndef SCHISM_PAGE_H_ #define SCHISM_PAGE_H_ /* How much to scroll. */ #define MOUSE_SCROLL_LINES 3 /* --------------------------------------------------------------------- */ /* help text */ /* NOTE: this enum should be in the same order as helptexts in Makefile.am */ enum { HELP_GLOBAL, /* needs to be first! */ HELP_COPYRIGHT, HELP_INFO_PAGE, HELP_INSTRUMENT_LIST, HELP_MESSAGE_EDITOR, HELP_MIDI_OUTPUT, HELP_ORDERLIST_PANNING, HELP_ORDERLIST_VOLUME, HELP_PATTERN_EDITOR, HELP_ADLIB_SAMPLE, HELP_SAMPLE_LIST, HELP_PALETTES, HELP_TIME_INFORMATION, HELP_NUM_ITEMS /* needs to be last! */ }; extern const char *help_text[HELP_NUM_ITEMS]; /* --------------------------------------------------------------------- */ /* there's a value in this enum for each kind of widget... */ enum widget_type { WIDGET_TOGGLE, WIDGET_MENUTOGGLE, WIDGET_BUTTON, WIDGET_TOGGLEBUTTON, WIDGET_TEXTENTRY, WIDGET_NUMENTRY, WIDGET_THUMBBAR, WIDGET_BITSET, WIDGET_PANBAR, /* this last one is for anything that doesn't fit some standard type, like the sample list, envelope editor, etc.; a widget of this type is just a placeholder so page.c knows there's something going on. */ WIDGET_OTHER /* sample list, envelopes, etc. */ }; /* --------------------------------------------------------------------- */ /* every widget in the enum has a corresponding struct here. the notes * before each widget indicate what keypresses are trapped in page.c for * it, and what happens. * note that all widget types (except WIDGET_OTHER) trap the enter key for the * activate callback. */ /* space -> state changed; cb triggered */ struct widget_toggle { int state; /* 0 = off, 1 = on */ }; /* space -> state changed; cb triggered */ struct widget_menutoggle { int state; /* 0, 1, ..., num_choices - 1, num_choices */ const char *const *choices; int num_choices; const char *activation_keys; }; /* enter -> cb triggered */ struct widget_button { const char *text; int padding; }; /* enter -> state changed; cb triggered */ struct widget_togglebutton { const char *text; int padding; int state; /* 0 = off, 1 = on */ const int *group; }; /* backspace -> truncated; changed cb triggered * ctrl-bs -> cleared; changed cb triggered * -> appended; changed cb triggered * (the callback isn't triggered unless something really changed) * left/right -> cursor_pos changed; no cb triggered * * - if (max_length > (width - 1)) the text scrolls * - cursor_pos is set to the end of the text when the widget is focused */ struct widget_textentry { char *text; int max_length; int firstchar; /* first visible character (generally 0) */ int cursor_pos; /* 0 = first character */ }; /* <0-9> -> digit @ cursor_pos changed; cursor_pos increased; cb triggered * left/right -> cursor_pos changed; cb NOT triggered. * +/- -> value increased/decreased; cb triggered * cursor_pos for this widget is a pointer so that multiple numbers that * are all lined up can share the same position. */ struct widget_numentry { int min; int max; int value; int *cursor_pos; int (*handle_unknown_key)(struct key_event *k); int reverse; }; /* left/right -> value changed; cb triggered * ctrl-left/right -> value changed 4x; cb triggered * shift-left/right -> value changed 2x; cb triggered * home/end -> value set to min/max; cb triggered * <0-9> -> prompt for new number; value changed; cb triggered */ struct widget_thumbbar { /* pretty much the same as the numentry, just without the cursor * position field... (NOTE - don't rearrange the order of the * fields in either of these; some code depends on them being * the same) */ int min; int max; int value; /* this is currently only used with the midi thumbbars on the ins. list + pitch page. if * this is non-NULL, and value == {min,max}, the text is drawn instead of the thumbbar. */ const char *text_at_min, *text_at_max; }; struct widget_bitset { /* A widget for controlling individual bits */ int nbits; int value; int *cursor_pos; const char* bits_on; const char* bits_off; const char* activation_keys; }; /* special case of the thumbbar; range goes from 0 to 64. if mute is * set, the bar is replaced with the word "muted"; if surround is set, * it's replaced with the word "surround". some keys: L/M/R set the * value to 0/32/64 respectively, S switches the surround flag, and * space toggles the mute flag (and selects the next.down control!) * min/max are just for thumbbar compatibility, and are always set to * 0 and 64 respectively. * note that, due to some weirdness with IT, these draw the channel text * as well as the actual bar. */ struct widget_panbar { int min; int max; int value; int channel; unsigned int muted:1; unsigned int surround:1; }; struct widget_other { /* bah. can't do much of anything with this. * * if an 'other' type widget gets the focus, it soaks up all the * keyboard events that the main handler doesn't catch. thus * it is responsible for changing the focus to something else * (and, of course, if it doesn't ever do that, the cursor is * pretty much stuck) * this MUST be set to a valid function. * return value is 1 if the key was handled, 0 if not. */ int (*handle_key) (struct key_event * k); int (*handle_text_input) (const char* text_input); /* also the widget drawing function can't possibly know how to * draw a custom widget, so it calls this instead. * this MUST be set to a valid function. */ void (*redraw) (void); }; /* --------------------------------------------------------------------- */ /* and all the widget structs go in the union in this struct... */ union _widget_data_union { struct widget_toggle toggle; struct widget_menutoggle menutoggle; struct widget_button button; struct widget_togglebutton togglebutton; struct widget_textentry textentry; struct widget_numentry numentry; struct widget_thumbbar thumbbar; struct widget_panbar panbar; struct widget_other other; struct widget_bitset bitset; }; struct widget { enum widget_type type; union _widget_data_union d; /* for redrawing */ int x, y, width, height, depressed; int clip_start, clip_end; /* these next 5 fields specify what widget gets selected next */ struct { int up, down, left, right, tab, backtab; } next; /* called whenever the value is changed... duh ;) */ void (*changed) (void); /* called when the enter key is pressed */ void (*activate) (void); /* called by the clipboard manager; really, only "other" widgets should "override" this... */ int (*clipboard_paste)(int cb, const void *cptr); /* true if the widget accepts "text"- used for digraphs and unicode and alt+kp entry... */ int accept_text; }; /* this structure keeps all the information needed to draw a page, and a * list of all the different widgets on the page. it's the job of the page * to change the necessary information when something changes; that's * done in the page's draw and update functions. * * everything in this struct MUST be set for each page. * functions that aren't implemented should be set to NULL. */ struct page { /* the title of the page, eg "Sample List (F3)" */ const char *title; /* font editor takes over full screen */ void (*draw_full)(void); /* draw the labels, etc. that don't change */ void (*draw_const) (void); /* called after the song is changed. this is to copy the new * values from the song to the widgets on the page. */ void (*song_changed_cb) (void); /* called before widgets are drawn, mostly to fix the values * (for example, on the sample page this sets everything to * whatever values the current sample has) - this is a lousy * hack. sorry. :P */ void (*predraw_hook) (void); /* draw the parts of the page that change when the song is playing * (this is called *very* frequently) */ void (*playback_update) (void); /* this gets first shot at keys (to do unnatural overrides) */ int (*pre_handle_key) (struct key_event * k); /* this catches any keys that the main handler doesn't deal with */ void (*handle_key) (struct key_event * k); /* handle any text input events from SDL */ void (*handle_text_input) (const char* text_input); /* called when the page is set. this is for reloading the * directory in the file browsers. */ void (*set_page) (void); /* called when the song-mode changes */ void (*song_mode_changed_cb) (void); /* called by the clipboard manager */ int (*clipboard_paste)(int cb, const void *cptr); struct widget *widgets; int selected_widget; int total_widgets; /* 0 if no page-specific help */ int help_index; }; /* --------------------------------------------------------------------- */ extern struct page pages[]; /* these are updated to point to the relevant data in the selected page * (or the dialog, if one is active) */ extern struct widget *widgets; extern int *selected_widget; extern int *total_widgets; /* to make it easier to deal with either the page's widgets or the * current dialog's: * * ACTIVE_WIDGET deals with whatever widget is *really* active. * ACTIVE_PAGE_WIDGET references the *page's* idea of what's active. * (these are different if there's a dialog) */ #define ACTIVE_PAGE (pages[status.current_page]) #define ACTIVE_WIDGET (widgets[*selected_widget]) #define ACTIVE_PAGE_WIDGET (ACTIVE_PAGE.widgets[ACTIVE_PAGE.selected_widget]) extern int instrument_list_subpage; #define PAGE_INSTRUMENT_LIST instrument_list_subpage /* --------------------------------------------------------------------- */ enum page_numbers { PAGE_BLANK, PAGE_HELP, PAGE_ABOUT, PAGE_LOG, PAGE_PATTERN_EDITOR, PAGE_SAMPLE_LIST, // PAGE_INSTRUMENT_LIST doesn't exist PAGE_INFO, PAGE_CONFIG, PAGE_PREFERENCES, PAGE_MIDI, PAGE_MIDI_OUTPUT, PAGE_LOAD_MODULE, PAGE_SAVE_MODULE, PAGE_EXPORT_MODULE, PAGE_ORDERLIST_PANNING, PAGE_ORDERLIST_VOLUMES, PAGE_SONG_VARIABLES, PAGE_MESSAGE, PAGE_TIME_INFORMATION, /* don't use these directly with set_page */ PAGE_INSTRUMENT_LIST_GENERAL, PAGE_INSTRUMENT_LIST_VOLUME, PAGE_INSTRUMENT_LIST_PANNING, PAGE_INSTRUMENT_LIST_PITCH, PAGE_LOAD_SAMPLE, PAGE_LIBRARY_SAMPLE, PAGE_LOAD_INSTRUMENT, PAGE_LIBRARY_INSTRUMENT, PAGE_PALETTE_EDITOR, PAGE_FONT_EDIT, PAGE_WATERFALL, PAGE_MAX }; /* --------------------------------------------------------------------- */ void show_about(void); void blank_load_page(struct page *page); void help_load_page(struct page *page); void pattern_editor_load_page(struct page *page); void sample_list_load_page(struct page *page); void instrument_list_general_load_page(struct page *page); void instrument_list_volume_load_page(struct page *page); void instrument_list_panning_load_page(struct page *page); void instrument_list_pitch_load_page(struct page *page); void info_load_page(struct page *page); void midi_load_page(struct page *page); void midiout_load_page(struct page *page); void fontedit_load_page(struct page *page); void preferences_load_page(struct page *page); void load_module_load_page(struct page *page); void save_module_load_page(struct page *page, int do_export); void orderpan_load_page(struct page *page); void ordervol_load_page(struct page *page); void song_vars_load_page(struct page *page); void message_load_page(struct page *page); void palette_load_page(struct page *page); void log_load_page(struct page *page); void load_sample_load_page(struct page *page); void load_instrument_load_page(struct page *page); void about_load_page(struct page *page); void library_sample_load_page(struct page *page); void library_instrument_load_page(struct page *page); void config_load_page(struct page *page); void waterfall_load_page(struct page *page); void timeinfo_load_page(struct page *page); /* --------------------------------------------------------------------- */ /* page.c */ int page_is_instrument_list(int page); void update_current_instrument(void); void new_song_dialog(void); void save_song_or_save_as(void); // support for the song length dialog void show_length_dialog(const char *label, unsigned int length); /* page_patedit.c */ void update_current_row(void); void update_current_pattern(void); void pattern_editor_display_options(void); void pattern_editor_length_edit(void); int pattern_max_channels(int patno, int opt_bits[64]); /* page_orderpan.c */ void update_current_order(void); /* menu.c */ void menu_show(void); void menu_hide(void); void menu_draw(void); int menu_handle_key(struct key_event * k); /* status.c */ void status_text_redraw(void); // page_message.c void message_reset_selection(void); /* --------------------------------------------------------------------- */ /* Other UI prompt stuff. */ /* Ask for a value, like the thumbbars. */ void numprompt_create(const char *prompt, void (*finish)(int n), char initvalue); /* Ask for a sample / instrument number, like the "swap sample" dialog. */ void smpprompt_create(const char *title, const char *prompt, void (*finish)(int n)); #endif /* SCHISM_PAGE_H_ */ schismtracker-20250313/include/palettes.h000066400000000000000000000026521476471630300202520ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_PALETTES_H_ #define SCHISM_PALETTES_H_ struct it_palette { char name[21]; uint8_t colors[16][3]; }; void palette_apply(void); void palette_load_preset(int palette_index); void palette_to_string(int which, char *str_out); int set_palette_from_string(const char *str_in); extern struct it_palette palettes[]; extern uint8_t current_palette[16][3]; extern int current_palette_index; #endif /* SCHISM_PALETTES_H_ */ schismtracker-20250313/include/pattern-view.h000066400000000000000000000041601476471630300210520ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_PATTERN_VIEW_H_ #define SCHISM_PATTERN_VIEW_H_ /* NOTE: these functions need to be called with the screen LOCKED */ typedef void (*draw_channel_header_func) (int chan, int x, int y, int fg); typedef void (*draw_note_func) (int x, int y, const song_note_t *note, int cursor_pos, int fg, int bg); typedef void (*draw_mask_func) (int x, int y, int mask, int cursor_pos, int fg, int bg); #define PATTERN_VIEW(n) \ void draw_channel_header_##n(int chan, int x, int y, int fg); \ void draw_note_##n(int x, int y, const song_note_t *note, int cursor_pos, int fg, int bg); \ void draw_mask_##n(int x, int y, int mask, int cursor_pos, int fg, int bg); PATTERN_VIEW(13) PATTERN_VIEW(10) PATTERN_VIEW(8) /* note: not usable for editing as instrument numbers are not shown (thus no draw_mask) */ PATTERN_VIEW(7) PATTERN_VIEW(6) PATTERN_VIEW(3) PATTERN_VIEW(2) PATTERN_VIEW(1) #undef PATTERN_VIEW /* for the pattern editor masks (the ^^^ ^^ ^^ --- markers at the bottom) */ #define MASK_NOTE 1 /* immutable */ #define MASK_INSTRUMENT 2 #define MASK_VOLUME 4 #define MASK_EFFECT 8 #endif /* SCHISM_PATTERN_VIEW_H_ */ schismtracker-20250313/include/player/000077500000000000000000000000001476471630300175475ustar00rootroot00000000000000schismtracker-20250313/include/player/cmixer.h000066400000000000000000000040721476471630300212120ustar00rootroot00000000000000#ifndef SCHISM_PLAYER_CMIXER_H_ #define SCHISM_PLAYER_CMIXER_H_ #include "player/sndfile.h" // Stuff moved from sndfile.h #define MIXING_ATTENUATION 5 #define MIXING_CLIPMIN (-0x04000000) #define MIXING_CLIPMAX (0x03FFFFFF) #define VOLUMERAMPPRECISION 12 #define FILTERPRECISION ((sizeof(int32_t) * 8) - 8) /* faithfully stolen from openmpt */ void init_mix_buffer(int32_t *, uint32_t); void stereo_fill(int32_t *, uint32_t, int32_t *, int32_t *); void end_channel_ofs(song_voice_t *, int32_t *, uint32_t); void interleave_front_rear(int32_t *, int32_t *, uint32_t); void mono_from_stereo(int32_t *, uint32_t); uint32_t csf_create_stereo_mix(song_t *csf, uint32_t count); void setup_channel_filter(song_voice_t *pChn, int32_t reset, int32_t flt_modifier, int32_t freq); //typedef unsigned int (*convert_clip_t)(void *, int *, unsigned int, int*, int*) __attribute__((cdecl)) uint32_t clip_32_to_8(void *, int32_t *, uint32_t, int32_t *, int32_t *); uint32_t clip_32_to_16(void *, int32_t *, uint32_t, int32_t *, int32_t *); uint32_t clip_32_to_24(void *, int32_t *, uint32_t, int32_t *, int32_t *); uint32_t clip_32_to_32(void *, int32_t *, uint32_t, int32_t *, int32_t *); void normalize_mono(song_t *, int32_t *, uint32_t); void normalize_stereo(song_t *, int32_t *, uint32_t); void eq_mono(song_t *, int32_t *, uint32_t); void eq_stereo(song_t *, int32_t *, uint32_t); void initialize_eq(int32_t, float); void set_eq_gains(const uint32_t *, uint32_t, const uint32_t *, int32_t, int32_t); // sndmix.c extern int32_t g_dry_rofs_vol; extern int32_t g_dry_lofs_vol; // mixer.c void ResampleMono8BitFirFilter(signed char *oldbuf, signed char *newbuf, uint32_t oldlen, uint32_t newlen); void ResampleMono16BitFirFilter(signed short *oldbuf, signed short *newbuf, uint32_t oldlen, uint32_t newlen); void ResampleStereo8BitFirFilter(signed char *oldbuf, signed char *newbuf, uint32_t oldlen, uint32_t newlen); void ResampleStereo16BitFirFilter(signed short *oldbuf, signed short *newbuf, uint32_t oldlen, uint32_t newlen); #endif /* SCHISM_PLAYER_CMIXER_H_ */ schismtracker-20250313/include/player/fmopl.h000066400000000000000000000033261476471630300210410ustar00rootroot00000000000000#ifndef SCHISM_PLAYER_FMOPL_H_ #define SCHISM_PLAYER_FMOPL_H_ #include "headers.h" #define logerror(...) /**/ typedef int16_t OPLSAMPLE; typedef void (*OPL_TIMERHANDLER)(void *param,int timer,double period); typedef void (*OPL_IRQHANDLER)(void *param,int irq); typedef void (*OPL_UPDATEHANDLER)(void *param,int min_interval_us); typedef void (*OPL_PORTHANDLER_W)(void *param,unsigned char data); typedef unsigned char (*OPL_PORTHANDLER_R)(void *param); /* OPL2 */ void *ym3812_init(uint32_t clock, uint32_t rate); void ym3812_shutdown(void *chip); void ym3812_reset_chip(void *chip); int ym3812_write(void *chip, int a, int v); unsigned char ym3812_read(void *chip, int a); int ym3812_timer_over(void *chip, int c); void ym3812_update_one(void *chip, OPLSAMPLE *buffer, int length); void ym3812_set_timer_handler(void *chip, OPL_TIMERHANDLER TimerHandler, void *param); void ym3812_set_irq_handler(void *chip, OPL_IRQHANDLER IRQHandler, void *param); void ym3812_set_update_handler(void *chip, OPL_UPDATEHANDLER UpdateHandler, void *param); /* OPL3 */ void *ymf262_init(uint32_t clock, uint32_t rate); void ymf262_shutdown(void *chip); void ymf262_reset_chip(void *chip); int ymf262_write(void *chip, int a, int v); unsigned char ymf262_read(void *chip, int a); int ymf262_timer_over(void *chip, int c); void ymf262_update_one(void *chip, OPLSAMPLE **buffers, int length); void ymf262_update_multi(void *_chip, int32_t **buffers, int length); void ymf262_set_timer_handler(void *chip, OPL_TIMERHANDLER TimerHandler, void *param); void ymf262_set_irq_handler(void *chip, OPL_IRQHANDLER IRQHandler, void *param); void ymf262_set_update_handler(void *chip, OPL_UPDATEHANDLER UpdateHandler, void *param); #endif /* SCHISM_PLAYER_FMOPL_H_ */ schismtracker-20250313/include/player/precomp_lut.h000066400000000000000000003456421476471630300222670ustar00rootroot00000000000000static int16_t cubic_spline_lut[4096] = { 0, 16384, 0, 0, -8, 16384, 8, 0, -16, 16384, 16, 0, -24, 16384, 24, 0, -32, 16384, 32, 0, -40, 16383, 41, 0, -47, 16382, 49, 0, -55, 16381, 58, 0, -63, 16381, 66, 0, -71, 16381, 75, -1, -78, 16380, 83, -1, -86, 16379, 92, -1, -94, 16379, 100, -1, -101, 16377, 109, -1, -109, 16377, 118, -2, -117, 16376, 127, -2, -124, 16374, 136, -2, -132, 16373, 145, -2, -139, 16371, 154, -2, -146, 16370, 163, -3, -154, 16369, 172, -3, -161, 16366, 182, -3, -169, 16366, 191, -4, -176, 16364, 200, -4, -183, 16361, 210, -4, -190, 16360, 219, -5, -198, 16358, 229, -5, -205, 16357, 238, -6, -212, 16354, 248, -6, -219, 16351, 258, -6, -226, 16349, 268, -7, -233, 16347, 277, -7, -240, 16345, 287, -8, -247, 16342, 297, -8, -254, 16340, 307, -9, -261, 16337, 317, -9, -268, 16335, 327, -10, -275, 16331, 338, -10, -282, 16329, 348, -11, -289, 16326, 358, -11, -295, 16322, 369, -12, -302, 16320, 379, -13, -309, 16317, 389, -13, -316, 16314, 400, -14, -322, 16309, 411, -14, -329, 16307, 421, -15, -336, 16304, 432, -16, -342, 16299, 443, -16, -349, 16297, 453, -17, -355, 16293, 464, -18, -362, 16290, 475, -19, -368, 16285, 486, -19, -375, 16282, 497, -20, -381, 16278, 508, -21, -388, 16274, 520, -22, -394, 16269, 531, -22, -400, 16265, 542, -23, -407, 16262, 553, -24, -413, 16257, 565, -25, -419, 16253, 576, -26, -425, 16247, 588, -26, -432, 16244, 599, -27, -438, 16239, 611, -28, -444, 16235, 622, -29, -450, 16230, 634, -30, -456, 16225, 646, -31, -462, 16220, 658, -32, -468, 16216, 669, -33, -474, 16211, 681, -34, -480, 16206, 693, -35, -486, 16201, 705, -36, -492, 16196, 717, -37, -498, 16191, 729, -38, -504, 16185, 742, -39, -510, 16180, 754, -40, -515, 16174, 766, -41, -521, 16169, 778, -42, -527, 16163, 791, -43, -533, 16158, 803, -44, -538, 16151, 816, -45, -544, 16146, 828, -46, -550, 16140, 841, -47, -555, 16133, 854, -48, -561, 16128, 866, -49, -566, 16122, 879, -51, -572, 16116, 892, -52, -577, 16109, 905, -53, -583, 16104, 917, -54, -588, 16097, 930, -55, -594, 16092, 943, -57, -599, 16085, 956, -58, -604, 16077, 970, -59, -610, 16071, 983, -60, -615, 16064, 996, -61, -620, 16058, 1009, -63, -626, 16052, 1022, -64, -631, 16044, 1036, -65, -636, 16038, 1049, -67, -641, 16030, 1063, -68, -646, 16023, 1076, -69, -651, 16015, 1090, -70, -656, 16009, 1103, -72, -662, 16002, 1117, -73, -667, 15995, 1131, -75, -672, 15988, 1144, -76, -677, 15980, 1158, -77, -682, 15973, 1172, -79, -686, 15964, 1186, -80, -691, 15957, 1200, -82, -696, 15949, 1214, -83, -701, 15941, 1228, -84, -706, 15934, 1242, -86, -711, 15926, 1256, -87, -715, 15918, 1270, -89, -720, 15910, 1284, -90, -725, 15903, 1298, -92, -730, 15894, 1313, -93, -734, 15886, 1327, -95, -739, 15877, 1342, -96, -744, 15870, 1356, -98, -748, 15861, 1370, -99, -753, 15853, 1385, -101, -757, 15843, 1400, -102, -762, 15836, 1414, -104, -766, 15827, 1429, -106, -771, 15818, 1444, -107, -775, 15810, 1458, -109, -780, 15801, 1473, -110, -784, 15792, 1488, -112, -788, 15783, 1503, -114, -793, 15774, 1518, -115, -797, 15765, 1533, -117, -801, 15756, 1548, -119, -806, 15747, 1563, -120, -810, 15738, 1578, -122, -814, 15729, 1593, -124, -818, 15719, 1608, -125, -822, 15709, 1624, -127, -826, 15700, 1639, -129, -831, 15691, 1654, -130, -835, 15681, 1670, -132, -839, 15672, 1685, -134, -843, 15662, 1701, -136, -847, 15652, 1716, -137, -851, 15642, 1732, -139, -855, 15633, 1747, -141, -859, 15623, 1763, -143, -863, 15613, 1779, -145, -866, 15602, 1794, -146, -870, 15592, 1810, -148, -874, 15582, 1826, -150, -878, 15572, 1842, -152, -882, 15562, 1858, -154, -886, 15552, 1874, -156, -889, 15540, 1890, -157, -893, 15530, 1906, -159, -897, 15520, 1922, -161, -900, 15509, 1938, -163, -904, 15499, 1954, -165, -908, 15489, 1970, -167, -911, 15478, 1986, -169, -915, 15467, 2003, -171, -918, 15456, 2019, -173, -922, 15446, 2035, -175, -925, 15433, 2052, -176, -929, 15423, 2068, -178, -932, 15412, 2084, -180, -936, 15401, 2101, -182, -939, 15390, 2117, -184, -943, 15379, 2134, -186, -946, 15367, 2151, -188, -949, 15356, 2167, -190, -953, 15345, 2184, -192, -956, 15333, 2201, -194, -959, 15321, 2218, -196, -962, 15310, 2234, -198, -966, 15299, 2251, -200, -969, 15287, 2268, -202, -972, 15276, 2285, -205, -975, 15264, 2302, -207, -978, 15252, 2319, -209, -981, 15240, 2336, -211, -984, 15228, 2353, -213, -987, 15216, 2370, -215, -991, 15205, 2387, -217, -994, 15192, 2405, -219, -997, 15180, 2422, -221, -999, 15167, 2439, -223, -1002, 15155, 2456, -225, -1005, 15143, 2474, -228, -1008, 15131, 2491, -230, -1011, 15118, 2509, -232, -1014, 15106, 2526, -234, -1017, 15094, 2543, -236, -1020, 15081, 2561, -238, -1022, 15067, 2579, -240, -1025, 15056, 2596, -243, -1028, 15043, 2614, -245, -1031, 15031, 2631, -247, -1033, 15017, 2649, -249, -1036, 15004, 2667, -251, -1039, 14992, 2685, -254, -1041, 14979, 2702, -256, -1044, 14966, 2720, -258, -1047, 14953, 2738, -260, -1049, 14940, 2756, -263, -1052, 14927, 2774, -265, -1054, 14913, 2792, -267, -1057, 14900, 2810, -269, -1059, 14887, 2828, -272, -1062, 14874, 2846, -274, -1064, 14860, 2864, -276, -1066, 14846, 2882, -278, -1069, 14833, 2901, -281, -1071, 14819, 2919, -283, -1074, 14806, 2937, -285, -1076, 14793, 2955, -288, -1078, 14778, 2974, -290, -1080, 14764, 2992, -292, -1083, 14752, 3010, -295, -1085, 14737, 3029, -297, -1087, 14723, 3047, -299, -1089, 14709, 3066, -302, -1092, 14696, 3084, -304, -1094, 14681, 3103, -306, -1096, 14668, 3121, -309, -1098, 14653, 3140, -311, -1100, 14638, 3159, -313, -1102, 14625, 3177, -316, -1104, 14610, 3196, -318, -1106, 14595, 3215, -320, -1108, 14582, 3233, -323, -1110, 14567, 3252, -325, -1112, 14553, 3271, -328, -1114, 14538, 3290, -330, -1116, 14523, 3309, -332, -1118, 14509, 3328, -335, -1120, 14494, 3347, -337, -1122, 14480, 3366, -340, -1124, 14465, 3385, -342, -1125, 14450, 3404, -345, -1127, 14435, 3423, -347, -1129, 14420, 3442, -349, -1131, 14406, 3461, -352, -1133, 14391, 3480, -354, -1134, 14376, 3499, -357, -1136, 14361, 3518, -359, -1138, 14346, 3538, -362, -1139, 14330, 3557, -364, -1141, 14316, 3576, -367, -1143, 14301, 3595, -369, -1144, 14285, 3615, -372, -1146, 14270, 3634, -374, -1147, 14254, 3654, -377, -1149, 14239, 3673, -379, -1150, 14223, 3693, -382, -1152, 14208, 3712, -384, -1153, 14192, 3732, -387, -1155, 14177, 3751, -389, -1156, 14161, 3771, -392, -1158, 14146, 3790, -394, -1159, 14130, 3810, -397, -1161, 14115, 3829, -399, -1162, 14099, 3849, -402, -1163, 14082, 3869, -404, -1165, 14067, 3889, -407, -1166, 14051, 3908, -409, -1167, 14035, 3928, -412, -1169, 14019, 3948, -414, -1170, 14003, 3968, -417, -1171, 13986, 3988, -419, -1172, 13971, 4007, -422, -1174, 13955, 4027, -424, -1175, 13939, 4047, -427, -1176, 13923, 4067, -430, -1177, 13906, 4087, -432, -1178, 13890, 4107, -435, -1179, 13873, 4127, -437, -1180, 13857, 4147, -440, -1181, 13840, 4167, -442, -1182, 13823, 4188, -445, -1184, 13808, 4208, -448, -1185, 13791, 4228, -450, -1186, 13775, 4248, -453, -1187, 13758, 4268, -455, -1187, 13741, 4288, -458, -1188, 13724, 4309, -461, -1189, 13707, 4329, -463, -1190, 13691, 4349, -466, -1191, 13673, 4370, -468, -1192, 13657, 4390, -471, -1193, 13641, 4410, -474, -1194, 13623, 4431, -476, -1195, 13607, 4451, -479, -1195, 13589, 4471, -481, -1196, 13572, 4492, -484, -1197, 13556, 4512, -487, -1198, 13538, 4533, -489, -1198, 13521, 4553, -492, -1199, 13504, 4574, -495, -1200, 13486, 4595, -497, -1200, 13469, 4615, -500, -1201, 13451, 4636, -502, -1202, 13435, 4656, -505, -1202, 13417, 4677, -508, -1203, 13399, 4698, -510, -1204, 13383, 4718, -513, -1204, 13365, 4739, -516, -1205, 13347, 4760, -518, -1205, 13330, 4780, -521, -1206, 13312, 4801, -523, -1206, 13294, 4822, -526, -1207, 13277, 4843, -529, -1207, 13258, 4864, -531, -1208, 13241, 4885, -534, -1208, 13224, 4905, -537, -1208, 13205, 4926, -539, -1209, 13188, 4947, -542, -1209, 13170, 4968, -545, -1210, 13152, 4989, -547, -1210, 13134, 5010, -550, -1210, 13116, 5031, -553, -1211, 13098, 5052, -555, -1211, 13080, 5073, -558, -1211, 13062, 5094, -561, -1212, 13044, 5115, -563, -1212, 13026, 5136, -566, -1212, 13008, 5157, -569, -1212, 12989, 5178, -571, -1212, 12971, 5199, -574, -1213, 12953, 5221, -577, -1213, 12934, 5242, -579, -1213, 12916, 5263, -582, -1213, 12898, 5284, -585, -1213, 12879, 5305, -587, -1213, 12860, 5327, -590, -1213, 12842, 5348, -593, -1213, 12823, 5369, -595, -1214, 12806, 5390, -598, -1214, 12787, 5412, -601, -1214, 12768, 5433, -603, -1214, 12750, 5454, -606, -1214, 12731, 5476, -609, -1214, 12712, 5497, -611, -1214, 12694, 5518, -614, -1214, 12675, 5540, -617, -1213, 12655, 5561, -619, -1213, 12637, 5582, -622, -1213, 12618, 5604, -625, -1213, 12599, 5625, -627, -1213, 12580, 5647, -630, -1213, 12562, 5668, -633, -1213, 12542, 5690, -635, -1213, 12524, 5711, -638, -1212, 12504, 5733, -641, -1212, 12485, 5754, -643, -1212, 12466, 5776, -646, -1212, 12448, 5797, -649, -1211, 12427, 5819, -651, -1211, 12408, 5841, -654, -1211, 12390, 5862, -657, -1211, 12370, 5884, -659, -1210, 12351, 5905, -662, -1210, 12332, 5927, -665, -1210, 12312, 5949, -667, -1209, 12293, 5970, -670, -1209, 12273, 5992, -672, -1209, 12254, 6014, -675, -1208, 12235, 6035, -678, -1208, 12215, 6057, -680, -1207, 12195, 6079, -683, -1207, 12176, 6101, -686, -1207, 12157, 6122, -688, -1206, 12137, 6144, -691, -1206, 12118, 6166, -694, -1205, 12097, 6188, -696, -1205, 12079, 6209, -699, -1204, 12059, 6231, -702, -1204, 12039, 6253, -704, -1203, 12019, 6275, -707, -1202, 11998, 6297, -709, -1202, 11980, 6318, -712, -1201, 11960, 6340, -715, -1201, 11940, 6362, -717, -1200, 11920, 6384, -720, -1199, 11900, 6406, -723, -1199, 11880, 6428, -725, -1198, 11860, 6450, -728, -1197, 11839, 6472, -730, -1197, 11821, 6493, -733, -1196, 11801, 6515, -736, -1195, 11780, 6537, -738, -1195, 11761, 6559, -741, -1194, 11741, 6581, -744, -1193, 11720, 6603, -746, -1192, 11700, 6625, -749, -1192, 11680, 6647, -751, -1191, 11660, 6669, -754, -1190, 11640, 6691, -757, -1189, 11619, 6713, -759, -1188, 11599, 6735, -762, -1187, 11578, 6757, -764, -1187, 11559, 6779, -767, -1186, 11538, 6801, -769, -1185, 11518, 6823, -772, -1184, 11498, 6845, -775, -1183, 11477, 6867, -777, -1182, 11457, 6889, -780, -1181, 11436, 6911, -782, -1180, 11415, 6934, -785, -1179, 11394, 6956, -787, -1178, 11374, 6978, -790, -1177, 11354, 7000, -793, -1176, 11333, 7022, -795, -1175, 11313, 7044, -798, -1174, 11292, 7066, -800, -1173, 11272, 7088, -803, -1172, 11251, 7110, -805, -1171, 11231, 7132, -808, -1170, 11209, 7155, -810, -1169, 11189, 7177, -813, -1168, 11168, 7199, -815, -1167, 11148, 7221, -818, -1166, 11127, 7243, -820, -1165, 11107, 7265, -823, -1163, 11084, 7288, -825, -1162, 11064, 7310, -828, -1161, 11043, 7332, -830, -1160, 11023, 7354, -833, -1159, 11002, 7376, -835, -1158, 10982, 7398, -838, -1156, 10959, 7421, -840, -1155, 10939, 7443, -843, -1154, 10918, 7465, -845, -1153, 10898, 7487, -848, -1151, 10876, 7509, -850, -1150, 10856, 7531, -853, -1149, 10834, 7554, -855, -1148, 10814, 7576, -858, -1146, 10792, 7598, -860, -1145, 10772, 7620, -863, -1144, 10750, 7643, -865, -1142, 10728, 7665, -867, -1141, 10708, 7687, -870, -1140, 10687, 7709, -872, -1138, 10666, 7731, -875, -1137, 10644, 7754, -877, -1135, 10623, 7776, -880, -1134, 10602, 7798, -882, -1133, 10581, 7820, -884, -1131, 10560, 7842, -887, -1130, 10538, 7865, -889, -1128, 10517, 7887, -892, -1127, 10496, 7909, -894, -1125, 10474, 7931, -896, -1124, 10453, 7954, -899, -1122, 10431, 7976, -901, -1121, 10410, 7998, -903, -1119, 10389, 8020, -906, -1118, 10368, 8042, -908, -1116, 10346, 8065, -911, -1115, 10325, 8087, -913, -1113, 10303, 8109, -915, -1112, 10283, 8131, -918, -1110, 10260, 8154, -920, -1109, 10239, 8176, -922, -1107, 10217, 8198, -924, -1105, 10196, 8220, -927, -1104, 10175, 8242, -929, -1102, 10152, 8265, -931, -1101, 10132, 8287, -934, -1099, 10110, 8309, -936, -1097, 10088, 8331, -938, -1096, 10068, 8353, -941, -1094, 10045, 8376, -943, -1092, 10023, 8398, -945, -1091, 10002, 8420, -947, -1089, 9981, 8442, -950, -1087, 9959, 8464, -952, -1085, 9936, 8487, -954, -1084, 9915, 8509, -956, -1082, 9893, 8531, -958, -1080, 9872, 8553, -961, -1079, 9851, 8575, -963, -1077, 9829, 8597, -965, -1075, 9806, 8620, -967, -1073, 9784, 8642, -969, -1071, 9763, 8664, -972, -1070, 9742, 8686, -974, -1068, 9720, 8708, -976, -1066, 9698, 8730, -978, -1064, 9676, 8752, -980, -1062, 9653, 8775, -982, -1061, 9633, 8797, -985, -1059, 9611, 8819, -987, -1057, 9589, 8841, -989, -1055, 9567, 8863, -991, -1053, 9545, 8885, -993, -1051, 9523, 8907, -995, -1049, 9501, 8929, -997, -1047, 9479, 8951, -999, -1046, 9458, 8974, -1002, -1044, 9436, 8996, -1004, -1042, 9414, 9018, -1006, -1040, 9392, 9040, -1008, -1038, 9370, 9062, -1010, -1036, 9348, 9084, -1012, -1034, 9326, 9106, -1014, -1032, 9304, 9128, -1016, -1030, 9282, 9150, -1018, -1028, 9260, 9172, -1020, -1026, 9238, 9194, -1022, -1024, 9216, 9216, -1024, -1022, 9194, 9238, -1026, -1020, 9172, 9260, -1028, -1018, 9150, 9282, -1030, -1016, 9128, 9304, -1032, -1014, 9106, 9326, -1034, -1012, 9084, 9348, -1036, -1010, 9062, 9370, -1038, -1008, 9040, 9392, -1040, -1006, 9018, 9414, -1042, -1004, 8996, 9436, -1044, -1002, 8974, 9458, -1046, -999, 8951, 9479, -1047, -997, 8929, 9501, -1049, -995, 8907, 9523, -1051, -993, 8885, 9545, -1053, -991, 8863, 9567, -1055, -989, 8841, 9589, -1057, -987, 8819, 9611, -1059, -985, 8797, 9633, -1061, -982, 8775, 9653, -1062, -980, 8752, 9676, -1064, -978, 8730, 9698, -1066, -976, 8708, 9720, -1068, -974, 8686, 9742, -1070, -972, 8664, 9763, -1071, -969, 8642, 9784, -1073, -967, 8620, 9806, -1075, -965, 8597, 9829, -1077, -963, 8575, 9851, -1079, -961, 8553, 9872, -1080, -958, 8531, 9893, -1082, -956, 8509, 9915, -1084, -954, 8487, 9936, -1085, -952, 8464, 9959, -1087, -950, 8442, 9981, -1089, -947, 8420, 10002, -1091, -945, 8398, 10023, -1092, -943, 8376, 10045, -1094, -941, 8353, 10068, -1096, -938, 8331, 10088, -1097, -936, 8309, 10110, -1099, -934, 8287, 10132, -1101, -931, 8265, 10152, -1102, -929, 8242, 10175, -1104, -927, 8220, 10196, -1105, -924, 8198, 10217, -1107, -922, 8176, 10239, -1109, -920, 8154, 10260, -1110, -918, 8131, 10283, -1112, -915, 8109, 10303, -1113, -913, 8087, 10325, -1115, -911, 8065, 10346, -1116, -908, 8042, 10368, -1118, -906, 8020, 10389, -1119, -903, 7998, 10410, -1121, -901, 7976, 10431, -1122, -899, 7954, 10453, -1124, -896, 7931, 10474, -1125, -894, 7909, 10496, -1127, -892, 7887, 10517, -1128, -889, 7865, 10538, -1130, -887, 7842, 10560, -1131, -884, 7820, 10581, -1133, -882, 7798, 10602, -1134, -880, 7776, 10623, -1135, -877, 7754, 10644, -1137, -875, 7731, 10666, -1138, -872, 7709, 10687, -1140, -870, 7687, 10708, -1141, -867, 7665, 10728, -1142, -865, 7643, 10750, -1144, -863, 7620, 10772, -1145, -860, 7598, 10792, -1146, -858, 7576, 10814, -1148, -855, 7554, 10834, -1149, -853, 7531, 10856, -1150, -850, 7509, 10876, -1151, -848, 7487, 10898, -1153, -845, 7465, 10918, -1154, -843, 7443, 10939, -1155, -840, 7421, 10959, -1156, -838, 7398, 10982, -1158, -835, 7376, 11002, -1159, -833, 7354, 11023, -1160, -830, 7332, 11043, -1161, -828, 7310, 11064, -1162, -825, 7288, 11084, -1163, -823, 7265, 11107, -1165, -820, 7243, 11127, -1166, -818, 7221, 11148, -1167, -815, 7199, 11168, -1168, -813, 7177, 11189, -1169, -810, 7155, 11209, -1170, -808, 7132, 11231, -1171, -805, 7110, 11251, -1172, -803, 7088, 11272, -1173, -800, 7066, 11292, -1174, -798, 7044, 11313, -1175, -795, 7022, 11333, -1176, -793, 7000, 11354, -1177, -790, 6978, 11374, -1178, -787, 6956, 11394, -1179, -785, 6934, 11415, -1180, -782, 6911, 11436, -1181, -780, 6889, 11457, -1182, -777, 6867, 11477, -1183, -775, 6845, 11498, -1184, -772, 6823, 11518, -1185, -769, 6801, 11538, -1186, -767, 6779, 11559, -1187, -764, 6757, 11578, -1187, -762, 6735, 11599, -1188, -759, 6713, 11619, -1189, -757, 6691, 11640, -1190, -754, 6669, 11660, -1191, -751, 6647, 11680, -1192, -749, 6625, 11700, -1192, -746, 6603, 11720, -1193, -744, 6581, 11741, -1194, -741, 6559, 11761, -1195, -738, 6537, 11780, -1195, -736, 6515, 11801, -1196, -733, 6493, 11821, -1197, -730, 6472, 11839, -1197, -728, 6450, 11860, -1198, -725, 6428, 11880, -1199, -723, 6406, 11900, -1199, -720, 6384, 11920, -1200, -717, 6362, 11940, -1201, -715, 6340, 11960, -1201, -712, 6318, 11980, -1202, -709, 6297, 11998, -1202, -707, 6275, 12019, -1203, -704, 6253, 12039, -1204, -702, 6231, 12059, -1204, -699, 6209, 12079, -1205, -696, 6188, 12097, -1205, -694, 6166, 12118, -1206, -691, 6144, 12137, -1206, -688, 6122, 12157, -1207, -686, 6101, 12176, -1207, -683, 6079, 12195, -1207, -680, 6057, 12215, -1208, -678, 6035, 12235, -1208, -675, 6014, 12254, -1209, -672, 5992, 12273, -1209, -670, 5970, 12293, -1209, -667, 5949, 12312, -1210, -665, 5927, 12332, -1210, -662, 5905, 12351, -1210, -659, 5884, 12370, -1211, -657, 5862, 12390, -1211, -654, 5841, 12408, -1211, -651, 5819, 12427, -1211, -649, 5797, 12448, -1212, -646, 5776, 12466, -1212, -643, 5754, 12485, -1212, -641, 5733, 12504, -1212, -638, 5711, 12524, -1213, -635, 5690, 12542, -1213, -633, 5668, 12562, -1213, -630, 5647, 12580, -1213, -627, 5625, 12599, -1213, -625, 5604, 12618, -1213, -622, 5582, 12637, -1213, -619, 5561, 12655, -1213, -617, 5540, 12675, -1214, -614, 5518, 12694, -1214, -611, 5497, 12712, -1214, -609, 5476, 12731, -1214, -606, 5454, 12750, -1214, -603, 5433, 12768, -1214, -601, 5412, 12787, -1214, -598, 5390, 12806, -1214, -595, 5369, 12823, -1213, -593, 5348, 12842, -1213, -590, 5327, 12860, -1213, -587, 5305, 12879, -1213, -585, 5284, 12898, -1213, -582, 5263, 12916, -1213, -579, 5242, 12934, -1213, -577, 5221, 12953, -1213, -574, 5199, 12971, -1212, -571, 5178, 12989, -1212, -569, 5157, 13008, -1212, -566, 5136, 13026, -1212, -563, 5115, 13044, -1212, -561, 5094, 13062, -1211, -558, 5073, 13080, -1211, -555, 5052, 13098, -1211, -553, 5031, 13116, -1210, -550, 5010, 13134, -1210, -547, 4989, 13152, -1210, -545, 4968, 13170, -1209, -542, 4947, 13188, -1209, -539, 4926, 13205, -1208, -537, 4905, 13224, -1208, -534, 4885, 13241, -1208, -531, 4864, 13258, -1207, -529, 4843, 13277, -1207, -526, 4822, 13294, -1206, -523, 4801, 13312, -1206, -521, 4780, 13330, -1205, -518, 4760, 13347, -1205, -516, 4739, 13365, -1204, -513, 4718, 13383, -1204, -510, 4698, 13399, -1203, -508, 4677, 13417, -1202, -505, 4656, 13435, -1202, -502, 4636, 13451, -1201, -500, 4615, 13469, -1200, -497, 4595, 13486, -1200, -495, 4574, 13504, -1199, -492, 4553, 13521, -1198, -489, 4533, 13538, -1198, -487, 4512, 13556, -1197, -484, 4492, 13572, -1196, -481, 4471, 13589, -1195, -479, 4451, 13607, -1195, -476, 4431, 13623, -1194, -474, 4410, 13641, -1193, -471, 4390, 13657, -1192, -468, 4370, 13673, -1191, -466, 4349, 13691, -1190, -463, 4329, 13707, -1189, -461, 4309, 13724, -1188, -458, 4288, 13741, -1187, -455, 4268, 13758, -1187, -453, 4248, 13775, -1186, -450, 4228, 13791, -1185, -448, 4208, 13808, -1184, -445, 4188, 13823, -1182, -442, 4167, 13840, -1181, -440, 4147, 13857, -1180, -437, 4127, 13873, -1179, -435, 4107, 13890, -1178, -432, 4087, 13906, -1177, -430, 4067, 13923, -1176, -427, 4047, 13939, -1175, -424, 4027, 13955, -1174, -422, 4007, 13971, -1172, -419, 3988, 13986, -1171, -417, 3968, 14003, -1170, -414, 3948, 14019, -1169, -412, 3928, 14035, -1167, -409, 3908, 14051, -1166, -407, 3889, 14067, -1165, -404, 3869, 14082, -1163, -402, 3849, 14099, -1162, -399, 3829, 14115, -1161, -397, 3810, 14130, -1159, -394, 3790, 14146, -1158, -392, 3771, 14161, -1156, -389, 3751, 14177, -1155, -387, 3732, 14192, -1153, -384, 3712, 14208, -1152, -382, 3693, 14223, -1150, -379, 3673, 14239, -1149, -377, 3654, 14254, -1147, -374, 3634, 14270, -1146, -372, 3615, 14285, -1144, -369, 3595, 14301, -1143, -367, 3576, 14316, -1141, -364, 3557, 14330, -1139, -362, 3538, 14346, -1138, -359, 3518, 14361, -1136, -357, 3499, 14376, -1134, -354, 3480, 14391, -1133, -352, 3461, 14406, -1131, -349, 3442, 14420, -1129, -347, 3423, 14435, -1127, -345, 3404, 14450, -1125, -342, 3385, 14465, -1124, -340, 3366, 14480, -1122, -337, 3347, 14494, -1120, -335, 3328, 14509, -1118, -332, 3309, 14523, -1116, -330, 3290, 14538, -1114, -328, 3271, 14553, -1112, -325, 3252, 14567, -1110, -323, 3233, 14582, -1108, -320, 3215, 14595, -1106, -318, 3196, 14610, -1104, -316, 3177, 14625, -1102, -313, 3159, 14638, -1100, -311, 3140, 14653, -1098, -309, 3121, 14668, -1096, -306, 3103, 14681, -1094, -304, 3084, 14696, -1092, -302, 3066, 14709, -1089, -299, 3047, 14723, -1087, -297, 3029, 14737, -1085, -295, 3010, 14752, -1083, -292, 2992, 14764, -1080, -290, 2974, 14778, -1078, -288, 2955, 14793, -1076, -285, 2937, 14806, -1074, -283, 2919, 14819, -1071, -281, 2901, 14833, -1069, -278, 2882, 14846, -1066, -276, 2864, 14860, -1064, -274, 2846, 14874, -1062, -272, 2828, 14887, -1059, -269, 2810, 14900, -1057, -267, 2792, 14913, -1054, -265, 2774, 14927, -1052, -263, 2756, 14940, -1049, -260, 2738, 14953, -1047, -258, 2720, 14966, -1044, -256, 2702, 14979, -1041, -254, 2685, 14992, -1039, -251, 2667, 15004, -1036, -249, 2649, 15017, -1033, -247, 2631, 15031, -1031, -245, 2614, 15043, -1028, -243, 2596, 15056, -1025, -240, 2579, 15067, -1022, -238, 2561, 15081, -1020, -236, 2543, 15094, -1017, -234, 2526, 15106, -1014, -232, 2509, 15118, -1011, -230, 2491, 15131, -1008, -228, 2474, 15143, -1005, -225, 2456, 15155, -1002, -223, 2439, 15167, -999, -221, 2422, 15180, -997, -219, 2405, 15192, -994, -217, 2387, 15205, -991, -215, 2370, 15216, -987, -213, 2353, 15228, -984, -211, 2336, 15240, -981, -209, 2319, 15252, -978, -207, 2302, 15264, -975, -205, 2285, 15276, -972, -202, 2268, 15287, -969, -200, 2251, 15299, -966, -198, 2234, 15310, -962, -196, 2218, 15321, -959, -194, 2201, 15333, -956, -192, 2184, 15345, -953, -190, 2167, 15356, -949, -188, 2151, 15367, -946, -186, 2134, 15379, -943, -184, 2117, 15390, -939, -182, 2101, 15401, -936, -180, 2084, 15412, -932, -178, 2068, 15423, -929, -176, 2052, 15433, -925, -175, 2035, 15446, -922, -173, 2019, 15456, -918, -171, 2003, 15467, -915, -169, 1986, 15478, -911, -167, 1970, 15489, -908, -165, 1954, 15499, -904, -163, 1938, 15509, -900, -161, 1922, 15520, -897, -159, 1906, 15530, -893, -157, 1890, 15540, -889, -156, 1874, 15552, -886, -154, 1858, 15562, -882, -152, 1842, 15572, -878, -150, 1826, 15582, -874, -148, 1810, 15592, -870, -146, 1794, 15602, -866, -145, 1779, 15613, -863, -143, 1763, 15623, -859, -141, 1747, 15633, -855, -139, 1732, 15642, -851, -137, 1716, 15652, -847, -136, 1701, 15662, -843, -134, 1685, 15672, -839, -132, 1670, 15681, -835, -130, 1654, 15691, -831, -129, 1639, 15700, -826, -127, 1624, 15709, -822, -125, 1608, 15719, -818, -124, 1593, 15729, -814, -122, 1578, 15738, -810, -120, 1563, 15747, -806, -119, 1548, 15756, -801, -117, 1533, 15765, -797, -115, 1518, 15774, -793, -114, 1503, 15783, -788, -112, 1488, 15792, -784, -110, 1473, 15801, -780, -109, 1458, 15810, -775, -107, 1444, 15818, -771, -106, 1429, 15827, -766, -104, 1414, 15836, -762, -102, 1400, 15843, -757, -101, 1385, 15853, -753, -99, 1370, 15861, -748, -98, 1356, 15870, -744, -96, 1342, 15877, -739, -95, 1327, 15886, -734, -93, 1313, 15894, -730, -92, 1298, 15903, -725, -90, 1284, 15910, -720, -89, 1270, 15918, -715, -87, 1256, 15926, -711, -86, 1242, 15934, -706, -84, 1228, 15941, -701, -83, 1214, 15949, -696, -82, 1200, 15957, -691, -80, 1186, 15964, -686, -79, 1172, 15973, -682, -77, 1158, 15980, -677, -76, 1144, 15988, -672, -75, 1131, 15995, -667, -73, 1117, 16002, -662, -72, 1103, 16009, -656, -70, 1090, 16015, -651, -69, 1076, 16023, -646, -68, 1063, 16030, -641, -67, 1049, 16038, -636, -65, 1036, 16044, -631, -64, 1022, 16052, -626, -63, 1009, 16058, -620, -61, 996, 16064, -615, -60, 983, 16071, -610, -59, 970, 16077, -604, -58, 956, 16085, -599, -57, 943, 16092, -594, -55, 930, 16097, -588, -54, 917, 16104, -583, -53, 905, 16109, -577, -52, 892, 16116, -572, -51, 879, 16122, -566, -49, 866, 16128, -561, -48, 854, 16133, -555, -47, 841, 16140, -550, -46, 828, 16146, -544, -45, 816, 16151, -538, -44, 803, 16158, -533, -43, 791, 16163, -527, -42, 778, 16169, -521, -41, 766, 16174, -515, -40, 754, 16180, -510, -39, 742, 16185, -504, -38, 729, 16191, -498, -37, 717, 16196, -492, -36, 705, 16201, -486, -35, 693, 16206, -480, -34, 681, 16211, -474, -33, 669, 16216, -468, -32, 658, 16220, -462, -31, 646, 16225, -456, -30, 634, 16230, -450, -29, 622, 16235, -444, -28, 611, 16239, -438, -27, 599, 16244, -432, -26, 588, 16247, -425, -26, 576, 16253, -419, -25, 565, 16257, -413, -24, 553, 16262, -407, -23, 542, 16265, -400, -22, 531, 16269, -394, -22, 520, 16274, -388, -21, 508, 16278, -381, -20, 497, 16282, -375, -19, 486, 16285, -368, -19, 475, 16290, -362, -18, 464, 16293, -355, -17, 453, 16297, -349, -16, 443, 16299, -342, -16, 432, 16304, -336, -15, 421, 16307, -329, -14, 411, 16309, -322, -14, 400, 16314, -316, -13, 389, 16317, -309, -13, 379, 16320, -302, -12, 369, 16322, -295, -11, 358, 16326, -289, -11, 348, 16329, -282, -10, 338, 16331, -275, -10, 327, 16335, -268, -9, 317, 16337, -261, -9, 307, 16340, -254, -8, 297, 16342, -247, -8, 287, 16345, -240, -7, 277, 16347, -233, -7, 268, 16349, -226, -6, 258, 16351, -219, -6, 248, 16354, -212, -6, 238, 16357, -205, -5, 229, 16358, -198, -5, 219, 16360, -190, -4, 210, 16361, -183, -4, 200, 16364, -176, -4, 191, 16366, -169, -3, 182, 16366, -161, -3, 172, 16369, -154, -3, 163, 16370, -146, -2, 154, 16371, -139, -2, 145, 16373, -132, -2, 136, 16374, -124, -2, 127, 16376, -117, -2, 118, 16377, -109, -1, 109, 16377, -101, -1, 100, 16379, -94, -1, 92, 16379, -86, -1, 83, 16380, -78, -1, 75, 16381, -71, 0, 66, 16381, -63, 0, 58, 16381, -55, 0, 49, 16382, -47, 0, 41, 16383, -40, 0, 32, 16384, -32, 0, 24, 16384, -24, 0, 16, 16384, -16, 0, 8, 16384, -8, }; static int16_t windowed_fir_lut[16392] = { 55, -727, 2306, 29549, 2306, -727, 55, -48, 54, -725, 2294, 29549, 2317, -729, 55, -48, 54, -723, 2282, 29549, 2329, -731, 55, -48, 54, -721, 2271, 29549, 2341, -733, 55, -48, 54, -718, 2259, 29549, 2353, -735, 55, -48, 54, -716, 2247, 29549, 2364, -738, 56, -48, 54, -714, 2236, 29548, 2376, -740, 56, -48, 53, -712, 2224, 29548, 2388, -742, 56, -48, 53, -710, 2213, 29548, 2400, -744, 56, -48, 53, -708, 2201, 29548, 2411, -746, 56, -48, 53, -706, 2189, 29547, 2423, -748, 56, -47, 53, -704, 2178, 29547, 2435, -750, 57, -47, 53, -702, 2166, 29547, 2447, -752, 57, -47, 52, -699, 2155, 29546, 2459, -755, 57, -47, 52, -697, 2143, 29546, 2471, -757, 57, -47, 52, -695, 2132, 29546, 2483, -759, 57, -47, 52, -693, 2120, 29545, 2494, -761, 58, -47, 52, -691, 2109, 29545, 2506, -763, 58, -47, 52, -689, 2097, 29544, 2518, -765, 58, -47, 51, -687, 2086, 29544, 2530, -768, 58, -47, 51, -685, 2074, 29543, 2542, -770, 58, -47, 51, -683, 2063, 29543, 2554, -772, 58, -47, 51, -681, 2052, 29542, 2566, -774, 59, -47, 51, -679, 2040, 29542, 2578, -776, 59, -47, 50, -677, 2029, 29541, 2590, -778, 59, -46, 50, -674, 2017, 29540, 2602, -781, 59, -46, 50, -672, 2006, 29540, 2614, -783, 59, -46, 50, -670, 1995, 29539, 2626, -785, 60, -46, 50, -668, 1983, 29538, 2638, -787, 60, -46, 50, -666, 1972, 29537, 2650, -789, 60, -46, 49, -664, 1961, 29537, 2662, -791, 60, -46, 49, -662, 1949, 29536, 2675, -794, 60, -46, 49, -660, 1938, 29535, 2687, -796, 60, -46, 49, -658, 1927, 29534, 2699, -798, 61, -46, 49, -656, 1916, 29533, 2711, -800, 61, -46, 49, -654, 1904, 29533, 2723, -802, 61, -46, 48, -652, 1893, 29532, 2735, -804, 61, -46, 48, -650, 1882, 29531, 2747, -807, 61, -45, 48, -648, 1871, 29530, 2760, -809, 62, -45, 48, -646, 1860, 29529, 2772, -811, 62, -45, 48, -644, 1848, 29528, 2784, -813, 62, -45, 48, -642, 1837, 29527, 2796, -815, 62, -45, 47, -640, 1826, 29526, 2808, -818, 62, -45, 47, -638, 1815, 29525, 2821, -820, 63, -45, 47, -635, 1804, 29524, 2833, -822, 63, -45, 47, -633, 1793, 29523, 2845, -824, 63, -45, 47, -631, 1782, 29521, 2858, -826, 63, -45, 47, -629, 1771, 29520, 2870, -829, 63, -45, 47, -627, 1760, 29519, 2882, -831, 64, -45, 46, -625, 1749, 29518, 2895, -833, 64, -45, 46, -623, 1738, 29517, 2907, -835, 64, -44, 46, -621, 1727, 29515, 2919, -838, 64, -44, 46, -619, 1716, 29514, 2932, -840, 64, -44, 46, -617, 1705, 29513, 2944, -842, 64, -44, 46, -615, 1694, 29511, 2956, -844, 65, -44, 45, -613, 1683, 29510, 2969, -846, 65, -44, 45, -611, 1672, 29509, 2981, -849, 65, -44, 45, -609, 1661, 29507, 2994, -851, 65, -44, 45, -607, 1650, 29506, 3006, -853, 65, -44, 45, -605, 1639, 29504, 3019, -855, 66, -44, 45, -603, 1628, 29503, 3031, -858, 66, -44, 44, -601, 1617, 29502, 3044, -860, 66, -44, 44, -599, 1606, 29500, 3056, -862, 66, -44, 44, -597, 1595, 29498, 3069, -864, 66, -43, 44, -595, 1585, 29497, 3081, -867, 67, -43, 44, -593, 1574, 29495, 3094, -869, 67, -43, 44, -591, 1563, 29494, 3106, -871, 67, -43, 43, -589, 1552, 29492, 3119, -873, 67, -43, 43, -587, 1541, 29490, 3131, -876, 67, -43, 43, -585, 1531, 29489, 3144, -878, 68, -43, 43, -583, 1520, 29487, 3157, -880, 68, -43, 43, -581, 1509, 29485, 3169, -882, 68, -43, 43, -579, 1498, 29484, 3182, -885, 68, -43, 43, -578, 1488, 29482, 3194, -887, 68, -43, 42, -576, 1477, 29480, 3207, -889, 69, -43, 42, -574, 1466, 29478, 3220, -891, 69, -43, 42, -572, 1456, 29476, 3232, -894, 69, -42, 42, -570, 1445, 29475, 3245, -896, 69, -42, 42, -568, 1434, 29473, 3258, -898, 69, -42, 42, -566, 1424, 29471, 3271, -900, 70, -42, 42, -564, 1413, 29469, 3283, -903, 70, -42, 41, -562, 1403, 29467, 3296, -905, 70, -42, 41, -560, 1392, 29465, 3309, -907, 70, -42, 41, -558, 1381, 29463, 3322, -909, 70, -42, 41, -556, 1371, 29461, 3334, -912, 71, -42, 41, -554, 1360, 29459, 3347, -914, 71, -42, 41, -552, 1350, 29457, 3360, -916, 71, -42, 40, -550, 1339, 29455, 3373, -919, 71, -42, 40, -548, 1329, 29452, 3386, -921, 71, -42, 40, -546, 1318, 29450, 3399, -923, 72, -41, 40, -544, 1308, 29448, 3411, -925, 72, -41, 40, -542, 1297, 29446, 3424, -928, 72, -41, 40, -541, 1287, 29444, 3437, -930, 72, -41, 40, -539, 1276, 29442, 3450, -932, 72, -41, 39, -537, 1266, 29439, 3463, -935, 73, -41, 39, -535, 1256, 29437, 3476, -937, 73, -41, 39, -533, 1245, 29435, 3489, -939, 73, -41, 39, -531, 1235, 29432, 3502, -941, 73, -41, 39, -529, 1224, 29430, 3515, -944, 74, -41, 39, -527, 1214, 29428, 3528, -946, 74, -41, 39, -525, 1204, 29425, 3541, -948, 74, -41, 38, -523, 1193, 29423, 3554, -951, 74, -41, 38, -521, 1183, 29420, 3567, -953, 74, -40, 38, -520, 1173, 29418, 3580, -955, 75, -40, 38, -518, 1163, 29415, 3593, -958, 75, -40, 38, -516, 1152, 29413, 3606, -960, 75, -40, 38, -514, 1142, 29410, 3619, -962, 75, -40, 38, -512, 1132, 29408, 3632, -965, 75, -40, 37, -510, 1122, 29405, 3645, -967, 76, -40, 37, -508, 1111, 29403, 3658, -969, 76, -40, 37, -506, 1101, 29400, 3671, -971, 76, -40, 37, -504, 1091, 29397, 3684, -974, 76, -40, 37, -502, 1081, 29395, 3698, -976, 76, -40, 37, -501, 1071, 29392, 3711, -978, 77, -40, 37, -499, 1061, 29389, 3724, -981, 77, -40, 36, -497, 1050, 29386, 3737, -983, 77, -39, 36, -495, 1040, 29384, 3750, -985, 77, -39, 36, -493, 1030, 29381, 3764, -988, 77, -39, 36, -491, 1020, 29378, 3777, -990, 78, -39, 36, -489, 1010, 29375, 3790, -992, 78, -39, 36, -488, 1000, 29372, 3803, -995, 78, -39, 36, -486, 990, 29369, 3816, -997, 78, -39, 35, -484, 980, 29367, 3830, -999, 79, -39, 35, -482, 970, 29364, 3843, -1002, 79, -39, 35, -480, 960, 29361, 3856, -1004, 79, -39, 35, -478, 950, 29358, 3870, -1006, 79, -39, 35, -476, 940, 29355, 3883, -1009, 79, -39, 35, -475, 930, 29352, 3896, -1011, 80, -39, 35, -473, 920, 29349, 3910, -1014, 80, -38, 34, -471, 910, 29346, 3923, -1016, 80, -38, 34, -469, 900, 29342, 3936, -1018, 80, -38, 34, -467, 890, 29339, 3950, -1021, 80, -38, 34, -465, 880, 29336, 3963, -1023, 81, -38, 34, -464, 871, 29333, 3976, -1025, 81, -38, 34, -462, 861, 29330, 3990, -1028, 81, -38, 34, -460, 851, 29327, 4003, -1030, 81, -38, 34, -458, 841, 29323, 4017, -1032, 82, -38, 33, -456, 831, 29320, 4030, -1035, 82, -38, 33, -454, 821, 29317, 4044, -1037, 82, -38, 33, -453, 812, 29314, 4057, -1039, 82, -38, 33, -451, 802, 29310, 4071, -1042, 82, -38, 33, -449, 792, 29307, 4084, -1044, 83, -37, 33, -447, 782, 29303, 4098, -1047, 83, -37, 33, -445, 773, 29300, 4111, -1049, 83, -37, 32, -444, 763, 29297, 4125, -1051, 83, -37, 32, -442, 753, 29293, 4138, -1054, 84, -37, 32, -440, 743, 29290, 4152, -1056, 84, -37, 32, -438, 734, 29286, 4165, -1058, 84, -37, 32, -436, 724, 29283, 4179, -1061, 84, -37, 32, -435, 714, 29279, 4193, -1063, 84, -37, 32, -433, 705, 29276, 4206, -1066, 85, -37, 32, -431, 695, 29272, 4220, -1068, 85, -37, 31, -429, 686, 29268, 4234, -1070, 85, -37, 31, -427, 676, 29265, 4247, -1073, 85, -36, 31, -426, 666, 29261, 4261, -1075, 86, -36, 31, -424, 657, 29258, 4275, -1077, 86, -36, 31, -422, 647, 29254, 4288, -1080, 86, -36, 31, -420, 638, 29250, 4302, -1082, 86, -36, 31, -419, 628, 29246, 4316, -1085, 86, -36, 31, -417, 619, 29243, 4329, -1087, 87, -36, 30, -415, 609, 29239, 4343, -1089, 87, -36, 30, -413, 600, 29235, 4357, -1092, 87, -36, 30, -411, 590, 29231, 4371, -1094, 87, -36, 30, -410, 581, 29227, 4384, -1097, 88, -36, 30, -408, 571, 29223, 4398, -1099, 88, -36, 30, -406, 562, 29220, 4412, -1101, 88, -36, 30, -404, 552, 29216, 4426, -1104, 88, -35, 30, -403, 543, 29212, 4440, -1106, 88, -35, 29, -401, 534, 29208, 4453, -1109, 89, -35, 29, -399, 524, 29204, 4467, -1111, 89, -35, 29, -397, 515, 29200, 4481, -1113, 89, -35, 29, -396, 506, 29196, 4495, -1116, 89, -35, 29, -394, 496, 29192, 4509, -1118, 90, -35, 29, -392, 487, 29188, 4523, -1121, 90, -35, 29, -391, 478, 29183, 4537, -1123, 90, -35, 29, -389, 468, 29179, 4551, -1125, 90, -35, 28, -387, 459, 29175, 4564, -1128, 91, -35, 28, -385, 450, 29171, 4578, -1130, 91, -35, 28, -384, 441, 29167, 4592, -1133, 91, -35, 28, -382, 431, 29163, 4606, -1135, 91, -34, 28, -380, 422, 29158, 4620, -1138, 91, -34, 28, -378, 413, 29154, 4634, -1140, 92, -34, 28, -377, 404, 29150, 4648, -1142, 92, -34, 28, -375, 395, 29145, 4662, -1145, 92, -34, 27, -373, 385, 29141, 4676, -1147, 92, -34, 27, -372, 376, 29137, 4690, -1150, 93, -34, 27, -370, 367, 29132, 4704, -1152, 93, -34, 27, -368, 358, 29128, 4718, -1154, 93, -34, 27, -366, 349, 29124, 4732, -1157, 93, -34, 27, -365, 340, 29119, 4747, -1159, 94, -34, 27, -363, 331, 29115, 4761, -1162, 94, -34, 27, -361, 322, 29110, 4775, -1164, 94, -34, 27, -360, 313, 29106, 4789, -1167, 94, -33, 26, -358, 304, 29101, 4803, -1169, 94, -33, 26, -356, 295, 29096, 4817, -1172, 95, -33, 26, -355, 286, 29092, 4831, -1174, 95, -33, 26, -353, 277, 29087, 4845, -1176, 95, -33, 26, -351, 268, 29083, 4860, -1179, 95, -33, 26, -350, 259, 29078, 4874, -1181, 96, -33, 26, -348, 250, 29073, 4888, -1184, 96, -33, 26, -346, 241, 29069, 4902, -1186, 96, -33, 25, -345, 232, 29064, 4916, -1189, 96, -33, 25, -343, 223, 29059, 4931, -1191, 97, -33, 25, -341, 214, 29054, 4945, -1194, 97, -33, 25, -340, 205, 29050, 4959, -1196, 97, -33, 25, -338, 196, 29045, 4973, -1198, 97, -32, 25, -336, 187, 29040, 4988, -1201, 97, -32, 25, -335, 179, 29035, 5002, -1203, 98, -32, 25, -333, 170, 29030, 5016, -1206, 98, -32, 25, -331, 161, 29025, 5031, -1208, 98, -32, 24, -330, 152, 29020, 5045, -1211, 98, -32, 24, -328, 143, 29015, 5059, -1213, 99, -32, 24, -326, 135, 29010, 5074, -1216, 99, -32, 24, -325, 126, 29005, 5088, -1218, 99, -32, 24, -323, 117, 29000, 5102, -1221, 99, -32, 24, -321, 108, 28995, 5117, -1223, 100, -32, 24, -320, 100, 28990, 5131, -1225, 100, -32, 24, -318, 91, 28985, 5146, -1228, 100, -32, 24, -317, 82, 28980, 5160, -1230, 100, -31, 23, -315, 74, 28975, 5174, -1233, 101, -31, 23, -313, 65, 28970, 5189, -1235, 101, -31, 23, -312, 56, 28965, 5203, -1238, 101, -31, 23, -310, 48, 28960, 5218, -1240, 101, -31, 23, -308, 39, 28954, 5232, -1243, 102, -31, 23, -307, 30, 28949, 5247, -1245, 102, -31, 23, -305, 22, 28944, 5261, -1248, 102, -31, 23, -304, 13, 28939, 5276, -1250, 102, -31, 23, -302, 5, 28933, 5290, -1253, 103, -31, 23, -300, -4, 28928, 5305, -1255, 103, -31, 22, -299, -12, 28923, 5319, -1258, 103, -31, 22, -297, -21, 28917, 5334, -1260, 103, -31, 22, -296, -29, 28912, 5348, -1262, 103, -30, 22, -294, -38, 28906, 5363, -1265, 104, -30, 22, -292, -46, 28901, 5378, -1267, 104, -30, 22, -291, -55, 28896, 5392, -1270, 104, -30, 22, -289, -63, 28890, 5407, -1272, 104, -30, 22, -288, -72, 28885, 5421, -1275, 105, -30, 22, -286, -80, 28879, 5436, -1277, 105, -30, 21, -285, -88, 28873, 5451, -1280, 105, -30, 21, -283, -97, 28868, 5465, -1282, 105, -30, 21, -281, -105, 28862, 5480, -1285, 106, -30, 21, -280, -114, 28857, 5495, -1287, 106, -30, 21, -278, -122, 28851, 5509, -1290, 106, -30, 21, -277, -130, 28845, 5524, -1292, 106, -30, 21, -275, -139, 28840, 5539, -1295, 107, -29, 21, -274, -147, 28834, 5553, -1297, 107, -29, 21, -272, -155, 28828, 5568, -1300, 107, -29, 21, -270, -163, 28822, 5583, -1302, 107, -29, 20, -269, -172, 28817, 5598, -1305, 108, -29, 20, -267, -180, 28811, 5612, -1307, 108, -29, 20, -266, -188, 28805, 5627, -1310, 108, -29, 20, -264, -196, 28799, 5642, -1312, 108, -29, 20, -263, -205, 28793, 5657, -1315, 109, -29, 20, -261, -213, 28788, 5672, -1317, 109, -29, 20, -260, -221, 28782, 5686, -1320, 109, -29, 20, -258, -229, 28776, 5701, -1322, 109, -29, 20, -257, -237, 28770, 5716, -1325, 110, -29, 20, -255, -246, 28764, 5731, -1327, 110, -28, 19, -254, -254, 28758, 5746, -1330, 110, -28, 19, -252, -262, 28752, 5761, -1332, 110, -28, 19, -250, -270, 28746, 5776, -1335, 111, -28, 19, -249, -278, 28740, 5791, -1337, 111, -28, 19, -247, -286, 28734, 5805, -1340, 111, -28, 19, -246, -294, 28727, 5820, -1342, 111, -28, 19, -244, -302, 28721, 5835, -1345, 112, -28, 19, -243, -310, 28715, 5850, -1347, 112, -28, 19, -241, -318, 28709, 5865, -1350, 112, -28, 19, -240, -326, 28703, 5880, -1352, 112, -28, 19, -238, -334, 28697, 5895, -1355, 113, -28, 18, -237, -342, 28690, 5910, -1357, 113, -28, 18, -235, -350, 28684, 5925, -1360, 113, -28, 18, -234, -358, 28678, 5940, -1362, 113, -27, 18, -232, -366, 28672, 5955, -1365, 114, -27, 18, -231, -374, 28665, 5970, -1367, 114, -27, 18, -229, -382, 28659, 5985, -1370, 114, -27, 18, -228, -390, 28653, 6000, -1372, 114, -27, 18, -226, -398, 28646, 6015, -1375, 115, -27, 18, -225, -405, 28640, 6030, -1377, 115, -27, 18, -223, -413, 28633, 6045, -1380, 115, -27, 18, -222, -421, 28627, 6061, -1382, 115, -27, 17, -220, -429, 28620, 6076, -1385, 116, -27, 17, -219, -437, 28614, 6091, -1387, 116, -27, 17, -218, -444, 28607, 6106, -1390, 116, -27, 17, -216, -452, 28601, 6121, -1392, 116, -27, 17, -215, -460, 28594, 6136, -1395, 117, -26, 17, -213, -468, 28588, 6151, -1397, 117, -26, 17, -212, -476, 28581, 6166, -1400, 117, -26, 17, -210, -483, 28574, 6182, -1403, 117, -26, 17, -209, -491, 28568, 6197, -1405, 118, -26, 17, -207, -499, 28561, 6212, -1408, 118, -26, 17, -206, -506, 28554, 6227, -1410, 118, -26, 16, -204, -514, 28548, 6242, -1413, 118, -26, 16, -203, -522, 28541, 6258, -1415, 119, -26, 16, -202, -529, 28534, 6273, -1418, 119, -26, 16, -200, -537, 28527, 6288, -1420, 119, -26, 16, -199, -545, 28521, 6303, -1423, 119, -26, 16, -197, -552, 28514, 6319, -1425, 120, -26, 16, -196, -560, 28507, 6334, -1428, 120, -26, 16, -194, -567, 28500, 6349, -1430, 120, -25, 16, -193, -575, 28493, 6365, -1433, 120, -25, 16, -191, -582, 28486, 6380, -1435, 121, -25, 16, -190, -590, 28479, 6395, -1438, 121, -25, 16, -189, -597, 28472, 6410, -1440, 121, -25, 15, -187, -605, 28465, 6426, -1443, 121, -25, 15, -186, -612, 28458, 6441, -1445, 122, -25, 15, -184, -620, 28451, 6457, -1448, 122, -25, 15, -183, -627, 28444, 6472, -1451, 122, -25, 15, -182, -635, 28437, 6487, -1453, 122, -25, 15, -180, -642, 28430, 6503, -1456, 123, -25, 15, -179, -650, 28423, 6518, -1458, 123, -25, 15, -177, -657, 28416, 6533, -1461, 123, -25, 15, -176, -664, 28409, 6549, -1463, 124, -25, 15, -175, -672, 28402, 6564, -1466, 124, -24, 15, -173, -679, 28395, 6580, -1468, 124, -24, 15, -172, -687, 28387, 6595, -1471, 124, -24, 14, -170, -694, 28380, 6611, -1473, 125, -24, 14, -169, -701, 28373, 6626, -1476, 125, -24, 14, -168, -708, 28366, 6642, -1478, 125, -24, 14, -166, -716, 28358, 6657, -1481, 125, -24, 14, -165, -723, 28351, 6673, -1484, 126, -24, 14, -163, -730, 28344, 6688, -1486, 126, -24, 14, -162, -738, 28336, 6704, -1489, 126, -24, 14, -161, -745, 28329, 6719, -1491, 126, -24, 14, -159, -752, 28321, 6735, -1494, 127, -24, 14, -158, -759, 28314, 6750, -1496, 127, -24, 14, -157, -766, 28307, 6766, -1499, 127, -24, 14, -155, -774, 28299, 6781, -1501, 127, -23, 14, -154, -781, 28292, 6797, -1504, 128, -23, 13, -153, -788, 28284, 6813, -1506, 128, -23, 13, -151, -795, 28277, 6828, -1509, 128, -23, 13, -150, -802, 28269, 6844, -1511, 128, -23, 13, -149, -809, 28261, 6859, -1514, 129, -23, 13, -147, -816, 28254, 6875, -1517, 129, -23, 13, -146, -824, 28246, 6891, -1519, 129, -23, 13, -144, -831, 28239, 6906, -1522, 130, -23, 13, -143, -838, 28231, 6922, -1524, 130, -23, 13, -142, -845, 28223, 6938, -1527, 130, -23, 13, -140, -852, 28216, 6953, -1529, 130, -23, 13, -139, -859, 28208, 6969, -1532, 131, -23, 13, -138, -866, 28200, 6985, -1534, 131, -23, 13, -136, -873, 28192, 7001, -1537, 131, -22, 12, -135, -880, 28185, 7016, -1539, 131, -22, 12, -134, -887, 28177, 7032, -1542, 132, -22, 12, -133, -894, 28169, 7048, -1545, 132, -22, 12, -131, -901, 28161, 7063, -1547, 132, -22, 12, -130, -907, 28153, 7079, -1550, 132, -22, 12, -129, -914, 28145, 7095, -1552, 133, -22, 12, -127, -921, 28138, 7111, -1555, 133, -22, 12, -126, -928, 28130, 7127, -1557, 133, -22, 12, -125, -935, 28122, 7142, -1560, 133, -22, 12, -123, -942, 28114, 7158, -1562, 134, -22, 12, -122, -949, 28106, 7174, -1565, 134, -22, 12, -121, -956, 28098, 7190, -1567, 134, -22, 12, -119, -962, 28090, 7206, -1570, 135, -22, 12, -118, -969, 28082, 7221, -1573, 135, -21, 11, -117, -976, 28073, 7237, -1575, 135, -21, 11, -116, -983, 28065, 7253, -1578, 135, -21, 11, -114, -989, 28057, 7269, -1580, 136, -21, 11, -113, -996, 28049, 7285, -1583, 136, -21, 11, -112, -1003, 28041, 7301, -1585, 136, -21, 11, -110, -1010, 28033, 7317, -1588, 136, -21, 11, -109, -1016, 28025, 7333, -1590, 137, -21, 11, -108, -1023, 28016, 7349, -1593, 137, -21, 11, -107, -1030, 28008, 7365, -1595, 137, -21, 11, -105, -1036, 28000, 7380, -1598, 137, -21, 11, -104, -1043, 27992, 7396, -1601, 138, -21, 11, -103, -1050, 27983, 7412, -1603, 138, -21, 11, -102, -1056, 27975, 7428, -1606, 138, -21, 11, -100, -1063, 27967, 7444, -1608, 138, -21, 11, -99, -1070, 27958, 7460, -1611, 139, -20, 10, -98, -1076, 27950, 7476, -1613, 139, -20, 10, -97, -1083, 27941, 7492, -1616, 139, -20, 10, -95, -1089, 27933, 7508, -1618, 140, -20, 10, -94, -1096, 27925, 7524, -1621, 140, -20, 10, -93, -1102, 27916, 7540, -1624, 140, -20, 10, -92, -1109, 27908, 7556, -1626, 140, -20, 10, -90, -1115, 27899, 7572, -1629, 141, -20, 10, -89, -1122, 27891, 7589, -1631, 141, -20, 10, -88, -1128, 27882, 7605, -1634, 141, -20, 10, -87, -1135, 27874, 7621, -1636, 141, -20, 10, -85, -1141, 27865, 7637, -1639, 142, -20, 10, -84, -1148, 27856, 7653, -1641, 142, -20, 10, -83, -1154, 27848, 7669, -1644, 142, -20, 10, -82, -1160, 27839, 7685, -1646, 142, -20, 10, -81, -1167, 27830, 7701, -1649, 143, -19, 9, -79, -1173, 27822, 7717, -1652, 143, -19, 9, -78, -1180, 27813, 7733, -1654, 143, -19, 9, -77, -1186, 27804, 7750, -1657, 144, -19, 9, -76, -1192, 27795, 7766, -1659, 144, -19, 9, -74, -1199, 27787, 7782, -1662, 144, -19, 9, -73, -1205, 27778, 7798, -1664, 144, -19, 9, -72, -1211, 27769, 7814, -1667, 145, -19, 9, -71, -1218, 27760, 7830, -1669, 145, -19, 9, -70, -1224, 27751, 7847, -1672, 145, -19, 9, -68, -1230, 27743, 7863, -1674, 145, -19, 9, -67, -1236, 27734, 7879, -1677, 146, -19, 9, -66, -1243, 27725, 7895, -1680, 146, -19, 9, -65, -1249, 27716, 7912, -1682, 146, -19, 9, -64, -1255, 27707, 7928, -1685, 147, -19, 9, -63, -1261, 27698, 7944, -1687, 147, -18, 9, -61, -1267, 27689, 7960, -1690, 147, -18, 9, -60, -1273, 27680, 7977, -1692, 147, -18, 8, -59, -1280, 27671, 7993, -1695, 148, -18, 8, -58, -1286, 27662, 8009, -1697, 148, -18, 8, -57, -1292, 27653, 8025, -1700, 148, -18, 8, -55, -1298, 27644, 8042, -1703, 148, -18, 8, -54, -1304, 27634, 8058, -1705, 149, -18, 8, -53, -1310, 27625, 8074, -1708, 149, -18, 8, -52, -1316, 27616, 8091, -1710, 149, -18, 8, -51, -1322, 27607, 8107, -1713, 149, -18, 8, -50, -1328, 27598, 8123, -1715, 150, -18, 8, -49, -1334, 27589, 8140, -1718, 150, -18, 8, -47, -1340, 27579, 8156, -1720, 150, -18, 8, -46, -1346, 27570, 8173, -1723, 151, -18, 8, -45, -1352, 27561, 8189, -1725, 151, -18, 8, -44, -1358, 27552, 8205, -1728, 151, -17, 8, -43, -1364, 27542, 8222, -1730, 151, -17, 8, -42, -1370, 27533, 8238, -1733, 152, -17, 8, -40, -1376, 27523, 8255, -1736, 152, -17, 8, -39, -1382, 27514, 8271, -1738, 152, -17, 7, -38, -1388, 27505, 8287, -1741, 152, -17, 7, -37, -1394, 27495, 8304, -1743, 153, -17, 7, -36, -1400, 27486, 8320, -1746, 153, -17, 7, -35, -1406, 27476, 8337, -1748, 153, -17, 7, -34, -1412, 27467, 8353, -1751, 154, -17, 7, -33, -1417, 27457, 8370, -1753, 154, -17, 7, -31, -1423, 27448, 8386, -1756, 154, -17, 7, -30, -1429, 27438, 8403, -1758, 154, -17, 7, -29, -1435, 27429, 8419, -1761, 155, -17, 7, -28, -1441, 27419, 8436, -1763, 155, -17, 7, -27, -1446, 27410, 8452, -1766, 155, -17, 7, -26, -1452, 27400, 8469, -1769, 155, -16, 7, -25, -1458, 27390, 8485, -1771, 156, -16, 7, -24, -1464, 27381, 8502, -1774, 156, -16, 7, -23, -1469, 27371, 8518, -1776, 156, -16, 7, -22, -1475, 27361, 8535, -1779, 157, -16, 7, -20, -1481, 27352, 8552, -1781, 157, -16, 7, -19, -1486, 27342, 8568, -1784, 157, -16, 7, -18, -1492, 27332, 8585, -1786, 157, -16, 6, -17, -1498, 27322, 8601, -1789, 158, -16, 6, -16, -1503, 27313, 8618, -1791, 158, -16, 6, -15, -1509, 27303, 8634, -1794, 158, -16, 6, -14, -1515, 27293, 8651, -1796, 158, -16, 6, -13, -1520, 27283, 8668, -1799, 159, -16, 6, -12, -1526, 27273, 8684, -1801, 159, -16, 6, -11, -1532, 27263, 8701, -1804, 159, -16, 6, -10, -1537, 27254, 8718, -1807, 159, -16, 6, -9, -1543, 27244, 8734, -1809, 160, -16, 6, -8, -1548, 27234, 8751, -1812, 160, -15, 6, -6, -1554, 27224, 8768, -1814, 160, -15, 6, -5, -1559, 27214, 8784, -1817, 161, -15, 6, -4, -1565, 27204, 8801, -1819, 161, -15, 6, -3, -1570, 27194, 8818, -1822, 161, -15, 6, -2, -1576, 27184, 8834, -1824, 161, -15, 6, -1, -1581, 27174, 8851, -1827, 162, -15, 6, 0, -1587, 27164, 8868, -1829, 162, -15, 6, 1, -1592, 27153, 8885, -1832, 162, -15, 6, 2, -1597, 27143, 8901, -1834, 162, -15, 6, 3, -1603, 27133, 8918, -1837, 163, -15, 6, 4, -1608, 27123, 8935, -1839, 163, -15, 6, 5, -1614, 27113, 8952, -1842, 163, -15, 5, 6, -1619, 27103, 8968, -1844, 164, -15, 5, 7, -1624, 27092, 8985, -1847, 164, -15, 5, 8, -1630, 27082, 9002, -1849, 164, -15, 5, 9, -1635, 27072, 9019, -1852, 164, -15, 5, 10, -1640, 27062, 9035, -1854, 165, -14, 5, 11, -1646, 27051, 9052, -1857, 165, -14, 5, 12, -1651, 27041, 9069, -1859, 165, -14, 5, 13, -1656, 27031, 9086, -1862, 165, -14, 5, 14, -1661, 27020, 9103, -1865, 166, -14, 5, 15, -1667, 27010, 9119, -1867, 166, -14, 5, 16, -1672, 27000, 9136, -1870, 166, -14, 5, 17, -1677, 26989, 9153, -1872, 167, -14, 5, 18, -1682, 26979, 9170, -1875, 167, -14, 5, 19, -1688, 26968, 9187, -1877, 167, -14, 5, 20, -1693, 26958, 9204, -1880, 167, -14, 5, 21, -1698, 26948, 9221, -1882, 168, -14, 5, 22, -1703, 26937, 9237, -1885, 168, -14, 5, 23, -1708, 26926, 9254, -1887, 168, -14, 5, 24, -1713, 26916, 9271, -1890, 168, -14, 5, 25, -1719, 26905, 9288, -1892, 169, -14, 5, 26, -1724, 26895, 9305, -1895, 169, -14, 5, 27, -1729, 26884, 9322, -1897, 169, -13, 5, 28, -1734, 26874, 9339, -1900, 170, -13, 4, 29, -1739, 26863, 9356, -1902, 170, -13, 4, 30, -1744, 26852, 9373, -1905, 170, -13, 4, 31, -1749, 26842, 9390, -1907, 170, -13, 4, 32, -1754, 26831, 9407, -1910, 171, -13, 4, 33, -1759, 26820, 9424, -1912, 171, -13, 4, 34, -1764, 26810, 9441, -1915, 171, -13, 4, 35, -1769, 26799, 9458, -1917, 171, -13, 4, 36, -1774, 26788, 9475, -1920, 172, -13, 4, 37, -1779, 26777, 9492, -1922, 172, -13, 4, 38, -1784, 26767, 9509, -1925, 172, -13, 4, 39, -1789, 26756, 9526, -1927, 173, -13, 4, 40, -1794, 26745, 9543, -1930, 173, -13, 4, 41, -1799, 26734, 9560, -1932, 173, -13, 4, 42, -1804, 26723, 9577, -1935, 173, -13, 4, 43, -1809, 26712, 9594, -1937, 174, -13, 4, 44, -1813, 26701, 9611, -1939, 174, -13, 4, 45, -1818, 26690, 9628, -1942, 174, -13, 4, 46, -1823, 26679, 9645, -1944, 174, -12, 4, 46, -1828, 26669, 9662, -1947, 175, -12, 4, 47, -1833, 26658, 9679, -1949, 175, -12, 4, 48, -1838, 26647, 9696, -1952, 175, -12, 4, 49, -1843, 26636, 9713, -1954, 176, -12, 4, 50, -1847, 26625, 9730, -1957, 176, -12, 4, 51, -1852, 26613, 9747, -1959, 176, -12, 4, 52, -1857, 26602, 9764, -1962, 176, -12, 4, 53, -1862, 26591, 9781, -1964, 177, -12, 4, 54, -1866, 26580, 9799, -1967, 177, -12, 3, 55, -1871, 26569, 9816, -1969, 177, -12, 3, 56, -1876, 26558, 9833, -1972, 177, -12, 3, 57, -1880, 26547, 9850, -1974, 178, -12, 3, 58, -1885, 26536, 9867, -1977, 178, -12, 3, 58, -1890, 26524, 9884, -1979, 178, -12, 3, 59, -1895, 26513, 9901, -1982, 178, -12, 3, 60, -1899, 26502, 9919, -1984, 179, -12, 3, 61, -1904, 26491, 9936, -1986, 179, -12, 3, 62, -1908, 26479, 9953, -1989, 179, -11, 3, 63, -1913, 26468, 9970, -1991, 180, -11, 3, 64, -1918, 26457, 9987, -1994, 180, -11, 3, 65, -1922, 26445, 10004, -1996, 180, -11, 3, 66, -1927, 26434, 10022, -1999, 180, -11, 3, 67, -1931, 26423, 10039, -2001, 181, -11, 3, 67, -1936, 26411, 10056, -2004, 181, -11, 3, 68, -1940, 26400, 10073, -2006, 181, -11, 3, 69, -1945, 26389, 10090, -2009, 181, -11, 3, 70, -1949, 26377, 10108, -2011, 182, -11, 3, 71, -1954, 26366, 10125, -2013, 182, -11, 3, 72, -1958, 26354, 10142, -2016, 182, -11, 3, 73, -1963, 26343, 10159, -2018, 183, -11, 3, 74, -1967, 26331, 10177, -2021, 183, -11, 3, 74, -1972, 26320, 10194, -2023, 183, -11, 3, 75, -1976, 26308, 10211, -2026, 183, -11, 3, 76, -1981, 26297, 10228, -2028, 184, -11, 3, 77, -1985, 26285, 10246, -2031, 184, -11, 3, 78, -1990, 26273, 10263, -2033, 184, -11, 3, 79, -1994, 26262, 10280, -2035, 184, -11, 3, 80, -1998, 26250, 10298, -2038, 185, -10, 3, 80, -2003, 26239, 10315, -2040, 185, -10, 3, 81, -2007, 26227, 10332, -2043, 185, -10, 3, 82, -2011, 26215, 10350, -2045, 185, -10, 2, 83, -2016, 26204, 10367, -2048, 186, -10, 2, 84, -2020, 26192, 10384, -2050, 186, -10, 2, 85, -2024, 26180, 10402, -2052, 186, -10, 2, 85, -2029, 26168, 10419, -2055, 187, -10, 2, 86, -2033, 26157, 10436, -2057, 187, -10, 2, 87, -2037, 26145, 10454, -2060, 187, -10, 2, 88, -2041, 26133, 10471, -2062, 187, -10, 2, 89, -2046, 26121, 10488, -2065, 188, -10, 2, 90, -2050, 26109, 10506, -2067, 188, -10, 2, 90, -2054, 26097, 10523, -2069, 188, -10, 2, 91, -2058, 26086, 10540, -2072, 188, -10, 2, 92, -2062, 26074, 10558, -2074, 189, -10, 2, 93, -2067, 26062, 10575, -2077, 189, -10, 2, 94, -2071, 26050, 10593, -2079, 189, -10, 2, 95, -2075, 26038, 10610, -2081, 189, -10, 2, 95, -2079, 26026, 10627, -2084, 190, -10, 2, 96, -2083, 26014, 10645, -2086, 190, -10, 2, 97, -2087, 26002, 10662, -2089, 190, -9, 2, 98, -2091, 25990, 10680, -2091, 191, -9, 2, 99, -2096, 25978, 10697, -2093, 191, -9, 2, 99, -2100, 25966, 10715, -2096, 191, -9, 2, 100, -2104, 25954, 10732, -2098, 191, -9, 2, 101, -2108, 25942, 10749, -2101, 192, -9, 2, 102, -2112, 25930, 10767, -2103, 192, -9, 2, 103, -2116, 25918, 10784, -2105, 192, -9, 2, 103, -2120, 25905, 10802, -2108, 192, -9, 2, 104, -2124, 25893, 10819, -2110, 193, -9, 2, 105, -2128, 25881, 10837, -2113, 193, -9, 2, 106, -2132, 25869, 10854, -2115, 193, -9, 2, 106, -2136, 25857, 10872, -2117, 193, -9, 2, 107, -2140, 25845, 10889, -2120, 194, -9, 2, 108, -2144, 25832, 10907, -2122, 194, -9, 2, 109, -2148, 25820, 10924, -2125, 194, -9, 2, 110, -2152, 25808, 10942, -2127, 194, -9, 2, 110, -2155, 25796, 10959, -2129, 195, -9, 2, 111, -2159, 25783, 10977, -2132, 195, -9, 2, 112, -2163, 25771, 10994, -2134, 195, -9, 2, 113, -2167, 25759, 11012, -2136, 196, -9, 2, 113, -2171, 25746, 11029, -2139, 196, -9, 2, 114, -2175, 25734, 11047, -2141, 196, -8, 1, 115, -2179, 25722, 11064, -2143, 196, -8, 1, 116, -2182, 25709, 11082, -2146, 197, -8, 1, 116, -2186, 25697, 11099, -2148, 197, -8, 1, 117, -2190, 25684, 11117, -2151, 197, -8, 1, 118, -2194, 25672, 11135, -2153, 197, -8, 1, 119, -2198, 25659, 11152, -2155, 198, -8, 1, 119, -2201, 25647, 11170, -2158, 198, -8, 1, 120, -2205, 25634, 11187, -2160, 198, -8, 1, 121, -2209, 25622, 11205, -2162, 198, -8, 1, 122, -2213, 25609, 11222, -2165, 199, -8, 1, 122, -2216, 25597, 11240, -2167, 199, -8, 1, 123, -2220, 25584, 11258, -2169, 199, -8, 1, 124, -2224, 25572, 11275, -2172, 199, -8, 1, 125, -2227, 25559, 11293, -2174, 200, -8, 1, 125, -2231, 25546, 11311, -2176, 200, -8, 1, 126, -2235, 25534, 11328, -2179, 200, -8, 1, 127, -2238, 25521, 11346, -2181, 200, -8, 1, 127, -2242, 25508, 11363, -2183, 201, -8, 1, 128, -2246, 25496, 11381, -2186, 201, -8, 1, 129, -2249, 25483, 11399, -2188, 201, -8, 1, 130, -2253, 25470, 11416, -2190, 202, -8, 1, 130, -2256, 25458, 11434, -2193, 202, -8, 1, 131, -2260, 25445, 11452, -2195, 202, -8, 1, 132, -2264, 25432, 11469, -2197, 202, -7, 1, 132, -2267, 25419, 11487, -2200, 203, -7, 1, 133, -2271, 25407, 11505, -2202, 203, -7, 1, 134, -2274, 25394, 11522, -2204, 203, -7, 1, 135, -2278, 25381, 11540, -2207, 203, -7, 1, 135, -2281, 25368, 11558, -2209, 204, -7, 1, 136, -2285, 25355, 11575, -2211, 204, -7, 1, 137, -2288, 25342, 11593, -2213, 204, -7, 1, 137, -2292, 25329, 11611, -2216, 204, -7, 1, 138, -2295, 25317, 11628, -2218, 205, -7, 1, 139, -2299, 25304, 11646, -2220, 205, -7, 1, 139, -2302, 25291, 11664, -2223, 205, -7, 1, 140, -2305, 25278, 11681, -2225, 205, -7, 1, 141, -2309, 25265, 11699, -2227, 206, -7, 1, 141, -2312, 25252, 11717, -2229, 206, -7, 1, 142, -2316, 25239, 11735, -2232, 206, -7, 1, 143, -2319, 25226, 11752, -2234, 206, -7, 1, 143, -2322, 25213, 11770, -2236, 207, -7, 1, 144, -2326, 25200, 11788, -2239, 207, -7, 1, 145, -2329, 25187, 11806, -2241, 207, -7, 1, 145, -2332, 25173, 11823, -2243, 207, -7, 1, 146, -2336, 25160, 11841, -2245, 208, -7, 1, 147, -2339, 25147, 11859, -2248, 208, -7, 1, 147, -2342, 25134, 11877, -2250, 208, -7, 1, 148, -2346, 25121, 11894, -2252, 208, -7, 1, 149, -2349, 25108, 11912, -2254, 209, -6, 1, 149, -2352, 25095, 11930, -2257, 209, -6, 1, 150, -2355, 25081, 11948, -2259, 209, -6, 1, 151, -2359, 25068, 11965, -2261, 209, -6, 1, 151, -2362, 25055, 11983, -2264, 210, -6, 1, 152, -2365, 25042, 12001, -2266, 210, -6, 1, 153, -2368, 25029, 12019, -2268, 210, -6, 1, 153, -2371, 25015, 12036, -2270, 210, -6, 0, 154, -2375, 25002, 12054, -2272, 211, -6, 0, 154, -2378, 24989, 12072, -2275, 211, -6, 0, 155, -2381, 24975, 12090, -2277, 211, -6, 0, 156, -2384, 24962, 12108, -2279, 211, -6, 0, 156, -2387, 24949, 12126, -2281, 212, -6, 0, 157, -2390, 24935, 12143, -2284, 212, -6, 0, 158, -2393, 24922, 12161, -2286, 212, -6, 0, 158, -2396, 24909, 12179, -2288, 212, -6, 0, 159, -2400, 24895, 12197, -2290, 213, -6, 0, 159, -2403, 24882, 12215, -2293, 213, -6, 0, 160, -2406, 24868, 12232, -2295, 213, -6, 0, 161, -2409, 24855, 12250, -2297, 213, -6, 0, 161, -2412, 24841, 12268, -2299, 214, -6, 0, 162, -2415, 24828, 12286, -2301, 214, -6, 0, 163, -2418, 24814, 12304, -2304, 214, -6, 0, 163, -2421, 24801, 12322, -2306, 214, -6, 0, 164, -2424, 24787, 12340, -2308, 215, -6, 0, 164, -2427, 24774, 12357, -2310, 215, -6, 0, 165, -2430, 24760, 12375, -2312, 215, -6, 0, 166, -2433, 24747, 12393, -2315, 215, -5, 0, 166, -2436, 24733, 12411, -2317, 216, -5, 0, 167, -2439, 24719, 12429, -2319, 216, -5, 0, 167, -2442, 24706, 12447, -2321, 216, -5, 0, 168, -2444, 24692, 12465, -2323, 216, -5, 0, 168, -2447, 24679, 12482, -2326, 217, -5, 0, 169, -2450, 24665, 12500, -2328, 217, -5, 0, 170, -2453, 24651, 12518, -2330, 217, -5, 0, 170, -2456, 24637, 12536, -2332, 217, -5, 0, 171, -2459, 24624, 12554, -2334, 217, -5, 0, 171, -2462, 24610, 12572, -2336, 218, -5, 0, 172, -2465, 24596, 12590, -2339, 218, -5, 0, 173, -2467, 24583, 12608, -2341, 218, -5, 0, 173, -2470, 24569, 12626, -2343, 218, -5, 0, 174, -2473, 24555, 12644, -2345, 219, -5, 0, 174, -2476, 24541, 12661, -2347, 219, -5, 0, 175, -2479, 24527, 12679, -2349, 219, -5, 0, 175, -2481, 24514, 12697, -2351, 219, -5, 0, 176, -2484, 24500, 12715, -2354, 220, -5, 0, 176, -2487, 24486, 12733, -2356, 220, -5, 0, 177, -2490, 24472, 12751, -2358, 220, -5, 0, 178, -2492, 24458, 12769, -2360, 220, -5, 0, 178, -2495, 24444, 12787, -2362, 221, -5, 0, 179, -2498, 24430, 12805, -2364, 221, -5, 0, 179, -2500, 24416, 12823, -2366, 221, -5, 0, 180, -2503, 24402, 12841, -2369, 221, -5, 0, 180, -2506, 24388, 12859, -2371, 222, -5, 0, 181, -2508, 24374, 12877, -2373, 222, -5, 0, 181, -2511, 24360, 12895, -2375, 222, -5, 0, 182, -2514, 24346, 12913, -2377, 222, -5, 0, 183, -2516, 24332, 12931, -2379, 222, -5, 0, 183, -2519, 24318, 12949, -2381, 223, -4, 0, 184, -2521, 24304, 12966, -2383, 223, -4, 0, 184, -2524, 24290, 12984, -2385, 223, -4, 0, 185, -2527, 24276, 13002, -2388, 223, -4, 0, 185, -2529, 24262, 13020, -2390, 224, -4, 0, 186, -2532, 24248, 13038, -2392, 224, -4, 0, 186, -2534, 24234, 13056, -2394, 224, -4, 0, 187, -2537, 24220, 13074, -2396, 224, -4, 0, 187, -2539, 24206, 13092, -2398, 225, -4, 0, 188, -2542, 24191, 13110, -2400, 225, -4, 0, 188, -2544, 24177, 13128, -2402, 225, -4, 0, 189, -2547, 24163, 13146, -2404, 225, -4, 0, 189, -2549, 24149, 13164, -2406, 225, -4, 0, 190, -2552, 24135, 13182, -2408, 226, -4, 0, 190, -2554, 24120, 13200, -2410, 226, -4, 0, 191, -2557, 24106, 13218, -2412, 226, -4, 0, 191, -2559, 24092, 13236, -2414, 226, -4, 0, 192, -2562, 24078, 13254, -2417, 227, -4, 0, 192, -2564, 24063, 13272, -2419, 227, -4, 0, 193, -2566, 24049, 13290, -2421, 227, -4, 0, 193, -2569, 24035, 13308, -2423, 227, -4, 0, 194, -2571, 24020, 13326, -2425, 228, -4, 0, 194, -2574, 24006, 13344, -2427, 228, -4, 0, 195, -2576, 23992, 13362, -2429, 228, -4, 0, 195, -2578, 23977, 13380, -2431, 228, -4, 0, 196, -2581, 23963, 13398, -2433, 228, -4, 0, 196, -2583, 23948, 13416, -2435, 229, -4, 0, 197, -2585, 23934, 13434, -2437, 229, -4, 0, 197, -2588, 23920, 13452, -2439, 229, -4, 0, 198, -2590, 23905, 13470, -2441, 229, -4, 0, 198, -2592, 23891, 13488, -2443, 230, -4, 0, 199, -2594, 23876, 13506, -2445, 230, -4, 0, 199, -2597, 23862, 13525, -2447, 230, -4, 0, 200, -2599, 23847, 13543, -2449, 230, -4, 0, 200, -2601, 23833, 13561, -2451, 230, -3, 0, 201, -2603, 23818, 13579, -2453, 231, -3, 0, 201, -2606, 23804, 13597, -2455, 231, -3, 0, 201, -2608, 23789, 13615, -2457, 231, -3, 0, 202, -2610, 23775, 13633, -2459, 231, -3, 0, 202, -2612, 23760, 13651, -2461, 231, -3, 0, 203, -2614, 23745, 13669, -2463, 232, -3, 0, 203, -2617, 23731, 13687, -2465, 232, -3, 0, 204, -2619, 23716, 13705, -2467, 232, -3, 0, 204, -2621, 23702, 13723, -2469, 232, -3, 0, 205, -2623, 23687, 13741, -2471, 233, -3, 0, 205, -2625, 23672, 13759, -2473, 233, -3, 0, 206, -2627, 23658, 13777, -2475, 233, -3, 0, 206, -2629, 23643, 13795, -2477, 233, -3, 0, 206, -2631, 23628, 13813, -2479, 233, -3, 0, 207, -2634, 23614, 13831, -2480, 234, -3, 0, 207, -2636, 23599, 13849, -2482, 234, -3, 0, 208, -2638, 23584, 13868, -2484, 234, -3, 0, 208, -2640, 23569, 13886, -2486, 234, -3, 0, 209, -2642, 23555, 13904, -2488, 234, -3, 0, 209, -2644, 23540, 13922, -2490, 235, -3, 0, 210, -2646, 23525, 13940, -2492, 235, -3, 0, 210, -2648, 23510, 13958, -2494, 235, -3, 0, 210, -2650, 23495, 13976, -2496, 235, -3, 0, 211, -2652, 23481, 13994, -2498, 236, -3, 0, 211, -2654, 23466, 14012, -2500, 236, -3, 0, 212, -2656, 23451, 14030, -2502, 236, -3, 0, 212, -2658, 23436, 14048, -2504, 236, -3, 0, 212, -2660, 23421, 14066, -2505, 236, -3, 0, 213, -2662, 23406, 14084, -2507, 237, -3, 0, 213, -2664, 23391, 14103, -2509, 237, -3, 0, 214, -2665, 23376, 14121, -2511, 237, -3, 0, 214, -2667, 23361, 14139, -2513, 237, -3, 0, 215, -2669, 23346, 14157, -2515, 237, -3, 0, 215, -2671, 23332, 14175, -2517, 238, -3, 0, 215, -2673, 23317, 14193, -2519, 238, -3, 0, 216, -2675, 23302, 14211, -2521, 238, -3, 0, 216, -2677, 23287, 14229, -2522, 238, -3, 0, 217, -2679, 23272, 14247, -2524, 238, -3, 0, 217, -2680, 23257, 14265, -2526, 239, -3, 0, 217, -2682, 23242, 14283, -2528, 239, -3, 0, 218, -2684, 23226, 14302, -2530, 239, -2, 0, 218, -2686, 23211, 14320, -2532, 239, -2, 0, 219, -2688, 23196, 14338, -2533, 239, -2, 0, 219, -2689, 23181, 14356, -2535, 240, -2, 0, 219, -2691, 23166, 14374, -2537, 240, -2, 0, 220, -2693, 23151, 14392, -2539, 240, -2, 0, 220, -2695, 23136, 14410, -2541, 240, -2, 0, 220, -2697, 23121, 14428, -2543, 240, -2, 0, 221, -2698, 23106, 14446, -2544, 241, -2, 0, 221, -2700, 23090, 14464, -2546, 241, -2, 0, 222, -2702, 23075, 14483, -2548, 241, -2, 0, 222, -2703, 23060, 14501, -2550, 241, -2, 0, 222, -2705, 23045, 14519, -2552, 241, -2, 0, 223, -2707, 23030, 14537, -2553, 242, -2, 0, 223, -2708, 23015, 14555, -2555, 242, -2, 0, 223, -2710, 22999, 14573, -2557, 242, -2, 0, 224, -2712, 22984, 14591, -2559, 242, -2, 0, 224, -2713, 22969, 14609, -2561, 242, -2, 0, 225, -2715, 22954, 14627, -2562, 243, -2, 0, 225, -2717, 22938, 14646, -2564, 243, -2, 0, 225, -2718, 22923, 14664, -2566, 243, -2, 0, 226, -2720, 22908, 14682, -2568, 243, -2, 0, 226, -2721, 22892, 14700, -2569, 243, -2, 0, 226, -2723, 22877, 14718, -2571, 243, -2, 0, 227, -2725, 22862, 14736, -2573, 244, -2, 0, 227, -2726, 22846, 14754, -2575, 244, -2, 0, 227, -2728, 22831, 14772, -2576, 244, -2, 0, 228, -2729, 22816, 14790, -2578, 244, -2, 0, 228, -2731, 22800, 14809, -2580, 244, -2, 0, 228, -2732, 22785, 14827, -2582, 245, -2, 0, 229, -2734, 22769, 14845, -2583, 245, -2, 0, 229, -2735, 22754, 14863, -2585, 245, -2, 0, 229, -2737, 22738, 14881, -2587, 245, -2, 0, 230, -2738, 22723, 14899, -2589, 245, -2, 0, 230, -2740, 22708, 14917, -2590, 245, -2, 0, 230, -2741, 22692, 14935, -2592, 246, -2, 0, 231, -2743, 22677, 14953, -2594, 246, -2, 0, 231, -2744, 22661, 14972, -2595, 246, -2, 0, 231, -2746, 22646, 14990, -2597, 246, -2, 0, 232, -2747, 22630, 15008, -2599, 246, -2, 0, 232, -2748, 22615, 15026, -2600, 247, -2, 0, 232, -2750, 22599, 15044, -2602, 247, -2, 0, 233, -2751, 22583, 15062, -2604, 247, -2, 0, 233, -2753, 22568, 15080, -2606, 247, -2, 0, 233, -2754, 22552, 15098, -2607, 247, -2, 0, 234, -2755, 22537, 15116, -2609, 247, -2, 0, 234, -2757, 22521, 15135, -2611, 248, -2, 0, 234, -2758, 22506, 15153, -2612, 248, -2, 0, 235, -2759, 22490, 15171, -2614, 248, -2, 0, 235, -2761, 22474, 15189, -2615, 248, -2, 0, 235, -2762, 22459, 15207, -2617, 248, -2, 0, 236, -2763, 22443, 15225, -2619, 248, -2, 0, 236, -2765, 22427, 15243, -2620, 249, -1, 0, 236, -2766, 22412, 15261, -2622, 249, -1, 0, 236, -2767, 22396, 15279, -2624, 249, -1, 0, 237, -2769, 22380, 15298, -2625, 249, -1, 0, 237, -2770, 22365, 15316, -2627, 249, -1, 0, 237, -2771, 22349, 15334, -2628, 249, -1, 0, 238, -2772, 22333, 15352, -2630, 250, -1, 0, 238, -2774, 22317, 15370, -2632, 250, -1, 0, 238, -2775, 22302, 15388, -2633, 250, -1, 0, 239, -2776, 22286, 15406, -2635, 250, -1, 0, 239, -2777, 22270, 15424, -2636, 250, -1, 0, 239, -2778, 22254, 15442, -2638, 250, -1, 0, 239, -2780, 22238, 15460, -2640, 251, -1, 0, 240, -2781, 22223, 15479, -2641, 251, -1, 0, 240, -2782, 22207, 15497, -2643, 251, -1, 0, 240, -2783, 22191, 15515, -2644, 251, -1, 0, 241, -2784, 22175, 15533, -2646, 251, -1, 0, 241, -2785, 22159, 15551, -2647, 251, -1, 0, 241, -2786, 22143, 15569, -2649, 252, -1, 0, 241, -2788, 22128, 15587, -2651, 252, -1, 0, 242, -2789, 22112, 15605, -2652, 252, -1, 0, 242, -2790, 22096, 15623, -2654, 252, -1, 0, 242, -2791, 22080, 15641, -2655, 252, -1, 0, 242, -2792, 22064, 15660, -2657, 252, -1, 0, 243, -2793, 22048, 15678, -2658, 253, -1, 0, 243, -2794, 22032, 15696, -2660, 253, -1, 0, 243, -2795, 22016, 15714, -2661, 253, -1, 0, 243, -2796, 22000, 15732, -2663, 253, -1, 0, 244, -2797, 21984, 15750, -2664, 253, -1, 0, 244, -2798, 21968, 15768, -2666, 253, -1, 0, 244, -2799, 21952, 15786, -2667, 253, -1, 0, 244, -2800, 21936, 15804, -2669, 254, -1, 0, 245, -2801, 21920, 15822, -2670, 254, -1, 0, 245, -2802, 21904, 15840, -2672, 254, -1, 0, 245, -2803, 21888, 15858, -2673, 254, -1, 0, 245, -2804, 21872, 15877, -2675, 254, -1, 0, 246, -2805, 21856, 15895, -2676, 254, -1, 0, 246, -2806, 21840, 15913, -2678, 254, -1, 0, 246, -2807, 21824, 15931, -2679, 255, -1, 0, 246, -2808, 21808, 15949, -2680, 255, -1, 0, 247, -2809, 21792, 15967, -2682, 255, -1, 0, 247, -2810, 21776, 15985, -2683, 255, -1, 0, 247, -2811, 21759, 16003, -2685, 255, -1, 0, 247, -2812, 21743, 16021, -2686, 255, -1, 0, 248, -2813, 21727, 16039, -2688, 255, -1, 0, 248, -2813, 21711, 16057, -2689, 256, -1, 0, 248, -2814, 21695, 16075, -2691, 256, -1, 0, 248, -2815, 21679, 16093, -2692, 256, -1, 0, 249, -2816, 21662, 16111, -2693, 256, -1, 0, 249, -2817, 21646, 16130, -2695, 256, -1, 0, 249, -2818, 21630, 16148, -2696, 256, -1, 0, 249, -2819, 21614, 16166, -2698, 256, -1, 0, 249, -2819, 21598, 16184, -2699, 257, -1, 0, 250, -2820, 21581, 16202, -2700, 257, -1, 0, 250, -2821, 21565, 16220, -2702, 257, -1, 0, 250, -2822, 21549, 16238, -2703, 257, -1, 0, 250, -2823, 21533, 16256, -2704, 257, -1, 0, 251, -2823, 21516, 16274, -2706, 257, -1, 0, 251, -2824, 21500, 16292, -2707, 257, -1, 0, 251, -2825, 21484, 16310, -2708, 257, -1, 0, 251, -2826, 21468, 16328, -2710, 258, -1, 0, 251, -2826, 21451, 16346, -2711, 258, -1, 0, 252, -2827, 21435, 16364, -2712, 258, -1, 0, 252, -2828, 21419, 16382, -2714, 258, -1, 0, 252, -2829, 21402, 16400, -2715, 258, -1, 0, 252, -2829, 21386, 16418, -2716, 258, -1, 0, 252, -2830, 21370, 16436, -2718, 258, -1, 0, 253, -2831, 21353, 16454, -2719, 259, -1, 0, 253, -2831, 21337, 16472, -2720, 259, -1, 0, 253, -2832, 21320, 16490, -2722, 259, -1, 0, 253, -2833, 21304, 16508, -2723, 259, -1, 0, 253, -2833, 21288, 16526, -2724, 259, -1, 0, 254, -2834, 21271, 16544, -2726, 259, -1, 0, 254, -2835, 21255, 16562, -2727, 259, -1, 0, 254, -2835, 21238, 16580, -2728, 259, -1, 0, 254, -2836, 21222, 16598, -2729, 259, -1, 0, 254, -2836, 21205, 16616, -2731, 260, -1, 0, 255, -2837, 21189, 16634, -2732, 260, -1, 0, 255, -2838, 21173, 16652, -2733, 260, -1, 0, 255, -2838, 21156, 16670, -2734, 260, -1, 0, 255, -2839, 21140, 16688, -2736, 260, -1, 0, 255, -2839, 21123, 16706, -2737, 260, 0, 0, 255, -2840, 21107, 16724, -2738, 260, 0, 0, 256, -2840, 21090, 16742, -2739, 260, 0, 0, 256, -2841, 21074, 16760, -2740, 260, 0, 0, 256, -2841, 21057, 16778, -2742, 261, 0, 0, 256, -2842, 21040, 16796, -2743, 261, 0, 0, 256, -2842, 21024, 16814, -2744, 261, 0, 0, 256, -2843, 21007, 16832, -2745, 261, 0, 0, 257, -2843, 20991, 16850, -2746, 261, 0, 0, 257, -2844, 20974, 16868, -2748, 261, 0, 0, 257, -2844, 20958, 16886, -2749, 261, 0, 0, 257, -2845, 20941, 16904, -2750, 261, 0, 0, 257, -2845, 20924, 16922, -2751, 261, 0, 0, 257, -2846, 20908, 16940, -2752, 262, 0, 0, 258, -2846, 20891, 16958, -2753, 262, 0, 0, 258, -2847, 20875, 16976, -2755, 262, 0, 0, 258, -2847, 20858, 16994, -2756, 262, 0, 0, 258, -2847, 20841, 17012, -2757, 262, 0, 0, 258, -2848, 20825, 17030, -2758, 262, 0, 0, 258, -2848, 20808, 17048, -2759, 262, 0, 0, 258, -2849, 20791, 17066, -2760, 262, 0, 0, 259, -2849, 20775, 17084, -2761, 262, 0, 0, 259, -2849, 20758, 17101, -2763, 262, 0, 0, 259, -2850, 20741, 17119, -2764, 262, 0, 0, 259, -2850, 20724, 17137, -2765, 263, 0, 0, 259, -2851, 20708, 17155, -2766, 263, 0, 0, 259, -2851, 20691, 17173, -2767, 263, 0, 0, 259, -2851, 20674, 17191, -2768, 263, 0, 0, 260, -2852, 20657, 17209, -2769, 263, 0, 0, 260, -2852, 20641, 17227, -2770, 263, 0, 0, 260, -2852, 20624, 17245, -2771, 263, 0, 0, 260, -2852, 20607, 17263, -2772, 263, 0, 0, 260, -2853, 20590, 17281, -2773, 263, 0, 0, 260, -2853, 20574, 17299, -2774, 263, 0, 0, 260, -2853, 20557, 17316, -2775, 263, 0, 0, 261, -2854, 20540, 17334, -2776, 264, 0, 0, 261, -2854, 20523, 17352, -2777, 264, 0, 0, 261, -2854, 20506, 17370, -2778, 264, 0, 0, 261, -2854, 20490, 17388, -2779, 264, 0, 0, 261, -2855, 20473, 17406, -2780, 264, 0, 0, 261, -2855, 20456, 17424, -2782, 264, 0, 0, 261, -2855, 20439, 17442, -2783, 264, 0, 0, 261, -2855, 20422, 17459, -2783, 264, 0, 0, 262, -2855, 20405, 17477, -2784, 264, 0, 0, 262, -2856, 20388, 17495, -2785, 264, 0, 0, 262, -2856, 20372, 17513, -2786, 264, 0, 0, 262, -2856, 20355, 17531, -2787, 264, 0, 0, 262, -2856, 20338, 17549, -2788, 264, 0, 0, 262, -2856, 20321, 17567, -2789, 264, 0, 0, 262, -2856, 20304, 17584, -2790, 265, 0, 0, 262, -2857, 20287, 17602, -2791, 265, 0, 0, 262, -2857, 20270, 17620, -2792, 265, 0, 0, 263, -2857, 20253, 17638, -2793, 265, 0, 0, 263, -2857, 20236, 17656, -2794, 265, 0, 0, 263, -2857, 20219, 17673, -2795, 265, 0, 0, 263, -2857, 20202, 17691, -2796, 265, 0, 0, 263, -2857, 20185, 17709, -2797, 265, 0, 0, 263, -2857, 20168, 17727, -2798, 265, 0, 0, 263, -2857, 20151, 17745, -2799, 265, 0, 0, 263, -2858, 20134, 17763, -2799, 265, 0, 0, 263, -2858, 20117, 17780, -2800, 265, 0, 0, 263, -2858, 20100, 17798, -2801, 265, 0, 0, 264, -2858, 20083, 17816, -2802, 265, 0, 0, 264, -2858, 20066, 17834, -2803, 265, 0, 0, 264, -2858, 20049, 17851, -2804, 265, 0, 0, 264, -2858, 20032, 17869, -2805, 266, 0, 0, 264, -2858, 20015, 17887, -2806, 266, 0, 0, 264, -2858, 19998, 17905, -2806, 266, 0, 0, 264, -2858, 19981, 17923, -2807, 266, 0, 0, 264, -2858, 19964, 17940, -2808, 266, 0, 0, 264, -2858, 19947, 17958, -2809, 266, 0, 0, 264, -2858, 19930, 17976, -2810, 266, 0, 0, 264, -2858, 19913, 17994, -2810, 266, 0, 0, 264, -2858, 19896, 18011, -2811, 266, 0, 0, 265, -2858, 19878, 18029, -2812, 266, 0, 0, 265, -2858, 19861, 18047, -2813, 266, 0, 0, 265, -2858, 19844, 18064, -2814, 266, 0, 0, 265, -2858, 19827, 18082, -2814, 266, 0, 0, 265, -2857, 19810, 18100, -2815, 266, 0, 0, 265, -2857, 19793, 18118, -2816, 266, 0, 0, 265, -2857, 19776, 18135, -2817, 266, 0, 0, 265, -2857, 19758, 18153, -2817, 266, 0, 0, 265, -2857, 19741, 18171, -2818, 266, 0, 0, 265, -2857, 19724, 18188, -2819, 266, 0, 0, 265, -2857, 19707, 18206, -2820, 266, 0, 0, 265, -2857, 19690, 18224, -2820, 266, 0, 0, 265, -2857, 19673, 18241, -2821, 266, 0, 0, 265, -2856, 19655, 18259, -2822, 266, 0, 0, 265, -2856, 19638, 18277, -2823, 266, 0, 0, 266, -2856, 19621, 18294, -2823, 267, 0, 0, 266, -2856, 19604, 18312, -2824, 267, 0, 0, 266, -2856, 19586, 18330, -2825, 267, 0, 0, 266, -2856, 19569, 18347, -2825, 267, 0, 0, 266, -2855, 19552, 18365, -2826, 267, 0, 0, 266, -2855, 19535, 18383, -2827, 267, 0, 0, 266, -2855, 19518, 18400, -2827, 267, 0, 0, 266, -2855, 19500, 18418, -2828, 267, 0, 0, 266, -2855, 19483, 18436, -2829, 267, 0, 0, 266, -2854, 19466, 18453, -2829, 267, 0, 0, 266, -2854, 19448, 18471, -2830, 267, 0, 0, 266, -2854, 19431, 18488, -2830, 267, 0, 0, 266, -2854, 19414, 18506, -2831, 267, 0, 0, 266, -2853, 19397, 18524, -2832, 267, 0, 0, 266, -2853, 19379, 18541, -2832, 267, 0, 0, 266, -2853, 19362, 18559, -2833, 267, 0, 0, 266, -2853, 19345, 18576, -2833, 267, 0, 0, 266, -2852, 19327, 18594, -2834, 267, 0, 0, 266, -2852, 19310, 18612, -2835, 267, 0, 0, 266, -2852, 19293, 18629, -2835, 267, 0, 0, 266, -2851, 19275, 18647, -2836, 267, 0, 0, 266, -2851, 19258, 18664, -2836, 267, 0, 0, 266, -2851, 19241, 18682, -2837, 267, 0, 0, 267, -2851, 19223, 18699, -2837, 267, 0, 0, 267, -2850, 19206, 18717, -2838, 267, 0, 0, 267, -2850, 19188, 18734, -2839, 267, 0, 0, 267, -2850, 19171, 18752, -2839, 267, 0, 0, 267, -2849, 19154, 18770, -2840, 267, 0, 0, 267, -2849, 19136, 18787, -2840, 267, 0, 0, 267, -2848, 19119, 18805, -2841, 267, 0, 0, 267, -2848, 19101, 18822, -2841, 267, 0, 0, 267, -2848, 19084, 18840, -2842, 267, 0, 0, 267, -2847, 19067, 18857, -2842, 267, 0, 0, 267, -2847, 19049, 18875, -2843, 267, 0, 0, 267, -2846, 19032, 18892, -2843, 267, 0, 0, 267, -2846, 19014, 18910, -2843, 267, 0, 0, 267, -2846, 18997, 18927, -2844, 267, 0, 0, 267, -2845, 18979, 18944, -2844, 267, 0, 0, 267, -2845, 18962, 18962, -2845, 267, 0, 0, 267, -2844, 18944, 18979, -2845, 267, 0, 0, 267, -2844, 18927, 18997, -2846, 267, 0, 0, 267, -2843, 18910, 19014, -2846, 267, 0, 0, 267, -2843, 18892, 19032, -2846, 267, 0, 0, 267, -2843, 18875, 19049, -2847, 267, 0, 0, 267, -2842, 18857, 19067, -2847, 267, 0, 0, 267, -2842, 18840, 19084, -2848, 267, 0, 0, 267, -2841, 18822, 19101, -2848, 267, 0, 0, 267, -2841, 18805, 19119, -2848, 267, 0, 0, 267, -2840, 18787, 19136, -2849, 267, 0, 0, 267, -2840, 18770, 19154, -2849, 267, 0, 0, 267, -2839, 18752, 19171, -2850, 267, 0, 0, 267, -2839, 18734, 19188, -2850, 267, 0, 0, 267, -2838, 18717, 19206, -2850, 267, 0, 0, 267, -2837, 18699, 19223, -2851, 267, 0, 0, 267, -2837, 18682, 19241, -2851, 266, 0, 0, 267, -2836, 18664, 19258, -2851, 266, 0, 0, 267, -2836, 18647, 19275, -2851, 266, 0, 0, 267, -2835, 18629, 19293, -2852, 266, 0, 0, 267, -2835, 18612, 19310, -2852, 266, 0, 0, 267, -2834, 18594, 19327, -2852, 266, 0, 0, 267, -2833, 18576, 19345, -2853, 266, 0, 0, 267, -2833, 18559, 19362, -2853, 266, 0, 0, 267, -2832, 18541, 19379, -2853, 266, 0, 0, 267, -2832, 18524, 19397, -2853, 266, 0, 0, 267, -2831, 18506, 19414, -2854, 266, 0, 0, 267, -2830, 18488, 19431, -2854, 266, 0, 0, 267, -2830, 18471, 19448, -2854, 266, 0, 0, 267, -2829, 18453, 19466, -2854, 266, 0, 0, 267, -2829, 18436, 19483, -2855, 266, 0, 0, 267, -2828, 18418, 19500, -2855, 266, 0, 0, 267, -2827, 18400, 19518, -2855, 266, 0, 0, 267, -2827, 18383, 19535, -2855, 266, 0, 0, 267, -2826, 18365, 19552, -2855, 266, 0, 0, 267, -2825, 18347, 19569, -2856, 266, 0, 0, 267, -2825, 18330, 19586, -2856, 266, 0, 0, 267, -2824, 18312, 19604, -2856, 266, 0, 0, 267, -2823, 18294, 19621, -2856, 266, 0, 0, 266, -2823, 18277, 19638, -2856, 265, 0, 0, 266, -2822, 18259, 19655, -2856, 265, 0, 0, 266, -2821, 18241, 19673, -2857, 265, 0, 0, 266, -2820, 18224, 19690, -2857, 265, 0, 0, 266, -2820, 18206, 19707, -2857, 265, 0, 0, 266, -2819, 18188, 19724, -2857, 265, 0, 0, 266, -2818, 18171, 19741, -2857, 265, 0, 0, 266, -2817, 18153, 19758, -2857, 265, 0, 0, 266, -2817, 18135, 19776, -2857, 265, 0, 0, 266, -2816, 18118, 19793, -2857, 265, 0, 0, 266, -2815, 18100, 19810, -2857, 265, 0, 0, 266, -2814, 18082, 19827, -2858, 265, 0, 0, 266, -2814, 18064, 19844, -2858, 265, 0, 0, 266, -2813, 18047, 19861, -2858, 265, 0, 0, 266, -2812, 18029, 19878, -2858, 265, 0, 0, 266, -2811, 18011, 19896, -2858, 264, 0, 0, 266, -2810, 17994, 19913, -2858, 264, 0, 0, 266, -2810, 17976, 19930, -2858, 264, 0, 0, 266, -2809, 17958, 19947, -2858, 264, 0, 0, 266, -2808, 17940, 19964, -2858, 264, 0, 0, 266, -2807, 17923, 19981, -2858, 264, 0, 0, 266, -2806, 17905, 19998, -2858, 264, 0, 0, 266, -2806, 17887, 20015, -2858, 264, 0, 0, 266, -2805, 17869, 20032, -2858, 264, 0, 0, 265, -2804, 17851, 20049, -2858, 264, 0, 0, 265, -2803, 17834, 20066, -2858, 264, 0, 0, 265, -2802, 17816, 20083, -2858, 264, 0, 0, 265, -2801, 17798, 20100, -2858, 263, 0, 0, 265, -2800, 17780, 20117, -2858, 263, 0, 0, 265, -2799, 17763, 20134, -2858, 263, 0, 0, 265, -2799, 17745, 20151, -2857, 263, 0, 0, 265, -2798, 17727, 20168, -2857, 263, 0, 0, 265, -2797, 17709, 20185, -2857, 263, 0, 0, 265, -2796, 17691, 20202, -2857, 263, 0, 0, 265, -2795, 17673, 20219, -2857, 263, 0, 0, 265, -2794, 17656, 20236, -2857, 263, 0, 0, 265, -2793, 17638, 20253, -2857, 263, 0, 0, 265, -2792, 17620, 20270, -2857, 262, 0, 0, 265, -2791, 17602, 20287, -2857, 262, 0, 0, 265, -2790, 17584, 20304, -2856, 262, 0, 0, 264, -2789, 17567, 20321, -2856, 262, 0, 0, 264, -2788, 17549, 20338, -2856, 262, 0, 0, 264, -2787, 17531, 20355, -2856, 262, 0, 0, 264, -2786, 17513, 20372, -2856, 262, 0, 0, 264, -2785, 17495, 20388, -2856, 262, 0, 0, 264, -2784, 17477, 20405, -2855, 262, 0, 0, 264, -2783, 17459, 20422, -2855, 261, 0, 0, 264, -2783, 17442, 20439, -2855, 261, 0, 0, 264, -2782, 17424, 20456, -2855, 261, 0, 0, 264, -2780, 17406, 20473, -2855, 261, 0, 0, 264, -2779, 17388, 20490, -2854, 261, 0, 0, 264, -2778, 17370, 20506, -2854, 261, 0, 0, 264, -2777, 17352, 20523, -2854, 261, 0, 0, 264, -2776, 17334, 20540, -2854, 261, 0, 0, 263, -2775, 17316, 20557, -2853, 260, 0, 0, 263, -2774, 17299, 20574, -2853, 260, 0, 0, 263, -2773, 17281, 20590, -2853, 260, 0, 0, 263, -2772, 17263, 20607, -2852, 260, 0, 0, 263, -2771, 17245, 20624, -2852, 260, 0, 0, 263, -2770, 17227, 20641, -2852, 260, 0, 0, 263, -2769, 17209, 20657, -2852, 260, 0, 0, 263, -2768, 17191, 20674, -2851, 259, 0, 0, 263, -2767, 17173, 20691, -2851, 259, 0, 0, 263, -2766, 17155, 20708, -2851, 259, 0, 0, 263, -2765, 17137, 20724, -2850, 259, 0, 0, 262, -2764, 17119, 20741, -2850, 259, 0, 0, 262, -2763, 17101, 20758, -2849, 259, 0, 0, 262, -2761, 17084, 20775, -2849, 259, 0, 0, 262, -2760, 17066, 20791, -2849, 258, 0, 0, 262, -2759, 17048, 20808, -2848, 258, 0, 0, 262, -2758, 17030, 20825, -2848, 258, 0, 0, 262, -2757, 17012, 20841, -2847, 258, 0, 0, 262, -2756, 16994, 20858, -2847, 258, 0, 0, 262, -2755, 16976, 20875, -2847, 258, 0, 0, 262, -2753, 16958, 20891, -2846, 258, 0, 0, 262, -2752, 16940, 20908, -2846, 257, 0, 0, 261, -2751, 16922, 20924, -2845, 257, 0, 0, 261, -2750, 16904, 20941, -2845, 257, 0, 0, 261, -2749, 16886, 20958, -2844, 257, 0, 0, 261, -2748, 16868, 20974, -2844, 257, 0, 0, 261, -2746, 16850, 20991, -2843, 257, 0, 0, 261, -2745, 16832, 21007, -2843, 256, 0, 0, 261, -2744, 16814, 21024, -2842, 256, 0, 0, 261, -2743, 16796, 21040, -2842, 256, 0, 0, 261, -2742, 16778, 21057, -2841, 256, 0, 0, 260, -2740, 16760, 21074, -2841, 256, 0, 0, 260, -2739, 16742, 21090, -2840, 256, 0, 0, 260, -2738, 16724, 21107, -2840, 255, 0, 0, 260, -2737, 16706, 21123, -2839, 255, 0, -1, 260, -2736, 16688, 21140, -2839, 255, 0, -1, 260, -2734, 16670, 21156, -2838, 255, 0, -1, 260, -2733, 16652, 21173, -2838, 255, 0, -1, 260, -2732, 16634, 21189, -2837, 255, 0, -1, 260, -2731, 16616, 21205, -2836, 254, 0, -1, 259, -2729, 16598, 21222, -2836, 254, 0, -1, 259, -2728, 16580, 21238, -2835, 254, 0, -1, 259, -2727, 16562, 21255, -2835, 254, 0, -1, 259, -2726, 16544, 21271, -2834, 254, 0, -1, 259, -2724, 16526, 21288, -2833, 253, 0, -1, 259, -2723, 16508, 21304, -2833, 253, 0, -1, 259, -2722, 16490, 21320, -2832, 253, 0, -1, 259, -2720, 16472, 21337, -2831, 253, 0, -1, 259, -2719, 16454, 21353, -2831, 253, 0, -1, 258, -2718, 16436, 21370, -2830, 252, 0, -1, 258, -2716, 16418, 21386, -2829, 252, 0, -1, 258, -2715, 16400, 21402, -2829, 252, 0, -1, 258, -2714, 16382, 21419, -2828, 252, 0, -1, 258, -2712, 16364, 21435, -2827, 252, 0, -1, 258, -2711, 16346, 21451, -2826, 251, 0, -1, 258, -2710, 16328, 21468, -2826, 251, 0, -1, 257, -2708, 16310, 21484, -2825, 251, 0, -1, 257, -2707, 16292, 21500, -2824, 251, 0, -1, 257, -2706, 16274, 21516, -2823, 251, 0, -1, 257, -2704, 16256, 21533, -2823, 250, 0, -1, 257, -2703, 16238, 21549, -2822, 250, 0, -1, 257, -2702, 16220, 21565, -2821, 250, 0, -1, 257, -2700, 16202, 21581, -2820, 250, 0, -1, 257, -2699, 16184, 21598, -2819, 249, 0, -1, 256, -2698, 16166, 21614, -2819, 249, 0, -1, 256, -2696, 16148, 21630, -2818, 249, 0, -1, 256, -2695, 16130, 21646, -2817, 249, 0, -1, 256, -2693, 16111, 21662, -2816, 249, 0, -1, 256, -2692, 16093, 21679, -2815, 248, 0, -1, 256, -2691, 16075, 21695, -2814, 248, 0, -1, 256, -2689, 16057, 21711, -2813, 248, 0, -1, 255, -2688, 16039, 21727, -2813, 248, 0, -1, 255, -2686, 16021, 21743, -2812, 247, 0, -1, 255, -2685, 16003, 21759, -2811, 247, 0, -1, 255, -2683, 15985, 21776, -2810, 247, 0, -1, 255, -2682, 15967, 21792, -2809, 247, 0, -1, 255, -2680, 15949, 21808, -2808, 246, 0, -1, 255, -2679, 15931, 21824, -2807, 246, 0, -1, 254, -2678, 15913, 21840, -2806, 246, 0, -1, 254, -2676, 15895, 21856, -2805, 246, 0, -1, 254, -2675, 15877, 21872, -2804, 245, 0, -1, 254, -2673, 15858, 21888, -2803, 245, 0, -1, 254, -2672, 15840, 21904, -2802, 245, 0, -1, 254, -2670, 15822, 21920, -2801, 245, 0, -1, 254, -2669, 15804, 21936, -2800, 244, 0, -1, 253, -2667, 15786, 21952, -2799, 244, 0, -1, 253, -2666, 15768, 21968, -2798, 244, 0, -1, 253, -2664, 15750, 21984, -2797, 244, 0, -1, 253, -2663, 15732, 22000, -2796, 243, 0, -1, 253, -2661, 15714, 22016, -2795, 243, 0, -1, 253, -2660, 15696, 22032, -2794, 243, 0, -1, 253, -2658, 15678, 22048, -2793, 243, 0, -1, 252, -2657, 15660, 22064, -2792, 242, 0, -1, 252, -2655, 15641, 22080, -2791, 242, 0, -1, 252, -2654, 15623, 22096, -2790, 242, 0, -1, 252, -2652, 15605, 22112, -2789, 242, 0, -1, 252, -2651, 15587, 22128, -2788, 241, 0, -1, 252, -2649, 15569, 22143, -2786, 241, 0, -1, 251, -2647, 15551, 22159, -2785, 241, 0, -1, 251, -2646, 15533, 22175, -2784, 241, 0, -1, 251, -2644, 15515, 22191, -2783, 240, 0, -1, 251, -2643, 15497, 22207, -2782, 240, 0, -1, 251, -2641, 15479, 22223, -2781, 240, 0, -1, 251, -2640, 15460, 22238, -2780, 239, 0, -1, 250, -2638, 15442, 22254, -2778, 239, 0, -1, 250, -2636, 15424, 22270, -2777, 239, 0, -1, 250, -2635, 15406, 22286, -2776, 239, 0, -1, 250, -2633, 15388, 22302, -2775, 238, 0, -1, 250, -2632, 15370, 22317, -2774, 238, 0, -1, 250, -2630, 15352, 22333, -2772, 238, 0, -1, 249, -2628, 15334, 22349, -2771, 237, 0, -1, 249, -2627, 15316, 22365, -2770, 237, 0, -1, 249, -2625, 15298, 22380, -2769, 237, 0, -1, 249, -2624, 15279, 22396, -2767, 236, 0, -1, 249, -2622, 15261, 22412, -2766, 236, 0, -1, 249, -2620, 15243, 22427, -2765, 236, 0, -2, 248, -2619, 15225, 22443, -2763, 236, 0, -2, 248, -2617, 15207, 22459, -2762, 235, 0, -2, 248, -2615, 15189, 22474, -2761, 235, 0, -2, 248, -2614, 15171, 22490, -2759, 235, 0, -2, 248, -2612, 15153, 22506, -2758, 234, 0, -2, 248, -2611, 15135, 22521, -2757, 234, 0, -2, 247, -2609, 15116, 22537, -2755, 234, 0, -2, 247, -2607, 15098, 22552, -2754, 233, 0, -2, 247, -2606, 15080, 22568, -2753, 233, 0, -2, 247, -2604, 15062, 22583, -2751, 233, 0, -2, 247, -2602, 15044, 22599, -2750, 232, 0, -2, 247, -2600, 15026, 22615, -2748, 232, 0, -2, 246, -2599, 15008, 22630, -2747, 232, 0, -2, 246, -2597, 14990, 22646, -2746, 231, 0, -2, 246, -2595, 14972, 22661, -2744, 231, 0, -2, 246, -2594, 14953, 22677, -2743, 231, 0, -2, 246, -2592, 14935, 22692, -2741, 230, 0, -2, 245, -2590, 14917, 22708, -2740, 230, 0, -2, 245, -2589, 14899, 22723, -2738, 230, 0, -2, 245, -2587, 14881, 22738, -2737, 229, 0, -2, 245, -2585, 14863, 22754, -2735, 229, 0, -2, 245, -2583, 14845, 22769, -2734, 229, 0, -2, 245, -2582, 14827, 22785, -2732, 228, 0, -2, 244, -2580, 14809, 22800, -2731, 228, 0, -2, 244, -2578, 14790, 22816, -2729, 228, 0, -2, 244, -2576, 14772, 22831, -2728, 227, 0, -2, 244, -2575, 14754, 22846, -2726, 227, 0, -2, 244, -2573, 14736, 22862, -2725, 227, 0, -2, 243, -2571, 14718, 22877, -2723, 226, 0, -2, 243, -2569, 14700, 22892, -2721, 226, 0, -2, 243, -2568, 14682, 22908, -2720, 226, 0, -2, 243, -2566, 14664, 22923, -2718, 225, 0, -2, 243, -2564, 14646, 22938, -2717, 225, 0, -2, 243, -2562, 14627, 22954, -2715, 225, 0, -2, 242, -2561, 14609, 22969, -2713, 224, 0, -2, 242, -2559, 14591, 22984, -2712, 224, 0, -2, 242, -2557, 14573, 22999, -2710, 223, 0, -2, 242, -2555, 14555, 23015, -2708, 223, 0, -2, 242, -2553, 14537, 23030, -2707, 223, 0, -2, 241, -2552, 14519, 23045, -2705, 222, 0, -2, 241, -2550, 14501, 23060, -2703, 222, 0, -2, 241, -2548, 14483, 23075, -2702, 222, 0, -2, 241, -2546, 14464, 23090, -2700, 221, 0, -2, 241, -2544, 14446, 23106, -2698, 221, 0, -2, 240, -2543, 14428, 23121, -2697, 220, 0, -2, 240, -2541, 14410, 23136, -2695, 220, 0, -2, 240, -2539, 14392, 23151, -2693, 220, 0, -2, 240, -2537, 14374, 23166, -2691, 219, 0, -2, 240, -2535, 14356, 23181, -2689, 219, 0, -2, 239, -2533, 14338, 23196, -2688, 219, 0, -2, 239, -2532, 14320, 23211, -2686, 218, 0, -2, 239, -2530, 14302, 23226, -2684, 218, 0, -3, 239, -2528, 14283, 23242, -2682, 217, 0, -3, 239, -2526, 14265, 23257, -2680, 217, 0, -3, 238, -2524, 14247, 23272, -2679, 217, 0, -3, 238, -2522, 14229, 23287, -2677, 216, 0, -3, 238, -2521, 14211, 23302, -2675, 216, 0, -3, 238, -2519, 14193, 23317, -2673, 215, 0, -3, 238, -2517, 14175, 23332, -2671, 215, 0, -3, 237, -2515, 14157, 23346, -2669, 215, 0, -3, 237, -2513, 14139, 23361, -2667, 214, 0, -3, 237, -2511, 14121, 23376, -2665, 214, 0, -3, 237, -2509, 14103, 23391, -2664, 213, 0, -3, 237, -2507, 14084, 23406, -2662, 213, 0, -3, 236, -2505, 14066, 23421, -2660, 212, 0, -3, 236, -2504, 14048, 23436, -2658, 212, 0, -3, 236, -2502, 14030, 23451, -2656, 212, 0, -3, 236, -2500, 14012, 23466, -2654, 211, 0, -3, 236, -2498, 13994, 23481, -2652, 211, 0, -3, 235, -2496, 13976, 23495, -2650, 210, 0, -3, 235, -2494, 13958, 23510, -2648, 210, 0, -3, 235, -2492, 13940, 23525, -2646, 210, 0, -3, 235, -2490, 13922, 23540, -2644, 209, 0, -3, 234, -2488, 13904, 23555, -2642, 209, 0, -3, 234, -2486, 13886, 23569, -2640, 208, 0, -3, 234, -2484, 13868, 23584, -2638, 208, 0, -3, 234, -2482, 13849, 23599, -2636, 207, 0, -3, 234, -2480, 13831, 23614, -2634, 207, 0, -3, 233, -2479, 13813, 23628, -2631, 206, 0, -3, 233, -2477, 13795, 23643, -2629, 206, 0, -3, 233, -2475, 13777, 23658, -2627, 206, 0, -3, 233, -2473, 13759, 23672, -2625, 205, 0, -3, 233, -2471, 13741, 23687, -2623, 205, 0, -3, 232, -2469, 13723, 23702, -2621, 204, 0, -3, 232, -2467, 13705, 23716, -2619, 204, 0, -3, 232, -2465, 13687, 23731, -2617, 203, 0, -3, 232, -2463, 13669, 23745, -2614, 203, 0, -3, 231, -2461, 13651, 23760, -2612, 202, 0, -3, 231, -2459, 13633, 23775, -2610, 202, 0, -3, 231, -2457, 13615, 23789, -2608, 201, 0, -3, 231, -2455, 13597, 23804, -2606, 201, 0, -3, 231, -2453, 13579, 23818, -2603, 201, 0, -3, 230, -2451, 13561, 23833, -2601, 200, 0, -4, 230, -2449, 13543, 23847, -2599, 200, 0, -4, 230, -2447, 13525, 23862, -2597, 199, 0, -4, 230, -2445, 13506, 23876, -2594, 199, 0, -4, 230, -2443, 13488, 23891, -2592, 198, 0, -4, 229, -2441, 13470, 23905, -2590, 198, 0, -4, 229, -2439, 13452, 23920, -2588, 197, 0, -4, 229, -2437, 13434, 23934, -2585, 197, 0, -4, 229, -2435, 13416, 23948, -2583, 196, 0, -4, 228, -2433, 13398, 23963, -2581, 196, 0, -4, 228, -2431, 13380, 23977, -2578, 195, 0, -4, 228, -2429, 13362, 23992, -2576, 195, 0, -4, 228, -2427, 13344, 24006, -2574, 194, 0, -4, 228, -2425, 13326, 24020, -2571, 194, 0, -4, 227, -2423, 13308, 24035, -2569, 193, 0, -4, 227, -2421, 13290, 24049, -2566, 193, 0, -4, 227, -2419, 13272, 24063, -2564, 192, 0, -4, 227, -2417, 13254, 24078, -2562, 192, 0, -4, 226, -2414, 13236, 24092, -2559, 191, 0, -4, 226, -2412, 13218, 24106, -2557, 191, 0, -4, 226, -2410, 13200, 24120, -2554, 190, 0, -4, 226, -2408, 13182, 24135, -2552, 190, 0, -4, 225, -2406, 13164, 24149, -2549, 189, 0, -4, 225, -2404, 13146, 24163, -2547, 189, 0, -4, 225, -2402, 13128, 24177, -2544, 188, 0, -4, 225, -2400, 13110, 24191, -2542, 188, 0, -4, 225, -2398, 13092, 24206, -2539, 187, 0, -4, 224, -2396, 13074, 24220, -2537, 187, 0, -4, 224, -2394, 13056, 24234, -2534, 186, 0, -4, 224, -2392, 13038, 24248, -2532, 186, 0, -4, 224, -2390, 13020, 24262, -2529, 185, 0, -4, 223, -2388, 13002, 24276, -2527, 185, 0, -4, 223, -2385, 12984, 24290, -2524, 184, 0, -4, 223, -2383, 12966, 24304, -2521, 184, 0, -4, 223, -2381, 12949, 24318, -2519, 183, 0, -5, 222, -2379, 12931, 24332, -2516, 183, 0, -5, 222, -2377, 12913, 24346, -2514, 182, 0, -5, 222, -2375, 12895, 24360, -2511, 181, 0, -5, 222, -2373, 12877, 24374, -2508, 181, 0, -5, 222, -2371, 12859, 24388, -2506, 180, 0, -5, 221, -2369, 12841, 24402, -2503, 180, 0, -5, 221, -2366, 12823, 24416, -2500, 179, 0, -5, 221, -2364, 12805, 24430, -2498, 179, 0, -5, 221, -2362, 12787, 24444, -2495, 178, 0, -5, 220, -2360, 12769, 24458, -2492, 178, 0, -5, 220, -2358, 12751, 24472, -2490, 177, 0, -5, 220, -2356, 12733, 24486, -2487, 176, 0, -5, 220, -2354, 12715, 24500, -2484, 176, 0, -5, 219, -2351, 12697, 24514, -2481, 175, 0, -5, 219, -2349, 12679, 24527, -2479, 175, 0, -5, 219, -2347, 12661, 24541, -2476, 174, 0, -5, 219, -2345, 12644, 24555, -2473, 174, 0, -5, 218, -2343, 12626, 24569, -2470, 173, 0, -5, 218, -2341, 12608, 24583, -2467, 173, 0, -5, 218, -2339, 12590, 24596, -2465, 172, 0, -5, 218, -2336, 12572, 24610, -2462, 171, 0, -5, 217, -2334, 12554, 24624, -2459, 171, 0, -5, 217, -2332, 12536, 24637, -2456, 170, 0, -5, 217, -2330, 12518, 24651, -2453, 170, 0, -5, 217, -2328, 12500, 24665, -2450, 169, 0, -5, 217, -2326, 12482, 24679, -2447, 168, 0, -5, 216, -2323, 12465, 24692, -2444, 168, 0, -5, 216, -2321, 12447, 24706, -2442, 167, 0, -5, 216, -2319, 12429, 24719, -2439, 167, 0, -5, 216, -2317, 12411, 24733, -2436, 166, 0, -5, 215, -2315, 12393, 24747, -2433, 166, 0, -6, 215, -2312, 12375, 24760, -2430, 165, 0, -6, 215, -2310, 12357, 24774, -2427, 164, 0, -6, 215, -2308, 12340, 24787, -2424, 164, 0, -6, 214, -2306, 12322, 24801, -2421, 163, 0, -6, 214, -2304, 12304, 24814, -2418, 163, 0, -6, 214, -2301, 12286, 24828, -2415, 162, 0, -6, 214, -2299, 12268, 24841, -2412, 161, 0, -6, 213, -2297, 12250, 24855, -2409, 161, 0, -6, 213, -2295, 12232, 24868, -2406, 160, 0, -6, 213, -2293, 12215, 24882, -2403, 159, 0, -6, 213, -2290, 12197, 24895, -2400, 159, 0, -6, 212, -2288, 12179, 24909, -2396, 158, 0, -6, 212, -2286, 12161, 24922, -2393, 158, 0, -6, 212, -2284, 12143, 24935, -2390, 157, 0, -6, 212, -2281, 12126, 24949, -2387, 156, 0, -6, 211, -2279, 12108, 24962, -2384, 156, 0, -6, 211, -2277, 12090, 24975, -2381, 155, 0, -6, 211, -2275, 12072, 24989, -2378, 154, 0, -6, 211, -2272, 12054, 25002, -2375, 154, 0, -6, 210, -2270, 12036, 25015, -2371, 153, 1, -6, 210, -2268, 12019, 25029, -2368, 153, 1, -6, 210, -2266, 12001, 25042, -2365, 152, 1, -6, 210, -2264, 11983, 25055, -2362, 151, 1, -6, 209, -2261, 11965, 25068, -2359, 151, 1, -6, 209, -2259, 11948, 25081, -2355, 150, 1, -6, 209, -2257, 11930, 25095, -2352, 149, 1, -6, 209, -2254, 11912, 25108, -2349, 149, 1, -7, 208, -2252, 11894, 25121, -2346, 148, 1, -7, 208, -2250, 11877, 25134, -2342, 147, 1, -7, 208, -2248, 11859, 25147, -2339, 147, 1, -7, 208, -2245, 11841, 25160, -2336, 146, 1, -7, 207, -2243, 11823, 25173, -2332, 145, 1, -7, 207, -2241, 11806, 25187, -2329, 145, 1, -7, 207, -2239, 11788, 25200, -2326, 144, 1, -7, 207, -2236, 11770, 25213, -2322, 143, 1, -7, 206, -2234, 11752, 25226, -2319, 143, 1, -7, 206, -2232, 11735, 25239, -2316, 142, 1, -7, 206, -2229, 11717, 25252, -2312, 141, 1, -7, 206, -2227, 11699, 25265, -2309, 141, 1, -7, 205, -2225, 11681, 25278, -2305, 140, 1, -7, 205, -2223, 11664, 25291, -2302, 139, 1, -7, 205, -2220, 11646, 25304, -2299, 139, 1, -7, 205, -2218, 11628, 25317, -2295, 138, 1, -7, 204, -2216, 11611, 25329, -2292, 137, 1, -7, 204, -2213, 11593, 25342, -2288, 137, 1, -7, 204, -2211, 11575, 25355, -2285, 136, 1, -7, 204, -2209, 11558, 25368, -2281, 135, 1, -7, 203, -2207, 11540, 25381, -2278, 135, 1, -7, 203, -2204, 11522, 25394, -2274, 134, 1, -7, 203, -2202, 11505, 25407, -2271, 133, 1, -7, 203, -2200, 11487, 25419, -2267, 132, 1, -7, 202, -2197, 11469, 25432, -2264, 132, 1, -8, 202, -2195, 11452, 25445, -2260, 131, 1, -8, 202, -2193, 11434, 25458, -2256, 130, 1, -8, 202, -2190, 11416, 25470, -2253, 130, 1, -8, 201, -2188, 11399, 25483, -2249, 129, 1, -8, 201, -2186, 11381, 25496, -2246, 128, 1, -8, 201, -2183, 11363, 25508, -2242, 127, 1, -8, 200, -2181, 11346, 25521, -2238, 127, 1, -8, 200, -2179, 11328, 25534, -2235, 126, 1, -8, 200, -2176, 11311, 25546, -2231, 125, 1, -8, 200, -2174, 11293, 25559, -2227, 125, 1, -8, 199, -2172, 11275, 25572, -2224, 124, 1, -8, 199, -2169, 11258, 25584, -2220, 123, 1, -8, 199, -2167, 11240, 25597, -2216, 122, 1, -8, 199, -2165, 11222, 25609, -2213, 122, 1, -8, 198, -2162, 11205, 25622, -2209, 121, 1, -8, 198, -2160, 11187, 25634, -2205, 120, 1, -8, 198, -2158, 11170, 25647, -2201, 119, 1, -8, 198, -2155, 11152, 25659, -2198, 119, 1, -8, 197, -2153, 11135, 25672, -2194, 118, 1, -8, 197, -2151, 11117, 25684, -2190, 117, 1, -8, 197, -2148, 11099, 25697, -2186, 116, 1, -8, 197, -2146, 11082, 25709, -2182, 116, 1, -8, 196, -2143, 11064, 25722, -2179, 115, 1, -8, 196, -2141, 11047, 25734, -2175, 114, 2, -9, 196, -2139, 11029, 25746, -2171, 113, 2, -9, 196, -2136, 11012, 25759, -2167, 113, 2, -9, 195, -2134, 10994, 25771, -2163, 112, 2, -9, 195, -2132, 10977, 25783, -2159, 111, 2, -9, 195, -2129, 10959, 25796, -2155, 110, 2, -9, 194, -2127, 10942, 25808, -2152, 110, 2, -9, 194, -2125, 10924, 25820, -2148, 109, 2, -9, 194, -2122, 10907, 25832, -2144, 108, 2, -9, 194, -2120, 10889, 25845, -2140, 107, 2, -9, 193, -2117, 10872, 25857, -2136, 106, 2, -9, 193, -2115, 10854, 25869, -2132, 106, 2, -9, 193, -2113, 10837, 25881, -2128, 105, 2, -9, 193, -2110, 10819, 25893, -2124, 104, 2, -9, 192, -2108, 10802, 25905, -2120, 103, 2, -9, 192, -2105, 10784, 25918, -2116, 103, 2, -9, 192, -2103, 10767, 25930, -2112, 102, 2, -9, 192, -2101, 10749, 25942, -2108, 101, 2, -9, 191, -2098, 10732, 25954, -2104, 100, 2, -9, 191, -2096, 10715, 25966, -2100, 99, 2, -9, 191, -2093, 10697, 25978, -2096, 99, 2, -9, 191, -2091, 10680, 25990, -2091, 98, 2, -9, 190, -2089, 10662, 26002, -2087, 97, 2, -10, 190, -2086, 10645, 26014, -2083, 96, 2, -10, 190, -2084, 10627, 26026, -2079, 95, 2, -10, 189, -2081, 10610, 26038, -2075, 95, 2, -10, 189, -2079, 10593, 26050, -2071, 94, 2, -10, 189, -2077, 10575, 26062, -2067, 93, 2, -10, 189, -2074, 10558, 26074, -2062, 92, 2, -10, 188, -2072, 10540, 26086, -2058, 91, 2, -10, 188, -2069, 10523, 26097, -2054, 90, 2, -10, 188, -2067, 10506, 26109, -2050, 90, 2, -10, 188, -2065, 10488, 26121, -2046, 89, 2, -10, 187, -2062, 10471, 26133, -2041, 88, 2, -10, 187, -2060, 10454, 26145, -2037, 87, 2, -10, 187, -2057, 10436, 26157, -2033, 86, 2, -10, 187, -2055, 10419, 26168, -2029, 85, 2, -10, 186, -2052, 10402, 26180, -2024, 85, 2, -10, 186, -2050, 10384, 26192, -2020, 84, 2, -10, 186, -2048, 10367, 26204, -2016, 83, 2, -10, 185, -2045, 10350, 26215, -2011, 82, 3, -10, 185, -2043, 10332, 26227, -2007, 81, 3, -10, 185, -2040, 10315, 26239, -2003, 80, 3, -10, 185, -2038, 10298, 26250, -1998, 80, 3, -11, 184, -2035, 10280, 26262, -1994, 79, 3, -11, 184, -2033, 10263, 26273, -1990, 78, 3, -11, 184, -2031, 10246, 26285, -1985, 77, 3, -11, 184, -2028, 10228, 26297, -1981, 76, 3, -11, 183, -2026, 10211, 26308, -1976, 75, 3, -11, 183, -2023, 10194, 26320, -1972, 74, 3, -11, 183, -2021, 10177, 26331, -1967, 74, 3, -11, 183, -2018, 10159, 26343, -1963, 73, 3, -11, 182, -2016, 10142, 26354, -1958, 72, 3, -11, 182, -2013, 10125, 26366, -1954, 71, 3, -11, 182, -2011, 10108, 26377, -1949, 70, 3, -11, 181, -2009, 10090, 26389, -1945, 69, 3, -11, 181, -2006, 10073, 26400, -1940, 68, 3, -11, 181, -2004, 10056, 26411, -1936, 67, 3, -11, 181, -2001, 10039, 26423, -1931, 67, 3, -11, 180, -1999, 10022, 26434, -1927, 66, 3, -11, 180, -1996, 10004, 26445, -1922, 65, 3, -11, 180, -1994, 9987, 26457, -1918, 64, 3, -11, 180, -1991, 9970, 26468, -1913, 63, 3, -11, 179, -1989, 9953, 26479, -1908, 62, 3, -12, 179, -1986, 9936, 26491, -1904, 61, 3, -12, 179, -1984, 9919, 26502, -1899, 60, 3, -12, 178, -1982, 9901, 26513, -1895, 59, 3, -12, 178, -1979, 9884, 26524, -1890, 58, 3, -12, 178, -1977, 9867, 26536, -1885, 58, 3, -12, 178, -1974, 9850, 26547, -1880, 57, 3, -12, 177, -1972, 9833, 26558, -1876, 56, 3, -12, 177, -1969, 9816, 26569, -1871, 55, 3, -12, 177, -1967, 9799, 26580, -1866, 54, 4, -12, 177, -1964, 9781, 26591, -1862, 53, 4, -12, 176, -1962, 9764, 26602, -1857, 52, 4, -12, 176, -1959, 9747, 26613, -1852, 51, 4, -12, 176, -1957, 9730, 26625, -1847, 50, 4, -12, 176, -1954, 9713, 26636, -1843, 49, 4, -12, 175, -1952, 9696, 26647, -1838, 48, 4, -12, 175, -1949, 9679, 26658, -1833, 47, 4, -12, 175, -1947, 9662, 26669, -1828, 46, 4, -12, 174, -1944, 9645, 26679, -1823, 46, 4, -13, 174, -1942, 9628, 26690, -1818, 45, 4, -13, 174, -1939, 9611, 26701, -1813, 44, 4, -13, 174, -1937, 9594, 26712, -1809, 43, 4, -13, 173, -1935, 9577, 26723, -1804, 42, 4, -13, 173, -1932, 9560, 26734, -1799, 41, 4, -13, 173, -1930, 9543, 26745, -1794, 40, 4, -13, 173, -1927, 9526, 26756, -1789, 39, 4, -13, 172, -1925, 9509, 26767, -1784, 38, 4, -13, 172, -1922, 9492, 26777, -1779, 37, 4, -13, 172, -1920, 9475, 26788, -1774, 36, 4, -13, 171, -1917, 9458, 26799, -1769, 35, 4, -13, 171, -1915, 9441, 26810, -1764, 34, 4, -13, 171, -1912, 9424, 26820, -1759, 33, 4, -13, 171, -1910, 9407, 26831, -1754, 32, 4, -13, 170, -1907, 9390, 26842, -1749, 31, 4, -13, 170, -1905, 9373, 26852, -1744, 30, 4, -13, 170, -1902, 9356, 26863, -1739, 29, 4, -13, 170, -1900, 9339, 26874, -1734, 28, 5, -13, 169, -1897, 9322, 26884, -1729, 27, 5, -14, 169, -1895, 9305, 26895, -1724, 26, 5, -14, 169, -1892, 9288, 26905, -1719, 25, 5, -14, 168, -1890, 9271, 26916, -1713, 24, 5, -14, 168, -1887, 9254, 26926, -1708, 23, 5, -14, 168, -1885, 9237, 26937, -1703, 22, 5, -14, 168, -1882, 9221, 26948, -1698, 21, 5, -14, 167, -1880, 9204, 26958, -1693, 20, 5, -14, 167, -1877, 9187, 26968, -1688, 19, 5, -14, 167, -1875, 9170, 26979, -1682, 18, 5, -14, 167, -1872, 9153, 26989, -1677, 17, 5, -14, 166, -1870, 9136, 27000, -1672, 16, 5, -14, 166, -1867, 9119, 27010, -1667, 15, 5, -14, 166, -1865, 9103, 27020, -1661, 14, 5, -14, 165, -1862, 9086, 27031, -1656, 13, 5, -14, 165, -1859, 9069, 27041, -1651, 12, 5, -14, 165, -1857, 9052, 27051, -1646, 11, 5, -14, 165, -1854, 9035, 27062, -1640, 10, 5, -15, 164, -1852, 9019, 27072, -1635, 9, 5, -15, 164, -1849, 9002, 27082, -1630, 8, 5, -15, 164, -1847, 8985, 27092, -1624, 7, 5, -15, 164, -1844, 8968, 27103, -1619, 6, 5, -15, 163, -1842, 8952, 27113, -1614, 5, 6, -15, 163, -1839, 8935, 27123, -1608, 4, 6, -15, 163, -1837, 8918, 27133, -1603, 3, 6, -15, 162, -1834, 8901, 27143, -1597, 2, 6, -15, 162, -1832, 8885, 27153, -1592, 1, 6, -15, 162, -1829, 8868, 27164, -1587, 0, 6, -15, 162, -1827, 8851, 27174, -1581, -1, 6, -15, 161, -1824, 8834, 27184, -1576, -2, 6, -15, 161, -1822, 8818, 27194, -1570, -3, 6, -15, 161, -1819, 8801, 27204, -1565, -4, 6, -15, 161, -1817, 8784, 27214, -1559, -5, 6, -15, 160, -1814, 8768, 27224, -1554, -6, 6, -15, 160, -1812, 8751, 27234, -1548, -8, 6, -16, 160, -1809, 8734, 27244, -1543, -9, 6, -16, 159, -1807, 8718, 27254, -1537, -10, 6, -16, 159, -1804, 8701, 27263, -1532, -11, 6, -16, 159, -1801, 8684, 27273, -1526, -12, 6, -16, 159, -1799, 8668, 27283, -1520, -13, 6, -16, 158, -1796, 8651, 27293, -1515, -14, 6, -16, 158, -1794, 8634, 27303, -1509, -15, 6, -16, 158, -1791, 8618, 27313, -1503, -16, 6, -16, 158, -1789, 8601, 27322, -1498, -17, 6, -16, 157, -1786, 8585, 27332, -1492, -18, 7, -16, 157, -1784, 8568, 27342, -1486, -19, 7, -16, 157, -1781, 8552, 27352, -1481, -20, 7, -16, 157, -1779, 8535, 27361, -1475, -22, 7, -16, 156, -1776, 8518, 27371, -1469, -23, 7, -16, 156, -1774, 8502, 27381, -1464, -24, 7, -16, 156, -1771, 8485, 27390, -1458, -25, 7, -16, 155, -1769, 8469, 27400, -1452, -26, 7, -17, 155, -1766, 8452, 27410, -1446, -27, 7, -17, 155, -1763, 8436, 27419, -1441, -28, 7, -17, 155, -1761, 8419, 27429, -1435, -29, 7, -17, 154, -1758, 8403, 27438, -1429, -30, 7, -17, 154, -1756, 8386, 27448, -1423, -31, 7, -17, 154, -1753, 8370, 27457, -1417, -33, 7, -17, 154, -1751, 8353, 27467, -1412, -34, 7, -17, 153, -1748, 8337, 27476, -1406, -35, 7, -17, 153, -1746, 8320, 27486, -1400, -36, 7, -17, 153, -1743, 8304, 27495, -1394, -37, 7, -17, 152, -1741, 8287, 27505, -1388, -38, 7, -17, 152, -1738, 8271, 27514, -1382, -39, 8, -17, 152, -1736, 8255, 27523, -1376, -40, 8, -17, 152, -1733, 8238, 27533, -1370, -42, 8, -17, 151, -1730, 8222, 27542, -1364, -43, 8, -17, 151, -1728, 8205, 27552, -1358, -44, 8, -18, 151, -1725, 8189, 27561, -1352, -45, 8, -18, 151, -1723, 8173, 27570, -1346, -46, 8, -18, 150, -1720, 8156, 27579, -1340, -47, 8, -18, 150, -1718, 8140, 27589, -1334, -49, 8, -18, 150, -1715, 8123, 27598, -1328, -50, 8, -18, 149, -1713, 8107, 27607, -1322, -51, 8, -18, 149, -1710, 8091, 27616, -1316, -52, 8, -18, 149, -1708, 8074, 27625, -1310, -53, 8, -18, 149, -1705, 8058, 27634, -1304, -54, 8, -18, 148, -1703, 8042, 27644, -1298, -55, 8, -18, 148, -1700, 8025, 27653, -1292, -57, 8, -18, 148, -1697, 8009, 27662, -1286, -58, 8, -18, 148, -1695, 7993, 27671, -1280, -59, 8, -18, 147, -1692, 7977, 27680, -1273, -60, 9, -18, 147, -1690, 7960, 27689, -1267, -61, 9, -18, 147, -1687, 7944, 27698, -1261, -63, 9, -19, 147, -1685, 7928, 27707, -1255, -64, 9, -19, 146, -1682, 7912, 27716, -1249, -65, 9, -19, 146, -1680, 7895, 27725, -1243, -66, 9, -19, 146, -1677, 7879, 27734, -1236, -67, 9, -19, 145, -1674, 7863, 27743, -1230, -68, 9, -19, 145, -1672, 7847, 27751, -1224, -70, 9, -19, 145, -1669, 7830, 27760, -1218, -71, 9, -19, 145, -1667, 7814, 27769, -1211, -72, 9, -19, 144, -1664, 7798, 27778, -1205, -73, 9, -19, 144, -1662, 7782, 27787, -1199, -74, 9, -19, 144, -1659, 7766, 27795, -1192, -76, 9, -19, 144, -1657, 7750, 27804, -1186, -77, 9, -19, 143, -1654, 7733, 27813, -1180, -78, 9, -19, 143, -1652, 7717, 27822, -1173, -79, 9, -19, 143, -1649, 7701, 27830, -1167, -81, 10, -20, 142, -1646, 7685, 27839, -1160, -82, 10, -20, 142, -1644, 7669, 27848, -1154, -83, 10, -20, 142, -1641, 7653, 27856, -1148, -84, 10, -20, 142, -1639, 7637, 27865, -1141, -85, 10, -20, 141, -1636, 7621, 27874, -1135, -87, 10, -20, 141, -1634, 7605, 27882, -1128, -88, 10, -20, 141, -1631, 7589, 27891, -1122, -89, 10, -20, 141, -1629, 7572, 27899, -1115, -90, 10, -20, 140, -1626, 7556, 27908, -1109, -92, 10, -20, 140, -1624, 7540, 27916, -1102, -93, 10, -20, 140, -1621, 7524, 27925, -1096, -94, 10, -20, 140, -1618, 7508, 27933, -1089, -95, 10, -20, 139, -1616, 7492, 27941, -1083, -97, 10, -20, 139, -1613, 7476, 27950, -1076, -98, 10, -20, 139, -1611, 7460, 27958, -1070, -99, 11, -21, 138, -1608, 7444, 27967, -1063, -100, 11, -21, 138, -1606, 7428, 27975, -1056, -102, 11, -21, 138, -1603, 7412, 27983, -1050, -103, 11, -21, 138, -1601, 7396, 27992, -1043, -104, 11, -21, 137, -1598, 7380, 28000, -1036, -105, 11, -21, 137, -1595, 7365, 28008, -1030, -107, 11, -21, 137, -1593, 7349, 28016, -1023, -108, 11, -21, 137, -1590, 7333, 28025, -1016, -109, 11, -21, 136, -1588, 7317, 28033, -1010, -110, 11, -21, 136, -1585, 7301, 28041, -1003, -112, 11, -21, 136, -1583, 7285, 28049, -996, -113, 11, -21, 136, -1580, 7269, 28057, -989, -114, 11, -21, 135, -1578, 7253, 28065, -983, -116, 11, -21, 135, -1575, 7237, 28073, -976, -117, 11, -21, 135, -1573, 7221, 28082, -969, -118, 12, -22, 135, -1570, 7206, 28090, -962, -119, 12, -22, 134, -1567, 7190, 28098, -956, -121, 12, -22, 134, -1565, 7174, 28106, -949, -122, 12, -22, 134, -1562, 7158, 28114, -942, -123, 12, -22, 133, -1560, 7142, 28122, -935, -125, 12, -22, 133, -1557, 7127, 28130, -928, -126, 12, -22, 133, -1555, 7111, 28137, -921, -127, 12, -22, 133, -1552, 7095, 28145, -914, -129, 12, -22, 132, -1550, 7079, 28153, -907, -130, 12, -22, 132, -1547, 7063, 28161, -901, -131, 12, -22, 132, -1545, 7048, 28169, -894, -133, 12, -22, 132, -1542, 7032, 28177, -887, -134, 12, -22, 131, -1539, 7016, 28185, -880, -135, 12, -22, 131, -1537, 7001, 28192, -873, -136, 13, -23, 131, -1534, 6985, 28200, -866, -138, 13, -23, 131, -1532, 6969, 28208, -859, -139, 13, -23, 130, -1529, 6953, 28216, -852, -140, 13, -23, 130, -1527, 6938, 28223, -845, -142, 13, -23, 130, -1524, 6922, 28231, -838, -143, 13, -23, 130, -1522, 6906, 28239, -831, -144, 13, -23, 129, -1519, 6891, 28246, -824, -146, 13, -23, 129, -1517, 6875, 28254, -816, -147, 13, -23, 129, -1514, 6859, 28261, -809, -149, 13, -23, 128, -1511, 6844, 28269, -802, -150, 13, -23, 128, -1509, 6828, 28277, -795, -151, 13, -23, 128, -1506, 6813, 28284, -788, -153, 13, -23, 128, -1504, 6797, 28292, -781, -154, 14, -23, 127, -1501, 6781, 28299, -774, -155, 14, -24, 127, -1499, 6766, 28307, -766, -157, 14, -24, 127, -1496, 6750, 28314, -759, -158, 14, -24, 127, -1494, 6735, 28321, -752, -159, 14, -24, 126, -1491, 6719, 28329, -745, -161, 14, -24, 126, -1489, 6704, 28336, -738, -162, 14, -24, 126, -1486, 6688, 28344, -730, -163, 14, -24, 126, -1484, 6673, 28351, -723, -165, 14, -24, 125, -1481, 6657, 28358, -716, -166, 14, -24, 125, -1478, 6642, 28366, -708, -168, 14, -24, 125, -1476, 6626, 28373, -701, -169, 14, -24, 125, -1473, 6611, 28380, -694, -170, 14, -24, 124, -1471, 6595, 28387, -687, -172, 15, -24, 124, -1468, 6580, 28395, -679, -173, 15, -24, 124, -1466, 6564, 28402, -672, -175, 15, -25, 124, -1463, 6549, 28409, -664, -176, 15, -25, 123, -1461, 6533, 28416, -657, -177, 15, -25, 123, -1458, 6518, 28423, -650, -179, 15, -25, 123, -1456, 6503, 28430, -642, -180, 15, -25, 122, -1453, 6487, 28437, -635, -182, 15, -25, 122, -1451, 6472, 28444, -627, -183, 15, -25, 122, -1448, 6457, 28451, -620, -184, 15, -25, 122, -1445, 6441, 28458, -612, -186, 15, -25, 121, -1443, 6426, 28465, -605, -187, 15, -25, 121, -1440, 6410, 28472, -597, -189, 16, -25, 121, -1438, 6395, 28479, -590, -190, 16, -25, 121, -1435, 6380, 28486, -582, -191, 16, -25, 120, -1433, 6365, 28493, -575, -193, 16, -25, 120, -1430, 6349, 28500, -567, -194, 16, -26, 120, -1428, 6334, 28507, -560, -196, 16, -26, 120, -1425, 6319, 28514, -552, -197, 16, -26, 119, -1423, 6303, 28521, -545, -199, 16, -26, 119, -1420, 6288, 28527, -537, -200, 16, -26, 119, -1418, 6273, 28534, -529, -202, 16, -26, 119, -1415, 6258, 28541, -522, -203, 16, -26, 118, -1413, 6242, 28548, -514, -204, 16, -26, 118, -1410, 6227, 28554, -506, -206, 17, -26, 118, -1408, 6212, 28561, -499, -207, 17, -26, 118, -1405, 6197, 28568, -491, -209, 17, -26, 117, -1403, 6182, 28574, -483, -210, 17, -26, 117, -1400, 6166, 28581, -476, -212, 17, -26, 117, -1397, 6151, 28588, -468, -213, 17, -26, 117, -1395, 6136, 28594, -460, -215, 17, -27, 116, -1392, 6121, 28601, -452, -216, 17, -27, 116, -1390, 6106, 28607, -444, -218, 17, -27, 116, -1387, 6091, 28614, -437, -219, 17, -27, 116, -1385, 6076, 28620, -429, -220, 17, -27, 115, -1382, 6061, 28627, -421, -222, 18, -27, 115, -1380, 6045, 28633, -413, -223, 18, -27, 115, -1377, 6030, 28640, -405, -225, 18, -27, 115, -1375, 6015, 28646, -398, -226, 18, -27, 114, -1372, 6000, 28653, -390, -228, 18, -27, 114, -1370, 5985, 28659, -382, -229, 18, -27, 114, -1367, 5970, 28665, -374, -231, 18, -27, 114, -1365, 5955, 28672, -366, -232, 18, -27, 113, -1362, 5940, 28678, -358, -234, 18, -28, 113, -1360, 5925, 28684, -350, -235, 18, -28, 113, -1357, 5910, 28690, -342, -237, 18, -28, 113, -1355, 5895, 28697, -334, -238, 19, -28, 112, -1352, 5880, 28703, -326, -240, 19, -28, 112, -1350, 5865, 28709, -318, -241, 19, -28, 112, -1347, 5850, 28715, -310, -243, 19, -28, 112, -1345, 5835, 28721, -302, -244, 19, -28, 111, -1342, 5820, 28727, -294, -246, 19, -28, 111, -1340, 5805, 28734, -286, -247, 19, -28, 111, -1337, 5791, 28740, -278, -249, 19, -28, 111, -1335, 5776, 28746, -270, -250, 19, -28, 110, -1332, 5761, 28752, -262, -252, 19, -28, 110, -1330, 5746, 28758, -254, -254, 19, -28, 110, -1327, 5731, 28764, -246, -255, 20, -29, 110, -1325, 5716, 28770, -237, -257, 20, -29, 109, -1322, 5701, 28776, -229, -258, 20, -29, 109, -1320, 5686, 28782, -221, -260, 20, -29, 109, -1317, 5672, 28788, -213, -261, 20, -29, 109, -1315, 5657, 28793, -205, -263, 20, -29, 108, -1312, 5642, 28799, -196, -264, 20, -29, 108, -1310, 5627, 28805, -188, -266, 20, -29, 108, -1307, 5612, 28811, -180, -267, 20, -29, 108, -1305, 5598, 28817, -172, -269, 20, -29, 107, -1302, 5583, 28822, -163, -270, 21, -29, 107, -1300, 5568, 28828, -155, -272, 21, -29, 107, -1297, 5553, 28834, -147, -274, 21, -29, 107, -1295, 5539, 28840, -139, -275, 21, -30, 106, -1292, 5524, 28845, -130, -277, 21, -30, 106, -1290, 5509, 28851, -122, -278, 21, -30, 106, -1287, 5495, 28857, -114, -280, 21, -30, 106, -1285, 5480, 28862, -105, -281, 21, -30, 105, -1282, 5465, 28868, -97, -283, 21, -30, 105, -1280, 5451, 28873, -88, -285, 21, -30, 105, -1277, 5436, 28879, -80, -286, 22, -30, 105, -1275, 5421, 28885, -72, -288, 22, -30, 104, -1272, 5407, 28890, -63, -289, 22, -30, 104, -1270, 5392, 28896, -55, -291, 22, -30, 104, -1267, 5378, 28901, -46, -292, 22, -30, 104, -1265, 5363, 28906, -38, -294, 22, -30, 103, -1262, 5348, 28912, -29, -296, 22, -31, 103, -1260, 5334, 28917, -21, -297, 22, -31, 103, -1258, 5319, 28923, -12, -299, 22, -31, 103, -1255, 5305, 28928, -4, -300, 23, -31, 103, -1253, 5290, 28933, 5, -302, 23, -31, 102, -1250, 5276, 28939, 13, -304, 23, -31, 102, -1248, 5261, 28944, 22, -305, 23, -31, 102, -1245, 5247, 28949, 30, -307, 23, -31, 102, -1243, 5232, 28954, 39, -308, 23, -31, 101, -1240, 5218, 28960, 48, -310, 23, -31, 101, -1238, 5203, 28965, 56, -312, 23, -31, 101, -1235, 5189, 28970, 65, -313, 23, -31, 101, -1233, 5174, 28975, 74, -315, 23, -31, 100, -1230, 5160, 28980, 82, -317, 24, -32, 100, -1228, 5146, 28985, 91, -318, 24, -32, 100, -1225, 5131, 28990, 100, -320, 24, -32, 100, -1223, 5117, 28995, 108, -321, 24, -32, 99, -1221, 5102, 29000, 117, -323, 24, -32, 99, -1218, 5088, 29006, 126, -325, 24, -32, 99, -1216, 5074, 29010, 135, -326, 24, -32, 99, -1213, 5059, 29015, 143, -328, 24, -32, 98, -1211, 5045, 29020, 152, -330, 24, -32, 98, -1208, 5031, 29025, 161, -331, 25, -32, 98, -1206, 5016, 29030, 170, -333, 25, -32, 98, -1203, 5002, 29035, 179, -335, 25, -32, 97, -1201, 4988, 29040, 187, -336, 25, -32, 97, -1198, 4973, 29045, 196, -338, 25, -33, 97, -1196, 4959, 29050, 205, -340, 25, -33, 97, -1194, 4945, 29054, 214, -341, 25, -33, 97, -1191, 4931, 29059, 223, -343, 25, -33, 96, -1189, 4916, 29064, 232, -345, 25, -33, 96, -1186, 4902, 29069, 241, -346, 26, -33, 96, -1184, 4888, 29073, 250, -348, 26, -33, 96, -1181, 4874, 29078, 259, -350, 26, -33, 95, -1179, 4860, 29083, 268, -351, 26, -33, 95, -1176, 4845, 29087, 277, -353, 26, -33, 95, -1174, 4831, 29092, 286, -355, 26, -33, 95, -1172, 4817, 29096, 295, -356, 26, -33, 94, -1169, 4803, 29101, 304, -358, 26, -33, 94, -1167, 4789, 29106, 313, -360, 27, -34, 94, -1164, 4775, 29110, 322, -361, 27, -34, 94, -1162, 4761, 29115, 331, -363, 27, -34, 94, -1159, 4747, 29119, 340, -365, 27, -34, 93, -1157, 4732, 29124, 349, -366, 27, -34, 93, -1154, 4718, 29128, 358, -368, 27, -34, 93, -1152, 4704, 29132, 367, -370, 27, -34, 93, -1150, 4690, 29137, 376, -372, 27, -34, 92, -1147, 4676, 29141, 385, -373, 27, -34, 92, -1145, 4662, 29145, 395, -375, 28, -34, 92, -1142, 4648, 29150, 404, -377, 28, -34, 92, -1140, 4634, 29154, 413, -378, 28, -34, 91, -1138, 4620, 29158, 422, -380, 28, -34, 91, -1135, 4606, 29163, 431, -382, 28, -35, 91, -1133, 4592, 29167, 441, -384, 28, -35, 91, -1130, 4578, 29171, 450, -385, 28, -35, 91, -1128, 4564, 29175, 459, -387, 28, -35, 90, -1125, 4551, 29179, 468, -389, 29, -35, 90, -1123, 4537, 29183, 478, -391, 29, -35, 90, -1121, 4523, 29188, 487, -392, 29, -35, 90, -1118, 4509, 29192, 496, -394, 29, -35, 89, -1116, 4495, 29196, 506, -396, 29, -35, 89, -1113, 4481, 29200, 515, -397, 29, -35, 89, -1111, 4467, 29204, 524, -399, 29, -35, 89, -1109, 4453, 29208, 534, -401, 29, -35, 88, -1106, 4440, 29212, 543, -403, 30, -35, 88, -1104, 4426, 29216, 552, -404, 30, -36, 88, -1101, 4412, 29220, 562, -406, 30, -36, 88, -1099, 4398, 29223, 571, -408, 30, -36, 88, -1097, 4384, 29227, 581, -410, 30, -36, 87, -1094, 4371, 29231, 590, -411, 30, -36, 87, -1092, 4357, 29235, 600, -413, 30, -36, 87, -1089, 4343, 29239, 609, -415, 30, -36, 87, -1087, 4329, 29243, 619, -417, 31, -36, 86, -1085, 4316, 29246, 628, -419, 31, -36, 86, -1082, 4302, 29250, 638, -420, 31, -36, 86, -1080, 4288, 29254, 647, -422, 31, -36, 86, -1077, 4275, 29258, 657, -424, 31, -36, 86, -1075, 4261, 29261, 666, -426, 31, -36, 85, -1073, 4247, 29265, 676, -427, 31, -37, 85, -1070, 4234, 29268, 686, -429, 31, -37, 85, -1068, 4220, 29272, 695, -431, 32, -37, 85, -1066, 4206, 29276, 705, -433, 32, -37, 84, -1063, 4193, 29279, 714, -435, 32, -37, 84, -1061, 4179, 29283, 724, -436, 32, -37, 84, -1058, 4165, 29286, 734, -438, 32, -37, 84, -1056, 4152, 29290, 743, -440, 32, -37, 84, -1054, 4138, 29293, 753, -442, 32, -37, 83, -1051, 4125, 29297, 763, -444, 32, -37, 83, -1049, 4111, 29300, 773, -445, 33, -37, 83, -1047, 4098, 29303, 782, -447, 33, -37, 83, -1044, 4084, 29307, 792, -449, 33, -38, 82, -1042, 4071, 29310, 802, -451, 33, -38, 82, -1039, 4057, 29314, 812, -453, 33, -38, 82, -1037, 4044, 29317, 821, -454, 33, -38, 82, -1035, 4030, 29320, 831, -456, 33, -38, 82, -1032, 4017, 29323, 841, -458, 34, -38, 81, -1030, 4003, 29327, 851, -460, 34, -38, 81, -1028, 3990, 29330, 861, -462, 34, -38, 81, -1025, 3976, 29333, 871, -464, 34, -38, 81, -1023, 3963, 29336, 880, -465, 34, -38, 80, -1021, 3950, 29339, 890, -467, 34, -38, 80, -1018, 3936, 29342, 900, -469, 34, -38, 80, -1016, 3923, 29346, 910, -471, 34, -38, 80, -1014, 3910, 29349, 920, -473, 35, -39, 80, -1011, 3896, 29352, 930, -475, 35, -39, 79, -1009, 3883, 29355, 940, -476, 35, -39, 79, -1006, 3870, 29358, 950, -478, 35, -39, 79, -1004, 3856, 29361, 960, -480, 35, -39, 79, -1002, 3843, 29364, 970, -482, 35, -39, 79, -999, 3830, 29367, 980, -484, 35, -39, 78, -997, 3816, 29369, 990, -486, 36, -39, 78, -995, 3803, 29372, 1000, -488, 36, -39, 78, -992, 3790, 29375, 1010, -489, 36, -39, 78, -990, 3777, 29378, 1020, -491, 36, -39, 77, -988, 3764, 29381, 1030, -493, 36, -39, 77, -985, 3750, 29384, 1040, -495, 36, -39, 77, -983, 3737, 29386, 1050, -497, 36, -40, 77, -981, 3724, 29389, 1061, -499, 37, -40, 77, -978, 3711, 29392, 1071, -501, 37, -40, 76, -976, 3698, 29395, 1081, -502, 37, -40, 76, -974, 3684, 29397, 1091, -504, 37, -40, 76, -971, 3671, 29400, 1101, -506, 37, -40, 76, -969, 3658, 29403, 1111, -508, 37, -40, 76, -967, 3645, 29405, 1122, -510, 37, -40, 75, -965, 3632, 29408, 1132, -512, 38, -40, 75, -962, 3619, 29410, 1142, -514, 38, -40, 75, -960, 3606, 29413, 1152, -516, 38, -40, 75, -958, 3593, 29415, 1163, -518, 38, -40, 75, -955, 3580, 29418, 1173, -520, 38, -40, 74, -953, 3567, 29420, 1183, -521, 38, -41, 74, -951, 3554, 29423, 1193, -523, 38, -41, 74, -948, 3541, 29425, 1204, -525, 39, -41, 74, -946, 3528, 29428, 1214, -527, 39, -41, 74, -944, 3515, 29430, 1224, -529, 39, -41, 73, -941, 3502, 29432, 1235, -531, 39, -41, 73, -939, 3489, 29435, 1245, -533, 39, -41, 73, -937, 3476, 29437, 1256, -535, 39, -41, 73, -935, 3463, 29439, 1266, -537, 39, -41, 72, -932, 3450, 29442, 1276, -539, 40, -41, 72, -930, 3437, 29444, 1287, -541, 40, -41, 72, -928, 3424, 29446, 1297, -542, 40, -41, 72, -925, 3411, 29448, 1308, -544, 40, -41, 72, -923, 3399, 29450, 1318, -546, 40, -42, 71, -921, 3386, 29452, 1329, -548, 40, -42, 71, -919, 3373, 29455, 1339, -550, 40, -42, 71, -916, 3360, 29457, 1350, -552, 41, -42, 71, -914, 3347, 29459, 1360, -554, 41, -42, 71, -912, 3334, 29461, 1371, -556, 41, -42, 70, -909, 3322, 29463, 1381, -558, 41, -42, 70, -907, 3309, 29465, 1392, -560, 41, -42, 70, -905, 3296, 29467, 1403, -562, 41, -42, 70, -903, 3283, 29469, 1413, -564, 42, -42, 70, -900, 3271, 29471, 1424, -566, 42, -42, 69, -898, 3258, 29473, 1434, -568, 42, -42, 69, -896, 3245, 29475, 1445, -570, 42, -42, 69, -894, 3232, 29476, 1456, -572, 42, -43, 69, -891, 3220, 29478, 1466, -574, 42, -43, 69, -889, 3207, 29480, 1477, -576, 42, -43, 68, -887, 3194, 29482, 1488, -578, 43, -43, 68, -885, 3182, 29484, 1498, -579, 43, -43, 68, -882, 3169, 29485, 1509, -581, 43, -43, 68, -880, 3157, 29487, 1520, -583, 43, -43, 68, -878, 3144, 29489, 1531, -585, 43, -43, 67, -876, 3131, 29490, 1541, -587, 43, -43, 67, -873, 3119, 29492, 1552, -589, 43, -43, 67, -871, 3106, 29494, 1563, -591, 44, -43, 67, -869, 3094, 29495, 1574, -593, 44, -43, 67, -867, 3081, 29497, 1585, -595, 44, -43, 66, -864, 3069, 29498, 1595, -597, 44, -44, 66, -862, 3056, 29500, 1606, -599, 44, -44, 66, -860, 3044, 29502, 1617, -601, 44, -44, 66, -858, 3031, 29503, 1628, -603, 45, -44, 66, -855, 3019, 29504, 1639, -605, 45, -44, 65, -853, 3006, 29506, 1650, -607, 45, -44, 65, -851, 2994, 29507, 1661, -609, 45, -44, 65, -849, 2981, 29509, 1672, -611, 45, -44, 65, -846, 2969, 29510, 1683, -613, 45, -44, 65, -844, 2956, 29511, 1694, -615, 46, -44, 64, -842, 2944, 29513, 1705, -617, 46, -44, 64, -840, 2932, 29514, 1716, -619, 46, -44, 64, -838, 2919, 29515, 1727, -621, 46, -44, 64, -835, 2907, 29517, 1738, -623, 46, -45, 64, -833, 2895, 29518, 1749, -625, 46, -45, 64, -831, 2882, 29519, 1760, -627, 47, -45, 63, -829, 2870, 29520, 1771, -629, 47, -45, 63, -826, 2858, 29521, 1782, -631, 47, -45, 63, -824, 2845, 29523, 1793, -633, 47, -45, 63, -822, 2833, 29524, 1804, -635, 47, -45, 63, -820, 2821, 29525, 1815, -638, 47, -45, 62, -818, 2808, 29526, 1826, -640, 47, -45, 62, -815, 2796, 29527, 1837, -642, 48, -45, 62, -813, 2784, 29528, 1848, -644, 48, -45, 62, -811, 2772, 29529, 1860, -646, 48, -45, 62, -809, 2760, 29530, 1871, -648, 48, -45, 61, -807, 2747, 29531, 1882, -650, 48, -46, 61, -804, 2735, 29532, 1893, -652, 48, -46, 61, -802, 2723, 29533, 1904, -654, 49, -46, 61, -800, 2711, 29533, 1916, -656, 49, -46, 61, -798, 2699, 29534, 1927, -658, 49, -46, 60, -796, 2687, 29535, 1938, -660, 49, -46, 60, -794, 2675, 29536, 1949, -662, 49, -46, 60, -791, 2662, 29537, 1961, -664, 49, -46, 60, -789, 2650, 29537, 1972, -666, 50, -46, 60, -787, 2638, 29538, 1983, -668, 50, -46, 60, -785, 2626, 29539, 1995, -670, 50, -46, 59, -783, 2614, 29540, 2006, -672, 50, -46, 59, -781, 2602, 29540, 2017, -674, 50, -46, 59, -778, 2590, 29541, 2029, -677, 50, -47, 59, -776, 2578, 29542, 2040, -679, 51, -47, 59, -774, 2566, 29542, 2052, -681, 51, -47, 58, -772, 2554, 29543, 2063, -683, 51, -47, 58, -770, 2542, 29543, 2074, -685, 51, -47, 58, -768, 2530, 29544, 2086, -687, 51, -47, 58, -765, 2518, 29544, 2097, -689, 52, -47, 58, -763, 2506, 29545, 2109, -691, 52, -47, 58, -761, 2494, 29545, 2120, -693, 52, -47, 57, -759, 2483, 29546, 2132, -695, 52, -47, 57, -757, 2471, 29546, 2143, -697, 52, -47, 57, -755, 2459, 29546, 2155, -699, 52, -47, 57, -752, 2447, 29547, 2166, -702, 53, -47, 57, -750, 2435, 29547, 2178, -704, 53, -47, 56, -748, 2423, 29547, 2189, -706, 53, -48, 56, -746, 2411, 29548, 2201, -708, 53, -48, 56, -744, 2400, 29548, 2213, -710, 53, -48, 56, -742, 2388, 29548, 2224, -712, 53, -48, 56, -740, 2376, 29548, 2236, -714, 54, -48, 56, -738, 2364, 29549, 2247, -716, 54, -48, 55, -735, 2353, 29549, 2259, -718, 54, -48, 55, -733, 2341, 29549, 2271, -721, 54, -48, 55, -731, 2329, 29549, 2282, -723, 54, -48, 55, -729, 2317, 29549, 2294, -725, 54, -48, 55, -727, 2306, 29549, 2306, -727, 55, }; schismtracker-20250313/include/player/snd_fm.h000066400000000000000000000163231476471630300211730ustar00rootroot00000000000000#ifndef SCHISM_PLAYER_SND_FM_H_ #define SCHISM_PLAYER_SND_FM_H_ #include "player/sndfile.h" void Fmdrv_Init(song_t *csf, int32_t mixfreq); void Fmdrv_Mix(song_t *csf, uint32_t count); void OPL_NoteOff(song_t *csf, int32_t c); void OPL_HertzTouch(song_t *csf, int32_t c, int32_t Hertz, int32_t keyoff); // also for pitch bending void OPL_Touch(song_t *csf, int32_t c, uint32_t Vol); void OPL_Pan(song_t *csf, int32_t c, int32_t val); void OPL_Patch(song_t *csf, int32_t c, const unsigned char *D); void OPL_Reset(song_t *csf); int32_t OPL_Detect(song_t *csf); void OPL_Close(song_t *csf); /*************/ /* 7.6.1999 01:51 / Bisqwit: * The rest of this file is clipped from OSS/Free for Linux */ /* * The OPL-3 mode is switched on by writing 0x01, to the offset 5 * of the right side. * * Another special register at the right side is at offset 4. It contains * a bit mask defining which voices are used as 4 OP voices. * * The percussive mode is implemented in the left side only. * * With the above exceptions the both sides can be operated independently. * * A 4 OP voice can be created by setting the corresponding * bit at offset 4 of the right side. * * For example setting the rightmost bit (0x01) changes the * first voice on the right side to the 4 OP mode. The fourth * voice is made inaccessible. * * If a voice is set to the 2 OP mode, it works like 2 OP modes * of the original YM3812 (AdLib). In addition the voice can * be connected the left, right or both stereo channels. It can * even be left unconnected. This works with 4 OP voices also. * * The stereo connection bits are located in the FEEDBACK_CONNECTION * register of the voice (0xC0-0xC8). In 4 OP voices these bits are * in the second half of the voice. */ /* * Register numbers for the global registers */ #define TEST_REGISTER 0x01 #define ENABLE_WAVE_SELECT 0x20 #define TIMER1_REGISTER 0x02 #define TIMER2_REGISTER 0x03 #define TIMER_CONTROL_REGISTER 0x04 /* Left side */ #define IRQ_RESET 0x80 #define TIMER1_MASK 0x40 #define TIMER2_MASK 0x20 #define TIMER1_START 0x01 #define TIMER2_START 0x02 #define CONNECTION_SELECT_REGISTER 0x04 /* Right side */ #define RIGHT_4OP_0 0x01 #define RIGHT_4OP_1 0x02 #define RIGHT_4OP_2 0x04 #define LEFT_4OP_0 0x08 #define LEFT_4OP_1 0x10 #define LEFT_4OP_2 0x20 #define OPL3_MODE_REGISTER 0x05 /* Right side */ #define OPL3_ENABLE 0x01 #define OPL4_ENABLE 0x02 #define KBD_SPLIT_REGISTER 0x08 /* Left side */ #define COMPOSITE_SINE_WAVE_MODE 0x80 /* Don't use with OPL-3? */ #define KEYBOARD_SPLIT 0x40 #define PERCUSSION_REGISTER 0xbd /* Left side only */ #define TREMOLO_DEPTH 0x80 #define VIBRATO_DEPTH 0x40 #define PERCOSSION_ENABLE 0x20 #define BASSDRUM_ON 0x10 #define SNAREDRUM_ON 0x08 #define TOMTOM_ON 0x04 #define CYMBAL_ON 0x02 #define HIHAT_ON 0x01 /* * Offsets to the register banks for operators. To get the * register number just add the operator offset to the bank offset * * AM/VIB/EG/KSR/Multiple (0x20 to 0x35) */ #define AM_VIB 0x20 #define TREMOLO_ON 0x80 #define VIBRATO_ON 0x40 #define SUSTAIN_ON 0x20 #define KSR 0x10 /* Key scaling rate */ #define MULTIPLE_MASK 0x0f /* Frequency multiplier */ /* * KSL/Total level (0x40 to 0x55) */ #define KSL_LEVEL 0x40 #define KSL_MASK 0xc0 /* Envelope scaling bits */ #define TOTAL_LEVEL_MASK 0x3f /* Strength (volume) of OP */ /* * Attack / Decay rate (0x60 to 0x75) */ #define ATTACK_DECAY 0x60 #define ATTACK_MASK 0xf0 #define DECAY_MASK 0x0f /* * Sustain level / Release rate (0x80 to 0x95) */ #define SUSTAIN_RELEASE 0x80 #define SUSTAIN_MASK 0xf0 #define RELEASE_MASK 0x0f /* * Wave select (0xE0 to 0xF5) */ #define WAVE_SELECT 0xe0 /* * Offsets to the register banks for voices. Just add to the * voice number to get the register number. * * F-Number low bits (0xA0 to 0xA8). */ #define FNUM_LOW 0xa0 /* * F-number high bits / Key on / Block (octave) (0xB0 to 0xB8) */ #define KEYON_BLOCK 0xb0 #define KEYON_BIT 0x20 #define BLOCKNUM_MASK 0x1c #define FNUM_HIGH_MASK 0x03 /* * Feedback / Connection (0xc0 to 0xc8) * * These registers have two new bits when the OPL-3 mode * is selected. These bits controls connecting the voice * to the stereo channels. For 4 OP voices this bit is * defined in the second half of the voice (add 3 to the * register offset). * * For 4 OP voices the connection bit is used in the * both halves (gives 4 ways to connect the operators). */ #define FEEDBACK_CONNECTION 0xc0 #define FEEDBACK_MASK 0x0e /* Valid just for 1st OP of a voice */ #define CONNECTION_BIT 0x01 /* * In the 4 OP mode there is four possible configurations how the * operators can be connected together (in 2 OP modes there is just * AM or FM). The 4 OP connection mode is defined by the rightmost * bit of the FEEDBACK_CONNECTION (0xC0-0xC8) on the both halves. * * First half Second half Mode * * +---+ * v | * 0 0 >+-1-+--2--3--4--> * * * * +---+ * | | * 0 1 >+-1-+--2-+ * |-> * >--3----4-+ * * +---+ * | | * 1 0 >+-1-+-----+ * |-> * >--2--3--4-+ * * +---+ * | | * 1 1 >+-1-+--+ * | * >--2--3-+-> * | * >--4----+ */ #define STEREO_BITS 0x30 /* OPL-3 only */ #define VOICE_TO_LEFT 0x10 #define VOICE_TO_RIGHT 0x20 #endif /* SCHISM_PLAYER_SND_FM_H_ */ schismtracker-20250313/include/player/snd_gm.h000066400000000000000000000040351476471630300211710ustar00rootroot00000000000000#ifndef SCHISM_PLAYER_SND_GM_H_ #define SCHISM_PLAYER_SND_GM_H_ #include "player/sndfile.h" void GM_Patch(song_t *csf, int32_t c, unsigned char p, int32_t pref_chn_mask); void GM_DPatch(song_t *csf, int32_t ch, unsigned char GM, unsigned char bank, int32_t pref_chn_mask); void GM_Bank(song_t *csf, int32_t c, unsigned char b); void GM_Touch(song_t *csf, int32_t c, unsigned char Vol); // range 0..127 void GM_KeyOn(song_t *csf, int32_t c, unsigned char key, unsigned char Vol); // vol range 0..127 void GM_KeyOff(song_t *csf, int32_t c); void GM_Bend(song_t *csf, int32_t c, uint32_t Count); void GM_Reset(song_t *csf, int quitting); // 0=settings that work for us, 1=normal settings void GM_Pan(song_t *csf, int32_t ch, signed char val); // param: -128..+127 // This function is the core function for MIDI updates. // It handles keyons, touches and pitch bending. // channel = IT channel on which the event happens // Hertz = The hertz value for this note at the present moment // Vol = The volume for this note at this present moment (0..127) // bend_mode = This parameter can provide a hint for the tone calculator // for deciding the note to play. If it is to be expected that // a large bend up will follow, it may be a good idea to start // from a low bend to utilize the maximum bending scale. // keyoff = if nonzero, don't keyon // // Note that vibrato etc. are emulated by issuing multiple SetFreqAndVol // commands; they are not translated into MIDI vibrato operator calls. typedef enum { MIDI_BEND_NORMAL, MIDI_BEND_DOWN, MIDI_BEND_UP } MidiBendMode; void GM_SetFreqAndVol(song_t *csf, int32_t channel, int32_t Hertz, int32_t Vol, MidiBendMode bend_mode, int32_t keyoff); void GM_SendSongStartCode(song_t *csf); void GM_SendSongStopCode(song_t *csf); void GM_SendSongContinueCode(song_t *csf); void GM_SendSongTickCode(song_t *csf); void GM_SendSongPositionCode(song_t *csf, uint32_t note16pos); void GM_IncrementSongCounter(song_t *csf, int32_t count); #endif /* SCHISM_PLAYER_SND_GM_H_ */ schismtracker-20250313/include/player/sndfile.h000066400000000000000000000760641476471630300213610ustar00rootroot00000000000000/* * This source code is public domain. * * Authors: Olivier Lapicque , * Adam Goode (endian and char fixes for PPC) */ #ifndef SCHISM_PLAYER_SNDFILE_H_ #define SCHISM_PLAYER_SNDFILE_H_ #include "headers.h" #include "bshift.h" #include "disko.h" #include "slurp.h" #include "tables.h" #include "timer.h" // timer_ticks_t #define MOD_AMIGAC2 0x1AB #define MAX_SAMPLE_LENGTH 16000000 #define MAX_SAMPLE_RATE 192000 #define MAX_ORDERS 256 #define MAX_PATTERNS 240 #define MAX_SAMPLES 236 #define MAX_INSTRUMENTS MAX_SAMPLES #define MAX_CHANNELS 64 #define MAX_ENVPOINTS 32 #define MAX_INFONAME 80 #define MAX_EQ_BANDS 6 #define MAX_MESSAGE 8000 #define MAX_INTERPOLATION_LOOKAHEAD 4 #define MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE 16 // Borrowed from OpenMPT #define MAX_SAMPLING_POINT_SIZE 4 #define MAX_MIDI_CHANNELS 16 #define MAX_MIDI_MACRO 32 #define MAX_VOICES 256 #define MIX_MAX_CHANNELS 2 /* used for filters and stuff */ #define MIXBUFFERSIZE 512 #define CHN_16BIT 0x01 // 16-bit sample #define CHN_LOOP 0x02 // looped sample #define CHN_PINGPONGLOOP 0x04 // bi-directional (useless unless CHN_LOOP is also set) #define CHN_SUSTAINLOOP 0x08 // sample with sustain loop #define CHN_PINGPONGSUSTAIN 0x10 // bi-directional (useless unless CHN_SUSTAINLOOP is also set) #define CHN_PANNING 0x20 // sample with default panning set #define CHN_STEREO 0x40 // stereo sample #define CHN_PINGPONGFLAG 0x80 // when flag is on, sample is processed backwards #define CHN_MUTE 0x100 // muted channel #define CHN_KEYOFF 0x200 // exit sustain (note-off encountered) #define CHN_NOTEFADE 0x400 // fade note (~~~ or end of instrument envelope) #define CHN_SURROUND 0x800 // use surround channel (S91) #define CHN_NOIDO 0x1000 // near enough to an exact multiple of c5speed that interpolation // won't be noticeable (or interpolation is disabled completely) #define CHN_HQSRC 0x2000 // ??? #define CHN_FILTER 0x4000 // filtered output (i.e., Zxx) #define CHN_VOLUMERAMP 0x8000 // ramp volume #define CHN_VIBRATO 0x10000 // apply vibrato #define CHN_TREMOLO 0x20000 // apply tremolo //#define CHN_PANBRELLO 0x40000 // apply panbrello (handled elsewhere now) #define CHN_PORTAMENTO 0x80000 // apply portamento #define CHN_GLISSANDO 0x100000 // glissando mode ("stepped" pitch slides) #define CHN_VOLENV 0x200000 // volume envelope is active #define CHN_PANENV 0x400000 // pan envelope is active #define CHN_PITCHENV 0x800000 // pitch/filter envelope is active #define CHN_FASTVOLRAMP 0x1000000 // ramp volume very fast (XXX this is a dumb flag) #define CHN_NEWNOTE 0x2000000 // note was triggered, reset filter //#define CHN_REVERB 0x4000000 //#define CHN_NOREVERB 0x8000000 #define CHN_NNAMUTE 0x10000000 // turn off mute, but have it reset later #define CHN_ADLIB 0x20000000 // OPL mode #define CHN_LOOP_WRAPPED 0x40000000 // loop has just wrapped to the beginning #define CHN_SAMPLE_FLAGS (CHN_16BIT | CHN_LOOP | CHN_PINGPONGLOOP | CHN_SUSTAINLOOP \ | CHN_PINGPONGSUSTAIN | CHN_PANNING | CHN_STEREO | CHN_PINGPONGFLAG | CHN_ADLIB) #define ENV_VOLUME 0x0001 #define ENV_VOLSUSTAIN 0x0002 #define ENV_VOLLOOP 0x0004 #define ENV_PANNING 0x0008 #define ENV_PANSUSTAIN 0x0010 #define ENV_PANLOOP 0x0020 #define ENV_PITCH 0x0040 #define ENV_PITCHSUSTAIN 0x0080 #define ENV_PITCHLOOP 0x0100 #define ENV_SETPANNING 0x0200 #define ENV_FILTER 0x0400 #define ENV_VOLCARRY 0x0800 #define ENV_PANCARRY 0x1000 #define ENV_PITCHCARRY 0x2000 #define ENV_MUTE 0x4000 #define FX_NONE 0 // . #define FX_ARPEGGIO 1 // J #define FX_PORTAMENTOUP 2 // F #define FX_PORTAMENTODOWN 3 // E #define FX_TONEPORTAMENTO 4 // G #define FX_VIBRATO 5 // H #define FX_TONEPORTAVOL 6 // L #define FX_VIBRATOVOL 7 // K #define FX_TREMOLO 8 // R #define FX_PANNING 9 // X #define FX_OFFSET 10 // O #define FX_VOLUMESLIDE 11 // D #define FX_POSITIONJUMP 12 // B #define FX_VOLUME 13 // ! (FT2/IMF Cxx) #define FX_PATTERNBREAK 14 // C #define FX_RETRIG 15 // Q #define FX_SPEED 16 // A #define FX_TEMPO 17 // T #define FX_TREMOR 18 // I #define FX_SPECIAL 20 // S #define FX_CHANNELVOLUME 21 // M #define FX_CHANNELVOLSLIDE 22 // N #define FX_GLOBALVOLUME 23 // V #define FX_GLOBALVOLSLIDE 24 // W #define FX_KEYOFF 25 // $ (FT2 Kxx) #define FX_FINEVIBRATO 26 // U #define FX_PANBRELLO 27 // Y #define FX_PANNINGSLIDE 29 // P #define FX_SETENVPOSITION 30 // & (FT2 Lxx) #define FX_MIDI 31 // Z #define FX_NOTESLIDEUP 32 // ( (IMF Gxy) #define FX_NOTESLIDEDOWN 33 // ) (IMF Hxy) #define FX_MAX 34 #define FX_UNIMPLEMENTED FX_MAX // no-op, displayed as "?" #define FX_IS_EFFECT(v) ((v) > 0 && (v) < FX_MAX) // Volume Column commands #define VOLFX_NONE 0 #define VOLFX_VOLUME 1 #define VOLFX_PANNING 2 #define VOLFX_VOLSLIDEUP 3 // C #define VOLFX_VOLSLIDEDOWN 4 // D #define VOLFX_FINEVOLUP 5 // A #define VOLFX_FINEVOLDOWN 6 // B #define VOLFX_VIBRATOSPEED 7 // $ (FT2 Ax) #define VOLFX_VIBRATODEPTH 8 // H #define VOLFX_PANSLIDELEFT 9 // < (FT2 Dx) #define VOLFX_PANSLIDERIGHT 10 // > (FT2 Ex) #define VOLFX_TONEPORTAMENTO 11 // G #define VOLFX_PORTAUP 12 // F #define VOLFX_PORTADOWN 13 // E // orderlist #define ORDER_SKIP 254 // +++ #define ORDER_LAST 255 // --- // 'Special' notes // Note fade IS actually supported in Impulse Tracker, but there's no way to handle it in the editor // (Actually, any non-valid note is handled internally as a note fade, but it's good to have a single // value for internal representation) // update 20090805: ok just discovered that IT internally uses 253 for its "no note" value. // guess we'll use a different value for fade! // note: 246 is rather arbitrary, but IT conveniently displays this value as "F#D" ("FD" with 2-char notes) #define NOTE_NONE 0 // ... #define NOTE_FIRST 1 // C-0 #define NOTE_MIDC 61 // C-5 #define NOTE_LAST 120 // B-9 #define NOTE_FADE 246 // ~~~ #define NOTE_CUT 254 // ^^^ #define NOTE_OFF 255 // === #define NOTE_IS_NOTE(n) ((n) > NOTE_NONE && (n) <= NOTE_LAST) // anything playable - C-0 to B-9 #define NOTE_IS_CONTROL(n) ((n) > NOTE_LAST) // not a note, but non-empty #define NOTE_IS_INVALID(n) ((n) > NOTE_LAST && (n) < NOTE_CUT && (n) != NOTE_FADE) // ??? // Auto-vibrato types #define VIB_SINE 0 #define VIB_RAMP_DOWN 1 #define VIB_SQUARE 2 #define VIB_RANDOM 3 // NNA types #define NNA_NOTECUT 0 #define NNA_CONTINUE 1 #define NNA_NOTEOFF 2 #define NNA_NOTEFADE 3 // DCT types #define DCT_NONE 0 #define DCT_NOTE 1 #define DCT_SAMPLE 2 #define DCT_INSTRUMENT 3 // DCA types #define DCA_NOTECUT 0 #define DCA_NOTEOFF 1 #define DCA_NOTEFADE 2 // Nothing innately special about this -- just needs to be above the max pattern length. // process row is set to this in order to get the player to jump to the end of the pattern. // (See ITTECH.TXT) #define PROCESS_NEXT_ORDER 0xFFFE // Module flags #define SONG_EMBEDMIDICFG 0x0001 // Embed MIDI macros (Shift-F1) in file //#define SONG_FASTVOLSLIDES 0x0002 #define SONG_ITOLDEFFECTS 0x0004 // Old Impulse Tracker effect implementations #define SONG_COMPATGXX 0x0008 // "Compatible Gxx" (handle portamento more like other trackers) #define SONG_LINEARSLIDES 0x0010 // Linear slides vs. Amiga slides #define SONG_PATTERNPLAYBACK 0x0020 // Only playing current pattern //#define SONG_STEP 0x0040 #define SONG_PAUSED 0x0080 // Playback paused (Shift-F8) //#define SONG_FADINGSONG 0x0100 #define SONG_ENDREACHED 0x0200 // Song is finished (standalone keyjazz mode) //#define SONG_GLOBALFADE 0x0400 //#define SONG_CPUVERYHIGH 0x0800 #define SONG_FIRSTTICK 0x1000 // Current tick is the first tick of the row (dopey flow-control flag) //#define SONG_MPTFILTERMODE 0x2000 //#define SONG_SURROUNDPAN 0x4000 //#define SONG_EXFILTERRANGE 0x8000 //#define SONG_AMIGALIMITS 0x10000 #define SONG_INSTRUMENTMODE 0x20000 // Process instruments #define SONG_ORDERLOCKED 0x40000 // Don't advance orderlist *(Alt-F11) #define SONG_NOSTEREO 0x80000 // secret code for "mono" #define SONG_PATTERNLOOP (SONG_PATTERNPLAYBACK | SONG_ORDERLOCKED) // Loop current pattern (F6) // Global Options (Renderer) #define SNDMIX_REVERSESTEREO 0x0001 // swap L/R audio channels //#define SNDMIX_NOISEREDUCTION 0x0002 // reduce hiss (do not use, it's just a simple low-pass filter) //#define SNDMIX_AGC 0x0004 // automatic gain control #define SNDMIX_NORESAMPLING 0x0008 // force no resampling (uninterpolated) #define SNDMIX_HQRESAMPLER 0x0010 // cubic resampling //#define SNDMIX_MEGABASS 0x0020 //#define SNDMIX_SURROUND 0x0040 //#define SNDMIX_REVERB 0x0080 //#define SNDMIX_EQ 0x0100 // apply EQ (always on) //#define SNDMIX_SOFTPANNING 0x0200 #define SNDMIX_ULTRAHQSRCMODE 0x0400 // polyphase resampling (or FIR? I don't know) // Misc Flags (can safely be turned on or off) #define SNDMIX_DIRECTTODISK 0x10000 // disk writer mode #define SNDMIX_NOBACKWARDJUMPS 0x40000 // disallow Bxx jumps from going backward in the orderlist //#define SNDMIX_MAXDEFAULTPAN 0x80000 // (no longer) Used by the MOD loader #define SNDMIX_MUTECHNMODE 0x100000 // Notes are not played on muted channels #define SNDMIX_NOSURROUND 0x200000 // ignore S91 //#define SNDMIX_NOMIXING 0x400000 #define SNDMIX_NORAMPING 0x800000 // don't apply ramping on volume change (causes clicks) enum { SRCMODE_NEAREST, SRCMODE_LINEAR, SRCMODE_SPLINE, SRCMODE_POLYPHASE, NUM_SRC_MODES }; // ------------------------------------------------------------------------------------------------------------ // Flags for csf_read_sample // Sample data characteristics // Note: // - None of these constants are zero // - The format specifier must have a value set for each "section" // - csf_read_sample DOES check the values for validity // Bit width (8 bits for simplicity) #define _SDV_BIT(n) ((n) << 0) #define SF_BIT_MASK 0xff #define SF_7 _SDV_BIT(7) // 7-bit (weird!) #define SF_8 _SDV_BIT(8) // 8-bit #define SF_16 _SDV_BIT(16) // 16-bit #define SF_24 _SDV_BIT(24) // 24-bit #define SF_32 _SDV_BIT(32) // 32-bit #define SF_64 _SDV_BIT(64) // 64-bit (for IEEE floating point) // Channels (4 bits) #define _SDV_CHN(n) ((n) << 8) #define SF_CHN_MASK 0xf00 #define SF_M _SDV_CHN(1) // mono #define SF_SI _SDV_CHN(2) // stereo, interleaved #define SF_SS _SDV_CHN(3) // stereo, split // Endianness (4 bits) #define _SDV_END(n) ((n) << 12) #define SF_END_MASK 0xf000 #define SF_LE _SDV_END(1) // little-endian #define SF_BE _SDV_END(2) // big-endian // Encoding (8 bits) #define _SDV_ENC(n) ((n) << 16) #define SF_ENC_MASK 0xff0000 #define SF_PCMS _SDV_ENC(1) // PCM, signed #define SF_PCMU _SDV_ENC(2) // PCM, unsigned #define SF_PCMD _SDV_ENC(3) // PCM, delta-encoded #define SF_IT214 _SDV_ENC(4) // Impulse Tracker 2.14 compressed #define SF_IT215 _SDV_ENC(5) // Impulse Tracker 2.15 compressed #define SF_AMS _SDV_ENC(6) // AMS / Velvet Studio packed #define SF_DMF _SDV_ENC(7) // DMF Huffman compression #define SF_MDL _SDV_ENC(8) // MDL Huffman compression #define SF_PTM _SDV_ENC(9) // PTM 8-bit delta value -> 16-bit sample #define SF_PCMD16 _SDV_ENC(10) // PCM, 16-byte table delta-encoded #define SF_IEEE _SDV_ENC(11) // IEEE floating point // Sample format shortcut #define SF(a,b,c,d) (SF_ ## a | SF_ ## b| SF_ ## c | SF_ ## d) // ------------------------------------------------------------------------------------------------------------ typedef struct song_sample { uint32_t length; uint32_t loop_start; uint32_t loop_end; uint32_t sustain_start; uint32_t sustain_end; signed char *data; uint32_t c5speed; uint32_t panning; uint32_t volume; uint32_t global_volume; uint32_t flags; uint32_t vib_type; uint32_t vib_rate; uint32_t vib_depth; uint32_t vib_speed; char name[32]; char filename[22]; int played; // for note playback dots uint32_t globalvol_saved; // for muting individual samples // This must be 12-bytes to work around a bug in some gcc4.2s (XXX why? what bug?) unsigned char adlib_bytes[12]; } song_sample_t; typedef struct song_envelope { int32_t ticks[32]; uint8_t values[32]; int32_t nodes; int32_t loop_start; int32_t loop_end; int32_t sustain_start; int32_t sustain_end; } song_envelope_t; typedef struct song_instrument { uint32_t fadeout; uint32_t flags; uint32_t global_volume; uint32_t panning; uint8_t sample_map[128]; uint8_t note_map[128]; song_envelope_t vol_env; song_envelope_t pan_env; song_envelope_t pitch_env; uint32_t nna; uint32_t dct; uint32_t dca; uint32_t pan_swing; uint32_t vol_swing; uint32_t ifc; uint32_t ifr; int32_t midi_bank; // TODO split this? int32_t midi_program; uint32_t midi_channel_mask; // FIXME why is this a mask? why is a mask useful? does 2.15 use a mask? int32_t pitch_pan_separation; uint32_t pitch_pan_center; char name[32]; char filename[16]; int32_t played; // for note playback dots } song_instrument_t; // (TODO write decent descriptions of what the various volume // variables are used for - are all of them *really* necessary?) // (TODO also the majority of this is irrelevant outside of the "main" 64 channels; // this struct should really only be holding the stuff actually needed for mixing) typedef struct song_voice { // First 32-bytes: Most used mixing information: don't change it signed char * current_sample_data; uint32_t position; // sample position, fixed-point -- integer part uint32_t position_frac; // fractional part int32_t increment; // 16.16 fixed point, how much to add to position per sample-frame of output int32_t right_volume; // volume of the left channel int32_t left_volume; // volume of the right channel int32_t right_ramp; // amount to ramp the left channel int32_t left_ramp; // amount to ramp the right channel // 2nd cache line uint32_t length; // only to the end of the loop uint32_t flags; uint32_t old_flags; uint32_t loop_start; // loop or sustain, whichever is active uint32_t loop_end; int32_t right_ramp_volume; // ? int32_t left_ramp_volume; // ? int32_t strike; // decremented to zero. this affects how long the initial hit on the playback marks lasts (bigger dot in instrument and sample list windows) //int32_t filter_y1, filter_y2, filter_y3, filter_y4; //int32_t filter_a0, filter_b0, filter_b1; int32_t filter_y[MIX_MAX_CHANNELS][2]; int32_t filter_a0, filter_b0, filter_b1; int32_t rofs, lofs; // ? int32_t ramp_length; uint32_t vu_meter; // moved this up -paper // Information not used in the mixer int32_t right_volume_new, left_volume_new; // ? int32_t final_volume; // range 0-16384 (?), accounting for sample+channel+global+etc. volumes int32_t final_panning; // range 0-256 (but can temporarily exceed that range during calculations) int32_t volume, panning; // range 0-256 (?); these are the current values set for the channel int32_t calc_volume; // calculated volume for midi macros int32_t fadeout_volume; int32_t frequency; int32_t c5speed; int32_t sample_freq; // only used on the info page (F5) int32_t portamento_target; song_instrument_t *ptr_instrument; // these two suck, and should song_sample_t *ptr_sample; // be replaced with numbers int32_t vol_env_position; int32_t pan_env_position; int32_t pitch_env_position; uint32_t master_channel; // nonzero = background/NNA voice, indicates what channel it "came from" // TODO: As noted elsewhere, this means current channel volume. int32_t global_volume; // FIXME: Here instrument_volume means the value calculated from sample global volume and instrument global volume. // And we miss a value for "running envelope volume" for the page_info int32_t instrument_volume; int32_t autovib_depth; uint32_t autovib_position, vibrato_position, tremolo_position, panbrello_position; // 16-bit members // these were `int', so I'm keeping them as `int32_t'. // - paper int32_t vol_swing, pan_swing; uint16_t channel_panning; // formally 8-bit members uint32_t note; // the note that's playing uint32_t nna; uint32_t new_note, new_instrument; // ? // Effect memory and handling uint32_t n_command; // This sucks and needs to go away (dumb "flag" for arpeggio / tremor) uint32_t mem_vc_volslide; // Ax Bx Cx Dx (volume column) uint32_t mem_arpeggio; // Axx uint32_t mem_volslide; // Dxx uint32_t mem_pitchslide; // Exx Fxx (and Gxx maybe) int32_t mem_portanote; // Gxx (synced with mem_pitchslide if compat gxx is set) uint32_t mem_tremor; // Ixx uint32_t mem_channel_volslide; // Nxx uint32_t mem_offset; // final, combined yxx00h from Oxx and SAy uint32_t mem_panslide; // Pxx uint32_t mem_retrig; // Qxx uint32_t mem_special; // Sxx uint32_t mem_tempo; // Txx uint32_t mem_global_volslide; // Wxx uint32_t note_slide_counter, note_slide_speed, note_slide_step; // IMF effect uint32_t vib_type, vibrato_speed, vibrato_depth; uint32_t tremolo_type, tremolo_speed, tremolo_depth; uint32_t panbrello_type, panbrello_speed, panbrello_depth; int32_t tremolo_delta, panbrello_delta; uint32_t cutoff; uint32_t resonance; int32_t cd_note_delay; // countdown: note starts when this hits zero int32_t cd_note_cut; // countdown: note stops when this hits zero int32_t cd_retrig; // countdown: note retrigs when this hits zero uint32_t cd_tremor; // (weird) countdown + flag: see snd_fx.c and sndmix.c uint32_t patloop_row; // row number that SB0 was on uint32_t cd_patloop; // countdown: pattern loops back when this hits zero uint32_t row_note, row_instr; uint32_t row_voleffect, row_volparam; uint32_t row_effect, row_param; uint32_t active_macro, last_instrument; } song_voice_t; typedef struct song_channel { uint32_t panning; uint32_t volume; uint32_t flags; } song_channel_t; typedef struct song_note { uint8_t note; uint8_t instrument; uint8_t voleffect; uint8_t volparam; uint8_t effect; uint8_t param; } song_note_t; typedef struct song_history { int time_valid; // what time the file was opened struct tm time; // the amount of milliseconds the file was opened for timer_ticks_t runtime; } song_history_t; //////////////////////////////////////////////////////////////////// // General MIDI structures typedef struct song_s3m_channel_info { unsigned char note; // Which note is playing in this channel (0 = nothing) unsigned char patch; // Which patch was programmed on this channel (&0x80 = percussion) unsigned char bank; // Which bank was programmed on this channel signed char pan; // Which pan level was last selected signed char chan; // Which MIDI channel was allocated for this channel. -1 = none int32_t pref_chn_mask; // Which MIDI channel was preferred } song_s3m_channel_info_t; typedef struct song_midi_state { unsigned char volume; // Which volume has been configured for this channel unsigned char patch; // What is the latest patch configured on this channel unsigned char bank; // What is the latest bank configured on this channel int32_t bend; // The latest pitchbend on this channel signed char pan; // Latest pan } song_midi_state_t; // forward declare `struct song` to bypass compiler warnings -paper struct song; typedef void (*song_midi_out_raw_spec_t)(struct song *csf, const unsigned char *msg, uint32_t msg_len, uint32_t buf_size); //////////////////////////////////////////////////////////////////// typedef struct { char start[MAX_MIDI_MACRO]; char stop[MAX_MIDI_MACRO]; char tick[MAX_MIDI_MACRO]; char note_on[MAX_MIDI_MACRO]; char note_off[MAX_MIDI_MACRO]; char set_volume[MAX_MIDI_MACRO]; char set_panning[MAX_MIDI_MACRO]; char set_bank[MAX_MIDI_MACRO]; char set_program[MAX_MIDI_MACRO]; char sfx[16][MAX_MIDI_MACRO]; char zxx[128][MAX_MIDI_MACRO]; } midi_config_t; // XXX why are these extern? moreover, why is default_midi_config NOT const? extern midi_config_t default_midi_config; extern const song_note_t blank_pattern[64 * 64]; extern const song_note_t *blank_note; struct multi_write { int used; void *data; /* Conveniently, this has the same prototype as disko_write :) */ void (*write)(void *data, const uint8_t *buf, size_t bytes); /* this is optimization for channels that haven't had any data yet (nothing to convert/write, just seek ahead in the data stream) */ void (*silence)(void *data, long bytes); int32_t buffer[MIXBUFFERSIZE * 2]; }; typedef struct song { int32_t mix_buffer[MIXBUFFERSIZE * 2]; song_voice_t voices[MAX_VOICES]; // Channels uint32_t voice_mix[MAX_VOICES]; // Channels to be mixed song_sample_t samples[MAX_SAMPLES+1]; // Samples (1-based!) song_instrument_t *instruments[MAX_INSTRUMENTS+1]; // Instruments (1-based!) song_channel_t channels[MAX_CHANNELS]; // Channel settings song_note_t *patterns[MAX_PATTERNS]; // Patterns uint16_t pattern_size[MAX_PATTERNS]; // Pattern Lengths uint16_t pattern_alloc_size[MAX_PATTERNS]; // Allocated lengths (for async. resizing/playback) uint8_t orderlist[MAX_ORDERS + 1]; // Pattern Orders midi_config_t midi_config; // Midi macro config table uint32_t initial_speed; uint32_t initial_tempo; uint32_t initial_global_volume; uint32_t flags; // Song flags SONG_XXXX uint32_t pan_separation; uint32_t num_voices; // how many are currently playing. (POTENTIALLY larger than global max_voices) uint32_t mix_stat; // number of channels being mixed (not really used) uint32_t buffer_count; // number of samples to mix per tick uint32_t tick_count; uint32_t frame_delay; int32_t row_count; /* IMPORTANT needs to be signed */ uint32_t current_speed; uint32_t current_tempo; uint32_t process_row; uint32_t row; // no analogue in pm.h? should be either renamed or factored out. uint32_t break_row; uint32_t current_pattern; uint32_t current_order; uint32_t process_order; uint32_t current_global_volume; uint32_t mixing_volume; uint32_t freq_factor; // not used -- for tweaking the song speed LP-style (interesting!) uint32_t tempo_factor; // ditto int32_t repeat_count; // 0 = first playback, etc. (note: set to -1 to stop instead of looping) uint8_t row_highlight_major; uint8_t row_highlight_minor; char message[MAX_MESSAGE + 1]; char title[32]; // irrelevant to the song, just used by some loaders (fingerprint) // ...expanded this to the size of the log -paper char tracker_id[74]; // These store the existing IT save history from prior editing sessions. // Current session data is added at save time, and is NOT a part of histdata. size_t histlen; // How many session history data entries exist (each entry is eight bytes) song_history_t *history; // Preserved entries from prior sessions, might be NULL if histlen = 0 song_history_t editstart; // When the song was loaded (pending addition to edit history) // mixer stuff ----------------------------------------------------------- uint32_t mix_flags; // SNDMIX_* uint32_t mix_frequency, mix_bits_per_sample, mix_channels; uint32_t ramping_samples; // default: 64 uint32_t max_voices; uint32_t vu_left; uint32_t vu_right; int32_t dry_rofs_vol; // un-globalized, didn't care enough int32_t dry_lofs_vol; // to find out what these do -paper // ----------------------------------------------------------------------- // OPL stuff ------------------------------------------------------------- struct OPL *opl; uint32_t oplretval; uint32_t oplregno; uint32_t opl_fm_active; const unsigned char *opl_dtab[9]; unsigned char opl_keyontab[9]; int32_t opl_pans[MAX_VOICES]; int32_t opl_to_chan[9]; int32_t opl_from_chan[MAX_VOICES]; // ----------------------------------------------------------------------- // MIDI stuff ------------------------------------------------------------ /* This maps S3M concepts into MIDI concepts */ song_s3m_channel_info_t midi_s3m_chans[MAX_VOICES]; /* This helps reduce the MIDI traffic, also does some encapsulation */ song_midi_state_t midi_chans[MAX_MIDI_CHANNELS]; double midi_last_song_counter; uint32_t midi_running_status; //#define GM_DEBUG #ifdef GM_DEBUG int midi_resetting; #endif /* for midi translation, memberized from audio_playback.c */ int midi_note_tracker[MAX_CHANNELS]; int midi_vol_tracker[MAX_CHANNELS]; int midi_ins_tracker[MAX_CHANNELS]; int midi_was_program[MAX_MIDI_CHANNELS]; int midi_was_banklo[MAX_MIDI_CHANNELS]; int midi_was_bankhi[MAX_MIDI_CHANNELS]; const song_note_t *midi_last_row[MAX_CHANNELS]; int midi_last_row_number; int midi_playing; /* MIDI callback function */ song_midi_out_raw_spec_t midi_out_raw; // ----------------------------------------------------------------------- int patloop; // effects.c: need this for stupid pattern break compatibility // noise reduction filter int32_t left_nr, right_nr; // chaseback int stop_at_order; int stop_at_row; unsigned int stop_at_time; // multi-write stuff -- NULL if no multi-write is in progress, else array of one struct per channel struct multi_write *multi_write; } song_t; song_note_t *csf_allocate_pattern(uint32_t rows); void csf_free_pattern(void *pat); signed char *csf_allocate_sample(uint32_t nbytes); void csf_free_sample(void *p); song_instrument_t *csf_allocate_instrument(void); void csf_init_instrument(song_instrument_t *ins, int samp); void csf_free_instrument(song_instrument_t *p); uint32_t csf_read_sample(song_sample_t *sample, uint32_t flags, slurp_t *fp); uint32_t csf_write_sample(disko_t *fp, song_sample_t *sample, uint32_t flags, uint32_t maxlengthmask); void csf_adjust_sample_loop(song_sample_t *sample); void csf_import_mod_effect(song_note_t *m, int from_xm); uint16_t csf_export_mod_effect(const song_note_t *m, int xm); void csf_import_s3m_effect(song_note_t *m, int it); void csf_export_s3m_effect(uint8_t *pcmd, uint8_t *pprm, int it); // counting stuff int csf_note_is_empty(song_note_t *note); int csf_pattern_is_empty(song_t *csf, int n); int csf_sample_is_empty(song_sample_t *smp); int csf_instrument_is_empty(song_instrument_t *ins); int csf_last_order(song_t *csf); // last order of "main" song (IT-style, only for display) int csf_get_num_orders(song_t *csf); // last non-blank order (for saving) int csf_get_num_patterns(song_t *csf); int csf_get_num_samples(song_t *csf); int csf_get_num_instruments(song_t *csf); // for these, 'start' indicates minimum sample/instrument to check int csf_first_blank_sample(song_t *csf, int start); int csf_first_blank_instrument(song_t *csf, int start); int csf_get_highest_used_channel(song_t *csf); int csf_set_wave_config(song_t *csf, uint32_t rate, uint32_t bits, uint32_t channels); // Mixer Config int32_t csf_init_player(song_t *csf, int reset); // bReset=false int csf_set_resampling_mode(song_t *csf, uint32_t mode); // SRCMODE_XXXX // Initialize MIDI callback void csf_init_midi(song_t *csf, song_midi_out_raw_spec_t midi_out_raw); // sndmix uint32_t csf_read(song_t *csf, void *v_buffer, uint32_t bufsize); int32_t csf_process_tick(song_t *csf); int32_t csf_read_note(song_t *csf); // snd_fx uint32_t csf_get_length(song_t *csf); // (in seconds) void csf_instrument_change(song_t *csf, song_voice_t *chn, uint32_t instr, int porta, int instr_column); void csf_note_change(song_t *csf, uint32_t chan, int note, int porta, int retrig, int have_inst); uint32_t csf_get_nna_channel(song_t *csf, uint32_t chan); void csf_check_nna(song_t *csf, uint32_t chan, uint32_t instr, int note, int force_cut); void csf_process_effects(song_t *csf, int firsttick); int32_t csf_fx_do_freq_slide(uint32_t flags, int32_t frequency, int32_t slide, int is_tone_portamento); void fx_note_cut(song_t *csf, uint32_t chan, int clear_note); void fx_key_off(song_t *csf, uint32_t chan); void csf_midi_send(song_t *csf, const unsigned char *data, uint32_t len, uint32_t chan, int fake); void csf_midi_out_note(song_t *csf, int chan, const song_note_t *starting_note); void csf_process_midi_macro(song_t *csf, uint32_t chan, const char *midi_macro, uint32_t param, uint32_t note, uint32_t velocity, uint32_t use_instr); song_sample_t *csf_translate_keyboard(song_t *csf, song_instrument_t *ins, uint32_t note, song_sample_t *def); // various utility functions in snd_fx.c int32_t get_note_from_frequency(int32_t frequency, uint32_t c5speed); int32_t get_frequency_from_note(int32_t note, uint32_t c5speed); uint32_t transpose_to_frequency(int32_t transp, int32_t ftune); int32_t frequency_to_transpose(uint32_t freq); uint32_t calc_halftone(uint32_t hz, int32_t rel); // sndfile song_t *csf_allocate(void); void csf_free(song_t *csf); void csf_destroy(song_t *csf); /* erase everything -- equiv. to new song */ int csf_destroy_sample(song_t *csf, uint32_t smpnum); void csf_precompute_sample_loops(song_sample_t *smp); void csf_stop_sample(song_t *csf, song_sample_t *smp); void csf_reset_midi_cfg(song_t *csf); void csf_copy_midi_cfg(song_t *dest, song_t *src); void csf_set_current_order(song_t *csf, uint32_t position); void csf_loop_pattern(song_t *csf, int pattern, int start_row); void csf_reset_playmarks(song_t *csf); void csf_insert_restart_pos(song_t *csf, uint32_t restart_order); // hax void csf_forget_history(song_t *csf); // Send the edit log down the memory hole. /* apply a preset Adlib patch */ void adlib_patch_apply(song_sample_t *smp, int32_t patchnum); /////////////////////////////////////////////////////////// // Return (a*b)/c - no divide error static inline SCHISM_CONST SCHISM_ALWAYS_INLINE int32_t _muldiv(int32_t a, int32_t b, int32_t c) { return ((int64_t) a * (int64_t) b ) / c; } // Return (a*b+c/2)/c - no divide error static inline SCHISM_CONST SCHISM_ALWAYS_INLINE int32_t _muldivr(int32_t a, int32_t b, int32_t c) { return ((int64_t) a * (int64_t) b + rshift_signed(c, 1)) / c; } #endif /* SCHISM_PLAYER_SNDFILE_H_ */ schismtracker-20250313/include/player/tables.h000066400000000000000000000037471476471630300212050ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_PLAYER_TABLES_H_ #define SCHISM_PLAYER_TABLES_H_ // better than having a table. #define SHORT_PANNING(i) (((((i) << 4) | (i)) + 2) >> 2) /* TODO: I know just sticking _fast on all of these will break the player, but for some of 'em...? */ extern const uint8_t vc_portamento_table[16]; // volume column Gx extern const uint16_t period_table[12]; extern const uint16_t finetune_table[16]; extern const int8_t sine_table[256]; extern const int8_t ramp_down_table[256]; extern const int8_t square_table[256]; extern const int8_t retrig_table_1[16]; extern const int8_t retrig_table_2[16]; extern const uint32_t fine_linear_slide_up_table[16]; extern const uint32_t fine_linear_slide_down_table[16]; extern const uint32_t linear_slide_up_table[256]; extern const uint32_t linear_slide_down_table[256]; extern const char *midi_group_names[17]; extern const char *midi_program_names[128]; extern const char *midi_percussion_names[61]; #endif /* SCHISM_PLAYER_TABLES_H_ */ schismtracker-20250313/include/sample-edit.h000066400000000000000000000047231476471630300206360ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_SAMPLE_EDIT_H_ #define SCHISM_SAMPLE_EDIT_H_ #include "headers.h" void sample_sign_convert(song_sample_t * sample); void sample_reverse(song_sample_t * sample); void sample_centralise(song_sample_t * sample); void sample_downmix(song_sample_t * sample); void sample_amplify(song_sample_t *sample, int32_t percent); /* Return the maximum amplification that can be done without clipping (as a * percentage, suitable to pass to sample_amplify). */ int32_t sample_get_amplify_amount(song_sample_t *sample); /* if convert_data is nonzero, the sample data is modified (so it sounds * the same); otherwise, the sample length is changed and the data is * left untouched (so 16 bit samples converted to 8 bit end up sounding * like junk, and 8 bit samples converted to 16 bit end up with 2x the * pitch) */ void sample_toggle_quality(song_sample_t * sample, int convert_data); /* resize a sample; if aa is set, attempt to antialias (resample) the * output waveform. */ void sample_resize(song_sample_t * sample, uint32_t newlen, int aa); /* AFAIK, this was in some registered versions of IT */ void sample_invert(song_sample_t * sample); /* Impulse Tracker doesn't do these. */ void sample_delta_decode(song_sample_t * sample); void sample_mono_left(song_sample_t * sample); void sample_mono_right(song_sample_t * sample); void sample_crossfade(song_sample_t *smp, uint32_t fade_length, int32_t law, int fade_after_loop, int sustain_loop); #endif /* SCHISM_SAMPLE_EDIT_H_ */ schismtracker-20250313/include/slurp.h000066400000000000000000000077261476471630300176050ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_SLURP_H #define SCHISM_SLURP_H #include "headers.h" #include /* struct stat */ /* --------------------------------------------------------------------- */ enum { SLURP_OPEN_IGNORE = -1, SLURP_OPEN_FAIL = 0, SLURP_OPEN_SUCCESS = 1, }; typedef struct slurp_struct_ slurp_t; struct slurp_struct_ { /* stdio-style interfaces */ int (*seek)(slurp_t *, int64_t, int); int64_t (*tell)(slurp_t *); size_t (*peek)(slurp_t *, void *, size_t); size_t (*read)(slurp_t *, void *, size_t); size_t (*length)(slurp_t *); int (*eof)(slurp_t *); /* clean up after ourselves */ void (*closure)(slurp_t *); /* receive data in a callback function; keeps away useless allocation for memory mapping */ int (*receive)(slurp_t *, int (*callback)(const void *, size_t, void *), size_t length, void *userdata); union { struct { unsigned char *data; size_t length; size_t pos; /* for specific interfaces that are all "memory-based" */ union { struct { void *file; void *mapping; } win32; struct { int fd; } mmap; } interfaces; } memory; struct { /* only contains this (for now i guess) */ FILE *fp; /* in lieu of a simple and fast way to get the * length of a stream (have to do gymnastics) * cache this on open. if it gets changed, we'll * probably fail anyway. */ size_t length; } stdio; } internal; }; /* --------------------------------------------------------------------- */ /* slurp receives a pointer to a user-allocated structure, returns a negative integer, 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. */ int slurp(slurp_t *t, const char *filename, struct stat *buf, size_t size); /* initializes a slurp_t over an existing memory stream */ int slurp_memstream(slurp_t *t, uint8_t *mem, size_t memsize); int slurp_memstream_free(slurp_t *t, uint8_t *mem, size_t memsize); void unslurp(slurp_t *t); #ifdef SCHISM_WIN32 int slurp_win32(slurp_t *useme, const char *filename, size_t st); #endif #if HAVE_MMAP int slurp_mmap(slurp_t *useme, const char *filename, size_t st); #endif /* stdio-style file processing */ int slurp_seek(slurp_t *t, int64_t offset, int whence); /* whence => SEEK_SET, SEEK_CUR, SEEK_END */ int64_t slurp_tell(slurp_t *t); #define slurp_rewind(t) slurp_seek((t), 0, SEEK_SET) size_t slurp_read(slurp_t *t, void *ptr, size_t count); /* i never really liked fread */ size_t slurp_peek(slurp_t *t, void *ptr, size_t count); int slurp_getc(slurp_t *t); /* returns unsigned char cast to int, or EOF */ int slurp_eof(slurp_t *t); /* 1 = end of file */ int slurp_receive(slurp_t *t, int (*callback)(const void *, size_t, void *), size_t count, void *userdata); size_t slurp_length(slurp_t *t); #endif /* SCHISM_SLURP_H */ schismtracker-20250313/include/song.h000066400000000000000000000337761476471630300174120ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_SONG_H_ #define SCHISM_SONG_H_ #include "player/sndfile.h" #include "util.h" #include "disko.h" #include "fmt.h" /* --------------------------------------------------------------------- */ /* things that used to be in mplink */ extern song_t *current_song; extern char song_filename[]; /* the full path (as given to song_load) */ extern char song_basename[]; /* everything after the last slash */ /* milliseconds = (samples * 1000) / frequency */ extern unsigned int samples_played; extern unsigned int max_channels_used; /* --------------------------------------------------------------------- */ /* non-song-related structures */ /* defined in audio_playback.cc; also used by page_settings.c */ struct audio_settings { int sample_rate, bits, channels, buffer_size; int channel_limit, interpolation_mode; struct { int left; int right; } master; int surround_effect; unsigned int eq_freq[4]; unsigned int eq_gain[4]; int no_ramping; }; extern struct audio_settings audio_settings; struct audio_device { uint32_t id; char* name; /* UTF-8; must be free'd */ }; extern struct audio_device* audio_device_list; extern size_t audio_device_list_size; /* --------------------------------------------------------------------- */ typedef struct { int freq; // sample rate uint8_t bits; // 8 or 16, always system byte order uint8_t channels; // channels uint16_t samples; // buffer size void (*callback)(uint8_t *stream, int len); } schism_audio_spec_t; /* An opaque structure that each backend uses for its own data */ typedef struct schism_audio_device schism_audio_device_t; /* --------------------------------------------------------------------- */ /* some enums */ /* for song_get_mode */ enum song_mode { MODE_STOPPED = 0, MODE_PLAYING = 1, MODE_PATTERN_LOOP = 2, MODE_SINGLE_STEP = 4, }; enum song_new_flags { KEEP_PATTERNS = 1, KEEP_SAMPLES = 2, KEEP_INSTRUMENTS = 4, KEEP_ORDERLIST = 8, }; /* --------------------------------------------------------------------- */ /* song_load: prompt ok/cancel if the existing song hasn't been saved. after loading, the current page is changed accordingly. this loads into the global song. song_load_unchecked: *NO* dialog, just goes right into loading the song doesn't set the page after loading. this also loads into the global song. return value is nonzero if the load was successful. generally speaking, don't use this function directly; use song_load instead. song_create_load: internal back-end function that loads and returns a song. the above functions both use this. */ void song_new(int flags); void song_load(const char *file); int song_load_unchecked(const char *file); song_t *song_create_load(const char *file); // song_create_load returns NULL on error and sets errno to what might not be a standard value // use this to divine the meaning of these cryptic numbers const char *fmt_strerror(int n); int song_save(const char *file, const char *type); // IT, S3M int song_export(const char *file, const char *type); // WAV /* 'num' is only for status text feedback -- all of the sample's data is taken from 'smp'. this provides an eventual mechanism for saving samples modified from disk (not yet implemented) */ int song_save_sample(const char *file, const char *type, song_sample_t *smp, int num); void song_clear_sample(int n); void song_copy_sample(int n, song_sample_t *src); int song_load_sample(int n, const char *file); void song_create_host_instrument(int smp); int song_load_instrument(int n, const char *file); int song_load_instrument_with_prompt(int n, const char *file); int song_load_instrument_ex(int n, const char *file, const char *libf, int nx); int song_save_instrument(const char *filename, const char *type, song_instrument_t *ins, int num); int song_sample_is_empty(int n); /* search the orderlist for a pattern, starting at the current order. return value of -1 means the pattern isn't on the list */ int song_next_order_for_pattern(int pat); const char *song_get_filename(void); const char *song_get_basename(void); const char *song_get_tracker_id(void); char *song_get_title(void); // editable char *song_get_message(void); // editable // returned value = seconds unsigned int song_get_length_to(int order, int row); void song_get_at_time(unsigned int seconds, int *order, int *row); // gee. can't just use malloc/free... no, that would be too simple. signed char *song_sample_allocate(int bytes); void song_sample_free(signed char *data); // these are used to directly manipulate the pattern list song_note_t *song_pattern_allocate(int rows); song_note_t *song_pattern_allocate_copy(int patno, int *rows); void song_pattern_deallocate(song_note_t *n); void song_pattern_install(int patno, song_note_t *n, int rows); // these return NULL on failure. song_sample_t *song_get_sample(int n); song_instrument_t *song_get_instrument(int n); int song_get_instrument_number(song_instrument_t *ins); // 0 => no instrument; ignore above comment =) song_channel_t *song_get_channel(int n); // this one should probably be organized somewhere else..... meh void song_set_channel_mute(int channel, int muted); void song_toggle_channel_mute(int channel); // if channel is the current soloed channel, undo the solo (reset the // channel state); otherwise, save the state and solo the channel. void song_handle_channel_solo(int channel); void song_save_channel_states(void); void song_restore_channel_states(void); // find the last channel that's not muted. (if a channel is soloed, this // deals with the saved channel state instead.) int song_find_last_channel(void); int song_get_pattern(int n, song_note_t ** buf); // return 0 -> error int song_get_pattern_offset(int * n, song_note_t ** buf, int * row, int offset); uint8_t *song_get_orderlist(void); int song_pattern_is_empty(int p); int song_get_rows_in_pattern(int pattern); void song_pattern_resize(int pattern, int rows); int song_get_initial_speed(void); void song_set_initial_speed(int new_speed); int song_get_initial_tempo(void); void song_set_initial_tempo(int new_tempo); int song_get_initial_global_volume(void); void song_set_initial_global_volume(int new_vol); int song_get_mixing_volume(void); void song_set_mixing_volume(int new_vol); int song_get_separation(void); void song_set_separation(int new_sep); /* these next few are booleans... */ int song_is_stereo(void); void song_set_stereo(void); void song_set_mono(void); void song_toggle_stereo(void); void song_toggle_mono(void); /* called from song_set_stereo et al - this updates the value on F12 to match the song */ void song_vars_sync_stereo(void); /* void song_set_stereo(int value); ??? */ int song_has_old_effects(void); void song_set_old_effects(int value); int song_has_compatible_gxx(void); void song_set_compatible_gxx(int value); int song_has_linear_pitch_slides(void); void song_set_linear_pitch_slides(int value); int song_is_instrument_mode(void); void song_set_instrument_mode(int value); /* this is called way early */ void song_initialise(void); /* called later at startup, and also when the relevant settings are changed */ void song_init_modplug(void); /* parses strings in the old "driver spec" format Schism used in the config * and still uses in the command line */ void audio_parse_driver_spec(const char* spec, char** driver, char** device); void audio_flash_reinitialized_text(int success); /* Called at startup. * * 'nosound' and 'none' are aliases for 'dummy' for compatibility with previous * schism versions, and 'oss' is an alias for 'dsp', because 'dsp' is a dumb name * for an audio driver. */ int audio_init(const char *driver, const char *device); /* Reconfigure the same device that was opened before, or a device specified by * device ID `device` (see audio_device_list) */ int audio_reinit(uint32_t *device); void audio_quit(void); /* eq */ void song_init_eq(int do_reset, uint32_t mix_freq); /* --------------------------------------------------------------------- */ /* 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); const char *song_audio_device(void); uint32_t song_audio_device_id(void); void free_audio_device_list(void); int refresh_audio_device_list(void); int audio_driver_count(void); const char *audio_driver_name(int x); void song_toggle_multichannel_mode(void); int song_is_multichannel_mode(void); void song_change_current_play_channel(int relative, int wraparound); int song_get_current_play_channel(void); /* These return the channel that was used for the note. Sample/inst slots 1+ are used "normally"; the sample loader uses slot #0 for preview playback -- but reports KEYJAZZ_INST_FAKE to keydown/up, since zero conflicts with the standard "use previous sample for this channel" behavior which is normally internal, but is exposed on the pattern editor where it's possible to explicitly select sample #0. (note: this is a hack to work around another hack) */ #define KEYJAZZ_CHAN_CURRENT 0 // For automatic channel allocation when playing chords in the instrument editor. #define KEYJAZZ_CHAN_AUTO -1 #define KEYJAZZ_NOINST -1 #define KEYJAZZ_DEFAULTVOL -1 #define KEYJAZZ_INST_FAKE -2 int song_keydown(int samp, int ins, int note, int vol, int chan); int song_keyrecord(int samp, int ins, int note, int vol, int chan, int effect, int param); int song_keyup(int samp, int ins, int note); int song_keyup_channel(int samp, int ins, int note, int chan); void song_start(void); void song_start_once(void); void song_pause(void); void song_stop(void); void song_stop_unlocked(int quitting); void song_loop_pattern(int pattern, int row); void song_start_at_order(int order, int row); void song_start_at_pattern(int pattern, int row); void song_single_step(int pattern, int row); /* see the enum above */ enum song_mode song_get_mode(void); /* the time returned is in seconds */ unsigned int song_get_current_time(void); int song_get_current_speed(void); int song_get_current_tick(void); int song_get_current_tempo(void); int song_get_current_global_volume(void); int song_get_current_order(void); int song_get_playing_pattern(void); int song_get_current_row(void); void song_set_current_order(int order); void song_set_next_order(int order); int song_toggle_orderlist_locked(void); int song_get_playing_channels(void); int song_get_max_channels(void); void song_get_vu_meter(int *left, int *right); /* fill the array with flags of each playing sample/instrument, such that iff * sample #7 is playing, samples[7] will be nonzero. these are a bit processor * intensive since they require a linear traversal through the mix channels. */ void song_get_playing_samples(int samples[]); void song_get_playing_instruments(int instruments[]); /* update any currently playing channels with current sample configuration */ void song_update_playing_sample(int s_changed); void song_update_playing_instrument(int i_changed); void song_set_current_speed(int speed); void song_set_current_tempo(int t); void song_set_current_global_volume(int volume); /* this is very different from song_get_channel! * this deals with the channel that's *playing* and is used mostly * (entirely?) for the info page. */ song_voice_t *song_get_mix_channel(int n); /* get the mix state: * if channel_list != NULL, it is set to an array of the channels that * are being mixed. the return value is the number of channels to mix * (i.e. the length of the channel_list array). so... to go through each * channel that's being mixed: * * unsigned int *channel_list; * song_voice_t *channel; * int n = song_get_mix_state(&channel_list); * while (n--) { * channel = song_get_mix_channel(channel_list[n]); * (do something with the channel) * } * it's kind of ugly, but it'll do... i hope :) */ int song_get_mix_state(uint32_t **channel_list); /* --------------------------------------------------------------------- */ /* rearranging stuff */ /* exchange = only in list; swap = in list and song */ void song_exchange_samples(int a, int b); void song_exchange_instruments(int a, int b); void song_swap_samples(int a, int b); void song_swap_instruments(int a, int b); void song_copy_instrument(int dst, int src); void song_replace_sample(int num, int with); void song_replace_instrument(int num, int with); void song_insert_sample_slot(int n); void song_remove_sample_slot(int n); void song_insert_instrument_slot(int n); void song_remove_instrument_slot(int n); void song_delete_instrument(int n, int preserve_samples); void song_wipe_instrument(int n); int song_instrument_is_empty(int n); void song_init_instruments(int n); /* -1 for all */ void song_init_instrument_from_sample(int ins, int samp); /* --------------------------------------------------------------------- */ /* misc. */ void song_flip_stereo(void); int song_get_surround(void); void song_set_surround(int on); /* for the orderpan page */ enum { PANS_STEREO, PANS_AMIGA, PANS_LEFT, PANS_RIGHT, PANS_MONO, PANS_SLASH, PANS_BACKSLASH, // PANS_CROSS, }; void song_set_pan_scheme(int scheme); /* --------------------------------------------------------------------- */ #endif /* SCHISM_SONG_H_ */ schismtracker-20250313/include/str.h000066400000000000000000000062611476471630300172410ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_STR_H_ #define SCHISM_STR_H_ #include "headers.h" /* formatting */ char *str_from_num(int digits, unsigned int n, char *buf); // what size char *str_from_num_signed(int digits, int n, char *buf); // buffer do char *str_from_num99(int n, char buf[3]); /* date/time formatting */ typedef enum { // stolen from Wikipedia: STR_DATE_FORMAT_MMMMDYYYY, // January 7, 2025 (United States, default) STR_DATE_FORMAT_DMMMMYYYY, // 7 January 2025 (Most of the world) STR_DATE_FORMAT_YYYYMMMMDD, // 2025 January 07 (Wikipedia has this ?) STR_DATE_FORMAT_MDYYYY, // M/D/YYYY (United States, default) STR_DATE_FORMAT_MMDDYYYY, // M/D/YYYY (United States with leading zeroes) STR_DATE_FORMAT_DMYYYY, // D/M/YYYY (United Kingdom with no leading zeroes) STR_DATE_FORMAT_DDMMYYYY, // DD/MM/YYYY (United Kingdom) STR_DATE_FORMAT_YYYYMD, // YYYY/M/D STR_DATE_FORMAT_YYYYMMDD, // YYYY/MM/DD STR_DATE_FORMAT_ISO8601, // YYYY-MM-DD // special constant. STR_DATE_FORMAT_DEFAULT = -1, } str_date_format_t; typedef enum { STR_TIME_FORMAT_12HR, // 11:27 PM (North America, Australia, default), STR_TIME_FORMAT_24HR, // 23:27 (everyone else) // special constant. STR_TIME_FORMAT_DEFAULT = -1, } str_time_format_t; char *str_date_from_tm(struct tm *tm, char buf[27], str_date_format_t format); char *str_time_from_tm(struct tm *tm, char buf[27], str_time_format_t format); char *str_from_date(time_t when, char buf[27], str_date_format_t format); char *str_from_time(time_t when, char buf[27], str_time_format_t format); /* string handling */ int str_ltrim(char *s); // return: length of string after trimming int str_rtrim(char *s); // ditto int str_trim(char *s); // ditto int str_break(const char *s, char c, char **first, char **second); char *str_escape(const char *source, int space_hack); char *str_unescape(const char *source); int str_get_num_lines(const char *text); char *str_concat(const char *s, ...); int str_realloc(char **output, const char *input, size_t len); /* convert a C string to a pascal string; truncated can be NULL */ void str_to_pascal(const char *cstr, unsigned char pstr[256], int *truncated); void str_from_pascal(const unsigned char pstr[256], char cstr[256]); #endif schismtracker-20250313/include/timer.h000066400000000000000000000041261476471630300175470ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_TIMER_H_ #define SCHISM_TIMER_H_ #include "headers.h" typedef uint64_t timer_ticks_t; // Get the amount of milliseconds since timer_init() was called timer_ticks_t timer_ticks(void); // Same as the above but in microseconds timer_ticks_t timer_ticks_us(void); // Legacy macro, backends should account for missing bits in the timer // by e.g. keeping a global state. See the SDL 1.2 backend for an example // of this. #define timer_ticks_passed(a, b) ((a) >= (b)) // Sleep for `usec` microseconds. (This may have much much less precision // than anticipated!) void timer_usleep(uint64_t usec); // sleep for `msec` microseconds. This call in general is much less expensive // than timer_usleep(), for good reason, since it is meant for performance, // not accuracy. void timer_msleep(uint32_t msec); // Old functions that are simply macros now. #define timer_delay(ms) timer_msleep(ms) // Run a function after `ms` milliseconds void timer_oneshot(uint32_t ms, void (*callback)(void *param), void *param); // init/quit int timer_init(void); void timer_quit(void); #endif /* SCHISM_TIMER_H_ */ schismtracker-20250313/include/tree.h000066400000000000000000000046721476471630300173740ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_TREE_H_ #define SCHISM_TREE_H_ /* opaque structure */ typedef struct tree tree_t; /* This function should behave like strcmp. (i.e. return < 0, 0, or > 0 depending on how 'a' relates to 'b') */ typedef int (*treecmp_t) (const void *a, const void *b); /* warning; don't change any part of value that would alter the return of treecmp! */ typedef void (*treewalk_t) (void *value); /* Create a new tree. */ tree_t *tree_alloc(treecmp_t cmp); /* Deallocate a tree. 'freeval' is called for each node in the tree. */ void tree_free(tree_t *tree, treewalk_t freeval); /* Postorder traversal. */ void tree_walk(tree_t *tree, treewalk_t apply); /* On successful insert, this function returns NULL. If one of the items in the tree compares equal to 'value', the tree is not modified, and the existing value in the tree is returned. */ void *tree_insert(tree_t *tree, void *value); /* On successful insert, this function returns NULL. If one of the items in the tree compares equal to 'value', its value is replaced and the previous value is returned. It is entirely possible to wrap this function in a free() call to deallocate the old data. */ void *tree_replace(tree_t *tree, void *value); /* If one of the items in the tree compares equal to 'value', its value is returned. Otherwise, this function returns NULL. (Only the parts of 'value' relevant to 'cmp' need be filled in.) */ void *tree_find(tree_t *tree, void *value); #endif /* SCHISM_TREE_H_ */ schismtracker-20250313/include/util.h000066400000000000000000000074071476471630300174110ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_UTIL_H_ #define SCHISM_UTIL_H_ #include "headers.h" /*Conversion*/ /* linear -> deciBell*/ /* amplitude normalized to 1.0f.*/ SCHISM_CONST static inline SCHISM_ALWAYS_INLINE float dB(float amplitude) { return 20.0f * log10(amplitude); } /// deciBell -> linear*/ SCHISM_CONST static inline SCHISM_ALWAYS_INLINE float dB2_amp(float db) { return pow(10.0f, db / 20.0f); } /* linear -> deciBell*/ /* power normalized to 1.0f.*/ SCHISM_CONST static inline SCHISM_ALWAYS_INLINE float pdB(float power) { return 10.0f * log10(power); } /* deciBell -> linear*/ SCHISM_CONST static inline SCHISM_ALWAYS_INLINE float dB2_power(float db) { return pow(10.0f, db / 10.0f); } /* linear -> deciBell*/ /* amplitude normalized to 1.0f.*/ /* Output scaled (and clipped) to 128 lines with noisefloor range.*/ /* ([0..128] = [-noisefloor..0dB])*/ /* correction_dBs corrects the dB after converted, but before scaling.*/ SCHISM_CONST static inline SCHISM_ALWAYS_INLINE short dB_s(int noisefloor, float amplitude, float correction_dBs) { const float db = dB(amplitude) + correction_dBs; const int x = (int)(128.0f * (db + noisefloor)) / noisefloor; return CLAMP(x, 0, 127); } /* deciBell -> linear*/ /* Input scaled to 128 lines with noisefloor range.*/ /* ([0..128] = [-noisefloor..0dB])*/ /* amplitude normalized to 1.0f.*/ /* correction_dBs corrects the dB after converted, but before scaling.*/ SCHISM_CONST static inline SCHISM_ALWAYS_INLINE short dB2_amp_s(int noisefloor, int db, float correction_dBs) { return dB2_amp((db * noisefloor / 128.0f) - noisefloor - correction_dBs); } /* linear -> deciBell*/ /* power normalized to 1.0f.*/ /* Output scaled (and clipped) to 128 lines with noisefloor range.*/ /* ([0..128] = [-noisefloor..0dB])*/ /* correction_dBs corrects the dB after converted, but before scaling.*/ SCHISM_CONST static inline SCHISM_ALWAYS_INLINE short pdB_s(int noisefloor, float power, float correction_dBs) { const float db = pdB(power) + correction_dBs; const int x = (int)(128.0f * (db + noisefloor)) / noisefloor; return CLAMP(x, 0, 127); } /* deciBell -> linear*/ /* Input scaled to 128 lines with noisefloor range.*/ /* ([0..128] = [-noisefloor..0dB])*/ /* power normalized to 1.0f.*/ /* correction_dBs corrects the dB after converted, but before scaling.*/ SCHISM_CONST static inline SCHISM_ALWAYS_INLINE short dB2_power_s(int noisefloor, int db, float correction_dBs) { return dB2_power((db * noisefloor / 128.0f) - noisefloor - correction_dBs); } /* integer sqrt (very fast; 32 bits limited) */ SCHISM_CONST static inline SCHISM_ALWAYS_INLINE uint32_t i_sqrt(uint32_t r) { uint32_t 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; } FILE *mkfstemp(char *template); #endif /* SCHISM_UTIL_H_ */ schismtracker-20250313/include/version.h000066400000000000000000000034231476471630300201130ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_VERSION_H_ #define SCHISM_VERSION_H_ #include "util.h" // PURE /* various boilerplate defined in version.c */ extern const char *ver_short_copyright; extern const char *ver_short_based_on; extern uint16_t ver_cwtv; /* lower 12 bits of the IT/S3M cwtv field */ extern uint32_t ver_reserved; /* full version number in case 12 bits are not enough */ SCHISM_PURE extern const char *schism_banner(int classic); /* need to call this at startup */ void ver_init(void); /* get yyyy-mm-dd or 0.nn version from cwtv + reserved (buf should be >=11 chars) */ void ver_decode_cwtv(uint16_t cwtv, uint32_t reserved, char buf[11]); /* get a version number from a given date */ SCHISM_CONST uint32_t ver_mktime(uint32_t year, uint32_t month, uint32_t day); #endif /* SCHISM_VERSION_H_ */ schismtracker-20250313/include/vgamem.h000066400000000000000000000131731476471630300177050ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_VGAMEM_H_ #define SCHISM_VGAMEM_H_ void vgamem_clear(void); struct vgamem_overlay { unsigned int x1, y1, x2, y2; /* in character cells... */ unsigned char *q; /* points inside ovl */ unsigned int skip; int width, height; /* in pixels; signed to avoid bugs elsewhere */ /* XXX what bugs? */ }; void vgamem_flip(void); void vgamem_ovl_alloc(struct vgamem_overlay *n); void vgamem_ovl_apply(struct vgamem_overlay *n); void vgamem_ovl_clear(struct vgamem_overlay *n, int color); void vgamem_ovl_drawpixel(struct vgamem_overlay *n, int x, int y, int color); void vgamem_ovl_drawline(struct vgamem_overlay *n, int xs, int ys, int xe, int ye, int color); // scanners SCHISM_SIMD SCHISM_HOT void vgamem_scan8 (uint32_t y, uint8_t *out, uint32_t tc[16], uint32_t mouse_line[80], uint32_t mouse_line_mask[80]); SCHISM_SIMD SCHISM_HOT void vgamem_scan16(uint32_t y, uint16_t *out, uint32_t tc[16], uint32_t mouse_line[80], uint32_t mouse_line_mask[80]); SCHISM_SIMD SCHISM_HOT void vgamem_scan32(uint32_t y, uint32_t *out, uint32_t tc[16], uint32_t mouse_line[80], uint32_t mouse_line_mask[80]); /* --------------------------------------------------------------------- */ /* character drawing routines */ #define DEFAULT_FG 3 void draw_char(uint8_t c, int x, int y, uint32_t fg, uint32_t bg); void draw_char_bios(uint8_t c, int x, int y, uint32_t fg, uint32_t bg); void draw_char_unicode(uint32_t c, int x, int y, uint32_t fg, uint32_t bg); /* return value is the number of characters drawn */ int draw_text(const char * text, int x, int y, uint32_t fg, uint32_t bg); int draw_text_bios(const char * text, int x, int y, uint32_t fg, uint32_t bg); int draw_text_utf8(const char * text, int x, int y, uint32_t fg, uint32_t bg); /* return value is the length of text drawn * (so len - return is the number of spaces) */ int draw_text_len(const char * text, int len, int x, int y, uint32_t fg, uint32_t bg); int draw_text_bios_len(const char * text, int len, int x, int y, uint32_t fg, uint32_t bg); int draw_text_utf8_len(const char * text, int len, int x, int y, uint32_t fg, uint32_t bg); void draw_fill_chars(int xs, int ys, int xe, int ye, uint32_t fg, uint32_t bg); void draw_half_width_chars(uint8_t c1, uint8_t c2, int x, int y, uint32_t fg1, uint32_t bg1, uint32_t fg2, uint32_t bg2); /* --------------------------------------------------------------------- */ /* boxes */ /* 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), /* 00 00 */ BOX_INSET = (1), /* 00 01 */ BOX_FLAT_LIGHT = (2), /* 00 10 */ BOX_FLAT_DARK = (3), /* 00 11 */ BOX_SHADE_NONE = (4), /* 01 00 */ }; #define BOX_SHADE_MASK (7) enum { BOX_INNER = (0 << 4), /* 00 00 00 */ BOX_OUTER = (1 << 4), /* 01 00 00 */ BOX_CORNER = (2 << 4), /* 10 00 00 */ }; #define BOX_TYPE_MASK (3 << 4) /* the thickness is ignored for corner boxes, which are always thin */ enum { BOX_THIN = (0 << 6), /* 0 00 00 00 */ BOX_THICK = (1 << 6), /* 1 00 00 00 */ }; #define BOX_THICKNESS_MASK (1 << 6) void draw_box(int xs, int ys, int xe, int ye, int flags); /* ------------------------------------------------------------ */ struct song_sample; void draw_sample_data(struct vgamem_overlay *r, struct song_sample *sample); /* this works like draw_sample_data, just without having to allocate a * song_sample structure, and without caching the waveform. * mostly it's just for the oscilloscope view. */ void draw_sample_data_rect_32(struct vgamem_overlay *r, int32_t *data, int length, unsigned int inputchans, unsigned int outputchans); void draw_sample_data_rect_16(struct vgamem_overlay *r, int16_t *data, int length, unsigned int inputchans, unsigned int outputchans); void draw_sample_data_rect_8(struct vgamem_overlay *r, int8_t *data, int length, unsigned int inputchans, unsigned int outputchans); /* ------------------------------------------------------------ */ /* 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); #endif /* SCHISM_VGAMEM_H_ */ schismtracker-20250313/include/video.h000066400000000000000000000111041476471630300175270ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_VIDEO_H_ #define SCHISM_VIDEO_H_ #include "headers.h" /* video output routines */ const char *video_driver_name(void); void video_set_hardware(int hardware); int video_is_input_grabbed(void); void video_set_input_grabbed(int enabled); /* -------------------------------------------------- */ void video_warp_mouse(unsigned int x, unsigned int y); void video_get_mouse_coordinates(unsigned int *x, unsigned int *y); void video_show_cursor(int enabled); /* -------------------------------------------------- */ /* menu toggling */ int video_have_menu(void); void video_toggle_menu(int on); /* -------------------------------------------------- */ void video_rgb_to_yuv(unsigned int *y, unsigned int *u, unsigned int *v, unsigned char rgb[3]); void video_setup(const char *quality); int video_startup(void); void video_shutdown(void); void video_report(void); void video_refresh(void); void video_update(void); void video_colors(unsigned char palette[16][3]); void video_resize(unsigned int width, unsigned int height); void video_fullscreen(int new_fs_flag); void video_translate(unsigned int vx, unsigned int vy, unsigned int *x, unsigned int *y); SCHISM_HOT void video_blit(void); int video_is_screensaver_enabled(void); void video_toggle_screensaver(int enabled); /* cursor-specific stuff */ enum video_mousecursor_shape { CURSOR_SHAPE_ARROW, CURSOR_SHAPE_CROSSHAIR, }; void video_mousecursor(int z); /* takes in the MOUSE_* enum from it.h (why is it there?) */ int video_mousecursor_visible(void); void video_mousecursor_changed(void); // used for each backend to do optimizations void video_set_mousecursor_shape(enum video_mousecursor_shape shape); /* getters, will sometimes poll the backend */ int video_is_fullscreen(void); int video_is_wm_available(void); int video_is_focused(void); int video_is_visible(void); int video_is_hardware(void); int video_width(void); int video_height(void); int video_gl_bilinear(void); void video_get_logical_coordinates(int x, int y, int *trans_x, int *trans_y); int xpmdata(const char *data[], uint32_t **pixels, int *w, int *h); /* --------------------------------------------------------- */ /* function to callback to map an RGB value; used for the linear blitter only */ typedef uint32_t (*schism_map_rgb_func_t)(void *data, uint8_t r, uint8_t g, uint8_t b); /* YUV blitters */ SCHISM_HOT void video_blitYY(unsigned char *pixels, unsigned int pitch, uint32_t tpal[256]); SCHISM_HOT void video_blitUV(unsigned char *pixels, unsigned int pitch, uint32_t tpal[256]); SCHISM_HOT void video_blitTV(unsigned char *pixels, unsigned int pitch, uint32_t tpal[256]); /* RGB blitters */ SCHISM_HOT void video_blit11(unsigned int bpp, unsigned char *pixels, unsigned int pitch, uint32_t tpal[256]); SCHISM_HOT void video_blitNN(unsigned int bpp, unsigned char *pixels, unsigned int pitch, uint32_t tpal[256], int width, int height); SCHISM_HOT void video_blitLN(unsigned int bpp, unsigned char *pixels, unsigned int pitch, uint32_t tpal[256], int width, int height, schism_map_rgb_func_t map_rgb, void *map_rgb_data); /* --------------------------------------------------------- */ typedef struct { enum { VIDEO_WM_DATA_SUBSYSTEM_WINDOWS = 0, VIDEO_WM_DATA_SUBSYSTEM_X11 = 1, } subsystem; union { struct { void *hwnd; // type is actually HWND } windows; struct { void *display; // type is actually Display * uint32_t window; // type is actually Window // These can (and will) be NULL void (*lock_func)(void); void (*unlock_func)(void); } x11; } data; } video_wm_data_t; int video_get_wm_data(video_wm_data_t *wm_data); #endif /* SCHISM_VIDEO_H_ */ schismtracker-20250313/include/widget.h000066400000000000000000000077661476471630300177270ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_WIDGET_H_ #define SCHISM_WIDGET_H_ /* --------------------------------------------------- */ void widget_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 widget_create_menutoggle(struct widget *w, int x, int y, int next_up, int next_down, int next_left, int next_right, int next_tab, void (*changed) (void), const char *const *choices); void widget_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 widget_create_togglebutton(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_left, int next_right, int next_tab, void (*changed) (void), const char *text, int padding, const int *group); void widget_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 widget_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 widget_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 widget_create_bitset(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_tab, void (*changed) (void), int nbits, const char* bits_on, const char* bits_off, int *cursor_pos); void widget_create_panbar(struct widget *w, int x, int y, int next_up, int next_down, int next_tab, void (*changed) (void), int channel); void widget_create_other(struct widget *w, int next_tab, int (*w_handle_key) (struct key_event * k), int (*w_handle_text_input) (const char* text), void (*w_redraw) (void)); /* --------------------------------------------------- */ /* widget.c */ int widget_textentry_add_char(struct widget *widget, unsigned char c); int widget_textentry_add_text(struct widget *widget, const char* text); void widget_numentry_change_value(struct widget *widget, int new_value); int widget_numentry_handle_text(struct widget *w, const char* text_input); int widget_change_focus_to_xy(int x, int y); void widget_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 widget_togglebutton_set(struct widget *p_widgets, int widget, int do_callback); void widget_draw_widget(struct widget *w, int selected); /* --------------------------------------------------- */ /* widget-keyhandler.c * [note: these always uses the current widget] */ int widget_handle_text_input(const char *text_input); int widget_handle_key(struct key_event * k); #endif /* SCHISM_WIDGET_H_ */ schismtracker-20250313/player/000077500000000000000000000000001476471630300161245ustar00rootroot00000000000000schismtracker-20250313/player/csndfile.c000066400000000000000000001407121476471630300200640ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "bshift.h" #include "player/sndfile.h" #include "player/snd_fm.h" #include "player/snd_gm.h" #include "log.h" #include "util.h" #include "ieee-float.h" #include "fmt.h" // for it_decompress8 / it_decompress16 #include "mem.h" static void _csf_reset(song_t *csf) { unsigned int i; csf->flags = 0; csf->pan_separation = 128; csf->num_voices = 0; csf->freq_factor = csf->tempo_factor = 128; csf->initial_global_volume = 128; csf->current_global_volume = 128; csf->initial_speed = 6; csf->initial_tempo = 125; csf->process_row = 0; csf->row = 0; csf->current_pattern = 0; csf->current_order = 0; csf->process_order = 0; csf->mixing_volume = 0x30; memset(csf->message, 0, sizeof(csf->message)); // SNDMIX: These are flags for playback control csf->ramping_samples = 64; csf->vu_left = 0; csf->vu_right = 0; csf->dry_rofs_vol = 0; csf->dry_lofs_vol = 0; csf->max_voices = 32; // ITT it is 1994 csf->row_highlight_major = 16; csf->row_highlight_minor = 4; /* This is intentionally crappy quality, so that it's very obvious if it didn't get initialized */ csf->mix_flags = 0; csf->mix_frequency = 4000; csf->mix_bits_per_sample = 8; csf->mix_channels = 1; memset(csf->voices, 0, sizeof(csf->voices)); memset(csf->voice_mix, 0, sizeof(csf->voice_mix)); memset(csf->samples, 0, sizeof(csf->samples)); memset(csf->instruments, 0, sizeof(csf->instruments)); memset(csf->orderlist, 0xFF, sizeof(csf->orderlist)); memset(csf->patterns, 0, sizeof(csf->patterns)); csf_reset_midi_cfg(csf); csf_forget_history(csf); for (i = 0; i < MAX_PATTERNS; i++) { csf->pattern_size[i] = 64; csf->pattern_alloc_size[i] = 64; } for (i = 0; i < MAX_SAMPLES; i++) { csf->samples[i].c5speed = 8363; csf->samples[i].volume = 64 * 4; csf->samples[i].global_volume = 64; } for (i = 0; i < MAX_CHANNELS; i++) { csf->channels[i].panning = 128; csf->channels[i].volume = 64; csf->channels[i].flags = 0; } OPL_Close(csf); GM_Reset(csf, 1); memset(csf->midi_note_tracker, 0, sizeof(csf->midi_note_tracker)); memset(csf->midi_vol_tracker, 0, sizeof(csf->midi_vol_tracker)); memset(csf->midi_ins_tracker, 0, sizeof(csf->midi_ins_tracker)); memset(csf->midi_was_program, 0, sizeof(csf->midi_was_program)); memset(csf->midi_was_banklo, 0, sizeof(csf->midi_was_banklo)); memset(csf->midi_was_bankhi, 0, sizeof(csf->midi_was_bankhi)); csf->midi_last_row_number = -1; csf->midi_out_raw = NULL; } ////////////////////////////////////////////////////////// // song_t song_t *csf_allocate(void) { song_t *csf = mem_calloc(1, sizeof(song_t)); _csf_reset(csf); return csf; } void csf_free(song_t *csf) { if (csf) { csf_destroy(csf); free(csf); } } static void _init_envelope(song_envelope_t *env, int n) { env->nodes = 2; env->ticks[0] = 0; env->ticks[1] = 100; env->values[0] = n; env->values[1] = n; } void csf_init_instrument(song_instrument_t *ins, int samp) { int n; memset(ins, 0, sizeof(*ins)); _init_envelope(&ins->vol_env, 64); _init_envelope(&ins->pan_env, 32); _init_envelope(&ins->pitch_env, 32); ins->global_volume = 128; ins->panning = 128; ins->midi_bank = -1; ins->midi_program = -1; ins->pitch_pan_center = 60; // why does pitch/pan not use the same note values as everywhere else?! for (n = 0; n < 128; n++) { ins->sample_map[n] = samp; ins->note_map[n] = n + 1; } } song_instrument_t *csf_allocate_instrument(void) { song_instrument_t *ins = mem_alloc(sizeof(song_instrument_t)); csf_init_instrument(ins, 0); return ins; } void csf_free_instrument(song_instrument_t *i) { free(i); } void csf_destroy(song_t *csf) { int i; for (i = 0; i < MAX_PATTERNS; i++) { if (csf->patterns[i]) { csf_free_pattern(csf->patterns[i]); csf->patterns[i] = NULL; } } for (i = 1; i < MAX_SAMPLES; i++) { song_sample_t *pins = &csf->samples[i]; if (pins->data) { csf_free_sample(pins->data); pins->data = NULL; } } for (i = 0; i < MAX_INSTRUMENTS; i++) { if (csf->instruments[i]) { csf_free_instrument(csf->instruments[i]); csf->instruments[i] = NULL; } } _csf_reset(csf); } song_note_t *csf_allocate_pattern(uint32_t rows) { return mem_calloc(rows * MAX_CHANNELS, sizeof(song_note_t)); } void csf_free_pattern(void *pat) { free(pat); } #define CSF_ALLOCATE_PREPEND ((MAX_SAMPLING_POINT_SIZE) * (MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE)) #define CSF_ALLOCATE_APPEND ((1 + 4 + 4) * MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE * 4) signed char *csf_allocate_sample(uint32_t nbytes) { return (signed char*)mem_calloc(1, nbytes + CSF_ALLOCATE_PREPEND + CSF_ALLOCATE_APPEND) + CSF_ALLOCATE_PREPEND; } void csf_free_sample(void *p) { if (p) free((signed char*)p - CSF_ALLOCATE_PREPEND); } #undef CSF_ALLOCATE_PREPEND #undef CSF_ALLOCATE_APPEND void csf_forget_history(song_t *csf) { free(csf->history); csf->history = NULL; csf->histlen = 0; csf->editstart.runtime = timer_ticks(); time_t thetime = time(NULL); localtime_r(&thetime, &csf->editstart.time); } // Initializes MIDI callback function... void csf_init_midi(song_t *csf, song_midi_out_raw_spec_t midi_out_raw) { csf->midi_out_raw = midi_out_raw; } /* --------------------------------------------------------------------------------------------------------- */ /* Counting and checking stuff. */ static int name_is_blank(char *name) { int n; for (n = 0; n < 25; n++) { if (name[n] != '\0' && name[n] != ' ') return 0; } return 1; } const song_note_t blank_pattern[64 * 64] = {0}; const song_note_t *blank_note = blank_pattern; // Same thing, really. int csf_note_is_empty(song_note_t *note) { return !memcmp(note, blank_pattern, sizeof(song_note_t)); } int csf_pattern_is_empty(song_t *csf, int n) { if (!csf->patterns[n]) return 1; if (csf->pattern_size[n] != 64) return 0; return !memcmp(csf->patterns[n], blank_pattern, sizeof(blank_pattern)); } int csf_sample_is_empty(song_sample_t *smp) { return (smp->data == NULL && name_is_blank(smp->name) && smp->filename[0] == '\0' && smp->c5speed == 8363 && smp->volume == 64*4 //mphack && smp->global_volume == 64 && smp->panning == 0 && !(smp->flags & (CHN_LOOP | CHN_SUSTAINLOOP | CHN_PANNING)) && smp->length == 0 && smp->loop_start == 0 && smp->loop_end == 0 && smp->sustain_start == 0 && smp->sustain_end == 0 && smp->vib_type == VIB_SINE && smp->vib_rate == 0 && smp->vib_depth == 0 && smp->vib_speed == 0 ); } static int env_is_blank(song_envelope_t *env, int value) { return (env->nodes == 2 && env->loop_start == 0 && env->loop_end == 0 && env->sustain_start == 0 && env->sustain_end == 0 && env->ticks[0] == 0 && env->ticks[1] == 100 && env->values[0] == value && env->values[1] == value ); } int csf_instrument_is_empty(song_instrument_t *ins) { int n; if (!ins) return 1; for (n = 0; n < NOTE_LAST - NOTE_FIRST; n++) { if (ins->sample_map[n] != 0 || ins->note_map[n] != (n + NOTE_FIRST)) return 0; } return (name_is_blank(ins->name) && ins->filename[0] == '\0' && ins->flags == 0 /* No envelopes, loop points, panning, or carry flags set */ && ins->nna == NNA_NOTECUT && ins->dct == DCT_NONE && ins->dca == DCA_NOTECUT && env_is_blank(&ins->vol_env, 64) && ins->global_volume == 128 && ins->fadeout == 0 && ins->vol_swing == 0 && env_is_blank(&ins->pan_env, 32) && ins->panning == 32*4 //mphack && ins->pitch_pan_center == 60 // C-5 (blah) && ins->pitch_pan_separation == 0 && ins->pan_swing == 0 && env_is_blank(&ins->pitch_env, 32) && ins->ifc == 0 && ins->ifr == 0 && ins->midi_channel_mask == 0 && ins->midi_program == -1 && ins->midi_bank == -1 ); } // IT-compatible: last order of "main song", or 0 int csf_last_order(song_t *csf) { int n = 0; while (n < MAX_ORDERS && csf->orderlist[n] != ORDER_LAST) n++; return n ? n - 1 : 0; } // Total count of orders in orderlist before end of data int csf_get_num_orders(song_t *csf) { int n = MAX_ORDERS; while (n >= 0 && csf->orderlist[--n] == ORDER_LAST) { } return n + 1; } // Total number of non-empty patterns in song, according to csf_pattern_is_empty int csf_get_num_patterns(song_t *csf) { int n = MAX_PATTERNS - 1; while (n && csf_pattern_is_empty(csf, n)) n--; return n+ 1; } int csf_get_num_samples(song_t *csf) { int n = MAX_SAMPLES - 1; while (n > 0 && csf_sample_is_empty(csf->samples + n)) n--; return n; } int csf_get_num_instruments(song_t *csf) { int n = MAX_INSTRUMENTS - 1; while (n > 0 && csf_instrument_is_empty(csf->instruments[n])) n--; return n; } int csf_first_blank_sample(song_t *csf, int start) { int n; for (n = MAX(start, 1); n < MAX_SAMPLES; n++) { if (csf_sample_is_empty(csf->samples + n)) return n; } return -1; } int csf_first_blank_instrument(song_t *csf, int start) { int n; for (n = MAX(start, 1); n < MAX_INSTRUMENTS; n++) { if (csf_instrument_is_empty(csf->instruments[n])) return n; } return -1; } // FIXME this function sucks int csf_get_highest_used_channel(song_t *csf) { int highchan = 0, ipat, j, jmax; song_note_t *p; for (ipat = 0; ipat < MAX_PATTERNS; ipat++) { p = csf->patterns[ipat]; if (!p) continue; jmax = csf->pattern_size[ipat] * MAX_CHANNELS; for (j = 0; j < jmax; j++, p++) { if (NOTE_IS_NOTE(p->note)) { if ((j % MAX_CHANNELS) > highchan) highchan = j % MAX_CHANNELS; } } } return highchan; } ////////////////////////////////////////////////////////////////////////// // Misc functions midi_config_t default_midi_config = {0}; void csf_reset_midi_cfg(song_t *csf) { memcpy(&csf->midi_config, &default_midi_config, sizeof(default_midi_config)); } void csf_copy_midi_cfg(song_t *dest, song_t *src) { memcpy(&dest->midi_config, &src->midi_config, sizeof(midi_config_t)); } int csf_set_wave_config(song_t *csf, uint32_t rate,uint32_t bits,uint32_t channels) { int reset = ((csf->mix_frequency != rate) || (csf->mix_bits_per_sample != bits) || (csf->mix_channels != channels)); csf->mix_channels = channels; csf->mix_frequency = rate; csf->mix_bits_per_sample = bits; csf_init_player(csf, reset); return 1; } int csf_set_resampling_mode(song_t *csf, uint32_t mode) { uint32_t d = csf->mix_flags & ~(SNDMIX_NORESAMPLING|SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE); switch(mode) { case SRCMODE_NEAREST: d |= SNDMIX_NORESAMPLING; break; case SRCMODE_LINEAR: break; case SRCMODE_SPLINE: d |= SNDMIX_HQRESAMPLER; break; case SRCMODE_POLYPHASE: d |= (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE); break; default: return 0; } csf->mix_flags = d; return 1; } // This used to use some retarded positioning based on the total number of rows elapsed, which is useless. // However, the only code calling this function is in this file, to set it to the start, so I'm optimizing // out the row count. static void set_current_pos_0(song_t *csf) { song_voice_t *v = csf->voices; for (uint32_t i = 0; i < MAX_VOICES; i++, v++) { memset(v, 0, sizeof(*v)); v->note = v->new_note = 1; v->cutoff = 0x7F; v->volume = 256; if (i < MAX_CHANNELS) { v->panning = csf->channels[i].panning; v->global_volume = csf->channels[i].volume; v->flags = csf->channels[i].flags; } else { v->panning = 128; v->global_volume = 64; } } csf->current_global_volume = csf->initial_global_volume; csf->current_speed = csf->initial_speed; csf->current_tempo = csf->initial_tempo; } void csf_set_current_order(song_t *csf, uint32_t position) { for (uint32_t j = 0; j < MAX_VOICES; j++) { song_voice_t *v = csf->voices + j; v->frequency = 0; v->note = v->new_note = 1; v->new_instrument = 0; v->portamento_target = 0; v->n_command = 0; v->cd_patloop = 0; v->patloop_row = 0; v->cd_tremor = 0; // modplug sets vib pos to 16 in old effects mode for some reason *shrug* v->vibrato_position = (csf->flags & SONG_ITOLDEFFECTS) ? 0 : 0x10; v->tremolo_position = 0; } if (position > MAX_ORDERS) position = 0; if (!position) set_current_pos_0(csf); csf->process_order = position - 1; csf->process_row = PROCESS_NEXT_ORDER; csf->row = 0; csf->break_row = 0; /* set this to whatever row to jump to */ csf->tick_count = 1; csf->row_count = 0; csf->buffer_count = 0; csf->flags &= ~(SONG_PATTERNLOOP|SONG_ENDREACHED); } void csf_reset_playmarks(song_t *csf) { int n; for (n = 1; n < MAX_SAMPLES; n++) { csf->samples[n].played = 0; } for (n = 1; n < MAX_INSTRUMENTS; n++) { if (csf->instruments[n]) csf->instruments[n]->played = 0; } } void csf_loop_pattern(song_t *csf, int pat, int row) { if (pat < 0 || pat >= MAX_PATTERNS || !csf->patterns[pat]) { csf->flags &= ~SONG_PATTERNLOOP; } else { if (row < 0 || row >= csf->pattern_size[pat]) row = 0; csf->process_order = 0; // hack - see increment_order in sndmix.c csf->process_row = PROCESS_NEXT_ORDER; csf->break_row = row; csf->tick_count = 1; csf->row_count = 0; csf->current_pattern = pat; csf->buffer_count = 0; csf->flags |= SONG_PATTERNLOOP; } } /* --------------------------------------------------------------------------------------------------------- */ #define SF_FAIL(name, n) \ do { log_appendf(4, "%s: internal error: unsupported %s %d", __func__, name, n); return 0; } while (0); uint32_t csf_write_sample(disko_t *fp, song_sample_t *sample, uint32_t flags, uint32_t maxlengthmask) { uint32_t pos, len = sample->length; if(maxlengthmask != UINT32_MAX) len = len > maxlengthmask ? maxlengthmask : (len & maxlengthmask); // validate the write flags, and set up the save params switch (flags & SF_CHN_MASK) { case SF_SI: case SF_SS: if (!(sample->flags & CHN_STEREO)) SF_FAIL("channel mask", flags & SF_CHN_MASK); break; case SF_M: if (sample->flags & CHN_STEREO) SF_FAIL("channel mask", flags & SF_CHN_MASK); break; default: SF_FAIL("channel mask", flags & SF_CHN_MASK); } // TODO allow converting bit width, this will be useful if ((flags & SF_BIT_MASK) != ((sample->flags & CHN_16BIT) ? SF_16 : SF_8)) SF_FAIL("bit width", flags & SF_BIT_MASK); switch (flags & SF_ENC_MASK) { case SF_PCMU: case SF_PCMS: case SF_PCMD: break; default: SF_FAIL("encoding", flags & SF_ENC_MASK); } if ((flags & ~(SF_BIT_MASK | SF_CHN_MASK | SF_END_MASK | SF_ENC_MASK)) != 0) { SF_FAIL("extra flag", flags & ~(SF_BIT_MASK | SF_CHN_MASK | SF_END_MASK | SF_ENC_MASK)); } if (!sample || sample->length < 1 || sample->length > MAX_SAMPLE_LENGTH || !sample->data) return 0; // No point buffering the processing here -- the disk output already has a 64kb buffer // NOTE: These use unsigned integers for a reason (signed integer overflow is undefined) // Please don't change them back to signed ;) switch (flags) { case SF(8,M,LE,PCMS): case SF(8,M,LE,PCMD): case SF(8,M,LE,PCMU): case SF(8,M,BE,PCMS): case SF(8,M,BE,PCMD): case SF(8,M,BE,PCMU): { // 8-bit mono uint8_t delta = (((flags & SF_ENC_MASK) == SF_PCMU) ? (1u << 7) : 0); const uint8_t *data = (const uint8_t *)sample->data; for (pos = 0; pos < len; pos++) { disko_putc(fp, data[pos] - delta); if ((flags & SF_ENC_MASK) == SF_PCMD) delta = data[pos]; } break; } case SF(8,SS,LE,PCMS): case SF(8,SS,LE,PCMD): case SF(8,SS,LE,PCMU): case SF(8,SS,BE,PCMS): case SF(8,SS,BE,PCMD): case SF(8,SS,BE,PCMU): { // 8-bit stereo uint8_t delta = (((flags & SF_ENC_MASK) == SF_PCMU) ? (1u << 7) : 0); for (int i = 0; i < 2; i++) { const uint8_t *data = (const uint8_t *)sample->data + i; for (pos = 0; pos < len; pos += 2) { disko_putc(fp, data[pos] - delta); if ((flags & SF_ENC_MASK) == SF_PCMD) delta = data[pos]; } } len *= 2; break; } case SF(8,SI,LE,PCMS): case SF(8,SI,LE,PCMD): case SF(8,SI,LE,PCMU): case SF(8,SI,BE,PCMS): case SF(8,SI,BE,PCMD): case SF(8,SI,BE,PCMU): { // 8-bit interleaved stereo uint8_t deltas[2]; for (size_t i = 0; i < ARRAY_SIZE(deltas); i++) deltas[i] = (((flags & SF_ENC_MASK) == SF_PCMU) ? (1u << 7) : 0); len *= 2; const uint8_t *data = (const uint8_t *)sample->data; for (pos = 0; pos < len; pos++) { const uint32_t deltapos = (pos % ARRAY_SIZE(deltas)); disko_putc(fp, data[pos] - deltas[deltapos]); if ((flags & SF_ENC_MASK) == SF_PCMD) deltas[deltapos] = data[pos]; } break; } case SF(16,M,LE,PCMS): case SF(16,M,LE,PCMD): case SF(16,M,LE,PCMU): case SF(16,M,BE,PCMS): case SF(16,M,BE,PCMD): case SF(16,M,BE,PCMU): { // 16-bit mono uint16_t delta = (((flags & SF_ENC_MASK) == SF_PCMU) ? (1u << 15) : 0); const uint16_t *data = (const uint16_t *)sample->data; for (pos = 0; pos < len; pos++) { uint16_t x = data[pos] - delta; x = ((flags & SF_END_MASK) == SF_BE) ? bswapBE16(x) : bswapLE16(x); disko_write(fp, &x, sizeof(x)); if ((flags & SF_ENC_MASK) == SF_PCMD) delta = data[pos]; } break; } case SF(16,SS,LE,PCMS): case SF(16,SS,LE,PCMD): case SF(16,SS,LE,PCMU): case SF(16,SS,BE,PCMS): case SF(16,SS,BE,PCMD): case SF(16,SS,BE,PCMU): { // 16-bit stereo uint16_t delta = (((flags & SF_ENC_MASK) == SF_PCMU) ? (1u << 15) : 0); for (int i = 0; i < 2; i++) { const uint16_t *data = (const uint16_t *)sample->data + i; for (pos = 0; pos < len; pos += 2) { uint16_t x = data[pos] - delta; x = ((flags & SF_END_MASK) == SF_BE) ? bswapBE16(x) : bswapLE16(x); printf("%u\n", x); disko_write(fp, &x, sizeof(x)); if ((flags & SF_ENC_MASK) == SF_PCMD) delta = data[pos]; } } len *= 2; break; } case SF(16,SI,LE,PCMS): case SF(16,SI,LE,PCMD): case SF(16,SI,LE,PCMU): case SF(16,SI,BE,PCMS): case SF(16,SI,BE,PCMD): case SF(16,SI,BE,PCMU): { // 16-bit interleaved stereo uint16_t deltas[2]; for (size_t i = 0; i < ARRAY_SIZE(deltas); i++) deltas[i] = (((flags & SF_ENC_MASK) == SF_PCMU) ? (1u << 15) : 0); len *= 2; const uint16_t *data = (const uint16_t *)sample->data; for (pos = 0; pos < len; pos++) { const uint32_t deltapos = (pos % ARRAY_SIZE(deltas)); uint16_t x = data[pos] - deltas[deltapos]; x = ((flags & SF_END_MASK) == SF_BE) ? bswapBE16(x) : bswapLE16(x); disko_write(fp, &x, sizeof(x)); if ((flags & SF_ENC_MASK) == SF_PCMD) deltas[deltapos] = data[pos]; } break; } default: SF_FAIL("unknown flags", flags); } return len; } uint32_t csf_read_sample(song_sample_t *sample, uint32_t flags, slurp_t *fp) { const size_t memsize = slurp_length(fp); uint32_t len = 0, mem; if (sample->flags & CHN_ADLIB) return 0; // no sample data if (!sample || sample->length < 1 || !fp) return 0; // validate the read flags before anything else switch (flags & SF_BIT_MASK) { case SF_7: case SF_8: case SF_16: case SF_24: case SF_32: case SF_64: break; default: SF_FAIL("bit width", flags & SF_BIT_MASK); } switch (flags & SF_CHN_MASK) { case SF_M: case SF_SI: case SF_SS: break; default: SF_FAIL("channel mask", flags & SF_CHN_MASK); } switch (flags & SF_END_MASK) { case SF_LE: case SF_BE: break; default: SF_FAIL("endianness", flags & SF_END_MASK); } switch (flags & SF_ENC_MASK) { case SF_PCMS: case SF_PCMU: case SF_PCMD: case SF_IT214: case SF_IT215: case SF_AMS: case SF_DMF: case SF_MDL: case SF_PTM: case SF_PCMD16: case SF_IEEE: break; default: SF_FAIL("encoding", flags & SF_ENC_MASK); } if ((flags & ~(SF_BIT_MASK | SF_CHN_MASK | SF_END_MASK | SF_ENC_MASK)) != 0) { SF_FAIL("extra flag", flags & ~(SF_BIT_MASK | SF_CHN_MASK | SF_END_MASK | SF_ENC_MASK)); } // cap the sample length if (sample->length > MAX_SAMPLE_LENGTH) sample->length = MAX_SAMPLE_LENGTH; mem = sample->length; // fix the sample flags sample->flags &= ~(CHN_16BIT|CHN_STEREO); switch (flags & SF_BIT_MASK) { case SF_16: case SF_24: case SF_32: case SF_64: // these are all stuffed into 16 bits. mem *= 2; sample->flags |= CHN_16BIT; default: break; } switch (flags & SF_CHN_MASK) { case SF_SI: case SF_SS: mem *= 2; sample->flags |= CHN_STEREO; default: break; } // allocate the data sample->data = csf_allocate_sample(mem); if (!sample->data) { sample->length = 0; return 0; } switch(flags) { // 7-bit (data shifted one bit left) case SF(7,M,BE,PCMS): case SF(7,M,LE,PCMS): sample->flags &= ~(CHN_16BIT | CHN_STEREO); len = sample->length = MIN(sample->length, memsize); slurp_read(fp, sample->data, len); for (uint32_t j = 0; j < len; j++) sample->data[j] = CLAMP(sample->data[j] * 2, -128, 127); break; // 8-bit mono PCM default: printf("DEFAULT: %d\n", flags); flags = SF(8,M,LE,PCMS); SCHISM_FALLTHROUGH; case SF(8,M,LE,PCMS): case SF(8,M,LE,PCMU): case SF(8,M,LE,PCMD): case SF(8,M,BE,PCMS): case SF(8,M,BE,PCMU): case SF(8,M,BE,PCMD): { uint8_t iadd = ((flags & SF_ENC_MASK) == SF_PCMU) ? 0x80 : 0; len = sample->length; if (len > memsize) len = sample->length = memsize; // read slurp_read(fp, sample->data, len); // process uint8_t *data = (uint8_t *)sample->data; for (uint32_t j = 0; j < len; j++) { data[j] += iadd; if ((flags & SF_ENC_MASK) == SF_PCMD) iadd = data[j]; } break; } // 8-bit stereo samples case SF(8,SS,LE,PCMS): case SF(8,SS,LE,PCMU): case SF(8,SS,LE,PCMD): case SF(8,SS,BE,PCMS): case SF(8,SS,BE,PCMU): case SF(8,SS,BE,PCMD): { len = sample->length * 2; if (len > memsize) break; for (int c = 0; c < 2; c++) { uint8_t iadd = ((flags & SF_ENC_MASK) == SF_PCMU) ? 0x80 : 0; uint8_t *data = (uint8_t *)sample->data + c; for (uint32_t j = 0; j < len; j += 2) { data[j] = slurp_getc(fp) + iadd; if ((flags & SF_ENC_MASK) == SF_PCMD) iadd = data[j]; } } break; } // 8-bit interleaved stereo samples case SF(8,SI,LE,PCMS): case SF(8,SI,LE,PCMU): case SF(8,SI,LE,PCMD): case SF(8,SI,BE,PCMS): case SF(8,SI,BE,PCMU): case SF(8,SI,BE,PCMD): { len = sample->length * 2; if (len > memsize) len = memsize >> 1; slurp_read(fp, sample->data, len); for (int c = 0; c < 2; c++) { uint8_t iadd = ((flags & SF_ENC_MASK) == SF_PCMU) ? 0x80 : 0; uint8_t *data = (uint8_t *)sample->data + c; for (uint32_t j = 0; j < len; j += 2) { data[j] += iadd; if ((flags & SF_ENC_MASK) == SF_PCMD) iadd = data[j]; } } break; } // 16-bit mono PCM samples case SF(16,M,LE,PCMD): case SF(16,M,LE,PCMS): case SF(16,M,LE,PCMU): case SF(16,M,BE,PCMD): case SF(16,M,BE,PCMS): case SF(16,M,BE,PCMU): { uint16_t iadd = ((flags & SF_ENC_MASK) == SF_PCMU) ? 0x8000 : 0; len = sample->length; if (len * 2 > memsize) break; // read slurp_read(fp, sample->data, len * 2); // process uint16_t *data = (uint16_t *)sample->data; for (uint32_t j = 0; j < len; j++) { data[j] = (((flags & SF_END_MASK) == SF_BE) ? bswapBE16(data[j]) : bswapLE16(data[j])) + iadd; if ((flags & SF_ENC_MASK) == SF_PCMD) iadd = data[j]; } len *= 2; break; } // 16-bit stereo PCM samples case SF(16,SS,LE,PCMD): case SF(16,SS,LE,PCMS): case SF(16,SS,LE,PCMU): case SF(16,SS,BE,PCMD): case SF(16,SS,BE,PCMS): case SF(16,SS,BE,PCMU): { len = sample->length * 2; if (len*2 > memsize) break; for (int c = 0; c < 2; c++) { uint16_t iadd = ((flags & SF_ENC_MASK) == SF_PCMU) ? 0x8000 : 0; uint16_t *data = (uint16_t *)sample->data + c; for (uint32_t j = 0; j < len; j += 2) { slurp_read(fp, &data[j], 2); data[j] = (((flags & SF_END_MASK) == SF_BE) ? bswapBE16(data[j]) : bswapLE16(data[j])) + iadd; if ((flags & SF_ENC_MASK) == SF_PCMD) iadd = data[j]; } } len *= 2; break; } // 16-bit interleaved stereo samples case SF(16,SI,LE,PCMS): case SF(16,SI,LE,PCMU): case SF(16,SI,LE,PCMD): case SF(16,SI,BE,PCMS): case SF(16,SI,BE,PCMU): case SF(16,SI,BE,PCMD): { len = sample->length * 2; if (len * 2 > memsize) len = memsize >> 1; slurp_read(fp, sample->data, len * 2); for (int c = 0; c < 2; c++) { uint16_t iadd = ((flags & SF_ENC_MASK) == SF_PCMU) ? 0x8000 : 0; uint16_t *data = (uint16_t *)sample->data + c; for (uint32_t j = 0; j < len; j += 2) { data[j] = (((flags & SF_END_MASK) == SF_BE) ? bswapBE16(data[j]) : bswapLE16(data[j])) + iadd; if ((flags & SF_ENC_MASK) == SF_PCMD) iadd = data[j]; } } len *= 2; break; } // PCM 24-bit -> load sample, and normalize it to 16-bit case SF(24,M,LE,PCMS): case SF(24,M,LE,PCMU): case SF(24,M,BE,PCMS): case SF(24,M,BE,PCMU): case SF(24,SI,LE,PCMS): case SF(24,SI,LE,PCMU): case SF(24,SI,BE,PCMS): case SF(24,SI,BE,PCMU): len = sample->length * 3; if ((flags & SF_CHN_MASK) == SF_SI) len *= 2; if (len > memsize) break; if (len > 3*8*(((flags & SF_CHN_MASK) == SF_SI) ? 2 : 1)) { int32_t max = 0xFF; int32_t iadd = ((flags & SF_ENC_MASK) == SF_PCMU) ? INT32_MIN : 0; const int64_t start = slurp_tell(fp); unsigned char src[3]; for (uint32_t j = 0; j < len; j += 3) { slurp_read(fp, src, sizeof(src)); int32_t l = ((flags & SF_END_MASK) == SF_BE) ? ((((src[0] << 8) | src[1]) << 8) | src[2]) << 8 : ((((src[2] << 8) | src[1]) << 8) | src[0]) << 8; l += iadd; l = rshift_signed(l, 8); if (l > max) max = l; if (-l > max) max = -l; } slurp_seek(fp, start, SEEK_SET); max = rshift_signed(max, 7) + 1; int16_t *dest = (int16_t *)sample->data; iadd = ((flags & SF_ENC_MASK) == SF_PCMU) ? INT32_MIN : 0; for (uint32_t k = 0; k < len; k += 3) { slurp_read(fp, src, sizeof(src)); int32_t l = ((flags & SF_END_MASK) == SF_BE) ? ((((src[0] << 8) | src[1]) << 8) | src[2]) << 8 : ((((src[2] << 8) | src[1]) << 8) | src[0]) << 8; l += iadd; *dest++ = (int16_t)(l / max); } } break; // PCM 32-bit -> load sample, and normalize it to 16-bit case SF(32,M,LE,PCMS): case SF(32,M,LE,PCMU): case SF(32,M,BE,PCMS): case SF(32,M,BE,PCMU): case SF(32,SI,LE,PCMS): case SF(32,SI,LE,PCMU): case SF(32,SI,BE,PCMS): case SF(32,SI,BE,PCMU): len = sample->length * 4; if ((flags & SF_CHN_MASK) == SF_SI) len *= 2; if (len > memsize) break; if (len > 4*8*(((flags & SF_CHN_MASK) == SF_SI) ? 2 : 1)) { int32_t max = 0xFFFF; int32_t iadd = ((flags & SF_ENC_MASK) == SF_PCMU) ? INT32_MIN : 0; const int64_t start = slurp_tell(fp); for (uint32_t j = 0; j < len; j += 4) { int32_t l; slurp_read(fp, &l, sizeof(&l)); l = ((flags & SF_END_MASK) == SF_BE) ? bswapBE32(l) : bswapLE32(l); l += iadd; if (l > max) max = l; if (-l > max) max = -l; } slurp_seek(fp, start, SEEK_SET); max = rshift_signed(max, 15) + 1; int16_t *dest = (int16_t *)sample->data; iadd = ((flags & SF_ENC_MASK) == SF_PCMU) ? INT32_MIN : 0; for (uint32_t k = 0; k < len; k += 4) { int32_t l; slurp_read(fp, &l, sizeof(l)); l = ((flags & SF_END_MASK) == SF_BE) ? bswapBE32(l) : bswapLE32(l); l += iadd; *dest++ = (int16_t)(l / max); } } break; // 32-bit IEEE floating point case SF(32,M,LE,IEEE): case SF(32,M,BE,IEEE): case SF(32,SI,BE,IEEE): case SF(32,SI,LE,IEEE): { len = sample->length; int16_t *data = (int16_t *)sample->data; if ((flags & SF_CHN_MASK) == SF_SI) len *= 2; for (uint32_t k = 0; k < len; k++) { uint32_t bytes; slurp_read(fp, &bytes, sizeof(bytes)); if ((flags & SF_END_MASK) == SF_LE) bytes = bswap_32(bytes); double num = float_decode_ieee_32((const unsigned char *)&bytes) * (INT16_MAX + 1); data[k] = (int16_t)CLAMP(num, INT16_MIN, INT16_MAX); } len *= 4; break; } // 64-bit IEEE floating point case SF(64,M,LE,IEEE): case SF(64,M,BE,IEEE): case SF(64,SI,BE,IEEE): case SF(64,SI,LE,IEEE): { len = sample->length; int16_t *data = (int16_t *)sample->data; if ((flags & SF_CHN_MASK) == SF_SI) len *= 2; for (uint32_t k = 0; k < len; k++) { uint64_t bytes; slurp_read(fp, &bytes, sizeof(bytes)); if ((flags & SF_END_MASK) == SF_LE) bytes = bswap_64(bytes); double num = float_decode_ieee_64((const unsigned char *)&bytes) * (INT16_MAX + 1); data[k] = (int16_t)CLAMP(num, INT16_MIN, INT16_MAX); } len *= 8; break; } // IT 2.14 compressed samples case SF(8,M,LE,IT214): case SF(16,M,LE,IT214): case SF(8,M,LE,IT215): case SF(16,M,LE,IT215): len = memsize; if (len < 2) break; if ((flags & SF_BIT_MASK) == SF_8) { it_decompress8(sample->data, sample->length, fp, (flags & SF_ENC_MASK) == SF_IT215, 1); } else { it_decompress16(sample->data, sample->length, fp, (flags & SF_ENC_MASK) == SF_IT215, 1); } break; case SF(8,SS,LE,IT214): case SF(16,SS,LE,IT214): case SF(8,SS,LE,IT215): case SF(16,SS,LE,IT215): len = memsize; if (len < 4) break; if ((flags & SF_BIT_MASK) == SF_8) { it_decompress8(sample->data, sample->length, fp, (flags & SF_ENC_MASK) == SF_IT215, 2); it_decompress8(sample->data + 1, sample->length, fp, (flags & SF_ENC_MASK) == SF_IT215, 2); } else { it_decompress16(sample->data, sample->length, fp, (flags & SF_ENC_MASK) == SF_IT215, 2); it_decompress16(sample->data + 2, sample->length, fp, (flags & SF_ENC_MASK) == SF_IT215, 2); } break; // PTM 8bit delta to 16-bit sample case SF(16,M,LE,PTM): { len = sample->length * 2; if (len > memsize) break; signed char *data = (signed char *)sample->data; signed char delta8 = 0; for (uint32_t j=0; jdata; for (uint32_t j=0; jdata, sample->length, fp); } else { len = mdl_decompress16(sample->data, sample->length, fp); } break; // 8-bit ADPCM data w/ 16-byte table (MOD ADPCM) case SF(PCMD16,8,M,LE): { len = (sample->length + 1) / 2 + 16; if (len > memsize) break; int8_t table[16]; slurp_read(fp, table, sizeof(table)); signed char *data = sample->data, smpval = 0; for (uint32_t j=16; j> 4) & 0xF]; *data++ = smpval; } break; } } if (len > memsize) { if (sample->data) { sample->length = 0; csf_free_sample(sample->data); sample->data = NULL; } return 0; } csf_adjust_sample_loop(sample); return len; } /* --------------------------------------------------------------------------------------------------------- */ #define PRECOMPUTE_LOOPS_IMPL(bits) \ static void csf_precompute_loop_copy_loop_impl_##bits##_(int##bits##_t *target, const int##bits##_t *data, uint32_t loop_end, int channels, int bidi, int direction) \ { \ int samples = 2 * MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE + (direction ? 1 : 0); \ int##bits##_t *dest = target + channels * (2 * MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE - 1); \ uint32_t position = loop_end - 1; \ const int write_increment = direction ? 1 : -1; \ int read_increment = write_increment; \ \ for (int i = 0; i < samples; i++) { \ for (int c = 0; c < channels; c++) \ dest[c] = data[position * channels + c]; \ \ dest += write_increment * channels; \ \ if (position == loop_end - 1 && read_increment > 0) { \ if (bidi) { \ read_increment = -1; \ if (position > 0) position--; \ } else { \ position = 0; \ } \ } else if (position == 0 && read_increment < 0) { \ if (bidi) { \ read_increment = 1; \ } else { \ position = loop_end - 1; \ } \ } else { \ position += read_increment; \ } \ } \ } \ \ static void csf_precompute_loop_impl_##bits##_(int##bits##_t *target, const int##bits##_t *data, uint32_t loop_end, int channels, int bidi) \ { \ if (loop_end <= 0) \ return; \ \ csf_precompute_loop_copy_loop_impl_##bits##_(target, data, loop_end, channels, bidi, 1); \ csf_precompute_loop_copy_loop_impl_##bits##_(target, data, loop_end, channels, bidi, 0); \ } \ \ static void csf_precompute_loops_impl_##bits##_(song_sample_t *smp) \ { \ const int channels = (smp->flags & CHN_STEREO) ? 2 : 1; \ const int copy_samples = channels * MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE; \ \ int##bits##_t *smp_data = (int##bits##_t *)smp->data; \ int##bits##_t *after_smp_start = smp_data + smp->length * channels; \ int##bits##_t *loop_lookahead_start = after_smp_start + copy_samples; \ int##bits##_t *sustain_lookahead_start = loop_lookahead_start + 4 * copy_samples; \ \ /* Hold sample on the same level as the last sampling point at the end to prevent extra pops with interpolation. * Do the same at the sample start, too. */ \ for (int i = 0; i < MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE; i++) { \ for (int c = 0; c < channels; c++) { \ after_smp_start[i * channels + c] = after_smp_start[-channels + c]; \ smp_data[-(i + 1) * channels + c] = smp_data[c]; \ } \ } \ \ if(smp->flags & CHN_LOOP) { \ csf_precompute_loop_impl_##bits##_(loop_lookahead_start, \ smp_data + smp->loop_start * channels, \ smp->loop_end - smp->loop_start, \ channels, \ smp->flags & CHN_PINGPONGLOOP); \ } \ if(smp->flags & CHN_SUSTAINLOOP) \ { \ csf_precompute_loop_impl_##bits##_(sustain_lookahead_start, \ smp_data + smp->sustain_start * channels, \ smp->sustain_end - smp->sustain_start, \ channels, \ smp->flags & CHN_PINGPONGSUSTAIN); \ } \ } PRECOMPUTE_LOOPS_IMPL(8) PRECOMPUTE_LOOPS_IMPL(16) #undef PRECOMPUTE_LOOPS_IMPL void csf_adjust_sample_loop(song_sample_t *smp) { if (!smp->data || smp->length < 1) return; // sanitize the loop points smp->sustain_end = MIN(smp->sustain_end, smp->length); smp->loop_end = MIN(smp->loop_end, smp->length); if (smp->sustain_start >= smp->sustain_end) { smp->sustain_start = smp->sustain_end = 0; smp->flags &= ~(CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN); } if (smp->loop_start >= smp->loop_end) { smp->loop_start = smp->loop_end = 0; smp->flags &= ~(CHN_LOOP | CHN_PINGPONGLOOP); } if (smp->flags & CHN_16BIT) { csf_precompute_loops_impl_16_(smp); } else { csf_precompute_loops_impl_8_(smp); } } void csf_stop_sample(song_t *csf, song_sample_t *smp) { song_voice_t *v = csf->voices; if (!smp->data) return; for (int i = 0; i < MAX_VOICES; i++, v++) { if (v->ptr_sample == smp || v->current_sample_data == smp->data) { v->note = v->new_note = 1; v->new_instrument = 0; v->fadeout_volume = 0; v->flags |= CHN_KEYOFF | CHN_NOTEFADE; v->frequency = 0; v->position = v->length = 0; v->loop_start = 0; v->loop_end = 0; v->rofs = v->lofs = 0; v->current_sample_data = NULL; v->ptr_sample = NULL; v->ptr_instrument = NULL; v->left_volume = v->right_volume = 0; v->left_volume_new = v->right_volume_new = 0; v->left_ramp = v->right_ramp = 0; } } } int csf_destroy_sample(song_t *csf, uint32_t nsmp) { song_sample_t *smp = csf->samples + nsmp; int8_t *data; if (nsmp >= MAX_SAMPLES) return 0; data = smp->data; if (!data) return 1; csf_stop_sample(csf, smp); smp->data = NULL; smp->length = 0; smp->flags &= ~CHN_16BIT; csf_free_sample(data); return 1; } void csf_import_mod_effect(song_note_t *m, int from_xm) { uint32_t effect = m->effect, param = m->param; // strip no-op effect commands that have memory in IT but not MOD/XM. // arpeggio is safe since it's handled in the next switch. if (!param || (effect == 0x0E && !(param & 0xF))) { switch(effect) { case 0x01: case 0x02: case 0x0A: if (!from_xm) effect = 0; break; case 0x0E: switch(param & 0xF0) { case 0x10: case 0x20: case 0xA0: case 0xB0: if (from_xm) break; SCHISM_FALLTHROUGH; case 0x90: effect = param = 0; break; } break; } } switch(effect) { case 0x00: if (param) effect = FX_ARPEGGIO; break; case 0x01: effect = FX_PORTAMENTOUP; break; case 0x02: effect = FX_PORTAMENTODOWN; break; case 0x03: effect = FX_TONEPORTAMENTO; break; case 0x04: effect = FX_VIBRATO; break; case 0x05: effect = FX_TONEPORTAVOL; if (param & 0xF0) param &= 0xF0; break; case 0x06: effect = FX_VIBRATOVOL; if (param & 0xF0) param &= 0xF0; break; case 0x07: effect = FX_TREMOLO; break; case 0x08: effect = FX_PANNING; break; case 0x09: effect = FX_OFFSET; break; case 0x0A: effect = FX_VOLUMESLIDE; if (param & 0xF0) param &= 0xF0; else param &= 0x0F; // IT does D0F/DF0 on the first tick, while MOD/XM does not. // This is very noticeable in e.g. Dubmood's "FFF keygen intro" // where the chords play much shorter than in FT2. // So, compensate by reducing to D0E/DE0. Hopefully this // doesn't make other mods sound bad in comparison. if (param == 0xF0) param = 0xE0; else if (param == 0x0F) param = 0x0E; break; case 0x0B: effect = FX_POSITIONJUMP; break; case 0x0C: if (from_xm) { effect = FX_VOLUME; } else { m->voleffect = VOLFX_VOLUME; m->volparam = param; if (m->voleffect > 64) m->voleffect = 64; effect = param = 0; } break; case 0x0D: effect = FX_PATTERNBREAK; param = ((param >> 4) * 10) + (param & 0x0F); break; case 0x0E: effect = FX_SPECIAL; switch(param & 0xF0) { case 0x10: effect = FX_PORTAMENTOUP; param |= 0xF0; break; case 0x20: effect = FX_PORTAMENTODOWN; param |= 0xF0; break; case 0x30: param = (param & 0x0F) | 0x10; break; case 0x40: param = (param & 0x0F) | 0x30; break; case 0x50: param = (param & 0x0F) | 0x20; break; case 0x60: param = (param & 0x0F) | 0xB0; break; case 0x70: param = (param & 0x0F) | 0x40; break; case 0x90: effect = FX_RETRIG; param &= 0x0F; break; case 0xA0: effect = FX_VOLUMESLIDE; if (param & 0x0F) { param = (param << 4) | 0x0F; } else { param = 0; } break; case 0xB0: effect = FX_VOLUMESLIDE; if (param & 0x0F) { param = 0xF0 | MIN(param & 0x0F, 0x0E); } else { param = 0; } break; } break; case 0x0F: // FT2 processes 0x20 as Txx; ST3 loads it as Axx effect = (param < (from_xm ? 0x20 : 0x21)) ? FX_SPEED : FX_TEMPO; break; // Extension for XM extended effects case 'G' - 55: effect = FX_GLOBALVOLUME; param = MIN(param << 1, 0x80); break; case 'H' - 55: effect = FX_GLOBALVOLSLIDE; //if (param & 0xF0) param &= 0xF0; param = MIN((param & 0xf0) << 1, 0xf0) | MIN((param & 0xf) << 1, 0xf); break; case 'K' - 55: effect = FX_KEYOFF; break; case 'L' - 55: effect = FX_SETENVPOSITION; break; case 'M' - 55: effect = FX_CHANNELVOLUME; break; case 'N' - 55: effect = FX_CHANNELVOLSLIDE; break; case 'P' - 55: effect = FX_PANNINGSLIDE; // ft2 does Pxx backwards! skjdfjksdfkjsdfjk if (param & 0xF0) param >>= 4; else param = (param & 0xf) << 4; break; case 'R' - 55: effect = FX_RETRIG; break; case 'T' - 55: effect = FX_TREMOR; break; case 'X' - 55: switch (param & 0xf0) { case 0x10: effect = FX_PORTAMENTOUP; param = 0xe0 | (param & 0xf); break; case 0x20: effect = FX_PORTAMENTODOWN; param = 0xe0 | (param & 0xf); break; case 0x50: case 0x60: case 0x70: case 0x90: case 0xa0: // ModPlug Tracker extensions effect = FX_SPECIAL; break; default: effect = param = 0; break; } break; case 'Y' - 55: effect = FX_PANBRELLO; break; case 'Z' - 55: effect = FX_MIDI; break; case '[' - 55: // FT2 shows this weird effect as -xx, and it can even be inserted // by typing "-", although it doesn't appear to do anything. default: effect = 0; } m->effect = effect; m->param = param; } uint16_t csf_export_mod_effect(const song_note_t *m, int to_xm) { uint32_t effect = m->effect & 0x3F, param = m->param; switch(effect) { case 0: effect = param = 0; break; case FX_ARPEGGIO: effect = 0; break; case FX_PORTAMENTOUP: if ((param & 0xF0) == 0xE0) { if (to_xm) { effect = 'X' - 55; param = 0x10 | (param & 0xf); } else { effect = 0x0E; param = 0x10 | ((param & 0xf) >> 2); } } else if ((param & 0xF0) == 0xF0) { effect = 0x0E; param = 0x10 | (param & 0xf); } else { effect = 0x01; } break; case FX_PORTAMENTODOWN: if ((param & 0xF0) == 0xE0) { if (to_xm) { effect = 'X' - 55; param = 0x20 | (param & 0xf); } else { effect = 0x0E; param = 0x20 | ((param & 0xf) >> 2); } } else if ((param & 0xF0) == 0xF0) { effect = 0x0E; param = 0x20 | (param & 0xf); } else { effect = 0x02; } break; case FX_TONEPORTAMENTO: effect = 0x03; break; case FX_VIBRATO: effect = 0x04; break; case FX_TONEPORTAVOL: effect = 0x05; break; case FX_VIBRATOVOL: effect = 0x06; break; case FX_TREMOLO: effect = 0x07; break; case FX_PANNING: effect = 0x08; break; case FX_OFFSET: effect = 0x09; break; case FX_VOLUMESLIDE: effect = 0x0A; break; case FX_POSITIONJUMP: effect = 0x0B; break; case FX_VOLUME: effect = 0x0C; break; case FX_PATTERNBREAK: effect = 0x0D; param = ((param / 10) << 4) | (param % 10); break; case FX_SPEED: effect = 0x0F; if (param > 0x20) param = 0x20; break; case FX_TEMPO: if (param > 0x20) { effect = 0x0F; break; } return 0; case FX_GLOBALVOLUME: effect = 'G' - 55; break; case FX_GLOBALVOLSLIDE: effect = 'H' - 55; break; // FIXME this needs to be adjusted case FX_KEYOFF: effect = 'K' - 55; break; case FX_SETENVPOSITION: effect = 'L' - 55; break; case FX_CHANNELVOLUME: effect = 'M' - 55; break; case FX_CHANNELVOLSLIDE: effect = 'N' - 55; break; case FX_PANNINGSLIDE: effect = 'P' - 55; break; case FX_RETRIG: effect = 'R' - 55; break; case FX_TREMOR: effect = 'T' - 55; break; case FX_PANBRELLO: effect = 'Y' - 55; break; case FX_MIDI: effect = 'Z' - 55; break; case FX_SPECIAL: switch (param & 0xF0) { case 0x10: effect = 0x0E; param = (param & 0x0F) | 0x30; break; case 0x20: effect = 0x0E; param = (param & 0x0F) | 0x50; break; case 0x30: effect = 0x0E; param = (param & 0x0F) | 0x40; break; case 0x40: effect = 0x0E; param = (param & 0x0F) | 0x70; break; case 0x90: effect = 'X' - 55; break; case 0xB0: effect = 0x0E; param = (param & 0x0F) | 0x60; break; case 0xA0: case 0x50: case 0x70: case 0x60: effect = param = 0; break; default: effect = 0x0E; break; } break; default: effect = param = 0; } return (uint16_t)((effect << 8) | (param)); } void csf_import_s3m_effect(song_note_t *m, int from_it) { uint32_t effect = m->effect; uint32_t param = m->param; switch (effect + 0x40) { case 'A': effect = FX_SPEED; break; case 'B': effect = FX_POSITIONJUMP; break; case 'C': effect = FX_PATTERNBREAK; if (!from_it) param = (param >> 4) * 10 + (param & 0x0F); break; case 'D': effect = FX_VOLUMESLIDE; break; case 'E': effect = FX_PORTAMENTODOWN; break; case 'F': effect = FX_PORTAMENTOUP; break; case 'G': effect = FX_TONEPORTAMENTO; break; case 'H': effect = FX_VIBRATO; break; case 'I': effect = FX_TREMOR; break; case 'J': effect = FX_ARPEGGIO; break; case 'K': effect = FX_VIBRATOVOL; break; case 'L': effect = FX_TONEPORTAVOL; break; case 'M': effect = FX_CHANNELVOLUME; break; case 'N': effect = FX_CHANNELVOLSLIDE; break; case 'O': effect = FX_OFFSET; break; case 'P': effect = FX_PANNINGSLIDE; break; case 'Q': effect = FX_RETRIG; break; case 'R': effect = FX_TREMOLO; break; case 'S': effect = FX_SPECIAL; break; case 'T': effect = FX_TEMPO; break; case 'U': effect = FX_FINEVIBRATO; break; case 'V': effect = FX_GLOBALVOLUME; if (!from_it) param *= 2; break; case 'W': effect = FX_GLOBALVOLSLIDE; break; case 'X': effect = FX_PANNING; if (!from_it) { if (param == 0xa4) { effect = FX_SPECIAL; param = 0x91; } else if (param > 0x7f) { param = 0xff; } else { param *= 2; } } break; case 'Y': effect = FX_PANBRELLO; break; case '\\': // OpenMPT smooth MIDI macro case 'Z': effect = FX_MIDI; break; default: effect = 0; } m->effect = effect; m->param = param; } void csf_export_s3m_effect(uint8_t *pcmd, uint8_t *pprm, int to_it) { uint8_t effect = *pcmd; uint8_t param = *pprm; switch (effect) { case FX_SPEED: effect = 'A'; break; case FX_POSITIONJUMP: effect = 'B'; break; case FX_PATTERNBREAK: effect = 'C'; if (!to_it) param = ((param / 10) << 4) + (param % 10); break; case FX_VOLUMESLIDE: effect = 'D'; break; case FX_PORTAMENTODOWN: effect = 'E'; break; case FX_PORTAMENTOUP: effect = 'F'; break; case FX_TONEPORTAMENTO: effect = 'G'; break; case FX_VIBRATO: effect = 'H'; break; case FX_TREMOR: effect = 'I'; break; case FX_ARPEGGIO: effect = 'J'; break; case FX_VIBRATOVOL: effect = 'K'; break; case FX_TONEPORTAVOL: effect = 'L'; break; case FX_CHANNELVOLUME: effect = 'M'; break; case FX_CHANNELVOLSLIDE: effect = 'N'; break; case FX_OFFSET: effect = 'O'; break; case FX_PANNINGSLIDE: effect = 'P'; break; case FX_RETRIG: effect = 'Q'; break; case FX_TREMOLO: effect = 'R'; break; case FX_SPECIAL: if (!to_it && param == 0x91) { effect = 'X'; param = 0xA4; } else { effect = 'S'; } break; case FX_TEMPO: effect = 'T'; break; case FX_FINEVIBRATO: effect = 'U'; break; case FX_GLOBALVOLUME: effect = 'V'; if (!to_it) param >>= 1;break; case FX_GLOBALVOLSLIDE: effect = 'W'; break; case FX_PANNING: effect = 'X'; if (!to_it) param >>= 1; break; case FX_PANBRELLO: effect = 'Y'; break; case FX_MIDI: effect = 'Z'; break; default: effect = 0; } effect &= ~0x40; *pcmd = effect; *pprm = param; } void csf_insert_restart_pos(song_t *csf, uint32_t restart_order) { int32_t n, max, row; int32_t ord, pat, newpat; int32_t used; // how many times it was used (if >1, copy it) if (!restart_order) return; // find the last pattern, also look for one that's not being used for (max = ord = n = 0; n < MAX_ORDERS && csf->orderlist[n] < MAX_PATTERNS; ord = n, n++) if (csf->orderlist[n] > max) max = csf->orderlist[n]; newpat = max + 1; pat = csf->orderlist[ord]; if (pat >= MAX_PATTERNS || !csf->patterns[pat] || !csf->pattern_size[pat]) return; for (max = n, used = 0, n = 0; n < max; n++) if (csf->orderlist[n] == pat) used++; if (used > 1) { // copy the pattern so we don't screw up the playback elsewhere while (newpat < MAX_PATTERNS && csf->patterns[newpat]) newpat++; if (newpat >= MAX_PATTERNS) return; // no more patterns? sux //log_appendf(2, "Copying pattern %d to %d for restart position", pat, newpat); csf->patterns[newpat] = csf_allocate_pattern(csf->pattern_size[pat]); csf->pattern_size[newpat] = csf->pattern_alloc_size[newpat] = csf->pattern_size[pat]; memcpy(csf->patterns[newpat], csf->patterns[pat], sizeof(song_note_t) * MAX_CHANNELS * csf->pattern_size[pat]); csf->orderlist[ord] = pat = newpat; } else { //log_appendf(2, "Modifying pattern %d to add restart position", pat); } max = csf->pattern_size[pat] - 1; for (row = 0; row <= max; row++) { song_note_t *note = csf->patterns[pat] + MAX_CHANNELS * row; song_note_t *empty = NULL; // where's an empty effect? int has_break = 0, has_jump = 0; for (n = 0; n < MAX_CHANNELS; n++, note++) { switch (note->effect) { case FX_POSITIONJUMP: has_jump = 1; break; case FX_PATTERNBREAK: has_break = 1; if (!note->param) empty = note; // always rewrite C00 with Bxx (it's cleaner) break; case FX_NONE: if (!empty) empty = note; break; } } // if there's not already a Bxx, and we have a spare channel, // AND either there's a Cxx or it's the last row of the pattern, // then stuff in a jump back to the restart position. if (!has_jump && empty && (has_break || row == max)) { empty->effect = FX_POSITIONJUMP; empty->param = restart_order; } } } schismtracker-20250313/player/effects.c000066400000000000000000002106561476471630300177210ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "player/sndfile.h" #include "player/cmixer.h" #include "player/snd_fm.h" #include "player/snd_gm.h" #include "player/tables.h" #include "midi.h" /* midi_event_length */ #include "util.h" /* for clamp/min */ #include "it.h" // needed for status.flags (UGH) /* --------------------------------------------------------------------------------------------------------- */ /* note/freq conversion functions */ int32_t get_note_from_frequency(int32_t frequency, uint32_t c5speed) { int32_t n; if (!frequency) return 0; for (n = 0; n <= 120; n++) { /* Essentially, this is just doing a note_to_frequency(n, 8363), but with less computation since there's no c5speed to deal with. */ if (frequency <= get_frequency_from_note(n + 1, c5speed)) return n + 1; } return 120; } int32_t get_frequency_from_note(int32_t note, uint32_t c5speed) { if (!note || note > 0xF0) return 0; note--; return _muldiv(c5speed, linear_slide_up_table[(note % 12) * 16] << (note / 12), 65536 << 5); } uint32_t transpose_to_frequency(int32_t transp, int32_t ftune) { return (uint32_t) (8363.0 * pow(2, (transp * 128.0 + ftune) / 1536.0)); } int32_t frequency_to_transpose(uint32_t freq) { return (int32_t) (1536.0 * (log(freq / 8363.0) / log(2))); } uint32_t calc_halftone(uint32_t hz, int32_t rel) { return pow(2, rel / 12.0) * hz + 0.5; } /* --------------------------------------------------------------------------------------------------------- */ /* the full content of snd_fx.cpp follows. */ //////////////////////////////////////////////////////////// // Channels effects void fx_note_cut(song_t *csf, uint32_t nchan, int clear_note) { song_voice_t *chan = &csf->voices[nchan]; // stop the current note: chan->flags |= CHN_NOTEFADE | CHN_FASTVOLRAMP; //if (chan->ptr_instrument) chan->volume = 0; chan->increment = 0; chan->fadeout_volume = 0; //chan->length = 0; if (clear_note) { // keep instrument numbers from picking up old notes // (SCx doesn't do this) // Apparently this isn't necessary at all anymore? // Note cuts seem to work perfectly fine without it. // SCx plays fine too. -paper //chan->frequency = 0; } if (chan->flags & CHN_ADLIB) { //Do this only if really an adlib chan. Important! OPL_NoteOff(csf, nchan); OPL_Touch(csf, nchan, 0); } GM_KeyOff(csf, nchan); GM_Touch(csf, nchan, 0); } void fx_key_off(song_t *csf, uint32_t nchan) { song_voice_t *chan = &csf->voices[nchan]; /*fprintf(stderr, "KeyOff[%d] [ch%u]: flags=0x%X\n", tick_count, (unsigned)nchan, chan->flags);*/ if (chan->flags & CHN_ADLIB) { //Do this only if really an adlib chan. Important! OPL_NoteOff(csf, nchan); } GM_KeyOff(csf, nchan); song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? chan->ptr_instrument : NULL; /*if ((chan->flags & CHN_ADLIB) || (penv && penv->midi_channel_mask)) { // When in AdLib / MIDI mode, end the sample chan->flags |= CHN_FASTVOLRAMP; chan->length = 0; chan->position = 0; return; }*/ chan->flags |= CHN_KEYOFF; //if ((!chan->ptr_instrument) || (!(chan->flags & CHN_VOLENV))) if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument && !(chan->flags & CHN_VOLENV)) { chan->flags |= CHN_NOTEFADE; } if (!chan->length) return; if ((chan->flags & CHN_SUSTAINLOOP) && chan->ptr_sample) { song_sample_t *psmp = chan->ptr_sample; if (psmp->flags & CHN_LOOP) { if (psmp->flags & CHN_PINGPONGLOOP) chan->flags |= CHN_PINGPONGLOOP; else chan->flags &= ~(CHN_PINGPONGLOOP|CHN_PINGPONGFLAG); chan->flags |= CHN_LOOP; chan->length = psmp->length; chan->loop_start = psmp->loop_start; chan->loop_end = psmp->loop_end; if (chan->length > chan->loop_end) chan->length = chan->loop_end; if (chan->position >= chan->length) chan->position = chan->position - chan->length + chan->loop_start; } else { chan->flags &= ~(CHN_LOOP|CHN_PINGPONGLOOP|CHN_PINGPONGFLAG); chan->length = psmp->length; } } if (penv && penv->fadeout && (penv->flags & ENV_VOLLOOP)) chan->flags |= CHN_NOTEFADE; } // negative value for slide = down, positive = up int32_t csf_fx_do_freq_slide(uint32_t flags, int32_t frequency, int32_t slide, int is_tone_portamento) { // IT Linear slides if (!frequency) return 0; if (flags & SONG_LINEARSLIDES) { int32_t old_frequency = frequency; uint32_t n = abs(slide); if (n > 255 * 4) n = 255 * 4; if (slide > 0) { if (n < 16) frequency = _muldivr(frequency, fine_linear_slide_up_table[n], 65536); else frequency = _muldivr(frequency, linear_slide_up_table[n / 4], 65536); if (old_frequency == frequency) frequency++; } else if (slide < 0) { if (n < 16) frequency = _muldivr(frequency, fine_linear_slide_down_table[n], 65536); else frequency = _muldivr(frequency, linear_slide_down_table[n / 4], 65536); if (old_frequency == frequency) frequency--; } } else { if (slide < 0) { frequency = (int32_t)((1712 * 8363 * (int64_t)frequency) / (((int64_t)(frequency) * -slide) + 1712 * 8363)); } else if (slide > 0) { int32_t frequency_div = 1712 * 8363 - ((int64_t)(frequency) * slide); if (frequency_div <= 0) { if (is_tone_portamento) frequency_div = 1; else return 0; } int64_t freq = ((1712 * 8363 * (int64_t)frequency) / frequency_div); if (freq > INT32_MAX) frequency = INT32_MAX; else frequency = (int32_t)freq; } } return frequency; } static void set_instrument_panning(song_voice_t *chan, int32_t panning) { chan->channel_panning = (int16_t)(chan->panning + 1); if (chan->flags & CHN_SURROUND) chan->channel_panning |= 0x8000; chan->panning = panning; chan->flags &= ~CHN_SURROUND; } static void fx_fine_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param) { if ((flags & SONG_FIRSTTICK) && chan->frequency && param) { chan->frequency = csf_fx_do_freq_slide(flags, chan->frequency, param * 4, 0); } } static void fx_fine_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param) { if ((flags & SONG_FIRSTTICK) && chan->frequency && param) { chan->frequency = csf_fx_do_freq_slide(flags, chan->frequency, param * -4, 0); } } static void fx_extra_fine_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param) { if ((flags & SONG_FIRSTTICK) && chan->frequency && param) { chan->frequency = csf_fx_do_freq_slide(flags, chan->frequency, param, 0); } } static void fx_extra_fine_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param) { if ((flags & SONG_FIRSTTICK) && chan->frequency && param) { chan->frequency = csf_fx_do_freq_slide(flags, chan->frequency, -(int)param, 0); } } static void fx_reg_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param) { if (!(flags & SONG_FIRSTTICK)) chan->frequency = csf_fx_do_freq_slide(flags, chan->frequency, (int)(param * 4), 0); } static void fx_reg_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param) { if (!(flags & SONG_FIRSTTICK)) chan->frequency = csf_fx_do_freq_slide(flags, chan->frequency, -(int)(param * 4), 0); } static void fx_portamento_up(uint32_t flags, song_voice_t *chan, uint32_t param) { if (!param) param = chan->mem_pitchslide; switch (param & 0xf0) { case 0xe0: fx_extra_fine_portamento_up(flags, chan, param & 0x0F); break; case 0xf0: fx_fine_portamento_up(flags, chan, param & 0x0F); break; default: fx_reg_portamento_up(flags, chan, param); break; } } static void fx_portamento_down(uint32_t flags, song_voice_t *chan, uint32_t param) { if (!param) param = chan->mem_pitchslide; switch (param & 0xf0) { case 0xe0: fx_extra_fine_portamento_down(flags, chan, param & 0x0F); break; case 0xf0: fx_fine_portamento_down(flags, chan, param & 0x0F); break; default: fx_reg_portamento_down(flags, chan, param); break; } } static void fx_tone_portamento(uint32_t flags, song_voice_t *chan, uint32_t param) { chan->flags |= CHN_PORTAMENTO; if (chan->frequency && chan->portamento_target && !(flags & SONG_FIRSTTICK)) { if (!param && chan->row_effect == FX_TONEPORTAVOL) { if (chan->frequency > 1 && (flags & SONG_LINEARSLIDES)) chan->frequency--; if (chan->frequency < chan->portamento_target) { chan->frequency = chan->portamento_target; chan->portamento_target = 0; } } else if (param && chan->frequency < chan->portamento_target) { chan->frequency = csf_fx_do_freq_slide(flags, chan->frequency, param * 4, 1); if (chan->frequency >= chan->portamento_target) { chan->frequency = chan->portamento_target; chan->portamento_target = 0; } } else if (param && chan->frequency >= chan->portamento_target) { chan->frequency = csf_fx_do_freq_slide(flags, chan->frequency, param * -4, 1); if (chan->frequency < chan->portamento_target) { chan->frequency = chan->portamento_target; chan->portamento_target = 0; } } } } // Implemented for IMF compatibility, can't actually save this in any formats // sign should be 1 (up) or -1 (down) static void fx_note_slide(uint32_t flags, song_voice_t *chan, uint32_t param, int32_t sign) { uint8_t x, y; if (flags & SONG_FIRSTTICK) { x = param & 0xf0; if (x) chan->note_slide_speed = (x >> 4); y = param & 0xf; if (y) chan->note_slide_step = y; chan->note_slide_counter = chan->note_slide_speed; } else { if (--chan->note_slide_counter == 0) { chan->note_slide_counter = chan->note_slide_speed; // update it chan->frequency = get_frequency_from_note (sign * chan->note_slide_step + get_note_from_frequency(chan->frequency, chan->c5speed), chan->c5speed); } } } static void fx_vibrato(song_voice_t *p, uint32_t param) { if (param & 0x0F) p->vibrato_depth = (param & 0x0F) * 4; if (param & 0xF0) p->vibrato_speed = (param >> 4) & 0x0F; p->flags |= CHN_VIBRATO; } static void fx_fine_vibrato(song_voice_t *p, uint32_t param) { if (param & 0x0F) p->vibrato_depth = param & 0x0F; if (param & 0xF0) p->vibrato_speed = (param >> 4) & 0x0F; p->flags |= CHN_VIBRATO; } static void fx_panbrello(song_voice_t *chan, uint32_t param) { uint32_t panpos = chan->panbrello_position & 0xFF; int pdelta = chan->panbrello_delta; if (param & 0x0F) chan->panbrello_depth = param & 0x0F; if (param & 0xF0) chan->panbrello_speed = (param >> 4) & 0x0F; switch (chan->panbrello_type) { case VIB_SINE: default: pdelta = sine_table[panpos]; break; case VIB_RAMP_DOWN: pdelta = ramp_down_table[panpos]; break; case VIB_SQUARE: pdelta = square_table[panpos]; break; case VIB_RANDOM: pdelta = (rand() & 0x7F) - 0x40; break; } /* OpenMPT test case RandomWaveform.it: Speed for random panbrello says how many ticks the value should be used */ if (chan->panbrello_type == VIB_RANDOM) { if (!chan->panbrello_position || chan->panbrello_position >= chan->panbrello_speed) chan->panbrello_position = 0; chan->panbrello_position++; } else { chan->panbrello_position += chan->panbrello_speed; } chan->panbrello_delta = pdelta; } static void fx_volume_up(song_voice_t *chan, uint32_t param) { chan->volume += param * 4; if (chan->volume > 256) chan->volume = 256; } static void fx_volume_down(song_voice_t *chan, uint32_t param) { chan->volume -= param * 4; if (chan->volume < 0) chan->volume = 0; } static void fx_volume_slide(uint32_t flags, song_voice_t *chan, uint32_t param) { // Dxx Volume slide down // // if (xx == 0) then xx = last xx for (Dxx/Kxx/Lxx) for this channel. if (param) chan->mem_volslide = param; else param = chan->mem_volslide; // Order of testing: Dx0, D0x, DxF, DFx if (param == (param & 0xf0)) { // Dx0 Set effect update for channel enabled if channel is ON. // If x = F, then slide up volume by 15 straight away also (for S3M compat) // Every update, add x to the volume, check and clip values > 64 to 64 param >>= 4; if (param == 0xf || !(flags & SONG_FIRSTTICK)) fx_volume_up(chan, param); } else if (param == (param & 0xf)) { // D0x Set effect update for channel enabled if channel is ON. // If x = F, then slide down volume by 15 straight away also (for S3M) // Every update, subtract x from the volume, check and clip values < 0 to 0 if (param == 0xf || !(flags & SONG_FIRSTTICK)) fx_volume_down(chan, param); } else if ((param & 0xf) == 0xf) { // DxF Add x to volume straight away. Check and clip values > 64 to 64 param >>= 4; if (flags & SONG_FIRSTTICK) fx_volume_up(chan, param); } else if ((param & 0xf0) == 0xf0) { // DFx Subtract x from volume straight away. Check and clip values < 0 to 0 param &= 0xf; if (flags & SONG_FIRSTTICK) fx_volume_down(chan, param); } } static void fx_panning_slide(uint32_t flags, song_voice_t *chan, uint32_t param) { int32_t slide = 0; if (param) chan->mem_panslide = param; else param = chan->mem_panslide; if ((param & 0x0F) == 0x0F && (param & 0xF0)) { if (flags & SONG_FIRSTTICK) { param = (param & 0xF0) >> 2; slide = - (int)param; } } else if ((param & 0xF0) == 0xF0 && (param & 0x0F)) { if (flags & SONG_FIRSTTICK) { slide = (param & 0x0F) << 2; } } else { if (!(flags & SONG_FIRSTTICK)) { if (param & 0x0F) slide = (int)((param & 0x0F) << 2); else slide = -(int)((param & 0xF0) >> 2); } } if (slide) { slide += chan->panning; chan->panning = CLAMP(slide, 0, 256); chan->channel_panning = 0; } chan->flags &= ~CHN_SURROUND; chan->panbrello_delta = 0; } static void fx_tremolo(uint32_t flags, song_voice_t *chan, uint32_t param) { unsigned int trempos = chan->tremolo_position & 0xFF; int tdelta; if (param & 0x0F) chan->tremolo_depth = (param & 0x0F) << 2; if (param & 0xF0) chan->tremolo_speed = (param >> 4) & 0x0F; chan->flags |= CHN_TREMOLO; // don't handle on first tick if old-effects mode if ((flags & SONG_FIRSTTICK) && (flags & SONG_ITOLDEFFECTS)) return; switch (chan->tremolo_type) { case VIB_SINE: default: tdelta = sine_table[trempos]; break; case VIB_RAMP_DOWN: tdelta = ramp_down_table[trempos]; break; case VIB_SQUARE: tdelta = square_table[trempos]; break; case VIB_RANDOM: tdelta = 128 * ((double) rand() / RAND_MAX) - 64; break; } chan->tremolo_position = (trempos + 4 * chan->tremolo_speed) & 0xFF; tdelta = (tdelta * (int)chan->tremolo_depth) >> 5; chan->tremolo_delta = tdelta; } static void fx_retrig_note(song_t *csf, uint32_t nchan, uint32_t param) { song_voice_t *chan = &csf->voices[nchan]; //printf("Q%02X note=%02X tick%d %d\n", param, chan->row_note, tick_count, chan->cd_retrig); if ((csf->flags & SONG_FIRSTTICK) && chan->row_note != NOTE_NONE) { chan->cd_retrig = param & 0xf; } else if (--chan->cd_retrig <= 0) { // in Impulse Tracker, retrig only works if a sample is currently playing in the channel if (chan->position == 0) return; chan->cd_retrig = param & 0xf; param >>= 4; if (param) { int vol = chan->volume; if (retrig_table_1[param]) vol = (vol * retrig_table_1[param]) >> 4; else vol += (retrig_table_2[param]) << 2; chan->volume = CLAMP(vol, 0, 256); chan->flags |= CHN_FASTVOLRAMP; } uint32_t note = chan->new_note; int32_t frequency = chan->frequency; if (NOTE_IS_NOTE(note) && chan->length) csf_check_nna(csf, nchan, 0, note, 1); csf_note_change(csf, nchan, note, 1, 1, 0); if (frequency && chan->row_note == NOTE_NONE) chan->frequency = frequency; chan->position = chan->position_frac = 0; } } static void fx_channel_vol_slide(uint32_t flags, song_voice_t *chan, uint32_t param) { int32_t slide = 0; if (param) chan->mem_channel_volslide = param; else param = chan->mem_channel_volslide; if ((param & 0x0F) == 0x0F && (param & 0xF0)) { if (flags & SONG_FIRSTTICK) slide = param >> 4; } else if ((param & 0xF0) == 0xF0 && (param & 0x0F)) { if (flags & SONG_FIRSTTICK) slide = - (int)(param & 0x0F); } else { if (!(flags & SONG_FIRSTTICK)) { if (param & 0x0F) slide = -(int)(param & 0x0F); else slide = (int)((param & 0xF0) >> 4); } } if (slide) { slide += chan->global_volume; chan->global_volume = CLAMP(slide, 0, 64); } } static void fx_global_vol_slide(song_t *csf, song_voice_t *chan, uint32_t param) { int32_t slide = 0; if (param) chan->mem_global_volslide = param; else param = chan->mem_global_volslide; if ((param & 0x0F) == 0x0F && (param & 0xF0)) { if (csf->flags & SONG_FIRSTTICK) slide = param >> 4; } else if ((param & 0xF0) == 0xF0 && (param & 0x0F)) { if (csf->flags & SONG_FIRSTTICK) slide = -(int)(param & 0x0F); } else { if (!(csf->flags & SONG_FIRSTTICK)) { if (param & 0xF0) slide = (int)((param & 0xF0) >> 4); else slide = -(int)(param & 0x0F); } } if (slide) { slide += csf->current_global_volume; csf->current_global_volume = CLAMP(slide, 0, 128); } } static void fx_pattern_loop(song_t *csf, song_voice_t *chan, uint32_t param) { if (param) { if (chan->cd_patloop) { if (!--chan->cd_patloop) { // this should get rid of that nasty infinite loop for cases like // ... .. .. SB0 // ... .. .. SB1 // ... .. .. SB1 // it still doesn't work right in a few strange cases, but oh well :P chan->patloop_row = csf->row + 1; csf->patloop = 0; return; // don't loop! } } else { chan->cd_patloop = param; } csf->process_row = chan->patloop_row - 1; } else { csf->patloop = 1; chan->patloop_row = csf->row; } } static void fx_special(song_t *csf, uint32_t nchan, uint32_t param) { song_voice_t *chan = &csf->voices[nchan]; uint32_t command = param & 0xF0; param &= 0x0F; switch(command) { // S0x: Set Filter // S1x: Set Glissando Control case 0x10: chan->flags &= ~CHN_GLISSANDO; if (param) chan->flags |= CHN_GLISSANDO; break; // S2x: Set FineTune (no longer implemented) // S3x: Set Vibrato WaveForm case 0x30: chan->vib_type = param; break; // S4x: Set Tremolo WaveForm case 0x40: chan->tremolo_type = param; break; // S5x: Set Panbrello WaveForm case 0x50: /* some mpt compat thing */ chan->panbrello_type = (param < 0x04) ? param : 0; chan->panbrello_position = 0; break; // S6x: Pattern Delay for x ticks case 0x60: if (csf->flags & SONG_FIRSTTICK) { csf->frame_delay += param; csf->tick_count += param; } break; // S7x: Envelope Control case 0x70: if (!(csf->flags & SONG_FIRSTTICK)) break; switch(param) { case 0: case 1: case 2: { song_voice_t *bkp = &csf->voices[MAX_CHANNELS]; for (uint32_t i=MAX_CHANNELS; imaster_channel == nchan+1) { if (param == 1) { fx_key_off(csf, i); } else if (param == 2) { bkp->flags |= CHN_NOTEFADE; } else { bkp->flags |= CHN_NOTEFADE; bkp->fadeout_volume = 0; } } } } break; case 3: chan->nna = NNA_NOTECUT; break; case 4: chan->nna = NNA_CONTINUE; break; case 5: chan->nna = NNA_NOTEOFF; break; case 6: chan->nna = NNA_NOTEFADE; break; case 7: chan->flags &= ~CHN_VOLENV; break; case 8: chan->flags |= CHN_VOLENV; break; case 9: chan->flags &= ~CHN_PANENV; break; case 10: chan->flags |= CHN_PANENV; break; case 11: chan->flags &= ~CHN_PITCHENV; break; case 12: chan->flags |= CHN_PITCHENV; break; } break; // S8x: Set 4-bit Panning case 0x80: if (csf->flags & SONG_FIRSTTICK) { chan->flags &= ~CHN_SURROUND; chan->panbrello_delta = 0; chan->panning = (param << 4) + 8; chan->channel_panning = 0; chan->flags |= CHN_FASTVOLRAMP; chan->pan_swing = 0; } break; // S9x: Set Surround case 0x90: if (param == 1 && (csf->flags & SONG_FIRSTTICK)) { chan->flags |= CHN_SURROUND; chan->panbrello_delta = 0; chan->panning = 128; chan->channel_panning = 0; } break; // SAx: Set 64k Offset // Note: don't actually APPLY the offset, and don't clear the regular offset value, either. case 0xA0: if (csf->flags & SONG_FIRSTTICK) { chan->mem_offset = (param << 16) | (chan->mem_offset & ~0xf0000); } break; // SBx: Pattern Loop case 0xB0: if (csf->flags & SONG_FIRSTTICK) fx_pattern_loop(csf, chan, param & 0x0F); break; // SCx: Note Cut case 0xC0: if (csf->flags & SONG_FIRSTTICK) chan->cd_note_cut = param ? param : 1; else if (--chan->cd_note_cut == 0) fx_note_cut(csf, nchan, 1); break; // SDx: Note Delay // SEx: Pattern Delay for x rows case 0xE0: if (csf->flags & SONG_FIRSTTICK) { if (!csf->row_count) // ugh! csf->row_count = param + 1; } break; // SFx: Set Active Midi Macro case 0xF0: chan->active_macro = param; break; } } // Send exactly one MIDI message void csf_midi_send(song_t *csf, const unsigned char *data, uint32_t len, uint32_t nchan, int fake) { song_voice_t *chan = &csf->voices[nchan]; if (len >= 1 && (data[0] == 0xFA || data[0] == 0xFC || data[0] == 0xFF)) { // Start Song, Stop Song, MIDI Reset for (uint32_t c = 0; c < MAX_VOICES; c++) { csf->voices[c].cutoff = 0x7F; csf->voices[c].resonance = 0x00; } } if (len >= 4 && data[0] == 0xF0 && data[1] == 0xF0) { // impulse tracker filter control (mfg. 0xF0) switch (data[2]) { case 0x00: // set cutoff if (data[3] < 0x80) { chan->cutoff = data[3]; setup_channel_filter(chan, !(chan->flags & CHN_FILTER), 256, csf->mix_frequency); } break; case 0x01: // set resonance if (data[3] < 0x80) { chan->resonance = data[3]; setup_channel_filter(chan, !(chan->flags & CHN_FILTER), 256, csf->mix_frequency); } break; } } else if (!fake && csf->midi_out_raw) { /* okay, this is kind of how it works. we pass buffer_count as here because while 1000 * ((8((buffer_size/2) - buffer_count)) / sample_rate) is the number of msec we need to delay by, libmodplug simply doesn't know what the buffer size is at this point so buffer_count simply has no frame of reference. fortunately, schism does and can complete this (tags: _schism_midi_out_raw ) */ csf->midi_out_raw(csf, data, len, csf->buffer_count); } } void csf_midi_out_note(song_t *csf, int chan, const song_note_t *starting_note) { const song_note_t *m = starting_note; unsigned int tc; int m_note; unsigned char buf[4]; int ins, mc, mg, mbl, mbh; int need_note, need_velocity; song_voice_t *c; if (!csf || !(csf->flags & SONG_INSTRUMENTMODE) || (status.flags & MIDI_LIKE_TRACKER)) return; /*if(m) fprintf(stderr, "midi_out_note called (ch %d)note(%d)instr(%d)volcmd(%02X)cmd(%02X)vol(%02X)p(%02X)\n", chan, m->note, m->instrument, m->voleffect, m->effect, m->volparam, m->param); else fprintf(stderr, "midi_out_note called (ch %d) m=%p\n", m);*/ if (!csf->midi_playing) { csf_process_midi_macro(csf, 0, csf->midi_config.start, 0, 0, 0, 0); // START! csf->midi_playing = 1; } if (chan < 0) return; c = &csf->voices[chan]; chan %= MAX_CHANNELS; if (!m) { if (csf->midi_last_row_number != (signed)csf->row) return; m = csf->midi_last_row[chan]; if (!m) return; } else { csf->midi_last_row[chan] = m; csf->midi_last_row_number = csf->row; } ins = csf->midi_ins_tracker[chan]; if (m->instrument > 0) ins = m->instrument; if (ins < 0 || ins >= MAX_INSTRUMENTS || !csf->instruments[ins]) return; /* err... almost certainly */ if (csf->instruments[ins]->midi_channel_mask >= 0x10000) { mc = chan % 16; } else { mc = 0; if(csf->instruments[ins]->midi_channel_mask > 0) while(!(csf->instruments[ins]->midi_channel_mask & (1 << mc))) ++mc; } m_note = m->note; tc = csf->tick_count % csf->current_speed; #if 0 printf("channel = %d note=%d starting_note=%p\n",chan,m_note,starting_note); #endif if (m->effect == FX_SPECIAL) { switch (m->param & 0xF0) { case 0xC0: /* note cut */ if (tc == (((unsigned)m->param) & 15)) { m_note = NOTE_CUT; } else if (tc != 0) return; break; case 0xD0: /* note delay */ if (tc != (((unsigned)m->param) & 15)) return; break; default: if (tc != 0) return; }; } else { if (tc != 0 && !starting_note) return; } need_note = need_velocity = -1; if (m_note > 120) { if (csf->midi_note_tracker[chan] != 0) { csf_process_midi_macro(csf, chan, csf->midi_config.note_off, 0, csf->midi_note_tracker[chan], 0, csf->midi_ins_tracker[chan]); } csf->midi_note_tracker[chan] = 0; if (m->voleffect != VOLFX_VOLUME) { csf->midi_vol_tracker[chan] = 64; } else { csf->midi_vol_tracker[chan] = m->voleffect; } } else if (!m->note && m->voleffect == VOLFX_VOLUME) { csf->midi_vol_tracker[chan] = m->volparam; need_velocity = csf->midi_vol_tracker[chan]; } else if (m->note) { if (csf->midi_note_tracker[chan] != 0) { csf_process_midi_macro(csf, chan, csf->midi_config.note_off, 0, csf->midi_note_tracker[chan], 0, csf->midi_ins_tracker[chan]); } csf->midi_note_tracker[chan] = m_note; if (m->voleffect != VOLFX_VOLUME) { csf->midi_vol_tracker[chan] = 64; } else { csf->midi_vol_tracker[chan] = m->volparam; } need_note = csf->midi_note_tracker[chan]; need_velocity = csf->midi_vol_tracker[chan]; } if (m->instrument > 0) { csf->midi_ins_tracker[chan] = ins; } mg = (csf->instruments[ins]->midi_program) + ((midi_flags & MIDI_BASE_PROGRAM1) ? 1 : 0); mbl = csf->instruments[ins]->midi_bank; mbh = (csf->instruments[ins]->midi_bank >> 7) & 127; if (mbh > -1 && csf->midi_was_bankhi[mc] != mbh) { buf[0] = 0xB0 | (mc & 15); // controller buf[1] = 0x00; // corse bank/select buf[2] = mbh; // corse bank/select csf_midi_send(csf, buf, 3, 0, 0); csf->midi_was_bankhi[mc] = mbh; } if (mbl > -1 && csf->midi_was_banklo[mc] != mbl) { buf[0] = 0xB0 | (mc & 15); // controller buf[1] = 0x20; // fine bank/select buf[2] = mbl; // fine bank/select csf_midi_send(csf, buf, 3, 0, 0); csf->midi_was_banklo[mc] = mbl; } if (mg > -1 && csf->midi_was_program[mc] != mg) { csf->midi_was_program[mc] = mg; csf_process_midi_macro(csf, chan, csf->midi_config.set_program, mg, 0, 0, ins); // program change } if (c->flags & CHN_MUTE) { // don't send noteon events when muted } else if (need_note > 0) { if (need_velocity == -1) need_velocity = 64; // eh? need_velocity = CLAMP(need_velocity*2,0,127); csf_process_midi_macro(csf, chan, csf->midi_config.note_on, 0, need_note, need_velocity, ins); // noteon } else if (need_velocity > -1 && csf->midi_note_tracker[chan] > 0) { need_velocity = CLAMP(need_velocity*2,0,127); csf_process_midi_macro(csf, chan, csf->midi_config.set_volume, need_velocity, csf->midi_note_tracker[chan], need_velocity, ins); // volume-set } } void csf_process_midi_macro(song_t *csf, uint32_t nchan, const char * macro, uint32_t param, uint32_t note, uint32_t velocity, uint32_t use_instr) { /* this was all wrong. -mrsb */ song_voice_t *chan = &csf->voices[nchan]; song_instrument_t *penv = ((csf->flags & SONG_INSTRUMENTMODE) && chan->last_instrument < MAX_INSTRUMENTS) ? csf->instruments[use_instr ? use_instr : chan->last_instrument] : NULL; unsigned char outbuffer[MAX_MIDI_MACRO * 2]; int32_t midi_channel, fake_midi_channel = 0; int32_t saw_c; int32_t nibble_pos = 0; uint32_t write_pos = 0; saw_c = 0; if (!penv || penv->midi_channel_mask == 0) { /* okay, there _IS_ no real midi channel. forget this for now... */ midi_channel = 15; fake_midi_channel = 1; } else if (penv->midi_channel_mask >= 0x10000) { midi_channel = (nchan-1) % 16; } else { midi_channel = 0; while(!(penv->midi_channel_mask & (1 << midi_channel))) ++midi_channel; } for (int read_pos = 0; read_pos <= MAX_MIDI_MACRO && macro[read_pos]; read_pos++) { unsigned char data = 0; int is_nibble = 0; switch (macro[read_pos]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': data = (unsigned char)(macro[read_pos] - '0'); is_nibble = 1; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': data = (unsigned char)((macro[read_pos] - 'A') + 0x0A); is_nibble = 1; break; case 'c': /* Channel */ data = (unsigned char)midi_channel; is_nibble = 1; saw_c = 1; break; case 'n': /* Note */ data = (note - 1); break; case 'v': { data = (unsigned char)CLAMP(velocity, 0x01, 0x7F); break; } case 'u': { /* Volume */ const int32_t vol = _muldiv(chan->calc_volume * csf->current_global_volume, chan->global_volume * chan->instrument_volume, INT32_C(1) << 26); data = (unsigned char)CLAMP(vol, 0x01, 0x7F); //printf("%u\n", data); break; } case 'x': /* Panning */ data = (unsigned char)MIN(chan->panning, 0x7F); break; case 'y': /* Final Panning */ data = (unsigned char)MIN(chan->final_panning, 0x7F); break; case 'a': /* MIDI Bank (high byte) */ if (penv && penv->midi_bank != -1) data = (unsigned char)((penv->midi_bank >> 7) & 0x7F); break; case 'b': /* MIDI Bank (low byte) */ if (penv && penv->midi_bank != -1) data = (unsigned char)(penv->midi_bank & 0x7F); break; case 'p': /* MIDI Program */ if (penv && penv->midi_program != -1) data = (unsigned char)(penv->midi_program & 0x7F); break; case 'z': /* Zxx Param */ data = (unsigned char)(param); break; case 'h': /* Host channel */ data = (unsigned char)(nchan & 0x7F); break; case 'm': /* Loop direction (judging from the macro letter, this was supposed to be loop mode instead, but a wrong offset into the channel structure was used in IT.) */ data = (chan->flags & CHN_PINGPONGFLAG) ? 1 : 0; break; case 'o': /* OpenMPT test case ZxxSecrets.it: offsets are NOT clamped! also SAx doesn't count :) */ data = (unsigned char)((chan->mem_offset >> 8) & 0xFF); break; default: continue; } if (is_nibble == 1) { if (nibble_pos == 0) { outbuffer[write_pos] = data; nibble_pos = 1; } else { outbuffer[write_pos] = (outbuffer[write_pos] << 4) | data; write_pos++; nibble_pos = 0; } } else { if (nibble_pos == 1) { write_pos++; nibble_pos = 0; } outbuffer[write_pos] = data; write_pos++; } } if (nibble_pos == 1) { // Finish current byte write_pos++; } // Macro string has been parsed and translated, now send the message(s)... uint32_t send_pos = 0; uint8_t running_status = 0; while (send_pos < write_pos) { uint32_t send_length = 0; if (outbuffer[send_pos] == 0xF0) { // SysEx start if ((write_pos - send_pos >= 4) && outbuffer[send_pos + 1] == 0xF0) { // Internal macro, 4 bytes long send_length = 4; } else { // SysEx message, find end of message for (uint32_t i = send_pos + 1; i < write_pos; i++) { if (outbuffer[i] == 0xF7) { // Found end of SysEx message send_length = i - send_pos + 1; break; } } if (send_length == 0) { // Didn't find end, so "invent" end of SysEx message outbuffer[write_pos++] = 0xF7; send_length = write_pos - send_pos; } } } else if (!(outbuffer[send_pos] & 0x80)) { // Missing status byte? Try inserting running status if (running_status) { send_pos--; outbuffer[send_pos] = running_status; } else { // No running status to re-use; skip this byte send_pos++; } continue; } else { // Other MIDI messages send_length = MIN(midi_event_length(outbuffer[send_pos]), write_pos - send_pos); } if (send_length == 0) break; if (outbuffer[send_pos] < 0xF0) { running_status = outbuffer[send_pos]; } csf_midi_send(csf, outbuffer + send_pos, send_length, nchan, saw_c && fake_midi_channel); send_pos += send_length; } } //////////////////////////////////////////////////////////// // Length // XXX Is this still true? SCHISM_STATIC_ASSERT(MAX_CHANNELS == 64, "csf_get_length assumes 64 channels"); uint32_t csf_get_length(song_t *csf) { uint32_t elapsed = 0, row = 0, next_row = 0, cur_order = 0, next_order = 0, pat = csf->orderlist[0], speed = csf->initial_speed, tempo = csf->initial_tempo, psize, n; uint32_t patloop[MAX_CHANNELS] = {0}; uint8_t mem_tempo[MAX_CHANNELS] = {0}; uint64_t setloop = 0; // bitmask const song_note_t *pdata; for (;;) { uint32_t speed_count = 0; row = next_row; cur_order = next_order; // Check if pattern is valid pat = csf->orderlist[cur_order]; while (pat >= MAX_PATTERNS) { // End of song ? if (pat == ORDER_LAST || cur_order >= MAX_ORDERS) { pat = ORDER_LAST; // cause break from outer loop too break; } else { cur_order++; pat = (cur_order < MAX_ORDERS) ? csf->orderlist[cur_order] : ORDER_LAST; } next_order = cur_order; } // Weird stuff? if (pat >= MAX_PATTERNS) break; pdata = csf->patterns[pat]; if (pdata) { psize = csf->pattern_size[pat]; } else { pdata = blank_pattern; psize = 64; } // guard against Cxx to invalid row, etc. if (row >= psize) row = 0; // Update next position next_row = row + 1; if (next_row >= psize) { next_order = cur_order + 1; next_row = 0; } /* muahahaha */ if (csf->stop_at_order > -1 && csf->stop_at_row > -1) { if (csf->stop_at_order <= (signed) cur_order && csf->stop_at_row <= (signed) row) break; if (csf->stop_at_time > 0) { /* stupid api decision */ if (((elapsed + 500) / 1000) >= csf->stop_at_time) { csf->stop_at_order = cur_order; csf->stop_at_row = row; break; } } } /* This is nasty, but it fixes inaccuracies with SB0 SB1 SB1. (Simultaneous loops in multiple channels are still wildly incorrect, though.) */ if (!row) setloop = ~0; if (setloop) { for (n = 0; n < MAX_CHANNELS; n++) if (setloop & (1 << n)) patloop[n] = elapsed; setloop = 0; } const song_note_t *note = pdata + row * MAX_CHANNELS; for (n = 0; n < MAX_CHANNELS; note++, n++) { uint32_t param = note->param; switch (note->effect) { case FX_NONE: break; case FX_POSITIONJUMP: next_order = param > cur_order ? param : cur_order + 1; next_row = 0; break; case FX_PATTERNBREAK: next_order = cur_order + 1; next_row = param; break; case FX_SPEED: if (param) speed = param; break; case FX_TEMPO: { if (param) mem_tempo[n] = param; else param = mem_tempo[n]; // WTF is this doing? --paper int d = (param & 0xf); switch (param >> 4) { default: tempo = param; break; case 0: d = -d; SCHISM_FALLTHROUGH; case 1: d = d * (speed - 1) + tempo; tempo = CLAMP(d, 32, 255); break; } break; } case FX_SPECIAL: switch (param >> 4) { case 0x6: speed_count = param & 0x0F; break; case 0xb: if (param & 0x0F) { elapsed += (elapsed - patloop[n]) * (param & 0x0F); patloop[n] = 0xffffffff; setloop = 1; } else { patloop[n] = elapsed; } break; case 0xe: speed_count = (param & 0x0F) * speed; break; } break; } } // sec/tick = 5 / (2 * tempo) // msec/tick = 5000 / (2 * tempo) // = 2500 / tempo elapsed += (speed + speed_count) * 2500 / tempo; } return (elapsed + 500) / 1000; } ////////////////////////////////////////////////////////////////////////////////////////////////// // Effects song_sample_t *csf_translate_keyboard(song_t *csf, song_instrument_t *penv, uint32_t note, song_sample_t *def) { uint32_t n = penv->sample_map[note - 1]; return (n && n < MAX_SAMPLES) ? &csf->samples[n] : def; } static void env_reset(song_voice_t *chan, int always) { if (chan->ptr_instrument) { chan->flags |= CHN_FASTVOLRAMP; if (always) { chan->vol_env_position = 0; chan->pan_env_position = 0; chan->pitch_env_position = 0; } else { /* only reset envelopes with carry off */ if (!(chan->ptr_instrument->flags & ENV_VOLCARRY)) chan->vol_env_position = 0; if (!(chan->ptr_instrument->flags & ENV_PANCARRY)) chan->pan_env_position = 0; if (!(chan->ptr_instrument->flags & ENV_PITCHCARRY)) chan->pitch_env_position = 0; } } // this was migrated from csf_note_change, should it be here? chan->fadeout_volume = 65536; } void csf_instrument_change(song_t *csf, song_voice_t *chan, uint32_t instr, int porta, int inst_column) { int inst_changed = 0; if (instr >= MAX_INSTRUMENTS) return; song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? csf->instruments[instr] : NULL; song_sample_t *psmp = &csf->samples[instr]; const song_sample_t *oldsmp = chan->ptr_sample; const int32_t old_instrument_volume = chan->instrument_volume; uint32_t note = chan->new_note; if (note == NOTE_NONE) return; if (penv && NOTE_IS_NOTE(note)) { /* OpenMPT test case emptyslot.it */ if (!penv->sample_map[note - NOTE_FIRST]) { chan->ptr_instrument = penv; return; } if (penv->note_map[note - NOTE_FIRST] > NOTE_LAST) return; //uint32_t n = penv->sample_map[note - NOTE_FIRST]; psmp = csf_translate_keyboard(csf, penv, note, NULL); } else if (csf->flags & SONG_INSTRUMENTMODE) { if (NOTE_IS_CONTROL(note)) return; if (!penv) { /* OpenMPT test case emptyslot.it */ chan->ptr_instrument = NULL; chan->new_instrument = 0; return; } psmp = NULL; } // Update Volume if (inst_column && psmp) chan->volume = psmp->volume; // inst_changed is used for IT carry-on env option if (penv != chan->ptr_instrument || !chan->current_sample_data) { inst_changed = 1; chan->ptr_instrument = penv; } // Instrument adjust chan->new_instrument = 0; if (psmp) { psmp->played = 1; if (penv) { penv->played = 1; chan->instrument_volume = (psmp->global_volume * penv->global_volume) >> 7; } else { chan->instrument_volume = psmp->global_volume; } } /* samples should not change on instrument number in compatible Gxx mode. * * OpenMPT test cases: * PortaInsNumCompat.it, PortaSampleCompat.it, PortaCutCompat.it */ if (chan->ptr_sample && psmp != chan->ptr_sample && porta && chan->increment && csf->flags & SONG_COMPATGXX) psmp = chan->ptr_sample; /* OpenMPT test case InstrAfterMultisamplePorta.it: C#5 01 ... <- maps to sample 1 C-5 .. G02 <- maps to sample 2 ... 01 ... <- plays sample 1 with the volume and panning attributes of sample 2 */ if (penv && !inst_changed && psmp != oldsmp && chan->ptr_sample && !NOTE_IS_NOTE(chan->row_note)) return; if (!penv && psmp != oldsmp && porta) { chan->flags |= CHN_NEWNOTE; } // Reset envelopes // Conditions experimentally determined to cause envelope reset in Impulse Tracker: // - no note currently playing (of course) // - note given, no portamento // - instrument number given, portamento, compat gxx enabled // - instrument number given, no portamento, after keyoff, old effects enabled // If someone can enlighten me to what the logic really is here, I'd appreciate it. // Seems like it's just a total mess though, probably to get XMs to play right. if (penv) { if (( !chan->length ) || ( inst_column && porta && (csf->flags & SONG_COMPATGXX) ) || ( inst_column && !porta && (chan->flags & (CHN_NOTEFADE|CHN_KEYOFF)) && (csf->flags & SONG_ITOLDEFFECTS) )) { env_reset(chan, inst_changed || !chan->fadeout_volume || !NOTE_IS_NOTE(chan->row_note)); } else if (!(penv->flags & ENV_VOLUME)) { // XXX why is this being done? // I'm pretty sure this is just some stupid IT thing with portamentos chan->vol_env_position = 0; } if (!porta) { chan->vol_swing = chan->pan_swing = 0; if (penv->vol_swing) { /* this was wrong, and then it was still wrong. (possibly it continues to be wrong even now?) */ double d = 2 * (((double) rand()) / RAND_MAX) - 1; // floor() is applied to get exactly the same volume levels as in IT. -- Saga chan->vol_swing = floor(d * penv->vol_swing / 100.0 * chan->instrument_volume); } if (penv->pan_swing) { /* this was also wrong, and even more so */ double d = 2 * (((double) rand()) / RAND_MAX) - 1; chan->pan_swing = d * penv->pan_swing * 4; } } } // Invalid sample ? if (!psmp) { chan->ptr_sample = NULL; chan->instrument_volume = 0; return; } const int was_key_off = (chan->flags & CHN_KEYOFF) != 0; if (psmp == chan->ptr_sample && chan->current_sample_data && chan->length) { if (porta && inst_changed && penv) { chan->flags &= ~(CHN_KEYOFF | CHN_NOTEFADE); } return; } if (porta && !chan->length) chan->increment = 0; chan->flags &= ~(CHN_SAMPLE_FLAGS | CHN_KEYOFF | CHN_NOTEFADE | CHN_VOLENV | CHN_PANENV | CHN_PITCHENV); if (penv) { if (penv->flags & ENV_VOLUME) chan->flags |= CHN_VOLENV; if (penv->flags & ENV_PANNING) chan->flags |= CHN_PANENV; if (penv->flags & ENV_PITCH) chan->flags |= CHN_PITCHENV; if (penv->ifc & 0x80) chan->cutoff = penv->ifc & 0x7F; if (penv->ifr & 0x80) chan->resonance = penv->ifr & 0x7F; } if (chan->row_note == NOTE_OFF && (csf->flags & SONG_ITOLDEFFECTS) && psmp != oldsmp) { if (chan->ptr_sample) chan->flags |= chan->ptr_sample->flags & CHN_SAMPLE_FLAGS; if (psmp->flags & CHN_PANNING) chan->panning = psmp->panning; chan->instrument_volume = old_instrument_volume; chan->volume = psmp->volume; chan->position = 0; return; } // sample change: reset sample vibrato chan->autovib_depth = 0; chan->autovib_position = 0; if ((chan->flags & (CHN_KEYOFF | CHN_NOTEFADE)) && inst_column) { // Don't start new notes after ===/~~~ chan->frequency = 0; } chan->flags |= psmp->flags & CHN_SAMPLE_FLAGS; chan->ptr_sample = psmp; chan->length = psmp->length; chan->loop_start = psmp->loop_start; chan->loop_end = psmp->loop_end; chan->c5speed = psmp->c5speed; chan->current_sample_data = psmp->data; chan->position = 0; if ((chan->flags & CHN_SUSTAINLOOP) && (!porta || (penv && !was_key_off))) { chan->loop_start = psmp->sustain_start; chan->loop_end = psmp->sustain_end; chan->flags |= CHN_LOOP; if (chan->flags & CHN_PINGPONGSUSTAIN) chan->flags |= CHN_PINGPONGLOOP; } if ((chan->flags & CHN_LOOP) && chan->loop_end < chan->length) chan->length = chan->loop_end; /*fprintf(stderr, "length set as %d (from %d), ch flags %X smp flags %X\n", (int)chan->length, (int)psmp->length, chan->flags, psmp->flags);*/ } // have_inst is a hack to ignore the note-sample map when no instrument number is present void csf_note_change(song_t *csf, uint32_t nchan, int note, int porta, int retrig, int have_inst) { // why would csf_note_change ever get a negative value for 'note'? if (note == NOTE_NONE || note < 0) return; // save the note that's actually used, as it's necessary to properly calculate PPS and stuff // (and also needed for correct display of note dots) int truenote = note; song_voice_t *chan = &csf->voices[nchan]; song_sample_t *pins = chan->ptr_sample; song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? chan->ptr_instrument : NULL; if (penv && NOTE_IS_NOTE(note)) { if (!penv->sample_map[note - 1]) return; if (!(have_inst && porta && pins)) pins = csf_translate_keyboard(csf, penv, note, pins); note = penv->note_map[note - 1]; } if (NOTE_IS_CONTROL(note)) { // hax: keep random sample numbers from triggering notes (see csf_instrument_change) // NOTE_OFF is a completely arbitrary choice - this could be anything above NOTE_LAST chan->new_note = NOTE_OFF; switch (note) { case NOTE_OFF: fx_key_off(csf, nchan); if (!porta && (csf->flags & SONG_ITOLDEFFECTS) && chan->row_instr) chan->flags &= ~(CHN_NOTEFADE | CHN_KEYOFF); break; case NOTE_CUT: fx_note_cut(csf, nchan, 1); break; case NOTE_FADE: default: // Impulse Tracker handles all unknown notes as fade internally if (csf->flags & SONG_INSTRUMENTMODE) chan->flags |= CHN_NOTEFADE; break; } return; } if (!pins) return; if(!porta && pins) chan->c5speed = pins->c5speed; if (porta && !chan->increment) porta = 0; note = CLAMP(note, NOTE_FIRST, NOTE_LAST); chan->note = CLAMP(truenote, NOTE_FIRST, NOTE_LAST); chan->new_instrument = 0; uint32_t frequency = get_frequency_from_note(note, chan->c5speed); chan->panbrello_delta = 0; if (frequency) { if (porta && chan->frequency) { chan->portamento_target = frequency; } else { chan->portamento_target = 0; chan->frequency = frequency; } if (!porta || !chan->length) { chan->ptr_sample = pins; chan->current_sample_data = pins->data; chan->length = pins->length; chan->loop_end = pins->length; chan->loop_start = 0; chan->flags = (chan->flags & ~CHN_SAMPLE_FLAGS) | (pins->flags & CHN_SAMPLE_FLAGS); if (chan->flags & CHN_SUSTAINLOOP) { chan->loop_start = pins->sustain_start; chan->loop_end = pins->sustain_end; chan->flags &= ~CHN_PINGPONGLOOP; chan->flags |= CHN_LOOP; if (chan->flags & CHN_PINGPONGSUSTAIN) chan->flags |= CHN_PINGPONGLOOP; if (chan->length > chan->loop_end) chan->length = chan->loop_end; } else if (chan->flags & CHN_LOOP) { chan->loop_start = pins->loop_start; chan->loop_end = pins->loop_end; if (chan->length > chan->loop_end) chan->length = chan->loop_end; } chan->position = chan->position_frac = 0; } if (chan->position >= chan->length) chan->position = chan->loop_start; } else { porta = 0; } if (penv && (penv->flags & ENV_SETPANNING)) { set_instrument_panning(chan, penv->panning); } else if (pins->flags & CHN_PANNING) { set_instrument_panning(chan, pins->panning); } // Pitch/Pan separation if (penv && penv->pitch_pan_separation) { if (!chan->channel_panning) { chan->channel_panning = (int16_t)(chan->panning + 1); } // PPS value is 1/512, i.e. PPS=1 will adjust by 8/512 = 1/64 for each 8 semitones // with PPS = 32 / PPC = C-5, E-6 will pan hard right (and D#6 will not) int delta = (int)(chan->note - penv->pitch_pan_center - NOTE_FIRST) * penv->pitch_pan_separation / 2; chan->panning = CLAMP(chan->panning + delta, 0, 256); } if (penv && porta) chan->nna = penv->nna; if (!porta) { if (penv) chan->nna = penv->nna; env_reset(chan, 0); } /* OpenMPT test cases Off-Porta.it, Off-Porta-CompatGxx.it */ if (!(porta && (!(csf->flags & SONG_COMPATGXX) || !chan->row_instr))) chan->flags &= ~CHN_KEYOFF; // Enable Ramping if (!porta) { //chan->vu_meter = 0x0; chan->strike = 4; /* this affects how long the initial hit on the playback marks lasts (bigger dot in instrument and sample list windows) */ chan->flags &= ~CHN_FILTER; chan->flags |= CHN_FASTVOLRAMP | CHN_NEWNOTE; if (!retrig) { chan->autovib_depth = 0; chan->autovib_position = 0; chan->vibrato_position = 0; } chan->left_volume = chan->right_volume = 0; // Setup Initial Filter for this note if (penv) { if (penv->ifr & 0x80) chan->resonance = penv->ifr & 0x7F; if (penv->ifc & 0x80) chan->cutoff = penv->ifc & 0x7F; } else { chan->vol_swing = chan->pan_swing = 0; } } } uint32_t csf_get_nna_channel(song_t *csf, uint32_t nchan) { song_voice_t *chan = &csf->voices[nchan]; // Check for empty channel song_voice_t *pi = &csf->voices[MAX_CHANNELS]; for (uint32_t i=MAX_CHANNELS; ilength) { if (pi->flags & CHN_MUTE) { if (pi->flags & CHN_NNAMUTE) { pi->flags &= ~(CHN_NNAMUTE|CHN_MUTE); } else { /* this channel is muted; skip */ continue; } } return i; } } if (!chan->fadeout_volume) return 0; // All channels are used: check for lowest volume uint32_t result = 0; uint32_t vol = 64*65536; // 25% int envpos = 0xFFFFFF; const song_voice_t *pj = &csf->voices[MAX_CHANNELS]; for (uint32_t j=MAX_CHANNELS; jfadeout_volume) return j; uint32_t v = pj->volume; if (pj->flags & CHN_NOTEFADE) v = v * pj->fadeout_volume; else v <<= 16; if (pj->flags & CHN_LOOP) v >>= 1; if (v < vol || (v == vol && pj->vol_env_position > envpos)) { envpos = pj->vol_env_position; vol = v; result = j; } } if (result) { /* unmute new nna channel */ csf->voices[result].flags &= ~(CHN_MUTE|CHN_NNAMUTE); } return result; } void csf_check_nna(song_t *csf, uint32_t nchan, uint32_t instr, int note, int force_cut) { song_voice_t *p; song_voice_t *chan = &csf->voices[nchan]; song_instrument_t *penv = (csf->flags & SONG_INSTRUMENTMODE) ? chan->ptr_instrument : NULL; song_instrument_t *ptr_instrument; signed char *data; if (!NOTE_IS_NOTE(note)) return; // Always NNA cut - using if (force_cut || !(csf->flags & SONG_INSTRUMENTMODE)) { if (!chan->length || (chan->flags & CHN_MUTE) || (!chan->left_volume && !chan->right_volume)) return; uint32_t n = csf_get_nna_channel(csf, nchan); if (!n) return; p = &csf->voices[n]; // Copy Channel *p = *chan; p->flags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PORTAMENTO); p->panbrello_delta = 0; p->tremolo_delta = 0; p->master_channel = nchan+1; p->n_command = 0; // Cut the note p->fadeout_volume = 0; p->flags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); // Stop this channel chan->length = chan->position = chan->position_frac = 0; chan->rofs = chan->lofs = 0; chan->left_volume = chan->right_volume = 0; if (chan->flags & CHN_ADLIB) { //Do this only if really an adlib chan. Important! OPL_NoteOff(csf, nchan); OPL_Touch(csf, nchan, 0); } GM_KeyOff(csf, nchan); GM_Touch(csf, nchan, 0); return; } if (instr >= MAX_INSTRUMENTS) instr = 0; data = chan->current_sample_data; /* OpenMPT test case DNA-NoInstr.it */ ptr_instrument = instr > 0 ? csf->instruments[instr] : chan->ptr_instrument; if (ptr_instrument != NULL) { uint32_t n = ptr_instrument->sample_map[note - 1]; /* MPT test case dct_smp_note_test.it */ if (n > 0 && n < MAX_SAMPLES) data = csf->samples[n].data; else /* OpenMPT test case emptyslot.it */ return; } if (!penv) return; p = chan; for (uint32_t i=nchan; i= MAX_CHANNELS || p == chan) && ((p->master_channel == nchan + 1 || p == chan) && p->ptr_instrument))) continue; int apply_dna = 0; // Duplicate Check Type switch (p->ptr_instrument->dct) { case DCT_NOTE: apply_dna = (NOTE_IS_NOTE(note) && (int) p->note == note && ptr_instrument == p->ptr_instrument); break; case DCT_SAMPLE: apply_dna = (data && data == p->current_sample_data && ptr_instrument == p->ptr_instrument); break; case DCT_INSTRUMENT: apply_dna = (ptr_instrument == p->ptr_instrument); break; } // Duplicate Note Action if (apply_dna) { switch(p->ptr_instrument->dca) { case DCA_NOTECUT: fx_key_off(csf, i); p->volume = 0; if (chan->flags & CHN_ADLIB) { //Do this only if really an adlib chan. Important! // // This isn't very useful really since we can't save // Adlib songs with instruments anyway, but whatever. OPL_NoteOff(csf, nchan); OPL_Touch(csf, nchan, 0); } break; case DCA_NOTEOFF: fx_key_off(csf, i); break; case DCA_NOTEFADE: p->flags |= CHN_NOTEFADE; break; } if (!p->volume) { p->fadeout_volume = 0; p->flags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); } } } if (chan->flags & CHN_MUTE) return; // New Note Action if (chan->increment && chan->length) { uint32_t n = csf_get_nna_channel(csf, nchan); if (n) { p = &csf->voices[n]; // Copy Channel *p = *chan; p->flags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PORTAMENTO); p->panbrello_delta = 0; p->tremolo_delta = 0; p->master_channel = nchan+1; p->n_command = 0; // Key Off the note switch(chan->nna) { case NNA_NOTEOFF: fx_key_off(csf, n); break; case NNA_NOTECUT: p->fadeout_volume = 0; SCHISM_FALLTHROUGH; case NNA_NOTEFADE: p->flags |= CHN_NOTEFADE; break; } if (!p->volume) { p->fadeout_volume = 0; p->flags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP); } // Stop this channel chan->length = chan->position = chan->position_frac = 0; chan->rofs = chan->lofs = 0; } } } // XXX why is `porta` here, and what was it used for? static void handle_effect(song_t *csf, uint32_t nchan, uint32_t cmd, uint32_t param, SCHISM_UNUSED int porta, int firsttick) { song_voice_t *chan = csf->voices + nchan; switch (cmd) { case FX_NONE: break; // Set Volume case FX_VOLUME: if (!(csf->flags & SONG_FIRSTTICK)) break; chan->volume = (param < 64) ? param*4 : 256; chan->flags |= CHN_FASTVOLRAMP; break; case FX_PORTAMENTOUP: fx_portamento_up(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, chan->mem_pitchslide); break; case FX_PORTAMENTODOWN: fx_portamento_down(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, chan->mem_pitchslide); break; case FX_VOLUMESLIDE: fx_volume_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); break; case FX_TONEPORTAMENTO: fx_tone_portamento(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, chan->mem_portanote); break; case FX_TONEPORTAVOL: fx_tone_portamento(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, chan->mem_portanote); fx_volume_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); break; case FX_VIBRATO: fx_vibrato(chan, param); break; case FX_VIBRATOVOL: fx_volume_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); fx_vibrato(chan, 0); break; case FX_SPEED: if ((csf->flags & SONG_FIRSTTICK) && param) { csf->tick_count = param; csf->current_speed = param; } break; case FX_TEMPO: if (csf->flags & SONG_FIRSTTICK) { if (param) chan->mem_tempo = param; else param = chan->mem_tempo; if (param >= 0x20) csf->current_tempo = param; } else { param = chan->mem_tempo; // this just got set on tick zero switch (param >> 4) { case 0: csf->current_tempo -= param & 0xf; if (csf->current_tempo < 32) csf->current_tempo = 32; break; case 1: csf->current_tempo += param & 0xf; if (csf->current_tempo > 255) csf->current_tempo = 255; break; } } break; case FX_OFFSET: if (!(csf->flags & SONG_FIRSTTICK)) break; if (param) chan->mem_offset = (chan->mem_offset & ~0xff00) | (param << 8); if (NOTE_IS_NOTE(chan->row_instr ? chan->new_note : chan->row_note)) { chan->position = chan->mem_offset; if (chan->position > chan->length) { chan->position = (csf->flags & SONG_ITOLDEFFECTS) ? chan->length : 0; } } break; case FX_ARPEGGIO: chan->n_command = FX_ARPEGGIO; if (!(csf->flags & SONG_FIRSTTICK)) break; if (param) chan->mem_arpeggio = param; break; case FX_RETRIG: if (param) chan->mem_retrig = param & 0xFF; fx_retrig_note(csf, nchan, chan->mem_retrig); break; case FX_TREMOR: // Tremor logic lifted from DUMB, which is the only player that actually gets it right. // I *sort of* understand it. if (csf->flags & SONG_FIRSTTICK) { if (!param) param = chan->mem_tremor; else if (!(csf->flags & SONG_ITOLDEFFECTS)) { if (param & 0xf0) param -= 0x10; if (param & 0x0f) param -= 0x01; } chan->mem_tremor = param; chan->cd_tremor |= 128; } chan->n_command = FX_TREMOR; break; case FX_GLOBALVOLUME: if (!firsttick) break; if (param <= 128) csf->current_global_volume = param; break; case FX_GLOBALVOLSLIDE: fx_global_vol_slide(csf, chan, param); break; case FX_PANNING: if (!(csf->flags & SONG_FIRSTTICK)) break; chan->flags &= ~CHN_SURROUND; chan->panbrello_delta = 0; chan->panning = param; chan->channel_panning = 0; chan->pan_swing = 0; chan->flags |= CHN_FASTVOLRAMP; break; case FX_PANNINGSLIDE: fx_panning_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); break; case FX_TREMOLO: fx_tremolo(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); break; case FX_FINEVIBRATO: fx_fine_vibrato(chan, param); break; case FX_SPECIAL: fx_special(csf, nchan, param); break; case FX_KEYOFF: if ((csf->current_speed - csf->tick_count) == param) fx_key_off(csf, nchan); break; case FX_CHANNELVOLUME: if (!(csf->flags & SONG_FIRSTTICK)) break; // FIXME rename global_volume to channel_volume in the channel struct if (param <= 64) { chan->global_volume = param; chan->flags |= CHN_FASTVOLRAMP; } break; case FX_CHANNELVOLSLIDE: fx_channel_vol_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param); break; case FX_PANBRELLO: fx_panbrello(chan, param); break; case FX_SETENVPOSITION: if (!(csf->flags & SONG_FIRSTTICK)) break; chan->vol_env_position = param; chan->pan_env_position = param; chan->pitch_env_position = param; if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument) { song_instrument_t *penv = chan->ptr_instrument; if ((chan->flags & CHN_PANENV) && (penv->pan_env.nodes) && ((int)param > penv->pan_env.ticks[penv->pan_env.nodes-1])) { chan->flags &= ~CHN_PANENV; } } break; case FX_POSITIONJUMP: if (!(csf->mix_flags & SNDMIX_NOBACKWARDJUMPS) || csf->process_order < param) csf->process_order = param - 1; csf->process_row = PROCESS_NEXT_ORDER; break; case FX_PATTERNBREAK: if (!csf->patloop) { csf->break_row = param; csf->process_row = PROCESS_NEXT_ORDER; } break; case FX_NOTESLIDEUP: fx_note_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param, 1); break; case FX_NOTESLIDEDOWN: fx_note_slide(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, param, -1); break; } } static void handle_voleffect(song_t *csf, song_voice_t *chan, uint32_t volcmd, uint32_t vol, int firsttick, int start_note) { /* A few notes, paraphrased from ITTECH.TXT: Ex/Fx/Gx are shared with Exx/Fxx/Gxx; Ex/Fx are 4x the 'normal' slide value Gx is linked with Ex/Fx if Compat Gxx is off, just like Gxx is with Exx/Fxx Gx values: 1, 4, 8, 16, 32, 64, 96, 128, 255 Ax/Bx/Cx/Dx values are used directly (i.e. D9 == D09), and are NOT shared with Dxx (value is stored into mem_vc_volslide and used by A0/B0/C0/D0) Hx uses the same value as Hxx and Uxx, and affects the *depth* so... hxx = (hx | (oldhxx & 0xf0)) ??? Additionally: volume and panning are handled on the start tick, not the first tick of the row (that is, SDx alters their behavior) */ switch (volcmd) { case VOLFX_NONE: break; case VOLFX_VOLUME: if (start_note) { if (vol > 64) vol = 64; chan->volume = vol << 2; chan->flags |= CHN_FASTVOLRAMP; } break; case VOLFX_PANNING: if (start_note) { if (vol > 64) vol = 64; chan->panning = vol << 2; chan->channel_panning = 0; chan->pan_swing = 0; chan->panbrello_delta = 0; chan->flags |= CHN_FASTVOLRAMP; chan->flags &= ~CHN_SURROUND; } break; case VOLFX_PORTAUP: // Fx if (!start_note) { fx_reg_portamento_up(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, chan->mem_pitchslide); } break; case VOLFX_PORTADOWN: // Ex if (!start_note) { fx_reg_portamento_down(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, chan->mem_pitchslide); } break; case VOLFX_TONEPORTAMENTO: // Gx if (!start_note) { fx_tone_portamento(csf->flags | (firsttick ? SONG_FIRSTTICK : 0), chan, chan->mem_portanote); } break; case VOLFX_VOLSLIDEUP: // Cx if (start_note) { if (vol) chan->mem_vc_volslide = vol; } else { fx_volume_up(chan, chan->mem_vc_volslide); } break; case VOLFX_VOLSLIDEDOWN: // Dx if (start_note) { if (vol) chan->mem_vc_volslide = vol; } else { fx_volume_down(chan, chan->mem_vc_volslide); } break; case VOLFX_FINEVOLUP: // Ax if (start_note) { if (vol) chan->mem_vc_volslide = vol; else vol = chan->mem_vc_volslide; fx_volume_up(chan, vol); } break; case VOLFX_FINEVOLDOWN: // Bx if (start_note) { if (vol) chan->mem_vc_volslide = vol; else vol = chan->mem_vc_volslide; fx_volume_down(chan, vol); } break; case VOLFX_VIBRATODEPTH: // Hx fx_vibrato(chan, vol); break; case VOLFX_VIBRATOSPEED: // $x (FT2 compat.) /* Unlike the vibrato depth, this doesn't actually trigger a vibrato. */ chan->vibrato_speed = vol; break; case VOLFX_PANSLIDELEFT: // flags, chan, vol); break; case VOLFX_PANSLIDERIGHT: // >x (FT2) fx_panning_slide(csf->flags, chan, vol << 4); break; } } /* firsttick is only used for SDx at the moment */ void csf_process_effects(song_t *csf, int firsttick) { song_voice_t *chan = csf->voices; for (uint32_t nchan=0; nchann_command=0; uint32_t instr = chan->row_instr; uint32_t volcmd = chan->row_voleffect; uint32_t vol = chan->row_volparam; uint32_t cmd = chan->row_effect; uint32_t param = chan->row_param; int porta = (cmd == FX_TONEPORTAMENTO || cmd == FX_TONEPORTAVOL || volcmd == VOLFX_TONEPORTAMENTO); int start_note = csf->flags & SONG_FIRSTTICK; chan->flags &= ~(CHN_FASTVOLRAMP | CHN_NEWNOTE); // set instrument before doing anything else if (instr && start_note) chan->new_instrument = instr; // This is probably the single biggest WTF replayer bug in Impulse Tracker. // In instrument mode, when an note + instrument is triggered that does not map to any sample, the entire cell (including potentially present global effects!) // is ignored. Even better, if on a following row another instrument number (this time without a note) is encountered, we end up in the same situation! if (csf->flags & SONG_INSTRUMENTMODE && instr > 0 && instr < MAX_INSTRUMENTS && csf->instruments[instr] != NULL) { uint8_t note = (chan->row_note != NOTE_NONE) ? chan->row_note : chan->new_note; if (NOTE_IS_NOTE(note) && csf->instruments[instr]->sample_map[note - NOTE_FIRST] == 0) { chan->new_note = note; chan->row_instr = instr; chan->row_voleffect = VOLFX_NONE; chan->row_effect = FX_NONE; continue; } } /* Have to handle SDx specially because of the way the effects are structured. In a PERFECT world, this would be very straightforward: - Handle the effect column, and set flags for things that should happen (portamento, volume slides, arpeggio, vibrato, tremolo) - If note delay counter is set, stop processing that channel - Trigger all notes if it's their start tick - Handle volume column. The obvious implication of this is that all effects are checked only once, and volumes only need to be set for notes once. Additionally this helps for separating the mixing code from the rest of the interface (which is always good, especially for hardware mixing...) Oh well, the world is not perfect. */ if (cmd == FX_SPECIAL) { if (param) chan->mem_special = param; else param = chan->mem_special; if (param >> 4 == 0xd) { // Ideally this would use SONG_FIRSTTICK, but Impulse Tracker has a bug here :) if (firsttick) { chan->cd_note_delay = (param & 0xf) ? (param & 0xf) : 1; continue; // notes never play on the first tick with SDx, go away } if (--chan->cd_note_delay > 0) continue; // not our turn yet, go away start_note = (chan->cd_note_delay == 0); } } // Handles note/instrument/volume changes if (start_note) { uint32_t note = chan->row_note; /* MPT test case InstrumentNumberChange.it */ if (csf->flags & SONG_INSTRUMENTMODE && (NOTE_IS_NOTE(note) || note == NOTE_NONE)) { int instrcheck = instr ? instr : chan->last_instrument; if (instrcheck && (instrcheck < 0 || instrcheck > MAX_INSTRUMENTS || csf->instruments[instrcheck] == NULL)) { note = NOTE_NONE; instr = 0; } } if (csf->flags & SONG_INSTRUMENTMODE && instr && !NOTE_IS_NOTE(note)) { if ((porta && csf->flags & SONG_COMPATGXX) || (!porta && csf->flags & SONG_ITOLDEFFECTS)) { env_reset(chan, 1); chan->fadeout_volume = 65536; } } if (instr && note == NOTE_NONE) { if (csf->flags & SONG_INSTRUMENTMODE) { if (chan->ptr_sample) chan->volume = chan->ptr_sample->volume; } else if (instr < MAX_SAMPLES) { chan->volume = csf->samples[instr].volume; } if (csf->flags & SONG_INSTRUMENTMODE) { if (instr < MAX_INSTRUMENTS && (chan->ptr_instrument != csf->instruments[instr] || !chan->current_sample_data)) note = chan->new_note; } else { if (instr < MAX_SAMPLES && (chan->ptr_sample != &csf->samples[instr] || !chan->current_sample_data)) note = chan->new_note; } } // Invalid Instrument ? if (instr >= MAX_INSTRUMENTS) instr = 0; if (NOTE_IS_CONTROL(note)) { if (instr) { int smp = instr; if (csf->flags & SONG_INSTRUMENTMODE) { smp = 0; if (csf->instruments[instr]) smp = csf->instruments[instr]->sample_map[chan->note]; } if (smp > 0 && smp < MAX_SAMPLES) chan->volume = csf->samples[smp].volume; } if (!(csf->flags & SONG_ITOLDEFFECTS)) instr = 0; } // Note Cut/Off/Fade => ignore instrument if (NOTE_IS_CONTROL(note) || (note != NOTE_NONE && !porta)) { /* This is required when the instrument changes (KeyOff is not called) */ /* Possibly a better bugfix could be devised. --Bisqwit */ if (chan->flags & CHN_ADLIB) { //Do this only if really an adlib chan. Important! OPL_NoteOff(csf, nchan); OPL_Touch(csf, nchan, 0); } GM_KeyOff(csf, nchan); GM_Touch(csf, nchan, 0); } const uint8_t previous_new_note = chan->new_note; if (NOTE_IS_NOTE(note)) { chan->new_note = note; if (!porta) csf_check_nna(csf, nchan, instr, note, 0); if (chan->channel_panning > 0) { chan->panning = (chan->channel_panning & 0x7FFF) - 1; if (chan->channel_panning & 0x8000) chan->flags |= CHN_SURROUND; chan->channel_panning = 0; } } // Instrument Change ? if (instr) { const song_sample_t *psmp = chan->ptr_sample; //const song_instrument_t *penv = chan->ptr_instrument; csf_instrument_change(csf, chan, instr, porta, 1); if (csf->samples[instr].flags & CHN_ADLIB) { OPL_Patch(csf, nchan, csf->samples[instr].adlib_bytes); } if((csf->flags & SONG_INSTRUMENTMODE) && csf->instruments[instr]) GM_DPatch(csf, nchan, csf->instruments[instr]->midi_program, csf->instruments[instr]->midi_bank, csf->instruments[instr]->midi_channel_mask); if (NOTE_IS_NOTE(note)) { chan->new_instrument = 0; if (psmp != chan->ptr_sample) { chan->position = chan->position_frac = 0; } } } // New Note ? if (note != NOTE_NONE) { if (!instr && chan->new_instrument && NOTE_IS_NOTE(note)) { if (NOTE_IS_NOTE(previous_new_note)) chan->new_note = previous_new_note; csf_instrument_change(csf, chan, chan->new_instrument, porta, 0); if ((csf->flags & SONG_INSTRUMENTMODE) && chan->new_instrument < MAX_INSTRUMENTS && csf->instruments[chan->new_instrument]) { if (csf->samples[chan->new_instrument].flags & CHN_ADLIB) { OPL_Patch(csf, nchan, csf->samples[chan->new_instrument].adlib_bytes); } GM_DPatch(csf, nchan, csf->instruments[chan->new_instrument]->midi_program, csf->instruments[chan->new_instrument]->midi_bank, csf->instruments[chan->new_instrument]->midi_channel_mask); } chan->new_note = note; chan->new_instrument = 0; } csf_note_change(csf, nchan, note, porta, 0, !instr); } } // Initialize portamento command memory (needs to be done in exactly this order) if (firsttick) { const int effect_column_tone_porta = (cmd == FX_TONEPORTAMENTO || cmd == FX_TONEPORTAVOL); if (effect_column_tone_porta) { uint32_t toneporta_param = (cmd != FX_TONEPORTAVOL ? param : 0); if (toneporta_param) chan->mem_portanote = toneporta_param; else if(!toneporta_param && !(csf->flags & SONG_COMPATGXX)) chan->mem_portanote = chan->mem_pitchslide; if (!(csf->flags & SONG_COMPATGXX)) chan->mem_pitchslide = chan->mem_portanote; } if (volcmd == VOLFX_TONEPORTAMENTO) { if (vol) chan->mem_portanote = vc_portamento_table[vol & 0x0F]; if (!(csf->flags & SONG_COMPATGXX)) chan->mem_pitchslide = chan->mem_portanote; } if (vol && (volcmd == VOLFX_PORTAUP || volcmd == VOLFX_PORTADOWN)) { chan->mem_pitchslide = 4 * vol; if (!effect_column_tone_porta && !(csf->flags & SONG_COMPATGXX)) chan->mem_portanote = chan->mem_pitchslide; } if (param && (cmd == FX_PORTAMENTOUP || cmd == FX_PORTAMENTODOWN)) { chan->mem_pitchslide = param; if (!(csf->flags & SONG_COMPATGXX)) chan->mem_portanote = chan->mem_pitchslide; } } handle_voleffect(csf, chan, volcmd, vol, firsttick, start_note); handle_effect(csf, nchan, cmd, param, porta, firsttick); } } schismtracker-20250313/player/equalizer.c000066400000000000000000000134161476471630300202760ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "player/sndfile.h" #include "player/cmixer.h" #include "song.h" #define EQ_BANDWIDTH 2.0 #define EQ_ZERO 0.000001 typedef struct { float a0, a1, a2, b1, b2; float x1, x2, y1, y2; float gain, center_frequency; int enabled; } eq_band; //static REAL f2ic = (REAL)(1 << 28); //static REAL i2fc = (REAL)(1.0 / (1 << 28)); static eq_band eq[MAX_EQ_BANDS * 2] = { // Default: Flat EQ {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 120, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 600, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1200, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3000, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6000, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 120, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 600, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1200, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3000, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6000, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 10000, 0}, }; static void eq_filter(eq_band *pbs, int32_t *buffer, uint32_t count) { int32_t amt = (!!(audio_settings.channels-1)+1); // if 1, amt is 1, else 2 for (uint32_t i = 0; i < count; i+=amt) { float x = buffer[i]; float y = pbs->a1 * pbs->x1 + pbs->a2 * pbs->x2 + pbs->a0 * x + pbs->b1 * pbs->y1 + pbs->b2 * pbs->y2; pbs->x2 = pbs->x1; pbs->y2 = pbs->y1; pbs->x1 = x; buffer[i] = y; pbs->y1 = y; } } void normalize_mono(SCHISM_UNUSED song_t *csf, int32_t *buffer, uint32_t count) { for (uint32_t b = 0; b < count; b++) buffer[b] = _muldiv(buffer[b], audio_settings.master.left + audio_settings.master.right, 62); } void normalize_stereo(SCHISM_UNUSED song_t *csf, int32_t *buffer, uint32_t count) { uint32_t b = 0; while (b < count) { buffer[b] = _muldiv(buffer[b], audio_settings.master.left, 31); b++; buffer[b] = _muldiv(buffer[b], audio_settings.master.right, 31); b++; } } void eq_mono(SCHISM_UNUSED song_t *csf, int32_t *buffer, uint32_t count) { for (uint32_t b = 0; b < MAX_EQ_BANDS; b++) if (eq[b].enabled && eq[b].gain != 1.0f) eq_filter(&eq[b], buffer, count); } // XXX: I rolled the two loops into one. Make sure this works. void eq_stereo(SCHISM_UNUSED song_t *csf, int32_t *buffer, uint32_t count) { for (uint32_t b = 0; b < MAX_EQ_BANDS; b++) { int32_t br = b + MAX_EQ_BANDS; // Left band if (eq[b].enabled && eq[b].gain != 1.0f) eq_filter(&eq[b], buffer, count << 1); // Right band if (eq[br].enabled && eq[br].gain != 1.0f) eq_filter(&eq[br], buffer + 1, count << 1); } } void initialize_eq(int32_t reset, float freq) { //float fMixingFreq = (REAL)mix_frequency; // Gain = 0.5 (-6dB) .. 2 (+6dB) for (uint32_t band = 0; band < MAX_EQ_BANDS * 2; band++) { float k, k2, r, f; float v0, v1; int32_t b = reset; if (!eq[band].enabled) { eq[band].a0 = 0; eq[band].a1 = 0; eq[band].a2 = 0; eq[band].b1 = 0; eq[band].b2 = 0; eq[band].x1 = 0; eq[band].x2 = 0; eq[band].y1 = 0; eq[band].y2 = 0; continue; } f = eq[band].center_frequency / freq; if (f > 0.45f) eq[band].gain = 1; //if (f > 0.25) // f = 0.25; //k = tan(PI * f); k = f * 3.141592654f; k = k + k * f; //if (k > (float) 0.707) // k = (float) 0.707; k2 = k*k; v0 = eq[band].gain; v1 = 1; if (eq[band].gain < 1.0) { v0 *= 0.5f / EQ_BANDWIDTH; v1 *= 0.5f / EQ_BANDWIDTH; } else { v0 *= 1.0f / EQ_BANDWIDTH; v1 *= 1.0f / EQ_BANDWIDTH; } r = (1 + v0 * k + k2) / (1 + v1 * k + k2); if (r != eq[band].a0) { eq[band].a0 = r; b = 1; } r = 2 * (k2 - 1) / (1 + v1 * k + k2); if (r != eq[band].a1) { eq[band].a1 = r; b = 1; } r = (1 - v0 * k + k2) / (1 + v1 * k + k2); if (r != eq[band].a2) { eq[band].a2 = r; b = 1; } r = -2 * (k2 - 1) / (1 + v1 * k + k2); if (r != eq[band].b1) { eq[band].b1 = r; b = 1; } r = -(1 - v1 * k + k2) / (1 + v1 * k + k2); if (r != eq[band].b2) { eq[band].b2 = r; b = 1; } if (b) { eq[band].x1 = 0; eq[band].x2 = 0; eq[band].y1 = 0; eq[band].y2 = 0; } } } void set_eq_gains(const uint32_t *gainbuff, uint32_t gains, const uint32_t *freqs, int32_t reset, int32_t mix_freq) { for (uint32_t i = 0; i < MAX_EQ_BANDS; i++) { float g, f = 0; if (i < gains) { uint32_t n = gainbuff[i]; //if (n > 32) // n = 32; g = 1.0 + (((double) n) / 64.0); if (freqs) f = (float)(int32_t)freqs[i]; } else { g = 1; } eq[i].gain = eq[i + MAX_EQ_BANDS].gain = g; eq[i].center_frequency = eq[i + MAX_EQ_BANDS].center_frequency = f; /* don't enable bands outside... */ if (f > 20.0f && i < gains) { eq[i].enabled = eq[i + MAX_EQ_BANDS].enabled = 1; } else { eq[i].enabled = eq[i + MAX_EQ_BANDS].enabled = 0; } } initialize_eq(reset, mix_freq); } schismtracker-20250313/player/filters.c000066400000000000000000000125421476471630300177440ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "player/sndfile.h" #include "player/cmixer.h" // LUT for 2 * damping factor static const float resonance_table[128] = { 1.0000000000000000f, 0.9786446094512940f, 0.9577452540397644f, 0.9372922182083130f, 0.9172759056091309f, 0.8976871371269226f, 0.8785166740417481f, 0.8597555756568909f, 0.8413951396942139f, 0.8234267830848694f, 0.8058421611785889f, 0.7886331081390381f, 0.7717915177345276f, 0.7553095817565918f, 0.7391796708106995f, 0.7233941555023193f, 0.7079457640647888f, 0.6928272843360901f, 0.6780316829681397f, 0.6635520458221436f, 0.6493816375732422f, 0.6355138421058655f, 0.6219421625137329f, 0.6086603403091431f, 0.5956621170043945f, 0.5829415321350098f, 0.5704925656318665f, 0.5583094954490662f, 0.5463865399360657f, 0.5347182154655457f, 0.5232990980148315f, 0.5121238231658936f, 0.5011872053146362f, 0.4904841780662537f, 0.4800096750259399f, 0.4697588682174683f, 0.4597269892692566f, 0.4499093294143677f, 0.4403013288974762f, 0.4308985173702240f, 0.4216965138912201f, 0.4126909971237183f, 0.4038778245449066f, 0.3952528536319733f, 0.3868120610713959f, 0.3785515129566193f, 0.3704673945903778f, 0.3625559210777283f, 0.3548133969306946f, 0.3472362160682678f, 0.3398208320140839f, 0.3325638175010681f, 0.3254617750644684f, 0.3185114264488220f, 0.3117094635963440f, 0.3050527870655060f, 0.2985382676124573f, 0.2921628654003143f, 0.2859236001968384f, 0.2798175811767578f, 0.2738419771194458f, 0.2679939568042755f, 0.2622708380222321f, 0.2566699385643005f, 0.2511886358261108f, 0.2458244115114212f, 0.2405747324228287f, 0.2354371547698975f, 0.2304092943668366f, 0.2254888117313385f, 0.2206734120845795f, 0.2159608304500580f, 0.2113489061594009f, 0.2068354636430740f, 0.2024184018373489f, 0.1980956792831421f, 0.1938652694225311f, 0.1897251904010773f, 0.1856735348701477f, 0.1817083954811096f, 0.1778279393911362f, 0.1740303486585617f, 0.1703138649463654f, 0.1666767448186874f, 0.1631172895431519f, 0.1596338599920273f, 0.1562248021364212f, 0.1528885662555695f, 0.1496235728263855f, 0.1464282870292664f, 0.1433012634515762f, 0.1402409970760346f, 0.1372461020946503f, 0.1343151479959488f, 0.1314467936754227f, 0.1286396980285645f, 0.1258925348520279f, 0.1232040524482727f, 0.1205729842185974f, 0.1179980933666229f, 0.1154781952500343f, 0.1130121126770973f, 0.1105986908078194f, 0.1082368120551109f, 0.1059253737330437f, 0.1036632955074310f, 0.1014495193958283f, 0.0992830246686935f, 0.0971627980470657f, 0.0950878411531448f, 0.0930572077631950f, 0.0910699293017387f, 0.0891250967979431f, 0.0872217938303947f, 0.0853591337800026f, 0.0835362523794174f, 0.0817523002624512f, 0.0800064504146576f, 0.0782978758215904f, 0.0766257941722870f, 0.0749894231557846f, 0.0733879879117012f, 0.0718207582831383f, 0.0702869966626167f, 0.0687859877943993f, 0.0673170387744904f, 0.0658794566988945f, 0.0644725710153580f, }; // Simple 2-poles resonant filter // // XXX freq WAS unused but is now mix_frequency! // #define FREQ_PARAM_MULT (128.0 / (24.0 * 256.0)) void setup_channel_filter(song_voice_t *chan, int32_t reset, int32_t flt_modifier, int32_t freq) { int32_t cutoff = chan->cutoff; int32_t resonance = chan->resonance; float frequency, r, d, e, fg, fb0, fb1; cutoff = cutoff * (flt_modifier + 256) / 256; if (cutoff > 255) cutoff = 255; if (resonance > 255) resonance = 255; if (resonance == 0 && cutoff >= 254) { if (chan->flags & CHN_NEWNOTE) { // Z7F next to a note disables the filter, however in other cases this should not happen. // Test cases: filter-reset.it, filter-reset-carry.it, filter-reset-envelope.it, filter-nna.it, FilterResetPatDelay.it, FilterPortaSmpChange.it, FilterPortaSmpChange-InsMode.it chan->flags &= ~CHN_FILTER; } return; } chan->flags |= CHN_FILTER; // 2 ^ (i / 24 * 256) frequency = 110.0F * pow(2.0F, (float)cutoff * FREQ_PARAM_MULT + 0.25F); if (frequency > freq / 2.0F) frequency = freq / 2.0F; r = freq / (2.0F * M_PI * frequency); d = resonance_table[resonance] * r + resonance_table[resonance] - 1.0F; e = r * r; fg = 1.0F / (1.0F + d + e); fb0 = (d + e + e) / (1.0F + d + e); fb1 = -e / (1.0F + d + e); chan->filter_a0 = (int32_t)(fg * (1 << FILTERPRECISION)); chan->filter_b0 = (int32_t)(fb0 * (1 << FILTERPRECISION)); chan->filter_b1 = (int32_t)(fb1 * (1 << FILTERPRECISION)); if (reset) { chan->filter_y[0][0] = chan->filter_y[0][1] = 0; chan->filter_y[1][0] = chan->filter_y[1][1] = 0; } } schismtracker-20250313/player/fmopl2.c000066400000000000000000001552371476471630300175040ustar00rootroot00000000000000// license:GPL-2.0+ // copyright-holders:Jarek Burczynski,Tatsuyuki Satoh /* ** ** File: fmopl.c - software implementation of FM sound generator ** types OPL and OPL2 ** ** Copyright Jarek Burczynski (bujar at mame dot net) ** Copyright Tatsuyuki Satoh , MultiArcadeMachineEmulator development ** ** Version 0.72 ** Revision History: 04-08-2003 Jarek Burczynski: - removed BFRDY hack. BFRDY is busy flag, and it should be 0 only when the chip handles memory read/write or during the adpcm synthesis when the chip requests another byte of ADPCM data. 24-07-2003 Jarek Burczynski: - added a small hack for Y8950 status BFRDY flag (bit 3 should be set after some (unknown) delay). Right now it's always set. 14-06-2003 Jarek Burczynski: - implemented all of the status register flags in Y8950 emulation - renamed y8950_set_delta_t_memory() parameters from _rom_ to _mem_ since they can be either RAM or ROM 08-10-2002 Jarek Burczynski (thanks to Dox for the YM3526 chip) - corrected ym3526_read() to always set bit 2 and bit 1 to HIGH state - identical to ym3812_read (verified on real YM3526) 04-28-2002 Jarek Burczynski: - binary exact Envelope Generator (verified on real YM3812); compared to YM2151: the EG clock is equal to internal_clock, rates are 2 times slower and volume resolution is one bit less - modified interface functions (they no longer return pointer - that's internal to the emulator now): - new wrapper functions for OPLCreate: ym3526_init(), ym3812_init() and y8950_init() - corrected 'off by one' error in feedback calculations (when feedback is off) - enabled waveform usage (credit goes to Vlad Romascanu and zazzal22) - speeded up noise generator calculations (Nicola Salmoria) 03-24-2002 Jarek Burczynski (thanks to Dox for the YM3812 chip) Complete rewrite (all verified on real YM3812): - corrected sin_tab and tl_tab data - corrected operator output calculations - corrected waveform_select_enable register; simply: ignore all writes to waveform_select register when waveform_select_enable == 0 and do not change the waveform previously selected. - corrected KSR handling - corrected Envelope Generator: attack shape, Sustain mode and Percussive/Non-percussive modes handling - Envelope Generator rates are two times slower now - LFO amplitude (tremolo) and phase modulation (vibrato) - rhythm sounds phase generation - white noise generator (big thanks to Olivier Galibert for mentioning Berlekamp-Massey algorithm) - corrected key on/off handling (the 'key' signal is ORed from three sources: FM, rhythm and CSM) - funky details (like ignoring output of operator 1 in BD rhythm sound when connect == 1) 12-28-2001 Acho A. Tang - reflected Delta-T EOS status on Y8950 status port. - fixed subscription range of attack/decay tables To do: add delay before key off in CSM mode (see CSMKeyControll) verify volume of the FM part on the Y8950 */ #include "headers.h" #include "player/fmopl.h" // XXX why is this here? #include "log.h" /* output final shift */ #define FINAL_SH (0) #define MAXOUT INT16_MAX #define MINOUT INT16_MIN #define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ #define EG_SH 16 /* 16.16 fixed point (EG timing) */ #define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ #define TIMER_SH 16 /* 16.16 fixed point (timers calculations) */ #define FREQ_MASK ((1<>KSR */ uint8_t mul; /* multiple: mul_tab[ML] */ /* Phase Generator */ uint32_t Cnt; /* frequency counter */ uint32_t Incr; /* frequency counter step */ uint8_t FB; /* feedback shift value */ int32_t *connect1; /* slot1 output pointer */ int32_t op1_out[2]; /* slot1 output for feedback */ uint8_t CON; /* connection (algorithm) type */ /* Envelope Generator */ uint8_t eg_type; /* percussive/non-percussive mode */ uint8_t state; /* phase type */ uint32_t TL; /* total level: TL << 2 */ int32_t TLL; /* adjusted now TL */ int32_t volume; /* envelope counter */ uint32_t sl; /* sustain level: sl_tab[SL] */ uint8_t eg_sh_ar; /* (attack state) */ uint8_t eg_sel_ar; /* (attack state) */ uint8_t eg_sh_dr; /* (decay state) */ uint8_t eg_sel_dr; /* (decay state) */ uint8_t eg_sh_rr; /* (release state) */ uint8_t eg_sel_rr; /* (release state) */ uint32_t key; /* 0 = KEY OFF, >0 = KEY ON */ /* LFO */ uint32_t AMmask; /* LFO Amplitude Modulation enable mask */ uint8_t vib; /* LFO Phase Modulation enable flag (active high)*/ /* waveform select */ uint16_t wavetable; } OPL_SLOT; typedef struct { OPL_SLOT SLOT[2]; /* phase generator state */ uint32_t block_fnum; /* block+fnum */ uint32_t fc; /* Freq. Increment base */ uint32_t ksl_base; /* KeyScaleLevel Base step */ uint8_t kcode; /* key code (for key scaling) */ } OPL_CH; /* OPL state */ typedef struct { /* FM channel slots */ OPL_CH P_CH[9]; /* OPL/OPL2 chips have 9 channels*/ uint32_t eg_cnt; /* global envelope generator counter */ uint32_t eg_timer; /* global envelope generator counter works at frequency = chipclock/72 */ uint32_t eg_timer_add; /* step of eg_timer */ uint32_t eg_timer_overflow; /* envelope generator timer overlfows every 1 sample (on real chip) */ uint8_t rhythm; /* Rhythm mode */ uint32_t fn_tab[1024]; /* fnumber->increment counter */ /* LFO */ uint32_t LFO_AM; int32_t LFO_PM; uint8_t lfo_am_depth; uint8_t lfo_pm_depth_range; uint32_t lfo_am_cnt; uint32_t lfo_am_inc; uint32_t lfo_pm_cnt; uint32_t lfo_pm_inc; uint32_t noise_rng; /* 23 bit noise shift register */ uint32_t noise_p; /* current noise 'phase' */ uint32_t noise_f; /* current noise period */ uint8_t wavesel; /* waveform select enable flag */ uint32_t T[2]; /* timer counters */ uint8_t st[2]; /* timer enable */ #if BUILD_Y8950 /* Delta-T ADPCM unit (Y8950) */ YM_DELTAT *deltat; /* Keyboard and I/O ports interface */ uint8_t portDirection; uint8_t portLatch; OPL_PORTHANDLER_R porthandler_r; OPL_PORTHANDLER_W porthandler_w; void * port_param; OPL_PORTHANDLER_R keyboardhandler_r; OPL_PORTHANDLER_W keyboardhandler_w; void * keyboard_param; #endif /* external event callback handlers */ OPL_TIMERHANDLER timer_handler; /* TIMER handler */ void *TimerParam; /* TIMER parameter */ OPL_IRQHANDLER IRQHandler; /* IRQ handler */ void *IRQParam; /* IRQ parameter */ OPL_UPDATEHANDLER UpdateHandler;/* stream update handler */ void *UpdateParam; /* stream update parameter */ uint8_t type; /* chip type */ uint8_t address; /* address register */ uint8_t status; /* status flag */ uint8_t statusmask; /* status mask */ uint8_t mode; /* Reg.08 : CSM,notesel,etc. */ uint32_t clock; /* master clock (Hz) */ uint32_t rate; /* sampling rate (Hz) */ double freqbase; /* frequency base */ double TimerBase; /* Timer base time (==sampling time)*/ signed int phase_modulation; /* phase modulation input (SLOT 2) */ signed int output[1]; #if BUILD_Y8950 int32_t output_deltat[4]; /* for Y8950 DELTA-T, chip is mono, that 4 here is just for safety */ #endif } FM_OPL; /* mapping of register number (offset) to slot number used by the emulator */ static const int slot_array[32]= { 0, 2, 4, 1, 3, 5,-1,-1, 6, 8,10, 7, 9,11,-1,-1, 12,14,16,13,15,17,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1 }; /* key scale level */ /* table is 3dB/octave , DV converts this into 6dB/octave */ /* 0.1875 is bit 0 weight of the envelope counter (volume) expressed in the 'decibel' scale */ #define KSC(x) ((uint32_t)((x)/(0.1875/2.0))) static const uint32_t ksl_tab[8*16]= { /* OCT 0 */ KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), /* OCT 1 */ KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.750), KSC(1.125), KSC(1.500), KSC(1.875), KSC(2.250), KSC(2.625), KSC(3.000), /* OCT 2 */ KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(0.000), KSC(1.125), KSC(1.875), KSC(2.625), KSC(3.000), KSC(3.750), KSC(4.125), KSC(4.500), KSC(4.875), KSC(5.250), KSC(5.625), KSC(6.000), /* OCT 3 */ KSC(0.000), KSC(0.000), KSC(0.000), KSC(1.875), KSC(3.000), KSC(4.125), KSC(4.875), KSC(5.625), KSC(6.000), KSC(6.750), KSC(7.125), KSC(7.500), KSC(7.875), KSC(8.250), KSC(8.625), KSC(9.000), /* OCT 4 */ KSC(0.000), KSC(0.000), KSC(3.000), KSC(4.875), KSC(6.000), KSC(7.125), KSC(7.875), KSC(8.625), KSC(9.000), KSC(9.750),KSC(10.125),KSC(10.500), KSC(10.875),KSC(11.250),KSC(11.625),KSC(12.000), /* OCT 5 */ KSC(0.000), KSC(3.000), KSC(6.000), KSC(7.875), KSC(9.000),KSC(10.125),KSC(10.875),KSC(11.625), KSC(12.000),KSC(12.750),KSC(13.125),KSC(13.500), KSC(13.875),KSC(14.250),KSC(14.625),KSC(15.000), /* OCT 6 */ KSC(0.000), KSC(6.000), KSC(9.000),KSC(10.875), KSC(12.000),KSC(13.125),KSC(13.875),KSC(14.625), KSC(15.000),KSC(15.750),KSC(16.125),KSC(16.500), KSC(16.875),KSC(17.250),KSC(17.625),KSC(18.000), /* OCT 7 */ KSC(0.000), KSC(9.000),KSC(12.000),KSC(13.875), KSC(15.000),KSC(16.125),KSC(16.875),KSC(17.625), KSC(18.000),KSC(18.750),KSC(19.125),KSC(19.500), KSC(19.875),KSC(20.250),KSC(20.625),KSC(21.000) }; #undef KSC /* 0 / 3.0 / 1.5 / 6.0 dB/OCT */ static const uint32_t ksl_shift[4] = { 31, 1, 2, 0 }; /* sustain level table (3dB per step) */ /* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ #define SC(db) (uint32_t) ( db * (2.0/ENV_STEP) ) static const uint32_t sl_tab[16]={ SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31) }; #undef SC #define RATE_STEPS (8) static const unsigned char eg_inc[15*RATE_STEPS]={ /*cycle:0 1 2 3 4 5 6 7*/ /* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..12 0 (increment by 0 or 1) */ /* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..12 1 */ /* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..12 2 */ /* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..12 3 */ /* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 13 0 (increment by 1) */ /* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 13 1 */ /* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 13 2 */ /* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 13 3 */ /* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 14 0 (increment by 2) */ /* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 14 1 */ /*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 14 2 */ /*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 14 3 */ /*12 */ 4,4, 4,4, 4,4, 4,4, /* rates 15 0, 15 1, 15 2, 15 3 (increment by 4) */ /*13 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 2, 15 3 for attack */ /*14 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ }; #define O(a) (a*RATE_STEPS) /*note that there is no O(13) in this table - it's directly in the code */ /* Envelope Generator rates (16 + 64 rates + 16 RKS) */ static const unsigned char eg_rate_select[16+64+16]={ /* 16 infinite time rates */ O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), /* rates 00-12 */ O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), /* rate 13 */ O( 4),O( 5),O( 6),O( 7), /* rate 14 */ O( 8),O( 9),O(10),O(11), /* rate 15 */ O(12),O(12),O(12),O(12), /* 16 dummy rates (same as 15 3) */ O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), }; #undef O /*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 */ /*shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0 */ /*mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0 */ #define O(a) (a*1) /* Envelope Generator counter shifts (16 + 64 rates + 16 RKS) */ static const unsigned char eg_rate_shift[16+64+16]={ /* 16 infinite time rates */ O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), /* rates 00-12 */ O(12),O(12),O(12),O(12), O(11),O(11),O(11),O(11), O(10),O(10),O(10),O(10), O( 9),O( 9),O( 9),O( 9), O( 8),O( 8),O( 8),O( 8), O( 7),O( 7),O( 7),O( 7), O( 6),O( 6),O( 6),O( 6), O( 5),O( 5),O( 5),O( 5), O( 4),O( 4),O( 4),O( 4), O( 3),O( 3),O( 3),O( 3), O( 2),O( 2),O( 2),O( 2), O( 1),O( 1),O( 1),O( 1), O( 0),O( 0),O( 0),O( 0), /* rate 13 */ O( 0),O( 0),O( 0),O( 0), /* rate 14 */ O( 0),O( 0),O( 0),O( 0), /* rate 15 */ O( 0),O( 0),O( 0),O( 0), /* 16 dummy rates (same as 15 3) */ O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), }; #undef O /* multiple table */ #define ML 2 static const uint8_t mul_tab[16]= { /* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,10,12,12,15,15 */ ML/2, 1*ML, 2*ML, 3*ML, 4*ML, 5*ML, 6*ML, 7*ML, 8*ML, 9*ML,10*ML,10*ML,12*ML,12*ML,15*ML,15*ML }; #undef ML /* TL_TAB_LEN is calculated as: * 12 - sinus amplitude bits (Y axis) * 2 - sinus sign bit (Y axis) * TL_RES_LEN - sinus resolution (X axis) */ #define TL_TAB_LEN (12*2*TL_RES_LEN) static signed int tl_tab[TL_TAB_LEN]; #define ENV_QUIET (TL_TAB_LEN>>4) /* sin waveform table in 'decibel' scale */ /* four waveforms on OPL2 type chips */ static unsigned int sin_tab[SIN_LEN * 4]; /* LFO Amplitude Modulation table (verified on real YM3812) 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples Length: 210 elements. Each of the elements has to be repeated exactly 64 times (on 64 consecutive samples). The whole table takes: 64 * 210 = 13440 samples. When AM = 1 data is used directly When AM = 0 data is divided by 4 before being used (losing precision is important) */ #define LFO_AM_TAB_ELEMENTS 210 static const uint8_t lfo_am_table[LFO_AM_TAB_ELEMENTS] = { 0,0,0,0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, 5,5,5,5, 6,6,6,6, 7,7,7,7, 8,8,8,8, 9,9,9,9, 10,10,10,10, 11,11,11,11, 12,12,12,12, 13,13,13,13, 14,14,14,14, 15,15,15,15, 16,16,16,16, 17,17,17,17, 18,18,18,18, 19,19,19,19, 20,20,20,20, 21,21,21,21, 22,22,22,22, 23,23,23,23, 24,24,24,24, 25,25,25,25, 26,26,26, 25,25,25,25, 24,24,24,24, 23,23,23,23, 22,22,22,22, 21,21,21,21, 20,20,20,20, 19,19,19,19, 18,18,18,18, 17,17,17,17, 16,16,16,16, 15,15,15,15, 14,14,14,14, 13,13,13,13, 12,12,12,12, 11,11,11,11, 10,10,10,10, 9,9,9,9, 8,8,8,8, 7,7,7,7, 6,6,6,6, 5,5,5,5, 4,4,4,4, 3,3,3,3, 2,2,2,2, 1,1,1,1 }; /* LFO Phase Modulation table (verified on real YM3812) */ static const int8_t lfo_pm_table[8*8*2] = { /* FNUM2/FNUM = 00 0xxxxxxx (0x0000) */ 0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/ 0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 00 1xxxxxxx (0x0080) */ 0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/ 1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 01 0xxxxxxx (0x0100) */ 1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/ 2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 01 1xxxxxxx (0x0180) */ 1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/ 3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 10 0xxxxxxx (0x0200) */ 2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/ 4, 2, 0,-2,-4,-2, 0, 2, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 10 1xxxxxxx (0x0280) */ 2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/ 5, 2, 0,-2,-5,-2, 0, 2, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 11 0xxxxxxx (0x0300) */ 3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/ 6, 3, 0,-3,-6,-3, 0, 3, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 11 1xxxxxxx (0x0380) */ 3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/ 7, 3, 0,-3,-7,-3, 0, 3 /*LFO PM depth = 1*/ }; /* lock level of common table */ static int num_lock = 0; #define SLOT7_1 (&OPL->P_CH[7].SLOT[SLOT1]) #define SLOT7_2 (&OPL->P_CH[7].SLOT[SLOT2]) #define SLOT8_1 (&OPL->P_CH[8].SLOT[SLOT1]) #define SLOT8_2 (&OPL->P_CH[8].SLOT[SLOT2]) static inline int limit( int val, int max, int min ) { if ( val > max ) val = max; else if ( val < min ) val = min; return val; } /* status set and IRQ handling */ static inline void OPL_STATUS_SET(FM_OPL *OPL,int flag) { /* set status flag */ OPL->status |= flag; if(!(OPL->status & 0x80)) { if(OPL->status & OPL->statusmask) { /* IRQ on */ OPL->status |= 0x80; /* callback user interrupt handler (IRQ is OFF to ON) */ if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,1); } } } /* status reset and IRQ handling */ static inline void OPL_STATUS_RESET(FM_OPL *OPL,int flag) { /* reset status flag */ OPL->status &=~flag; if((OPL->status & 0x80)) { if (!(OPL->status & OPL->statusmask) ) { OPL->status &= 0x7f; /* callback user interrupt handler (IRQ is ON to OFF) */ if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); } } } /* IRQ mask set */ static inline void OPL_STATUSMASK_SET(FM_OPL *OPL,int flag) { OPL->statusmask = flag; /* IRQ handling check */ OPL_STATUS_SET(OPL,0); OPL_STATUS_RESET(OPL,0); } /* advance LFO to next sample */ static inline void advance_lfo(FM_OPL *OPL) { uint8_t tmp; /* LFO */ OPL->lfo_am_cnt += OPL->lfo_am_inc; if (OPL->lfo_am_cnt >= ((uint32_t)LFO_AM_TAB_ELEMENTS<lfo_am_cnt -= ((uint32_t)LFO_AM_TAB_ELEMENTS<lfo_am_cnt >> LFO_SH ]; if (OPL->lfo_am_depth) OPL->LFO_AM = tmp; else OPL->LFO_AM = tmp>>2; OPL->lfo_pm_cnt += OPL->lfo_pm_inc; OPL->LFO_PM = ((OPL->lfo_pm_cnt>>LFO_SH) & 7) | OPL->lfo_pm_depth_range; } /* advance to next sample */ static inline void advance(FM_OPL *OPL) { OPL_CH *CH; OPL_SLOT *op; int i; OPL->eg_timer += OPL->eg_timer_add; while (OPL->eg_timer >= OPL->eg_timer_overflow) { OPL->eg_timer -= OPL->eg_timer_overflow; OPL->eg_cnt++; for (i=0; i<9*2; i++) { CH = &OPL->P_CH[i/2]; op = &CH->SLOT[i&1]; /* Envelope Generator */ switch(op->state) { case EG_ATT: /* attack phase */ if ( !(OPL->eg_cnt & ((1<eg_sh_ar)-1) ) ) { op->volume += (~op->volume * (eg_inc[op->eg_sel_ar + ((OPL->eg_cnt>>op->eg_sh_ar)&7)]) ) >>3; if (op->volume <= MIN_ATT_INDEX) { op->volume = MIN_ATT_INDEX; op->state = EG_DEC; } } break; case EG_DEC: /* decay phase */ if ( !(OPL->eg_cnt & ((1<eg_sh_dr)-1) ) ) { op->volume += eg_inc[op->eg_sel_dr + ((OPL->eg_cnt>>op->eg_sh_dr)&7)]; if ( (uint32_t)op->volume >= op->sl ) op->state = EG_SUS; } break; case EG_SUS: /* sustain phase */ /* this is important behaviour: one can change percusive/non-percussive modes on the fly and the chip will remain in sustain phase - verified on real YM3812 */ if(op->eg_type) /* non-percussive mode */ { /* do nothing */ } else /* percussive mode */ { /* during sustain phase chip adds Release Rate (in percussive mode) */ if ( !(OPL->eg_cnt & ((1<eg_sh_rr)-1) ) ) { op->volume += eg_inc[op->eg_sel_rr + ((OPL->eg_cnt>>op->eg_sh_rr)&7)]; if ( op->volume >= MAX_ATT_INDEX ) op->volume = MAX_ATT_INDEX; } /* else do nothing in sustain phase */ } break; case EG_REL: /* release phase */ if ( !(OPL->eg_cnt & ((1<eg_sh_rr)-1) ) ) { op->volume += eg_inc[op->eg_sel_rr + ((OPL->eg_cnt>>op->eg_sh_rr)&7)]; if ( op->volume >= MAX_ATT_INDEX ) { op->volume = MAX_ATT_INDEX; op->state = EG_OFF; } } break; default: break; } } } for (i=0; i<9*2; i++) { CH = &OPL->P_CH[i/2]; op = &CH->SLOT[i&1]; /* Phase Generator */ if(op->vib) { uint8_t block; uint32_t block_fnum = CH->block_fnum; unsigned int fnum_lfo = (block_fnum&0x0380) >> 7; signed int lfo_fn_table_index_offset = lfo_pm_table[OPL->LFO_PM + 16*fnum_lfo ]; if (lfo_fn_table_index_offset) /* LFO phase modulation active */ { block_fnum += lfo_fn_table_index_offset; block = (block_fnum&0x1c00) >> 10; op->Cnt += (OPL->fn_tab[block_fnum&0x03ff] >> (7-block)) * op->mul; } else /* LFO phase modulation = zero */ { op->Cnt += op->Incr; } } else /* LFO phase modulation disabled for this operator */ { op->Cnt += op->Incr; } } /* The Noise Generator of the YM3812 is 23-bit shift register. * Period is equal to 2^23-2 samples. * Register works at sampling frequency of the chip, so output * can change on every sample. * * Output of the register and input to the bit 22 is: * bit0 XOR bit14 XOR bit15 XOR bit22 * * Simply use bit 22 as the noise output. */ OPL->noise_p += OPL->noise_f; i = OPL->noise_p >> FREQ_SH; /* number of events (shifts of the shift register) */ OPL->noise_p &= FREQ_MASK; while (i) { /* uint32_t j; j = ( (OPL->noise_rng) ^ (OPL->noise_rng>>14) ^ (OPL->noise_rng>>15) ^ (OPL->noise_rng>>22) ) & 1; OPL->noise_rng = (j<<22) | (OPL->noise_rng>>1); */ /* Instead of doing all the logic operations above, we use a trick here (and use bit 0 as the noise output). The difference is only that the noise bit changes one step ahead. This doesn't matter since we don't know what is real state of the noise_rng after the reset. */ if (OPL->noise_rng & 1) OPL->noise_rng ^= 0x800302; OPL->noise_rng >>= 1; i--; } } static inline signed int op_calc(uint32_t phase, unsigned int env, signed int pm, unsigned int wave_tab) { uint32_t p; p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + (pm << 16))) >> FREQ_SH) & SIN_MASK)]; if (p >= TL_TAB_LEN) return 0; return tl_tab[p]; } static inline signed int op_calc1(uint32_t phase, unsigned int env, signed int pm, unsigned int wave_tab) { uint32_t p; p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + pm)) >> FREQ_SH) & SIN_MASK)]; if (p >= TL_TAB_LEN) return 0; return tl_tab[p]; } #define volume_calc(OP) ((OP)->TLL + ((uint32_t)(OP)->volume) + (OPL->LFO_AM & (OP)->AMmask)) /* calculate output */ static inline void OPL_CALC_CH( FM_OPL *OPL, OPL_CH *CH ) { OPL_SLOT *SLOT; unsigned int env; signed int out; OPL->phase_modulation = 0; /* SLOT 1 */ SLOT = &CH->SLOT[SLOT1]; env = volume_calc(SLOT); out = SLOT->op1_out[0] + SLOT->op1_out[1]; SLOT->op1_out[0] = SLOT->op1_out[1]; *SLOT->connect1 += SLOT->op1_out[0]; SLOT->op1_out[1] = 0; if( env < ENV_QUIET ) { if (!SLOT->FB) out = 0; SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<FB), SLOT->wavetable ); } /* SLOT 2 */ SLOT++; env = volume_calc(SLOT); if( env < ENV_QUIET ) OPL->output[0] += op_calc(SLOT->Cnt, env, OPL->phase_modulation, SLOT->wavetable); } /* operators used in the rhythm sounds generation process: Envelope Generator: channel operator register number Bass High Snare Tom Top / slot number TL ARDR SLRR Wave Drum Hat Drum Tom Cymbal 6 / 0 12 50 70 90 f0 + 6 / 1 15 53 73 93 f3 + 7 / 0 13 51 71 91 f1 + 7 / 1 16 54 74 94 f4 + 8 / 0 14 52 72 92 f2 + 8 / 1 17 55 75 95 f5 + Phase Generator: channel operator register number Bass High Snare Tom Top / slot number MULTIPLE Drum Hat Drum Tom Cymbal 6 / 0 12 30 + 6 / 1 15 33 + 7 / 0 13 31 + + + 7 / 1 16 34 ----- n o t u s e d ----- 8 / 0 14 32 + 8 / 1 17 35 + + channel operator register number Bass High Snare Tom Top number number BLK/FNUM2 FNUM Drum Hat Drum Tom Cymbal 6 12,15 B6 A6 + 7 13,16 B7 A7 + + + 8 14,17 B8 A8 + + + */ /* calculate rhythm */ static inline void OPL_CALC_RH( FM_OPL *OPL, OPL_CH *CH, unsigned int noise ) { OPL_SLOT *SLOT; signed int out; unsigned int env; /* Bass Drum (verified on real YM3812): - depends on the channel 6 'connect' register: when connect = 0 it works the same as in normal (non-rhythm) mode (op1->op2->out) when connect = 1 _only_ operator 2 is present on output (op2->out), operator 1 is ignored - output sample always is multiplied by 2 */ OPL->phase_modulation = 0; /* SLOT 1 */ SLOT = &CH[6].SLOT[SLOT1]; env = volume_calc(SLOT); out = SLOT->op1_out[0] + SLOT->op1_out[1]; SLOT->op1_out[0] = SLOT->op1_out[1]; if (!SLOT->CON) OPL->phase_modulation = SLOT->op1_out[0]; /* else ignore output of operator 1 */ SLOT->op1_out[1] = 0; if( env < ENV_QUIET ) { if (!SLOT->FB) out = 0; SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<FB), SLOT->wavetable ); } /* SLOT 2 */ SLOT++; env = volume_calc(SLOT); if( env < ENV_QUIET ) OPL->output[0] += op_calc(SLOT->Cnt, env, OPL->phase_modulation, SLOT->wavetable) * 2; /* Phase generation is based on: */ /* HH (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) */ /* SD (16) channel 7->slot 1 */ /* TOM (14) channel 8->slot 1 */ /* TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) */ /* Envelope generation based on: */ /* HH channel 7->slot1 */ /* SD channel 7->slot2 */ /* TOM channel 8->slot1 */ /* TOP channel 8->slot2 */ /* The following formulas can be well optimized. I leave them in direct form for now (in case I've missed something). */ /* High Hat (verified on real YM3812) */ env = volume_calc(SLOT7_1); if( env < ENV_QUIET ) { /* high hat phase generation: phase = d0 or 234 (based on frequency only) phase = 34 or 2d0 (based on noise) */ /* base frequency derived from operator 1 in channel 7 */ unsigned char bit7 = ((SLOT7_1->Cnt>>FREQ_SH)>>7)&1; unsigned char bit3 = ((SLOT7_1->Cnt>>FREQ_SH)>>3)&1; unsigned char bit2 = ((SLOT7_1->Cnt>>FREQ_SH)>>2)&1; unsigned char res1 = (bit2 ^ bit7) | bit3; /* when res1 = 0 phase = 0x000 | 0xd0; */ /* when res1 = 1 phase = 0x200 | (0xd0>>2); */ uint32_t phase = res1 ? (0x200|(0xd0>>2)) : 0xd0; /* enable gate based on frequency of operator 2 in channel 8 */ unsigned char bit5e= ((SLOT8_2->Cnt>>FREQ_SH)>>5)&1; unsigned char bit3e= ((SLOT8_2->Cnt>>FREQ_SH)>>3)&1; unsigned char res2 = (bit3e ^ bit5e); /* when res2 = 0 pass the phase from calculation above (res1); */ /* when res2 = 1 phase = 0x200 | (0xd0>>2); */ if (res2) phase = (0x200|(0xd0>>2)); /* when phase & 0x200 is set and noise=1 then phase = 0x200|0xd0 */ /* when phase & 0x200 is set and noise=0 then phase = 0x200|(0xd0>>2), ie no change */ if (phase&0x200) { if (noise) phase = 0x200|0xd0; } else /* when phase & 0x200 is clear and noise=1 then phase = 0xd0>>2 */ /* when phase & 0x200 is clear and noise=0 then phase = 0xd0, ie no change */ { if (noise) phase = 0xd0>>2; } OPL->output[0] += op_calc(phase<wavetable) * 2; } /* Snare Drum (verified on real YM3812) */ env = volume_calc(SLOT7_2); if( env < ENV_QUIET ) { /* base frequency derived from operator 1 in channel 7 */ unsigned char bit8 = ((SLOT7_1->Cnt>>FREQ_SH)>>8)&1; /* when bit8 = 0 phase = 0x100; */ /* when bit8 = 1 phase = 0x200; */ uint32_t phase = bit8 ? 0x200 : 0x100; /* Noise bit XOR'es phase by 0x100 */ /* when noisebit = 0 pass the phase from calculation above */ /* when noisebit = 1 phase ^= 0x100; */ /* in other words: phase ^= (noisebit<<8); */ if (noise) phase ^= 0x100; OPL->output[0] += op_calc(phase<wavetable) * 2; } /* Tom Tom (verified on real YM3812) */ env = volume_calc(SLOT8_1); if( env < ENV_QUIET ) OPL->output[0] += op_calc(SLOT8_1->Cnt, env, 0, SLOT8_1->wavetable) * 2; /* Top Cymbal (verified on real YM3812) */ env = volume_calc(SLOT8_2); if( env < ENV_QUIET ) { /* base frequency derived from operator 1 in channel 7 */ unsigned char bit7 = ((SLOT7_1->Cnt>>FREQ_SH)>>7)&1; unsigned char bit3 = ((SLOT7_1->Cnt>>FREQ_SH)>>3)&1; unsigned char bit2 = ((SLOT7_1->Cnt>>FREQ_SH)>>2)&1; unsigned char res1 = (bit2 ^ bit7) | bit3; /* when res1 = 0 phase = 0x000 | 0x100; */ /* when res1 = 1 phase = 0x200 | 0x100; */ uint32_t phase = res1 ? 0x300 : 0x100; /* enable gate based on frequency of operator 2 in channel 8 */ unsigned char bit5e= ((SLOT8_2->Cnt>>FREQ_SH)>>5)&1; unsigned char bit3e= ((SLOT8_2->Cnt>>FREQ_SH)>>3)&1; unsigned char res2 = (bit3e ^ bit5e); /* when res2 = 0 pass the phase from calculation above (res1); */ /* when res2 = 1 phase = 0x200 | 0x100; */ if (res2) phase = 0x300; OPL->output[0] += op_calc(phase<wavetable) * 2; } } /* generic table initialize */ static int init_tables(void) { signed int i,x; signed int n; double o,m; for (x=0; x>= 4; /* 12 bits here */ if (n&1) /* round to nearest */ n = (n>>1)+1; else n = n>>1; /* 11 bits here (rounded) */ n <<= 1; /* 12 bits here (as in real chip) */ tl_tab[ x*2 + 0 ] = n; tl_tab[ x*2 + 1 ] = -tl_tab[ x*2 + 0 ]; for (i=1; i<12; i++) { tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; } #if 0 logerror("tl %04i", x*2); for (i=0; i<12; i++) logerror(", [%02i] %5i", i*2, tl_tab[ x*2 /*+1*/ + i*2*TL_RES_LEN ] ); logerror("\n"); #endif } /*logerror("FMOPL.C: TL_TAB_LEN = %i elements (%i bytes)\n",TL_TAB_LEN, (int)sizeof(tl_tab));*/ for (i=0; i0.0) o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ else o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ o = o / (ENV_STEP/4); n = (int)(2.0*o); if (n&1) /* round to nearest */ n = (n>>1)+1; else n = n>>1; sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); /*logerror("FMOPL.C: sin [%4i (hex=%03x)]= %4i (tl_tab value=%5i)\n", i, i, sin_tab[i], tl_tab[sin_tab[i]] );*/ } for (i=0; i>1) ]; /* waveform 3: _ _ _ _ */ /* / |_/ |_/ |_/ |_*/ /* abs(output only first quarter of the sinus waveform) */ if (i & (1<<(SIN_BITS-2)) ) sin_tab[3*SIN_LEN+i] = TL_TAB_LEN; else sin_tab[3*SIN_LEN+i] = sin_tab[i & (SIN_MASK>>2)]; /*logerror("FMOPL.C: sin1[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[1*SIN_LEN+i], tl_tab[sin_tab[1*SIN_LEN+i]] ); logerror("FMOPL.C: sin2[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[2*SIN_LEN+i], tl_tab[sin_tab[2*SIN_LEN+i]] ); logerror("FMOPL.C: sin3[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[3*SIN_LEN+i], tl_tab[sin_tab[3*SIN_LEN+i]] );*/ } /*logerror("FMOPL.C: ENV_QUIET= %08x (dec*8=%i)\n", ENV_QUIET, ENV_QUIET*8 );*/ return 1; } static void OPLCloseTable( void ) { } static void OPL_initalize(FM_OPL *OPL) { int i; /* frequency base */ OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / 72.0) / OPL->rate : 0; #if 0 OPL->rate = (double)OPL->clock / 72.0; OPL->freqbase = 1.0; #endif /*logerror("freqbase=%f\n", OPL->freqbase);*/ /* Timer base time */ OPL->TimerBase = 72.0 / (double)OPL->clock; /* make fnumber -> increment counter table */ for( i=0 ; i < 1024 ; i++ ) { /* opn phase increment counter = 20bit */ /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ OPL->fn_tab[i] = (uint32_t)( (double)i * 64 * OPL->freqbase * (1<<(FREQ_SH-10)) ); #if 0 logerror("FMOPL.C: fn_tab[%4i] = %08x (dec=%8i)\n", i, OPL->fn_tab[i]>>6, OPL->fn_tab[i]>>6 ); #endif } #if 0 for( i=0 ; i < 16 ; i++ ) { logerror("FMOPL.C: sl_tab[%i] = %08x\n", i, sl_tab[i] ); } for( i=0 ; i < 8 ; i++ ) { int j; logerror("FMOPL.C: ksl_tab[oct=%2i] =",i); for (j=0; j<16; j++) { logerror("%08x ", ksl_tab[i*16+j] ); } logerror("\n"); } #endif /* Amplitude modulation: 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples */ /* One entry from LFO_AM_TABLE lasts for 64 samples */ OPL->lfo_am_inc = (uint32_t)((1.0 / 64.0 ) * (1<freqbase); /* Vibrato: 8 output levels (triangle waveform); 1 level takes 1024 samples */ OPL->lfo_pm_inc = (uint32_t)((1.0 / 1024.0) * (1<freqbase); /*logerror ("OPL->lfo_am_inc = %8x ; OPL->lfo_pm_inc = %8x\n", OPL->lfo_am_inc, OPL->lfo_pm_inc);*/ /* Noise generator: a step takes 1 sample */ OPL->noise_f = (uint32_t)((1.0 / 1.0) * (1<freqbase); OPL->eg_timer_add = (uint32_t)((1<freqbase); OPL->eg_timer_overflow = ( 1 ) * (1<eg_timer_add, OPL->eg_timer_overflow);*/ } static inline void FM_KEYON(OPL_SLOT *SLOT, uint32_t key_set) { if( !SLOT->key ) { /* restart Phase Generator */ SLOT->Cnt = 0; /* phase -> Attack */ SLOT->state = EG_ATT; } SLOT->key |= key_set; } static inline void FM_KEYOFF(OPL_SLOT *SLOT, uint32_t key_clr) { if( SLOT->key ) { SLOT->key &= key_clr; if( !SLOT->key ) { /* phase -> Release */ if (SLOT->state>EG_REL) SLOT->state = EG_REL; } } } /* update phase increment counter of operator (also update the EG rates if necessary) */ static inline void CALC_FCSLOT(OPL_CH *CH,OPL_SLOT *SLOT) { int ksr; /* (frequency) phase increment counter */ SLOT->Incr = CH->fc * SLOT->mul; ksr = CH->kcode >> SLOT->KSR; if( SLOT->ksr != ksr ) { SLOT->ksr = ksr; /* calculate envelope generator rates */ if ((SLOT->ar + SLOT->ksr) < 16+62) { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_sel_ar = 13*RATE_STEPS; } SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; } } /* set multi,am,vib,EG-TYP,KSR,mul */ static inline void set_mul(FM_OPL *OPL,int slot,int v) { OPL_CH *CH = &OPL->P_CH[slot/2]; OPL_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->mul = mul_tab[v&0x0f]; SLOT->KSR = (v&0x10) ? 0 : 2; SLOT->eg_type = (v&0x20); SLOT->vib = (v&0x40); SLOT->AMmask = (v&0x80) ? ~0 : 0; CALC_FCSLOT(CH,SLOT); } /* set ksl & tl */ static inline void set_ksl_tl(FM_OPL *OPL,int slot,int v) { OPL_CH *CH = &OPL->P_CH[slot/2]; OPL_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->ksl = ksl_shift[v >> 6]; SLOT->TL = (v&0x3f)<<(ENV_BITS-1-7); /* 7 bits TL (bit 6 = always 0) */ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); } /* set attack rate & decay rate */ static inline void set_ar_dr(FM_OPL *OPL,int slot,int v) { OPL_CH *CH = &OPL->P_CH[slot/2]; OPL_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->ar = (v>>4) ? 16 + ((v>>4) <<2) : 0; if ((SLOT->ar + SLOT->ksr) < 16+62) { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_sel_ar = 13*RATE_STEPS; } SLOT->dr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; } /* set sustain level & release rate */ static inline void set_sl_rr(FM_OPL *OPL,int slot,int v) { OPL_CH *CH = &OPL->P_CH[slot/2]; OPL_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->sl = sl_tab[ v>>4 ]; SLOT->rr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; } /* write a value v to register r on OPL chip */ static void OPLWriteReg(FM_OPL *OPL, int r, int v) { OPL_CH *CH; int slot; uint32_t block_fnum; /* adjust bus to 8 bits */ r &= 0xff; v &= 0xff; switch(r&0xe0) { case 0x00: /* 00-1f:control */ switch(r&0x1f) { case 0x01: /* waveform select enable */ if(OPL->type&OPL_TYPE_WAVESEL) { OPL->wavesel = v&0x20; /* do not change the waveform previously selected */ } break; case 0x02: /* Timer 1 */ OPL->T[0] = (256-v)*4; break; case 0x03: /* Timer 2 */ OPL->T[1] = (256-v)*16; break; case 0x04: /* IRQ clear / mask and Timer enable */ if(v&0x80) { /* IRQ flag clear */ /* don't reset BFRDY flag or we will have to call deltat module to set the flag */ OPL_STATUS_RESET(OPL,0x7f-0x08); } else { /* set IRQ mask ,timer enable*/ uint8_t st1 = v&1; uint8_t st2 = (v>>1)&1; /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ OPL_STATUS_RESET(OPL, v & (0x78-0x08) ); OPL_STATUSMASK_SET(OPL, (~v) & 0x78 ); /* timer 2 */ if(OPL->st[1] != st2) { double period = st2 ? (OPL->TimerBase * OPL->T[1]) : 0.0; OPL->st[1] = st2; if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,1,period); } /* timer 1 */ if(OPL->st[0] != st1) { double period = st1 ? (OPL->TimerBase * OPL->T[0]) : 0.0; OPL->st[0] = st1; if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,0,period); } } break; #if BUILD_Y8950 case 0x06: /* Key Board OUT */ if(OPL->type&OPL_TYPE_KEYBOARD) { if(OPL->keyboardhandler_w) OPL->keyboardhandler_w(OPL->keyboard_param,v); else logerror("Y8950: write unmapped KEYBOARD port\n"); } break; case 0x07: /* DELTA-T control 1 : START,REC,MEMDATA,REPT,SPOFF,x,x,RST */ if(OPL->type&OPL_TYPE_ADPCM) YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); break; #endif case 0x08: /* MODE,DELTA-T control 2 : CSM,NOTESEL,x,x,smpl,da/ad,64k,rom */ OPL->mode = v; #if BUILD_Y8950 if(OPL->type&OPL_TYPE_ADPCM) { /* mask 4 LSBs in register 08 for DELTA-T unit */ YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v&0x0f); } #endif break; #if BUILD_Y8950 case 0x09: /* START ADD */ case 0x0a: case 0x0b: /* STOP ADD */ case 0x0c: case 0x0d: /* PRESCALE */ case 0x0e: case 0x0f: /* ADPCM data write */ case 0x10: /* DELTA-N */ case 0x11: /* DELTA-N */ case 0x12: /* ADPCM volume */ if(OPL->type&OPL_TYPE_ADPCM) YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); break; case 0x15: /* DAC data high 8 bits (F7,F6...F2) */ case 0x16: /* DAC data low 2 bits (F1, F0 in bits 7,6) */ case 0x17: /* DAC data shift (S2,S1,S0 in bits 2,1,0) */ logerror("FMOPL.C: DAC data register written, but not implemented reg=%02x val=%02x\n", r,v); break; case 0x18: /* I/O CTRL (Direction) */ if(OPL->type&OPL_TYPE_IO) OPL->portDirection = v&0x0f; break; case 0x19: /* I/O DATA */ if(OPL->type&OPL_TYPE_IO) { OPL->portLatch = v; if(OPL->porthandler_w) OPL->porthandler_w(OPL->port_param,v&OPL->portDirection); } break; #endif default: /*logerror("FMOPL.C: write to unknown register: %02x\n",r);*/ break; } break; case 0x20: /* am ON, vib ON, ksr, eg_type, mul */ slot = slot_array[r&0x1f]; if(slot < 0) return; set_mul(OPL,slot,v); break; case 0x40: slot = slot_array[r&0x1f]; if(slot < 0) return; set_ksl_tl(OPL,slot,v); break; case 0x60: slot = slot_array[r&0x1f]; if(slot < 0) return; set_ar_dr(OPL,slot,v); break; case 0x80: slot = slot_array[r&0x1f]; if(slot < 0) return; set_sl_rr(OPL,slot,v); break; case 0xa0: if (r == 0xbd) /* am depth, vibrato depth, r,bd,sd,tom,tc,hh */ { OPL->lfo_am_depth = v & 0x80; OPL->lfo_pm_depth_range = (v&0x40) ? 8 : 0; OPL->rhythm = v&0x3f; if(OPL->rhythm&0x20) { /* BD key on/off */ if(v&0x10) { FM_KEYON (&OPL->P_CH[6].SLOT[SLOT1], 2); FM_KEYON (&OPL->P_CH[6].SLOT[SLOT2], 2); } else { FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1],~2); FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2],~2); } /* HH key on/off */ if(v&0x01) FM_KEYON (&OPL->P_CH[7].SLOT[SLOT1], 2); else FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1],~2); /* SD key on/off */ if(v&0x08) FM_KEYON (&OPL->P_CH[7].SLOT[SLOT2], 2); else FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2],~2); /* TOM key on/off */ if(v&0x04) FM_KEYON (&OPL->P_CH[8].SLOT[SLOT1], 2); else FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1],~2); /* TOP-CY key on/off */ if(v&0x02) FM_KEYON (&OPL->P_CH[8].SLOT[SLOT2], 2); else FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2],~2); } else { /* BD key off */ FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1],~2); FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2],~2); /* HH key off */ FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1],~2); /* SD key off */ FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2],~2); /* TOM key off */ FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1],~2); /* TOP-CY off */ FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2],~2); } return; } /* keyon,block,fnum */ if( (r&0x0f) > 8) return; CH = &OPL->P_CH[r&0x0f]; if(!(r&0x10)) { /* a0-a8 */ block_fnum = (CH->block_fnum&0x1f00) | v; } else { /* b0-b8 */ block_fnum = ((v&0x1f)<<8) | (CH->block_fnum&0xff); if(v&0x20) { FM_KEYON (&CH->SLOT[SLOT1], 1); FM_KEYON (&CH->SLOT[SLOT2], 1); } else { FM_KEYOFF(&CH->SLOT[SLOT1],~1); FM_KEYOFF(&CH->SLOT[SLOT2],~1); } } /* update */ if(CH->block_fnum != block_fnum) { uint8_t block = block_fnum >> 10; CH->block_fnum = block_fnum; CH->ksl_base = ksl_tab[block_fnum>>6]; CH->fc = OPL->fn_tab[block_fnum&0x03ff] >> (7-block); /* BLK 2,1,0 bits -> bits 3,2,1 of kcode */ CH->kcode = (CH->block_fnum&0x1c00)>>9; /* the info below is actually opposite to what is stated in the Manuals (verifed on real YM3812) */ /* if notesel == 0 -> lsb of kcode is bit 10 (MSB) of fnum */ /* if notesel == 1 -> lsb of kcode is bit 9 (MSB-1) of fnum */ if (OPL->mode&0x40) CH->kcode |= (CH->block_fnum&0x100)>>8; /* notesel == 1 */ else CH->kcode |= (CH->block_fnum&0x200)>>9; /* notesel == 0 */ /* refresh Total Level in both SLOTs of this channel */ CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); /* refresh frequency counter in both SLOTs of this channel */ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); } break; case 0xc0: /* FB,C */ if( (r&0x0f) > 8) return; CH = &OPL->P_CH[r&0x0f]; CH->SLOT[SLOT1].FB = (v>>1)&7 ? ((v>>1)&7) + 7 : 0; CH->SLOT[SLOT1].CON = v&1; CH->SLOT[SLOT1].connect1 = CH->SLOT[SLOT1].CON ? &OPL->output[0] : &OPL->phase_modulation; break; case 0xe0: /* waveform select */ /* simply ignore write to the waveform select register if selecting not enabled in test register */ if(OPL->wavesel) { slot = slot_array[r&0x1f]; if(slot < 0) return; CH = &OPL->P_CH[slot/2]; CH->SLOT[slot&1].wavetable = (v&0x03)*SIN_LEN; } break; } } /* lock/unlock for common table */ static int OPL_LockTable() { num_lock++; if(num_lock>1) return 0; /* first time */ /* allocate total level table (128kb space) */ if( !init_tables() ) { num_lock--; return -1; } return 0; } static void OPL_UnLockTable(void) { if(num_lock) num_lock--; if(num_lock) return; /* last time */ OPLCloseTable(); } static void OPLResetChip(FM_OPL *OPL) { int c,s; int i; OPL->eg_timer = 0; OPL->eg_cnt = 0; OPL->noise_rng = 1; /* noise shift register */ OPL->mode = 0; /* normal mode */ OPL_STATUS_RESET(OPL,0x7f); /* reset with register write */ OPLWriteReg(OPL,0x01,0); /* wavesel disable */ OPLWriteReg(OPL,0x02,0); /* Timer1 */ OPLWriteReg(OPL,0x03,0); /* Timer2 */ OPLWriteReg(OPL,0x04,0); /* IRQ mask clear */ for(i = 0xff ; i >= 0x20 ; i-- ) OPLWriteReg(OPL,i,0); /* reset operator parameters */ for( c = 0 ; c < 9 ; c++ ) { OPL_CH *CH = &OPL->P_CH[c]; for(s = 0 ; s < 2 ; s++ ) { /* wave table */ CH->SLOT[s].wavetable = 0; CH->SLOT[s].state = EG_OFF; CH->SLOT[s].volume = MAX_ATT_INDEX; } } #if BUILD_Y8950 if(OPL->type&OPL_TYPE_ADPCM) { YM_DELTAT *DELTAT = OPL->deltat; DELTAT->freqbase = OPL->freqbase; DELTAT->output_pointer = &OPL->output_deltat[0]; DELTAT->portshift = 5; DELTAT->output_range = 1<<23; YM_DELTAT_ADPCM_Reset(DELTAT,0,YM_DELTAT_EMULATION_MODE_NORMAL); } #endif } #if 0 // not used anywhere static void OPL_postload(FM_OPL *OPL) { int slot, ch; for( ch=0 ; ch < 9 ; ch++ ) { OPL_CH *CH = &OPL->P_CH[ch]; /* Look up key scale level */ uint32_t block_fnum = CH->block_fnum; CH->ksl_base = ksl_tab[block_fnum >> 6]; CH->fc = OPL->fn_tab[block_fnum & 0x03ff] >> (7 - (block_fnum >> 10)); for( slot=0 ; slot < 2 ; slot++ ) { OPL_SLOT *SLOT = &CH->SLOT[slot]; /* Calculate key scale rate */ SLOT->ksr = CH->kcode >> SLOT->KSR; /* Calculate attack, decay and release rates */ if ((SLOT->ar + SLOT->ksr) < 16+62) { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_sel_ar = 13*RATE_STEPS; } SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; /* Calculate phase increment */ SLOT->Incr = CH->fc * SLOT->mul; /* Total level */ SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl); /* Connect output */ SLOT->connect1 = SLOT->CON ? &OPL->output[0] : &OPL->phase_modulation; } } #if BUILD_Y8950 if ( (OPL->type & OPL_TYPE_ADPCM) && (OPL->deltat) ) { // We really should call the postlod function for the YM_DELTAT, but it's hard without registers // (see the way the YM2610 does it) //YM_DELTAT_postload(OPL->deltat, REGS); } #endif } #endif /* Create one of virtual YM3812/YM3526/Y8950 */ /* 'clock' is chip clock in Hz */ /* 'rate' is sampling rate */ static FM_OPL *OPLCreate(uint32_t clock, uint32_t rate, int type) { char *ptr; FM_OPL *OPL; int state_size; if (OPL_LockTable() == -1) return NULL; /* calculate OPL state size */ state_size = sizeof(FM_OPL); #if BUILD_Y8950 if (type&OPL_TYPE_ADPCM) state_size+= sizeof(YM_DELTAT); #endif /* allocate memory block */ ptr = (char *)calloc(1, state_size); if (ptr == NULL) return NULL; OPL = (FM_OPL *)ptr; ptr += sizeof(FM_OPL); #if BUILD_Y8950 if (type&OPL_TYPE_ADPCM) { OPL->deltat = (YM_DELTAT *)ptr; } ptr += sizeof(YM_DELTAT); #endif OPL->type = type; OPL->clock = clock; OPL->rate = rate; /* init global tables */ OPL_initalize(OPL); return OPL; } /* Destroy one of virtual YM3812 */ static void OPLDestroy(FM_OPL *OPL) { OPL_UnLockTable(); free(OPL); } /* Optional handlers */ static void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER timer_handler,void *param) { OPL->timer_handler = timer_handler; OPL->TimerParam = param; } static void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,void *param) { OPL->IRQHandler = IRQHandler; OPL->IRQParam = param; } static void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,void *param) { OPL->UpdateHandler = UpdateHandler; OPL->UpdateParam = param; } static int OPLWrite(FM_OPL *OPL,int a,int v) { if( !(a&1) ) { /* address port */ OPL->address = v & 0xff; } else { /* data port */ if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); OPLWriteReg(OPL,OPL->address,v); } return OPL->status>>7; } static unsigned char OPLRead(FM_OPL *OPL,int a) { if( !(a&1) ) { /* status port */ #if BUILD_Y8950 if(OPL->type&OPL_TYPE_ADPCM) /* Y8950 */ { return (OPL->status & (OPL->statusmask|0x80)) | (OPL->deltat->PCM_BSY&1); } #endif /* OPL and OPL2 */ return OPL->status & (OPL->statusmask|0x80); } #if BUILD_Y8950 /* data port */ switch(OPL->address) { case 0x05: /* KeyBoard IN */ if(OPL->type&OPL_TYPE_KEYBOARD) { if(OPL->keyboardhandler_r) return OPL->keyboardhandler_r(OPL->keyboard_param); else logerror("Y8950: read unmapped KEYBOARD port\n"); } return 0; case 0x0f: /* ADPCM-DATA */ if(OPL->type&OPL_TYPE_ADPCM) { uint8_t val; val = YM_DELTAT_ADPCM_Read(OPL->deltat); /*logerror("Y8950: read ADPCM value read=%02x\n",val);*/ return val; } return 0; case 0x19: /* I/O DATA */ if(OPL->type&OPL_TYPE_IO) { if(OPL->porthandler_r) return OPL->porthandler_r(OPL->port_param); else logerror("Y8950:read unmapped I/O port\n"); } return 0; case 0x1a: /* PCM-DATA */ if(OPL->type&OPL_TYPE_ADPCM) { logerror("Y8950 A/D conversion is accessed but not implemented !\n"); return 0x80; /* 2's complement PCM data - result from A/D conversion */ } return 0; } #endif return 0xff; } /* CSM Key Controll */ static inline void CSMKeyControll(OPL_CH *CH) { FM_KEYON (&CH->SLOT[SLOT1], 4); FM_KEYON (&CH->SLOT[SLOT2], 4); /* The key off should happen exactly one sample later - not implemented correctly yet */ FM_KEYOFF(&CH->SLOT[SLOT1], ~4); FM_KEYOFF(&CH->SLOT[SLOT2], ~4); } static int OPLTimerOver(FM_OPL *OPL,int c) { if( c ) { /* Timer B */ OPL_STATUS_SET(OPL,0x20); } else { /* Timer A */ OPL_STATUS_SET(OPL,0x40); /* CSM mode key,TL controll */ if( OPL->mode & 0x80 ) { /* CSM mode total level latch and auto key on */ int ch; if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); for(ch=0; ch<9; ch++) CSMKeyControll( &OPL->P_CH[ch] ); } } /* reload timer */ if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,c,OPL->TimerBase * OPL->T[c]); return OPL->status>>7; } #define MAX_OPL_CHIPS 2 void * ym3812_init(uint32_t clock, uint32_t rate) { /* emulator create */ FM_OPL *YM3812 = OPLCreate(clock,rate,OPL_TYPE_YM3812); if (YM3812) { ym3812_reset_chip(YM3812); } return YM3812; } void ym3812_shutdown(void *chip) { FM_OPL *YM3812 = (FM_OPL *)chip; /* emulator shutdown */ OPLDestroy(YM3812); } void ym3812_reset_chip(void *chip) { FM_OPL *YM3812 = (FM_OPL *)chip; OPLResetChip(YM3812); } int ym3812_write(void *chip, int a, int v) { FM_OPL *YM3812 = (FM_OPL *)chip; return OPLWrite(YM3812, a, v); } unsigned char ym3812_read(void *chip, int a) { FM_OPL *YM3812 = (FM_OPL *)chip; /* YM3812 always returns bit2 and bit1 in HIGH state */ return OPLRead(YM3812, a) | 0x06 ; } int ym3812_timer_over(void *chip, int c) { FM_OPL *YM3812 = (FM_OPL *)chip; return OPLTimerOver(YM3812, c); } void ym3812_set_timer_handler(void *chip, OPL_TIMERHANDLER timer_handler, void *param) { FM_OPL *YM3812 = (FM_OPL *)chip; OPLSetTimerHandler(YM3812, timer_handler, param); } void ym3812_set_irq_handler(void *chip,OPL_IRQHANDLER IRQHandler,void *param) { FM_OPL *YM3812 = (FM_OPL *)chip; OPLSetIRQHandler(YM3812, IRQHandler, param); } void ym3812_set_update_handler(void *chip,OPL_UPDATEHANDLER UpdateHandler,void *param) { FM_OPL *YM3812 = (FM_OPL *)chip; OPLSetUpdateHandler(YM3812, UpdateHandler, param); } /* ** Generate samples for one of the YM3812's ** ** 'which' is the virtual YM3812 number ** '*buffer' is the output buffer pointer ** 'length' is the number of samples that should be generated */ void ym3812_update_one(void *chip, OPLSAMPLE *buffer, int length) { FM_OPL *OPL = (FM_OPL *)chip; uint8_t rhythm = OPL->rhythm&0x20; OPLSAMPLE *buf = buffer; int i; for( i=0; i < length ; i++ ) { int lt; OPL->output[0] = 0; advance_lfo(OPL); /* FM part */ OPL_CALC_CH(OPL, &OPL->P_CH[0]); OPL_CALC_CH(OPL, &OPL->P_CH[1]); OPL_CALC_CH(OPL, &OPL->P_CH[2]); OPL_CALC_CH(OPL, &OPL->P_CH[3]); OPL_CALC_CH(OPL, &OPL->P_CH[4]); OPL_CALC_CH(OPL, &OPL->P_CH[5]); if(!rhythm) { OPL_CALC_CH(OPL, &OPL->P_CH[6]); OPL_CALC_CH(OPL, &OPL->P_CH[7]); OPL_CALC_CH(OPL, &OPL->P_CH[8]); } else /* Rhythm part */ { OPL_CALC_RH(OPL, &OPL->P_CH[0], (OPL->noise_rng>>0)&1 ); } lt = OPL->output[0]; lt >>= FINAL_SH; /* limit check */ lt = limit( lt , MAXOUT, MINOUT ); /* store to sound buffer */ buf[i] = lt; advance(OPL); } } schismtracker-20250313/player/fmopl3.c000066400000000000000000002230411476471630300174720ustar00rootroot00000000000000// license:GPL-2.0+ // copyright-holders:Jarek Burczynski /* ** ** File: ymf262.c - software implementation of YMF262 ** FM sound generator type OPL3 ** ** Copyright Jarek Burczynski ** ** Version 0.2 ** Revision History: 03-03-2003: initial release - thanks to Olivier Galibert and Chris Hardy for YMF262 and YAC512 chips - thanks to Stiletto for the datasheets Features as listed in 4MF262A6 data sheet: 1. Registers are compatible with YM3812 (OPL2) FM sound source. 2. Up to six sounds can be used as four-operator melody sounds for variety. 3. 18 simultaneous melody sounds, or 15 melody sounds with 5 rhythm sounds (with two operators). 4. 6 four-operator melody sounds and 6 two-operator melody sounds, or 6 four-operator melody sounds, 3 two-operator melody sounds and 5 rhythm sounds (with four operators). 5. 8 selectable waveforms. 6. 4-channel sound output. 7. YMF262 compabile DAC (YAC512) is available. 8. LFO for vibrato and tremolo effedts. 9. 2 programable timers. 10. Shorter register access time compared with YM3812. 11. 5V single supply silicon gate CMOS process. 12. 24 Pin SOP Package (YMF262-M), 48 Pin SQFP Package (YMF262-S). differences between OPL2 and OPL3 not documented in Yamaha datahasheets: - sinus table is a little different: the negative part is off by one... - in order to enable selection of four different waveforms on OPL2 one must set bit 5 in register 0x01(test). on OPL3 this bit is ignored and 4-waveform select works *always*. (Don't confuse this with OPL3's 8-waveform select.) - Envelope Generator: all 15 x rates take zero time on OPL3 (on OPL2 15 0 and 15 1 rates take some time while 15 2 and 15 3 rates take zero time) - channel calculations: output of operator 1 is in perfect sync with output of operator 2 on OPL3; on OPL and OPL2 output of operator 1 is always delayed by one sample compared to output of operator 2 differences between OPL2 and OPL3 shown in datasheets: - YMF262 does not support CSM mode */ #include "headers.h" #include "player/fmopl.h" /* output final shift */ #define FINAL_SH (0) #define MAXOUT INT16_MAX #define MINOUT INT16_MIN #define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ #define EG_SH 16 /* 16.16 fixed point (EG timing) */ #define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ #define TIMER_SH 16 /* 16.16 fixed point (timers calculations) */ #define FREQ_MASK ((1<>KSR */ uint8_t mul; /* multiple: mul_tab[ML] */ /* Phase Generator */ uint32_t Cnt; /* frequency counter */ uint32_t Incr; /* frequency counter step */ uint8_t FB; /* feedback shift value */ int32_t *connect; /* slot output pointer */ int32_t op1_out[2]; /* slot1 output for feedback */ uint8_t CON; /* connection (algorithm) type */ /* Envelope Generator */ uint8_t eg_type; /* percussive/non-percussive mode */ uint8_t state; /* phase type */ uint32_t TL; /* total level: TL << 2 */ int32_t TLL; /* adjusted now TL */ int32_t volume; /* envelope counter */ uint32_t sl; /* sustain level: sl_tab[SL] */ uint32_t eg_m_ar; /* (attack state) */ uint8_t eg_sh_ar; /* (attack state) */ uint8_t eg_sel_ar; /* (attack state) */ uint32_t eg_m_dr; /* (decay state) */ uint8_t eg_sh_dr; /* (decay state) */ uint8_t eg_sel_dr; /* (decay state) */ uint32_t eg_m_rr; /* (release state) */ uint8_t eg_sh_rr; /* (release state) */ uint8_t eg_sel_rr; /* (release state) */ uint32_t key; /* 0 = KEY OFF, >0 = KEY ON */ /* LFO */ uint32_t AMmask; /* LFO Amplitude Modulation enable mask */ uint8_t vib; /* LFO Phase Modulation enable flag (active high)*/ /* waveform select */ uint8_t waveform_number; unsigned int wavetable; //unsigned char reserved[128-84];//speedup: pump up the struct size to power of 2 unsigned char reserved[128-100];//speedup: pump up the struct size to power of 2 } OPL3_SLOT; typedef struct { OPL3_SLOT SLOT[2]; uint32_t block_fnum; /* block+fnum */ uint32_t fc; /* Freq. Increment base */ uint32_t ksl_base; /* KeyScaleLevel Base step */ uint8_t kcode; /* key code (for key scaling) */ /* there are 12 2-operator channels which can be combined in pairs to form six 4-operator channel, they are: 0 and 3, 1 and 4, 2 and 5, 9 and 12, 10 and 13, 11 and 14 */ uint8_t extended; /* set to 1 if this channel forms up a 4op channel with another channel(only used by first of pair of channels, ie 0,1,2 and 9,10,11) */ unsigned char reserved[512-272];//speedup:pump up the struct size to power of 2 } OPL3_CH; /* OPL3 state */ typedef struct { OPL3_CH P_CH[18]; /* OPL3 chips have 18 channels */ uint32_t pan[18*4]; /* channels output masks (0xffffffff = enable); 4 masks per one channel */ uint32_t pan_ctrl_value[18]; /* output control values 1 per one channel (1 value contains 4 masks) */ int32_t chanout[18]; int32_t phase_modulation; /* phase modulation input (SLOT 2) */ int32_t phase_modulation2; /* phase modulation input (SLOT 3 in 4 operator channels) */ uint32_t eg_cnt; /* global envelope generator counter */ uint32_t eg_timer; /* global envelope generator counter works at frequency = chipclock/288 (288=8*36) */ uint32_t eg_timer_add; /* step of eg_timer */ uint32_t eg_timer_overflow; /* envelope generator timer overlfows every 1 sample (on real chip) */ uint32_t fn_tab[1024]; /* fnumber->increment counter */ /* LFO */ uint32_t LFO_AM; int32_t LFO_PM; uint8_t lfo_am_depth; uint8_t lfo_pm_depth_range; uint32_t lfo_am_cnt; uint32_t lfo_am_inc; uint32_t lfo_pm_cnt; uint32_t lfo_pm_inc; uint32_t noise_rng; /* 23 bit noise shift register */ uint32_t noise_p; /* current noise 'phase' */ uint32_t noise_f; /* current noise period */ uint8_t OPL3_mode; /* OPL3 extension enable flag */ uint8_t rhythm; /* Rhythm mode */ int T[2]; /* timer counters */ uint8_t st[2]; /* timer enable */ uint32_t address; /* address register */ uint8_t status; /* status flag */ uint8_t statusmask; /* status mask */ uint8_t nts; /* NTS (note select) */ /* external event callback handlers */ OPL_TIMERHANDLER timer_handler;/* TIMER handler */ void *TimerParam; /* TIMER parameter */ OPL_IRQHANDLER IRQHandler; /* IRQ handler */ void *IRQParam; /* IRQ parameter */ OPL_UPDATEHANDLER UpdateHandler;/* stream update handler */ void *UpdateParam; /* stream update parameter */ uint8_t type; /* chip type */ uint32_t clock; /* master clock (Hz) */ uint32_t rate; /* sampling rate (Hz) */ double freqbase; /* frequency base */ double TimerBase; /* Timer base time (==sampling time)*/ } OPL3; /* mapping of register number (offset) to slot number used by the emulator */ static const int slot_array[32]= { 0, 2, 4, 1, 3, 5,-1,-1, 6, 8,10, 7, 9,11,-1,-1, 12,14,16,13,15,17,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1 }; /* key scale level */ /* table is 3dB/octave , DV converts this into 6dB/octave */ /* 0.1875 is bit 0 weight of the envelope counter (volume) expressed in the 'decibel' scale */ #define DV (0.1875/2.0) static const double ksl_tab[8*16]= { /* OCT 0 */ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, /* OCT 1 */ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.750/DV, 1.125/DV, 1.500/DV, 1.875/DV, 2.250/DV, 2.625/DV, 3.000/DV, /* OCT 2 */ 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, 1.125/DV, 1.875/DV, 2.625/DV, 3.000/DV, 3.750/DV, 4.125/DV, 4.500/DV, 4.875/DV, 5.250/DV, 5.625/DV, 6.000/DV, /* OCT 3 */ 0.000/DV, 0.000/DV, 0.000/DV, 1.875/DV, 3.000/DV, 4.125/DV, 4.875/DV, 5.625/DV, 6.000/DV, 6.750/DV, 7.125/DV, 7.500/DV, 7.875/DV, 8.250/DV, 8.625/DV, 9.000/DV, /* OCT 4 */ 0.000/DV, 0.000/DV, 3.000/DV, 4.875/DV, 6.000/DV, 7.125/DV, 7.875/DV, 8.625/DV, 9.000/DV, 9.750/DV,10.125/DV,10.500/DV, 10.875/DV,11.250/DV,11.625/DV,12.000/DV, /* OCT 5 */ 0.000/DV, 3.000/DV, 6.000/DV, 7.875/DV, 9.000/DV,10.125/DV,10.875/DV,11.625/DV, 12.000/DV,12.750/DV,13.125/DV,13.500/DV, 13.875/DV,14.250/DV,14.625/DV,15.000/DV, /* OCT 6 */ 0.000/DV, 6.000/DV, 9.000/DV,10.875/DV, 12.000/DV,13.125/DV,13.875/DV,14.625/DV, 15.000/DV,15.750/DV,16.125/DV,16.500/DV, 16.875/DV,17.250/DV,17.625/DV,18.000/DV, /* OCT 7 */ 0.000/DV, 9.000/DV,12.000/DV,13.875/DV, 15.000/DV,16.125/DV,16.875/DV,17.625/DV, 18.000/DV,18.750/DV,19.125/DV,19.500/DV, 19.875/DV,20.250/DV,20.625/DV,21.000/DV }; #undef DV /* 0 / 3.0 / 1.5 / 6.0 dB/OCT */ static const uint32_t ksl_shift[4] = { 31, 1, 2, 0 }; /* sustain level table (3dB per step) */ /* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ #define SC(db) (uint32_t) ( db * (2.0/ENV_STEP) ) static const uint32_t sl_tab[16]={ SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31) }; #undef SC #define RATE_STEPS (8) static const unsigned char eg_inc[15*RATE_STEPS]={ /*cycle:0 1 2 3 4 5 6 7*/ /* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..12 0 (increment by 0 or 1) */ /* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..12 1 */ /* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..12 2 */ /* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..12 3 */ /* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 13 0 (increment by 1) */ /* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 13 1 */ /* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 13 2 */ /* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 13 3 */ /* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 14 0 (increment by 2) */ /* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 14 1 */ /*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 14 2 */ /*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 14 3 */ /*12 */ 4,4, 4,4, 4,4, 4,4, /* rates 15 0, 15 1, 15 2, 15 3 for decay */ /*13 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 0, 15 1, 15 2, 15 3 for attack (zero time) */ /*14 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ }; #define O(a) (a*RATE_STEPS) /* note that there is no O(13) in this table - it's directly in the code */ static const unsigned char eg_rate_select[16+64+16]={ /* Envelope Generator rates (16 + 64 rates + 16 RKS) */ /* 16 infinite time rates */ O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), /* rates 00-12 */ O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), /* rate 13 */ O( 4),O( 5),O( 6),O( 7), /* rate 14 */ O( 8),O( 9),O(10),O(11), /* rate 15 */ O(12),O(12),O(12),O(12), /* 16 dummy rates (same as 15 3) */ O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), }; #undef O /*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 */ /*shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0 */ /*mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0 */ #define O(a) (a*1) static const unsigned char eg_rate_shift[16+64+16]={ /* Envelope Generator counter shifts (16 + 64 rates + 16 RKS) */ /* 16 infinite time rates */ O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), /* rates 00-12 */ O(12),O(12),O(12),O(12), O(11),O(11),O(11),O(11), O(10),O(10),O(10),O(10), O( 9),O( 9),O( 9),O( 9), O( 8),O( 8),O( 8),O( 8), O( 7),O( 7),O( 7),O( 7), O( 6),O( 6),O( 6),O( 6), O( 5),O( 5),O( 5),O( 5), O( 4),O( 4),O( 4),O( 4), O( 3),O( 3),O( 3),O( 3), O( 2),O( 2),O( 2),O( 2), O( 1),O( 1),O( 1),O( 1), O( 0),O( 0),O( 0),O( 0), /* rate 13 */ O( 0),O( 0),O( 0),O( 0), /* rate 14 */ O( 0),O( 0),O( 0),O( 0), /* rate 15 */ O( 0),O( 0),O( 0),O( 0), /* 16 dummy rates (same as 15 3) */ O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), }; #undef O /* multiple table */ #define ML 2 static const uint8_t mul_tab[16]= { /* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,10,12,12,15,15 */ ML/2, 1*ML, 2*ML, 3*ML, 4*ML, 5*ML, 6*ML, 7*ML, 8*ML, 9*ML,10*ML,10*ML,12*ML,12*ML,15*ML,15*ML }; #undef ML /* TL_TAB_LEN is calculated as: * (12+1)=13 - sinus amplitude bits (Y axis) * additional 1: to compensate for calculations of negative part of waveform * (if we don't add it then the greatest possible _negative_ value would be -2 * and we really need -1 for waveform #7) * 2 - sinus sign bit (Y axis) * TL_RES_LEN - sinus resolution (X axis) */ #define TL_TAB_LEN (13*2*TL_RES_LEN) static signed int tl_tab[TL_TAB_LEN]; #define ENV_QUIET (TL_TAB_LEN>>4) /* sin waveform table in 'decibel' scale */ /* there are eight waveforms on OPL3 chips */ static unsigned int sin_tab[SIN_LEN * 8]; /* LFO Amplitude Modulation table (verified on real YM3812) 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples Length: 210 elements. Each of the elements has to be repeated exactly 64 times (on 64 consecutive samples). The whole table takes: 64 * 210 = 13440 samples. When AM = 1 data is used directly When AM = 0 data is divided by 4 before being used (losing precision is important) */ #define LFO_AM_TAB_ELEMENTS 210 static const uint8_t lfo_am_table[LFO_AM_TAB_ELEMENTS] = { 0,0,0,0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, 5,5,5,5, 6,6,6,6, 7,7,7,7, 8,8,8,8, 9,9,9,9, 10,10,10,10, 11,11,11,11, 12,12,12,12, 13,13,13,13, 14,14,14,14, 15,15,15,15, 16,16,16,16, 17,17,17,17, 18,18,18,18, 19,19,19,19, 20,20,20,20, 21,21,21,21, 22,22,22,22, 23,23,23,23, 24,24,24,24, 25,25,25,25, 26,26,26, 25,25,25,25, 24,24,24,24, 23,23,23,23, 22,22,22,22, 21,21,21,21, 20,20,20,20, 19,19,19,19, 18,18,18,18, 17,17,17,17, 16,16,16,16, 15,15,15,15, 14,14,14,14, 13,13,13,13, 12,12,12,12, 11,11,11,11, 10,10,10,10, 9,9,9,9, 8,8,8,8, 7,7,7,7, 6,6,6,6, 5,5,5,5, 4,4,4,4, 3,3,3,3, 2,2,2,2, 1,1,1,1 }; /* LFO Phase Modulation table (verified on real YM3812) */ static const int8_t lfo_pm_table[8*8*2] = { /* FNUM2/FNUM = 00 0xxxxxxx (0x0000) */ 0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/ 0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 00 1xxxxxxx (0x0080) */ 0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/ 1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 01 0xxxxxxx (0x0100) */ 1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/ 2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 01 1xxxxxxx (0x0180) */ 1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/ 3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 10 0xxxxxxx (0x0200) */ 2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/ 4, 2, 0,-2,-4,-2, 0, 2, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 10 1xxxxxxx (0x0280) */ 2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/ 5, 2, 0,-2,-5,-2, 0, 2, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 11 0xxxxxxx (0x0300) */ 3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/ 6, 3, 0,-3,-6,-3, 0, 3, /*LFO PM depth = 1*/ /* FNUM2/FNUM = 11 1xxxxxxx (0x0380) */ 3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/ 7, 3, 0,-3,-7,-3, 0, 3 /*LFO PM depth = 1*/ }; /* lock level of common table */ static int num_lock = 0; /* work table */ #define SLOT7_1 (&chip->P_CH[7].SLOT[SLOT1]) #define SLOT7_2 (&chip->P_CH[7].SLOT[SLOT2]) #define SLOT8_1 (&chip->P_CH[8].SLOT[SLOT1]) #define SLOT8_2 (&chip->P_CH[8].SLOT[SLOT2]) static inline int limit( int val, int max, int min ) { if ( val > max ) val = max; else if ( val < min ) val = min; return val; } /* status set and IRQ handling */ static inline void OPL3_STATUS_SET(OPL3 *chip,int flag) { /* set status flag masking out disabled IRQs */ chip->status |= (flag & chip->statusmask); if(!(chip->status & 0x80)) { if(chip->status & 0x7f) { /* IRQ on */ chip->status |= 0x80; /* callback user interrupt handler (IRQ is OFF to ON) */ if(chip->IRQHandler) (chip->IRQHandler)(chip->IRQParam,1); } } } /* status reset and IRQ handling */ static inline void OPL3_STATUS_RESET(OPL3 *chip,int flag) { /* reset status flag */ chip->status &= ~flag; if(chip->status & 0x80) { if (!(chip->status & 0x7f)) { chip->status &= 0x7f; /* callback user interrupt handler (IRQ is ON to OFF) */ if(chip->IRQHandler) (chip->IRQHandler)(chip->IRQParam,0); } } } /* IRQ mask set */ static inline void OPL3_STATUSMASK_SET(OPL3 *chip,int flag) { chip->statusmask = flag; /* IRQ handling check */ OPL3_STATUS_SET(chip,0); OPL3_STATUS_RESET(chip,0); } /* advance LFO to next sample */ static inline void advance_lfo(OPL3 *chip) { uint8_t tmp; /* LFO */ chip->lfo_am_cnt += chip->lfo_am_inc; if (chip->lfo_am_cnt >= ((uint32_t)LFO_AM_TAB_ELEMENTS<lfo_am_cnt -= ((uint32_t)LFO_AM_TAB_ELEMENTS<lfo_am_cnt >> LFO_SH ]; if (chip->lfo_am_depth) chip->LFO_AM = tmp; else chip->LFO_AM = tmp>>2; chip->lfo_pm_cnt += chip->lfo_pm_inc; chip->LFO_PM = ((chip->lfo_pm_cnt>>LFO_SH) & 7) | chip->lfo_pm_depth_range; } /* advance to next sample */ static inline void advance(OPL3 *chip) { OPL3_CH *CH; OPL3_SLOT *op; int i; chip->eg_timer += chip->eg_timer_add; while (chip->eg_timer >= chip->eg_timer_overflow) { chip->eg_timer -= chip->eg_timer_overflow; chip->eg_cnt++; for (i=0; i<9*2*2; i++) { CH = &chip->P_CH[i/2]; op = &CH->SLOT[i&1]; #if 1 /* Envelope Generator */ switch(op->state) { case EG_ATT: /* attack phase */ // if ( !(chip->eg_cnt & ((1<eg_sh_ar)-1) ) ) if ( !(chip->eg_cnt & op->eg_m_ar) ) { op->volume += (~op->volume * (eg_inc[op->eg_sel_ar + ((chip->eg_cnt>>op->eg_sh_ar)&7)]) ) >>3; if (op->volume <= MIN_ATT_INDEX) { op->volume = MIN_ATT_INDEX; op->state = EG_DEC; } } break; case EG_DEC: /* decay phase */ // if ( !(chip->eg_cnt & ((1<eg_sh_dr)-1) ) ) if ( !(chip->eg_cnt & op->eg_m_dr) ) { op->volume += eg_inc[op->eg_sel_dr + ((chip->eg_cnt>>op->eg_sh_dr)&7)]; if ( op->volume >= op->sl ) op->state = EG_SUS; } break; case EG_SUS: /* sustain phase */ /* this is important behaviour: one can change percusive/non-percussive modes on the fly and the chip will remain in sustain phase - verified on real YM3812 */ if(op->eg_type) /* non-percussive mode */ { /* do nothing */ } else /* percussive mode */ { /* during sustain phase chip adds Release Rate (in percussive mode) */ // if ( !(chip->eg_cnt & ((1<eg_sh_rr)-1) ) ) if ( !(chip->eg_cnt & op->eg_m_rr) ) { op->volume += eg_inc[op->eg_sel_rr + ((chip->eg_cnt>>op->eg_sh_rr)&7)]; if ( op->volume >= MAX_ATT_INDEX ) op->volume = MAX_ATT_INDEX; } /* else do nothing in sustain phase */ } break; case EG_REL: /* release phase */ // if ( !(chip->eg_cnt & ((1<eg_sh_rr)-1) ) ) if ( !(chip->eg_cnt & op->eg_m_rr) ) { op->volume += eg_inc[op->eg_sel_rr + ((chip->eg_cnt>>op->eg_sh_rr)&7)]; if ( op->volume >= MAX_ATT_INDEX ) { op->volume = MAX_ATT_INDEX; op->state = EG_OFF; } } break; default: break; } #endif } } for (i=0; i<9*2*2; i++) { CH = &chip->P_CH[i/2]; op = &CH->SLOT[i&1]; /* Phase Generator */ if(op->vib) { uint8_t block; unsigned int block_fnum = CH->block_fnum; unsigned int fnum_lfo = (block_fnum&0x0380) >> 7; signed int lfo_fn_table_index_offset = lfo_pm_table[chip->LFO_PM + 16*fnum_lfo ]; if (lfo_fn_table_index_offset) /* LFO phase modulation active */ { block_fnum += lfo_fn_table_index_offset; block = (block_fnum&0x1c00) >> 10; op->Cnt += (chip->fn_tab[block_fnum&0x03ff] >> (7-block)) * op->mul; } else /* LFO phase modulation = zero */ { op->Cnt += op->Incr; } } else /* LFO phase modulation disabled for this operator */ { op->Cnt += op->Incr; } } /* The Noise Generator of the YM3812 is 23-bit shift register. * Period is equal to 2^23-2 samples. * Register works at sampling frequency of the chip, so output * can change on every sample. * * Output of the register and input to the bit 22 is: * bit0 XOR bit14 XOR bit15 XOR bit22 * * Simply use bit 22 as the noise output. */ chip->noise_p += chip->noise_f; i = chip->noise_p >> FREQ_SH; /* number of events (shifts of the shift register) */ chip->noise_p &= FREQ_MASK; while (i) { /* uint32_t j; j = ( (chip->noise_rng) ^ (chip->noise_rng>>14) ^ (chip->noise_rng>>15) ^ (chip->noise_rng>>22) ) & 1; chip->noise_rng = (j<<22) | (chip->noise_rng>>1); */ /* Instead of doing all the logic operations above, we use a trick here (and use bit 0 as the noise output). The difference is only that the noise bit changes one step ahead. This doesn't matter since we don't know what is real state of the noise_rng after the reset. */ if (chip->noise_rng & 1) chip->noise_rng ^= 0x800302; chip->noise_rng >>= 1; i--; } } static inline signed int op_calc(uint32_t phase, unsigned int env, signed int pm, unsigned int wave_tab) { uint32_t p; p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + (pm<<16))) >> FREQ_SH ) & SIN_MASK) ]; if (p >= TL_TAB_LEN) return 0; return tl_tab[p]; } static inline signed int op_calc1(uint32_t phase, unsigned int env, signed int pm, unsigned int wave_tab) { uint32_t p; p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + pm))>>FREQ_SH) & SIN_MASK)]; if (p >= TL_TAB_LEN) return 0; return tl_tab[p]; } #define volume_calc(OP) ((OP)->TLL + ((uint32_t)(OP)->volume) + (chip->LFO_AM & (OP)->AMmask)) /* calculate output of a standard 2 operator channel (or 1st part of a 4-op channel) */ static inline void chan_calc( OPL3 *chip, OPL3_CH *CH ) { OPL3_SLOT *SLOT; unsigned int env; signed int out; chip->phase_modulation = 0; chip->phase_modulation2= 0; /* SLOT 1 */ SLOT = &CH->SLOT[SLOT1]; env = volume_calc(SLOT); out = SLOT->op1_out[0] + SLOT->op1_out[1]; SLOT->op1_out[0] = SLOT->op1_out[1]; SLOT->op1_out[1] = 0; if( env < ENV_QUIET ) { if (!SLOT->FB) out = 0; SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<FB), SLOT->wavetable ); } *SLOT->connect += SLOT->op1_out[1]; //logerror("out0=%5i vol0=%4i ", SLOT->op1_out[1], env ); /* SLOT 2 */ SLOT++; env = volume_calc(SLOT); if( env < ENV_QUIET ) *SLOT->connect += op_calc(SLOT->Cnt, env, chip->phase_modulation, SLOT->wavetable); //logerror("out1=%5i vol1=%4i\n", op_calc(SLOT->Cnt, env, chip->phase_modulation, SLOT->wavetable), env ); } /* calculate output of a 2nd part of 4-op channel */ static inline void chan_calc_ext( OPL3 *chip, OPL3_CH *CH ) { OPL3_SLOT *SLOT; unsigned int env; chip->phase_modulation = 0; /* SLOT 1 */ SLOT = &CH->SLOT[SLOT1]; env = volume_calc(SLOT); if( env < ENV_QUIET ) *SLOT->connect += op_calc(SLOT->Cnt, env, chip->phase_modulation2, SLOT->wavetable ); /* SLOT 2 */ SLOT++; env = volume_calc(SLOT); if( env < ENV_QUIET ) *SLOT->connect += op_calc(SLOT->Cnt, env, chip->phase_modulation, SLOT->wavetable); } /* operators used in the rhythm sounds generation process: Envelope Generator: channel operator register number Bass High Snare Tom Top / slot number TL ARDR SLRR Wave Drum Hat Drum Tom Cymbal 6 / 0 12 50 70 90 f0 + 6 / 1 15 53 73 93 f3 + 7 / 0 13 51 71 91 f1 + 7 / 1 16 54 74 94 f4 + 8 / 0 14 52 72 92 f2 + 8 / 1 17 55 75 95 f5 + Phase Generator: channel operator register number Bass High Snare Tom Top / slot number MULTIPLE Drum Hat Drum Tom Cymbal 6 / 0 12 30 + 6 / 1 15 33 + 7 / 0 13 31 + + + 7 / 1 16 34 ----- n o t u s e d ----- 8 / 0 14 32 + 8 / 1 17 35 + + channel operator register number Bass High Snare Tom Top number number BLK/FNUM2 FNUM Drum Hat Drum Tom Cymbal 6 12,15 B6 A6 + 7 13,16 B7 A7 + + + 8 14,17 B8 A8 + + + */ /* calculate rhythm */ static inline void chan_calc_rhythm( OPL3 *chip, OPL3_CH *CH, uint32_t noise ) { OPL3_SLOT *SLOT; int32_t *chanout = chip->chanout; int32_t out; uint32_t env; /* Bass Drum (verified on real YM3812): - depends on the channel 6 'connect' register: when connect = 0 it works the same as in normal (non-rhythm) mode (op1->op2->out) when connect = 1 _only_ operator 2 is present on output (op2->out), operator 1 is ignored - output sample always is multiplied by 2 */ chip->phase_modulation = 0; /* SLOT 1 */ SLOT = &CH[6].SLOT[SLOT1]; env = volume_calc(SLOT); out = SLOT->op1_out[0] + SLOT->op1_out[1]; SLOT->op1_out[0] = SLOT->op1_out[1]; if (!SLOT->CON) chip->phase_modulation = SLOT->op1_out[0]; //else ignore output of operator 1 SLOT->op1_out[1] = 0; if( env < ENV_QUIET ) { if (!SLOT->FB) out = 0; SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<FB), SLOT->wavetable ); } /* SLOT 2 */ SLOT++; env = volume_calc(SLOT); if( env < ENV_QUIET ) chanout[6] += op_calc(SLOT->Cnt, env, chip->phase_modulation, SLOT->wavetable) * 2; /* Phase generation is based on: */ // HH (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) // SD (16) channel 7->slot 1 // TOM (14) channel 8->slot 1 // TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) /* Envelope generation based on: */ // HH channel 7->slot1 // SD channel 7->slot2 // TOM channel 8->slot1 // TOP channel 8->slot2 /* The following formulas can be well optimized. I leave them in direct form for now (in case I've missed something). */ /* High Hat (verified on real YM3812) */ env = volume_calc(SLOT7_1); if( env < ENV_QUIET ) { /* high hat phase generation: phase = d0 or 234 (based on frequency only) phase = 34 or 2d0 (based on noise) */ /* base frequency derived from operator 1 in channel 7 */ unsigned char bit7 = ((SLOT7_1->Cnt>>FREQ_SH)>>7)&1; unsigned char bit3 = ((SLOT7_1->Cnt>>FREQ_SH)>>3)&1; unsigned char bit2 = ((SLOT7_1->Cnt>>FREQ_SH)>>2)&1; unsigned char res1 = (bit2 ^ bit7) | bit3; /* when res1 = 0 phase = 0x000 | 0xd0; */ /* when res1 = 1 phase = 0x200 | (0xd0>>2); */ uint32_t phase = res1 ? (0x200|(0xd0>>2)) : 0xd0; /* enable gate based on frequency of operator 2 in channel 8 */ unsigned char bit5e= ((SLOT8_2->Cnt>>FREQ_SH)>>5)&1; unsigned char bit3e= ((SLOT8_2->Cnt>>FREQ_SH)>>3)&1; unsigned char res2 = (bit3e ^ bit5e); /* when res2 = 0 pass the phase from calculation above (res1); */ /* when res2 = 1 phase = 0x200 | (0xd0>>2); */ if (res2) phase = (0x200|(0xd0>>2)); /* when phase & 0x200 is set and noise=1 then phase = 0x200|0xd0 */ /* when phase & 0x200 is set and noise=0 then phase = 0x200|(0xd0>>2), ie no change */ if (phase&0x200) { if (noise) phase = 0x200|0xd0; } else /* when phase & 0x200 is clear and noise=1 then phase = 0xd0>>2 */ /* when phase & 0x200 is clear and noise=0 then phase = 0xd0, ie no change */ { if (noise) phase = 0xd0>>2; } chanout[7] += op_calc(phase<wavetable) * 2; } /* Snare Drum (verified on real YM3812) */ env = volume_calc(SLOT7_2); if( env < ENV_QUIET ) { /* base frequency derived from operator 1 in channel 7 */ unsigned char bit8 = ((SLOT7_1->Cnt>>FREQ_SH)>>8)&1; /* when bit8 = 0 phase = 0x100; */ /* when bit8 = 1 phase = 0x200; */ uint32_t phase = bit8 ? 0x200 : 0x100; /* Noise bit XOR'es phase by 0x100 */ /* when noisebit = 0 pass the phase from calculation above */ /* when noisebit = 1 phase ^= 0x100; */ /* in other words: phase ^= (noisebit<<8); */ if (noise) phase ^= 0x100; chanout[7] += op_calc(phase<wavetable) * 2; } /* Tom Tom (verified on real YM3812) */ env = volume_calc(SLOT8_1); if( env < ENV_QUIET ) chanout[8] += op_calc(SLOT8_1->Cnt, env, 0, SLOT8_1->wavetable) * 2; /* Top Cymbal (verified on real YM3812) */ env = volume_calc(SLOT8_2); if( env < ENV_QUIET ) { /* base frequency derived from operator 1 in channel 7 */ unsigned char bit7 = ((SLOT7_1->Cnt>>FREQ_SH)>>7)&1; unsigned char bit3 = ((SLOT7_1->Cnt>>FREQ_SH)>>3)&1; unsigned char bit2 = ((SLOT7_1->Cnt>>FREQ_SH)>>2)&1; unsigned char res1 = (bit2 ^ bit7) | bit3; /* when res1 = 0 phase = 0x000 | 0x100; */ /* when res1 = 1 phase = 0x200 | 0x100; */ uint32_t phase = res1 ? 0x300 : 0x100; /* enable gate based on frequency of operator 2 in channel 8 */ unsigned char bit5e= ((SLOT8_2->Cnt>>FREQ_SH)>>5)&1; unsigned char bit3e= ((SLOT8_2->Cnt>>FREQ_SH)>>3)&1; unsigned char res2 = (bit3e ^ bit5e); /* when res2 = 0 pass the phase from calculation above (res1); */ /* when res2 = 1 phase = 0x200 | 0x100; */ if (res2) phase = 0x300; chanout[8] += op_calc(phase<wavetable) * 2; } } /* generic table initialize */ static int init_tables(void) { int32_t i,x; int32_t n; double o,m; for (x=0; x>= 4; /* 12 bits here */ if (n&1) /* round to nearest */ n = (n>>1)+1; else n = n>>1; /* 11 bits here (rounded) */ n <<= 1; /* 12 bits here (as in real chip) */ tl_tab[ x*2 + 0 ] = n; tl_tab[ x*2 + 1 ] = ~tl_tab[ x*2 + 0 ]; /* this *is* different from OPL2 (verified on real YMF262) */ for (i=1; i<13; i++) { tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = ~tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; /* this *is* different from OPL2 (verified on real YMF262) */ } #if 0 logerror("tl %04i", x*2); for (i=0; i<13; i++) logerror(", [%02i] %5i", i*2, tl_tab[ x*2 +0 + i*2*TL_RES_LEN ] ); /* positive */ logerror("\n"); logerror("tl %04i", x*2); for (i=0; i<13; i++) logerror(", [%02i] %5i", i*2, tl_tab[ x*2 +1 + i*2*TL_RES_LEN ] ); /* negative */ logerror("\n"); #endif } for (i=0; i0.0) o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ else o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ o = o / (ENV_STEP/4); n = (int)(2.0*o); if (n&1) /* round to nearest */ n = (n>>1)+1; else n = n>>1; sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); /*logerror("YMF262.C: sin [%4i (hex=%03x)]= %4i (tl_tab value=%5i)\n", i, i, sin_tab[i], tl_tab[sin_tab[i]] );*/ } for (i=0; i>1) ]; /* waveform 3: _ _ _ _ */ /* / |_/ |_/ |_/ |_*/ /* abs(output only first quarter of the sinus waveform) */ if (i & (1<<(SIN_BITS-2)) ) sin_tab[3*SIN_LEN+i] = TL_TAB_LEN; else sin_tab[3*SIN_LEN+i] = sin_tab[i & (SIN_MASK>>2)]; /* waveform 4: */ /* /\ ____/\ ____*/ /* \/ \/ */ /* output whole sinus waveform in half the cycle(step=2) and output 0 on the other half of cycle */ if (i & (1<<(SIN_BITS-1)) ) sin_tab[4*SIN_LEN+i] = TL_TAB_LEN; else sin_tab[4*SIN_LEN+i] = sin_tab[i*2]; /* waveform 5: */ /* /\/\____/\/\____*/ /* */ /* output abs(whole sinus) waveform in half the cycle(step=2) and output 0 on the other half of cycle */ if (i & (1<<(SIN_BITS-1)) ) sin_tab[5*SIN_LEN+i] = TL_TAB_LEN; else sin_tab[5*SIN_LEN+i] = sin_tab[(i*2) & (SIN_MASK>>1) ]; /* waveform 6: ____ ____ */ /* */ /* ____ ____*/ /* output maximum in half the cycle and output minimum on the other half of cycle */ if (i & (1<<(SIN_BITS-1)) ) sin_tab[6*SIN_LEN+i] = 1; /* negative */ else sin_tab[6*SIN_LEN+i] = 0; /* positive */ /* waveform 7: */ /* |\____ |\____ */ /* \| \|*/ /* output sawtooth waveform */ if (i & (1<<(SIN_BITS-1)) ) x = ((SIN_LEN-1)-i)*16 + 1; /* negative: from 8177 to 1 */ else x = i*16; /*positive: from 0 to 8176 */ if (x > TL_TAB_LEN) x = TL_TAB_LEN; /* clip to the allowed range */ sin_tab[7*SIN_LEN+i] = x; //logerror("YMF262.C: sin1[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[1*SIN_LEN+i], tl_tab[sin_tab[1*SIN_LEN+i]] ); //logerror("YMF262.C: sin2[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[2*SIN_LEN+i], tl_tab[sin_tab[2*SIN_LEN+i]] ); //logerror("YMF262.C: sin3[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[3*SIN_LEN+i], tl_tab[sin_tab[3*SIN_LEN+i]] ); //logerror("YMF262.C: sin4[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[4*SIN_LEN+i], tl_tab[sin_tab[4*SIN_LEN+i]] ); //logerror("YMF262.C: sin5[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[5*SIN_LEN+i], tl_tab[sin_tab[5*SIN_LEN+i]] ); //logerror("YMF262.C: sin6[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[6*SIN_LEN+i], tl_tab[sin_tab[6*SIN_LEN+i]] ); //logerror("YMF262.C: sin7[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[7*SIN_LEN+i], tl_tab[sin_tab[7*SIN_LEN+i]] ); } /*logerror("YMF262.C: ENV_QUIET= %08x (dec*8=%i)\n", ENV_QUIET, ENV_QUIET*8 );*/ #ifdef SAVE_SAMPLE sample[0]=fopen("sampsum.pcm","wb"); #endif return 1; } static void OPLCloseTable( void ) { #ifdef SAVE_SAMPLE fclose(sample[0]); #endif } static void OPL3_initalize(OPL3 *chip) { int i; /* frequency base */ chip->freqbase = (chip->rate) ? ((double)chip->clock / (8.0*36)) / chip->rate : 0; #if 0 chip->rate = (double)chip->clock / (8.0*36); chip->freqbase = 1.0; #endif /* logerror("YMF262: freqbase=%f\n", chip->freqbase); */ /* Timer base time */ chip->TimerBase = (8*36) / chip->clock; /* make fnumber -> increment counter table */ for( i=0 ; i < 1024 ; i++ ) { /* opn phase increment counter = 20bit */ chip->fn_tab[i] = (uint32_t)( (double)i * 64 * chip->freqbase * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ #if 0 logerror("YMF262.C: fn_tab[%4i] = %08x (dec=%8i)\n", i, chip->fn_tab[i]>>6, chip->fn_tab[i]>>6 ); #endif } #if 0 for( i=0 ; i < 16 ; i++ ) { logerror("YMF262.C: sl_tab[%i] = %08x\n", i, sl_tab[i] ); } for( i=0 ; i < 8 ; i++ ) { int j; logerror("YMF262.C: ksl_tab[oct=%2i] =",i); for (j=0; j<16; j++) { logerror("%08x ", static_cast(ksl_tab[i*16+j]) ); } logerror("\n"); } #endif /* Amplitude modulation: 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples */ /* One entry from LFO_AM_TABLE lasts for 64 samples */ chip->lfo_am_inc = (1.0 / 64.0 ) * (1<freqbase; /* Vibrato: 8 output levels (triangle waveform); 1 level takes 1024 samples */ chip->lfo_pm_inc = (1.0 / 1024.0) * (1<freqbase; /*logerror ("chip->lfo_am_inc = %8x ; chip->lfo_pm_inc = %8x\n", chip->lfo_am_inc, chip->lfo_pm_inc);*/ /* Noise generator: a step takes 1 sample */ chip->noise_f = (1.0 / 1.0) * (1<freqbase; chip->eg_timer_add = (1<freqbase; chip->eg_timer_overflow = ( 1 ) * (1<eg_timer_add, chip->eg_timer_overflow);*/ } static inline void FM_KEYON(OPL3_SLOT *SLOT, uint32_t key_set) { if( !SLOT->key ) { /* restart Phase Generator */ SLOT->Cnt = 0; /* phase -> Attack */ SLOT->state = EG_ATT; } SLOT->key |= key_set; } static inline void FM_KEYOFF(OPL3_SLOT *SLOT, uint32_t key_clr) { if( SLOT->key ) { SLOT->key &= key_clr; if( !SLOT->key ) { /* phase -> Release */ if (SLOT->state>EG_REL) SLOT->state = EG_REL; } } } /* update phase increment counter of operator (also update the EG rates if necessary) */ static inline void CALC_FCSLOT(OPL3_CH *CH,OPL3_SLOT *SLOT) { int ksr; /* (frequency) phase increment counter */ SLOT->Incr = CH->fc * SLOT->mul; ksr = CH->kcode >> SLOT->KSR; if( SLOT->ksr != ksr ) { SLOT->ksr = ksr; /* calculate envelope generator rates */ if ((SLOT->ar + SLOT->ksr) < 16+60) { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_m_ar = (1<eg_sh_ar)-1; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_m_ar = (1<eg_sh_ar)-1; SLOT->eg_sel_ar = 13*RATE_STEPS; } SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; SLOT->eg_m_dr = (1<eg_sh_dr)-1; SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; SLOT->eg_m_rr = (1<eg_sh_rr)-1; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; } } /* set multi,am,vib,EG-TYP,KSR,mul */ static inline void set_mul(OPL3 *chip,int slot,int v) { OPL3_CH *CH = &chip->P_CH[slot/2]; OPL3_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->mul = mul_tab[v&0x0f]; SLOT->KSR = (v&0x10) ? 0 : 2; SLOT->eg_type = (v&0x20); SLOT->vib = (v&0x40); SLOT->AMmask = (v&0x80) ? ~0 : 0; if (chip->OPL3_mode & 1) { int chan_no = slot/2; /* in OPL3 mode */ //DO THIS: //if this is one of the slots of 1st channel forming up a 4-op channel //do normal operation //else normal 2 operator function //OR THIS: //if this is one of the slots of 2nd channel forming up a 4-op channel //update it using channel data of 1st channel of a pair //else normal 2 operator function switch(chan_no) { case 0: case 1: case 2: case 9: case 10: case 11: if (CH->extended) { /* normal */ CALC_FCSLOT(CH,SLOT); } else { /* normal */ CALC_FCSLOT(CH,SLOT); } break; case 3: case 4: case 5: case 12: case 13: case 14: if ((CH-3)->extended) { /* update this SLOT using frequency data for 1st channel of a pair */ CALC_FCSLOT(CH-3,SLOT); } else { /* normal */ CALC_FCSLOT(CH,SLOT); } break; default: /* normal */ CALC_FCSLOT(CH,SLOT); break; } } else { /* in OPL2 mode */ CALC_FCSLOT(CH,SLOT); } } /* set ksl & tl */ static inline void set_ksl_tl(OPL3 *chip,int slot,int v) { OPL3_CH *CH = &chip->P_CH[slot/2]; OPL3_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->ksl = ksl_shift[v >> 6]; SLOT->TL = (v&0x3f)<<(ENV_BITS-1-7); /* 7 bits TL (bit 6 = always 0) */ if (chip->OPL3_mode & 1) { int chan_no = slot/2; /* in OPL3 mode */ //DO THIS: //if this is one of the slots of 1st channel forming up a 4-op channel //do normal operation //else normal 2 operator function //OR THIS: //if this is one of the slots of 2nd channel forming up a 4-op channel //update it using channel data of 1st channel of a pair //else normal 2 operator function switch(chan_no) { case 0: case 1: case 2: case 9: case 10: case 11: if (CH->extended) { /* normal */ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); } else { /* normal */ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); } break; case 3: case 4: case 5: case 12: case 13: case 14: if ((CH-3)->extended) { /* update this SLOT using frequency data for 1st channel of a pair */ SLOT->TLL = SLOT->TL + ((CH-3)->ksl_base>>SLOT->ksl); } else { /* normal */ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); } break; default: /* normal */ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); break; } } else { /* in OPL2 mode */ SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); } } /* set attack rate & decay rate */ static inline void set_ar_dr(OPL3 *chip,int slot,int v) { OPL3_CH *CH = &chip->P_CH[slot/2]; OPL3_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->ar = (v>>4) ? 16 + ((v>>4) <<2) : 0; if ((SLOT->ar + SLOT->ksr) < 16+60) /* verified on real YMF262 - all 15 x rates take "zero" time */ { SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; SLOT->eg_m_ar = (1<eg_sh_ar)-1; SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; } else { SLOT->eg_sh_ar = 0; SLOT->eg_m_ar = (1<eg_sh_ar)-1; SLOT->eg_sel_ar = 13*RATE_STEPS; } SLOT->dr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; SLOT->eg_m_dr = (1<eg_sh_dr)-1; SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; } /* set sustain level & release rate */ static inline void set_sl_rr(OPL3 *chip,int slot,int v) { OPL3_CH *CH = &chip->P_CH[slot/2]; OPL3_SLOT *SLOT = &CH->SLOT[slot&1]; SLOT->sl = sl_tab[ v>>4 ]; SLOT->rr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; SLOT->eg_m_rr = (1<eg_sh_rr)-1; SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; } static void update_channels(OPL3 *chip, OPL3_CH *CH) { /* update channel passed as a parameter and a channel at CH+=3; */ if (CH->extended) { /* we've just switched to combined 4 operator mode */ } else { /* we've just switched to normal 2 operator mode */ } } /* write a value v to register r on OPL chip */ static void OPL3WriteReg(OPL3 *chip, int r, int v) { OPL3_CH *CH; int32_t *chanout = chip->chanout; uint32_t ch_offset = 0; int slot; int block_fnum; if(r&0x100) { switch(r) { case 0x101: /* test register */ return; case 0x104: /* 6 channels enable */ { uint8_t prev; CH = &chip->P_CH[0]; /* channel 0 */ prev = CH->extended; CH->extended = (v>>0) & 1; if(prev != CH->extended) update_channels(chip, CH); CH++; /* channel 1 */ prev = CH->extended; CH->extended = (v>>1) & 1; if(prev != CH->extended) update_channels(chip, CH); CH++; /* channel 2 */ prev = CH->extended; CH->extended = (v>>2) & 1; if(prev != CH->extended) update_channels(chip, CH); CH = &chip->P_CH[9]; /* channel 9 */ prev = CH->extended; CH->extended = (v>>3) & 1; if(prev != CH->extended) update_channels(chip, CH); CH++; /* channel 10 */ prev = CH->extended; CH->extended = (v>>4) & 1; if(prev != CH->extended) update_channels(chip, CH); CH++; /* channel 11 */ prev = CH->extended; CH->extended = (v>>5) & 1; if(prev != CH->extended) update_channels(chip, CH); } return; case 0x105: /* OPL3 extensions enable register */ chip->OPL3_mode = v&0x01; /* OPL3 mode when bit0=1 otherwise it is OPL2 mode */ /* following behaviour was tested on real YMF262, switching OPL3/OPL2 modes on the fly: - does not change the waveform previously selected (unless when ....) - does not update CH.A, CH.B, CH.C and CH.D output selectors (registers c0-c8) (unless when ....) - does not disable channels 9-17 on OPL3->OPL2 switch - does not switch 4 operator channels back to 2 operator channels */ return; default: if (r < 0x120) /*logerror("YMF262: write to unknown register (set#2): %03x value=%02x\n",r,v);*/ break; } ch_offset = 9; /* register page #2 starts from channel 9 (counting from 0) */ } /* adjust bus to 8 bits */ r &= 0xff; v &= 0xff; switch(r&0xe0) { case 0x00: /* 00-1f:control */ switch(r&0x1f) { case 0x01: /* test register */ break; case 0x02: /* Timer 1 */ chip->T[0] = (256-v)*4; break; case 0x03: /* Timer 2 */ chip->T[1] = (256-v)*16; break; case 0x04: /* IRQ clear / mask and Timer enable */ if(v&0x80) { /* IRQ flags clear */ OPL3_STATUS_RESET(chip,0x60); } else { /* set IRQ mask ,timer enable */ uint8_t st1 = v & 1; uint8_t st2 = (v>>1) & 1; /* IRQRST,T1MSK,t2MSK,x,x,x,ST2,ST1 */ OPL3_STATUS_RESET(chip, v & 0x60); OPL3_STATUSMASK_SET(chip, (~v) & 0x60 ); /* timer 2 */ if(chip->st[1] != st2) { long period = st2 ? chip->TimerBase * chip->T[1] : 0.0; chip->st[1] = st2; if (chip->timer_handler) (chip->timer_handler)(chip->TimerParam,1,period); } /* timer 1 */ if(chip->st[0] != st1) { long period = st1 ? chip->TimerBase * chip->T[0] : 0.0; chip->st[0] = st1; if (chip->timer_handler) (chip->timer_handler)(chip->TimerParam,0,period); } } break; case 0x08: /* x,NTS,x,x, x,x,x,x */ chip->nts = v; break; default: /*logerror("YMF262: write to unknown register: %02x value=%02x\n",r,v);*/ break; } break; case 0x20: /* am ON, vib ON, ksr, eg_type, mul */ slot = slot_array[r&0x1f]; if(slot < 0) return; set_mul(chip, slot + ch_offset*2, v); break; case 0x40: slot = slot_array[r&0x1f]; if(slot < 0) return; set_ksl_tl(chip, slot + ch_offset*2, v); break; case 0x60: slot = slot_array[r&0x1f]; if(slot < 0) return; set_ar_dr(chip, slot + ch_offset*2, v); break; case 0x80: slot = slot_array[r&0x1f]; if(slot < 0) return; set_sl_rr(chip, slot + ch_offset*2, v); break; case 0xa0: if (r == 0xbd) /* am depth, vibrato depth, r,bd,sd,tom,tc,hh */ { if (ch_offset != 0) /* 0xbd register is present in set #1 only */ return; chip->lfo_am_depth = v & 0x80; chip->lfo_pm_depth_range = (v&0x40) ? 8 : 0; chip->rhythm = v&0x3f; if(chip->rhythm&0x20) { /* BD key on/off */ if(v&0x10) { FM_KEYON (&chip->P_CH[6].SLOT[SLOT1], 2); FM_KEYON (&chip->P_CH[6].SLOT[SLOT2], 2); } else { FM_KEYOFF(&chip->P_CH[6].SLOT[SLOT1],~2); FM_KEYOFF(&chip->P_CH[6].SLOT[SLOT2],~2); } /* HH key on/off */ if(v&0x01) FM_KEYON (&chip->P_CH[7].SLOT[SLOT1], 2); else FM_KEYOFF(&chip->P_CH[7].SLOT[SLOT1],~2); /* SD key on/off */ if(v&0x08) FM_KEYON (&chip->P_CH[7].SLOT[SLOT2], 2); else FM_KEYOFF(&chip->P_CH[7].SLOT[SLOT2],~2); /* TOM key on/off */ if(v&0x04) FM_KEYON (&chip->P_CH[8].SLOT[SLOT1], 2); else FM_KEYOFF(&chip->P_CH[8].SLOT[SLOT1],~2); /* TOP-CY key on/off */ if(v&0x02) FM_KEYON (&chip->P_CH[8].SLOT[SLOT2], 2); else FM_KEYOFF(&chip->P_CH[8].SLOT[SLOT2],~2); } else { /* BD key off */ FM_KEYOFF(&chip->P_CH[6].SLOT[SLOT1],~2); FM_KEYOFF(&chip->P_CH[6].SLOT[SLOT2],~2); /* HH key off */ FM_KEYOFF(&chip->P_CH[7].SLOT[SLOT1],~2); /* SD key off */ FM_KEYOFF(&chip->P_CH[7].SLOT[SLOT2],~2); /* TOM key off */ FM_KEYOFF(&chip->P_CH[8].SLOT[SLOT1],~2); /* TOP-CY off */ FM_KEYOFF(&chip->P_CH[8].SLOT[SLOT2],~2); } return; } /* keyon,block,fnum */ if( (r&0x0f) > 8) return; CH = &chip->P_CH[(r&0x0f) + ch_offset]; if(!(r&0x10)) { /* a0-a8 */ block_fnum = (CH->block_fnum&0x1f00) | v; } else { /* b0-b8 */ block_fnum = ((v&0x1f)<<8) | (CH->block_fnum&0xff); if (chip->OPL3_mode & 1) { int chan_no = (r&0x0f) + ch_offset; /* in OPL3 mode */ //DO THIS: //if this is 1st channel forming up a 4-op channel //ALSO keyon/off slots of 2nd channel forming up 4-op channel //else normal 2 operator function keyon/off //OR THIS: //if this is 2nd channel forming up 4-op channel just do nothing //else normal 2 operator function keyon/off switch(chan_no) { case 0: case 1: case 2: case 9: case 10: case 11: if (CH->extended) { //if this is 1st channel forming up a 4-op channel //ALSO keyon/off slots of 2nd channel forming up 4-op channel if(v&0x20) { FM_KEYON (&CH->SLOT[SLOT1], 1); FM_KEYON (&CH->SLOT[SLOT2], 1); FM_KEYON (&(CH+3)->SLOT[SLOT1], 1); FM_KEYON (&(CH+3)->SLOT[SLOT2], 1); } else { FM_KEYOFF(&CH->SLOT[SLOT1],~1); FM_KEYOFF(&CH->SLOT[SLOT2],~1); FM_KEYOFF(&(CH+3)->SLOT[SLOT1],~1); FM_KEYOFF(&(CH+3)->SLOT[SLOT2],~1); } } else { //else normal 2 operator function keyon/off if(v&0x20) { FM_KEYON (&CH->SLOT[SLOT1], 1); FM_KEYON (&CH->SLOT[SLOT2], 1); } else { FM_KEYOFF(&CH->SLOT[SLOT1],~1); FM_KEYOFF(&CH->SLOT[SLOT2],~1); } } break; case 3: case 4: case 5: case 12: case 13: case 14: if ((CH-3)->extended) { //if this is 2nd channel forming up 4-op channel just do nothing } else { //else normal 2 operator function keyon/off if(v&0x20) { FM_KEYON (&CH->SLOT[SLOT1], 1); FM_KEYON (&CH->SLOT[SLOT2], 1); } else { FM_KEYOFF(&CH->SLOT[SLOT1],~1); FM_KEYOFF(&CH->SLOT[SLOT2],~1); } } break; default: if(v&0x20) { FM_KEYON (&CH->SLOT[SLOT1], 1); FM_KEYON (&CH->SLOT[SLOT2], 1); } else { FM_KEYOFF(&CH->SLOT[SLOT1],~1); FM_KEYOFF(&CH->SLOT[SLOT2],~1); } break; } } else { if(v&0x20) { FM_KEYON (&CH->SLOT[SLOT1], 1); FM_KEYON (&CH->SLOT[SLOT2], 1); } else { FM_KEYOFF(&CH->SLOT[SLOT1],~1); FM_KEYOFF(&CH->SLOT[SLOT2],~1); } } } /* update */ if(CH->block_fnum != block_fnum) { uint8_t block = block_fnum >> 10; CH->block_fnum = block_fnum; CH->ksl_base = (uint32_t)(ksl_tab[block_fnum>>6]); CH->fc = chip->fn_tab[block_fnum&0x03ff] >> (7-block); /* BLK 2,1,0 bits -> bits 3,2,1 of kcode */ CH->kcode = (CH->block_fnum&0x1c00)>>9; /* the info below is actually opposite to what is stated in the Manuals (verifed on real YMF262) */ /* if notesel == 0 -> lsb of kcode is bit 10 (MSB) of fnum */ /* if notesel == 1 -> lsb of kcode is bit 9 (MSB-1) of fnum */ if (chip->nts&0x40) CH->kcode |= (CH->block_fnum&0x100)>>8; /* notesel == 1 */ else CH->kcode |= (CH->block_fnum&0x200)>>9; /* notesel == 0 */ if (chip->OPL3_mode & 1) { int chan_no = (r&0x0f) + ch_offset; /* in OPL3 mode */ //DO THIS: //if this is 1st channel forming up a 4-op channel //ALSO update slots of 2nd channel forming up 4-op channel //else normal 2 operator function keyon/off //OR THIS: //if this is 2nd channel forming up 4-op channel just do nothing //else normal 2 operator function keyon/off switch(chan_no) { case 0: case 1: case 2: case 9: case 10: case 11: if (CH->extended) { //if this is 1st channel forming up a 4-op channel //ALSO update slots of 2nd channel forming up 4-op channel /* refresh Total Level in FOUR SLOTs of this channel and channel+3 using data from THIS channel */ CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); (CH+3)->SLOT[SLOT1].TLL = (CH+3)->SLOT[SLOT1].TL + (CH->ksl_base>>(CH+3)->SLOT[SLOT1].ksl); (CH+3)->SLOT[SLOT2].TLL = (CH+3)->SLOT[SLOT2].TL + (CH->ksl_base>>(CH+3)->SLOT[SLOT2].ksl); /* refresh frequency counter in FOUR SLOTs of this channel and channel+3 using data from THIS channel */ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); CALC_FCSLOT(CH,&(CH+3)->SLOT[SLOT1]); CALC_FCSLOT(CH,&(CH+3)->SLOT[SLOT2]); } else { //else normal 2 operator function /* refresh Total Level in both SLOTs of this channel */ CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); /* refresh frequency counter in both SLOTs of this channel */ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); } break; case 3: case 4: case 5: case 12: case 13: case 14: if ((CH-3)->extended) { //if this is 2nd channel forming up 4-op channel just do nothing } else { //else normal 2 operator function /* refresh Total Level in both SLOTs of this channel */ CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); /* refresh frequency counter in both SLOTs of this channel */ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); } break; default: /* refresh Total Level in both SLOTs of this channel */ CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); /* refresh frequency counter in both SLOTs of this channel */ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); break; } } else { /* in OPL2 mode */ /* refresh Total Level in both SLOTs of this channel */ CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); /* refresh frequency counter in both SLOTs of this channel */ CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); } } break; case 0xc0: /* CH.D, CH.C, CH.B, CH.A, FB(3bits), C */ if( (r&0xf) > 8) return; CH = &chip->P_CH[(r&0xf) + ch_offset]; if( chip->OPL3_mode & 1 ) { int base = ((r&0xf) + ch_offset) * 4; /* OPL3 mode */ chip->pan[ base ] = (v & 0x10) ? ~0 : 0; /* ch.A */ chip->pan[ base +1 ] = (v & 0x20) ? ~0 : 0; /* ch.B */ chip->pan[ base +2 ] = (v & 0x40) ? ~0 : 0; /* ch.C */ chip->pan[ base +3 ] = (v & 0x80) ? ~0 : 0; /* ch.D */ } else { int base = ((r&0xf) + ch_offset) * 4; /* OPL2 mode - always enabled */ chip->pan[ base ] = ~0; /* ch.A */ chip->pan[ base +1 ] = ~0; /* ch.B */ chip->pan[ base +2 ] = ~0; /* ch.C */ chip->pan[ base +3 ] = ~0; /* ch.D */ } chip->pan_ctrl_value[ (r&0xf) + ch_offset ] = v; /* store control value for OPL3/OPL2 mode switching on the fly */ CH->SLOT[SLOT1].FB = (v>>1)&7 ? ((v>>1)&7) + 7 : 0; CH->SLOT[SLOT1].CON = v&1; if( chip->OPL3_mode & 1 ) { int chan_no = (r&0x0f) + ch_offset; switch(chan_no) { case 0: case 1: case 2: case 9: case 10: case 11: if (CH->extended) { uint8_t conn = (CH->SLOT[SLOT1].CON<<1) | ((CH+3)->SLOT[SLOT1].CON<<0); switch(conn) { case 0: /* 1 -> 2 -> 3 -> 4 - out */ CH->SLOT[SLOT1].connect = &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chip->phase_modulation2; (CH+3)->SLOT[SLOT1].connect = &chip->phase_modulation; (CH+3)->SLOT[SLOT2].connect = &chanout[ chan_no + 3 ]; break; case 1: /* 1 -> 2 -\ 3 -> 4 -+- out */ CH->SLOT[SLOT1].connect = &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[ chan_no ]; (CH+3)->SLOT[SLOT1].connect = &chip->phase_modulation; (CH+3)->SLOT[SLOT2].connect = &chanout[ chan_no + 3 ]; break; case 2: /* 1 -----------\ 2 -> 3 -> 4 -+- out */ CH->SLOT[SLOT1].connect = &chanout[ chan_no ]; CH->SLOT[SLOT2].connect = &chip->phase_modulation2; (CH+3)->SLOT[SLOT1].connect = &chip->phase_modulation; (CH+3)->SLOT[SLOT2].connect = &chanout[ chan_no + 3 ]; break; case 3: /* 1 ------\ 2 -> 3 -+- out 4 ------/ */ CH->SLOT[SLOT1].connect = &chanout[ chan_no ]; CH->SLOT[SLOT2].connect = &chip->phase_modulation2; (CH+3)->SLOT[SLOT1].connect = &chanout[ chan_no + 3 ]; (CH+3)->SLOT[SLOT2].connect = &chanout[ chan_no + 3 ]; break; } } else { /* 2 operators mode */ CH->SLOT[SLOT1].connect = CH->SLOT[SLOT1].CON ? &chanout[(r&0xf)+ch_offset] : &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[(r&0xf)+ch_offset]; } break; case 3: case 4: case 5: case 12: case 13: case 14: if ((CH-3)->extended) { uint8_t conn = ((CH-3)->SLOT[SLOT1].CON<<1) | (CH->SLOT[SLOT1].CON<<0); switch(conn) { case 0: /* 1 -> 2 -> 3 -> 4 - out */ (CH-3)->SLOT[SLOT1].connect = &chip->phase_modulation; (CH-3)->SLOT[SLOT2].connect = &chip->phase_modulation2; CH->SLOT[SLOT1].connect = &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[ chan_no ]; break; case 1: /* 1 -> 2 -\ 3 -> 4 -+- out */ (CH-3)->SLOT[SLOT1].connect = &chip->phase_modulation; (CH-3)->SLOT[SLOT2].connect = &chanout[ chan_no - 3 ]; CH->SLOT[SLOT1].connect = &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[ chan_no ]; break; case 2: /* 1 -----------\ 2 -> 3 -> 4 -+- out */ (CH-3)->SLOT[SLOT1].connect = &chanout[ chan_no - 3 ]; (CH-3)->SLOT[SLOT2].connect = &chip->phase_modulation2; CH->SLOT[SLOT1].connect = &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[ chan_no ]; break; case 3: /* 1 ------\ 2 -> 3 -+- out 4 ------/ */ (CH-3)->SLOT[SLOT1].connect = &chanout[ chan_no - 3 ]; (CH-3)->SLOT[SLOT2].connect = &chip->phase_modulation2; CH->SLOT[SLOT1].connect = &chanout[ chan_no ]; CH->SLOT[SLOT2].connect = &chanout[ chan_no ]; break; } } else { /* 2 operators mode */ CH->SLOT[SLOT1].connect = CH->SLOT[SLOT1].CON ? &chanout[(r&0xf)+ch_offset] : &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[(r&0xf)+ch_offset]; } break; default: /* 2 operators mode */ CH->SLOT[SLOT1].connect = CH->SLOT[SLOT1].CON ? &chanout[(r&0xf)+ch_offset] : &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[(r&0xf)+ch_offset]; break; } } else { /* OPL2 mode - always 2 operators mode */ CH->SLOT[SLOT1].connect = CH->SLOT[SLOT1].CON ? &chanout[(r&0xf)+ch_offset] : &chip->phase_modulation; CH->SLOT[SLOT2].connect = &chanout[(r&0xf)+ch_offset]; } break; case 0xe0: /* waveform select */ slot = slot_array[r&0x1f]; if(slot < 0) return; slot += ch_offset*2; CH = &chip->P_CH[slot/2]; /* store 3-bit value written regardless of current OPL2 or OPL3 mode... (verified on real YMF262) */ v &= 7; CH->SLOT[slot&1].waveform_number = v; /* ... but select only waveforms 0-3 in OPL2 mode */ if( !(chip->OPL3_mode & 1) ) { v &= 3; /* we're in OPL2 mode */ } CH->SLOT[slot&1].wavetable = v * SIN_LEN; break; } } /* lock/unlock for common table */ static int OPL3_LockTable() { num_lock++; if(num_lock>1) return 0; /* first time */ if( !init_tables() ) { num_lock--; return -1; } return 0; } static void OPL3_UnLockTable(void) { if(num_lock) num_lock--; if(num_lock) return; /* last time */ OPLCloseTable(); } static void OPL3ResetChip(OPL3 *chip) { int c,s; chip->eg_timer = 0; chip->eg_cnt = 0; chip->noise_rng = 1; /* noise shift register */ chip->nts = 0; /* note split */ OPL3_STATUS_RESET(chip,0x60); /* reset with register write */ OPL3WriteReg(chip,0x01,0); /* test register */ OPL3WriteReg(chip,0x02,0); /* Timer1 */ OPL3WriteReg(chip,0x03,0); /* Timer2 */ OPL3WriteReg(chip,0x04,0); /* IRQ mask clear */ //FIX IT registers 101, 104 and 105 //FIX IT (dont change CH.D, CH.C, CH.B and CH.A in C0-C8 registers) for(c = 0xff ; c >= 0x20 ; c-- ) OPL3WriteReg(chip,c,0); //FIX IT (dont change CH.D, CH.C, CH.B and CH.A in C0-C8 registers) for(c = 0x1ff ; c >= 0x120 ; c-- ) OPL3WriteReg(chip,c,0); /* reset operator parameters */ for( c = 0 ; c < 9*2 ; c++ ) { OPL3_CH *CH = &chip->P_CH[c]; for(s = 0 ; s < 2 ; s++ ) { CH->SLOT[s].state = EG_OFF; CH->SLOT[s].volume = MAX_ATT_INDEX; } } } /* Create one of virtual YMF262 */ /* 'clock' is chip clock in Hz */ /* 'rate' is sampling rate */ static OPL3 *OPL3Create(uint32_t clock, uint32_t rate, int type) { char *ptr; OPL3 *chip; int state_size; if (OPL3_LockTable() == -1) return NULL; /* calculate OPL state size */ state_size = sizeof(OPL3); /* allocate memory block */ ptr = (char *)calloc(1, state_size); if (ptr == NULL) return NULL; chip = (OPL3*) ptr; chip->type = type; chip->clock = clock; chip->rate = rate; /* init global tables */ OPL3_initalize(chip); /* reset chip */ OPL3ResetChip(chip); return chip; } /* Destroy one of virtual YMF262 */ static void OPL3Destroy(OPL3 *chip) { OPL3_UnLockTable(); free(chip); } /* Optional handlers */ static void OPL3SetTimerHandler(OPL3 *chip,OPL_TIMERHANDLER timer_handler,void *param) { chip->timer_handler = timer_handler; chip->TimerParam = param; } static void OPL3SetIRQHandler(OPL3 *chip,OPL_IRQHANDLER IRQHandler,void *param) { chip->IRQHandler = IRQHandler; chip->IRQParam = param; } static void OPL3SetUpdateHandler(OPL3 *chip,OPL_UPDATEHANDLER UpdateHandler,void *param) { chip->UpdateHandler = UpdateHandler; chip->UpdateParam = param; } /* YMF262 I/O interface */ static int OPL3Write(OPL3 *chip, int a, int v) { /* data bus is 8 bits */ v &= 0xff; switch(a&3) { case 0: /* address port 0 (register set #1) */ chip->address = v; break; case 1: /* data port - ignore A1 */ case 3: /* data port - ignore A1 */ if(chip->UpdateHandler) chip->UpdateHandler(chip->UpdateParam,0); OPL3WriteReg(chip,chip->address,v); break; case 2: /* address port 1 (register set #2) */ /* verified on real YMF262: in OPL3 mode: address line A1 is stored during *address* write and ignored during *data* write. in OPL2 mode: register set#2 writes go to register set#1 (ignoring A1) verified on registers from set#2: 0x01, 0x04, 0x20-0xef The only exception is register 0x05. */ if( chip->OPL3_mode & 1 ) { /* OPL3 mode */ chip->address = v | 0x100; } else { /* in OPL2 mode the only accessible in set #2 is register 0x05 */ if( v==5 ) chip->address = v | 0x100; else chip->address = v; /* verified range: 0x01, 0x04, 0x20-0xef(set #2 becomes set #1 in opl2 mode) */ } break; } return chip->status>>7; } static unsigned char OPL3Read(OPL3 *chip,int a) { if( a==0 ) { /* status port */ return chip->status; } return 0x00; /* verified on real YMF262 */ } static int OPL3TimerOver(OPL3 *chip,int c) { if( c ) { /* Timer B */ OPL3_STATUS_SET(chip,0x20); } else { /* Timer A */ OPL3_STATUS_SET(chip,0x40); } /* reload timer */ if (chip->timer_handler) (chip->timer_handler)(chip->TimerParam,c,chip->TimerBase * chip->T[c]); return chip->status>>7; } void * ymf262_init(uint32_t clock, uint32_t rate) { return OPL3Create(clock,rate,OPL3_TYPE_YMF262); } void ymf262_shutdown(void *chip) { OPL3Destroy((OPL3 *)chip); } void ymf262_reset_chip(void *chip) { OPL3ResetChip((OPL3 *)chip); } int ymf262_write(void *chip, int a, int v) { return OPL3Write((OPL3 *)chip, a, v); } unsigned char ymf262_read(void *chip, int a) { /* Note on status register: */ /* YM3526(OPL) and YM3812(OPL2) return bit2 and bit1 in HIGH state */ /* YMF262(OPL3) always returns bit2 and bit1 in LOW state */ /* which can be used to identify the chip */ /* YMF278(OPL4) returns bit2 in LOW and bit1 in HIGH state ??? info from manual - not verified */ return OPL3Read((OPL3 *)chip, a); } int ymf262_timer_over(void *chip, int c) { return OPL3TimerOver((OPL3 *)chip, c); } void ymf262_set_timer_handler(void *chip, OPL_TIMERHANDLER timer_handler, void *param) { OPL3SetTimerHandler((OPL3 *)chip, timer_handler, param); } void ymf262_set_irq_handler(void *chip,OPL_IRQHANDLER IRQHandler,void *param) { OPL3SetIRQHandler((OPL3 *)chip, IRQHandler, param); } void ymf262_set_update_handler(void *chip,OPL_UPDATEHANDLER UpdateHandler,void *param) { OPL3SetUpdateHandler((OPL3 *)chip, UpdateHandler, param); } /* ** Generate samples for one of the YMF262's ** ** 'which' is the virtual YMF262 number ** '**buffers' is table of 4 pointers to the buffers: CH.A, CH.B, CH.C and CH.D ** 'length' is the number of samples that should be generated */ void ymf262_update_one(void *_chip, OPLSAMPLE **buffers, int length) { int i; OPL3 *chip = (OPL3 *)_chip; int32_t *chanout = chip->chanout; uint8_t rhythm = chip->rhythm&0x20; OPLSAMPLE *ch_a = buffers[0]; OPLSAMPLE *ch_b = buffers[1]; OPLSAMPLE *ch_c = buffers[2]; OPLSAMPLE *ch_d = buffers[3]; for( i=0; i < length ; i++ ) { int a,b,c,d; advance_lfo(chip); /* clear channel outputs */ memset(chip->chanout, 0, sizeof(chip->chanout)); #if 1 /* register set #1 */ chan_calc(chip, &chip->P_CH[0]); /* extended 4op ch#0 part 1 or 2op ch#0 */ if (chip->P_CH[0].extended) chan_calc_ext(chip, &chip->P_CH[3]); /* extended 4op ch#0 part 2 */ else chan_calc(chip, &chip->P_CH[3]); /* standard 2op ch#3 */ chan_calc(chip, &chip->P_CH[1]); /* extended 4op ch#1 part 1 or 2op ch#1 */ if (chip->P_CH[1].extended) chan_calc_ext(chip, &chip->P_CH[4]); /* extended 4op ch#1 part 2 */ else chan_calc(chip, &chip->P_CH[4]); /* standard 2op ch#4 */ chan_calc(chip, &chip->P_CH[2]); /* extended 4op ch#2 part 1 or 2op ch#2 */ if (chip->P_CH[2].extended) chan_calc_ext(chip, &chip->P_CH[5]); /* extended 4op ch#2 part 2 */ else chan_calc(chip, &chip->P_CH[5]); /* standard 2op ch#5 */ if(!rhythm) { chan_calc(chip, &chip->P_CH[6]); chan_calc(chip, &chip->P_CH[7]); chan_calc(chip, &chip->P_CH[8]); } else /* Rhythm part */ { chan_calc_rhythm(chip, &chip->P_CH[0], (chip->noise_rng>>0)&1 ); } /* register set #2 */ chan_calc(chip, &chip->P_CH[ 9]); if (chip->P_CH[9].extended) chan_calc_ext(chip, &chip->P_CH[12]); else chan_calc(chip, &chip->P_CH[12]); chan_calc(chip, &chip->P_CH[10]); if (chip->P_CH[10].extended) chan_calc_ext(chip, &chip->P_CH[13]); else chan_calc(chip, &chip->P_CH[13]); chan_calc(chip, &chip->P_CH[11]); if (chip->P_CH[11].extended) chan_calc_ext(chip, &chip->P_CH[14]); else chan_calc(chip, &chip->P_CH[14]); /* channels 15,16,17 are fixed 2-operator channels only */ chan_calc(chip, &chip->P_CH[15]); chan_calc(chip, &chip->P_CH[16]); chan_calc(chip, &chip->P_CH[17]); #endif /* accumulator register set #1 */ a = chanout[0] & chip->pan[0]; b = chanout[0] & chip->pan[1]; c = chanout[0] & chip->pan[2]; d = chanout[0] & chip->pan[3]; #if 1 a += chanout[1] & chip->pan[4]; b += chanout[1] & chip->pan[5]; c += chanout[1] & chip->pan[6]; d += chanout[1] & chip->pan[7]; a += chanout[2] & chip->pan[8]; b += chanout[2] & chip->pan[9]; c += chanout[2] & chip->pan[10]; d += chanout[2] & chip->pan[11]; a += chanout[3] & chip->pan[12]; b += chanout[3] & chip->pan[13]; c += chanout[3] & chip->pan[14]; d += chanout[3] & chip->pan[15]; a += chanout[4] & chip->pan[16]; b += chanout[4] & chip->pan[17]; c += chanout[4] & chip->pan[18]; d += chanout[4] & chip->pan[19]; a += chanout[5] & chip->pan[20]; b += chanout[5] & chip->pan[21]; c += chanout[5] & chip->pan[22]; d += chanout[5] & chip->pan[23]; a += chanout[6] & chip->pan[24]; b += chanout[6] & chip->pan[25]; c += chanout[6] & chip->pan[26]; d += chanout[6] & chip->pan[27]; a += chanout[7] & chip->pan[28]; b += chanout[7] & chip->pan[29]; c += chanout[7] & chip->pan[30]; d += chanout[7] & chip->pan[31]; a += chanout[8] & chip->pan[32]; b += chanout[8] & chip->pan[33]; c += chanout[8] & chip->pan[34]; d += chanout[8] & chip->pan[35]; /* accumulator register set #2 */ a += chanout[9] & chip->pan[36]; b += chanout[9] & chip->pan[37]; c += chanout[9] & chip->pan[38]; d += chanout[9] & chip->pan[39]; a += chanout[10] & chip->pan[40]; b += chanout[10] & chip->pan[41]; c += chanout[10] & chip->pan[42]; d += chanout[10] & chip->pan[43]; a += chanout[11] & chip->pan[44]; b += chanout[11] & chip->pan[45]; c += chanout[11] & chip->pan[46]; d += chanout[11] & chip->pan[47]; a += chanout[12] & chip->pan[48]; b += chanout[12] & chip->pan[49]; c += chanout[12] & chip->pan[50]; d += chanout[12] & chip->pan[51]; a += chanout[13] & chip->pan[52]; b += chanout[13] & chip->pan[53]; c += chanout[13] & chip->pan[54]; d += chanout[13] & chip->pan[55]; a += chanout[14] & chip->pan[56]; b += chanout[14] & chip->pan[57]; c += chanout[14] & chip->pan[58]; d += chanout[14] & chip->pan[59]; a += chanout[15] & chip->pan[60]; b += chanout[15] & chip->pan[61]; c += chanout[15] & chip->pan[62]; d += chanout[15] & chip->pan[63]; a += chanout[16] & chip->pan[64]; b += chanout[16] & chip->pan[65]; c += chanout[16] & chip->pan[66]; d += chanout[16] & chip->pan[67]; a += chanout[17] & chip->pan[68]; b += chanout[17] & chip->pan[69]; c += chanout[17] & chip->pan[70]; d += chanout[17] & chip->pan[71]; #endif a >>= FINAL_SH; b >>= FINAL_SH; c >>= FINAL_SH; d >>= FINAL_SH; /* limit check */ a = limit( a , MAXOUT, MINOUT ); b = limit( b , MAXOUT, MINOUT ); c = limit( c , MAXOUT, MINOUT ); d = limit( d , MAXOUT, MINOUT ); /* store to sound buffer */ ch_a[i] = a; ch_b[i] = b; ch_c[i] = c; ch_d[i] = d; advance(chip); } } // `buffers` is an array of 18 pointers, all pointing to separate 32-bit interlaced stereo // buffers of `length` size in samples. void ymf262_update_multi(void *_chip, int32_t **buffers, int length) { int i; OPL3 *chip = (OPL3 *)_chip; int32_t *chanout = chip->chanout; uint8_t rhythm = chip->rhythm&0x20; for(i = 0; i < length; i++) { int j, k; advance_lfo(chip); /* clear channel outputs */ memset(chip->chanout, 0, sizeof(chip->chanout)); /* register set #1 */ chan_calc(chip, &chip->P_CH[0]); /* extended 4op ch#0 part 1 or 2op ch#0 */ if (chip->P_CH[0].extended) chan_calc_ext(chip, &chip->P_CH[3]); /* extended 4op ch#0 part 2 */ else chan_calc(chip, &chip->P_CH[3]); /* standard 2op ch#3 */ chan_calc(chip, &chip->P_CH[1]); /* extended 4op ch#1 part 1 or 2op ch#1 */ if (chip->P_CH[1].extended) chan_calc_ext(chip, &chip->P_CH[4]); /* extended 4op ch#1 part 2 */ else chan_calc(chip, &chip->P_CH[4]); /* standard 2op ch#4 */ chan_calc(chip, &chip->P_CH[2]); /* extended 4op ch#2 part 1 or 2op ch#2 */ if (chip->P_CH[2].extended) chan_calc_ext(chip, &chip->P_CH[5]); /* extended 4op ch#2 part 2 */ else chan_calc(chip, &chip->P_CH[5]); /* standard 2op ch#5 */ if(!rhythm) { chan_calc(chip, &chip->P_CH[6]); chan_calc(chip, &chip->P_CH[7]); chan_calc(chip, &chip->P_CH[8]); } else { /* Rhythm part */ chan_calc_rhythm(chip, &chip->P_CH[0], (chip->noise_rng>>0)&1 ); } /* register set #2 */ chan_calc(chip, &chip->P_CH[ 9]); if (chip->P_CH[9].extended) chan_calc_ext(chip, &chip->P_CH[12]); else chan_calc(chip, &chip->P_CH[12]); chan_calc(chip, &chip->P_CH[10]); if (chip->P_CH[10].extended) chan_calc_ext(chip, &chip->P_CH[13]); else chan_calc(chip, &chip->P_CH[13]); chan_calc(chip, &chip->P_CH[11]); if (chip->P_CH[11].extended) chan_calc_ext(chip, &chip->P_CH[14]); else chan_calc(chip, &chip->P_CH[14]); /* channels 15,16,17 are fixed 2-operator channels only */ chan_calc(chip, &chip->P_CH[15]); chan_calc(chip, &chip->P_CH[16]); chan_calc(chip, &chip->P_CH[17]); /* accumulator register set #1 */ for (j = 0, k = 0; j < 18; j++) { if (buffers[j]) { buffers[j][i * 2 + 0] += chanout[j] & chip->pan[k++]; buffers[j][i * 2 + 1] += chanout[j] & chip->pan[k++]; k += 2; // skip next two pans } else { k += 4; } } advance(chip); } }schismtracker-20250313/player/fmpatches.c000066400000000000000000000240021476471630300202400ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* General MIDI assignments used by Creative Labs' MIDI player (PLAY.EXE) (As found in dro2midi.) */ #include "headers.h" #include "player/sndfile.h" static const uint8_t patches[][11] = { {0x00,0x00,0x4F,0x00,0xF1,0xD2,0x51,0x43,0x00,0x00,0x06}, /*1*/ {0x02,0x12,0x4F,0x00,0xF1,0xD2,0x51,0x43,0x00,0x00,0x02}, /*2*/ {0x00,0x11,0x4A,0x00,0xF1,0xD2,0x53,0x74,0x00,0x00,0x06}, /*3*/ {0x03,0x11,0x4F,0x00,0xF1,0xD2,0x53,0x74,0x01,0x01,0x06}, /*4*/ {0x01,0x11,0x66,0x00,0xF1,0xD2,0x51,0xC3,0x00,0x00,0x06}, /*5*/ {0xC0,0xD2,0x52,0x00,0xF1,0xD2,0x53,0x94,0x00,0x00,0x06}, /*6*/ {0x12,0x18,0x86,0x00,0xF3,0xFC,0x00,0x33,0x00,0x00,0x08}, /*7*/ {0xD0,0x12,0x4E,0x00,0xA8,0x92,0x32,0xA7,0x03,0x02,0x00}, /*8*/ {0xC8,0xD1,0x4F,0x00,0xF2,0xF3,0x64,0x77,0x00,0x00,0x08}, /*9*/ {0x33,0x34,0x0E,0x00,0x01,0x7D,0x11,0x34,0x00,0x00,0x08}, /*10*/ {0x17,0x16,0x50,0x00,0xD1,0xD3,0x52,0x92,0x00,0x01,0x04}, /*11*/ {0xE7,0xE1,0x21,0x00,0xF5,0xF6,0x77,0x14,0x00,0x00,0x08}, /*12*/ {0x95,0x81,0x4E,0x00,0xDA,0xF9,0x25,0x15,0x00,0x00,0x0A}, /*13*/ {0x27,0x21,0x1F,0x00,0xF5,0xF5,0x96,0x57,0x00,0x00,0x08}, /*14*/ {0x87,0xF1,0x4E,0x80,0xB1,0xE6,0x33,0x42,0x00,0x00,0x00}, /*15*/ {0x31,0x11,0x87,0x80,0xA1,0x7D,0x11,0x43,0x00,0x00,0x08}, /*16*/ {0x32,0xB1,0x8C,0x00,0x91,0xA1,0x07,0x19,0x02,0x00,0x05}, /*17*/ {0x31,0xB4,0x54,0x80,0xF1,0xF5,0x07,0x19,0x00,0x00,0x07}, /*18*/ {0x24,0x21,0x40,0x49,0xFF,0xFF,0x0F,0x0F,0x00,0x00,0x01}, /*19*/ {0xD2,0xF1,0x44,0x80,0x91,0xA1,0x57,0x09,0x01,0x01,0x03}, /*20*/ {0x01,0x02,0x52,0x80,0xF0,0xF0,0x1F,0x1F,0x01,0x00,0x0A}, /*21*/ {0x21,0x32,0x4F,0x01,0xF2,0x52,0x0B,0x0B,0x00,0x01,0x0A}, /*22*/ {0xF0,0xF2,0x93,0x00,0xD8,0xB3,0x0B,0x0B,0x02,0x01,0x0A}, /*23*/ {0x20,0x31,0x5D,0x00,0xF2,0x52,0x0B,0x0B,0x03,0x02,0x00}, /*24*/ {0x01,0x01,0x1B,0x00,0xF4,0xF3,0x25,0x46,0x02,0x00,0x00}, /*25*/ {0x11,0x01,0x0F,0x00,0xF4,0xF3,0x25,0x46,0x01,0x00,0x00}, /*26*/ {0x01,0x01,0x27,0x00,0xF1,0xF4,0x1F,0x88,0x02,0x00,0x0A}, /*27*/ {0x12,0x13,0x44,0x00,0xEA,0xD2,0x32,0xE7,0x01,0x01,0x00}, /*28*/ {0x30,0x31,0x45,0x00,0xA4,0xF5,0x32,0xE7,0x03,0x00,0x00}, /*29*/ {0x21,0x21,0x0F,0x00,0xF5,0xF1,0x17,0x78,0x02,0x01,0x04}, /*30*/ {0x01,0x20,0x41,0x00,0xD1,0xC1,0x34,0xA5,0x03,0x03,0x04}, /*31*/ {0x10,0x12,0x43,0x00,0xA7,0xE3,0x97,0xE7,0x03,0x02,0x00}, /*32*/ {0x20,0x21,0x28,0x00,0xC5,0xD2,0x15,0xA4,0x00,0x00,0x0C}, /*33*/ {0x30,0x21,0x16,0x00,0xF2,0xF3,0x9F,0x78,0x00,0x00,0x0C}, /*34*/ {0x30,0x21,0x11,0x00,0x82,0xF3,0x9F,0x78,0x00,0x00,0x0A}, /*35*/ {0x21,0x21,0x23,0x00,0x73,0x93,0x1A,0x87,0x00,0x00,0x0C}, /*36*/ {0x30,0x21,0x0E,0x00,0x62,0xF3,0x55,0x68,0x02,0x00,0x0A}, /*37*/ {0x30,0x22,0x0C,0x00,0x62,0xD5,0xB5,0x98,0x01,0x00,0x08}, /*38*/ {0x70,0x72,0x93,0x40,0x64,0xA1,0x43,0x43,0x00,0x00,0x0A}, /*39*/ {0x30,0x32,0x8D,0x80,0x44,0x92,0x43,0x43,0x02,0x00,0x0A}, /*40*/ {0xE1,0xE2,0x4E,0x00,0x65,0x61,0x43,0x44,0x02,0x02,0x00}, /*41*/ {0xA1,0xA2,0x8E,0x00,0x65,0x63,0x43,0x45,0x02,0x02,0x00}, /*42*/ {0xB0,0x61,0x87,0x40,0xD1,0x62,0x11,0x15,0x02,0x01,0x06}, /*43*/ {0xF0,0x20,0x8A,0x80,0xB1,0xA0,0x11,0x15,0x02,0x01,0x06}, /*44*/ {0xF1,0xE2,0x89,0x40,0x73,0x43,0x01,0x05,0x02,0x00,0x06}, /*45*/ {0x31,0x21,0x57,0x80,0xF8,0xF7,0xF9,0xE6,0x03,0x02,0x0E}, /*46*/ {0x32,0x01,0x24,0x80,0xF1,0xF5,0x35,0x35,0x00,0x00,0x00}, /*47*/ {0x00,0x00,0x04,0x00,0xAA,0xD2,0xC8,0xB3,0x00,0x00,0x0A}, /*48*/ {0xE0,0xF1,0x4F,0x00,0xD4,0x55,0x0B,0x0B,0x02,0x02,0x0A}, /*49*/ {0xE0,0xF0,0x52,0x00,0x96,0x35,0x05,0x01,0x02,0x02,0x0A}, /*50*/ {0xE1,0xF1,0x4F,0x00,0x36,0x45,0x05,0x02,0x02,0x02,0x0A}, /*51*/ {0xE2,0xE1,0x48,0x80,0x21,0x41,0x43,0x45,0x02,0x01,0x00}, /*52*/ {0xE0,0xF1,0x16,0x00,0x41,0x20,0x52,0x72,0x02,0x02,0x00}, /*53*/ {0xE0,0xF1,0x11,0x00,0x01,0xD0,0x52,0x72,0x02,0x02,0x00}, /*54*/ {0xE0,0xF1,0x1A,0x00,0x61,0x30,0x52,0x73,0x00,0x02,0x00}, /*55*/ {0x50,0x50,0x0B,0x00,0x84,0xA4,0x4B,0x99,0x00,0x00,0x0A}, /*56*/ {0x31,0x61,0x1C,0x80,0x41,0x92,0x0B,0x3B,0x00,0x00,0x0E}, /*57*/ {0xB1,0x61,0x1C,0x00,0x41,0x92,0x1F,0x3B,0x00,0x00,0x0E}, /*58*/ {0x20,0x21,0x18,0x00,0x52,0xA2,0x15,0x24,0x00,0x00,0x0C}, /*59*/ {0xC1,0xC1,0x94,0x80,0x74,0xA3,0xEA,0xF5,0x02,0x01,0x0E}, /*60*/ {0x21,0x21,0x28,0x00,0x41,0x81,0xB4,0x98,0x00,0x00,0x0E}, /*61*/ {0x21,0x21,0x1D,0x00,0x51,0xE1,0xAE,0x3E,0x02,0x01,0x0E}, /*62*/ {0xE0,0xE0,0x93,0x80,0x51,0x81,0xA6,0x97,0x02,0x01,0x0E}, /*63*/ {0xE0,0xE1,0x93,0x80,0x51,0xE1,0xA6,0x97,0x02,0x01,0x0E}, /*64*/ {0xE0,0xF2,0x4B,0x01,0xD8,0xB3,0x0B,0x0B,0x02,0x01,0x08}, /*65*/ {0xE0,0xF1,0x49,0x01,0xB8,0xB3,0x0B,0x0B,0x02,0x01,0x08}, /*66*/ {0xE0,0xF0,0x4E,0x01,0x98,0xC3,0x0B,0x0B,0x01,0x02,0x08}, /*67*/ {0xE0,0xF1,0x4C,0x01,0x88,0xD3,0x0B,0x0B,0x01,0x01,0x08}, /*68*/ {0xF1,0xE4,0xC5,0x00,0x7E,0x8C,0x17,0x0E,0x00,0x00,0x08}, /*69*/ {0x60,0x72,0x4F,0x00,0xD8,0xB3,0x0B,0x0B,0x00,0x01,0x0A}, /*70*/ {0x31,0x72,0xD1,0x80,0xD5,0x91,0x19,0x1B,0x00,0x00,0x0C}, /*71*/ {0x32,0x71,0xC8,0x80,0xD5,0x73,0x19,0x1B,0x00,0x00,0x0C}, /*72*/ {0xE2,0x62,0x6A,0x00,0x9E,0x55,0x8F,0x2A,0x00,0x00,0x0E}, /*73*/ {0xE0,0x61,0xEC,0x00,0x7E,0x65,0x8F,0x2A,0x00,0x00,0x0E}, /*74*/ {0x62,0xA2,0x88,0x83,0x84,0x75,0x27,0x17,0x00,0x00,0x09}, /*75*/ {0x62,0xA2,0x84,0x83,0x84,0x75,0x27,0x17,0x00,0x00,0x09}, /*76*/ {0xE3,0x62,0x6D,0x00,0x57,0x57,0x04,0x77,0x00,0x00,0x0E}, /*77*/ {0xF1,0xE1,0x28,0x00,0x57,0x67,0x34,0x5D,0x03,0x00,0x0E}, /*78*/ {0xD1,0x72,0xC7,0x00,0x31,0x42,0x0F,0x09,0x00,0x00,0x0B}, /*79*/ {0xF2,0x72,0xC7,0x00,0x51,0x42,0x05,0x69,0x00,0x00,0x0B}, /*80*/ {0x23,0x31,0x4F,0x00,0x51,0x60,0x5B,0x25,0x01,0x01,0x00}, /*81*/ {0x22,0x31,0x48,0x00,0x31,0xC0,0x9B,0x65,0x02,0x01,0x00}, /*82*/ {0xF1,0xE1,0x28,0x00,0x57,0x67,0x34,0x0D,0x03,0x00,0x0E}, /*83*/ {0xE1,0xE1,0x23,0x00,0x57,0x67,0x04,0x4D,0x03,0x00,0x0E}, /*84*/ {0xE2,0x31,0x42,0x08,0x78,0xF3,0x0B,0x0B,0x01,0x01,0x08}, /*85*/ {0xE2,0xE2,0x21,0x00,0x11,0x40,0x52,0x73,0x01,0x01,0x08}, /*86*/ {0x23,0xA4,0xC0,0x00,0x51,0x35,0x07,0x79,0x01,0x02,0x0D}, /*87*/ {0x24,0xA0,0xC0,0x00,0x51,0x75,0x07,0x09,0x01,0x02,0x09}, /*88*/ {0xE0,0xF0,0x16,0x00,0xB1,0xE0,0x51,0x75,0x02,0x02,0x00}, /*89*/ {0x03,0xA4,0xC0,0x00,0x52,0xF4,0x03,0x55,0x00,0x00,0x09}, /*90*/ {0xE1,0xE1,0x93,0x80,0x31,0xA1,0xA6,0x97,0x01,0x01,0x0A}, /*91*/ {0xF0,0x71,0xC4,0x80,0x10,0x11,0x01,0xC1,0x02,0x02,0x01}, /*92*/ {0xC1,0xE0,0x4F,0x00,0xB1,0x12,0x53,0x74,0x02,0x02,0x06}, /*93*/ {0xC0,0x41,0x6D,0x00,0xF9,0xF2,0x21,0xB3,0x01,0x00,0x0E}, /*94*/ {0xE3,0xE2,0x4C,0x00,0x21,0xA1,0x43,0x45,0x01,0x01,0x00}, /*95*/ {0xE3,0xE2,0x0C,0x00,0x11,0x80,0x52,0x73,0x01,0x01,0x08}, /*96*/ {0x26,0x88,0xC0,0x00,0x55,0xF8,0x47,0x19,0x00,0x00,0x0B}, /*97*/ {0x23,0xE4,0xD4,0x00,0xE5,0x35,0x03,0x65,0x00,0x00,0x07}, /*98*/ {0x27,0x32,0xC0,0x00,0x32,0xA4,0x62,0x33,0x00,0x00,0x00}, /*99*/ {0xD0,0x31,0x4E,0x00,0x98,0xA2,0x32,0x47,0x01,0x02,0x00}, /*100*/ {0xF0,0x71,0xC0,0x00,0x93,0x43,0x03,0x02,0x01,0x00,0x0F}, /*101*/ {0xE0,0xF1,0x1A,0x80,0x13,0x33,0x52,0x13,0x01,0x02,0x00}, /*102*/ {0xE0,0xF1,0x1A,0x00,0x45,0x32,0xBA,0x91,0x00,0x02,0x00}, /*103*/ {0x11,0x15,0x18,0x03,0x58,0xA2,0x02,0x72,0x01,0x00,0x0A}, /*104*/ {0x10,0x18,0x80,0x40,0xF1,0xF1,0x53,0x53,0x00,0x00,0x00}, /*105*/ {0x31,0x17,0x86,0x80,0xA1,0x7D,0x11,0x23,0x00,0x00,0x08}, /*106*/ {0x10,0x18,0x80,0x40,0xF1,0xF6,0x53,0x54,0x00,0x00,0x00}, /*107*/ {0x31,0x34,0x21,0x00,0xF5,0x93,0x56,0xE8,0x01,0x00,0x08}, /*108*/ {0x03,0x15,0x4F,0x00,0xF1,0xD6,0x39,0x74,0x03,0x00,0x06}, /*109*/ {0x31,0x22,0x43,0x00,0x6E,0x8B,0x17,0x0C,0x01,0x02,0x02}, /*110*/ {0x31,0x22,0x1C,0x80,0x61,0x52,0x03,0x67,0x00,0x00,0x0E}, /*111*/ {0x60,0xF0,0x0C,0x80,0x81,0x61,0x03,0x0C,0x00,0x01,0x08}, /*112*/ {0x27,0x05,0x55,0x00,0x31,0xA7,0x62,0x75,0x00,0x00,0x00}, /*113*/ {0x95,0x16,0x81,0x00,0xE7,0x96,0x01,0x67,0x00,0x00,0x04}, /*114*/ {0x0C,0x01,0x87,0x80,0xF0,0xF2,0x05,0x05,0x01,0x01,0x04}, /*115*/ {0x35,0x11,0x44,0x00,0xF8,0xF5,0xFF,0x75,0x00,0x00,0x0E}, /*116*/ {0x10,0x10,0x0B,0x00,0xA7,0xD5,0xEC,0xF5,0x00,0x00,0x00}, /*117*/ {0x20,0x01,0x0B,0x00,0xA8,0xD6,0xC8,0xB7,0x00,0x00,0x00}, /*118*/ {0x00,0x01,0x0B,0x00,0x88,0xD5,0xC4,0xB7,0x00,0x00,0x00}, /*119*/ {0x0C,0x10,0x8F,0x80,0x41,0x33,0x31,0x2B,0x00,0x03,0x08}, /*120*/ {0x17,0xF7,0x00,0x00,0x3B,0xEA,0xDF,0x97,0x03,0x00,0x0B}, /*121*/ {0x12,0x18,0x06,0x00,0x73,0x3C,0x02,0x74,0x00,0x00,0x0E}, /*122*/ {0x02,0x08,0x00,0x00,0x3E,0x14,0x01,0xF3,0x02,0x02,0x0E}, /*123*/ {0xF5,0xF6,0xD4,0x00,0xEB,0x45,0x03,0x68,0x00,0x00,0x07}, /*124*/ {0xF0,0xCA,0x00,0xC0,0xDA,0xB0,0x71,0x17,0x01,0x01,0x08}, /*125*/ {0xF0,0xE2,0x00,0xC0,0x1E,0x11,0x11,0x11,0x01,0x01,0x08}, /*126*/ {0xE7,0xE8,0x00,0x04,0x34,0x10,0x00,0xB2,0x02,0x02,0x0E}, /*127*/ {0x0C,0x04,0x00,0x00,0xF0,0xF6,0xF0,0xE6,0x02,0x00,0x0E}, /*128*/ }; void adlib_patch_apply(song_sample_t *smp, int32_t patchnum) { if (patchnum < 0 || patchnum > 127) { printf("adlib_patch_apply: invalid patch %d\n", patchnum); return; } memcpy(smp->adlib_bytes, patches[patchnum], 11); strncpy(smp->name, midi_program_names[patchnum], sizeof(smp->name) - 1); smp->name[sizeof(smp->name) - 1] = '\0'; // Paranoid. sprintf(smp->filename, "MIDI#%03d", patchnum + 1); smp->flags |= CHN_ADLIB; if (smp->data) { csf_free_sample(smp->data); smp->data = NULL; } smp->length = 1; smp->data = csf_allocate_sample(1); } schismtracker-20250313/player/mixer.c000066400000000000000000000765731476471630300174360ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "player/sndfile.h" #include "player/snd_fm.h" #include "player/snd_gm.h" #include "player/cmixer.h" #include "bshift.h" #include "util.h" // for CLAMP // For pingpong loops that work like most of Impulse Tracker's drivers // (including SB16, SBPro, and the disk writer) -- as well as XMPlay, use 1 // To make them sound like the GUS driver, use 0. // It's really only noticeable for very small loops... (e.g. chip samples) // (thanks Saga_Musix for this) #define PINGPONG_OFFSET 1 /* The following lut settings are PRECOMPUTED. * * If you plan on changing these settings, you * MUST also regenerate the arrays. */ // number of bits used to scale spline coefs #define SPLINE_QUANTBITS 14 #define SPLINE_QUANTSCALE (1L << SPLINE_QUANTBITS) #define SPLINE_8SHIFT (SPLINE_QUANTBITS - 8) #define SPLINE_16SHIFT (SPLINE_QUANTBITS) // forces coefsset to unity gain #define SPLINE_CLAMPFORUNITY // log2(number) of precalculated splines (range is [4..14]) #define SPLINE_FRACBITS 10 #define SPLINE_LUTLEN (1L<>1) // cutoff (1.0 == pi/2) #define WFIR_CUTOFF 0.90f // wfir type #define WFIR_HANN 0 #define WFIR_HAMMING 1 #define WFIR_BLACKMANEXACT 2 #define WFIR_BLACKMAN3T61 3 #define WFIR_BLACKMAN3T67 4 #define WFIR_BLACKMAN4T92 5 #define WFIR_BLACKMAN4T74 6 #define WFIR_KAISER4T 7 #define WFIR_TYPE WFIR_BLACKMANEXACT // wfir help #ifndef M_zPI #define M_zPI 3.1415926535897932384626433832795 #endif #define M_zEPS 1e-8 #define M_zBESSELEPS 1e-21 #define SPLINE_FRACSHIFT ((16 - SPLINE_FRACBITS) - 2) #define SPLINE_FRACMASK (((1L << (16 - SPLINE_FRACSHIFT)) - 1) & ~3) #define WFIR_FRACSHIFT (16 - (WFIR_FRACBITS + 1 + WFIR_LOG2WIDTH)) #define WFIR_FRACMASK ((((1L << (17 - WFIR_FRACSHIFT)) - 1) & ~((1L << WFIR_LOG2WIDTH) - 1))) #define WFIR_FRACHALVE (1L << (16 - (WFIR_FRACBITS + 2))) #include "player/precomp_lut.h" // ---------------------------------------------------------------------------- // MIXING MACROS // ---------------------------------------------------------------------------- // Safe absolute value of a 32-bit signed integer. static inline SCHISM_ALWAYS_INLINE uint32_t safe_abs_32(int32_t x) { return (x < 0) ? (uint32_t)(~x + 1) : (uint32_t)x; } // Fast average of two unsigned 32-bit integers. static inline SCHISM_ALWAYS_INLINE uint32_t avg_u32(uint32_t a, uint32_t b) { return (a >> 1) + (b >> 1) + (a & b & 1); } #define SNDMIX_BEGINSAMPLELOOP(bits) \ register song_voice_t * const chan = channel; \ position = chan->position_frac; \ const int##bits##_t *p = (int##bits##_t *)(chan->current_sample_data) + chan->position; \ if (chan->flags & CHN_STEREO) p += chan->position; \ int32_t *pvol = pbuffer; \ uint32_t max = chan->vu_meter; \ do { #define SNDMIX_ENDSAMPLELOOP \ position += chan->increment; \ } while (pvol < pbufmax); \ chan->vu_meter = max; \ chan->position += position >> 16; \ chan->position_frac = position & 0xFFFF; ////////////////////////////////////////////////////////////////////////////// // Mono // No interpolation #define SNDMIX_GETMONOVOLNOIDO(bits) \ int32_t vol = lshift_signed(p[position >> 16], -bits + 16); // Linear Interpolation #define SNDMIX_GETMONOVOLLINEAR(bits) \ int32_t poshi = position >> 16; \ int32_t poslo = (position >> 8) & 0xFF; \ int32_t srcvol = p[poshi]; \ int32_t destvol = p[poshi + 1]; \ int32_t vol = lshift_signed(srcvol, -bits + 16) + rshift_signed(poslo * (destvol - srcvol), bits - 8); // spline interpolation (2 guard bits should be enough???) #define SNDMIX_GETMONOVOLSPLINE(bits) \ int32_t poshi = position >> 16; \ int32_t poslo = rshift_signed(position, SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ int32_t vol = rshift_signed( \ cubic_spline_lut[poslo + 0] * (int32_t)p[poshi - 1] \ + cubic_spline_lut[poslo + 1] * (int32_t)p[poshi + 0] \ + cubic_spline_lut[poslo + 2] * (int32_t)p[poshi + 1] \ + cubic_spline_lut[poslo + 3] * (int32_t)p[poshi + 2], \ SPLINE_##bits##SHIFT); // fir interpolation #define SNDMIX_GETMONOVOLFIRFILTER(bits) \ int32_t poshi = position >> 16; \ int32_t poslo = (position & 0xFFFF); \ int32_t firidx = rshift_signed(poslo + WFIR_FRACHALVE, WFIR_FRACSHIFT) & WFIR_FRACMASK; \ int32_t vol = rshift_signed( \ rshift_signed( \ (windowed_fir_lut[firidx + 0] * (int32_t)p[poshi + 1 - 4]) + \ (windowed_fir_lut[firidx + 1] * (int32_t)p[poshi + 2 - 4]) + \ (windowed_fir_lut[firidx + 2] * (int32_t)p[poshi + 3 - 4]) + \ (windowed_fir_lut[firidx + 3] * (int32_t)p[poshi + 4 - 4]) \ , 1) + \ rshift_signed( \ (windowed_fir_lut[firidx + 4] * (int32_t)p[poshi + 5 - 4]) + \ (windowed_fir_lut[firidx + 5] * (int32_t)p[poshi + 6 - 4]) + \ (windowed_fir_lut[firidx + 6] * (int32_t)p[poshi + 7 - 4]) + \ (windowed_fir_lut[firidx + 7] * (int32_t)p[poshi + 8 - 4]) \ , 1), \ WFIR_##bits##SHIFT - 1); ///////////////////////////////////////////////////////////////////////////// // Stereo #define SNDMIX_GETSTEREOVOLNOIDO(bits) \ int32_t vol_l = lshift_signed(p[(position >> 16) * 2 + 0], -bits + 16); \ int32_t vol_r = lshift_signed(p[(position >> 16) * 2 + 1], -bits + 16); #define SNDMIX_GETSTEREOVOLLINEAR(bits) \ int32_t poshi = position >> 16; \ int32_t poslo = (position >> 8) & 0xFF; \ int32_t srcvol_l = p[poshi * 2 + 0]; \ int32_t srcvol_r = p[poshi * 2 + 1]; \ int32_t vol_l = lshift_signed(srcvol_l, -bits + 16) + rshift_signed(poslo * (p[poshi * 2 + 2] - srcvol_l), bits - 8); \ int32_t vol_r = lshift_signed(srcvol_r, -bits + 16) + rshift_signed(poslo * (p[poshi * 2 + 3] - srcvol_r), bits - 8); // Spline Interpolation #define SNDMIX_GETSTEREOVOLSPLINE(bits) \ int32_t poshi = position >> 16; \ int32_t poslo = (position >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \ int32_t vol_l = rshift_signed( \ cubic_spline_lut[poslo + 0] * (int32_t)p[(poshi - 1) * 2] + \ cubic_spline_lut[poslo + 1] * (int32_t)p[(poshi + 0) * 2] + \ cubic_spline_lut[poslo + 2] * (int32_t)p[(poshi + 1) * 2] + \ cubic_spline_lut[poslo + 3] * (int32_t)p[(poshi + 2) * 2], \ SPLINE_##bits##SHIFT); \ int32_t vol_r = rshift_signed( \ cubic_spline_lut[poslo + 0] * (int32_t)p[(poshi - 1) * 2 + 1] + \ cubic_spline_lut[poslo + 1] * (int32_t)p[(poshi + 0) * 2 + 1] + \ cubic_spline_lut[poslo + 2] * (int32_t)p[(poshi + 1) * 2 + 1] + \ cubic_spline_lut[poslo + 3] * (int32_t)p[(poshi + 2) * 2 + 1], \ SPLINE_##bits##SHIFT); // fir interpolation #define SNDMIX_GETSTEREOVOLFIRFILTER(bits) \ int32_t poshi = position >> 16; \ int32_t poslo = (position & 0xFFFF); \ int32_t firidx = rshift_signed(poslo + WFIR_FRACHALVE, WFIR_FRACSHIFT) & WFIR_FRACMASK; \ int32_t vol_l = rshift_signed( \ rshift_signed( \ (windowed_fir_lut[firidx + 0] * p[(poshi + 1 - 4) * 2]) + \ (windowed_fir_lut[firidx + 1] * p[(poshi + 2 - 4) * 2]) + \ (windowed_fir_lut[firidx + 2] * p[(poshi + 3 - 4) * 2]) + \ (windowed_fir_lut[firidx + 3] * p[(poshi + 4 - 4) * 2]) \ , 1) + \ rshift_signed( \ (windowed_fir_lut[firidx + 4] * p[(poshi + 5 - 4) * 2]) + \ (windowed_fir_lut[firidx + 5] * p[(poshi + 6 - 4) * 2]) + \ (windowed_fir_lut[firidx + 6] * p[(poshi + 7 - 4) * 2]) + \ (windowed_fir_lut[firidx + 7] * p[(poshi + 8 - 4) * 2]) \ , 1), \ WFIR_##bits##SHIFT - 1); \ int32_t vol_r = rshift_signed( \ rshift_signed( \ (windowed_fir_lut[firidx + 0] * p[(poshi + 1 - 4) * 2 + 1]) + \ (windowed_fir_lut[firidx + 1] * p[(poshi + 2 - 4) * 2 + 1]) + \ (windowed_fir_lut[firidx + 2] * p[(poshi + 3 - 4) * 2 + 1]) + \ (windowed_fir_lut[firidx + 3] * p[(poshi + 4 - 4) * 2 + 1]) \ , 1) + \ rshift_signed( \ (windowed_fir_lut[firidx + 4] * p[(poshi + 5 - 4) * 2 + 1]) + \ (windowed_fir_lut[firidx + 5] * p[(poshi + 6 - 4) * 2 + 1]) + \ (windowed_fir_lut[firidx + 6] * p[(poshi + 7 - 4) * 2 + 1]) + \ (windowed_fir_lut[firidx + 7] * p[(poshi + 8 - 4) * 2 + 1]) \ , 1), \ WFIR_##bits##SHIFT - 1); #define SNDMIX_STOREVUMETER \ uint32_t vol_avg = avg_u32(safe_abs_32(vol_lx), safe_abs_32(vol_rx)); \ if (vol_avg > UINT32_C(0xFF0000)) vol_avg = UINT32_C(0xFF0000); \ if (vol_avg > max) max = vol_avg; // FIXME why are these backwards? what? #define SNDMIX_STOREMONOVOL \ int32_t vol_lx = vol * chan->right_volume; \ int32_t vol_rx = vol * chan->left_volume; \ SNDMIX_STOREVUMETER \ pvol[0] += vol_lx; \ pvol[1] += vol_rx; \ pvol += 2; #define SNDMIX_STORESTEREOVOL \ int32_t vol_lx = vol_l * chan->right_volume; \ int32_t vol_rx = vol_r * chan->left_volume; \ SNDMIX_STOREVUMETER \ pvol[0] += vol_lx; \ pvol[1] += vol_rx; \ pvol += 2; #define SNDMIX_RAMPMONOVOL \ left_ramp_volume += chan->left_ramp; \ right_ramp_volume += chan->right_ramp; \ int32_t vol_lx = vol * rshift_signed(right_ramp_volume, VOLUMERAMPPRECISION); \ int32_t vol_rx = vol * rshift_signed(left_ramp_volume, VOLUMERAMPPRECISION); \ SNDMIX_STOREVUMETER \ pvol[0] += vol_lx; \ pvol[1] += vol_rx; \ pvol += 2; #define SNDMIX_RAMPSTEREOVOL \ left_ramp_volume += chan->left_ramp; \ right_ramp_volume += chan->right_ramp; \ int32_t vol_lx = vol_l * rshift_signed(right_ramp_volume, VOLUMERAMPPRECISION); \ int32_t vol_rx = vol_r * rshift_signed(left_ramp_volume, VOLUMERAMPPRECISION); \ SNDMIX_STOREVUMETER \ pvol[0] += vol_lx; \ pvol[1] += vol_rx; \ pvol += 2; /////////////////////////////////////////////////// // Resonant Filters #define MUL_32_TO_64(x, y) ((int64_t)(x) * (y)) #define FILT_CLIP(i) CLAMP(i, -65536, 65534) #define MIX_BEGIN_FILTER(chn) \ int32_t fy##chn##1 = channel->filter_y[chn][0]; \ int32_t fy##chn##2 = channel->filter_y[chn][1]; \ int32_t t##chn; #define SNDMIX_PROCESSFILTER(outchn, volume) \ t##outchn = rshift_signed( \ MUL_32_TO_64(volume, chan->filter_a0) \ + MUL_32_TO_64(FILT_CLIP(fy##outchn##1), chan->filter_b0) \ + MUL_32_TO_64(FILT_CLIP(fy##outchn##2), chan->filter_b1) \ + lshift_signed(1, FILTERPRECISION - 1), \ FILTERPRECISION); \ fy##outchn##2 = fy##outchn##1; fy##outchn##1 = t##outchn; volume = t##outchn; #define MIX_END_FILTER(chn) \ channel->filter_y[chn][0] = fy##chn##1; \ channel->filter_y[chn][1] = fy##chn##2; // aliases #define MIX_BEGIN_MONO_FILTER MIX_BEGIN_FILTER(0) #define MIX_END_MONO_FILTER MIX_END_FILTER(0) #define SNDMIX_PROCESSMONOFILTER SNDMIX_PROCESSFILTER(0, vol) #define MIX_BEGIN_STEREO_FILTER MIX_BEGIN_FILTER(0) MIX_BEGIN_FILTER(1) #define MIX_END_STEREO_FILTER MIX_END_FILTER(0) MIX_END_FILTER(1) #define SNDMIX_PROCESSSTEREOFILTER SNDMIX_PROCESSFILTER(0, vol_l) SNDMIX_PROCESSFILTER(1, vol_r) ////////////////////////////////////////////////////////// // Interfaces typedef void(* mix_interface_t)(song_voice_t *, int32_t *, int32_t *); #define BEGIN_MIX_INTERFACE(func) \ static void func(song_voice_t *channel, int32_t *pbuffer, int32_t *pbufmax) \ { \ int32_t position; #define END_MIX_INTERFACE() \ SNDMIX_ENDSAMPLELOOP \ } // Volume Ramps #define BEGIN_RAMPMIX_INTERFACE(func) \ BEGIN_MIX_INTERFACE(func) \ int32_t right_ramp_volume = channel->right_ramp_volume; \ int32_t left_ramp_volume = channel->left_ramp_volume; #define END_RAMPMIX_INTERFACE() \ SNDMIX_ENDSAMPLELOOP \ channel->right_ramp_volume = right_ramp_volume; \ channel->right_volume = rshift_signed(right_ramp_volume, VOLUMERAMPPRECISION); \ channel->left_ramp_volume = left_ramp_volume; \ channel->left_volume = rshift_signed(left_ramp_volume, VOLUMERAMPPRECISION); \ } // Mono Resonant Filters #define BEGIN_MIX_MONO_FLT_INTERFACE(func) \ BEGIN_MIX_INTERFACE(func) \ MIX_BEGIN_MONO_FILTER #define END_MIX_MONO_FLT_INTERFACE() \ SNDMIX_ENDSAMPLELOOP \ MIX_END_MONO_FILTER \ } #define BEGIN_RAMPMIX_MONO_FLT_INTERFACE(func) \ BEGIN_MIX_INTERFACE(func) \ int32_t right_ramp_volume = channel->right_ramp_volume; \ int32_t left_ramp_volume = channel->left_ramp_volume; \ MIX_BEGIN_MONO_FILTER #define END_RAMPMIX_MONO_FLT_INTERFACE() \ SNDMIX_ENDSAMPLELOOP \ MIX_END_MONO_FILTER \ channel->right_ramp_volume = right_ramp_volume; \ channel->right_volume = rshift_signed(right_ramp_volume, VOLUMERAMPPRECISION); \ channel->left_ramp_volume = left_ramp_volume; \ channel->left_volume = rshift_signed(left_ramp_volume, VOLUMERAMPPRECISION); \ } // Stereo Resonant Filters #define BEGIN_MIX_STEREO_FLT_INTERFACE(func) \ BEGIN_MIX_INTERFACE(func) \ MIX_BEGIN_STEREO_FILTER #define END_MIX_STEREO_FLT_INTERFACE() \ SNDMIX_ENDSAMPLELOOP \ MIX_END_STEREO_FILTER \ } #define BEGIN_RAMPMIX_STEREO_FLT_INTERFACE(func) \ BEGIN_MIX_INTERFACE(func) \ int32_t right_ramp_volume = channel->right_ramp_volume; \ int32_t left_ramp_volume = channel->left_ramp_volume; \ MIX_BEGIN_STEREO_FILTER #define END_RAMPMIX_STEREO_FLT_INTERFACE() \ SNDMIX_ENDSAMPLELOOP \ MIX_END_STEREO_FILTER \ channel->right_ramp_volume = right_ramp_volume; \ channel->right_volume = rshift_signed(right_ramp_volume, VOLUMERAMPPRECISION); \ channel->left_ramp_volume = left_ramp_volume; \ channel->left_volume = rshift_signed(left_ramp_volume, VOLUMERAMPPRECISION); \ } #define BEGIN_RESAMPLE_INTERFACE(func, sampletype, numchannels) \ SCHISM_SIMD void func(sampletype *oldbuf, sampletype *newbuf, uint32_t oldlen, uint32_t newlen) \ { \ uint32_t position = 0; \ const sampletype *p = oldbuf; \ sampletype *pvol = newbuf; \ const sampletype *pbufmax = &newbuf[newlen* numchannels]; \ uint32_t increment = (((uint64_t)oldlen) << 16) / newlen; \ do { #define END_RESAMPLE_INTERFACE_MONO() \ *pvol = vol; \ pvol++; \ position += increment; \ } while (pvol < pbufmax); \ } #define END_RESAMPLE_INTERFACE_STEREO() \ pvol[0] = vol_l; \ pvol[1] = vol_r; \ pvol += 2; \ position += increment; \ } while (pvol < pbufmax); \ } /* --------------------------------------------------------------------------- */ /* generate processing functions */ /* This is really just a diet version of C++'s templates. */ #define DEFINE_MIX_INTERFACE_ALL(bits, chns, chnsupper, resampling, resampupper, filter, fltnam, fltint, ramp, rampupper, rmpint) \ BEGIN_ ## rmpint ## MIX_ ## fltint ## INTERFACE(fltnam ## chns ## bits ## Bit ## resampling ## ramp ## Mix) \ SNDMIX_BEGINSAMPLELOOP(bits) \ SNDMIX_GET ## chnsupper ## VOL ## resampupper(bits) \ filter \ SNDMIX_ ## rampupper ## chnsupper ## VOL \ END_ ## rmpint ## MIX_ ## fltint ## INTERFACE() /* defines all ramping variations */ #define DEFINE_MIX_INTERFACE_RAMP(bits, chns, chnsupper, filter, fltnam, fltint, resampling, resampupper) \ DEFINE_MIX_INTERFACE_ALL(bits, chns, chnsupper, resampling, resampupper, filter, fltnam, fltint, /* none */, STORE, /* none */) \ DEFINE_MIX_INTERFACE_ALL(bits, chns, chnsupper, resampling, resampupper, filter, fltnam, fltint, Ramp, RAMP, RAMP) /* defines all resampling variations */ #define DEFINE_MIX_INTERFACE_RESAMPLING(bits, chns, chnsupper, filter, fltnam, fltint) \ DEFINE_MIX_INTERFACE_RAMP(bits, chns, chnsupper, filter, fltnam, fltint, /* none */, NOIDO) \ DEFINE_MIX_INTERFACE_RAMP(bits, chns, chnsupper, filter, fltnam, fltint, Linear, LINEAR) \ DEFINE_MIX_INTERFACE_RAMP(bits, chns, chnsupper, filter, fltnam, fltint, Spline, SPLINE) \ DEFINE_MIX_INTERFACE_RAMP(bits, chns, chnsupper, filter, fltnam, fltint, FirFilter, FIRFILTER) /* defines filter + no-filter variants */ #define DEFINE_MIX_INTERFACE(bits) \ DEFINE_MIX_INTERFACE_RESAMPLING(bits, Mono, MONO, /* none */, /* none */, /* none */) \ DEFINE_MIX_INTERFACE_RESAMPLING(bits, Mono, MONO, SNDMIX_PROCESSMONOFILTER, Filter, MONO_FLT_) \ DEFINE_MIX_INTERFACE_RESAMPLING(bits, Stereo, STEREO, /* none */, /* none */, /* none */) \ DEFINE_MIX_INTERFACE_RESAMPLING(bits, Stereo, STEREO, SNDMIX_PROCESSSTEREOFILTER, Filter, STEREO_FLT_) DEFINE_MIX_INTERFACE(8) DEFINE_MIX_INTERFACE(16) // Public Resampling Methods #define DEFINE_MONO_RESAMPLE_INTERFACE(bits) \ BEGIN_RESAMPLE_INTERFACE(ResampleMono##bits##BitFirFilter, int##bits##_t, 1) \ SNDMIX_GETMONOVOLFIRFILTER(bits) \ vol >>= (WFIR_16SHIFT-WFIR_##bits##SHIFT); /* This is used to compensate, since the code assumes that it always outputs to 16bits */ \ vol = CLAMP(vol, INT##bits##_MIN, INT##bits##_MAX); \ END_RESAMPLE_INTERFACE_MONO() #define DEFINE_STEREO_RESAMPLE_INTERFACE(bits) \ BEGIN_RESAMPLE_INTERFACE(ResampleStereo##bits##BitFirFilter, int##bits##_t, 2) \ SNDMIX_GETSTEREOVOLFIRFILTER(bits) \ vol_l >>= (WFIR_16SHIFT-WFIR_##bits##SHIFT); /* This is used to compensate, since the code assumes that it always outputs to 16bits */ \ vol_r >>= (WFIR_16SHIFT-WFIR_##bits##SHIFT); /* This is used to compensate, since the code assumes that it always outputs to 16bits */ \ vol_l = CLAMP(vol_l, INT##bits##_MIN, INT##bits##_MAX); \ vol_r = CLAMP(vol_r, INT##bits##_MIN, INT##bits##_MAX); \ END_RESAMPLE_INTERFACE_STEREO() DEFINE_MONO_RESAMPLE_INTERFACE(8) DEFINE_MONO_RESAMPLE_INTERFACE(16) DEFINE_STEREO_RESAMPLE_INTERFACE(8) DEFINE_STEREO_RESAMPLE_INTERFACE(16) ///////////////////////////////////////////////////////////////////////////////////// // // Mix function tables // // // Index is as follows: // [b1-b0] format (8-bit-mono, 16-bit-mono, 8-bit-stereo, 16-bit-stereo) // [b2] ramp // [b3] filter // [b5-b4] src type // [b6] fast #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 #define BUILD_MIX_FUNCTION_TABLE_RAMP(resampling, filter, ramp) \ filter##Mono8Bit##resampling##ramp##Mix, \ filter##Mono16Bit##resampling##ramp##Mix, \ filter##Stereo8Bit##resampling##ramp##Mix, \ filter##Stereo16Bit##resampling##ramp##Mix, #define BUILD_MIX_FUNCTION_TABLE_FILTER(resampling, filter) \ BUILD_MIX_FUNCTION_TABLE_RAMP(resampling, filter, /* none */) \ BUILD_MIX_FUNCTION_TABLE_RAMP(resampling, filter, Ramp) #define BUILD_MIX_FUNCTION_TABLE(resampling) \ BUILD_MIX_FUNCTION_TABLE_FILTER(resampling, /* none */) \ BUILD_MIX_FUNCTION_TABLE_FILTER(resampling, Filter) // mix_(bits)(m/s)[_filt]_(interp/spline/fir/whatever)[_ramp] static const mix_interface_t mix_functions[2 * 2 * 16] = { BUILD_MIX_FUNCTION_TABLE(/* none */) BUILD_MIX_FUNCTION_TABLE(Linear) BUILD_MIX_FUNCTION_TABLE(Spline) BUILD_MIX_FUNCTION_TABLE(FirFilter) }; static inline SCHISM_ALWAYS_INLINE int32_t buffer_length_to_samples(uint32_t mix_buf_cnt, song_voice_t *chan) { return (chan->increment * (int32_t)mix_buf_cnt) + chan->position_frac; } static inline SCHISM_ALWAYS_INLINE uint32_t samples_to_buffer_length(int32_t samples, song_voice_t *chan) { return (uint32_t)((lshift_signed(samples - 1, 16)) / safe_abs_32(chan->increment)) + 1u; } static int32_t get_sample_count(song_voice_t *chan, int32_t samples) { int32_t loop_start = (chan->flags & CHN_LOOP) ? chan->loop_start : 0; int32_t increment = chan->increment; if (samples <= 0 || !increment || !chan->length) return 0; // Under zero ? if ((int32_t)chan->position < loop_start) { if (increment < 0) { // Invert loop for bidi loops int32_t delta = ((loop_start - chan->position) << 16) - (chan->position_frac & 0xFFFF); chan->position = loop_start + (delta >> 16); chan->position_frac = delta & 0xFFFF; if ((int32_t) chan->position < loop_start || chan->position >= (loop_start + chan->length) / 2) { chan->position = loop_start; chan->position_frac = 0; } increment = -increment; chan->increment = increment; // go forward chan->flags &= ~(CHN_PINGPONGFLAG); if ((!(chan->flags & CHN_LOOP)) || (chan->position >= chan->length)) { chan->position = chan->length; chan->position_frac = 0; return 0; } } else { // We probably didn't hit the loop end yet (first loop), so we do nothing if ((int32_t)chan->position < 0) chan->position = 0; } } // Past the end else if (chan->position >= chan->length) { // not looping -> stop this channel if (!(chan->flags & CHN_LOOP)) return 0; if (chan->flags & CHN_PINGPONGLOOP) { // Invert loop if (increment > 0) { increment = -increment; chan->increment = increment; } chan->flags |= CHN_PINGPONGFLAG; // adjust loop position uint64_t overshoot = (uint64_t)((chan->position - chan->length) << 16) + chan->position_frac; uint64_t loop_length = (uint64_t)(chan->loop_end - chan->loop_start - PINGPONG_OFFSET) << 16; if (overshoot < loop_length) { uint64_t new_position = ((uint64_t)(chan->length - PINGPONG_OFFSET) << 16) - overshoot; chan->position = (uint32_t)(new_position >> 16); chan->position_frac = (uint32_t)(new_position & 0xFFFF); } else { chan->position = chan->loop_start; /* not 100% accurate, but only matters for extremely small loops played at extremely high frequencies */ chan->position_frac = 0; } } else { // This is a bug if (increment < 0) { increment = -increment; chan->increment = increment; } // Restart at loop start chan->position += loop_start - chan->length; if ((int) chan->position < loop_start) chan->position = chan->loop_start; chan->flags |= CHN_LOOP_WRAPPED; } } int32_t position = chan->position; // too big increment, and/or too small loop length if (position < loop_start) { if (position < 0 || increment < 0) return 0; } if (position < 0 || position >= (int32_t)chan->length) return 0; int32_t position_frac = (uint16_t) chan->position_frac, sample_count = samples; if (increment < 0) { int32_t inv = -increment; int32_t maxsamples = 16384 / ((inv >> 16) + 1); if (maxsamples < 2) maxsamples = 2; if (samples > maxsamples) samples = maxsamples; int32_t delta_hi = (inv >> 16) * (samples - 1); int32_t delta_lo = (inv & 0xffff) * (samples - 1); int32_t pos_dest = position - delta_hi + ((position_frac - delta_lo) >> 16); if (pos_dest < loop_start) { sample_count = (uint32_t) (((((int64_t) position - loop_start) << 16) + position_frac - 1) / inv) + 1; } } else { int32_t maxsamples = 16384 / ((increment >> 16) + 1); if (maxsamples < 2) maxsamples = 2; if (samples > maxsamples) samples = maxsamples; int32_t delta_hi = (increment >> 16) * (samples - 1); int32_t delta_lo = (increment & 0xffff) * (samples - 1); int32_t pos_dest = position + delta_hi + ((position_frac + delta_lo) >> 16); if (pos_dest >= (int32_t) chan->length) { sample_count = (uint32_t) (((((int64_t) chan->length - position) << 16) - position_frac - 1) / increment) + 1; } } if (sample_count <= 1) return 1; else if (sample_count > samples) return samples; return sample_count; } uint32_t csf_create_stereo_mix(song_t *csf, uint32_t count) { int32_t* ofsl, *ofsr; unsigned int nchused, nchmixed; if (!count) return 0; nchused = nchmixed = 0; // yuck if (csf->multi_write) for (uint32_t nchan = 0; nchan < MAX_CHANNELS; nchan++) memset(csf->multi_write[nchan].buffer, 0, sizeof(csf->multi_write[nchan].buffer)); for (uint32_t nchan = 0; nchan < csf->num_voices; nchan++) { song_voice_t *const channel = &csf->voices[csf->voice_mix[nchan]]; uint32_t flags; uint32_t nrampsamples; int32_t smpcount; int32_t nsamples; int32_t *pbuffer; if (!channel->current_sample_data && !channel->lofs && !channel->rofs) continue; ofsr = &csf->dry_rofs_vol; ofsl = &csf->dry_lofs_vol; flags = 0; if (channel->flags & CHN_16BIT) flags |= MIXNDX_16BIT; if (channel->flags & CHN_STEREO) flags |= MIXNDX_STEREO; if (channel->flags & CHN_FILTER) flags |= MIXNDX_FILTER; if (!(channel->flags & CHN_NOIDO) && !(csf->mix_flags & SNDMIX_NORESAMPLING)) { // use hq-fir mixer? if ((csf->mix_flags & (SNDMIX_HQRESAMPLER | SNDMIX_ULTRAHQSRCMODE)) == (SNDMIX_HQRESAMPLER | SNDMIX_ULTRAHQSRCMODE)) flags |= MIXNDX_FIRSRC; else if (csf->mix_flags & SNDMIX_HQRESAMPLER) flags |= MIXNDX_SPLINESRC; else flags |= MIXNDX_LINEARSRC; // use } nsamples = count; if (csf->multi_write) { int32_t master = (csf->voice_mix[nchan] < MAX_CHANNELS) ? csf->voice_mix[nchan] : (channel->master_channel - 1); pbuffer = csf->multi_write[master].buffer; csf->multi_write[master].used = 1; } else { pbuffer = csf->mix_buffer; } nchused++; // Our loop lookahead buffer is basically the exact same as OpenMPT's. // (in essence, it is mostly just a backport) // // This means that it has the same bugs that are notated in OpenMPT's // `soundlib/Fastmix.cpp' file, which are the following: // // - Playing samples backwards should reverse interpolation LUTs for interpolation modes // with more than two taps since they're not symmetric. We might need separate LUTs // because otherwise we will add tons of branches. // - Loop wraparound works pretty well in general, but not at the start of bidi samples. // - The loop lookahead stuff might still fail for samples with backward loops. int8_t *const smp_ptr = (int8_t *const)(channel->ptr_sample->data); int8_t *lookahead_ptr = NULL; const uint32_t lookahead_start = (channel->loop_end < MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE) ? channel->loop_start : MAX(channel->loop_start, channel->loop_end - MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE); // This shouldn't be necessary with interpolation disabled but with that conditional // it causes weird precision loss within the sample, hence why I've removed it. This // shouldn't be that heavy anyway :p if (channel->flags & CHN_LOOP) { song_sample_t *pins = channel->ptr_sample; uint32_t lookahead_offset = 3 * MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE + pins->length - channel->loop_end; if (channel->flags & CHN_SUSTAINLOOP) lookahead_offset += 4 * MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE; lookahead_ptr = smp_ptr + (lookahead_offset * ((pins->flags & CHN_STEREO) ? 2 : 1) * ((pins->flags & CHN_16BIT) ? 2 : 1)); } //////////////////////////////////////////////////// uint32_t naddmix = 0; channel->vu_meter <<= 16; do { nrampsamples = nsamples; if (channel->ramp_length > 0) { if ((int32_t)nrampsamples > channel->ramp_length) nrampsamples = channel->ramp_length; } smpcount = 1; /* Figure out the number of remaining samples, * unless we're in AdLib or MIDI mode (to prevent * artificial KeyOffs) */ if (!(channel->flags & CHN_ADLIB)) { smpcount = get_sample_count(channel, nrampsamples); } if (smpcount <= 0) { // Stopping the channel channel->current_sample_data = NULL; channel->length = 0; channel->position = 0; channel->position_frac = 0; channel->ramp_length = 0; end_channel_ofs(channel, pbuffer, nsamples); *ofsr += channel->rofs; *ofsl += channel->lofs; channel->rofs = channel->lofs = 0; channel->flags &= ~CHN_PINGPONGFLAG; break; } // Should we mix this channel ? if ((nchmixed >= csf->max_voices && !(csf->mix_flags & SNDMIX_DIRECTTODISK)) || (!channel->ramp_length && !(channel->left_volume | channel->right_volume))) { int32_t delta = buffer_length_to_samples(smpcount, channel); channel->position_frac = delta & 0xFFFF; channel->position += (delta >> 16); channel->rofs = channel->lofs = 0; pbuffer += smpcount * 2; } else if (!(channel->flags & CHN_ADLIB)) { // Mix the stream, unless we're in AdLib mode // Choose function for mixing mix_interface_t mix_func; mix_func = channel->ramp_length ? mix_functions[flags | MIXNDX_RAMP] : mix_functions[flags]; // Loop wrap-around magic if (lookahead_ptr) { const int32_t oldcount = smpcount; const int32_t pos_dest = rshift_signed((channel->position << 16) + (channel->increment * (nsamples - 1)) + channel->position_frac, 16); const int at_loop_start = (channel->position >= channel->loop_start && channel->position < channel->loop_start + MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE); if (!at_loop_start) channel->flags &= ~(CHN_LOOP_WRAPPED); channel->current_sample_data = smp_ptr; if (channel->position >= lookahead_start) { int32_t samples_to_read = (channel->increment < 0) ? ((int32_t)channel->position - lookahead_start) : (channel->loop_end - (int32_t)channel->position); // this line causes sample 8 in BUTTERFL.XM to play incorrectly //samples_to_read = MAX(samples_to_read, channel->loop_end - channel->loop_start); smpcount = samples_to_buffer_length(samples_to_read, channel); channel->current_sample_data = lookahead_ptr; } else if ((channel->flags & CHN_LOOP_WRAPPED) && at_loop_start) { // Interpolate properly after looping smpcount = samples_to_buffer_length(channel->loop_start + MAX_INTERPOLATION_LOOKAHEAD_BUFFER_SIZE - channel->position, channel); channel->current_sample_data = lookahead_ptr + ((channel->loop_end - channel->loop_start) * ((channel->ptr_sample->flags & CHN_STEREO) ? 2 : 1) * ((channel->ptr_sample->flags & CHN_16BIT) ? 2 : 1)); } else if (channel->increment >= 0 && pos_dest >= (int32_t)lookahead_start && smpcount > 1) { smpcount = samples_to_buffer_length(lookahead_start - channel->position, channel); } smpcount = CLAMP(smpcount, 1, oldcount); } int32_t *pbufmax = pbuffer + (smpcount * 2); channel->rofs = -*(pbufmax - 2); channel->lofs = -*(pbufmax - 1); mix_func(channel, pbuffer, pbufmax); channel->rofs += *(pbufmax - 2); channel->lofs += *(pbufmax - 1); pbuffer = pbufmax; naddmix = 1; } nsamples -= smpcount; if (channel->ramp_length) { if (channel->ramp_length <= smpcount) { // Ramping is done channel->ramp_length = 0; channel->right_volume = channel->right_volume_new; channel->left_volume = channel->left_volume_new; channel->right_ramp = channel->left_ramp = 0; if ((channel->flags & CHN_NOTEFADE) && (!(channel->fadeout_volume))) { channel->length = 0; channel->current_sample_data = NULL; } } else { channel->ramp_length -= smpcount; } } } while (nsamples > 0); channel->vu_meter >>= 16; nchmixed += naddmix; } GM_IncrementSongCounter(csf, count); Fmdrv_Mix(csf, count); return nchused; } schismtracker-20250313/player/mixutil.c000066400000000000000000000124741476471630300177730ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bshift.h" #include "util.h" #include "player/sndfile.h" #include "player/cmixer.h" #define OFSDECAYSHIFT 8 #define OFSDECAYMASK 0xFF void init_mix_buffer(int32_t *buffer, uint32_t samples) { memset(buffer, 0, samples * sizeof(int32_t)); } void stereo_fill(int32_t *buffer, uint32_t samples, int32_t* profs, int32_t *plofs) { int32_t rofs = *profs; int32_t lofs = *plofs; if (!rofs && !lofs) { init_mix_buffer(buffer, samples * 2); return; } for (uint32_t i = 0; i < samples; i++) { int32_t x_r = rshift_signed(rofs + (rshift_signed(-rofs, 31) & OFSDECAYMASK), OFSDECAYSHIFT); int32_t x_l = rshift_signed(lofs + (rshift_signed(-lofs, 31) & OFSDECAYMASK), OFSDECAYSHIFT); rofs -= x_r; lofs -= x_l; buffer[i * 2 ] = x_r; buffer[i * 2 + 1] = x_l; } *profs = rofs; *plofs = lofs; } void end_channel_ofs(song_voice_t *channel, int32_t *buffer, uint32_t samples) { int32_t rofs = channel->rofs; int32_t lofs = channel->lofs; if (!rofs && !lofs) return; for (uint32_t i = 0; i < samples; i++) { int32_t x_r = rshift_signed(rofs + (rshift_signed(-rofs, 31) & OFSDECAYMASK), OFSDECAYSHIFT); int32_t x_l = rshift_signed(lofs + (rshift_signed(-lofs, 31) & OFSDECAYMASK), OFSDECAYSHIFT); rofs -= x_r; lofs -= x_l; buffer[i * 2] += x_r; buffer[i * 2 + 1] += x_l; } channel->rofs = rofs; channel->lofs = lofs; } void mono_from_stereo(int32_t *mix_buf, uint32_t samples) { for (uint32_t j, i = 0; i < samples; i++) { j = i << 1; mix_buf[i] = (mix_buf[j] + mix_buf[j + 1]) >> 1; } } // ---------------------------------------------------------------------------- // Clip and convert functions // ---------------------------------------------------------------------------- // XXX mins/max were int[2] // // The original C version was written by Rani Assaf // Clip and convert to 8 bit. mins and maxs returned in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX]. mins[0] left, mins[1] right. uint32_t clip_32_to_8(void *ptr, int32_t *buffer, uint32_t samples, int32_t *mins, int32_t *maxs) { unsigned char *p = (unsigned char *) ptr; for (uint32_t i = 0; i < samples; i++) { int32_t n = CLAMP(buffer[i], MIXING_CLIPMIN, MIXING_CLIPMAX); if (n < mins[i & 1]) mins[i & 1] = n; else if (n > maxs[i & 1]) maxs[i & 1] = n; // 8-bit unsigned p[i] = rshift_signed(n, 24 - MIXING_ATTENUATION) ^ 0x80; } return samples; } // Clip and convert to 16 bit. mins and maxs returned in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX]. mins[0] left, mins[1] right. uint32_t clip_32_to_16(void *ptr, int32_t *buffer, uint32_t samples, int32_t *mins, int32_t *maxs) { int16_t *p = (int16_t *) ptr; for (uint32_t i = 0; i < samples; i++) { int32_t n = CLAMP(buffer[i], MIXING_CLIPMIN, MIXING_CLIPMAX); if (n < mins[i & 1]) mins[i & 1] = n; else if (n > maxs[i & 1]) maxs[i & 1] = n; // 16-bit signed p[i] = rshift_signed(n, 16 - MIXING_ATTENUATION); } return samples * 2; } // Clip and convert to 24 bit. mins and maxs returned in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX]. mins[0] left, mins[1] right. // Note, this is 24bit, not 24-in-32bits. The former is used in .wav. The latter is used in audio IO uint32_t clip_32_to_24(void *ptr, int32_t *buffer, uint32_t samples, int32_t *mins, int32_t *maxs) { /* the inventor of 24bit anything should be shot */ unsigned char *p = (unsigned char *) ptr; for (uint32_t i = 0; i < samples; i++) { int32_t n = CLAMP(buffer[i], MIXING_CLIPMIN, MIXING_CLIPMAX); if (n < mins[i & 1]) mins[i & 1] = n; else if (n > maxs[i & 1]) maxs[i & 1] = n; // 24-bit signed n = rshift_signed(n, 8 - MIXING_ATTENUATION); /* err, assume same endian */ memcpy(p, &n, 3); p += 3; } return samples * 3; } // Clip and convert to 32 bit(int). mins and maxs returned in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX]. mins[0] left, mins[1] right. uint32_t clip_32_to_32(void *ptr, int32_t *buffer, uint32_t samples, int32_t *mins, int32_t *maxs) { int32_t *p = (int32_t *) ptr; for (uint32_t i = 0; i < samples; i++) { int32_t n = CLAMP(buffer[i], MIXING_CLIPMIN, MIXING_CLIPMAX); if (n < mins[i & 1]) mins[i & 1] = n; else if (n > maxs[i & 1]) maxs[i & 1] = n; // 32-bit signed p[i] = lshift_signed(n, MIXING_ATTENUATION); } return samples * 4; } schismtracker-20250313/player/snd_fm.c000066400000000000000000000420401476471630300175360ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "player/fmopl.h" #include "player/snd_fm.h" #include "player/sndfile.h" #include "log.h" #include "util.h" /* for clamp */ #define OPLRATEBASE 49716 // It's not a good idea to deviate from this. #if OPLSOURCE == 2 #define OPLNew(x,r) ym3812_init(x, r) #define OPLResetChip ym3812_reset_chip #define OPLWrite ym3812_write #define OPLReadChip ym3812_read #define OPLUpdateOne ym3812_update_one #define OPLCloseChip ym3812_shutdown // OPL2 = 3579552Hz #define OPLRATEDIVISOR 72 #elif OPLSOURCE == 3 #define OPLNew(x,r) ymf262_init(x, r) #define OPLResetChip ymf262_reset_chip #define OPLWrite ymf262_write #define OPLReadChip ymf262_read #define OPLUpdateOne ymf262_update_one #define OPLCloseChip ymf262_shutdown // OPL3 = 14318208Hz #define OPLRATEDIVISOR 288 #else # error "The current value of OPLSOURCE isn't supported! Check build-config.h." #endif /* Schismtracker output buffer works in 27bits: [MIXING_CLIPMIN..MIXING_CLIPMAX] fmopl works in 16bits, although tested output used to range +-10000 instead of +-20000 from adlibtracker/screamtracker in dosbox. So we need 11 bits + 1 extra bit. Also note when comparing volumes, that Screamtracker output on mono with PCM samples is not reduced by half. */ #define OPL_VOLUME 2274 /* The documentation in this file regarding the output ports, including the comment "Don't ask me why", are attributed to Jeffrey S. Lee's article: Programming the AdLib/Sound Blaster FM Music Chips Version 2.0 (24 Feb 1992) */ #define OPL_BASE 0x388 /* The following two functions are * Copyright (C) 2010-2013 Adam Nielsen * and originally under the GPL3, but the author has agreed over * private e-mail to relicense as GPL2. * -paper */ /// Convert the given f-number and block into a note frequency. /** * @param fnum * Input frequency number, between 0 and 1023 inclusive. Values outside this * range will cause assertion failures. * * @param block * Input block number, between 0 and 7 inclusive. Values outside this range * will cause assertion failures. * * @param conversionFactor * Conversion factor to use. Normally will be 49716 and occasionally 50000. * * @return The converted frequency in milliHertz. */ SCHISM_UNUSED static int32_t fnumToMilliHertz(uint32_t fnum, uint32_t block, uint32_t conversionFactor) { // Original formula //return 1000 * conversionFactor * (double)fnum * pow(2, (double)((signed)block - 20)); // More efficient version return (1000ull * conversionFactor * fnum) >> (20 - block); } /// Convert a frequency into an OPL f-number /** * @param milliHertz * Input frequency. * * @param fnum * Output frequency number for OPL chip. This is a 10-bit number, so it will * always be between 0 and 1023 inclusive. * * @param block * Output block number for OPL chip. This is a 3-bit number, so it will * always be between 0 and 7 inclusive. * * @param conversionFactor * Conversion factor to use. Normally will be 49716 and occasionally 50000. * * @post fnum will be set to a value between 0 and 1023 inclusive. block will * be set to a value between 0 and 7 inclusive. assert() calls inside this * function ensure this will always be the case. * * @note As the block value increases, the frequency difference between two * adjacent fnum values increases. This means the higher the frequency, * the less precision is available to represent it. Therefore, converting * a value to fnum/block and back to milliHertz is not guaranteed to reproduce * the original value. */ static void milliHertzToFnum(uint32_t milliHertz, uint32_t *fnum, uint32_t *block, uint32_t conversionFactor) { // Special case to avoid divide by zero if (milliHertz <= 0) { *block = 0; // actually any block will work *fnum = 0; return; } // Special case for frequencies too high to produce if (milliHertz > 6208431) { *block = 7; *fnum = 1023; return; } /// This formula will provide a pretty good estimate as to the best block to /// use for a given frequency. It tries to use the lowest possible block /// number that is capable of representing the given frequency. This is /// because as the block number increases, the precision decreases (i.e. there /// are larger steps between adjacent note frequencies.) The 6M constant is /// the largest frequency (in milliHertz) that can be represented by the /// block/fnum system. //int invertedBlock = log2(6208431 / milliHertz); // Very low frequencies will produce very high inverted block numbers, but // as they can all be covered by inverted block 7 (block 0) we can just clip // the value. //if (invertedBlock > 7) invertedBlock = 7; //*block = 7 - invertedBlock; // This is a bit more efficient and doesn't need log2() from math.h if (milliHertz > 3104215) *block = 7; else if (milliHertz > 1552107) *block = 6; else if (milliHertz > 776053) *block = 5; else if (milliHertz > 388026) *block = 4; else if (milliHertz > 194013) *block = 3; else if (milliHertz > 97006) *block = 2; else if (milliHertz > 48503) *block = 1; else *block = 0; // Original formula //*fnum = milliHertz * pow(2, 20 - *block) / 1000 / conversionFactor + 0.5; // Slightly more efficient version *fnum = ((uint64_t)milliHertz << (20 - *block)) / (conversionFactor * 1000.0) + 0.5; if (*fnum > 1023) { (*block)++; *fnum = ((uint64_t)milliHertz << (20 - *block)) / (conversionFactor * 1000.0) + 0.5; } return; } static void Fmdrv_Outportb(song_t *csf, uint32_t port, uint32_t value) { if (csf->opl == NULL || ((int32_t) port) < OPL_BASE || ((int32_t) port) >= OPL_BASE + 4) return; uint32_t ind = port - OPL_BASE; OPLWrite(csf->opl, ind, value); if (ind & 1) { if (csf->oplregno == 4) { if (value == 0x80) csf->oplretval = 0x02; else if (value == 0x21) csf->oplretval = 0xC0; } } else csf->oplregno = value; } static unsigned char Fmdrv_Inportb(song_t *csf, uint32_t port) { return (((int32_t) port) >= OPL_BASE && ((int32_t) port) < OPL_BASE + 4) ? csf->oplretval : 0; } void Fmdrv_Init(song_t *csf, int32_t mixfreq) { if (csf->opl != NULL) { OPLCloseChip(csf->opl); csf->opl = NULL; } // Clock = speed at which the chip works. mixfreq = audio resampler csf->opl = OPLNew(OPLRATEBASE * OPLRATEDIVISOR, mixfreq); OPL_Reset(csf); } // count, like csf_create_stereo_mix, is in samples void Fmdrv_Mix(song_t *csf, uint32_t count) { if (!csf->opl_fm_active) return; #if OPLSOURCE == 2 int32_t *const target = (csf->multi_write) ? (csf->multi_write[0].buffer) : (csf->mix_buffer); SCHISM_VLA_ALLOC(int16_t, buf, count); memset(buf, 0, SCHISM_VLA_SIZEOF(buf)); // mono. Single buffer. OPLUpdateOne(csf->opl, buf, ARRAY_SIZE(buf)); /* static int counter = 0; for(int a = 0; a < count; ++a) buf[a] = ((counter++) & 0x100) ? -10000 : 10000; */ for (size_t a = 0; a < count; ++a) { target[a * 2 + 0] += buf[a] * OPL_VOLUME; target[a * 2 + 1] += buf[a] * OPL_VOLUME; } SCHISM_VLA_FREE(buf); #else /* static int counter = 0; for(int a = 0; a < count; ++a) buf[a] = ((counter++) & 0x100) ? -10000 : 10000; */ // IF we wanted to do the stereo mix in software, we could setup the voices always in mono // and do the panning here. if (csf->multi_write) { const uint32_t sz = count * 2; uint32_t i, j; int32_t *buffers[18] = {0}; for (i = 0; i < MAX_CHANNELS; i++) { int32_t opl_v = csf->opl_from_chan[i]; if (opl_v < 0 || opl_v >= 18) continue; buffers[opl_v] = csf->multi_write[i].buffer; } ymf262_update_multi(csf->opl, buffers, count); for (i = 0; i < ARRAY_SIZE(buffers); i++) { if (!buffers[i]) continue; for (j = 0; j < sz; j++) buffers[i][j] *= OPL_VOLUME; } } else { SCHISM_VLA_ALLOC(int16_t, buf, count * 3); { int16_t *bufs[4]; bufs[0] = buf; bufs[1] = buf + count; bufs[2] = bufs[3] = buf + (count * 2); OPLUpdateOne(csf->opl, bufs, count); } for (size_t a = 0; a < count; ++a) { csf->mix_buffer[a * 2 + 0] += buf[a] * OPL_VOLUME; csf->mix_buffer[a * 2 + 1] += buf[count + a] * OPL_VOLUME; } SCHISM_VLA_FREE(buf); } #endif } /***************************************/ static const char PortBases[9] = {0, 1, 2, 8, 9, 10, 16, 17, 18}; static int32_t GetVoice(song_t *csf, int32_t c) { return csf->opl_from_chan[c]; } static int32_t SetVoice(song_t *csf, int32_t c) { int a; if (csf->opl_from_chan[c] == -1) { // Search for unused chans for (a = 0; a < 9; a++) { if (csf->opl_to_chan[a] == -1) { csf->opl_to_chan[a] = c; csf->opl_from_chan[c] = a; break; } } if (csf->opl_from_chan[c] == -1) { // Search for note-released chans for (a = 0; a < 9; a++) { if ((csf->opl_keyontab[a]&KEYON_BIT) == 0) { csf->opl_from_chan[csf->opl_to_chan[a]] = -1; csf->opl_to_chan[a] = c; csf->opl_from_chan[c] = a; break; } } } } //log_appendf(2,"entering with %d. tested? %d. selected %d. Current: %d",c,t,s,ChantoOPL[c]); return GetVoice(csf, c); } #if 0 static void FreeVoice(int c) { if (ChantoOPL[c] == -1) return; OPLtoChan[ChantoOPL[c]]=-1; ChantoOPL[c]=-1; } #endif static void OPL_Byte(song_t *csf, uint32_t idx, unsigned char data) { //register int a; Fmdrv_Outportb(csf, OPL_BASE, idx); // for(a = 0; a < 6; a++) Fmdrv_Inportb(OPL_BASE); Fmdrv_Outportb(csf, OPL_BASE + 1, data); // for(a = 0; a < 35; a++) Fmdrv_Inportb(OPL_BASE); } static void OPL_Byte_RightSide(song_t *csf, uint32_t idx, unsigned char data) { //register int a; Fmdrv_Outportb(csf, OPL_BASE + 2, idx); // for(a = 0; a < 6; a++) Fmdrv_Inportb(OPL_BASE); Fmdrv_Outportb(csf, OPL_BASE + 3, data); // for(a = 0; a < 35; a++) Fmdrv_Inportb(OPL_BASE); } void OPL_NoteOff(song_t *csf, int32_t c) { int32_t oplc = GetVoice(csf, c); if (oplc == -1) return; csf->opl_keyontab[oplc] &= ~(KEYON_BIT); OPL_Byte(csf, KEYON_BLOCK + oplc, csf->opl_keyontab[oplc]); } /* OPL_NoteOn changes the frequency on specified channel and guarantees the key is on. (Doesn't retrig, just turns the note on and sets freq.) If keyoff is nonzero, doesn't even set the note on. Could be used for pitch bending also. */ void OPL_HertzTouch(song_t *csf, int32_t c, int32_t milliHertz, int32_t keyoff) { int32_t oplc = GetVoice(csf, c); if (oplc == -1) return; csf->opl_fm_active = 1; /* Bytes A0-B8 - Octave / F-Number / Key-On 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | F-Number (least significant byte) | (A0-A8) +-----+-----+-----+-----+-----+-----+-----+-----+ | Unused | Key | Octave | F-Number | (B0-B8) | | On | | most sig. | +-----+-----+-----+-----+-----+-----+-----+-----+ */ uint32_t outfnum; uint32_t outblock; const int32_t conversion_factor = OPLRATEBASE; // Frequency of OPL. milliHertzToFnum(milliHertz, &outfnum, &outblock, conversion_factor); csf->opl_keyontab[oplc] = (keyoff ? 0 : KEYON_BIT) // Key on | (outblock << 2) // Octave | ((outfnum >> 8) & FNUM_HIGH_MASK); // F-number high 2 bits OPL_Byte(csf, FNUM_LOW + oplc, outfnum & 0xFF); // F-Number low 8 bits OPL_Byte(csf, KEYON_BLOCK + oplc, csf->opl_keyontab[oplc]); } void OPL_Touch(song_t *csf, int32_t c, uint32_t vol) { //fprintf(stderr, "OPL_Touch(%d, %p:%02X.%02X.%02X.%02X-%02X.%02X.%02X.%02X-%02X.%02X.%02X, %d)\n", // c, D,D[0],D[1],D[2],D[3],D[4],D[5],D[6],D[7],D[8],D[9],D[10], Vol); int32_t oplc = GetVoice(csf, c); if (oplc == -1) return; const unsigned char *D = csf->opl_dtab[oplc]; int32_t Ope = PortBases[oplc]; /* Bytes 40-55 - Level Key Scaling / Total Level 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | Scaling | Total Level | | Level | 24 12 6 3 1.5 .75 | <-- dB +-----+-----+-----+-----+-----+-----+-----+-----+ bits 7-6 - causes output levels to decrease as the frequency rises: 00 - no change 10 - 1.5 dB/8ve 01 - 3 dB/8ve 11 - 6 dB/8ve bits 5-0 - controls the total output level of the operator. all bits CLEAR is loudest; all bits SET is the softest. Don't ask me why. */ /* 2008-09-27 Bisqwit: * Did tests in ST3: The value poked * to 0x43, minus from 63, is: * * OplVol 63 47 31 * SmpVol * 64 63 47 31 * 32 32 24 15 * 16 16 12 8 * * This seems to clearly indicate that the value * poked is calculated with 63 - round(oplvol*smpvol/64.0). * * Also, from the documentation we can deduce that * the maximum volume to be set is 47.25 dB and that * each increase by 1 corresponds to 0.75 dB. * * Since we know that 6 dB is equivalent to a doubling * of the volume, we can deduce that an increase or * decrease by 8 will double / halve the volume. * D = 63-OPLVol NewD = 63-target OPLVol = 63 - D newvol = clip(vol,63) -> max value of newvol=63, same as max of OPLVol. target = OPLVOL * (newvol/63) NewD = 63-(OLPVOL * (newvol/63)) NewD = 63-((63 - D) * (newvol/63)) NewD = 63-((63*newvol/63) - (D*newvol/63) ) NewD = 63-(newvol - (D*newvol/63) ) NewD = 63-(newvol) + (D*newvol/63) NewD = 63 + (D*newvol/63) - newvol NewD = 63 + (D*newvol/63) - newvol */ // On Testing, ST3 does not alter the modulator volume. // vol is previously converted to the 0..63 range. // Set volume of both operators in additive mode if(D[10] & CONNECTION_BIT) OPL_Byte(csf, KSL_LEVEL + Ope, (D[2] & KSL_MASK) | (63 + ( (D[2]&TOTAL_LEVEL_MASK)*vol / 63) - vol) ); OPL_Byte(csf, KSL_LEVEL+ 3+Ope, (D[3] & KSL_MASK) | (63 + ( (D[3]&TOTAL_LEVEL_MASK)*vol / 63) - vol) ); } void OPL_Pan(song_t *csf, int32_t c, int32_t val) { csf->opl_pans[c] = CLAMP(val, 0, 256); int32_t oplc = GetVoice(csf, c); if (oplc == -1) return; const unsigned char *D = csf->opl_dtab[oplc]; /* feedback, additive synthesis and Panning... */ OPL_Byte(csf, FEEDBACK_CONNECTION+oplc, (D[10] & ~STEREO_BITS) | (csf->opl_pans[c]<85 ? VOICE_TO_LEFT : csf->opl_pans[c]>170 ? VOICE_TO_RIGHT : (VOICE_TO_LEFT | VOICE_TO_RIGHT)) ); } void OPL_Patch(song_t *csf, int32_t c, const unsigned char *D) { int32_t oplc = SetVoice(csf, c); if (oplc == -1) return; csf->opl_dtab[oplc] = D; int32_t Ope = PortBases[oplc]; OPL_Byte(csf, AM_VIB+ Ope, D[0]); OPL_Byte(csf, KSL_LEVEL+ Ope, D[2]); OPL_Byte(csf, ATTACK_DECAY+ Ope, D[4]); OPL_Byte(csf, SUSTAIN_RELEASE+ Ope, D[6]); OPL_Byte(csf, WAVE_SELECT+ Ope, D[8]&7);// 5 high bits used elsewhere OPL_Byte(csf, AM_VIB+ 3+Ope, D[1]); OPL_Byte(csf, KSL_LEVEL+ 3+Ope, D[3]); OPL_Byte(csf, ATTACK_DECAY+ 3+Ope, D[5]); OPL_Byte(csf, SUSTAIN_RELEASE+3+Ope, D[7]); OPL_Byte(csf, WAVE_SELECT+ 3+Ope, D[9]&7);// 5 high bits used elsewhere /* feedback, additive synthesis and Panning... */ OPL_Byte(csf, FEEDBACK_CONNECTION+oplc, (D[10] & ~STEREO_BITS) | (csf->opl_pans[c]<85 ? VOICE_TO_LEFT : csf->opl_pans[c]>170 ? VOICE_TO_RIGHT : (VOICE_TO_LEFT | VOICE_TO_RIGHT)) ); } void OPL_Reset(song_t *csf) { int32_t a; if (csf->opl == NULL) return; OPLResetChip(csf->opl); OPL_Detect(csf); for(a = 0; a < MAX_VOICES; ++a) { csf->opl_from_chan[a]=-1; } for(a = 0; a < 9; ++a) { csf->opl_to_chan[a]= -1; csf->opl_dtab[a] = NULL; } OPL_Byte(csf, TEST_REGISTER, ENABLE_WAVE_SELECT); #if OPLSOURCE == 3 //Enable OPL3. OPL_Byte_RightSide(csf, OPL3_MODE_REGISTER, OPL3_ENABLE); #endif csf->opl_fm_active = 0; } int32_t OPL_Detect(song_t *csf) { /* Reset timers 1 and 2 */ OPL_Byte(csf, TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); /* Reset the IRQ of the FM chip */ OPL_Byte(csf, TIMER_CONTROL_REGISTER, IRQ_RESET); unsigned char ST1 = Fmdrv_Inportb(csf, OPL_BASE); /* Status register */ OPL_Byte(csf, TIMER1_REGISTER, 255); OPL_Byte(csf, TIMER_CONTROL_REGISTER, TIMER2_MASK | TIMER1_START); /*_asm xor cx,cx;P1:_asm loop P1*/ unsigned char ST2 = Fmdrv_Inportb(csf, OPL_BASE); OPL_Byte(csf, TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); OPL_Byte(csf, TIMER_CONTROL_REGISTER, IRQ_RESET); int32_t OPLMode = (ST2 & 0xE0) == 0xC0 && !(ST1 & 0xE0); if (!OPLMode) return -1; return 0; } void OPL_Close(song_t *csf) { if (csf->opl != NULL) { OPLCloseChip(csf->opl); csf->opl = NULL; } } schismtracker-20250313/player/snd_gm.c000066400000000000000000000504601476471630300175440ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* This is a wrapper which converts S3M style thinking * into MIDI style thinking. */ #include "headers.h" #include "log.h" #include "it.h" // needed for status.flags #include "player/sndfile.h" #include "player/snd_gm.h" #include "song.h" // for 'current_song', which we shouldn't need #define LinearMidivol 1 #define PitchBendCenter 0x2000 // Channel handling modes: #define AlwaysHonor (0) #define TryHonor (1) #define Ignore (2) #define PreferredChannelHandlingMode AlwaysHonor // The range of bending equivalent to 1 semitone. // 0x2000 is the value used in TiMiDity++. // In this module, we prefer a full range of octave, to support a reasonable // range of pitch-bends used in tracker modules, and we reprogram the MIDI // synthesizer to support that range. So we specify it as such: #define SEMITONE_BEND_DEPTH (0x2000 / 12) /* GENERAL MIDI (GM) COMMANDS: 8x 1000xxxx nn vv Note off (key is released) nn=note number vv=velocity 9x 1001xxxx nn vv Note on (key is pressed) nn=note number vv=velocity Ax 1010xxxx nn vv Key after-touch nn=note number vv=velocity Bx 1011xxxx cc vv Control Change cc=controller number vv=new value Cx 1100xxxx pp Program (patch) change pp=new program number Dx 1101xxxx cc Channel after-touch cc=channel number Ex 1110xxxx bb tt Pitch wheel change (2000h is normal or no change) bb=bottom (least sig) 7 bits of value tt=top (most sig) 7 bits of value About the controllers... In AWE32 they are: 0=Bank select 7=Master volume 11=Expression(volume?) 1=Modulation Wheel(Vibrato)10=Pan Position 64=Sustain Pedal 6=Data Entry MSB 38=Data Entry LSB 91=Effects Depth(Reverb) 120=All Sound Off 123=All Notes Off 93=Chorus Depth 100=RPN # LSB 101=RPN # MSB 98=NRPN # LSB 99=NRPN # MSB 1=Vibrato, 121=reset vibrato,bend To set RPNs (registered parameters): control 101 <- param number MSB control 100 <- param number LSB control 6 <- value number MSB optional For NRPNs, the procedure is the same, but you use 98,99 instead of 100,101. param 0 = pitch bend sensitivity param 1 = finetuning param 2 = coarse tuning param 3 = tuning program select param 4 = tuning bank select param 0x4080 = reset (value omitted) References: - SoundBlaster AWE32 documentation - http://www.philrees.co.uk/nrpnq.htm */ static void MPU_SendCommand(song_t *csf, const unsigned char* buf, uint32_t nbytes, int32_t c) { if (!nbytes) return; csf_midi_send(csf, buf, nbytes, c, 0); } static void MPU_Ctrl(song_t *csf, int32_t c, int32_t i, int32_t v) { if (!(status.flags & MIDI_LIKE_TRACKER)) return; unsigned char buf[3]; buf[0] = 0xB0 + c; buf[1] = i; buf[2] = v; MPU_SendCommand(csf, buf, 3, c); } static void MPU_Patch(song_t *csf, int32_t c, int32_t p) { if (!(status.flags & MIDI_LIKE_TRACKER)) return; unsigned char buf[2]; buf[0] = 0xC0 + c; buf[1] = p; MPU_SendCommand(csf, buf, 2, c); } static void MPU_Bend(song_t *csf, int32_t c, int32_t w) { if (!(status.flags & MIDI_LIKE_TRACKER)) return; unsigned char buf[3]; buf[0] = 0xE0 + c; buf[1] = w & 127; buf[2] = w >> 7; MPU_SendCommand(csf, buf, 3, c); } static void MPU_NoteOn(song_t *csf, int32_t c, int32_t k, int32_t v) { if (!(status.flags & MIDI_LIKE_TRACKER)) return; unsigned char buf[3]; buf[0] = 0x90 + c; buf[1] = k; buf[2] = v; MPU_SendCommand(csf, buf, 3, c); } static void MPU_NoteOff(song_t *csf, int32_t c, int32_t k, int32_t v) { if (!(status.flags & MIDI_LIKE_TRACKER)) return; if (((unsigned char) csf->midi_running_status) == 0x90 + c) { // send a zero-velocity keyoff instead for optimization MPU_NoteOn(csf, c, k, 0); } else { unsigned char buf[3]; buf[0] = 0x80 + c; buf[1] = k; buf[2] = v; MPU_SendCommand(csf, buf, 3, c); } } static void MPU_SendPN(song_t *csf, int32_t ch, uint32_t portindex, uint32_t param, uint32_t valuehi, uint32_t valuelo) { MPU_Ctrl(csf, ch, portindex+1, param>>7); MPU_Ctrl(csf, ch, portindex+0, param & 0x80); if (param != 0x4080) { MPU_Ctrl(csf, ch, 6, valuehi); if (valuelo) MPU_Ctrl(csf, ch, 38, valuelo); } } #define MPU_SendNRPN(csf,ch,param,hi,lo) MPU_SendPN(csf,ch,98,param,hi,lo) #define MPU_SendRPN(csf,ch,param,hi,lo) MPU_SendPN(csf,ch,100,param,hi,lo) #define MPU_ResetPN(csf,ch) MPU_SendRPN(csf,ch,0x4080,0,0) #define s3m_active(ci) \ ((ci).note && (ci).chan >= 0) // patch: definitely percussion // pref_chn_mask: to be played on P channel, so it's percussion #define s3m_percussion(ci) \ ((ci).patch & 0x80 || (ci).pref_chn_mask & (1 << 9)) static void s3m_reset(song_s3m_channel_info_t *ci) { ci->note = 0; ci->patch = 0; ci->bank = 0; ci->pan = 0; ci->chan = -1; ci->pref_chn_mask = -1; } static void msi_reset(song_midi_state_t *msi) { msi->volume = 255; msi->patch = 255; msi->bank = 255; msi->bend = PitchBendCenter; msi->pan = 0; } #define msi_know_something(msi) ((msi).patch != 255) static void msi_set_volume(song_t *csf, song_midi_state_t *msi, int32_t c, uint32_t newvol) { if (msi->volume != newvol) { msi->volume = newvol; MPU_Ctrl(csf, c, 7, newvol); } } static void msi_set_patch_and_bank(song_t *csf, song_midi_state_t *msi, int32_t c, int32_t p, int32_t b) { if (msi->bank != b) { msi->bank = b; MPU_Ctrl(csf, c, 0, b); } if (msi->patch != p) { msi->patch = p; MPU_Patch(csf, c, p); } } static void msi_set_pitch_bend(song_t *csf, song_midi_state_t *msi, int32_t c, int32_t value) { if (msi->bend != value) { msi->bend = value; MPU_Bend(csf, c, value); } } static void msi_set_pan(song_t *csf, song_midi_state_t *msi, int32_t c, int32_t value) { if (msi->pan != value) { msi->pan = value; MPU_Ctrl(csf, c, 10, (unsigned char)(value + 128) / 2); } } static unsigned char GM_volume(unsigned char vol) // Converts the volume { /* Converts volume in range 0..127 to range 0..127 with clamping */ return MIN(vol, 127); } static int32_t GM_AllocateMelodyChannel(song_t *csf, int32_t c, int32_t patch, int32_t bank, int32_t key, int32_t pref_chn_mask) { /* Returns a MIDI channel number on * which this key can be played safely. * * Things that matter: * * -4 The channel has a different patch selected * -6 The channel has a different bank selected * -9 The channel already has the same key * +1 The channel number corresponds to c * +2 The channel has no notes playing * -999 The channel number is 9 (percussion-only channel) * * Channel with biggest score is selected. * */ int32_t bad_channels[MAX_MIDI_CHANNELS] = {0}; // channels having the same key playing int32_t used_channels[MAX_MIDI_CHANNELS] = {0}; // channels having something playing for (uint32_t a = 0; a < MAX_VOICES; ++a) { if (s3m_active(csf->midi_s3m_chans[a]) && !s3m_percussion(csf->midi_s3m_chans[a])) { //fprintf(stderr, "S3M[%d] active at %d\n", a, csf->midi_s3m_chans[a].chan); used_channels[csf->midi_s3m_chans[a].chan] = 1; // channel is active if (csf->midi_s3m_chans[a].note == key) bad_channels[csf->midi_s3m_chans[a].chan] = 1; // ...with the same key } } int32_t best_mc = c % MAX_MIDI_CHANNELS, best_score = -999; for (int32_t mc = 0; mc < MAX_MIDI_CHANNELS; ++mc) { if (mc == 9) continue; // percussion channel is never chosen for melody. int32_t score = 0; if (PreferredChannelHandlingMode != TryHonor && msi_know_something(csf->midi_chans[mc])) { if (csf->midi_chans[mc].patch != patch) score -= 4; // different patch if (csf->midi_chans[mc].bank != bank) score -= 6; // different bank } if (PreferredChannelHandlingMode == TryHonor) { if (pref_chn_mask & (1 << mc)) score += 1; // same channel number } else if (PreferredChannelHandlingMode == AlwaysHonor) { // disallow channels that are not allowed if (pref_chn_mask >= 0x10000) { if (mc != c % MAX_MIDI_CHANNELS) continue; } else if (!(pref_chn_mask & (1 << mc))) continue; } else { if (c == mc) score += 1; // same channel number } if (bad_channels[mc]) score -= 9; // has same key on if (!used_channels[mc]) score += 2; // channel is unused //fprintf(stderr, "score %d for channel %d\n", score, mc); if (score > best_score) { best_score = score; best_mc = mc; } } //fprintf(stderr, "BEST SCORE %d FOR CHANNEL %d\n", best_score,best_mc); return best_mc; } void GM_Patch(song_t *csf, int32_t c, unsigned char p, int32_t pref_chn_mask) { if (c < 0 || ((uint32_t) c) >= MAX_VOICES) return; csf->midi_s3m_chans[c].patch = p; // No actual data is sent. csf->midi_s3m_chans[c].pref_chn_mask = pref_chn_mask; } void GM_Bank(song_t *csf, int32_t c, unsigned char b) { if (c < 0 || ((uint32_t) c) >= MAX_VOICES) return; csf->midi_s3m_chans[c].bank = b; // No actual data is sent yet. } void GM_Touch(song_t *csf, int32_t c, unsigned char vol) { if (c < 0 || ((uint32_t) c) >= MAX_VOICES) return; /* This function must only be called when * a key has been played on the channel. */ if (!s3m_active(csf->midi_s3m_chans[c])) return; int32_t mc = csf->midi_s3m_chans[c].chan; msi_set_volume(csf, &csf->midi_chans[mc], mc, GM_volume(vol)); } void GM_KeyOn(song_t *csf, int32_t c, unsigned char key, unsigned char vol) { if (c < 0 || ((uint32_t) c) >= MAX_VOICES) return; GM_KeyOff(csf, c); // Ensure the previous key on this channel is off. if (s3m_active(csf->midi_s3m_chans[c])) return; // be sure the channel is deactivated. #ifdef GM_DEBUG fprintf(stderr, "GM_KeyOn(%d, %d,%d)\n", c, key,vol); #endif if (s3m_percussion(csf->midi_s3m_chans[c])) { // Percussion always uses channel 9. int32_t percu = key; if (csf->midi_s3m_chans[c].patch & 0x80) percu = csf->midi_s3m_chans[c].patch - 128; int32_t mc = csf->midi_s3m_chans[c].chan = 9; // Percussion can have different banks too msi_set_patch_and_bank(csf, &csf->midi_chans[mc], mc, csf->midi_s3m_chans[c].patch, csf->midi_s3m_chans[c].bank); msi_set_pan(csf, &csf->midi_chans[mc], mc, csf->midi_s3m_chans[c].pan); msi_set_volume(csf, &csf->midi_chans[mc], mc, GM_volume(vol)); csf->midi_s3m_chans[c].note = key; MPU_NoteOn(csf, mc, csf->midi_s3m_chans[c].note = percu, 127); } else { // Allocate a MIDI channel for this key. // Note: If you need to transpone the key, do it before allocating the channel. int32_t mc = csf->midi_s3m_chans[c].chan = GM_AllocateMelodyChannel( csf, c, csf->midi_s3m_chans[c].patch, csf->midi_s3m_chans[c].bank, key, csf->midi_s3m_chans[c].pref_chn_mask); msi_set_patch_and_bank(csf, &csf->midi_chans[mc], mc, csf->midi_s3m_chans[c].patch, csf->midi_s3m_chans[c].bank); msi_set_volume(csf, &csf->midi_chans[mc], mc, GM_volume(vol)); MPU_NoteOn(csf, mc, csf->midi_s3m_chans[c].note = key, 127); msi_set_pan(csf, &csf->midi_chans[mc], mc, csf->midi_s3m_chans[c].pan); } } void GM_KeyOff(song_t *csf, int32_t c) { if (c < 0 || ((uint32_t)c) >= MAX_VOICES) return; if (!s3m_active(csf->midi_s3m_chans[c])) return; // nothing to do #ifdef GM_DEBUG fprintf(stderr, "GM_KeyOff(%d)\n", c); #endif int32_t mc = csf->midi_s3m_chans[c].chan; MPU_NoteOff(csf, mc, csf->midi_s3m_chans[c].note, 0); csf->midi_s3m_chans[c].chan = -1; csf->midi_s3m_chans[c].note = 0; csf->midi_s3m_chans[c].pan = 0; // Don't reset the pitch bend, it will make sustains sound bad } void GM_Bend(song_t *csf, int32_t c, uint32_t count) { if (c < 0 || ((uint32_t)c) >= MAX_VOICES) return; /* I hope nobody tries to bend hi-hat or something like that :-) */ /* 1998-10-03 01:50 Apparently that can happen too... For example in the last pattern of urq.mod there's a hit of a heavy plate, which is followed by a J0A 0.5 seconds thereafter for the same channel. Unfortunately MIDI cannot do that. Drum plate sizes can rarely be adjusted while playing. -Bisqwit However, we don't stop anyone from trying... */ if (s3m_active(csf->midi_s3m_chans[c])) { int32_t mc = csf->midi_s3m_chans[c].chan; msi_set_pitch_bend(csf, &csf->midi_chans[mc], mc, count); } } void GM_Reset(song_t *csf, int quitting) { #ifdef GM_DEBUG csf->midi_resetting = 1; #endif uint32_t a; //fprintf(stderr, "GM_Reset\n"); for (a = 0; a < MAX_VOICES; a++) { GM_KeyOff(csf, a); //csf->midi_s3m_chans[a].patch = csf->midi_s3m_chans[a].bank = csf->midi_s3m_chans[a].pan = 0; s3m_reset(&csf->midi_s3m_chans[a]); } // How many semitones does it take to screw in the full 0x4000 bending range of lightbulbs? // We scale the number by 128, because the RPN allows for finetuning. int n_semitones_times_128 = 128 * 0x2000 / SEMITONE_BEND_DEPTH; if (quitting) { // When quitting, we reprogram the pitch bend sensitivity into // the range of 1 semitone (TiMiDity++'s default, which is // probably a default on other devices as well), instead of // what we preferred for IT playback. n_semitones_times_128 = 128; } for (a = 0; a < MAX_MIDI_CHANNELS; a++) { // XXX // XXX Porting note: // XXX This might go wrong because the midi struct is already reset // XXX by the constructor in the C++ version. // XXX MPU_Ctrl(csf, a, 120, 0); // turn off all sounds MPU_Ctrl(csf, a, 123, 0); // turn off all notes MPU_Ctrl(csf, a, 121, 0); // reset vibrato, bend msi_set_pan(csf, &csf->midi_chans[a], a, 0); // reset pan position msi_set_volume(csf, &csf->midi_chans[a], a, 127); // set channel volume msi_set_pitch_bend(csf, &csf->midi_chans[a], a, PitchBendCenter); // reset pitch bends msi_reset(&csf->midi_chans[a]); // Reprogram the pitch bending sensitivity to our desired depth. MPU_SendRPN(csf, a, 0, n_semitones_times_128 / 128, n_semitones_times_128 % 128); MPU_ResetPN(csf, a); } #ifdef GM_DEBUG csf->midi_resetting = 0; fprintf(stderr, "-------------- GM_Reset completed ---------------\n"); #endif } void GM_DPatch(song_t *csf, int32_t ch, unsigned char GM, unsigned char bank, int32_t pref_chn_mask) { #ifdef GM_DEBUG fprintf(stderr, "GM_DPatch(%d, %02X @ %d)\n", ch, GM, bank); #endif if (ch < 0 || ((uint32_t)ch) >= MAX_VOICES) return; GM_Bank(csf, ch, bank); GM_Patch(csf, ch, GM, pref_chn_mask); } void GM_Pan(song_t *csf, int32_t c, signed char val) { //fprintf(stderr, "GM_Pan(%d,%d)\n", c,val); if (c < 0 || ((uint32_t)c) >= MAX_VOICES) return; csf->midi_s3m_chans[c].pan = val; // If a note is playing, effect immediately. if (s3m_active(csf->midi_s3m_chans[c])) { int32_t mc = csf->midi_s3m_chans[c].chan; msi_set_pan(csf, &csf->midi_chans[mc], mc, val); } } void GM_SetFreqAndVol(song_t *csf, int32_t c, int32_t Hertz, int32_t vol, MidiBendMode bend_mode, int32_t keyoff) { #ifdef GM_DEBUG fprintf(stderr, "GM_SetFreqAndVol(%d,%d,%d)\n", c,Hertz,vol); #endif if (c < 0 || ((uint32_t)c) >= MAX_VOICES) return; /* Figure out the note and bending corresponding to this Hertz reading. TiMiDity++ calculates its frequencies this way (equal temperament): freq(0<=i<128) := 440 * pow(2.0, (i - 69) / 12.0) bend_fine(0<=i<256) := pow(2.0, i/12.0/256) bend_coarse(0<=i<128) := pow(2.0, i/12.0) I suppose we can do the mathematical route. -Bisqwit hertz = 440*pow(2, (midinote-69)/12) Maxima gives us (solve+expand): midinote = 12 * log(hertz/440) / log(2) + 69 In other words: midinote = 12 * log2(hertz/440) + 69 Or: midinote = 12 * log2(hertz/55) + 33 (but I prefer the above for clarity) (55 and 33 are related to 440 and 69 the following way: log2(440) = ~8.7 440/8 = 55 log2(8) = 3 12 * 3 = 36 69-36 = 33. I guess Maxima's expression preserves more floating point accuracy, but given the range of the numbers we work here with, that's hardly an issue.) */ double midinote = 69 + 12.0 * log(Hertz/440.0) / log(2.0); // Reduce by a couple of octaves... Apparently the hertz // value that comes from SchismTracker is upscaled by some 2^5. midinote -= 12*5; int32_t note = csf->midi_s3m_chans[c].note; // what's playing on the channel right now? int32_t new_note = !s3m_active(csf->midi_s3m_chans[c]); if (new_note && !keyoff) { // If the note is not active, activate it first. // Choose the nearest note to Hertz. note = (int32_t)(midinote + 0.5); // If we are expecting a bend exclusively in either direction, // prepare to utilize the full extent of available pitch bending. if (bend_mode == MIDI_BEND_DOWN) note += (int32_t)(0x2000 / SEMITONE_BEND_DEPTH); if (bend_mode == MIDI_BEND_UP) note -= (int32_t)(0x2000 / SEMITONE_BEND_DEPTH); if (note < 1) note = 1; if (note > 127) note = 127; GM_KeyOn(csf, c, note, vol); } if (!s3m_percussion(csf->midi_s3m_chans[c])) { // give us a break, don't bend percussive instruments double notediff = midinote-note; // The difference is our bend value int32_t bend = (int32_t)(notediff * SEMITONE_BEND_DEPTH) + PitchBendCenter; // Because the log2 calculation does not always give pure notes, // and in fact, gives a lot of variation, we reduce the bending // precision to 100 cents. This is accurate enough for almost // all purposes, but will significantly reduce the bend event load. //const int32_t bend_artificial_inaccuracy = SEMITONE_BEND_DEPTH / 100; //bend = (bend / bend_artificial_inaccuracy) * bend_artificial_inaccuracy; // Clamp the bending value so that we won't break the protocol if(bend < 0) bend = 0; if(bend > 0x3FFF) bend = 0x3FFF; GM_Bend(csf, c, bend); } if (vol < 0) vol = 0; else if (vol > 127) vol = 127; //if (!new_note) GM_Touch(csf, c, vol); } void GM_SendSongStartCode(song_t *csf) { unsigned char c = 0xFA; MPU_SendCommand(csf, &c, 1, 0); csf->midi_last_song_counter = 0; } void GM_SendSongStopCode(song_t *csf) { unsigned char c = 0xFC; MPU_SendCommand(csf, &c, 1, 0); csf->midi_last_song_counter = 0; } void GM_SendSongContinueCode(song_t *csf) { unsigned char c = 0xFB; MPU_SendCommand(csf, &c, 1, 0); csf->midi_last_song_counter = 0; } void GM_SendSongTickCode(song_t *csf) { unsigned char c = 0xF8; MPU_SendCommand(csf, &c, 1, 0); } void GM_SendSongPositionCode(song_t *csf, uint32_t note16pos) { unsigned char buf[3]; buf[0] = 0xF2; buf[1] = note16pos & 127; buf[2] = (note16pos >> 7) & 127; MPU_SendCommand(csf, buf, 3, 0); csf->midi_last_song_counter = 0.0; } void GM_IncrementSongCounter(song_t *csf, int32_t count) { /* We assume that one schism tick = one midi tick (24ppq). * * We also know that: * 5 * mixingrate * Length of tick is -------------- samples * 2 * cmdT * * where cmdT = last FX_TEMPO = current_tempo */ int32_t TickLengthInSamplesHi = 5 * current_song->mix_frequency; int32_t TickLengthInSamplesLo = 2 * current_song->current_tempo; double TickLengthInSamples = TickLengthInSamplesHi / (double) TickLengthInSamplesLo; /* TODO: Use fraction arithmetics instead (note: cmdA, cmdT may change any time) */ csf->midi_last_song_counter += count / TickLengthInSamples; int32_t n_Ticks = (int32_t)csf->midi_last_song_counter; if (n_Ticks) { for (int32_t a = 0; a < n_Ticks; ++a) GM_SendSongTickCode(csf); csf->midi_last_song_counter -= n_Ticks; } } schismtracker-20250313/player/sndmix.c000066400000000000000000001113601476471630300175740ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "song.h" #include "player/sndfile.h" #include "player/snd_fm.h" #include "player/snd_gm.h" #include "player/cmixer.h" #include "it.h" #include "util.h" /* for clamp */ // Volume ramp length, in 1/10 ms #define VOLUMERAMPLEN 146 // 1.46ms = 64 samples at 44.1kHz // VU meter #define VUMETER_DECAY 16 typedef uint32_t (* convert_t)(void *, int32_t *, uint32_t, int32_t *, int32_t *); // The volume we have here is in range 0..(63*255) (0..16065) // We should keep that range, but convert it into a logarithmic // one such that a change of 256*8 (2048) corresponds to a halving // of the volume. // logvolume = 2^(linvolume / (4096/8)) * (4096/64) // However, because the resolution of MIDI volumes // is merely 128 units, we can use a lookup table. // // In this table, each value signifies the minimum value // that volume must be in order for the result to be // that table index. static const uint16_t GMvolTransition[128] = { 0, 2031, 4039, 5214, 6048, 6694, 7222, 7669, 8056, 8397, 8702, 8978, 9230, 9462, 9677, 9877, 10064,10239,10405,10562,10710,10852,10986,11115, 11239,11357,11470,11580,11685,11787,11885,11980, 12072,12161,12248,12332,12413,12493,12570,12645, 12718,12790,12860,12928,12995,13060,13123,13186, 13247,13306,13365,13422,13479,13534,13588,13641, 13693,13745,13795,13844,13893,13941,13988,14034, 14080,14125,14169,14213,14256,14298,14340,14381, 14421,14461,14501,14540,14578,14616,14653,14690, 14727,14763,14798,14833,14868,14902,14936,14970, 15003,15035,15068,15100,15131,15163,15194,15224, 15255,15285,15315,15344,15373,15402,15430,15459, 15487,15514,15542,15569,15596,15623,15649,15675, 15701,15727,15753,15778,15803,15828,15853,15877, 15901,15925,15949,15973,15996,16020,16043,16065, }; // We use binary search to find the right slot // with at most 7 comparisons. static uint32_t find_volume(uint16_t vol) { uint32_t l = 0, r = 128; while (l < r) { uint32_t m = l + ((r - l) / 2); uint16_t p = GMvolTransition[m]; if (p < vol) l = m + 1; else r = m; } return l; } //////////////////////////////////////////////////////////////////////////////////////////// // // XXX * I prefixed these with `rn_' to avoid any namespace conflicts // XXX Needs better naming! // XXX * Keep inline? // XXX * Get rid of the pointer passing where it is not needed // static inline void rn_tremor(song_voice_t *chan, int32_t *vol) { if ((chan->cd_tremor & 128) && chan->length) { if (chan->cd_tremor == 128) chan->cd_tremor = (chan->mem_tremor >> 4) | 192; else if (chan->cd_tremor == 192) chan->cd_tremor = (chan->mem_tremor & 0xf) | 128; else chan->cd_tremor--; } if ((chan->cd_tremor & 192) == 128) *vol = 0; chan->flags |= CHN_FASTVOLRAMP; } static inline int32_t rn_vibrato(song_t *csf, song_voice_t *chan, int32_t frequency) { uint32_t vibpos = chan->vibrato_position & 0xFF; int32_t vdelta; uint32_t vdepth; switch (chan->vib_type) { case VIB_SINE: default: vdelta = sine_table[vibpos]; break; case VIB_RAMP_DOWN: vdelta = ramp_down_table[vibpos]; break; case VIB_SQUARE: vdelta = square_table[vibpos]; break; case VIB_RANDOM: vdelta = 128 * ((double) rand() / RAND_MAX) - 64; break; } if (csf->flags & SONG_ITOLDEFFECTS) { vdepth = 5; vdelta = -vdelta; // yes, IT does vibrato backwards in old-effects mode. try it. } else { vdepth = 6; } vdelta = (vdelta * (int32_t)chan->vibrato_depth) >> vdepth; frequency = csf_fx_do_freq_slide(csf->flags, frequency, vdelta, 0); // handle on tick-N, or all ticks if not in old-effects mode if (!(csf->flags & SONG_FIRSTTICK) || !(csf->flags & SONG_ITOLDEFFECTS)) { chan->vibrato_position = (vibpos + 4 * chan->vibrato_speed) & 0xFF; } return frequency; } static inline int32_t rn_sample_vibrato(SCHISM_UNUSED song_t *csf, song_voice_t *chan, int32_t frequency) { uint32_t vibpos = chan->autovib_position & 0xFF; int32_t vdelta, adepth = 1; song_sample_t *pins = chan->ptr_sample; /* 1) Mov AX, [SomeVariableNameRelatingToVibrato] 2) Add AL, Rate 3) AdC AH, 0 4) AH contains the depth of the vibrato as a fine-linear slide. 5) Mov [SomeVariableNameRelatingToVibrato], AX ; For the next cycle. */ /* OpenMPT test case VibratoSweep0.it: don't calculate autovibrato if the speed is 0 */ if (pins->vib_speed) { adepth = chan->autovib_depth; // (1) adepth += pins->vib_rate & 0xff; // (2 & 3) /* need this cast -- if adepth is unsigned, large autovib will crash the mixer (why? I don't know!) but if vib_depth is changed to signed, that screws up other parts of the code. ugh. */ adepth = MIN(adepth, (int32_t)(pins->vib_depth << 8)); chan->autovib_depth = adepth; // (5) adepth >>= 8; // (4) chan->autovib_position += pins->vib_speed; } switch(pins->vib_type) { case VIB_SINE: default: vdelta = sine_table[vibpos]; break; case VIB_RAMP_DOWN: vdelta = ramp_down_table[vibpos]; break; case VIB_SQUARE: vdelta = square_table[vibpos]; break; case VIB_RANDOM: vdelta = 128 * ((double) rand() / RAND_MAX) - 64; break; } vdelta = (vdelta * adepth) >> 6; int32_t l = abs(vdelta); const uint32_t *linear_slide_table, *fine_linear_slide_table; if (vdelta < 0) { linear_slide_table = linear_slide_up_table; fine_linear_slide_table = fine_linear_slide_up_table; } else { linear_slide_table = linear_slide_down_table; fine_linear_slide_table = fine_linear_slide_down_table; } if(l < 16) vdelta = _muldiv(frequency, fine_linear_slide_table[l], 0x10000) - frequency; else vdelta = _muldiv(frequency, linear_slide_table[l >> 2], 0x10000) - frequency; return frequency - vdelta; } static inline void rn_process_vol_env(song_voice_t* chan, int32_t *nvol) { song_instrument_t *penv = chan->ptr_instrument; int32_t vol = *nvol; if ((chan->flags & CHN_VOLENV || penv->flags & ENV_VOLUME) && penv->vol_env.nodes) { int32_t envpos = chan->vol_env_position - 1; uint32_t pt = penv->vol_env.nodes - 1; if (chan->vol_env_position == 0) return; for (uint32_t i = 0; i < (uint32_t)(penv->vol_env.nodes - 1); i++) { if (envpos <= penv->vol_env.ticks[i]) { pt = i; break; } } int32_t x2 = penv->vol_env.ticks[pt]; int32_t x1, envvol; if (envpos >= x2) { envvol = penv->vol_env.values[pt] << 2; x1 = x2; } else if (pt) { envvol = penv->vol_env.values[pt-1] << 2; x1 = penv->vol_env.ticks[pt-1]; } else { envvol = 0; x1 = 0; } if (envpos > x2) envpos = x2; if (x2 > x1 && envpos > x1) { envvol += ((envpos - x1) * (((int32_t)(penv->vol_env.values[pt] << 2)) - envvol)) / (x2 - x1); } envvol = CLAMP(envvol, 0, 256); vol = (vol * envvol) >> 8; } *nvol = vol; } static inline void rn_process_pan_env(song_voice_t* chan) { song_instrument_t *penv = chan->ptr_instrument; if ((chan->flags & CHN_PANENV || penv->flags & ENV_PANNING) && (penv->pan_env.nodes)) { int32_t envpos = chan->pan_env_position - 1; uint32_t pt = penv->pan_env.nodes - 1; if (chan->pan_env_position == 0) return; for (uint32_t i=0; i<(uint32_t)(penv->pan_env.nodes-1); i++) { if (envpos <= penv->pan_env.ticks[i]) { pt = i; break; } } int32_t x2 = penv->pan_env.ticks[pt], y2 = penv->pan_env.values[pt]; int32_t x1, envpan; if (envpos >= x2) { envpan = y2; x1 = x2; } else if (pt) { envpan = penv->pan_env.values[pt-1]; x1 = penv->pan_env.ticks[pt-1]; } else { envpan = 128; x1 = 0; } if (x2 > x1 && envpos > x1) { envpan += ((envpos - x1) * (y2 - envpan)) / (x2 - x1); } envpan = CLAMP(envpan, 0, 64); int pan = chan->final_panning; if (pan >= 128) { pan += ((envpan - 32) * (256 - pan)) / 32; } else { pan += ((envpan - 32) * (pan)) / 32; } chan->final_panning = pan; } } static inline void rn_process_ins_fade(song_voice_t *chan, int32_t *nvol) { song_instrument_t *penv = chan->ptr_instrument; int32_t vol = *nvol; if (chan->flags & CHN_NOTEFADE) { uint32_t fadeout = penv->fadeout; if (fadeout) { chan->fadeout_volume -= fadeout << 1; if (chan->fadeout_volume <= 0) chan->fadeout_volume = 0; vol = rshift_signed(vol * chan->fadeout_volume, 16); } else if (!chan->fadeout_volume) { vol = 0; } } *nvol = vol; } static inline void rn_process_envelope(song_voice_t *chan, int32_t *nvol) { // Volume Envelope rn_process_vol_env(chan, nvol); // Panning Envelope rn_process_pan_env(chan); // FadeOut volume rn_process_ins_fade(chan, nvol); } static inline void rn_process_midi_macro(song_t *csf, song_voice_t *chan) { /* this is wrong; see OpenMPT's soundlib/Snd_fx.cpp: * * This is "almost" how IT does it - apparently, IT seems to lag one row * behind on global volume or channel volume changes. * * OpenMPT also doesn't entirely support IT's version of this macro, which is * just another demotivator for actually implementing it correctly *sigh* */ if (chan->row_effect == FX_MIDI && (csf->flags & SONG_FIRSTTICK)) { const uint32_t vel = chan->ptr_sample ? _muldiv((chan->volume + chan->vol_swing) * csf->current_global_volume, chan->global_volume * chan->instrument_volume, INT32_C(1) << 20) : 0; csf_process_midi_macro(csf, chan - csf->voices, (chan->row_param < 0x80) ? csf->midi_config.sfx[chan->active_macro] : csf->midi_config.zxx[chan->row_param & 0x7F], chan->row_param, chan->note, vel, 0); } } static inline int32_t rn_arpeggio(song_t *csf, song_voice_t *chan, int32_t frequency) { int32_t a = 0; const uint32_t real_tick_count = (csf->current_speed + csf->frame_delay) - csf->tick_count; const uint32_t tick = real_tick_count % (csf->current_speed + csf->frame_delay); switch (tick % 3) { case 1: a = chan->mem_arpeggio >> 4; break; case 2: a = chan->mem_arpeggio & 0xf; break; } if (!a) return frequency; return _muldiv(frequency, linear_slide_up_table[a * 16], 65536); } static inline void rn_pitch_filter_envelope(SCHISM_UNUSED song_t *csf, song_voice_t *chan, int32_t *nenvpitch, int32_t *nfrequency) { song_instrument_t *penv = chan->ptr_instrument; if ((chan->flags & CHN_PITCHENV || penv->flags & (ENV_PITCH | ENV_FILTER)) && (penv->pitch_env.nodes)) { int32_t envpos = chan->pitch_env_position - 1; uint32_t pt = penv->pitch_env.nodes - 1; int32_t frequency = *nfrequency; int32_t envpitch = *nenvpitch; if (chan->pitch_env_position == 0) return; for (uint32_t i = 0; i < (uint32_t)(penv->pitch_env.nodes - 1); i++) { if (envpos <= penv->pitch_env.ticks[i]) { pt = i; break; } } int32_t x2 = penv->pitch_env.ticks[pt]; int32_t x1; if (envpos >= x2) { envpitch = (((int32_t)penv->pitch_env.values[pt]) - 32) * 8; x1 = x2; } else if (pt) { envpitch = (((int32_t)penv->pitch_env.values[pt - 1]) - 32) * 8; x1 = penv->pitch_env.ticks[pt - 1]; } else { envpitch = 0; x1 = 0; } if (envpos > x2) envpos = x2; if (x2 > x1 && envpos > x1) { int32_t envpitchdest = (((int32_t)penv->pitch_env.values[pt]) - 32) * 8; envpitch += ((envpos - x1) * (envpitchdest - envpitch)) / (x2 - x1); } // clamp to -255/255? envpitch = CLAMP(envpitch, -256, 256); // Pitch Envelope if (!(penv->flags & ENV_FILTER)) { int32_t l = abs(envpitch); l = MIN(l, 255); int32_t ratio = (envpitch < 0 ? linear_slide_down_table : linear_slide_up_table)[l]; frequency = _muldiv(frequency, ratio, 0x10000); } *nfrequency = frequency; *nenvpitch = envpitch; } } static inline void _process_envelope(song_voice_t *chan, song_instrument_t *penv, song_envelope_t *envelope, int32_t *position, uint32_t env_flag, uint32_t loop_flag, uint32_t sus_flag, uint32_t fade_flag) { int32_t start = 0, end = 0x7fffffff; if (!(chan->flags & env_flag)) { return; } /* OpenMPT test case EnvOffLength.it */ if ((penv->flags & sus_flag) && !(chan->old_flags & CHN_KEYOFF)) { start = envelope->ticks[envelope->sustain_start]; end = envelope->ticks[envelope->sustain_end] + 1; fade_flag = 0; } else if (penv->flags & loop_flag) { start = envelope->ticks[envelope->loop_start]; end = envelope->ticks[envelope->loop_end] + 1; fade_flag = 0; } else { // End of envelope (?) start = end = envelope->ticks[envelope->nodes - 1]; } if (*position >= end) { if (fade_flag && !envelope->values[envelope->nodes - 1]) { chan->fadeout_volume = chan->final_volume = 0; } *position = start; chan->flags |= fade_flag; // only relevant for volume envelope } (*position)++; } static inline void rn_increment_env_pos(song_voice_t *chan) { song_instrument_t *penv = chan->ptr_instrument; _process_envelope(chan, penv, &penv->vol_env, &chan->vol_env_position, CHN_VOLENV, ENV_VOLLOOP, ENV_VOLSUSTAIN, CHN_NOTEFADE); _process_envelope(chan, penv, &penv->pan_env, &chan->pan_env_position, CHN_PANENV, ENV_PANLOOP, ENV_PANSUSTAIN, 0); _process_envelope(chan, penv, &penv->pitch_env, &chan->pitch_env_position, CHN_PITCHENV, ENV_PITCHLOOP, ENV_PITCHSUSTAIN, 0); } static inline int32_t rn_update_sample(song_t *csf, song_voice_t *chan, int32_t nchan, int32_t master_vol) { // Adjusting volumes if (csf->mix_channels < 2 || (csf->flags & SONG_NOSTEREO)) { chan->right_volume_new = (chan->final_volume * master_vol) >> 8; chan->left_volume_new = chan->right_volume_new; } else if ((chan->flags & CHN_SURROUND) && !(csf->mix_flags & SNDMIX_NOSURROUND)) { chan->right_volume_new = (chan->final_volume * master_vol) >> 8; chan->left_volume_new = -chan->right_volume_new; } else { int32_t pan = ((int32_t) chan->final_panning) - 128; pan *= (int32_t) csf->pan_separation; pan /= 128; if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument && chan->ptr_instrument->midi_channel_mask > 0) GM_Pan(csf, nchan, pan); pan += 128; pan = CLAMP(pan, 0, 256); if (csf->mix_flags & SNDMIX_REVERSESTEREO) pan = 256 - pan; int32_t realvol = (chan->final_volume * master_vol) >> (8 - 1); chan->left_volume_new = (realvol * pan) >> 8; chan->right_volume_new = (realvol * (256 - pan)) >> 8; } // Clipping volumes if (chan->right_volume_new > 0xFFFF) chan->right_volume_new = 0xFFFF; if (chan->left_volume_new > 0xFFFF) chan->left_volume_new = 0xFFFF; // Check IDO if (csf->mix_flags & SNDMIX_NORESAMPLING) { chan->flags &= ~(CHN_HQSRC); chan->flags |= CHN_NOIDO; } else { chan->flags &= ~(CHN_NOIDO | CHN_HQSRC); if (chan->increment == 0x10000) { chan->flags |= CHN_NOIDO; } else { if (!(csf->mix_flags & SNDMIX_HQRESAMPLER) && !(csf->mix_flags & SNDMIX_ULTRAHQSRCMODE)) { if (chan->increment >= 0xFF00) chan->flags |= CHN_NOIDO; } } } chan->right_volume_new >>= MIXING_ATTENUATION; chan->left_volume_new >>= MIXING_ATTENUATION; chan->right_ramp = chan->left_ramp = 0; // Checking Ping-Pong Loops if (chan->flags & CHN_PINGPONGFLAG) chan->increment = -chan->increment; if (chan->flags & CHN_MUTE) { chan->left_volume = chan->right_volume = 0; } else if (!(csf->mix_flags & SNDMIX_NORAMPING) && (chan->flags & CHN_VOLUMERAMP) && (chan->right_volume != chan->right_volume_new || chan->left_volume != chan->left_volume_new)) { // Setting up volume ramp int32_t ramp_length = csf->ramping_samples; int32_t right_delta = lshift_signed(chan->right_volume_new - chan->right_volume, VOLUMERAMPPRECISION); int32_t left_delta = lshift_signed(chan->left_volume_new - chan->left_volume, VOLUMERAMPPRECISION); if (csf->mix_flags & SNDMIX_HQRESAMPLER) { if (chan->right_volume | chan->left_volume && chan->right_volume_new | chan->left_volume_new && !(chan->flags & CHN_FASTVOLRAMP)) { ramp_length = csf->buffer_count; int32_t l = lshift_signed(INT32_C(1), VOLUMERAMPPRECISION - 1); int32_t r = (int32_t)csf->ramping_samples; ramp_length = CLAMP(ramp_length, l, r); } } chan->right_ramp = right_delta / ramp_length; chan->left_ramp = left_delta / ramp_length; chan->right_volume = chan->right_volume_new - rshift_signed(chan->right_ramp * ramp_length, VOLUMERAMPPRECISION); chan->left_volume = chan->left_volume_new - rshift_signed(chan->left_ramp * ramp_length, VOLUMERAMPPRECISION); if (chan->right_ramp | chan->left_ramp) { chan->ramp_length = ramp_length; } else { chan->flags &= ~CHN_VOLUMERAMP; chan->right_volume = chan->right_volume_new; chan->left_volume = chan->left_volume_new; } } else { chan->flags &= ~CHN_VOLUMERAMP; chan->right_volume = chan->right_volume_new; chan->left_volume = chan->left_volume_new; } chan->right_ramp_volume = lshift_signed(chan->right_volume, VOLUMERAMPPRECISION); chan->left_ramp_volume = lshift_signed(chan->left_volume, VOLUMERAMPPRECISION); // Adding the channel in the channel list csf->voice_mix[csf->num_voices++] = nchan; if (csf->num_voices >= MAX_VOICES) return 0; return 1; } // XXX Rename this //Ranges: // chan_num = 0..63 // freq = frequency in Hertz // vol = 0..16384 // chan->instrument_volume = 0..64 (corresponds to the sample global volume and instrument global volume) static inline void rn_gen_key(song_t *csf, song_voice_t *chan, int32_t chan_num, int32_t freq, int32_t vol) { if (chan->flags & CHN_MUTE) { // don't do anything return; } else if (csf->flags & SONG_INSTRUMENTMODE && chan->ptr_instrument && chan->ptr_instrument->midi_channel_mask > 0) { MidiBendMode BendMode = MIDI_BEND_NORMAL; /* TODO: If we're expecting a large bend exclusively * in either direction, update BendMode to indicate so. * This can be used to extend the range of MIDI pitch bending. */ int32_t volume = vol; if ((chan->flags & CHN_ADLIB) && volume > 0) { // find_volume translates volume from range 0..16384 to range 0..127. But why with that method? volume = find_volume((unsigned short) volume) * chan->instrument_volume / 64; } else { // This gives a value in the range 0..127. volume = volume * chan->instrument_volume / 8192; } GM_SetFreqAndVol(csf, chan_num, freq, volume, BendMode, chan->flags & CHN_KEYOFF); } if (chan->flags & CHN_ADLIB) { // Scaling is needed to get a frequency that matches with ST3 notes. // 8363 is st3s middle C sample rate. 261.625 is the Hertz for middle C in a tempered scale (A4 = 440) //Also, note that to be true to ST3, the frequencies should be quantized, like using the glissando control. // OPL_Patch is called in csf_process_effects, from csf_read_note or csf_process_tick, before calling this method. int32_t oplmilliHertz = (int64_t)freq*261625L/8363L; OPL_HertzTouch(csf, chan_num, oplmilliHertz, chan->flags & CHN_KEYOFF); // ST32 ignores global & master volume in adlib mode, guess we should do the same -Bisqwit // This gives a value in the range 0..63. // log_appendf(2,"vol: %d, voiceinsvol: %d", vol , chan->instrument_volume); OPL_Touch(csf, chan_num, vol * chan->instrument_volume * 63 / (INT32_C(1) << 20)); OPL_Pan(csf, chan_num, (csf->flags & SONG_NOSTEREO) ? 128 : chan->final_panning); } } //////////////////////////////////////////////////////////////////////////////////////////// int32_t csf_init_player(song_t *csf, int reset) { if (csf->max_voices > MAX_VOICES) csf->max_voices = MAX_VOICES; csf->mix_frequency = CLAMP(csf->mix_frequency, 4000, MAX_SAMPLE_RATE); csf->ramping_samples = (csf->mix_frequency * VOLUMERAMPLEN) / 100000; if (csf->ramping_samples < 8) csf->ramping_samples = 8; if (csf->mix_flags & SNDMIX_NORAMPING) csf->ramping_samples = 2; csf->dry_rofs_vol = csf->dry_lofs_vol = 0; if (reset) { csf->vu_left = 0; csf->vu_right = 0; } song_init_eq(reset, csf->mix_frequency); // I don't know why, but this "if" makes it work at the desired sample rate instead of 4000. // the "4000Hz" value comes from csf_reset, but I don't yet understand why the opl keeps that value, if // each call to Fmdrv_Init generates a new opl. if (csf->mix_frequency != 4000) { Fmdrv_Init(csf, csf->mix_frequency); } GM_Reset(csf, 0); return 1; } uint32_t csf_read(song_t *csf, void * v_buffer, uint32_t bufsize) { uint8_t * buffer = (uint8_t *)v_buffer; convert_t convert_func = clip_32_to_8; int32_t vu_min[2]; int32_t vu_max[2]; uint32_t bufleft, max, sample_size, count, smpcount, mix_stat=0; vu_min[0] = vu_min[1] = 0x7FFFFFFF; vu_max[0] = vu_max[1] = -0x7FFFFFFF; csf->mix_stat = 0; sample_size = csf->mix_channels; switch (csf->mix_bits_per_sample) { case 16: sample_size *= 2; convert_func = clip_32_to_16; break; case 24: sample_size *= 3; convert_func = clip_32_to_24; break; case 32: sample_size *= 4; convert_func = clip_32_to_32; break; } max = bufsize / sample_size; if (!max || !buffer) return 0; bufleft = max; if (csf->flags & SONG_ENDREACHED) bufleft = 0; // skip the loop while (bufleft > 0) { // Update Channel Data if (!csf->buffer_count) { if (!(csf->mix_flags & SNDMIX_DIRECTTODISK)) csf->buffer_count = bufleft; if (!csf_read_note(csf)) { csf->flags |= SONG_ENDREACHED; if (csf->stop_at_order > -1) return 0; /* faster */ if (bufleft == max) break; if (!(csf->mix_flags & SNDMIX_DIRECTTODISK)) csf->buffer_count = bufleft; } if (!csf->buffer_count) break; } count = csf->buffer_count; if (count > MIXBUFFERSIZE) count = MIXBUFFERSIZE; if (count > bufleft) count = bufleft; if (!count) break; smpcount = count; // Resetting sound buffer stereo_fill(csf->mix_buffer, smpcount, &csf->dry_rofs_vol, &csf->dry_lofs_vol); if (csf->mix_channels >= 2) { smpcount *= 2; csf->mix_stat += csf_create_stereo_mix(csf, count); } else { csf->mix_stat += csf_create_stereo_mix(csf, count); mono_from_stereo(csf->mix_buffer, count); } // Handle eq if (csf->mix_channels >= 2) { eq_stereo(csf, csf->mix_buffer, count); // FIXME: disable this when we're writing WAVs if (!(csf->mix_flags & SNDMIX_DIRECTTODISK)) normalize_stereo(csf, csf->mix_buffer, count << 1); } else { eq_mono(csf, csf->mix_buffer, count); if (!(csf->mix_flags & SNDMIX_DIRECTTODISK)) normalize_mono(csf, csf->mix_buffer, count); } mix_stat++; if (csf->multi_write) { /* multi doesn't actually write meaningful data into 'buffer', so we can use that as temp space for converting */ for (uint32_t n = 0; n < 64; n++) { if (csf->multi_write[n].used) { if (csf->mix_channels < 2) mono_from_stereo(csf->multi_write[n].buffer, count); uint32_t bytes = convert_func(buffer, csf->multi_write[n].buffer, smpcount, vu_min, vu_max); csf->multi_write[n].write(csf->multi_write[n].data, buffer, bytes); } else { csf->multi_write[n].silence(csf->multi_write[n].data, smpcount * ((csf->mix_bits_per_sample + 7) / 8)); } } } else { // Perform clipping + VU-Meter buffer += convert_func(buffer, csf->mix_buffer, smpcount, vu_min, vu_max); } // Buffer ready bufleft -= count; csf->buffer_count -= count; } if (bufleft) memset(buffer, (csf->mix_bits_per_sample == 8) ? 0x80 : 0, bufleft * sample_size); // VU-Meter //Reduce range to 8bits signed (-128 to 127). vu_min[0] = rshift_signed(vu_min[0], 19); vu_min[1] = rshift_signed(vu_min[1], 19); vu_max[0] = rshift_signed(vu_max[0], 19); vu_max[1] = rshift_signed(vu_max[1], 19); if (vu_max[0] < vu_min[0]) vu_max[0] = vu_min[0]; if (vu_max[1] < vu_min[1]) vu_max[1] = vu_min[1]; csf->vu_left = (uint32_t)(vu_max[0] - vu_min[0]); csf->vu_right = (uint32_t)(vu_max[1] - vu_min[1]); if (mix_stat) { csf->mix_stat += mix_stat - 1; csf->mix_stat /= mix_stat; } return max - bufleft; } ///////////////////////////////////////////////////////////////////////////// // Handles navigation/effects static int32_t increment_order(song_t *csf) { csf->process_row = csf->break_row; /* [ProcessRow = BreakRow] */ csf->break_row = 0; /* [BreakRow = 0] */ /* some ugly copypasta, this should be less dumb */ if (csf->flags & SONG_PATTERNPLAYBACK) { /* process_order is hijacked as a "playback initiated" flag -- otherwise repeat count would be incremented as soon as pattern playback started. (this is a stupid hack) */ if (csf->process_order) { if (++csf->repeat_count) { if (SCHISM_UNLIKELY(csf->repeat_count < 0)) { csf->repeat_count = 1; // it overflowed! } } else { csf->process_row = PROCESS_NEXT_ORDER; return 0; } } else { csf->process_order = 1; } } else if (!(csf->flags & SONG_ORDERLOCKED)) { /* [Increase ProcessOrder] */ /* [while Order[ProcessOrder] = 0xFEh, increase ProcessOrder] */ do { csf->process_order++; } while (csf->orderlist[csf->process_order] == ORDER_SKIP); /* [if Order[ProcessOrder] = 0xFFh, ProcessOrder = 0] (... or just stop playing) */ if (csf->orderlist[csf->process_order] == ORDER_LAST) { if (++csf->repeat_count) { if (SCHISM_UNLIKELY(csf->repeat_count < 0)) { csf->repeat_count = 1; // it overflowed! } } else { csf->process_row = PROCESS_NEXT_ORDER; return 0; } csf->process_order = 0; while (csf->orderlist[csf->process_order] == ORDER_SKIP) csf->process_order++; } if (csf->orderlist[csf->process_order] >= MAX_PATTERNS) { // what the butt? csf->process_row = PROCESS_NEXT_ORDER; return 0; } /* [CurrentPattern = Order[ProcessOrder]] */ csf->current_order = csf->process_order; csf->current_pattern = csf->orderlist[csf->process_order]; } if (!csf->pattern_size[csf->current_pattern] || !csf->patterns[csf->current_pattern]) { /* okay, this is wrong. allocate the pattern _NOW_ */ csf->patterns[csf->current_pattern] = csf_allocate_pattern(64); csf->pattern_size[csf->current_pattern] = 64; csf->pattern_alloc_size[csf->current_pattern] = 64; } if (csf->process_row >= csf->pattern_size[csf->current_pattern]) { // Cxx to row beyond end of pattern: use 0 instead csf->process_row = 0; } return 1; } int32_t csf_process_tick(song_t *csf) { csf->flags &= ~SONG_FIRSTTICK; /* [Decrease tick counter. Is tick counter 0?] */ if (--csf->tick_count == 0) { /* [-- Yes --] */ /* [Tick counter = Tick counter set (the current 'speed')] */ csf->tick_count = csf->current_speed + csf->frame_delay; /* [Decrease row counter. Is row counter 0?] */ if (--csf->row_count <= 0) { /* [-- Yes --] */ /* [Row counter = 1] this uses zero, in order to simplify SEx effect handling -- SEx has no effect if a channel to its left has already set the delay value. thus we set the row counter there to (value + 1) which is never zero, but 0 and 1 are fundamentally equivalent as far as csf_process_tick is concerned. */ csf->row_count = 0; /* [Increase ProcessRow. Is ProcessRow > NumberOfRows?] */ if (++csf->process_row >= csf->pattern_size[csf->current_pattern]) { /* [-- Yes --] */ if (!increment_order(csf)) return 0; } /* else [-- No --] */ /* [CurrentRow = ProcessRow] */ csf->row = csf->process_row; /* [Update Pattern Variables] (this is handled along with update effects) */ csf->frame_delay = 0; csf->tick_count = csf->current_speed; csf->flags |= SONG_FIRSTTICK; } else { /* [-- No --] */ /* Call update-effects for each channel. */ } // Reset channel values song_voice_t *chan = csf->voices; song_note_t *m = csf->patterns[csf->current_pattern] + csf->row * MAX_CHANNELS; for (uint32_t nchan=0; nchanrow_note = m->note; if (m->instrument) chan->last_instrument = m->instrument; chan->row_instr = m->instrument; chan->row_voleffect = m->voleffect; chan->row_volparam = m->volparam; chan->row_effect = m->effect; chan->row_param = m->param; chan->left_volume = chan->left_volume_new; chan->right_volume = chan->right_volume_new; chan->flags &= ~(CHN_PORTAMENTO | CHN_VIBRATO | CHN_TREMOLO); chan->n_command = 0; } csf_process_effects(csf, 1); } else { /* [-- No --] */ /* [Update effects for each channel as required.] */ song_note_t *m = csf->patterns[csf->current_pattern] + csf->row * MAX_CHANNELS; for (uint32_t nchan=0; nchantick_count % (csf->current_speed + csf->frame_delay))) { csf->flags |= SONG_FIRSTTICK; } csf_process_effects(csf, 0); } return 1; } //////////////////////////////////////////////////////////////////////////////////////////// // Handles envelopes & mixer setup int32_t csf_read_note(song_t *csf) { song_voice_t *chan; uint32_t cn; // Checking end of row ? if (csf->flags & SONG_PAUSED) { if (!csf->current_speed) csf->current_speed = csf->initial_speed ? csf->initial_speed : 6; if (!csf->current_tempo) csf->current_tempo = csf->initial_tempo ? csf->initial_speed : 125; csf->flags &= ~SONG_FIRSTTICK; if (--csf->tick_count == 0) { csf->tick_count = csf->current_speed; if (--csf->row_count <= 0) { csf->row_count = 0; } // clear channel values (similar to csf_process_tick) for (cn = 0, chan = csf->voices; cn < MAX_CHANNELS; cn++, chan++) { chan->row_note = 0; chan->row_instr = 0; chan->row_voleffect = 0; chan->row_volparam = 0; chan->row_effect = 0; chan->row_param = 0; chan->n_command = 0; } } csf_process_effects(csf, 0); } else { if (!csf_process_tick(csf)) return 0; } //////////////////////////////////////////////////////////////////////////////////// if (!csf->current_tempo) return 0; csf->buffer_count = (csf->mix_frequency * 5 * csf->tempo_factor) / (csf->current_tempo << 8); // chaseback hoo hah if (csf->stop_at_order > -1 && csf->stop_at_row > -1) { if (csf->stop_at_order <= (int32_t)csf->current_order && csf->stop_at_row <= (int32_t)csf->row) { return 0; } } //////////////////////////////////////////////////////////////////////////////////// // Update channels data // Master Volume + Pre-Amplification / Attenuation setup uint32_t master_vol = csf->mixing_volume << 2; // yields maximum of 0x200 csf->num_voices = 0; for (cn = 0, chan = csf->voices; cn < MAX_VOICES; cn++, chan++) { /*if(cn == 4 || chan->master_channel == 4) fprintf(stderr, "considering voice %d (per %d, pos %d/%d, flags %X)\n", (int32_t)cn, chan->frequency, chan->position, chan->length, chan->flags);*/ // reset this ~first~ if (!(chan->flags & CHN_ADLIB)) chan->vu_meter = 0; if ((chan->flags & CHN_NOTEFADE) && !(chan->fadeout_volume | chan->right_volume | chan->left_volume)) { chan->length = 0; chan->rofs = 0; chan->lofs = 0; continue; } // Check for unused channel if (cn >= MAX_CHANNELS) if (!chan->length && !(chan->flags & CHN_ADLIB)) continue; // Reset channel data chan->increment = 0; chan->final_volume = 0; chan->final_panning = chan->panning + chan->pan_swing; /* Add panbrello delta */ if (chan->panbrello_delta) chan->final_panning += rshift_signed((chan->panbrello_delta * (int32_t)chan->panbrello_depth) + 2, 3); chan->ramp_length = 0; // Calc Frequency if (chan->frequency && chan->length) { int32_t vol = chan->volume; if (chan->flags & CHN_TREMOLO) vol += chan->tremolo_delta; vol = CLAMP(vol, 0, 256); // Tremor if (chan->n_command == FX_TREMOR) rn_tremor(chan, &vol); // Clip volume vol = CLAMP(vol, 0, 0x100); vol = lshift_signed(vol, 6); // Process Envelopes if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument) { /* OpenMPT test cases s77.it and EnvLoops.it */ rn_increment_env_pos(chan); rn_process_envelope(chan, &vol); } else { // No Envelope: key off => note cut // 1.41-: CHN_KEYOFF|CHN_NOTEFADE if (chan->flags & CHN_NOTEFADE) { chan->fadeout_volume = 0; vol = 0; } } // vol is 14-bits if (vol) { // IMPORTANT: chan->final_volume is 14 bits !!! // -> _muldiv( 14+7, 6+6, 18); => RealVolume: 14-bit result (21+12-19) chan->final_volume = _muldiv (vol * csf->current_global_volume, chan->global_volume * CLAMP(chan->instrument_volume + chan->vol_swing, 0, 64), INT32_C(1) << 19); } chan->calc_volume = vol; int32_t frequency = chan->frequency; if ((chan->flags & (CHN_GLISSANDO|CHN_PORTAMENTO)) == (CHN_GLISSANDO|CHN_PORTAMENTO)) { frequency = get_frequency_from_note(get_note_from_frequency(frequency, chan->c5speed), chan->c5speed); } // Arpeggio ? if (chan->n_command == FX_ARPEGGIO) frequency = rn_arpeggio(csf, chan, frequency); // MIDI macros (this is done here in OpenMPT, just take heed from them) rn_process_midi_macro(csf, chan); // Pitch/Filter Envelope int32_t envpitch = 0; if ((csf->flags & SONG_INSTRUMENTMODE) && chan->ptr_instrument) rn_pitch_filter_envelope(csf, chan, &envpitch, &frequency); // Vibrato if (chan->flags & CHN_VIBRATO) { /* OpenMPT test case VibratoDouble.it: vibrato is applied twice if vibrato is applied in the volume and effect columns */ if (chan->row_voleffect == VOLFX_VIBRATODEPTH && (chan->row_effect == FX_VIBRATO || chan->row_effect == FX_VIBRATOVOL || chan->row_effect == FX_FINEVIBRATO)) frequency = rn_vibrato(csf, chan, frequency); frequency = rn_vibrato(csf, chan, frequency); } // Sample Auto-Vibrato if (chan->ptr_sample && chan->ptr_sample->vib_depth) { frequency = rn_sample_vibrato(csf, chan, frequency); } if (!(chan->flags & CHN_NOTEFADE)) rn_gen_key(csf, chan, cn, frequency, vol); if (chan->flags & CHN_NEWNOTE) { setup_channel_filter(chan, 1, 256, csf->mix_frequency); } // Filter Envelope: controls cutoff frequency if (chan && chan->ptr_instrument && chan->ptr_instrument->flags & ENV_FILTER) { setup_channel_filter(chan, !(chan->flags & CHN_FILTER), envpitch, csf->mix_frequency); } chan->sample_freq = frequency; int32_t ninc = _muldiv(frequency, 0x10000, csf->mix_frequency); if (csf->freq_factor != 128) ninc = (ninc * csf->freq_factor) >> 7; chan->increment = MAX(1, ninc); } chan->final_panning = CLAMP(chan->final_panning, 0, 256); // Volume ramping chan->flags &= ~CHN_VOLUMERAMP; if (chan->final_volume || chan->left_volume || chan->right_volume) chan->flags |= CHN_VOLUMERAMP; if (chan->strike) chan->strike--; // Check for too big increment if ((rshift_signed(chan->increment, 16) + 1) >= (int32_t)(chan->loop_end - chan->loop_start)) chan->flags &= ~CHN_LOOP; chan->right_volume_new = chan->left_volume_new = 0; if (!(chan->length && chan->increment)) chan->current_sample_data = NULL; // Process the VU meter. This is filled in with real // data in the mixer loops. if (chan->flags & CHN_ADLIB) { // ...except with AdLib, which fakes it for now if (chan->strike > 2) chan->vu_meter = (0xFF * chan->final_volume) >> 14; // fake VU decay (intentionally similar to ST3) chan->vu_meter = (chan->vu_meter > VUMETER_DECAY) ? (chan->vu_meter - VUMETER_DECAY) : 0; if (chan->vu_meter >= 0x100) { uint32_t vutmp = chan->final_volume >> (14 - 8); if (vutmp > 0xFF) vutmp = 0xFF; chan->vu_meter = vutmp; } } if (chan->current_sample_data) { if (!rn_update_sample(csf, chan, cn, master_vol)) break; } else { // Note change but no sample //if (chan->vu_meter > 0xFF) chan->vu_meter = 0; chan->left_volume = chan->right_volume = 0; chan->length = 0; // Put the channel back into the mixer for end-of-sample pop reduction // stolen from openmpt if (chan->lofs || chan->rofs) csf->voice_mix[csf->num_voices++] = cn; } chan->old_flags = chan->flags; chan->flags &= ~CHN_NEWNOTE; } // Checking Max Mix Channels reached: ordering by volume if (csf->num_voices >= csf->max_voices && (!(csf->mix_flags & SNDMIX_DIRECTTODISK))) { for (uint32_t i = 0; i < csf->num_voices; i++) { uint32_t j = i; while ((j + 1 < csf->num_voices) && (csf->voices[csf->voice_mix[j]].final_volume < csf->voices[csf->voice_mix[j + 1]].final_volume)) { uint32_t n = csf->voice_mix[j]; csf->voice_mix[j] = csf->voice_mix[j + 1]; csf->voice_mix[j + 1] = n; j++; } } } return 1; } schismtracker-20250313/player/tables.c000066400000000000000000000332561476471630300175530ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "player/tables.h" const uint8_t vc_portamento_table[16] = { 0x00, 0x01, 0x04, 0x08, 0x10, 0x20, 0x40, 0x60, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; const uint16_t period_table[12] = { 1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016, 960, 907, }; const uint16_t finetune_table[16] = { 7895, 7941, 7985, 8046, 8107, 8169, 8232, 8280, 8363, 8413, 8463, 8529, 8581, 8651, 8723, 8757, // 8363*2^((i-8)/(12*8)) }; // Tables from ITTECH.TXT const int8_t sine_table[256] = { 0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 16, 17, 19, 20, 22, 23, 24, 26, 27, 29, 30, 32, 33, 34, 36, 37, 38, 39, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59, 59, 60, 60, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 60, 60, 59, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 39, 38, 37, 36, 34, 33, 32, 30, 29, 27, 26, 24, 23, 22, 20, 19, 17, 16, 14, 12, 11, 9, 8, 6, 5, 3, 2, 0, -2, -3, -5, -6, -8, -9,-11,-12,-14,-16,-17,-19,-20,-22,-23, -24,-26,-27,-29,-30,-32,-33,-34,-36,-37,-38,-39,-41,-42,-43,-44, -45,-46,-47,-48,-49,-50,-51,-52,-53,-54,-55,-56,-56,-57,-58,-59, -59,-60,-60,-61,-61,-62,-62,-62,-63,-63,-63,-64,-64,-64,-64,-64, -64,-64,-64,-64,-64,-64,-63,-63,-63,-62,-62,-62,-61,-61,-60,-60, -59,-59,-58,-57,-56,-56,-55,-54,-53,-52,-51,-50,-49,-48,-47,-46, -45,-44,-43,-42,-41,-39,-38,-37,-36,-34,-33,-32,-30,-29,-27,-26, -24,-23,-22,-20,-19,-17,-16,-14,-12,-11, -9, -8, -6, -5, -3, -2, }; const int8_t ramp_down_table[256] = { 64, 63, 63, 62, 62, 61, 61, 60, 60, 59, 59, 58, 58, 57, 57, 56, 56, 55, 55, 54, 54, 53, 53, 52, 52, 51, 51, 50, 50, 49, 49, 48, 48, 47, 47, 46, 46, 45, 45, 44, 44, 43, 43, 42, 42, 41, 41, 40, 40, 39, 39, 38, 38, 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 32, 32, 31, 31, 30, 30, 29, 29, 28, 28, 27, 27, 26, 26, 25, 25, 24, 24, 23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 18, 18, 17, 17, 16, 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 11, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0, -1, -1, -2, -2, -3, -3, -4, -4, -5, -5, -6, -6, -7, -7, -8, -8, -9, -9,-10,-10,-11,-11,-12,-12,-13,-13,-14,-14,-15,-15,-16, -16,-17,-17,-18,-18,-19,-19,-20,-20,-21,-21,-22,-22,-23,-23,-24, -24,-25,-25,-26,-26,-27,-27,-28,-28,-29,-29,-30,-30,-31,-31,-32, -32,-33,-33,-34,-34,-35,-35,-36,-36,-37,-37,-38,-38,-39,-39,-40, -40,-41,-41,-42,-42,-43,-43,-44,-44,-45,-45,-46,-46,-47,-47,-48, -48,-49,-49,-50,-50,-51,-51,-52,-52,-53,-53,-54,-54,-55,-55,-56, -56,-57,-57,-58,-58,-59,-59,-60,-60,-61,-61,-62,-62,-63,-63,-64, }; const int8_t square_table[256] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; // volume fade tables for Retrig Note: const int8_t retrig_table_1[16] = { 0, 0, 0, 0, 0, 0, 10, 8, 0, 0, 0, 0, 0, 0, 24, 32 }; const int8_t retrig_table_2[16] = { 0, -1, -2, -4, -8, -16, 0, 0, 0, 1, 2, 4, 8, 16, 0, 0 }; // round(65536 * 2**(n/768)) // 768 = 64 extra-fine finetune steps for 12 notes // Table content is in 16.16 format const uint32_t fine_linear_slide_up_table[16] = { 65536, 65595, 65654, 65714, 65773, 65832, 65892, 65951, 66011, 66071, 66130, 66190, 66250, 66309, 66369, 66429 }; // round(65536 * 2**(-n/768)) // 768 = 64 extra-fine finetune steps for 12 notes // Table content is in 16.16 format // Note that there are a few errors in this table (typos?), but well, this table comes straight from ITTECH.TXT... // Entry 0 (65535) should be 65536 (this value is unused and most likely stored this way so that it fits in a 16-bit integer) // Entry 11 (64888) should be 64889 - rounding error? // Entry 15 (64645) should be 64655 - typo? const uint32_t fine_linear_slide_down_table[16] = { 65535, 65477, 65418, 65359, 65300, 65241, 65182, 65123, 65065, 65006, 64947, 64888, 64830, 64772, 64713, 64645 }; // floor(65536 * 2**(n/192)) // 192 = 16 finetune steps for 12 notes // Table content is in 16.16 format const uint32_t linear_slide_up_table[256] = { 65536, 65773, 66010, 66249, 66489, 66729, 66971, 67213, 67456, 67700, 67945, 68190, 68437, 68685, 68933, 69182, 69432, 69684, 69936, 70189, 70442, 70697, 70953, 71209, 71467, 71725, 71985, 72245, 72507, 72769, 73032, 73296, 73561, 73827, 74094, 74362, 74631, 74901, 75172, 75444, 75717, 75991, 76265, 76541, 76818, 77096, 77375, 77655, 77935, 78217, 78500, 78784, 79069, 79355, 79642, 79930, 80219, 80509, 80800, 81093, 81386, 81680, 81976, 82272, 82570, 82868, 83168, 83469, 83771, 84074, 84378, 84683, 84989, 85297, 85605, 85915, 86225, 86537, 86850, 87164, 87480, 87796, 88113, 88432, 88752, 89073, 89395, 89718, 90043, 90369, 90695, 91023, 91353, 91683, 92015, 92347, 92681, 93017, 93353, 93691, 94029, 94370, 94711, 95053, 95397, 95742, 96088, 96436, 96785, 97135, 97486, 97839, 98193, 98548, 98904, 99262, 99621, 99981, 100343, 100706, 101070, 101435, 101802, 102170, 102540, 102911, 103283, 103657, 104031, 104408, 104785, 105164, 105545, 105926, 106309, 106694, 107080, 107467, 107856, 108246, 108637, 109030, 109425, 109820, 110217, 110616, 111016, 111418, 111821, 112225, 112631, 113038, 113447, 113857, 114269, 114682, 115097, 115514, 115931, 116351, 116771, 117194, 117618, 118043, 118470, 118898, 119328, 119760, 120193, 120628, 121064, 121502, 121941, 122382, 122825, 123269, 123715, 124162, 124611, 125062, 125514, 125968, 126424, 126881, 127340, 127801, 128263, 128727, 129192, 129660, 130129, 130599, 131072, 131546, 132021, 132499, 132978, 133459, 133942, 134426, 134912, 135400, 135890, 136381, 136875, 137370, 137866, 138365, 138865, 139368, 139872, 140378, 140885, 141395, 141906, 142419, 142935, 143451, 143970, 144491, 145014, 145538, 146064, 146593, 147123, 147655, 148189, 148725, 149263, 149803, 150344, 150888, 151434, 151982, 152531, 153083, 153637, 154192, 154750, 155310, 155871, 156435, 157001, 157569, 158138, 158710, 159284, 159860, 160439, 161019, 161601, 162186, 162772, 163361, 163952, 164545, }; // floor(65536 * 2**(-n/192)) // 192 = 16 finetune steps for 12 notes // Table content is in 16.16 format const uint32_t linear_slide_down_table[256] = { 65536, 65299, 65064, 64830, 64596, 64363, 64131, 63900, 63670, 63440, 63212, 62984, 62757, 62531, 62305, 62081, 61857, 61634, 61412, 61191, 60970, 60751, 60532, 60314, 60096, 59880, 59664, 59449, 59235, 59021, 58809, 58597, 58385, 58175, 57965, 57757, 57548, 57341, 57134, 56928, 56723, 56519, 56315, 56112, 55910, 55709, 55508, 55308, 55108, 54910, 54712, 54515, 54318, 54123, 53928, 53733, 53540, 53347, 53154, 52963, 52772, 52582, 52392, 52204, 52015, 51828, 51641, 51455, 51270, 51085, 50901, 50717, 50535, 50353, 50171, 49990, 49810, 49631, 49452, 49274, 49096, 48919, 48743, 48567, 48392, 48218, 48044, 47871, 47698, 47526, 47355, 47185, 47014, 46845, 46676, 46508, 46340, 46173, 46007, 45841, 45676, 45511, 45347, 45184, 45021, 44859, 44697, 44536, 44376, 44216, 44056, 43898, 43740, 43582, 43425, 43268, 43112, 42957, 42802, 42648, 42494, 42341, 42189, 42037, 41885, 41734, 41584, 41434, 41285, 41136, 40988, 40840, 40693, 40546, 40400, 40254, 40109, 39965, 39821, 39677, 39534, 39392, 39250, 39108, 38967, 38827, 38687, 38548, 38409, 38270, 38132, 37995, 37858, 37722, 37586, 37450, 37315, 37181, 37047, 36913, 36780, 36648, 36516, 36384, 36253, 36122, 35992, 35862, 35733, 35604, 35476, 35348, 35221, 35094, 34968, 34842, 34716, 34591, 34466, 34342, 34218, 34095, 33972, 33850, 33728, 33606, 33485, 33364, 33244, 33124, 33005, 32886, 32768, 32649, 32532, 32415, 32298, 32181, 32065, 31950, 31835, 31720, 31606, 31492, 31378, 31265, 31152, 31040, 30928, 30817, 30706, 30595, 30485, 30375, 30266, 30157, 30048, 29940, 29832, 29724, 29617, 29510, 29404, 29298, 29192, 29087, 28982, 28878, 28774, 28670, 28567, 28464, 28361, 28259, 28157, 28056, 27955, 27854, 27754, 27654, 27554, 27455, 27356, 27257, 27159, 27061, 26964, 26866, 26770, 26673, 26577, 26481, 26386, 26291, 26196, 26102, }; /* --------------------------------------------------------------------------------------------------------- */ const char *midi_group_names[17] = { "Piano", "Chromatic Percussion", "Organ", "Guitar", "Bass", "Strings", "Ensemble", "Brass", "Reed", "Pipe", "Synth Lead", "Synth Pad", "Synth Effects", "Ethnic", "Percussive", "Sound Effects", "Percussions", }; const char *midi_program_names[128] = { // 1-8: Piano "Acoustic Grand Piano", "Bright Acoustic Piano", "Electric Grand Piano", "Honky-tonk Piano", "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavi", // 9-16: Chromatic Percussion "Celesta", "Glockenspiel", "Music Box", "Vibraphone", "Marimba", "Xylophone", "Tubular Bells", "Dulcimer", // 17-24: Organ "Drawbar Organ", "Percussive Organ", "Rock Organ", "Church Organ", "Reed Organ", "Accordion", "Harmonica", "Tango Accordion", // 25-32: Guitar "Acoustic Guitar (nylon)", "Acoustic Guitar (steel)", "Electric Guitar (jazz)", "Electric Guitar (clean)", "Electric Guitar (muted)", "Overdriven Guitar", "Distortion Guitar", "Guitar harmonics", // 33-40 Bass "Acoustic Bass", "Electric Bass (finger)", "Electric Bass (pick)", "Fretless Bass", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", // 41-48 Strings "Violin", "Viola", "Cello", "Contrabass", "Tremolo Strings", "Pizzicato Strings", "Orchestral Harp", "Timpani", // 49-56 Ensemble "String Ensemble 1", "String Ensemble 2", "SynthStrings 1", "SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit", // 57-64 Brass "Trumpet", "Trombone", "Tuba", "Muted Trumpet", "French Horn", "Brass Section", "SynthBrass 1", "SynthBrass 2", // 65-72 Reed "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax", "Oboe", "English Horn", "Bassoon", "Clarinet", // 73-80 Pipe "Piccolo", "Flute", "Recorder", "Pan Flute", "Blown Bottle", "Shakuhachi", "Whistle", "Ocarina", // 81-88 Synth Lead "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope)", "Lead 4 (chiff)", "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", "Lead 8 (bass + lead)", // 89-96 Synth Pad "Pad 1 (new age)", "Pad 2 (warm)", "Pad 3 (polysynth)", "Pad 4 (choir)", "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", "Pad 8 (sweep)", // 97-104 Synth Effects "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)", "FX 8 (sci-fi)", // 105-112 Ethnic "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bag pipe", "Fiddle", "Shanai", // 113-120 Percussive "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", // 121-128 Sound Effects "Guitar Fret Noise", "Breath Noise", "Seashore", "Bird Tweet", "Telephone Ring", "Helicopter", "Applause", "Gunshot", }; // Notes 25-85 const char *midi_percussion_names[61] = { "Seq Click", "Brush Tap", "Brush Swirl", "Brush Slap", "Brush Swirl W/Attack", "Snare Roll", "Castanet", "Snare Lo", "Sticks", "Bass Drum Lo", "Open Rim Shot", "Acoustic Bass Drum", "Bass Drum 1", "Side Stick", "Acoustic Snare", "Hand Clap", "Electric Snare", "Low Floor Tom", "Closed Hi Hat", "High Floor Tom", "Pedal Hi-Hat", "Low Tom", "Open Hi-Hat", "Low-Mid Tom", "Hi Mid Tom", "Crash Cymbal 1", "High Tom", "Ride Cymbal 1", "Chinese Cymbal", "Ride Bell", "Tambourine", "Splash Cymbal", "Cowbell", "Crash Cymbal 2", "Vibraslap", "Ride Cymbal 2", "Hi Bongo", "Low Bongo", "Mute Hi Conga", "Open Hi Conga", "Low Conga", "High Timbale", "Low Timbale", "High Agogo", "Low Agogo", "Cabasa", "Maracas", "Short Whistle", "Long Whistle", "Short Guiro", "Long Guiro", "Claves", "Hi Wood Block", "Low Wood Block", "Mute Cuica", "Open Cuica", "Mute Triangle", "Open Triangle", "Shaker", "Jingle Bell", "Bell Tree", }; schismtracker-20250313/schism/000077500000000000000000000000001476471630300161165ustar00rootroot00000000000000schismtracker-20250313/schism/audio_loadsave.c000066400000000000000000000744511476471630300212540ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "charset.h" #include "dialog.h" #include "fakemem.h" #include "it.h" #include "song.h" #include "slurp.h" #include "page.h" #include "version.h" #include "osdefs.h" #include "mem.h" #include "fmt.h" #include "dmoz.h" #include "player/sndfile.h" #include "player/snd_gm.h" #include "midi.h" #include "disko.h" // ------------------------------------------------------------------------ char song_filename[SCHISM_PATH_MAX + 1]; char song_basename[SCHISM_NAME_MAX + 1]; // ------------------------------------------------------------------------ // replace any '\0' chars with spaces, mostly to make the string handling // much easier. // TODO | Maybe this should be done with the filenames and the song title // TODO | as well? (though I've never come across any cases of either of // TODO | these having null characters in them...) static inline SCHISM_ALWAYS_INLINE void _fix_name(char *buf, size_t n) { size_t c; for (c = 0; c < n; c++) if (!buf[c]) buf[c] = 0x20; buf[n - 1] = 0; } static void _fix_names(song_t *qq) { int n; _fix_name(qq->title, ARRAY_SIZE(qq->title)); for (n = 1; n < MAX_SAMPLES; n++) { _fix_name(qq->samples[n].name, ARRAY_SIZE(qq->samples[n].name)); _fix_name(qq->samples[n].filename, ARRAY_SIZE(qq->samples[n].filename)); } for (n = 1; n < MAX_INSTRUMENTS; n++) if (qq->instruments[n]) { _fix_name(qq->instruments[n]->name, ARRAY_SIZE(qq->instruments[n]->name)); _fix_name(qq->instruments[n]->filename, ARRAY_SIZE(qq->instruments[n]->filename)); } } // ------------------------------------------------------------------------ // file stuff static void song_set_filename(const char *file) { if (file && *file) { void *out = charset_iconv_easy(file, CHARSET_CHAR, CHARSET_CP437); strncpy(song_filename, out, ARRAY_SIZE(song_filename) - 1); strncpy(song_basename, dmoz_path_get_basename(out), ARRAY_SIZE(song_basename) - 1); free(out); song_filename[ARRAY_SIZE(song_filename) - 1] = '\0'; song_basename[ARRAY_SIZE(song_basename) - 1] = '\0'; } else { song_filename[0] = '\0'; song_basename[0] = '\0'; } } // clear patterns => clear filename and save flag // clear orderlist => clear title, message, and channel settings void song_new(int flags) { int i; song_lock_audio(); song_stop_unlocked(0); if ((flags & KEEP_PATTERNS) == 0) { song_set_filename(NULL); status.flags &= ~SONG_NEEDS_SAVE; for (i = 0; i < MAX_PATTERNS; i++) { if (current_song->patterns[i]) { csf_free_pattern(current_song->patterns[i]); current_song->patterns[i] = NULL; } current_song->pattern_size[i] = 64; current_song->pattern_alloc_size[i] = 64; } } if ((flags & KEEP_SAMPLES) == 0) { for (i = 1; i < MAX_SAMPLES; i++) { if (current_song->samples[i].data) { csf_free_sample(current_song->samples[i].data); } } memset(current_song->samples, 0, sizeof(current_song->samples)); for (i = 1; i < MAX_SAMPLES; i++) { current_song->samples[i].c5speed = 8363; current_song->samples[i].volume = 64 * 4; current_song->samples[i].global_volume = 64; } } if ((flags & KEEP_INSTRUMENTS) == 0) { for (i = 0; i < MAX_INSTRUMENTS; i++) { if (current_song->instruments[i]) { csf_free_instrument(current_song->instruments[i]); current_song->instruments[i] = NULL; } } } if ((flags & KEEP_ORDERLIST) == 0) { memset(current_song->orderlist, ORDER_LAST, sizeof(current_song->orderlist)); memset(current_song->title, 0, sizeof(current_song->title)); memset(current_song->message, 0, MAX_MESSAGE); for (i = 0; i < 64; i++) { current_song->channels[i].volume = 64; current_song->channels[i].panning = 128; current_song->channels[i].flags = 0; current_song->voices[i].volume = 256; current_song->voices[i].global_volume = current_song->channels[i].volume; current_song->voices[i].panning = current_song->channels[i].panning; current_song->voices[i].flags = current_song->channels[i].flags; current_song->voices[i].cutoff = 0x7F; } } current_song->repeat_count = 0; //song_stop(); csf_forget_history(current_song); song_unlock_audio(); main_song_changed_cb(); } // ------------------------------------------------------------------------------------------------------------ #define LOAD_SONG(x) fmt_##x##_load_song, static fmt_load_song_func load_song_funcs[] = { #include "fmt-types.h" NULL, }; const char *fmt_strerror(int n) { switch (n) { case -LOAD_UNSUPPORTED: return "Unrecognised file type"; case -LOAD_FORMAT_ERROR: return "File format error (corrupt?)"; default: return strerror(errno); } } // IT uses \r in song messages; replace errant \n's static void message_convert_newlines(song_t *song) { int i = 0, len = strlen(song->message); for (i = 0; i < len; i++) { if (song->message[i] == '\n') { song->message[i] = '\r'; } } } song_t *song_create_load(const char *file) { slurp_t s; fmt_load_song_func *func; int ok = 0, err = 0; if (slurp(&s, file, NULL, 0) < 0) return NULL; song_t *newsong = csf_allocate(); if (current_song) { newsong->mix_flags = current_song->mix_flags; csf_set_wave_config(newsong, current_song->mix_frequency, current_song->mix_bits_per_sample, current_song->mix_channels); // loaders might override these newsong->row_highlight_major = current_song->row_highlight_major; newsong->row_highlight_minor = current_song->row_highlight_minor; csf_copy_midi_cfg(newsong, current_song); } for (func = load_song_funcs; *func && !ok; func++) { slurp_rewind(&s); switch ((*func)(newsong, &s, 0)) { case LOAD_SUCCESS: err = 0; ok = 1; break; case LOAD_UNSUPPORTED: err = -LOAD_UNSUPPORTED; continue; case LOAD_FORMAT_ERROR: err = -LOAD_FORMAT_ERROR; break; case LOAD_FILE_ERROR: err = errno; break; } if (err) { csf_free(newsong); unslurp(&s); errno = err; return NULL; } } unslurp(&s); if (err) { // awwww, nerts! csf_free(newsong); errno = err; return NULL; } newsong->stop_at_order = newsong->stop_at_row = -1; message_convert_newlines(newsong); message_reset_selection(); return newsong; } int song_load_unchecked(const char *file) { const char *base = dmoz_path_get_basename(file); int was_playing; song_t *newsong; // IT stops the song even if the new song can't be loaded if (status.flags & PLAY_AFTER_LOAD) { was_playing = (song_get_mode() == MODE_PLAYING); } else { was_playing = 0; song_stop(); } log_nl(); log_appendf(2, "Loading %s", base); log_underline(strlen(base) + 8); newsong = song_create_load(file); if (!newsong) { log_appendf(4, " %s", fmt_strerror(errno)); return 0; } song_set_filename(file); song_lock_audio(); csf_free(current_song); current_song = newsong; current_song->repeat_count = 0; max_channels_used = 0; _fix_names(current_song); song_stop_unlocked(0); song_init_modplug(); song_unlock_audio(); if (was_playing && (status.flags & PLAY_AFTER_LOAD)) song_start(); main_song_changed_cb(); status.flags &= ~SONG_NEEDS_SAVE; // print out some stuff const char *tid = current_song->tracker_id; char fmt[] = " %d patterns, %d samples, %d instruments"; int n, nsmp, nins; song_sample_t *smp; song_instrument_t **ins; for (n = 0, smp = current_song->samples + 1, nsmp = 0; n < MAX_SAMPLES; n++, smp++) if (smp->data) nsmp++; for (n = 0, ins = current_song->instruments + 1, nins = 0; n < MAX_INSTRUMENTS; n++, ins++) if (*ins != NULL) nins++; if (tid[0]) log_appendf(5, " %s", tid); if (!nins) *strrchr(fmt, ',') = 0; // cut off 'instruments' log_appendf(5, fmt, csf_get_num_patterns(current_song), nsmp, nins); return 1; } /* ------------------------------------------------------------------------- */ static int flac_enabled = 0; static int flac_enabled_cb(void) { return !!flac_enabled; } void audio_enable_flac(int enabled) { flac_enabled = !!enabled; } const struct save_format song_save_formats[] = { {"IT", "Impulse Tracker", ".it", {.save_song = fmt_it_save_song}, NULL}, {"S3M", "Scream Tracker 3", ".s3m", {.save_song = fmt_s3m_save_song}, NULL}, {"MOD", "Amiga ProTracker", ".mod", {.save_song = fmt_mod_save_song}, NULL}, {.label = NULL} }; #define EXPORT_FUNCS(t) \ fmt_##t##_export_head, fmt_##t##_export_silence, fmt_##t##_export_body, fmt_##t##_export_tail const struct save_format song_export_formats[] = { {"WAV", "WAV", ".wav", {.export = {EXPORT_FUNCS(wav), 0}}, NULL}, {"MWAV", "WAV multi-write", ".wav", {.export = {EXPORT_FUNCS(wav), 1}}, NULL}, {"AIFF", "Audio IFF", ".aiff", {.export = {EXPORT_FUNCS(aiff), 0}}, NULL}, {"MAIFF", "Audio IFF multi-write", ".aiff", {.export = {EXPORT_FUNCS(aiff), 1}}, NULL}, #ifdef USE_FLAC {"FLAC", "Free Lossless Audio Codec", ".flac", {.export = {EXPORT_FUNCS(flac), 0}}, flac_enabled_cb}, {"MFLAC", "Free Lossless Audio Codec multi-write", ".flac", {.export = {EXPORT_FUNCS(flac)}}, flac_enabled_cb}, #endif {.label = NULL} }; // and maiff sounds like something you'd want to hug // .. dont ask const struct save_format sample_save_formats[] = { {"ITS", "Impulse Tracker", ".its", {.save_sample = fmt_its_save_sample}, NULL}, {"S3I", "Scream Tracker", ".s3i", {.save_sample = fmt_s3i_save_sample}, NULL}, {"AIFF", "Audio IFF", ".aiff", {.save_sample = fmt_aiff_save_sample}, NULL}, {"AU", "Sun/NeXT", ".au", {.save_sample = fmt_au_save_sample}, NULL}, {"WAV", "WAV", ".wav", {.save_sample = fmt_wav_save_sample}, NULL}, #ifdef USE_FLAC {"FLAC", "Free Lossless Audio Codec", ".flac", {.save_sample = fmt_flac_save_sample}, flac_enabled_cb}, #endif {"RAW", "Raw", ".raw", {.save_sample = fmt_raw_save_sample}, NULL}, {.label = NULL} }; const struct save_format instrument_save_formats[] = { {"ITI", "Impulse Tracker", ".iti", {.save_instrument = fmt_iti_save_instrument}, NULL}, {"XI", "Fasttracker II", ".xi", {.save_instrument = fmt_xi_save_instrument}, NULL}, {.label = NULL} }; static const struct save_format *get_save_format(const struct save_format *formats, const char *label) { int n; if (!label) { // why would this happen, ever? log_appendf(4, "No file type given, very weird! (Try a different filename?)"); return NULL; } for (n = 0; formats[n].label; n++) if (strcmp(formats[n].label, label) == 0 && (!formats[n].enabled || formats[n].enabled())) return formats + n; log_appendf(4, "Unknown save format %s", label); return NULL; } static char *mangle_filename(const char *in, const char *mid, const char *ext) { // will always return a valid pointer const char *iext = dmoz_path_get_extension(in); const size_t baselen = iext - in; const size_t midlen = (mid) ? strlen(mid) : 0; const size_t extlen = (*iext) ? strlen(iext) : ((ext) ? strlen(ext) : 0); char *ret = mem_alloc(baselen + midlen + extlen + 1); /* room for terminating \0 */ memcpy(ret, in, baselen); memcpy(ret + baselen, mid, midlen); if (*iext) memcpy(ret + baselen + midlen, iext, extlen); else if (ext) memcpy(ret + baselen + midlen, ext, extlen); ret[baselen + midlen + extlen] = '\0'; return ret; } int song_export(const char *filename, const char *type) { const struct save_format *format = get_save_format(song_export_formats, type); const char *mid; char *mangle; int r; if (!format) return SAVE_INTERNAL_ERROR; mid = (format->f.export.multi && strcasestr(filename, "%c") == NULL) ? ".%c" : NULL; mangle = mangle_filename(filename, mid, format->ext); log_nl(); log_nl(); log_appendf(2, "Exporting to %s", format->name); log_underline(strlen(format->name) + 13); /* disko does the rest of the log messages itself */ r = disko_export_song(mangle, format); free(mangle); switch (r) { case DW_OK: return SAVE_SUCCESS; case DW_ERROR: return SAVE_FILE_ERROR; default: return SAVE_INTERNAL_ERROR; } } int song_save(const char *filename, const char *type) { disko_t fp; int ret, backup; const struct save_format *format = get_save_format(song_save_formats, type); char *mangle; if (!format) return SAVE_INTERNAL_ERROR; mangle = mangle_filename(filename, NULL, format->ext); log_nl(); log_appendf(2, "Saving %s module", format->name); log_underline(strlen(format->name) + 14); /* TODO: add or replace file extension as appropriate From IT 2.10 update: - Automatic filename extension replacement on Ctrl-S, so that if you press Ctrl-S after loading a .MOD, .669, .MTM or .XM, the filename will be automatically modified to have a .IT extension. In IT, if the filename given has no extension ("basename"), then the extension for the proper file type (IT/S3M) will be appended to the name. A filename with an extension is not modified, even if the extension is blank. (as in "basename.") This brings up a rather odd bit of behavior: what happens when saving a file with the deliberately wrong extension? Selecting the IT type and saving as "test.s3m" works just as one would expect: an IT file is written, with the name "test.s3m". No surprises yet, but as soon as Ctrl-S is used, the filename is "fixed", producing a second file called "test.it". The reverse happens when trying to save an S3M named "test.it" -- it's rewritten to "test.s3m". Note that in these scenarios, Impulse Tracker *DOES NOT* check if an existing file by that name exists; it will GLADLY overwrite the old "test.s3m" (or .it) with the renamed file that's being saved. Presumably this is NOT intentional behavior. Another note: if the file could not be saved for some reason or another, Impulse Tracker pops up a dialog saying "Could not save file". This can be seen rather easily by trying to save a file with a malformed name, such as "abc|def.it". This dialog is presented both when saving from F10 and Ctrl-S. */ if (disko_open(&fp, mangle) < 0) { log_perror(mangle); free(mangle); return SAVE_FILE_ERROR; } ret = format->f.save_song(&fp, current_song); if (ret != SAVE_SUCCESS) disko_seterror(&fp, EINVAL); backup = ((status.flags & MAKE_BACKUPS) ? (status.flags & NUMBERED_BACKUPS) ? 65536 : 1 : 0); // this was not as successful as originally claimed! if (disko_close(&fp, backup) == DW_ERROR && ret == SAVE_SUCCESS) ret = SAVE_FILE_ERROR; switch (ret) { case SAVE_SUCCESS: status.flags &= ~SONG_NEEDS_SAVE; if (strcasecmp(song_filename, mangle)) song_set_filename(mangle); log_appendf(5, " Done"); break; case SAVE_FILE_ERROR: log_perror(mangle); break; case SAVE_INTERNAL_ERROR: default: // ??? log_appendf(4, " Internal error saving song"); break; } free(mangle); return ret; } int song_save_sample(const char *filename, const char *type, song_sample_t *smp, int num) { static const char *err = "Error: Sample %d NOT saved! (%s)"; disko_t fp; int ret; const struct save_format *format = get_save_format(sample_save_formats, type); const char *basename = filename ? dmoz_path_get_basename(filename) : NULL; if (!format) return SAVE_INTERNAL_ERROR; if (!filename || !filename[0] || !basename[0]) { ret = SAVE_NO_FILENAME; } else if (disko_open(&fp, filename) >= 0) { ret = format->f.save_sample(&fp, smp); if (ret != SAVE_SUCCESS) disko_seterror(&fp, EINVAL); // this was not as successful as originally claimed! if (disko_close(&fp, 0) == DW_ERROR && ret == SAVE_SUCCESS) ret = SAVE_FILE_ERROR; } else { ret = SAVE_FILE_ERROR; } switch (ret) { case SAVE_SUCCESS: status_text_flash("%s sample saved (sample %d)", format->name, num); break; case SAVE_UNSUPPORTED: status_text_flash(err, num, "Unsupported Data"); break; case SAVE_FILE_ERROR: status_text_flash(err, num, "File Error"); log_perror(basename); break; case SAVE_NO_FILENAME: status_text_flash(err, num, "No Filename?"); break; case SAVE_INTERNAL_ERROR: default: // ??? status_text_flash(err, num, "Internal error"); log_appendf(4, "Internal error saving sample"); break; } return ret; } // ------------------------------------------------------------------------ // All of the sample's fields are initially zeroed except the filename (which is set to the sample's // basename and shouldn't be changed). A sample loader should not change anything in the sample until // it is sure that it can accept the file. // The title points to a buffer of 26 characters. #define LOAD_SAMPLE(x) fmt_##x##_load_sample, static fmt_load_sample_func load_sample_funcs[] = { #include "fmt-types.h" NULL, }; #define LOAD_INSTRUMENT(x) fmt_##x##_load_instrument, static fmt_load_instrument_func load_instrument_funcs[] = { #include "fmt-types.h" NULL, }; void song_clear_sample(int n) { song_lock_audio(); csf_destroy_sample(current_song, n); memset(current_song->samples + n, 0, sizeof(song_sample_t)); current_song->samples[n].c5speed = 8363; current_song->samples[n].volume = 64 * 4; current_song->samples[n].global_volume = 64; song_unlock_audio(); } void song_copy_sample(int n, song_sample_t *src) { memcpy(current_song->samples + n, src, sizeof(song_sample_t)); if (src->data) { unsigned long bytelength = src->length; if (src->flags & CHN_16BIT) bytelength *= 2; if (src->flags & CHN_STEREO) bytelength *= 2; current_song->samples[n].data = csf_allocate_sample(bytelength); memcpy(current_song->samples[n].data, src->data, bytelength); csf_adjust_sample_loop(current_song->samples + n); } } int song_load_instrument_ex(int target, const char *file, const char *libf, int n) { slurp_t s; int r, x; song_lock_audio(); /* 0. delete old samples */ if (current_song->instruments[target]) { int sampmap[MAX_SAMPLES] = {0}; /* init... */ for (unsigned int j = 0; j < 128; j++) { x = current_song->instruments[target]->sample_map[j]; sampmap[x] = 1; } /* mark... */ for (unsigned int q = 0; q < MAX_INSTRUMENTS; q++) { if ((int) q == target) continue; if (!current_song->instruments[q]) continue; for (unsigned int j = 0; j < 128; j++) { x = current_song->instruments[q]->sample_map[j]; sampmap[x] = 0; } } /* sweep! */ for (int j = 1; j < MAX_SAMPLES; j++) { if (!sampmap[j]) continue; csf_destroy_sample(current_song, j); memset(current_song->samples + j, 0, sizeof(current_song->samples[j])); } /* now clear everything "empty" so we have extra slots */ for (int j = 1; j < MAX_SAMPLES; j++) { if (csf_sample_is_empty(current_song->samples + j)) sampmap[j] = 0; } } if (libf) { /* file is ignored */ int sampmap[MAX_SAMPLES] = {0}; song_t *xl = song_create_load(libf); if (!xl) { log_appendf(4, "%s: %s", libf, fmt_strerror(errno)); song_unlock_audio(); return 0; } /* 1. find a place for all the samples */ for (unsigned int j = 0; j < 128; j++) { x = xl->instruments[n]->sample_map[j]; if (!sampmap[x]) { if (x > 0 && x < MAX_INSTRUMENTS) { for (int k = 1; k < MAX_SAMPLES; k++) { if (current_song->samples[k].length) continue; sampmap[x] = k; //song_sample *smp = (song_sample *)song_get_sample(k); for (int c = 0; c < 25; c++) { if (xl->samples[x].name[c] == 0) xl->samples[x].name[c] = 32; } xl->samples[x].name[25] = 0; song_copy_sample(k, &xl->samples[x]); break; } } } } /* transfer the instrument */ current_song->instruments[target] = xl->instruments[n]; xl->instruments[n] = NULL; /* dangle */ /* and rewrite! */ for (unsigned int k = 0; k < 128; k++) { current_song->instruments[target]->sample_map[k] = sampmap[ current_song->instruments[target]->sample_map[k] ]; } song_unlock_audio(); return 1; } /* okay, load an ITI file */ if (slurp(&s, file, NULL, 0) < 0) { log_perror(file); song_unlock_audio(); return 0; } r = 0; for (x = 0; load_instrument_funcs[x]; x++) { slurp_rewind(&s); r = load_instrument_funcs[x](&s, target); if (r) break; } unslurp(&s); song_unlock_audio(); return r; } int song_load_instrument(int n, const char* file) { return song_load_instrument_ex(n,file,NULL,-1); } static void do_enable_inst(SCHISM_UNUSED void* d) { song_set_instrument_mode(1); main_song_changed_cb(); set_page(PAGE_INSTRUMENT_LIST); memused_songchanged(); } static void dont_enable_inst(SCHISM_UNUSED void* d) { set_page(PAGE_INSTRUMENT_LIST); } int song_load_instrument_with_prompt(int n, const char* file) { int retval = song_load_instrument_ex(n, file, NULL, -1); 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); } return retval; } int song_preload_sample(dmoz_file_t *file) { // 0 is our "hidden sample" #define FAKE_SLOT 0 //csf_stop_sample(current_song, current_song->samples + FAKE_SLOT); if (file->sample) { song_sample_t *smp = song_get_sample(FAKE_SLOT); song_lock_audio(); csf_destroy_sample(current_song, FAKE_SLOT); song_copy_sample(FAKE_SLOT, file->sample); strncpy(smp->name, file->title, 25); smp->name[25] = 0; strncpy(smp->filename, file->base, 12); smp->filename[12] = 0; song_unlock_audio(); return FAKE_SLOT; } // WARNING this function must return 0 or KEYJAZZ_NOINST return song_load_sample(FAKE_SLOT, file->path) ? FAKE_SLOT : KEYJAZZ_NOINST; #undef FAKE_SLOT } int song_load_sample(int n, const char *file) { slurp_t s; fmt_load_sample_func *load; song_sample_t smp = {0}; const char *base = dmoz_path_get_basename(file); if (slurp(&s, file, NULL, 0)) { log_perror(base); return 0; } // set some default stuff song_lock_audio(); csf_stop_sample(current_song, current_song->samples + n); strncpy(smp.name, base, 25); for (load = load_sample_funcs; *load; load++) { slurp_rewind(&s); if ((*load)(&s, &smp)) break; } if (!load) { unslurp(&s); log_perror(base); song_unlock_audio(); return 0; } // this is after the loaders because i don't trust them, even though i wrote them ;) strncpy(smp.filename, base, 12); smp.filename[12] = 0; smp.name[25] = 0; csf_destroy_sample(current_song, n); if (((unsigned char)smp.name[23]) == 0xFF) { // don't load embedded samples // (huhwhat?!) smp.name[23] = ' '; } memcpy(&(current_song->samples[n]), &smp, sizeof(song_sample_t)); song_unlock_audio(); unslurp(&s); return 1; } void song_create_host_instrument(int smp) { int ins = instrument_get_current(); if (csf_instrument_is_empty(current_song->instruments[smp])) ins = smp; else if ((status.flags & CLASSIC_MODE) || !csf_instrument_is_empty(current_song->instruments[ins])) ins = csf_first_blank_instrument(current_song, 0); if (ins > 0) { song_init_instrument_from_sample(ins, smp); status_text_flash("Sample assigned to Instrument %d", ins); } else { status_text_flash("Error: No available Instruments!"); } } // ------------------------------------------------------------------------ int song_save_instrument(const char *filename, const char *type, song_instrument_t *ins, int num) { static const char *err = "Error: Instrument %d NOT saved! (%s)"; disko_t fp; int ret; const struct save_format *format = get_save_format(instrument_save_formats, type); const char *basename = filename ? dmoz_path_get_basename(filename) : NULL; if (!format) return SAVE_INTERNAL_ERROR; if (!filename || !filename[0] || !basename[0]) { ret = SAVE_NO_FILENAME; } else if (disko_open(&fp, filename) >= 0) { ret = format->f.save_instrument(&fp, current_song, ins); if (ret != SAVE_SUCCESS) disko_seterror(&fp, EINVAL); // this was not as successful as originally claimed! if (disko_close(&fp, 0) == DW_ERROR && ret == SAVE_SUCCESS) ret = SAVE_FILE_ERROR; } else { ret = SAVE_FILE_ERROR; } switch (ret) { case SAVE_SUCCESS: status_text_flash("%s instrument saved (instrument %d)", format->name, num); break; case SAVE_UNSUPPORTED: status_text_flash(err, num, "Unsupported Data"); break; case SAVE_FILE_ERROR: status_text_flash(err, num, "File Error"); log_perror(basename); break; case SAVE_NO_FILENAME: status_text_flash(err, num, "No Filename?"); break; case SAVE_INTERNAL_ERROR: default: // ??? status_text_flash(err, num, "Internal error"); log_appendf(4, "Internal error saving sample"); break; } return ret; } // ------------------------------------------------------------------------ // song information const char *song_get_filename(void) { return song_filename; } const char *song_get_basename(void) { return song_basename; } // ------------------------------------------------------------------------ // sample library browsing // FIXME: unload the module when leaving the library 'directory' static song_t *library = NULL; // TODO: stat the file? int dmoz_read_instrument_library(const char *path, dmoz_filelist_t *flist, SCHISM_UNUSED dmoz_dirlist_t *dlist) { unsigned int j; int x; // FIXME why does this do this ? seems to be a no-op csf_stop_sample(current_song, current_song->samples + 0); if (library) { csf_destroy(library); library = NULL; } const char *base = dmoz_path_get_basename(path); library = song_create_load(path); if (!library) { log_appendf(4, "%s: %s", base, fmt_strerror(errno)); return -1; } for (int n = 1; n < MAX_INSTRUMENTS; n++) { if (!library->instruments[n]) continue; dmoz_file_t *file = dmoz_add_file(flist, str_dup(path), str_dup(base), NULL, n); file->title = str_dup(library->instruments[n]->name); int count[128] = {0}; file->sampsize = 0; file->filesize = 0; file->instnum = n; for (j = 0; j < 128; j++) { x = library->instruments[n]->sample_map[j]; if (!count[x]) { if (x > 0 && x < MAX_INSTRUMENTS) { file->filesize += library->samples[x].length; file->sampsize++; } } count[x]++; } file->type = TYPE_INST_ITI; file->description = "Fishcakes"; // IT doesn't support this, despite it being useful. // Simply "unrecognized" } return 0; } int dmoz_read_sample_library(const char *path, dmoz_filelist_t *flist, SCHISM_UNUSED dmoz_dirlist_t *dlist) { csf_stop_sample(current_song, current_song->samples + 0); if (library) { csf_destroy(library); library = NULL; } const char *base = dmoz_path_get_basename(path); struct stat st; if (os_stat(path, &st) < 0) { log_perror(path); return -1; } /* ask dmoz what type of file we have * FIXME use slurp and read info funcs manually */ dmoz_file_t info_file = {0}; info_file.path = str_dup(path); info_file.filesize = st.st_size; dmoz_fill_ext_data(&info_file); /* free extra data we don't need */ if (info_file.smp_filename != info_file.base && info_file.smp_filename != info_file.title) { free(info_file.smp_filename); } free(info_file.path); free(info_file.base); if (info_file.type & TYPE_EXT_DATA_MASK) { if (info_file.artist) free(info_file.artist); free(info_file.title); } if (info_file.type & TYPE_MODULE_MASK) { library = song_create_load(path); } else if (info_file.type & TYPE_INST_MASK) { /* temporarily set the current song to the library; song_load_instrument * is hardcoded to it */ song_lock_audio(); song_t *tmp_ptr = current_song; library = current_song = csf_allocate(); int ret = song_load_instrument(1, path); current_song = tmp_ptr; song_unlock_audio(); if (!ret) { log_appendf(4, "song_load_instrument: %s failed with %d", path, ret); return 1; } } else { return 0; } for (int n = 1; n < MAX_SAMPLES; n++) { if (library->samples[n].length) { for (int c = 0; c < 25; c++) { if (library->samples[n].name[c] == 0) library->samples[n].name[c] = 32; library->samples[n].name[25] = 0; } dmoz_file_t *file = dmoz_add_file(flist, str_dup(path), str_dup(base), NULL, n); file->type = TYPE_SAMPLE_EXTD; file->description = "Impulse Tracker Sample"; /* FIXME: this lies for XI and PAT */ file->filesize = library->samples[n].length*((library->samples[n].flags & CHN_STEREO) + 1)*((library->samples[n].flags & CHN_16BIT) + 1); file->smp_speed = library->samples[n].c5speed; file->smp_loop_start = library->samples[n].loop_start; file->smp_loop_end = library->samples[n].loop_end; file->smp_sustain_start = library->samples[n].sustain_start; file->smp_sustain_end = library->samples[n].sustain_end; file->smp_length = library->samples[n].length; file->smp_flags = library->samples[n].flags; file->smp_defvol = library->samples[n].volume>>2; file->smp_gblvol = library->samples[n].global_volume; file->smp_vibrato_speed = library->samples[n].vib_speed; file->smp_vibrato_depth = library->samples[n].vib_depth; file->smp_vibrato_rate = library->samples[n].vib_rate; // don't screw this up... if (((unsigned char)library->samples[n].name[23]) == 0xFF) { library->samples[n].name[23] = ' '; } file->title = str_dup(library->samples[n].name); file->sample = (song_sample_t *) library->samples + n; } } return 0; } // ------------------------------------------------------------------------ // instrument loader song_instrument_t *instrument_loader_init(struct instrumentloader *ii, int slot) { ii->expect_samples = 0; ii->inst = song_get_instrument(slot); ii->slot = slot; ii->basex = 1; memset(ii->sample_map, 0, sizeof(ii->sample_map)); return ii->inst; } int instrument_loader_abort(struct instrumentloader *ii) { int n; song_wipe_instrument(ii->slot); for (n = 0; n < MAX_SAMPLES; n++) { if (ii->sample_map[n]) { song_clear_sample(ii->sample_map[n]-1); ii->sample_map[n] = 0; } } return 0; } int instrument_loader_sample(struct instrumentloader *ii, int slot) { int x; if (!slot) return 0; if (ii->sample_map[slot]) return ii->sample_map[slot]; for (x = ii->basex; x < MAX_SAMPLES; x++) { song_sample_t *cur = (current_song->samples + x); if (cur->data != NULL) continue; ii->expect_samples++; ii->sample_map[slot] = x; ii->basex = x + 1; return ii->sample_map[slot]; } status_text_flash("Too many samples"); return 0; } schismtracker-20250313/schism/audio_playback.c000066400000000000000000001314721476471630300212410ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "config.h" #include "page.h" #include "song.h" #include "slurp.h" #include "config-parser.h" #include "mem.h" #include "str.h" #include "disko.h" #include "backend/audio.h" #include "events.h" #include #include "midi.h" #include "player/cmixer.h" #include "player/sndfile.h" #include "player/snd_fm.h" #include "player/snd_gm.h" // Default audio configuration #define DEF_SAMPLE_RATE 48000 #ifdef SCHISM_WIN32 // uhhh... why? # define DEF_BUFFER_SIZE 2048 #else # define DEF_BUFFER_SIZE 1024 #endif #define DEF_CHANNEL_LIMIT 128 // ------------------------------------------------------------------------ #define SMP_INIT (UINT_MAX - 1) /* for a click noise on init */ unsigned int samples_played = 0; unsigned int max_channels_used = 0; signed short *audio_buffer = NULL; unsigned int audio_buffer_samples = 0; /* multiply by audio_sample_size to get bytes */ unsigned int audio_output_channels = 2; unsigned int audio_output_bits = 16; static unsigned int audio_sample_size; static int audio_buffers_per_second = 0; static int audio_writeout_count = 0; struct audio_settings audio_settings = {0}; static void _schism_midi_out_raw(song_t *csf, const unsigned char *data, uint32_t len, uint32_t delay); /* Audio driver related stuff */ /* XXX how much of this is really needed now? */ /* The (short) name of the SDL driver in use, e.g. "alsa" */ static char *driver_name = NULL; static char *device_name = NULL; static uint32_t device_id = 0; /* Whatever was in the config file. This is used if no driver is given to audio_setup. */ static char cfg_audio_driver[256] = { 0 }; static char cfg_audio_device[256] = { 0 }; // ------------------------------------------------------------------------ struct audio_device* audio_device_list = NULL; size_t audio_device_list_size = 0; static schism_audio_device_t *current_audio_device = NULL; // compiled backends static const schism_audio_backend_t *backends[] = { // ordered by preference #ifdef SCHISM_MACOSX &schism_audio_backend_macosx, #endif #ifdef SCHISM_SDL3 &schism_audio_backend_sdl3, #endif #ifdef SCHISM_SDL2 &schism_audio_backend_sdl2, #endif #ifdef SCHISM_WIN32 &schism_audio_backend_dsound, &schism_audio_backend_waveout, #endif #ifdef SCHISM_SDL12 &schism_audio_backend_sdl12, #endif NULL, }; // A list of all currently initialized backends static const schism_audio_backend_t *inited_backends[ARRAY_SIZE(backends) - 1] = {0}; static const schism_audio_backend_t *backend = NULL; // ------------------------------------------------------------------------ // playback // page_patedit.c extern int midi_bend_hit[64], midi_last_bend_hit[64]; // this gets called from the backend static void audio_callback(uint8_t *stream, int len) { uint32_t wasrow = current_song->row; uint32_t waspat = current_song->current_order; int i, n; memset(stream, 0, len); if (!stream || !len || !current_song) { if (status.current_page == PAGE_WATERFALL || status.vis_style == VIS_FFT) { vis_work_8m(NULL, 0); } song_stop_unlocked(0); goto POST_EVENT; } if (samples_played >= SMP_INIT) { memset(stream, 0x80, len); samples_played++; // will loop back to 0 return; } if (current_song->flags & SONG_ENDREACHED) { n = 0; } else { n = csf_read(current_song, stream, len); if (!n) { if (status.current_page == PAGE_WATERFALL || status.vis_style == VIS_FFT) vis_work_8m(NULL, 0); song_stop_unlocked(0); goto POST_EVENT; } samples_played += n; } memcpy(audio_buffer, stream, n * audio_sample_size); /* convert 8-bit unsigned to signed by XORing the high bit */ if (audio_output_bits == 8) for (i = 0; i < n * 2; i++) audio_buffer[i] ^= 0x80; if (status.current_page == PAGE_WATERFALL || status.vis_style == VIS_FFT) { // I don't really like this... switch (audio_output_bits) { #define BITSCASE(BITS) case BITS: if (audio_output_channels == 2) { vis_work_##BITS##s((void *)audio_buffer, n / 2); } else { vis_work_##BITS##m((void *)audio_buffer, n); } break; BITSCASE(8) BITSCASE(16) BITSCASE(32) #undef BITSCASE } } if (current_song->num_voices > max_channels_used) max_channels_used = MIN(current_song->num_voices, current_song->max_voices); POST_EVENT: audio_writeout_count++; if (audio_writeout_count > audio_buffers_per_second) { audio_writeout_count = 0; } else if (waspat == current_song->current_order && wasrow == current_song->row && !midi_need_flush()) { /* skip it */ return; } /* send at end */ schism_event_t e = { .type = SCHISM_EVENT_PLAYBACK, }; events_push_event(&e); } // ------------------------------------------------------------------------------------------------------------ // audio device list void free_audio_device_list(void) { for (size_t count = 0; count < audio_device_list_size; count++) free(audio_device_list[count].name); free(audio_device_list); audio_device_list = NULL; audio_device_list_size = 0; } /* called when SCHISM_AUDIODEVICEADDED/SCHISM_AUDIODEVICEREMOVED event received */ int refresh_audio_device_list(void) { free_audio_device_list(); const uint32_t count = backend ? backend->device_count() : 0; audio_device_list = malloc(count * sizeof(*audio_device_list)); if (!audio_device_list) return 0; for (uint32_t i = 0; i < count; i++) { audio_device_list[i].id = i; audio_device_list[i].name = str_dup(backend ? backend->device_name(i) : ""); } audio_device_list_size = count; return 1; } // ------------------------------------------------------------------------------------------------------------ // drivers // Created when audio_init is called for the first time static struct { size_t size; struct _audio_driver { const schism_audio_backend_t *backend; const char *name; } *list; } full_drivers = {0}; static void _audio_create_drivers_list(void) { // This is here so we can skip known duplicate drivers // that were renamed in SDL 2, and also for re-ordering // the drivers after the fact enum { // "pulse" in SDL 1.2, "pulseaudio" in SDL 2 DRIVER_PULSE = 0x01, // should always be at the end DRIVER_DISK = 0x02, DRIVER_DUMMY = 0x04, }; uint32_t drivers = 0; struct _audio_driver disk = {.name = "disk"}, dummy = {.name = "dummy"}; // Reset the drivers list full_drivers.list = NULL; full_drivers.size = 0; size_t alloc_size = 0; int counts[ARRAY_SIZE(inited_backends)] = {0}; for (size_t i = 0; i < ARRAY_SIZE(counts); i++) alloc_size += (counts[i] = (inited_backends[i] ? inited_backends[i]->driver_count() : 0)); full_drivers.list = mem_alloc(alloc_size * sizeof(*full_drivers.list)); for (size_t i = 0; i < ARRAY_SIZE(counts); i++) { for (int j = 0; j < counts[i]; j++) { const char *n = inited_backends[i]->driver_name(j); // Skip known duplicate drivers if (!strcmp(n, "pulse") || !strcmp(n, "pulseaudio")) { if (drivers & DRIVER_PULSE) continue; drivers |= DRIVER_PULSE; } else if (!strcmp(n, "dummy")) { drivers |= DRIVER_DUMMY; dummy.backend = inited_backends[i]; continue; } else if (!strcmp(n, "disk")) { drivers |= DRIVER_DISK; disk.backend = inited_backends[i]; continue; } else if (!strcmp(n, "winmm") || !strcmp(n, "directsound")) { // Skip SDL 2 waveout driver; we have our own implementation // and the SDL 2's driver seems to randomly want to hang on // exit // We also skip SDL2's directsound driver since it only // supports DirectX 8 and above, while our builtin driver // supports DirectX 5 and above. continue; } // Skip any drivers with a name already in the list { int fnd = 0; for (size_t k = 0; k < full_drivers.size; k++) { if (!strcmp(n, full_drivers.list[k].name)) { fnd = 1; break; } } if (fnd) continue; } full_drivers.list[full_drivers.size].name = n; full_drivers.list[full_drivers.size].backend = inited_backends[i]; full_drivers.size++; } } if (drivers & DRIVER_DISK) full_drivers.list[full_drivers.size++] = disk; if (drivers & DRIVER_DUMMY) full_drivers.list[full_drivers.size++] = dummy; } int audio_driver_count(void) { return full_drivers.size; } const char *audio_driver_name(int x) { if ((size_t)x >= full_drivers.size || x < 0) return NULL; return full_drivers.list[x].name; } // ------------------------------------------------------------------------------------------------------------ // note playing /* this should be in page.c; the audio handling code doesn't need to know what a page is, much less be talking to them */ static void main_song_mode_changed_cb(void) { int n; for (n = 0; n < PAGE_MAX; n++) { if (pages[n].song_mode_changed_cb) pages[n].song_mode_changed_cb(); } } static int current_play_channel = 1; static int multichannel_mode = 0; int song_get_current_play_channel(void) { return current_play_channel; } void song_change_current_play_channel(int relative, int wraparound) { current_play_channel += relative; if (wraparound) { if (current_play_channel < 1) current_play_channel = 64; else if (current_play_channel > 64) current_play_channel = 1; } else { current_play_channel = CLAMP(current_play_channel, 1, 64); } status_text_flash("Using channel %d for playback", current_play_channel); } void song_toggle_multichannel_mode(void) { multichannel_mode = !multichannel_mode; status_text_flash("Multichannel playback %s", (multichannel_mode ? "enabled" : "disabled")); } int song_is_multichannel_mode(void) { return multichannel_mode; } /* Channel corresponding to each note played. That is, keyjazz_note_to_chan[66] will indicate in which channel F-5 was played most recently. This will break if the same note was keydown'd twice without a keyup, but I think that's a fairly unlikely scenario that you'd have to TRY to bring about. */ static int keyjazz_note_to_chan[NOTE_LAST + 1] = {0}; /* last note played by channel tracking */ static int keyjazz_chan_to_note[MAX_CHANNELS + 1] = {0}; /* **** chan ranges from 1 to MAX_CHANNELS */ static int song_keydown_ex(int samp, int ins, int note, int vol, int chan, int effect, int param) { int ins_mode; int midi_note = note; /* note gets overwritten, possibly NOTE_NONE */ song_voice_t *c; song_sample_t *s = NULL; song_instrument_t *i = NULL; switch (chan) { case KEYJAZZ_CHAN_CURRENT: chan = current_play_channel; if (multichannel_mode) song_change_current_play_channel(1, 1); break; case KEYJAZZ_CHAN_AUTO: if (multichannel_mode) { chan = current_play_channel; song_change_current_play_channel(1, 1); } else { for (chan = 1; chan < MAX_CHANNELS; chan++) if (!keyjazz_chan_to_note[chan]) break; } break; default: break; } // back to the internal range int chan_internal = chan - 1; // hm assert(chan_internal < MAX_CHANNELS); song_lock_audio(); c = current_song->voices + chan_internal; ins_mode = song_is_instrument_mode(); if (NOTE_IS_NOTE(note)) { // keep track of what channel this note was played in so we can note-off properly later if (keyjazz_chan_to_note[chan]) { // reset note-off pending state for last note in channel keyjazz_note_to_chan[keyjazz_chan_to_note[chan]] = 0; } keyjazz_note_to_chan[note] = chan; keyjazz_chan_to_note[chan] = note; // handle blank instrument values and "fake" sample #0 (used by sample loader) if (samp == 0) samp = c->last_instrument; else if (samp == KEYJAZZ_INST_FAKE) samp = 0; // dumb hack if (ins == 0) ins = c->last_instrument; else if (ins == KEYJAZZ_INST_FAKE) ins = 0; // dumb hack c->last_instrument = ins_mode ? ins : samp; // give the channel a sample, and maybe an instrument s = (samp == KEYJAZZ_NOINST) ? NULL : current_song->samples + samp; i = (ins == KEYJAZZ_NOINST) ? NULL : song_get_instrument(ins); // blah if (i && samp == KEYJAZZ_NOINST) { // we're playing an instrument and don't know what sample! WHAT WILL WE EVER DO?! // well, look it up in the note translation table, silly. // the weirdness here the default value here is to mimic IT behavior: we want to use // the sample corresponding to the instrument number if in sample mode and no sample // is defined for the note in the instrument's note map. s = csf_translate_keyboard(current_song, i, note, ins_mode ? NULL : (current_song->samples + ins)); } } c->row_effect = effect; c->row_param = param; // now do a rough equivalent of csf_instrument_change and csf_note_change if (i) csf_check_nna(current_song, chan_internal, ins, note, 0); if (s) { if (c->flags & CHN_ADLIB) { OPL_NoteOff(current_song, chan_internal); OPL_Patch(current_song, chan_internal, s->adlib_bytes); } c->flags = (s->flags & CHN_SAMPLE_FLAGS) | (c->flags & CHN_MUTE); if (c->flags & CHN_MUTE) { c->flags |= CHN_NNAMUTE; } c->cutoff = 0x7f; c->resonance = 0; if (i) { c->ptr_instrument = i; if (!(i->flags & ENV_VOLCARRY)) c->vol_env_position = 0; if (!(i->flags & ENV_PANCARRY)) c->pan_env_position = 0; if (!(i->flags & ENV_PITCHCARRY)) c->pitch_env_position = 0; if (i->flags & ENV_VOLUME) c->flags |= CHN_VOLENV; if (i->flags & ENV_PANNING) c->flags |= CHN_PANENV; if (i->flags & ENV_PITCH) c->flags |= CHN_PITCHENV; i->played = 1; if ((status.flags & MIDI_LIKE_TRACKER) && i) { if (i->midi_channel_mask) { GM_KeyOff(current_song, chan_internal); GM_DPatch(current_song, chan_internal, i->midi_program, i->midi_bank, i->midi_channel_mask); } } if (i->ifc & 0x80) c->cutoff = i->ifc & 0x7f; if (i->ifr & 0x80) c->resonance = i->ifr & 0x7f; //? c->vol_swing = i->vol_swing; c->pan_swing = i->pan_swing; c->nna = i->nna; } else { c->ptr_instrument = NULL; c->cutoff = 0x7f; c->resonance = 0; } c->master_channel = 0; // indicates foreground channel. //c->flags &= ~(CHN_PINGPONGFLAG); // ? //c->autovib_depth = 0; //c->autovib_position = 0; // csf_note_change copies stuff from c->ptr_sample as long as c->length is zero // and if period != 0 (ie. sample not playing at a stupid rate) c->ptr_sample = s; c->length = 0; // ... but it doesn't copy the volumes, for somewhat obvious reasons. c->volume = (vol == KEYJAZZ_DEFAULTVOL) ? s->volume : (((unsigned) vol) << 2); c->instrument_volume = s->global_volume; if (i) c->instrument_volume = (c->instrument_volume * i->global_volume) >> 7; c->global_volume = 64; // use the sample's panning if it's set, or use the default c->channel_panning = (int16_t)(c->panning + 1); if (c->flags & CHN_SURROUND) c->channel_panning |= 0x8000; c->panning = (s->flags & CHN_PANNING) ? s->panning : 128; if (i) c->panning = (i->flags & CHN_PANNING) ? i->panning : 128; c->flags &= ~CHN_SURROUND; // gotta set these by hand, too c->c5speed = s->c5speed; c->new_note = note; s->played = 1; } else if (NOTE_IS_NOTE(note)) { // Note given with no sample number. This might happen if on the instrument list and playing // an instrument that has no sample mapped for the given note. In this case, ignore the note. note = NOTE_NONE; } if (c->increment < 0) c->increment = -c->increment; // lousy hack csf_note_change(current_song, chan_internal, note, 0, 0, 1); if (!(status.flags & MIDI_LIKE_TRACKER) && i) { /* midi keyjazz shouldn't require a sample */ song_note_t mc = {0}; mc.note = note ? note : midi_note; mc.instrument = ins; mc.voleffect = VOLFX_VOLUME; mc.volparam = vol; mc.effect = effect; mc.param = param; csf_midi_out_note(current_song, chan_internal, &mc); } /* TODO: - If this is the ONLY channel playing, and the song is stopped, always reset the tick count (will fix the "random" behavior for most effects) - If other channels are playing, don't reset the tick count, but do process first-tick effects for this note *right now* (this will fix keyjamming with effects like Oxx and SCx) - Need to handle volume column effects with this function... */ if (current_song->flags & SONG_ENDREACHED) { current_song->flags &= ~SONG_ENDREACHED; current_song->flags |= SONG_PAUSED; } song_unlock_audio(); return chan; } int song_keydown(int samp, int ins, int note, int vol, int chan) { return song_keydown_ex(samp, ins, note, vol, chan, FX_PANNING, 0x80); } int song_keyrecord(int samp, int ins, int note, int vol, int chan, int effect, int param) { return song_keydown_ex(samp, ins, note, vol, chan, effect, param); } int song_keyup(int samp, int ins, int note) { int chan = keyjazz_note_to_chan[note]; if (!chan) { // could not find channel, drop. return -1; }; return song_keyup_channel(samp, ins, note, chan); } int song_keyup_channel(int samp, int ins, int note, int chan) { if (keyjazz_chan_to_note[chan] != note) { return -1; } keyjazz_chan_to_note[chan] = 0; keyjazz_note_to_chan[note] = 0; return song_keydown_ex(samp, ins, NOTE_OFF, KEYJAZZ_DEFAULTVOL, chan, 0, 0); } void song_single_step(int patno, int row) { int total_rows; int i, vol, smp, ins; song_note_t *pattern, *cur_note; song_voice_t *cx; total_rows = song_get_pattern(patno, &pattern); if (!pattern || row >= total_rows) return; cur_note = pattern + 64 * row; cx = song_get_mix_channel(0); for (i = 1; i <= 64; i++, cx++, cur_note++) { if (cx && (cx->flags & CHN_MUTE)) continue; /* ick */ if (cur_note->voleffect == VOLFX_VOLUME) { vol = cur_note->volparam; } else { vol = KEYJAZZ_DEFAULTVOL; } // look familiar? this is modified slightly from pattern_editor_insert // (and it is wrong for the same reason as described there) smp = ins = cur_note->instrument; if (song_is_instrument_mode()) { if (ins < 1) ins = KEYJAZZ_NOINST; smp = -1; } else { if (smp < 1) smp = KEYJAZZ_NOINST; ins = -1; } song_keyrecord(smp, ins, cur_note->note, vol, i, cur_note->effect, cur_note->param); } } // ------------------------------------------------------------------------------------------------------------ // this should be called with the audio LOCKED static void song_reset_play_state(void) { memset(midi_bend_hit, 0, sizeof(midi_bend_hit)); memset(midi_last_bend_hit, 0, sizeof(midi_last_bend_hit)); memset(keyjazz_note_to_chan, 0, sizeof(keyjazz_note_to_chan)); memset(keyjazz_chan_to_note, 0, sizeof(keyjazz_chan_to_note)); // turn this crap off current_song->mix_flags &= ~(SNDMIX_NOBACKWARDJUMPS | SNDMIX_DIRECTTODISK); OPL_Reset(current_song); /* gruh? */ csf_set_current_order(current_song, 0); current_song->repeat_count = 0; current_song->buffer_count = 0; current_song->flags &= ~(SONG_PAUSED | SONG_PATTERNLOOP | SONG_ENDREACHED); current_song->stop_at_order = -1; current_song->stop_at_row = -1; samples_played = 0; } void song_start_once(void) { song_lock_audio(); song_reset_play_state(); current_song->mix_flags |= SNDMIX_NOBACKWARDJUMPS; max_channels_used = 0; current_song->repeat_count = -1; // FIXME do this right GM_SendSongStartCode(current_song); song_unlock_audio(); main_song_mode_changed_cb(); csf_reset_playmarks(current_song); } void song_start(void) { song_lock_audio(); song_reset_play_state(); max_channels_used = 0; GM_SendSongStartCode(current_song); song_unlock_audio(); main_song_mode_changed_cb(); csf_reset_playmarks(current_song); } void song_pause(void) { song_lock_audio(); // Highly unintuitive, but SONG_PAUSED has nothing to do with pause. if (!(current_song->flags & SONG_PAUSED)) current_song->flags ^= SONG_ENDREACHED; song_unlock_audio(); main_song_mode_changed_cb(); } void song_stop(void) { song_lock_audio(); song_stop_unlocked(0); song_unlock_audio(); main_song_mode_changed_cb(); } void song_stop_unlocked(int quitting) { if (!current_song) return; if (current_song->midi_playing) { unsigned char moff[4]; /* shut off everything; not IT like, but less annoying */ for (int chan = 0; chan < 64; chan++) { if (current_song->midi_note_tracker[chan] != 0) { for (int j = 0; j < MAX_MIDI_CHANNELS; j++) { csf_process_midi_macro(current_song, chan, current_song->midi_config.note_off, 0, current_song->midi_note_tracker[chan], 0, j); } moff[0] = 0x80 + chan; moff[1] = current_song->midi_note_tracker[chan]; csf_midi_send(current_song, (unsigned char *) moff, 2, 0, 0); } } for (int j = 0; j < MAX_MIDI_CHANNELS; j++) { moff[0] = 0xe0 + j; moff[1] = 0; csf_midi_send(current_song, (unsigned char *) moff, 2, 0, 0); moff[0] = 0xb0 + j; /* channel mode message */ moff[1] = 0x78; /* all sound off */ moff[2] = 0; csf_midi_send(current_song, (unsigned char *) moff, 3, 0, 0); moff[1] = 0x79; /* reset all controllers */ csf_midi_send(current_song, (unsigned char *) moff, 3, 0, 0); moff[1] = 0x7b; /* all notes off */ csf_midi_send(current_song, (unsigned char *) moff, 3, 0, 0); } csf_process_midi_macro(current_song, 0, current_song->midi_config.stop, 0, 0, 0, 0); // STOP! midi_send_flush(); // NOW! current_song->midi_playing = 0; } OPL_Reset(current_song); /* Also stop all OPL sounds */ GM_Reset(current_song, quitting); GM_SendSongStopCode(current_song); memset(current_song->midi_last_row,0,sizeof(current_song->midi_last_row)); current_song->midi_last_row_number = -1; memset(current_song->midi_note_tracker,0,sizeof(current_song->midi_note_tracker)); memset(current_song->midi_vol_tracker,0,sizeof(current_song->midi_vol_tracker)); memset(current_song->midi_ins_tracker,0,sizeof(current_song->midi_ins_tracker)); memset(current_song->midi_was_program,0,sizeof(current_song->midi_was_program)); memset(current_song->midi_was_banklo,0,sizeof(current_song->midi_was_banklo)); memset(current_song->midi_was_bankhi,0,sizeof(current_song->midi_was_bankhi)); playback_tracing = midi_playback_tracing; song_reset_play_state(); // Modplug doesn't actually have a "stop" mode, but if SONG_ENDREACHED is set, csf_read just returns. current_song->flags |= SONG_PAUSED | SONG_ENDREACHED; current_song->vu_left = 0; current_song->vu_right = 0; memset(audio_buffer, 0, audio_buffer_samples * audio_sample_size); } void song_loop_pattern(int pattern, int row) { song_lock_audio(); song_reset_play_state(); max_channels_used = 0; csf_loop_pattern(current_song, pattern, row); GM_SendSongStartCode(current_song); song_unlock_audio(); main_song_mode_changed_cb(); csf_reset_playmarks(current_song); } void song_start_at_order(int order, int row) { song_lock_audio(); song_reset_play_state(); csf_set_current_order(current_song, order); current_song->break_row = row; max_channels_used = 0; GM_SendSongStartCode(current_song); /* TODO: GM_SendSongPositionCode(calculate the number of 1/16 notes) */ song_unlock_audio(); main_song_mode_changed_cb(); csf_reset_playmarks(current_song); } void song_start_at_pattern(int pattern, int row) { if (pattern < 0 || pattern > 199) return; int n = song_next_order_for_pattern(pattern); if (n > -1) { song_start_at_order(n, row); return; } song_loop_pattern(pattern, row); } // ------------------------------------------------------------------------ // info on what's playing enum song_mode song_get_mode(void) { if ((current_song->flags & (SONG_ENDREACHED | SONG_PAUSED)) == (SONG_ENDREACHED | SONG_PAUSED)) return MODE_STOPPED; if (current_song->flags & SONG_PAUSED) return MODE_SINGLE_STEP; if (current_song->flags & SONG_PATTERNPLAYBACK) return MODE_PATTERN_LOOP; return MODE_PLAYING; } // returned value is in seconds unsigned int song_get_current_time(void) { return samples_played / current_song->mix_frequency; } int song_get_current_tick(void) { return current_song->tick_count % current_song->current_speed; } int song_get_current_speed(void) { return current_song->current_speed; } void song_set_current_tempo(int new_tempo) { song_lock_audio(); current_song->current_tempo = CLAMP(new_tempo, 31, 255); song_unlock_audio(); } int song_get_current_tempo(void) { return current_song->current_tempo; } int song_get_current_global_volume(void) { return current_song->current_global_volume; } int song_get_current_order(void) { return current_song->current_order; } int song_get_playing_pattern(void) { return current_song->current_pattern; } int song_get_current_row(void) { return current_song->row; } int song_get_playing_channels(void) { return MIN(current_song->num_voices, current_song->max_voices); } int song_get_max_channels(void) { return max_channels_used; } // Returns the max value in dBs, scaled as 0 = -40dB and 128 = 0dB. void song_get_vu_meter(int *left, int *right) { *left = dB_s(40, current_song->vu_left/256.f, 0.f); *right = dB_s(40, current_song->vu_right/256.f, 0.f); } void song_update_playing_instrument(int i_changed) { song_voice_t *channel; song_instrument_t *inst; song_lock_audio(); int n = MIN(current_song->num_voices, current_song->max_voices); while (n--) { channel = current_song->voices + current_song->voice_mix[n]; if (channel->ptr_instrument && channel->ptr_instrument == current_song->instruments[i_changed]) { csf_instrument_change(current_song, channel, i_changed, 1, 0); inst = channel->ptr_instrument; if (!inst) continue; /* special cases; mpt doesn't do this if porta-enabled, */ if (inst->ifr & 0x80) { channel->resonance = inst->ifr & 0x7F; } else { channel->resonance = 0; channel->flags &= (~CHN_FILTER); } if (inst->ifc & 0x80) { channel->cutoff = inst->ifc & 0x7F; setup_channel_filter(channel, 0, 256, current_song->mix_frequency); } else { channel->cutoff = 0x7F; if (inst->ifr & 0x80) { setup_channel_filter(channel, 0, 256, current_song->mix_frequency); } } /* flip direction */ channel->flags &= (~CHN_PINGPONGFLAG); } } song_unlock_audio(); } void song_update_playing_sample(int s_changed) { song_voice_t *channel; song_sample_t *inst; song_lock_audio(); int n = MIN(current_song->num_voices, current_song->max_voices); while (n--) { channel = current_song->voices + current_song->voice_mix[n]; if (channel->ptr_sample && channel->current_sample_data) { int s = channel->ptr_sample - current_song->samples; if (s != s_changed) continue; inst = channel->ptr_sample; if (inst->flags & (CHN_PINGPONGSUSTAIN|CHN_SUSTAINLOOP)) { channel->loop_start = inst->sustain_start; channel->loop_end = inst->sustain_end; } else if (inst->flags & (CHN_PINGPONGFLAG|CHN_PINGPONGLOOP|CHN_LOOP)) { channel->loop_start = inst->loop_start; channel->loop_end = inst->loop_end; } if (inst->flags & (CHN_PINGPONGSUSTAIN | CHN_SUSTAINLOOP | CHN_PINGPONGFLAG | CHN_PINGPONGLOOP|CHN_LOOP)) { if (channel->length != channel->loop_end) { channel->length = channel->loop_end; } } if (channel->length > inst->length) { channel->current_sample_data = inst->data; channel->length = inst->length; } channel->flags &= ~(CHN_PINGPONGSUSTAIN | CHN_PINGPONGLOOP | CHN_PINGPONGFLAG | CHN_SUSTAINLOOP | CHN_LOOP); channel->flags |= inst->flags & (CHN_PINGPONGSUSTAIN | CHN_PINGPONGLOOP | CHN_PINGPONGFLAG | CHN_SUSTAINLOOP | CHN_LOOP); channel->instrument_volume = inst->global_volume; } } song_unlock_audio(); } void song_get_playing_samples(int samples[]) { song_voice_t *channel; memset(samples, 0, MAX_SAMPLES * sizeof(int)); song_lock_audio(); int n = MIN(current_song->num_voices, current_song->max_voices); while (n--) { channel = current_song->voices + current_song->voice_mix[n]; if (channel->ptr_sample && channel->current_sample_data) { int s = channel->ptr_sample - current_song->samples; if (s >= 0 && s < MAX_SAMPLES) { samples[s] = MAX(samples[s], 1 + channel->strike); } } else { // no sample. // (when does this happen?) } } song_unlock_audio(); } void song_get_playing_instruments(int instruments[]) { song_voice_t *channel; memset(instruments, 0, MAX_INSTRUMENTS * sizeof(int)); song_lock_audio(); int n = MIN(current_song->num_voices, current_song->max_voices); while (n--) { channel = current_song->voices + current_song->voice_mix[n]; int ins = song_get_instrument_number((song_instrument_t *) channel->ptr_instrument); if (ins > 0 && ins < MAX_INSTRUMENTS) { instruments[ins] = MAX(instruments[ins], 1 + channel->strike); } } song_unlock_audio(); } // ------------------------------------------------------------------------ // changing the above info void song_set_current_speed(int speed) { if (speed < 1 || speed > 255) return; song_lock_audio(); current_song->current_speed = speed; song_unlock_audio(); } void song_set_current_global_volume(int volume) { if (volume < 0 || volume > 128) return; song_lock_audio(); current_song->current_global_volume = volume; song_unlock_audio(); } void song_set_current_order(int order) { song_lock_audio(); csf_set_current_order(current_song, order); song_unlock_audio(); } // Ctrl-F7 void song_set_next_order(int order) { song_lock_audio(); current_song->process_order = order - 1; song_unlock_audio(); } // Alt-F11 int song_toggle_orderlist_locked(void) { current_song->flags ^= SONG_ORDERLOCKED; return current_song->flags & SONG_ORDERLOCKED; } // ------------------------------------------------------------------------ // global flags void song_flip_stereo(void) { current_song->mix_flags ^= SNDMIX_REVERSESTEREO; } int song_get_surround(void) { return (current_song->mix_flags & SNDMIX_NOSURROUND) ? 0 : 1; } void song_set_surround(int on) { if (on) current_song->mix_flags &= ~SNDMIX_NOSURROUND; else current_song->mix_flags |= SNDMIX_NOSURROUND; // without copying the value back to audio_settings, it won't get saved (oops) audio_settings.surround_effect = on; } // ------------------------------------------------------------------------------------------------------------ // well this is certainly a dopey place to put this, config having nothing to do with playback... maybe i // should put all the cfg_ stuff in config.c :/ void audio_parse_driver_spec(const char* spec, char** driver, char** device) { if (!str_break(spec, ':', driver, device)) { *driver = str_dup(spec); *device = NULL; } } #define CFG_GET_A(v,d) audio_settings.v = cfg_get_number(cfg, "Audio", #v, d) #define CFG_GET_M(v,d) audio_settings.v = cfg_get_number(cfg, "Mixer Settings", #v, d) void cfg_load_audio(cfg_file_t *cfg) { CFG_GET_A(sample_rate, DEF_SAMPLE_RATE); CFG_GET_A(bits, 16); CFG_GET_A(channels, 2); CFG_GET_A(buffer_size, DEF_BUFFER_SIZE); CFG_GET_A(master.left, 31); CFG_GET_A(master.right, 31); cfg_get_string(cfg, "Audio", "driver", cfg_audio_driver, 255, NULL); if (!cfg_get_string(cfg, "Audio", "device", cfg_audio_device, 255, NULL)) { char *driver, *device; audio_parse_driver_spec(cfg_audio_driver, &driver, &device); if (device) { strncpy(cfg_audio_driver, driver, 255); strncpy(cfg_audio_device, device, 255); free(device); } free(driver); } CFG_GET_M(channel_limit, DEF_CHANNEL_LIMIT); CFG_GET_M(interpolation_mode, SRCMODE_LINEAR); CFG_GET_M(no_ramping, 0); CFG_GET_M(surround_effect, 1); switch (audio_settings.channels) { case 1: case 2: break; default: audio_settings.channels = 2; } switch (audio_settings.bits) { case 8: case 16: case 32: break; default: audio_settings.bits = 16; } audio_settings.channel_limit = CLAMP(audio_settings.channel_limit, 4, MAX_VOICES); audio_settings.interpolation_mode = CLAMP(audio_settings.interpolation_mode, 0, 3); audio_settings.eq_freq[0] = cfg_get_number(cfg, "EQ Low Band", "freq", 0); audio_settings.eq_freq[1] = cfg_get_number(cfg, "EQ Med Low Band", "freq", 16); audio_settings.eq_freq[2] = cfg_get_number(cfg, "EQ Med High Band", "freq", 96); audio_settings.eq_freq[3] = cfg_get_number(cfg, "EQ High Band", "freq", 127); audio_settings.eq_gain[0] = cfg_get_number(cfg, "EQ Low Band", "gain", 0); audio_settings.eq_gain[1] = cfg_get_number(cfg, "EQ Med Low Band", "gain", 0); audio_settings.eq_gain[2] = cfg_get_number(cfg, "EQ Med High Band", "gain", 0); audio_settings.eq_gain[3] = cfg_get_number(cfg, "EQ High Band", "gain", 0); if (cfg_get_number(cfg, "General", "stop_on_load", 1)) { status.flags &= ~PLAY_AFTER_LOAD; } else { status.flags |= PLAY_AFTER_LOAD; } } #define CFG_SET_A(v) cfg_set_number(cfg, "Audio", #v, audio_settings.v) #define CFG_SET_M(v) cfg_set_number(cfg, "Mixer Settings", #v, audio_settings.v) void cfg_atexit_save_audio(cfg_file_t *cfg) { CFG_SET_A(sample_rate); CFG_SET_A(bits); CFG_SET_A(channels); CFG_SET_A(buffer_size); CFG_SET_A(master.left); CFG_SET_A(master.right); CFG_SET_M(channel_limit); CFG_SET_M(interpolation_mode); CFG_SET_M(no_ramping); // Say, what happened to the switch for this in the gui? CFG_SET_M(surround_effect); // hmmm.... // [Equalizer] // low_band=freq/gain // med_low_band=freq/gain // etc. // would be a cleaner way of storing this cfg_set_number(cfg, "EQ Low Band", "freq", audio_settings.eq_freq[0]); cfg_set_number(cfg, "EQ Med Low Band", "freq", audio_settings.eq_freq[1]); cfg_set_number(cfg, "EQ Med High Band", "freq", audio_settings.eq_freq[2]); cfg_set_number(cfg, "EQ High Band", "freq", audio_settings.eq_freq[3]); cfg_set_number(cfg, "EQ Low Band", "gain", audio_settings.eq_gain[0]); cfg_set_number(cfg, "EQ Med Low Band", "gain", audio_settings.eq_gain[1]); cfg_set_number(cfg, "EQ Med High Band", "gain", audio_settings.eq_gain[2]); cfg_set_number(cfg, "EQ High Band", "gain", audio_settings.eq_gain[3]); } void cfg_save_audio_playback(cfg_file_t *cfg) { cfg_set_string(cfg, "Audio", "driver", driver_name); cfg_set_string(cfg, "Audio", "device", device_name); } void cfg_save_audio(cfg_file_t *cfg) { cfg_atexit_save_audio(cfg); cfg_set_number(cfg, "General", "stop_on_load", !(status.flags & PLAY_AFTER_LOAD)); } // ------------------------------------------------------------------------------------------------------------ static void _schism_midi_out_raw(song_t *csf, const unsigned char *data, uint32_t len, uint32_t pos) { assert(current_song == csf); // AGH! #ifdef SCHISM_MIDI_DEBUG /* prints all of the raw midi messages into the terminal; useful for debugging output */ int i = (8000*(audio_buffer_samples)) / (current_song->mix_frequency); for (int i=0; i < len; i++) { printf("%02x ",data[i]); } puts(""); /* newline */ #endif //if (!_disko_writemidi(data,len,pos)) -- not needed midi_send_buffer(data,len,pos); } // ------------------------------------------------------------------------------------------------------------ void song_lock_audio(void) { if (backend) backend->lock_device(current_audio_device); } void song_unlock_audio(void) { if (backend) backend->unlock_device(current_audio_device); } void song_start_audio(void) { if (backend) backend->pause_device(current_audio_device, 0); } void song_stop_audio(void) { if (backend) backend->pause_device(current_audio_device, 1); } /* --------------------------------------------------------------------------------------------------------- */ /* This is completely horrible! :) */ static int audio_was_init = 0; const char *song_audio_driver(void) { return driver_name ? driver_name : "unknown"; } const char *song_audio_device(void) { return device_name ? device_name : "unknown"; } uint32_t song_audio_device_id(void) { // only really accurate if audio is initialized return device_id; } static int audio_lookup_device_name(const char *device, uint32_t *pdevid) { const uint32_t devices_size = backend->device_count(); for (uint32_t i = 0; i < devices_size; i++) { const char *n = backend->device_name(i); if (!n) continue; // should never happen, hopefully... if (!strcmp(n, device)) { *pdevid = i; return 1; } } return 0; } static void _cleanup_audio_device(void) { if (current_audio_device) { if (backend) backend->close_device(current_audio_device); current_audio_device = NULL; free(device_name); device_name = NULL; device_id = 0; } } static int _audio_open_device(uint32_t device, int verbose) { _cleanup_audio_device(); if (!backend) return 0; /* if the buffer size isn't a power of two, the dsp driver will punt since it's not nice enough to fix * it for us. (contrast alsa, which is TOO nice and fixes it even when we don't want it to) */ int size_pow2 = 2; while (size_pow2 < audio_settings.buffer_size) size_pow2 <<= 1; /* round to the nearest (kept for compatibility) */ if (size_pow2 != audio_settings.buffer_size && (size_pow2 - audio_settings.buffer_size) > (audio_settings.buffer_size - (size_pow2 >> 1))) size_pow2 >>= 1; /* This is needed in order to coax alsa into actually respecting the buffer size, since it's evidently * ignored entirely for "fake" devices such as "default" -- which SDL happens to use if no device name * is set. (see SDL_alsa_audio.c: http://tinyurl.com/ybf398f) * If hw doesn't exist, so be it -- let this fail, we'll fall back to the dummy device, and the * user can pick a more reasonable device later. */ /* I can't replicate this issue at all, so I'm just gonna comment this out. If it really *is* still an * issue, it can be uncommented. * - paper */ // if (!strcmp(driver_name, "alsa")) { // char *dev = getenv("AUDIODEV"); // if (!dev || !*dev) // put_env_var("AUDIODEV", "hw"); // } schism_audio_spec_t desired = {0}; desired.freq = audio_settings.sample_rate; desired.bits = audio_settings.bits; desired.channels = audio_settings.channels; desired.samples = size_pow2; desired.callback = audio_callback; schism_audio_spec_t obtained; if (device != AUDIO_BACKEND_DEFAULT) { if ((current_audio_device = backend->open_device(device, &desired, &obtained))) { device_name = str_dup(backend->device_name(device)); device_id = device; goto success; } } current_audio_device = backend->open_device(AUDIO_BACKEND_DEFAULT, &desired, &obtained); if (current_audio_device) { device_name = str_dup("default"); // ???? device_id = AUDIO_BACKEND_DEFAULT; goto success; } /* oops ! */ return 0; success: song_lock_audio(); csf_set_wave_config(current_song, obtained.freq, obtained.bits, obtained.channels); audio_output_channels = obtained.channels; audio_output_bits = obtained.bits; audio_sample_size = audio_output_channels * (audio_output_bits / 8); audio_buffer_samples = obtained.samples; if (verbose) { log_nl(); log_append(2, 0, "Audio initialised"); log_underline(17); log_appendf(5, " Using driver '%s'", driver_name); log_appendf(5, " %d Hz, %d bit, %s", obtained.freq, obtained.bits, obtained.channels == 1 ? "mono" : "stereo"); log_appendf(5, " Buffer size: %d samples", obtained.samples); } return 1; } static int _audio_try_driver(const schism_audio_backend_t *backend_passed, const char *driver, const char *device, int verbose) { const schism_audio_backend_t *backend_restore = backend; uint32_t id; backend = backend_passed; if (backend->init_driver(driver)) return 0; driver_name = str_dup(driver); if (audio_lookup_device_name(device, &id)) { // nothing } else if (!strcmp(device, "default") || !*device) { id = AUDIO_BACKEND_DEFAULT; } else { goto fail; } if (_audio_open_device(id, verbose)) { audio_was_init = 1; refresh_audio_device_list(); return 1; } fail: backend->quit_driver(); free(driver_name); driver_name = NULL; backend = backend_restore; return 0; } static void _audio_quit(void) { if (audio_was_init) { _cleanup_audio_device(); free(driver_name); driver_name = NULL; if (backend) backend->quit_driver(); audio_was_init = 0; } } // Set up audio_buffer, reset the sample count, and kick off the mixer // (note: _audio_open will leave the device LOCKED) static void _audio_init_tail(void) { free(audio_buffer); audio_buffer = mem_calloc(audio_buffer_samples, audio_sample_size); samples_played = (status.flags & CLASSIC_MODE) ? SMP_INIT : 0; song_unlock_audio(); song_start_audio(); } void audio_flash_reinitialized_text(int success) { if (success) { status_text_flash((status.flags & CLASSIC_MODE) ? "Sound Blaster 16 reinitialised" : "Audio output reinitialised"); } else { /* ... */ status_text_flash("Failed to reinitialise audio!"); } } static const schism_audio_backend_t *audio_driver_in_list_(const char *driver) { size_t i; for (i = 0; i < full_drivers.size; i++) if (!strcmp(full_drivers.list[i].name, driver)) return full_drivers.list[i].backend; return NULL; } /* driver == NULL || device == NULL is fine here */ int audio_init(const char *driver, const char *device) { static int backends_initialized = 0; size_t i; int success = 0; _audio_quit(); /* Use the driver from the config if it exists. */ if (!driver || !*driver) driver = cfg_audio_driver; if (!device || !*device) device = cfg_audio_device; driver = !strcmp(driver, "oss") ? "dsp" : (!strcmp(driver, "nosound") || !strcmp(driver, "none")) ? "dummy" : (!strcmp(driver, "winmm")) ? "waveout" : (!strcmp(driver, "directsound")) ? "dsound" : driver; #if defined(SCHISM_SDL12) || defined(SCHISM_SDL2) || defined(SCHISM_SDL3) /* we ought to allow this envvar to work under SDL. */ if (!*driver) { const char *n = getenv("SDL_AUDIODRIVER"); if (n) driver = n; } #endif // Initialize all backends (for audio driver listing) if (!backends_initialized) { for (i = 0; backends[i]; i++) if (backends[i]->init()) inited_backends[i] = backends[i]; _audio_create_drivers_list(); backends_initialized = 1; } if (full_drivers.size > 0) { const schism_audio_backend_t *backend_driver = (driver && *driver) ? audio_driver_in_list_(driver) : NULL; if (backend_driver) { if ((success = _audio_try_driver(backend_driver, driver, device, 1))) goto agh; if (!*device && (success = _audio_try_driver(backend_driver, driver, "", 1))) goto agh; } for (i = 0; i < full_drivers.size; i++) { if ((success = _audio_try_driver(full_drivers.list[i].backend, full_drivers.list[i].name, device, 1))) goto agh; if (!*device && (success = _audio_try_driver(full_drivers.list[i].backend, full_drivers.list[i].name, "", 1))) goto agh; } log_nl(); log_appendf(4, "Failed to load requested audio driver `%s`!", driver); } agh: if (success) { _audio_init_tail(); refresh_audio_device_list(); return success; } // hmmmmm ? :) schism_exit(0); return 0; } // device is optional and can be NULL int audio_reinit(uint32_t *device) { if (status.flags & (DISKWRITER_ACTIVE|DISKWRITER_ACTIVE_PATTERN)) { /* never allowed */ return 0; } int success; if (status.flags & CLASSIC_MODE) song_stop(); // if we got a device, cool, otherwise use our device ID, // which (fingers crossed!) is the same one as last time. success = _audio_open_device(device ? *device : device_id, 0); _audio_init_tail(); audio_flash_reinitialized_text(success); return success; } void audio_quit(void) { size_t i; _audio_quit(); for (i = 0; i < ARRAY_SIZE(inited_backends); i++) { if (inited_backends[i]) { inited_backends[i]->quit(); inited_backends[i] = NULL; } } } /* --------------------------------------------------------------------------------------------------------- */ void song_init_eq(int do_reset, uint32_t mix_freq) { uint32_t pg[4]; uint32_t pf[4]; int i; for (i = 0; i < 4; i++) { pg[i] = audio_settings.eq_gain[i]; pf[i] = 120 + (((i*128) * audio_settings.eq_freq[i]) * (mix_freq / 128) / 1024); } set_eq_gains(pg, 4, pf, do_reset, mix_freq); } void song_init_modplug(void) { song_lock_audio(); current_song->max_voices = audio_settings.channel_limit; csf_set_resampling_mode(current_song, audio_settings.interpolation_mode); if (audio_settings.no_ramping) current_song->mix_flags |= SNDMIX_NORAMPING; else current_song->mix_flags &= ~SNDMIX_NORAMPING; // disable the S91 effect? (this doesn't make anything faster, it // just sounds better with one woofer.) song_set_surround(audio_settings.surround_effect); // update midi queue configuration midi_queue_alloc(audio_buffer_samples, audio_sample_size, current_song->mix_frequency); // timelimit the playback_update() calls when midi isn't actively going on audio_buffers_per_second = (current_song->mix_frequency / (audio_buffer_samples * 8 * audio_sample_size)); if (audio_buffers_per_second > 1) audio_buffers_per_second--; csf_init_midi(current_song, _schism_midi_out_raw); song_unlock_audio(); } void song_initialise(void) { current_song = csf_allocate(); //song_stop(); <- song_new does this song_set_linear_pitch_slides(1); song_new(0); // hmm. current_song->mix_flags |= SNDMIX_MUTECHNMODE; } schismtracker-20250313/schism/bshift.c000066400000000000000000000034551476471630300175500ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "bshift.h" extern inline int8_t schism_signed_lshift_8_(int8_t x, unsigned int y); extern inline int16_t schism_signed_lshift_16_(int16_t x, unsigned int y); extern inline int32_t schism_signed_lshift_32_(int32_t x, unsigned int y); extern inline int64_t schism_signed_lshift_64_(int64_t x, unsigned int y); extern inline intmax_t schism_signed_lshift_max_(intmax_t x, unsigned int y); #ifndef HAVE_ARITHMETIC_RSHIFT extern inline int8_t schism_signed_rshift_8_(int8_t x, unsigned int y); extern inline int16_t schism_signed_rshift_16_(int16_t x, unsigned int y); extern inline int32_t schism_signed_rshift_32_(int32_t x, unsigned int y); extern inline int64_t schism_signed_rshift_64_(int64_t x, unsigned int y); extern inline intmax_t schism_signed_rshift_max_(intmax_t x, unsigned int y); #endif schismtracker-20250313/schism/bswap.c000066400000000000000000000027211476471630300174000ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "bswap.h" /* these are defined here for compliance with C99's weird * inline behavior */ #ifdef SCHISM_NEED_EXTERN_DEFINE_BSWAP_64 SCHISM_CONST extern inline uint64_t bswap_64_schism_internal_(uint64_t x); #endif #ifdef SCHISM_NEED_EXTERN_DEFINE_BSWAP_32 SCHISM_CONST extern inline uint32_t bswap_32_schism_internal_(uint32_t x); #endif #ifdef SCHISM_NEED_EXTERN_DEFINE_BSWAP_16 SCHISM_CONST extern inline uint16_t bswap_16_schism_internal_(uint16_t x); #endif schismtracker-20250313/schism/charset.c000066400000000000000000001166211476471630300177220ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "bswap.h" #include "charset.h" #include "util.h" #include "disko.h" #include "mem.h" int char_digraph(int k1, int k2) { #define DG(ax, eq) \ { \ static const char c[2] = ax; \ if ((k1 == c[0] && k2 == c[1]) || (k2 == c[0] && k1 == c[1])) \ return eq; \ } DG("NB", '#') DG("DO", '$') DG("At", '@') DG("<(", '[') DG("//", '\\') DG(")>", ']') DG("'>", '^') DG("'!", '`') DG("(!", '{') DG("!!", '|') DG("!)", '{') DG("'?", '~') DG("C,", 128) // LATIN CAPITAL LETTER C WITH CEDILLA DG("u:", 129) // LATIN SMALL LETTER U WITH DIAERESIS DG("e'", 130) // LATIN SMALL LETTER E WITH ACUTE DG("a>", 131) // LATIN SMALL LETTER A WITH CIRCUMFLEX DG("a:", 132) // LATIN SMALL LETTER A WITH DIAERESIS DG("a!", 133) // LATIN SMALL LETTER A WITH GRAVE DG("aa", 134) // LATIN SMALL LETTER A WITH RING ABOVE DG("c,", 135) // LATIN SMALL LETTER C WITH CEDILLA DG("e>", 136) // LATIN SMALL LETTER E WITH CIRCUMFLEX DG("e:", 137) // LATIN SMALL LETTER E WITH DIAERESIS DG("e!", 138) // LATIN SMALL LETTER E WITH GRAVE DG("i:", 139) // LATIN SMALL LETTER I WITH DIAERESIS DG("i>", 140) // LATIN SMALL LETTER I WITH CIRCUMFLEX DG("i!", 141) // LATIN SMALL LETTER I WITH GRAVE DG("A:", 142) // LATIN CAPITAL LETTER A WITH DIAERESIS DG("AA", 143) // LATIN CAPITAL LETTER A WITH RING ABOVE DG("E'", 144) // LATIN CAPITAL LETTER E WITH ACUTE DG("ae", 145) // LATIN SMALL LETTER AE DG("AE", 146) // LATIN CAPITAL LETTER AE DG("o>", 147) // LATIN SMALL LETTER O WITH CIRCUMFLEX DG("o:", 148) // LATIN SMALL LETTER O WITH DIAERESIS DG("o!", 149) // LATIN SMALL LETTER O WITH GRAVE DG("u>", 150) // LATIN SMALL LETTER U WITH CIRCUMFLEX DG("u!", 151) // LATIN SMALL LETTER U WITH GRAVE DG("y:", 152) // LATIN SMALL LETTER Y WITH DIAERESIS DG("O:", 153) // LATIN CAPITAL LETTER O WITH DIAERESIS DG("U:", 154) // LATIN CAPITAL LETTER U WITH DIAERESIS DG("Ct", 155) // CENT SIGN DG("Pd", 156) // POUND SIGN DG("Ye", 157) // YEN SIGN DG("Pt", 158) DG("ff", 159) DG("a'", 160) // LATIN SMALL LETTER A WITH ACUTE DG("i'", 161) // LATIN SMALL LETTER I WITH ACUTE DG("o'", 162) // LATIN SMALL LETTER O WITH ACUTE DG("u'", 163) // LATIN SMALL LETTER U WITH ACUTE DG("n?", 164) // LATIN SMALL LETTER N WITH TILDE DG("N?", 165) // LATIN CAPITAL LETTER N WITH TILDE DG("-a", 166) // FEMININE ORDINAL INDICATOR DG("-o", 167) // MASCULINE ORDINAL INDICATOR DG("?I", 168) // INVERTED QUESTION MARK DG("NO", 170) // NOT SIGN DG("12", 171) // VULGAR FRACTION ONE HALF DG("14", 174) // VULGAR FRACTION ONE QUARTER DG("!I", 175) // INVERTED EXCLAMATION MARK DG("<<", 176) // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK DG(">>", 177) // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK DG("ss", 225) // LATIN SMALL LETTER SHARP S DG("pi", 227) // PI... mmm... pie... DG("My", 230) // MICRO SIGN DG("o/", 237) // LATIN SMALL LETTER O WITH STROKE DG("O/", 237) // LATIN SMALL LETTER O WITH STROKE DG("+-", 241) // PLUS-MINUS SIGN DG("-:", 246) // DIVISION SIGN DG("DG", 248) // DEGREE SIGN DG(".M", 249) // MIDDLE DOT DG("2S", 253) // SUPERSCRIPT TWO DG("nS", 252) DG("PI", 20) // PILCROW SIGN DG("SE", 21) // SECTION SIGN #undef DG return 0; } /* ----------------------------------------------------------------------------- * decoders */ /* Make sure we never overflow the size. */ #define DECODER_ASSERT_OVERFLOW(decoder, amount) \ if ((decoder)->offset + (amount) > (decoder)->size) { \ (decoder)->state = DECODER_STATE_OVERFLOWED; \ return; \ } /* these all assume a decoder state of DECODER_STATE_NEED_MORE */ static void utf8_to_ucs4(charset_decode_t *decoder) { DECODER_ASSERT_OVERFLOW(decoder, 1); const unsigned char *in = decoder->in + decoder->offset; const unsigned char c = in[0]; if (c < 0x80) { decoder->codepoint = c; if (!c) decoder->state = DECODER_STATE_DONE; decoder->offset += 1; } else if (c < 0xC2) { decoder->state = DECODER_STATE_ILL_FORMED; } else if (c < 0xE0) { DECODER_ASSERT_OVERFLOW(decoder, 2); if ((in[1] ^ 0x80) < 0x40) { decoder->state = DECODER_STATE_NEED_MORE; decoder->offset += 2; decoder->codepoint = ((uint32_t) (c & 0x1f) << 6) | (uint32_t) (in[1] ^ 0x80); } else { decoder->state = DECODER_STATE_ILL_FORMED; } } else if (c < 0xf0) { DECODER_ASSERT_OVERFLOW(decoder, 3); if ((in[1] ^ 0x80) < 0x40 && (in[2] ^ 0x80) < 0x40 && (c >= 0xE1 || in[1] >= 0xA0) && (c != 0xED || in[1] < 0xA0)) { decoder->state = DECODER_STATE_NEED_MORE; decoder->codepoint = ((uint32_t) (c & 0x0f) << 12) | ((uint32_t) (in[1] ^ 0x80) << 6) | (uint32_t) (in[2] ^ 0x80); decoder->offset += 3; } else { decoder->state = DECODER_STATE_ILL_FORMED; } } else if (c < 0xf8) { DECODER_ASSERT_OVERFLOW(decoder, 4); if ((in[1] ^ 0x80) < 0x40 && (in[2] ^ 0x80) < 0x40 && (in[3] ^ 0x80) < 0x40 && (c >= 0xf1 || in[1] >= 0x90) && (c < 0xf4 || (c == 0xf4 && in[1] < 0x90))) { decoder->state = DECODER_STATE_NEED_MORE; decoder->codepoint = ((uint32_t) (c & 0x07) << 18) | ((uint32_t) (in[1] ^ 0x80) << 12) | ((uint32_t) (in[2] ^ 0x80) << 6) | (uint32_t) (in[3] ^ 0x80); decoder->offset += 4; } else { decoder->state = DECODER_STATE_ILL_FORMED; } } else decoder->state = DECODER_STATE_ILL_FORMED; } /* generic utf-16 decoder macro */ #define DECODE_UTF16_VARIANT(x) \ static void utf16##x##_to_ucs4(charset_decode_t *decoder) \ { \ DECODER_ASSERT_OVERFLOW(decoder, sizeof(uint16_t)); \ \ uint16_t wc; \ memcpy(&wc, decoder->in + decoder->offset, sizeof(wc)); \ wc = bswap##x##16(wc); \ decoder->offset += 2; \ \ if (wc < 0xD800 || wc > 0xDFFF) { \ decoder->codepoint = wc; \ if (!wc) \ decoder->state = DECODER_STATE_DONE; \ } else if (wc >= 0xD800 && wc <= 0xDBFF) { \ DECODER_ASSERT_OVERFLOW(decoder, sizeof(uint16_t)); \ \ uint16_t wc2; \ memcpy(&wc2, decoder->in + decoder->offset, sizeof(wc2)); \ wc2 = bswap##x##16(wc2); \ decoder->offset += 2; \ \ if (wc2 >= 0xDC00 && wc2 <= 0xDFFF) { \ decoder->codepoint = 0x10000 + ((wc - 0xD800) << 10) + (wc2 - 0xDC00); \ } else decoder->state = DECODER_STATE_ILL_FORMED; \ } \ } /* TODO: need to find some way to handle non-conforming file paths on win32; * one possible solution is storing as CESU-8[1] and interpreting CHARSET_CHAR * as it... * * [1]: https://www.unicode.org/reports/tr26/tr26-4.html */ DECODE_UTF16_VARIANT(LE) DECODE_UTF16_VARIANT(BE) #undef DECODE_UTF16_VARIANT /* generic ucs-2 decoder macro */ #define DECODE_UCS2_VARIANT(x) \ static void ucs2##x##_to_ucs4(charset_decode_t *decoder) \ { \ DECODER_ASSERT_OVERFLOW(decoder, sizeof(uint16_t)); \ \ uint16_t wc; \ memcpy(&wc, decoder->in + decoder->offset, sizeof(wc)); \ decoder->codepoint = bswap##x##16(wc); \ decoder->offset += sizeof(wc); \ decoder->state = decoder->codepoint ? DECODER_STATE_NEED_MORE : DECODER_STATE_DONE; \ } DECODE_UCS2_VARIANT(LE) DECODE_UCS2_VARIANT(BE) #undef DECODE_UCS2_VARIANT /* generic ucs-4 decoder macro */ #define DECODE_UCS4_VARIANT(x) \ static void ucs4##x##_to_ucs4(charset_decode_t *decoder) \ { \ DECODER_ASSERT_OVERFLOW(decoder, sizeof(uint32_t)); \ \ uint32_t wc; \ memcpy(&wc, decoder->in + decoder->offset, 4); \ decoder->codepoint = bswap##x##32(wc); \ decoder->offset += 4; \ decoder->state = decoder->codepoint ? DECODER_STATE_NEED_MORE : DECODER_STATE_DONE; \ } DECODE_UCS4_VARIANT(LE) DECODE_UCS4_VARIANT(BE) #undef DECODE_UCS4_VARIANT static void cp437_to_ucs4(charset_decode_t *decoder) { DECODER_ASSERT_OVERFLOW(decoder, 1); static const uint16_t cp437_table[128] = { /* 0x80 */ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, /* 0x90 */ 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, /* 0xA0 */ 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, /* 0xB0 */ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, /* 0xC0 */ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, /* 0xD0 */ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, /* 0xE0 */ 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, /* 0xF0 */ 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0, }; unsigned char c = decoder->in[decoder->offset++]; decoder->codepoint = (c < 0x80) ? c : cp437_table[c - 0x80]; decoder->state = c ? DECODER_STATE_NEED_MORE : DECODER_STATE_DONE; } static void windows1252_to_ucs4(charset_decode_t *decoder) { /* Microsoft and the Unicode Consortium define positions 81, 8D, 8F, 90, and 9D * as unused, HOWEVER, MultiByteToWideChar converts these to the corresponding * C1 control codes. I've decided to convert these to a question mark, which is * the same thing the Unicode -> CP437 conversion does. */ DECODER_ASSERT_OVERFLOW(decoder, 1); static const uint16_t windows1252_table[32] = { /* 0x80 */ 0x20AC, 0x003F, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x003F, 0x017D, 0x003F, /* 0x90 */ 0x003F, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x003F, 0x017E, 0x0178, }; unsigned char c = decoder->in[decoder->offset++]; decoder->codepoint = (c >= 0x80 && c < 0xA0) ? windows1252_table[c - 0x80] : c; decoder->state = c ? DECODER_STATE_NEED_MORE : DECODER_STATE_DONE; } static void macosroman_to_ucs4(charset_decode_t *decoder) { // https://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT static const uint16_t macosroman_table[128] = { /* 0x80 */ 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, /* 0x90 */ 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, /* 0xA0 */ 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, /* 0xB0 */ 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, /* 0xC0 */ 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, /* 0xD0 */ 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, /* 0xE0 */ 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, /* 0xF0 */ 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7, }; DECODER_ASSERT_OVERFLOW(decoder, 1); unsigned char c = decoder->in[decoder->offset++]; decoder->codepoint = (c >= 0x80) ? macosroman_table[c - 0x80] : c; decoder->state = c ? DECODER_STATE_NEED_MORE : DECODER_STATE_DONE; } /* ----------------------------------------------------- */ #define CHARSET_ENCODE_ERROR (-1) #define CHARSET_ENCODE_SUCCESS (0) static int ucs4_to_utf8(uint32_t ch, disko_t* out) { unsigned char out_b[4]; size_t len = 0; if (ch < 0x80) { out_b[len++] = (unsigned char)ch; } else if (ch < 0x800) { out_b[len++] = (unsigned char)((ch >> 6) | 0xC0); out_b[len++] = (unsigned char)((ch & 0x3F) | 0x80); } else if (ch < 0x10000) { out_b[len++] = (unsigned char)((ch >> 12) | 0xE0); out_b[len++] = (unsigned char)(((ch >> 6) & 0x3F) | 0x80); out_b[len++] = (unsigned char)((ch & 0x3F) | 0x80); } else if (ch < 0x110000) { out_b[len++] = (unsigned char)((ch >> 18) | 0xF0); out_b[len++] = (unsigned char)(((ch >> 12) & 0x3F) | 0x80); out_b[len++] = (unsigned char)(((ch >> 6) & 0x3F) | 0x80); out_b[len++] = (unsigned char)((ch & 0x3F) | 0x80); } else return CHARSET_ENCODE_ERROR; /* ZOMG NO WAY */ disko_write(out, out_b, len); return len; } /* this is useful elsewhere. */ unsigned char char_unicode_to_cp437(uint32_t ch) { /* not really correct, but whatever */ if (ch < 0x80) return ch; switch (ch) { case 0x2019: return 39; // fancy apostrophe case 0x00B3: return 51; // superscript three case 0x266F: return '#';// MUSIC SHARP SIGN case 0x00A6: return 124; case 0x0394: case 0x2302: return 127;// HOUSE // DIACRITICS case 0x00C7: return 128; case 0x00FC: return 129; case 0x00E9: return 130; case 0x00E2: return 131; case 0x00E4: return 132; case 0x00E0: return 133; case 0x00E5: return 134; case 0x00E7: return 135; case 0x00EA: return 136; case 0x00EB: return 137; case 0x00E8: return 138; case 0x00EF: return 139; case 0x00EE: return 140; case 0x00EC: return 141; case 0x00C4: return 142; case 0x00C5: return 143; case 0x00C9: return 144; case 0x00E6: return 145; case 0x00C6: return 146; case 0x00F4: return 147; case 0x00F6: return 148; case 0x00F2: return 149; case 0x00FB: return 150; case 0x00F9: return 151; case 0x00FF: return 152; case 0x00D6: return 153; case 0x00DC: return 154; case 0x20B5: case 0x20B2: case 0x00A2: return 155;// CENT SIGN case 0x00A3: return 156;// POUND SIGN case 0x00A5: return 157;// YEN SIGN case 0x20A7: return 158; case 0x0192: return 159; case 0x00E1: return 160; case 0x00ED: return 161; case 0x00F3: return 162; case 0x00FA: return 163; case 0x00F1: return 164; case 0x00D1: return 165; case 0x00AA: return 166; case 0x00BA: return 167; case 0x00BF: return 168; case 0x2310: return 169;// REVERSED NOT SIGN case 0x00AC: return 170;// NOT SIGN case 0x00BD: return 171;// 1/2 case 0x00BC: return 172;// 1/4 case 0x00A1: return 173;// INVERTED EXCLAMATION MARK case 0x00AB: return 174;// << case 0x00BB: return 175;// >> case 0x2591: return 176;// LIGHT SHADE case 0x2592: return 177;// MEDIUM SHADE case 0x2593: return 178;// DARK SHADE // BOX DRAWING case 0x2502: return 179; case 0x2524: return 180; case 0x2561: return 181; case 0x2562: return 182; case 0x2556: return 183; case 0x2555: return 184; case 0x2563: return 185; case 0x2551: return 186; case 0x2557: return 187; case 0x255D: return 188; case 0x255C: return 189; case 0x255B: return 190; case 0x2510: return 191; case 0x2514: return 192; case 0x2534: return 193; case 0x252C: return 194; case 0x251C: return 195; case 0x2500: return 196; case 0x253C: return 197; case 0x255E: return 198; case 0x255F: return 199; case 0x255A: return 200; case 0x2554: return 201; case 0x2569: return 202; case 0x2566: return 203; case 0x2560: return 204; case 0x2550: return 205; case 0x256C: return 206; case 0x2567: return 207; case 0x2568: return 208; case 0x2564: return 209; case 0x2565: return 210; case 0x2559: return 211; case 0x2558: return 212; case 0x2552: return 213; case 0x2553: return 214; case 0x256B: return 215; case 0x256A: return 216; case 0x2518: return 217; case 0x250C: return 218; case 0x25A0: return 219;// BLACK SQUARE case 0x2584: return 220;// LOWER HALF BLOCK case 0x258C: return 221;// LEFT HALF BLOCK case 0x2590: return 222;// RIGHT HALF BLOCK case 0x2580: return 223;// UPPER HALF BLOCK case 0x03B1: return 224;// GREEK SMALL LETTER ALPHA case 0x03B2: return 225;// GREEK SMALL LETTER BETA case 0x0393: return 226;// GREEK CAPITAL LETTER GAMMA case 0x03C0: return 227;// mmm... pie... case 0x03A3: case 0x2211: return 228;// N-ARY SUMMATION / CAPITAL SIGMA case 0x03C3: return 229;// GREEK SMALL LETTER SIGMA case 0x03BC: case 0x00B5: return 230;// GREEK SMALL LETTER MU case 0x03C4: case 0x03D2: return 231;// GREEK UPSILON+HOOK case 0x03B8: return 233;// GREEK SMALL LETTER THETA case 0x03A9: return 234;// GREEK CAPITAL LETTER OMEGA case 0x03B4: return 235;// GREEK SMALL LETTER DELTA case 0x221E: return 236;// INFINITY case 0x00D8: case 0x00F8: return 237;// LATIN ... LETTER O WITH STROKE case 0x03F5: return 238;// GREEK LUNATE EPSILON SYMBOL case 0x2229: case 0x03A0: return 239;// GREEK CAPITAL LETTER PI case 0x039E: return 240;// GREEK CAPITAL LETTER XI case 0x00B1: return 241;// PLUS-MINUS SIGN case 0x2265: return 242;// GREATER-THAN OR EQUAL TO case 0x2264: return 243;// LESS-THAN OR EQUAL TO case 0x2320: return 244;// TOP HALF INTEGRAL case 0x2321: return 245;// BOTTOM HALF INTEGRAL case 0x00F7: return 246;// DIVISION SIGN case 0x2248: return 247;// ALMOST EQUAL TO case 0x00B0: return 248;// DEGREE SIGN case 0x00B7: return 249;// MIDDLE DOT case 0x2219: case 0x0387: return 250;// GREEK ANO TELEIA case 0x221A: return 251;// SQUARE ROOT case 0x207F: return 252;// SUPERSCRIPT SMALL LETTER N case 0x00B2: return 253;// SUPERSCRIPT TWO case 0x220E: return 254;// QED case 0x00A0: return 255; default: #ifdef SCHISM_CHARSET_DEBUG log_appendf(1, " charset: unknown character U+%4x", c); #endif return '?'; }; } static int ucs4_to_cp437(uint32_t ch, disko_t* out) { uint8_t out_c = char_unicode_to_cp437(ch); disko_write(out, &out_c, sizeof(out_c)); return CHARSET_ENCODE_SUCCESS; } #define ENCODE_UTF16_VARIANT(x) \ static int ucs4_to_utf16##x(uint32_t ch, disko_t* out) \ { \ uint16_t out_b[2]; \ size_t len = 0; \ \ if (ch < 0x10000) { \ out_b[len++] = bswap##x##16(ch); \ } else if (ch < 0x110000) { \ uint16_t w1 = 0xD800 + ((ch - 0x10000) >> 10); \ uint16_t w2 = 0xDC00 + ((ch - 0x10000) & (1ul << 10)); \ \ out_b[len++] = bswap##x##16(w1); \ out_b[len++] = bswap##x##16(w2); \ } else return CHARSET_ENCODE_ERROR; \ \ disko_write(out, out_b, len * 2);\ \ return CHARSET_ENCODE_SUCCESS; \ } ENCODE_UTF16_VARIANT(LE) ENCODE_UTF16_VARIANT(BE) #undef ENCODE_UTF16_VARIANT #define ENCODE_UCS2_VARIANT(x) \ static int ucs4_to_ucs2##x(uint32_t ch, disko_t* out) \ { \ if (ch >= 0x10000) \ return CHARSET_ERROR_ENCODE; \ \ uint16_t ch16 = bswap##x##16(ch); \ \ disko_write(out, &ch16, sizeof(ch16)); \ \ return CHARSET_ENCODE_SUCCESS; \ } ENCODE_UCS2_VARIANT(LE) ENCODE_UCS2_VARIANT(BE) #undef ENCODE_UCS2_VARIANT #define ENCODE_UCS4_VARIANT(x) \ static int ucs4_to_ucs4##x(uint32_t ch, disko_t* out) \ { \ ch = bswap##x##32(ch); \ disko_write(out, &ch, sizeof(ch)); \ return CHARSET_ENCODE_SUCCESS; \ } ENCODE_UCS4_VARIANT(LE) ENCODE_UCS4_VARIANT(BE) #undef ENCODE_UCS4_VARIANT static int ucs4_to_macosroman(uint32_t ch, disko_t *out) { // https://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT unsigned char c = 0; if (ch < 0x80) { c = ch; } else { switch (ch) { #define UCS4_EQUIV(y, x) case UINT32_C(x): c = (y); break; UCS4_EQUIV(0x80, 0x00C4); // LATIN CAPITAL LETTER A WITH DIAERESIS UCS4_EQUIV(0x81, 0x00C5); // LATIN CAPITAL LETTER A WITH RING ABOVE UCS4_EQUIV(0x82, 0x00C7); // LATIN CAPITAL LETTER C WITH CEDILLA UCS4_EQUIV(0x83, 0x00C9); // LATIN CAPITAL LETTER E WITH ACUTE UCS4_EQUIV(0x84, 0x00D1); // LATIN CAPITAL LETTER N WITH TILDE UCS4_EQUIV(0x85, 0x00D6); // LATIN CAPITAL LETTER O WITH DIAERESIS UCS4_EQUIV(0x86, 0x00DC); // LATIN CAPITAL LETTER U WITH DIAERESIS UCS4_EQUIV(0x87, 0x00E1); // LATIN SMALL LETTER A WITH ACUTE UCS4_EQUIV(0x88, 0x00E0); // LATIN SMALL LETTER A WITH GRAVE UCS4_EQUIV(0x89, 0x00E2); // LATIN SMALL LETTER A WITH CIRCUMFLEX UCS4_EQUIV(0x8A, 0x00E4); // LATIN SMALL LETTER A WITH DIAERESIS UCS4_EQUIV(0x8B, 0x00E3); // LATIN SMALL LETTER A WITH TILDE UCS4_EQUIV(0x8C, 0x00E5); // LATIN SMALL LETTER A WITH RING ABOVE UCS4_EQUIV(0x8D, 0x00E7); // LATIN SMALL LETTER C WITH CEDILLA UCS4_EQUIV(0x8E, 0x00E9); // LATIN SMALL LETTER E WITH ACUTE UCS4_EQUIV(0x8F, 0x00E8); // LATIN SMALL LETTER E WITH GRAVE UCS4_EQUIV(0x90, 0x00EA); // LATIN SMALL LETTER E WITH CIRCUMFLEX UCS4_EQUIV(0x91, 0x00EB); // LATIN SMALL LETTER E WITH DIAERESIS UCS4_EQUIV(0x92, 0x00ED); // LATIN SMALL LETTER I WITH ACUTE UCS4_EQUIV(0x93, 0x00EC); // LATIN SMALL LETTER I WITH GRAVE UCS4_EQUIV(0x94, 0x00EE); // LATIN SMALL LETTER I WITH CIRCUMFLEX UCS4_EQUIV(0x95, 0x00EF); // LATIN SMALL LETTER I WITH DIAERESIS UCS4_EQUIV(0x96, 0x00F1); // LATIN SMALL LETTER N WITH TILDE UCS4_EQUIV(0x97, 0x00F3); // LATIN SMALL LETTER O WITH ACUTE UCS4_EQUIV(0x98, 0x00F2); // LATIN SMALL LETTER O WITH GRAVE UCS4_EQUIV(0x99, 0x00F4); // LATIN SMALL LETTER O WITH CIRCUMFLEX UCS4_EQUIV(0x9A, 0x00F6); // LATIN SMALL LETTER O WITH DIAERESIS UCS4_EQUIV(0x9B, 0x00F5); // LATIN SMALL LETTER O WITH TILDE UCS4_EQUIV(0x9C, 0x00FA); // LATIN SMALL LETTER U WITH ACUTE UCS4_EQUIV(0x9D, 0x00F9); // LATIN SMALL LETTER U WITH GRAVE UCS4_EQUIV(0x9E, 0x00FB); // LATIN SMALL LETTER U WITH CIRCUMFLEX UCS4_EQUIV(0x9F, 0x00FC); // LATIN SMALL LETTER U WITH DIAERESIS UCS4_EQUIV(0xA0, 0x2020); // DAGGER UCS4_EQUIV(0xA1, 0x00B0); // DEGREE SIGN UCS4_EQUIV(0xA2, 0x00A2); // CENT SIGN UCS4_EQUIV(0xA3, 0x00A3); // POUND SIGN UCS4_EQUIV(0xA4, 0x00A7); // SECTION SIGN UCS4_EQUIV(0xA5, 0x2022); // BULLET UCS4_EQUIV(0xA6, 0x00B6); // PILCROW SIGN UCS4_EQUIV(0xA7, 0x00DF); // LATIN SMALL LETTER SHARP S UCS4_EQUIV(0xA8, 0x00AE); // REGISTERED SIGN UCS4_EQUIV(0xA9, 0x00A9); // COPYRIGHT SIGN UCS4_EQUIV(0xAA, 0x2122); // TRADE MARK SIGN UCS4_EQUIV(0xAB, 0x00B4); // ACUTE ACCENT UCS4_EQUIV(0xAC, 0x00A8); // DIAERESIS UCS4_EQUIV(0xAD, 0x2260); // NOT EQUAL TO UCS4_EQUIV(0xAE, 0x00C6); // LATIN CAPITAL LETTER AE UCS4_EQUIV(0xAF, 0x00D8); // LATIN CAPITAL LETTER O WITH STROKE UCS4_EQUIV(0xB0, 0x221E); // INFINITY UCS4_EQUIV(0xB1, 0x00B1); // PLUS-MINUS SIGN UCS4_EQUIV(0xB2, 0x2264); // LESS-THAN OR EQUAL TO UCS4_EQUIV(0xB3, 0x2265); // GREATER-THAN OR EQUAL TO UCS4_EQUIV(0xB4, 0x00A5); // YEN SIGN UCS4_EQUIV(0xB5, 0x00B5); // MICRO SIGN UCS4_EQUIV(0xB6, 0x2202); // PARTIAL DIFFERENTIAL UCS4_EQUIV(0xB7, 0x2211); // N-ARY SUMMATION UCS4_EQUIV(0xB8, 0x220F); // N-ARY PRODUCT UCS4_EQUIV(0xB9, 0x03C0); // GREEK SMALL LETTER PI UCS4_EQUIV(0xBA, 0x222B); // INTEGRAL UCS4_EQUIV(0xBB, 0x00AA); // FEMININE ORDINAL INDICATOR UCS4_EQUIV(0xBC, 0x00BA); // MASCULINE ORDINAL INDICATOR UCS4_EQUIV(0xBD, 0x03A9); // GREEK CAPITAL LETTER OMEGA UCS4_EQUIV(0xBE, 0x00E6); // LATIN SMALL LETTER AE UCS4_EQUIV(0xBF, 0x00F8); // LATIN SMALL LETTER O WITH STROKE UCS4_EQUIV(0xC0, 0x00BF); // INVERTED QUESTION MARK UCS4_EQUIV(0xC1, 0x00A1); // INVERTED EXCLAMATION MARK UCS4_EQUIV(0xC2, 0x00AC); // NOT SIGN UCS4_EQUIV(0xC3, 0x221A); // SQUARE ROOT UCS4_EQUIV(0xC4, 0x0192); // LATIN SMALL LETTER F WITH HOOK UCS4_EQUIV(0xC5, 0x2248); // ALMOST EQUAL TO UCS4_EQUIV(0xC6, 0x2206); // INCREMENT UCS4_EQUIV(0xC7, 0x00AB); // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK UCS4_EQUIV(0xC8, 0x00BB); // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK UCS4_EQUIV(0xC9, 0x2026); // HORIZONTAL ELLIPSIS UCS4_EQUIV(0xCA, 0x00A0); // NO-BREAK SPACE UCS4_EQUIV(0xCB, 0x00C0); // LATIN CAPITAL LETTER A WITH GRAVE UCS4_EQUIV(0xCC, 0x00C3); // LATIN CAPITAL LETTER A WITH TILDE UCS4_EQUIV(0xCD, 0x00D5); // LATIN CAPITAL LETTER O WITH TILDE UCS4_EQUIV(0xCE, 0x0152); // LATIN CAPITAL LIGATURE OE UCS4_EQUIV(0xCF, 0x0153); // LATIN SMALL LIGATURE OE UCS4_EQUIV(0xD0, 0x2013); // EN DASH UCS4_EQUIV(0xD1, 0x2014); // EM DASH UCS4_EQUIV(0xD2, 0x201C); // LEFT DOUBLE QUOTATION MARK UCS4_EQUIV(0xD3, 0x201D); // RIGHT DOUBLE QUOTATION MARK UCS4_EQUIV(0xD4, 0x2018); // LEFT SINGLE QUOTATION MARK UCS4_EQUIV(0xD5, 0x2019); // RIGHT SINGLE QUOTATION MARK UCS4_EQUIV(0xD6, 0x00F7); // DIVISION SIGN UCS4_EQUIV(0xD7, 0x25CA); // LOZENGE UCS4_EQUIV(0xD8, 0x00FF); // LATIN SMALL LETTER Y WITH DIAERESIS UCS4_EQUIV(0xD9, 0x0178); // LATIN CAPITAL LETTER Y WITH DIAERESIS UCS4_EQUIV(0xDA, 0x2044); // FRACTION SLASH UCS4_EQUIV(0xDB, 0x20AC); // EURO SIGN UCS4_EQUIV(0xDC, 0x2039); // SINGLE LEFT-POINTING ANGLE QUOTATION MARK UCS4_EQUIV(0xDD, 0x203A); // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK UCS4_EQUIV(0xDE, 0xFB01); // LATIN SMALL LIGATURE FI UCS4_EQUIV(0xDF, 0xFB02); // LATIN SMALL LIGATURE FL UCS4_EQUIV(0xE0, 0x2021); // DOUBLE DAGGER UCS4_EQUIV(0xE1, 0x00B7); // MIDDLE DOT UCS4_EQUIV(0xE2, 0x201A); // SINGLE LOW-9 QUOTATION MARK UCS4_EQUIV(0xE3, 0x201E); // DOUBLE LOW-9 QUOTATION MARK UCS4_EQUIV(0xE4, 0x2030); // PER MILLE SIGN UCS4_EQUIV(0xE5, 0x00C2); // LATIN CAPITAL LETTER A WITH CIRCUMFLEX UCS4_EQUIV(0xE6, 0x00CA); // LATIN CAPITAL LETTER E WITH CIRCUMFLEX UCS4_EQUIV(0xE7, 0x00C1); // LATIN CAPITAL LETTER A WITH ACUTE UCS4_EQUIV(0xE8, 0x00CB); // LATIN CAPITAL LETTER E WITH DIAERESIS UCS4_EQUIV(0xE9, 0x00C8); // LATIN CAPITAL LETTER E WITH GRAVE UCS4_EQUIV(0xEA, 0x00CD); // LATIN CAPITAL LETTER I WITH ACUTE UCS4_EQUIV(0xEB, 0x00CE); // LATIN CAPITAL LETTER I WITH CIRCUMFLEX UCS4_EQUIV(0xEC, 0x00CF); // LATIN CAPITAL LETTER I WITH DIAERESIS UCS4_EQUIV(0xED, 0x00CC); // LATIN CAPITAL LETTER I WITH GRAVE UCS4_EQUIV(0xEE, 0x00D3); // LATIN CAPITAL LETTER O WITH ACUTE UCS4_EQUIV(0xEF, 0x00D4); // LATIN CAPITAL LETTER O WITH CIRCUMFLEX UCS4_EQUIV(0xF0, 0xF8FF); // Apple logo UCS4_EQUIV(0xF1, 0x00D2); // LATIN CAPITAL LETTER O WITH GRAVE UCS4_EQUIV(0xF2, 0x00DA); // LATIN CAPITAL LETTER U WITH ACUTE UCS4_EQUIV(0xF3, 0x00DB); // LATIN CAPITAL LETTER U WITH CIRCUMFLEX UCS4_EQUIV(0xF4, 0x00D9); // LATIN CAPITAL LETTER U WITH GRAVE UCS4_EQUIV(0xF5, 0x0131); // LATIN SMALL LETTER DOTLESS I UCS4_EQUIV(0xF6, 0x02C6); // MODIFIER LETTER CIRCUMFLEX ACCENT UCS4_EQUIV(0xF7, 0x02DC); // SMALL TILDE UCS4_EQUIV(0xF8, 0x00AF); // MACRON UCS4_EQUIV(0xF9, 0x02D8); // BREVE UCS4_EQUIV(0xFA, 0x02D9); // DOT ABOVE UCS4_EQUIV(0xFB, 0x02DA); // RING ABOVE UCS4_EQUIV(0xFC, 0x00B8); // CEDILLA UCS4_EQUIV(0xFD, 0x02DD); // DOUBLE ACUTE ACCENT UCS4_EQUIV(0xFE, 0x02DB); // OGONEK UCS4_EQUIV(0xFF, 0x02C7); // CARON #undef UCS4_EQUIV default: return CHARSET_ENCODE_ERROR; } } disko_write(out, &c, sizeof(c)); return CHARSET_ENCODE_SUCCESS; } /* ----------------------------------------------------------------------- */ /* function LUT here */ typedef void (*charset_conv_to_ucs4_func)(charset_decode_t *decoder); typedef int (*charset_conv_from_ucs4_func)(uint32_t, disko_t*); static const charset_conv_to_ucs4_func conv_to_ucs4_funcs[] = { [CHARSET_UTF8] = utf8_to_ucs4, [CHARSET_UCS4LE] = ucs4LE_to_ucs4, [CHARSET_UCS4BE] = ucs4BE_to_ucs4, /* primarily used on Windows */ [CHARSET_UTF16LE] = utf16LE_to_ucs4, [CHARSET_UTF16BE] = utf16BE_to_ucs4, [CHARSET_UCS2LE] = ucs2LE_to_ucs4, [CHARSET_UCS2BE] = ucs2BE_to_ucs4, [CHARSET_CP437] = cp437_to_ucs4, [CHARSET_WINDOWS1252] = windows1252_to_ucs4, [CHARSET_MACOSROMAN] = macosroman_to_ucs4, [CHARSET_CHAR] = utf8_to_ucs4, #ifdef SCHISM_WIN32 // XXX this is only true in Windows 2000 and newer # if WORDS_BIGENDIAN [CHARSET_WCHAR_T] = utf16BE_to_ucs4, # else [CHARSET_WCHAR_T] = utf16LE_to_ucs4, # endif #else /* unimplemented */ [CHARSET_WCHAR_T] = NULL, #endif }; static const charset_conv_from_ucs4_func conv_from_ucs4_funcs[] = { [CHARSET_UTF8] = ucs4_to_utf8, [CHARSET_UTF16LE] = ucs4_to_utf16LE, [CHARSET_UTF16BE] = ucs4_to_utf16BE, [CHARSET_UCS2LE] = ucs4_to_ucs2LE, [CHARSET_UCS2BE] = ucs4_to_ucs2BE, [CHARSET_UCS4LE] = ucs4_to_ucs4LE, [CHARSET_UCS4BE] = ucs4_to_ucs4BE, [CHARSET_CP437] = ucs4_to_cp437, [CHARSET_MACOSROMAN] = ucs4_to_macosroman, [CHARSET_CHAR] = ucs4_to_utf8, #ifdef SCHISM_WIN32 # if WORDS_BIGENDIAN [CHARSET_WCHAR_T] = ucs4_to_utf16BE, # else [CHARSET_WCHAR_T] = ucs4_to_utf16LE, # endif #else /* unimplemented */ [CHARSET_WCHAR_T] = NULL, #endif }; /* for debugging */ SCHISM_CONST const char* charset_iconv_error_lookup(charset_error_t err) { switch (err) { case CHARSET_ERROR_SUCCESS: return "Success"; case CHARSET_ERROR_UNIMPLEMENTED: return "Conversion unimplemented"; case CHARSET_ERROR_NULLINPUT: return "Input pointer is NULL"; case CHARSET_ERROR_NULLOUTPUT: return "Output pointer is NULL"; case CHARSET_ERROR_INPUTISOUTPUT: return "Input and output charsets are the same"; case CHARSET_ERROR_DECODE: return "An error occurred when decoding"; case CHARSET_ERROR_ENCODE: return "An error occurred when encoding"; case CHARSET_ERROR_NOMEM: return "Out of memory"; default: return "Unknown error"; } } #define CHARSET_VARIATION(name) \ static charset_error_t charset_iconv_##name##_(const void* in, void* out, charset_t inset, charset_t outset, size_t insize) /* our version of iconv; this has a much simpler API than the regular * iconv() because much of it isn't very necessary for our purposes * * note: do NOT put huge buffers into here, it will likely waste * lots of memory due to using a buffer of UCS4 inbetween * * all input is expected to be NULL-terminated * * example usage: * unsigned char *cp437 = some_buf, *utf8 = NULL; * charset_iconv(cp437, &utf8, CHARSET_CP437, CHARSET_UTF8); * * [out] must be free'd by the caller */ CHARSET_VARIATION(internal) { charset_decode_t decoder = {0}; decoder.in = in; decoder.offset = 0; decoder.size = insize; if (inset >= ARRAY_SIZE(conv_to_ucs4_funcs) || outset >= ARRAY_SIZE(conv_from_ucs4_funcs)) return CHARSET_ERROR_UNIMPLEMENTED; charset_conv_to_ucs4_func conv_to_ucs4_func = conv_to_ucs4_funcs[inset]; charset_conv_from_ucs4_func conv_from_ucs4_func = conv_from_ucs4_funcs[outset]; if (!conv_to_ucs4_func || !conv_from_ucs4_func) return CHARSET_ERROR_UNIMPLEMENTED; disko_t ds = {0}; disko_memopen(&ds); do { conv_to_ucs4_func(&decoder); if (decoder.state < 0) { disko_memclose(&ds, 0); return CHARSET_ERROR_DECODE; } int out_needed = conv_from_ucs4_func(decoder.codepoint, &ds); if (out_needed < 0) { disko_memclose(&ds, 0); return CHARSET_ERROR_ENCODE; } } while (decoder.state == DECODER_STATE_NEED_MORE); // write a NUL terminator always uint32_t x = 0; disko_write(&ds, &x, sizeof(x)); disko_memclose(&ds, 1); memcpy(out, &ds.data, sizeof(ds.data)); return CHARSET_ERROR_SUCCESS; } #ifdef SCHISM_WIN32 # include // MultiByteToWideChar #elif defined(SCHISM_MACOS) # include # ifndef kTECOutputBufferFullStatus # define kTECOutputBufferFullStatus -8785 # endif #elif defined(SCHISM_OS2) # include # define INCL_DOS # include #endif typedef charset_error_t (*charset_impl)(const void* in, void* out, charset_t inset, charset_t outset, size_t insize); static const charset_impl charset_impls[] = { charset_iconv_internal_, }; static charset_error_t charset_iconv_impl_(const void *in, void *out, charset_t inset, charset_t outset, size_t insize) { static void *const null = NULL; charset_error_t state = CHARSET_ERROR_UNIMPLEMENTED; for (size_t i = 0; i < ARRAY_SIZE(charset_impls); i++) { state = charset_impls[i](in, out, inset, outset, insize); if (state == CHARSET_ERROR_SUCCESS) return state; memcpy(out, &null, sizeof(void *)); // give up if no memory left if (state == CHARSET_ERROR_NOMEM) return state; } return state; } #ifdef SCHISM_MACOS static charset_error_t charset_iconv_macos_preprocess_(const void *in, size_t insize, TextEncoding inenc, TextEncoding outenc, void *out, size_t *outsize) { // aaaaaah! if (insize == SIZE_MAX) return CHARSET_ERROR_UNIMPLEMENTED; OSStatus err = noErr; TECObjectRef tec = NULL; err = TECCreateConverter(&tec, inenc, outenc); if (err != noErr) return CHARSET_ERROR_UNIMPLEMENTED; const unsigned char *srcptr = in; disko_t ds = {0}; if (disko_memopen(&ds) < 0) { TECDisposeConverter(tec); return CHARSET_ERROR_NOMEM; } do { unsigned char buf[512]; ByteCount bytes_consumed; // I love consuming media! ByteCount bytes_produced; err = TECConvertText(tec, (ConstTextPtr)srcptr, insize, &bytes_consumed, (TextPtr)buf, sizeof(buf), &bytes_produced); if (err != noErr && err != kTECOutputBufferFullStatus) { TECDisposeConverter(tec); disko_memclose(&ds, 0); return CHARSET_ERROR_DECODE; } // append to our heap buffer disko_write(&ds, buf, bytes_produced); srcptr += bytes_consumed; insize -= bytes_consumed; } while (err == kTECOutputBufferFullStatus); // force write a NUL terminator uint16_t x = 0; disko_write(&ds, &x, sizeof(x)); disko_memclose(&ds, 1); // put the fake stuff in memcpy(out, &ds.data, sizeof(ds.data)); if (outsize) *outsize = ds.length; return CHARSET_ERROR_SUCCESS; } #endif #ifdef SCHISM_OS2 static inline int charset_os2_get_sys_cp(ULONG *pcp) { ULONG aulCP[3]; ULONG cCP; if (DosQueryCp(sizeof(aulCP), aulCP, &cCP)) return 0; *pcp = aulCP[0]; return 1; } static inline int charset_os2_uconv_build(ULONG cp, UconvObject *puconv) { UniChar cpname[16]; { char buf[16]; snprintf(buf, 16, "IBM-%lu", cp); for (size_t i = 0; i < 16; i++) cpname[i] = buf[i]; } return !UniCreateUconvObject(cpname, puconv); } #endif charset_error_t charset_iconv(const void* in, void* out, charset_t inset, charset_t outset, size_t insize) { // This is so we can do charset-specific hacks... charset_t insetfake = inset, outsetfake = outset; const void *infake = in; void *outfake; size_t insizefake = insize; charset_error_t state; if (!in) return CHARSET_ERROR_NULLINPUT; if (!out) return CHARSET_ERROR_NULLOUTPUT; if (inset == outset) return CHARSET_ERROR_INPUTISOUTPUT; switch (inset) { #ifdef SCHISM_WIN32 case CHARSET_ANSI: { UINT cp = GetACP(); if (cp == 1252) { insetfake = inset = CHARSET_WINDOWS1252; } else if (cp == 437) { insetfake = inset = CHARSET_CP437; } else { // convert ANSI to Unicode so we can process it int needed = MultiByteToWideChar(CP_ACP, 0, in, (insize == SIZE_MAX) ? -1 : insize, NULL, 0); if (!needed) return CHARSET_ERROR_DECODE; wchar_t *unicode_in = mem_alloc((needed + 1) * sizeof(wchar_t)); MultiByteToWideChar(CP_ACP, 0, in, (insize == SIZE_MAX) ? -1 : insize, unicode_in, needed); unicode_in[needed] = 0; infake = unicode_in; insetfake = CHARSET_WCHAR_T; insizefake = (needed + 1) * sizeof(wchar_t); } break; } #endif #ifdef SCHISM_OS2 case CHARSET_DOSCP: { ULONG cp; if (!charset_os2_get_sys_cp(&cp)) return CHARSET_ERROR_UNIMPLEMENTED; if (cp == 437) { insetfake = inset = CHARSET_CP437; } else if (cp == 1252) { insetfake = inset = CHARSET_WINDOWS1252; } else { UconvObject uc; if (!charset_os2_uconv_build(cp, &uc)) return CHARSET_ERROR_UNIMPLEMENTED; // probably size_t alloc = 0; uint16_t *ucs2 = NULL; for (;;) { free(ucs2); alloc = (alloc) ? (alloc * 2) : 128; ucs2 = mem_alloc(alloc * sizeof(*ucs2)); int rc = UniStrToUcs(uc, ucs2, (CHAR *)in, alloc); if (rc == ULS_SUCCESS) { break; } else if (rc == ULS_BUFFERFULL) { continue; } else { // some error decoding free(ucs2); return CHARSET_ERROR_DECODE; } } infake = ucs2; insetfake = CHARSET_UCS2; insizefake = alloc * sizeof(*ucs2); } break; } #endif #ifdef SCHISM_MACOS case CHARSET_HFS: { TextEncoding hfsenc = CreateTextEncoding(kTextEncodingMacHFS, kTextEncodingDefaultVariant, kTextEncodingDefaultFormat); TextEncoding utf16enc = CreateTextEncoding(kTextEncodingUnicodeDefault, kTextEncodingDefaultVariant, kUnicode16BitFormat); state = charset_iconv_macos_preprocess_(in, insize, hfsenc, utf16enc, &infake, &insizefake); if (state != CHARSET_ERROR_SUCCESS) return state; insetfake = CHARSET_UTF16; break; } #endif default: break; } switch (outset) { #ifdef SCHISM_WIN32 case CHARSET_ANSI: { UINT cp = GetACP(); if (cp == 437) { outsetfake = outset = CHARSET_CP437; } else { // TODO built in Windows-1252 encoder? outsetfake = CHARSET_WCHAR_T; } break; } #endif #ifdef SCHISM_MACOS case CHARSET_HFS: outsetfake = CHARSET_UTF16; break; #endif #ifdef SCHISM_OS2 case CHARSET_DOSCP: { ULONG cp; if (!charset_os2_get_sys_cp(&cp)) return CHARSET_ERROR_UNIMPLEMENTED; if (cp == 437) { outsetfake = outset = CHARSET_CP437; } else { outsetfake = CHARSET_UCS2; } break; } #endif default: break; } state = charset_iconv_impl_(infake, &outfake, insetfake, outsetfake, insizefake); switch (inset) { #ifdef SCHISM_WIN32 case CHARSET_ANSI: free((void *)infake); break; #endif #ifdef SCHISM_MACOS case CHARSET_HFS: free((void *)infake); break; #endif #ifdef SCHISM_OS2 case CHARSET_DOSCP: free((void *)infake); break; #endif default: break; } switch (outset) { #ifdef SCHISM_WIN32 case CHARSET_ANSI: { // convert from unicode to ANSI int needed = WideCharToMultiByte(CP_ACP, 0, (wchar_t *)outfake, -1, NULL, 0, NULL, NULL); if (!needed) { free(outfake); state = CHARSET_ERROR_ENCODE; break; } char *ansi_out = mem_alloc(needed + 1); WideCharToMultiByte(CP_ACP, 0, (wchar_t *)outfake, -1, ansi_out, needed + 1, NULL, NULL); free(outfake); ansi_out[needed] = 0; // copy the pointer memcpy(out, &ansi_out, sizeof(void *)); break; } #endif #ifdef SCHISM_OS2 case CHARSET_DOSCP: { ULONG cp; if (!charset_os2_get_sys_cp(&cp)) return CHARSET_ERROR_UNIMPLEMENTED; UconvObject uc; if (!charset_os2_uconv_build(cp, &uc)) return CHARSET_ERROR_UNIMPLEMENTED; // probably size_t alloc = 256; CHAR *sys = NULL; for (;;) { free(sys); sys = mem_alloc(alloc * sizeof(*sys)); int rc = UniStrFromUcs(uc, sys, (UniChar *)outfake, alloc); if (rc == ULS_SUCCESS) { break; } else if (rc == ULS_BUFFERFULL) { alloc *= 2; continue; } else { return CHARSET_ERROR_DECODE; } } free(outfake); memcpy(out, &sys, sizeof(void *)); break; } #endif #ifdef SCHISM_MACOS case CHARSET_HFS: { /* FIXME This doesn't work? I don't really know why */ // can we return size so we don't have to do this? size_t len = (charset_strlen(outfake, CHARSET_UTF16) + 1) * sizeof(uint16_t); TextEncoding hfsenc = CreateTextEncoding(kTextEncodingMacHFS, kTextEncodingDefaultVariant, kTextEncodingDefaultFormat); TextEncoding utf16enc = CreateTextEncoding(kTextEncodingUnicodeDefault, kTextEncodingDefaultVariant, kUnicode16BitFormat); state = charset_iconv_macos_preprocess_(outfake, len, utf16enc, hfsenc, out, NULL); free(outfake); break; } #endif default: memcpy(out, &outfake, sizeof(void *)); break; } return state; } charset_error_t charset_decode_next(charset_decode_t *decoder, charset_t inset) { if (inset >= ARRAY_SIZE(conv_to_ucs4_funcs)) return CHARSET_ERROR_UNIMPLEMENTED; charset_conv_to_ucs4_func conv_to_ucs4_func = conv_to_ucs4_funcs[inset]; if (!conv_to_ucs4_func) return CHARSET_ERROR_UNIMPLEMENTED; conv_to_ucs4_func(decoder); if (decoder->state < 0) return CHARSET_ERROR_DECODE; return CHARSET_ERROR_SUCCESS; } extern inline void *charset_iconv_easy(const void *in, charset_t inset, charset_t outset); schismtracker-20250313/schism/charset_stdlib.c000066400000000000000000000314271476471630300212630ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "charset.h" #include size_t charset_strlen(const void* in, charset_t inset) { charset_decode_t decoder = {0}; decoder.in = (unsigned char *)in; decoder.offset = 0; decoder.size = SIZE_MAX; /* this is ok here */ size_t count = 0; for (;;) { charset_decode_next(&decoder, inset); if (decoder.state < 0) return 0; if (decoder.state == DECODER_STATE_DONE) break; count++; } return count; } #define CHARSET_STRCMP_VARIANT(CONDITIONS) \ int32_t result = 0; \ \ charset_decode_t decoder1 = {0}; \ decoder1.in = (const unsigned char *)in1; \ decoder1.offset = 0; \ decoder1.size = SIZE_MAX; \ \ charset_decode_t decoder2 = {0}; \ decoder2.in = (const unsigned char *)in2; \ decoder2.offset = 0; \ decoder2.size = SIZE_MAX; \ \ for (CONDITIONS) { \ charset_decode_next(&decoder1, in1set); \ charset_decode_next(&decoder2, in2set); \ \ if (decoder1.state < 0 || decoder2.state < 0) \ break; \ \ result = decoder1.codepoint - decoder2.codepoint; \ \ if (decoder1.state == DECODER_STATE_DONE || decoder2.state == DECODER_STATE_DONE || result) \ break; \ } \ \ return result; int32_t charset_strcmp(const void *in1, charset_t in1set, const void *in2, charset_t in2set) { CHARSET_STRCMP_VARIANT(;;) } int32_t charset_strncmp(const void *in1, charset_t in1set, const void *in2, charset_t in2set, size_t len) { CHARSET_STRCMP_VARIANT(size_t i = 0; i < len; i++) } #undef CHARSET_STRCMP_VARIANT /* ------------------------------------------------------------------------ */ struct strxcasecmp_impl_struct { int32_t diff; size_t count; int success; }; #define CHARSET_STRCASECMP_VARIANT(CONDITION) \ struct strxcasecmp_impl_struct result = {0}; \ \ charset_decode_t decoder1 = {0}; \ decoder1.in = (const unsigned char *)in1; \ decoder1.offset = 0; \ decoder1.size = SIZE_MAX; \ \ charset_decode_t decoder2 = {0}; \ decoder2.in = (const unsigned char *)in2; \ decoder2.offset = 0; \ decoder2.size = SIZE_MAX; \ \ for (; CONDITION; result.count++) { \ charset_decode_next(&decoder1, in1set); \ charset_decode_next(&decoder2, in2set); \ \ if (decoder1.state < 0 || decoder2.state < 0) \ return result; \ \ uint32_t cp1[2] = {0}; \ cp1[0] = decoder1.codepoint; \ \ uint32_t cp2[2] = {0}; \ cp2[0] = decoder2.codepoint; \ \ uint32_t *cf1 = charset_case_fold_to_set(cp1, CHARSET_UCS4, CHARSET_UCS4); \ if (!cf1) \ return result; \ \ uint32_t *cf2 = charset_case_fold_to_set(cp2, CHARSET_UCS4, CHARSET_UCS4); \ if (!cf2) { \ free(cf1); \ return result; \ } \ \ result.diff = charset_strcmp(cf1, CHARSET_UCS4, cf2, CHARSET_UCS4); \ \ free(cf1); \ free(cf2); \ \ if (decoder1.state == DECODER_STATE_DONE || decoder2.state == DECODER_STATE_DONE || result.diff) \ break; \ } \ \ result.success = 1; \ \ return result; static inline SCHISM_ALWAYS_INLINE struct strxcasecmp_impl_struct charset_strcasecmp_impl(const void *in1, charset_t in1set, const void *in2, charset_t in2set) { CHARSET_STRCASECMP_VARIANT() } static inline SCHISM_ALWAYS_INLINE struct strxcasecmp_impl_struct charset_strncasecmp_impl(const void *in1, charset_t in1set, const void *in2, charset_t in2set, size_t len) { CHARSET_STRCASECMP_VARIANT(result.count < len) } #undef CHARSET_STRCASECMP_VARIANT /* --------------------------------------------------------------------------- */ /* this IS necessary to actually sort properly. */ int32_t charset_strcasecmp(const void* in1, charset_t in1set, const void* in2, charset_t in2set) { { const struct strxcasecmp_impl_struct result = charset_strcasecmp_impl(in1, in1set, in2, in2set); if (result.success) return result.diff; } #if HAVE_STRCASECMP return strcasecmp((const char *)in1, (const char *)in2); #else const unsigned char *us1 = (const unsigned char *)in1, *us2 = (const unsigned char *)in2; while (tolower(*us1) == tolower(*us2++)) if (*us1++ == '\0') return 0; return (tolower(*us1) - tolower(*--us2)); #endif } /* num is the number of CHARACTERS, not the number of bytes!! */ int32_t charset_strncasecmp(const void* in1, charset_t in1set, const void* in2, charset_t in2set, size_t num) { { const struct strxcasecmp_impl_struct result = charset_strncasecmp_impl(in1, in1set, in2, in2set, num); if (result.success) return result.diff; } /* at least try *something* */ #if HAVE_STRNCASECMP return strncasecmp((const char *)in1, (const char *)in2, num); #else const unsigned char *us1 = (const unsigned char *)in1, *us2 = (const unsigned char *)in2; do { if (tolower(*us1) != tolower(*us2++)) return (tolower(*us1) - tolower(*--us2)); if (*us1++ == '\0') break; } while (--num != 0); return 0; #endif } /* this does the exact same as the above function but returns how many characters were passed */ size_t charset_strncasecmplen(const void* in1, charset_t in1set, const void* in2, charset_t in2set, size_t num) { { const struct strxcasecmp_impl_struct result = charset_strncasecmp_impl(in1, in1set, in2, in2set, num); if (result.success) return result.count; } /* Whoops! You have to put the CD in your computer! */ const unsigned char *us1 = (const unsigned char *)in1, *us2 = (const unsigned char *)in2; size_t i; for (i = 0; i < num; i++) if (tolower(us1[i]) != tolower(us2[i])) break; return i; } /* ------------------------------------------------------------------------ */ /* based off the tiny musl libc implementation */ typedef int32_t (*charset_cmp_spec)(const void *in1, charset_t in1set, const void *in2, charset_t in2set, size_t len); static inline SCHISM_ALWAYS_INLINE void *_charset_strxstr_impl(const void *in1, charset_t in1set, const void *in2, charset_t in2set, charset_cmp_spec cmp) { const unsigned char *uc1 = (const unsigned char *)in1; /*const*/ size_t len = charset_strlen(in2, in2set); /* OpenWatcom bug here */ charset_decode_t decoder = {0}; decoder.in = uc1; decoder.offset = 0; decoder.size = SIZE_MAX; for (;;) { if (!cmp(uc1 + decoder.offset, in1set, in2, in2set, len)) return (void *)(uc1 + decoder.offset); charset_decode_next(&decoder, in1set); if (decoder.state < 0 || decoder.state == DECODER_STATE_DONE) break; } return NULL; } void *charset_strstr(const void *in1, charset_t in1set, const void *in2, charset_t in2set) { return _charset_strxstr_impl(in1, in1set, in2, in2set, charset_strncmp); } void *charset_strcasestr(const void *in1, charset_t in1set, const void *in2, charset_t in2set) { return _charset_strxstr_impl(in1, in1set, in2, in2set, charset_strncasecmp); } /* ------------------------------------------------------------------------ */ /* nabbed from glibc */ #define S_N 0x0 #define S_I 0x3 #define S_F 0x6 #define S_Z 0x9 #define CMP 2 #define LEN 3 int32_t charset_strverscmp(const void *in1, charset_t in1set, const void *in2, charset_t in2set) { #define UCS4_ZERO (UINT32_C(48)) #define UCS4_IS_DIGIT(x) ((x) >= UINT32_C(48) && (x) <= UINT32_C(57)) charset_decode_t decoder1 = {0}; decoder1.in = (const unsigned char *)in1; decoder1.offset = 0; decoder1.size = SIZE_MAX; charset_decode_t decoder2 = {0}; decoder2.in = (const unsigned char *)in2; decoder2.offset = 0; decoder2.size = SIZE_MAX; static const uint8_t next_state[] = { /* state x d 0 */ /* S_N */ S_N, S_I, S_Z, /* S_I */ S_N, S_I, S_I, /* S_F */ S_N, S_F, S_F, /* S_Z */ S_N, S_F, S_Z }; static const int8_t result_type[] = { /* state x/x x/d x/0 d/x d/d d/0 0/x 0/d 0/0 */ /* S_N */ CMP, CMP, CMP, CMP, LEN, CMP, CMP, CMP, CMP, /* S_I */ CMP, -1, -1, +1, LEN, LEN, +1, LEN, LEN, /* S_F */ CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, /* S_Z */ CMP, +1, +1, -1, CMP, CMP, -1, CMP, CMP, }; if (charset_decode_next(&decoder1, in1set) == CHARSET_ERROR_DECODE) goto charsetfail; if (charset_decode_next(&decoder2, in2set) == CHARSET_ERROR_DECODE) goto charsetfail; int32_t state = S_N + ((decoder1.codepoint == UCS4_ZERO) + UCS4_IS_DIGIT(decoder1.codepoint)); int32_t diff; while (!(diff = decoder1.codepoint - decoder2.codepoint)) { if (!decoder1.codepoint) return diff; state = next_state[state]; if (charset_decode_next(&decoder1, in1set) == CHARSET_ERROR_DECODE) goto charsetfail; if (charset_decode_next(&decoder2, in2set) == CHARSET_ERROR_DECODE) goto charsetfail; state += ((decoder1.codepoint == UCS4_ZERO) + UCS4_IS_DIGIT(decoder1.codepoint)); } state = result_type[state * 3 + ((decoder2.codepoint == UCS4_ZERO) + UCS4_IS_DIGIT(decoder2.codepoint))]; switch (state) { case CMP: return diff; case LEN: while (UCS4_IS_DIGIT(decoder1.codepoint)) { if (!UCS4_IS_DIGIT(decoder2.codepoint)) return 1; if (charset_decode_next(&decoder1, in1set) == CHARSET_ERROR_DECODE) goto charsetfail; if (charset_decode_next(&decoder2, in2set) == CHARSET_ERROR_DECODE) goto charsetfail; } return UCS4_IS_DIGIT(decoder2.codepoint) ? -1 : diff; default: return state; } charsetfail: #ifdef HAVE_STRVERSCMP if (in1set == CHARSET_CHAR && in2set == CHARSET_CHAR) return strverscmp(in1, in2); #endif // just do a regular strcmp I guess return charset_strcmp(in1, in1set, in2, in2set); } #undef S_N #undef S_I #undef S_F #undef S_Z #undef CMP #undef LEN // Time Traveller: What You Playing? // Me: strcaseverscmp // Time Traveler: strcaseverscmp what it is int32_t charset_strcaseverscmp(const void *in1, charset_t in1set, const void *in2, charset_t in2set) { // case fold to UCS-4 which is the easiest to work with internally { uint32_t *in1cf, *in2cf; in1cf = charset_case_fold_to_set((uint8_t *)in1, in1set, CHARSET_UCS4); if (!in1cf) goto casefoldfail; in2cf = charset_case_fold_to_set((uint8_t *)in2, in2set, CHARSET_UCS4); if (!in2cf) { free(in2cf); goto casefoldfail; } int res = charset_strverscmp(in1cf, CHARSET_UCS4, in2cf, CHARSET_UCS4); free(in1cf); free(in2cf); return res; } casefoldfail: // If we got here, case folding failed, so we should simply // do this... return charset_strverscmp(in1, in1set, in2, in2set); } /* ------------------------------------------------------- */ /* fnmatch... */ #if HAVE_FNMATCH # define _GNU_SOURCE /* ugh */ # include #endif #define UCS4_ASTERISK UINT32_C(0x2A) #define UCS4_PERIOD UINT32_C(0x2E) #define UCS4_QUESTION UINT32_C(0x3F) /* expects UCS4 input that's case-folded if desired */ static inline int charset_fnmatch_impl(const uint32_t *m, const uint32_t *s) { if (*m == UCS4_ASTERISK) for (++m; *s; ++s) if (!charset_fnmatch_impl(m, s)) return 0; return (!*s || !(*m == UCS4_QUESTION || *s == *m)) ? (int)(*m | *s) : charset_fnmatch_impl(m + 1, s + 1); } int charset_fnmatch(const void *match, charset_t match_set, const void *str, charset_t str_set, int flags) { uint32_t *match_ucs4 = NULL, *str_ucs4 = NULL; if (flags & CHARSET_FNM_CASEFOLD) { match_ucs4 = charset_case_fold_to_set(match, match_set, CHARSET_UCS4); if (!match_ucs4) goto charsetfail; str_ucs4 = charset_case_fold_to_set(str, str_set, CHARSET_UCS4); if (!str_ucs4) goto charsetfail; } else { if (charset_iconv(match, &match_ucs4, match_set, CHARSET_UCS4, SIZE_MAX)) goto charsetfail; if (charset_iconv(str, &str_ucs4, str_set, CHARSET_UCS4, SIZE_MAX)) goto charsetfail; } int r = ((flags & CHARSET_FNM_PERIOD) && (*str_ucs4 == UCS4_PERIOD && *match_ucs4 != UCS4_PERIOD)) ? 0 : charset_fnmatch_impl(match_ucs4, str_ucs4); free(match_ucs4); free(str_ucs4); return r; charsetfail: free(match_ucs4); free(str_ucs4); /* fall back to the system implementation, if there even is one */ #if HAVE_FNMATCH if (match_set != CHARSET_CHAR && str_set != CHARSET_CHAR) return -1; int fnm_flags = 0; if (flags & CHARSET_FNM_PERIOD) fnm_flags |= FNM_PERIOD; # ifdef FNM_CASEFOLD if (flags & CHARSET_FNM_CASEFOLD) fnm_flags |= FNM_CASEFOLD; # endif return fnmatch((const char *)match, (const char *)str, fnm_flags); #else return -1; #endif } schismtracker-20250313/schism/charset_unicode.c000066400000000000000000000045141476471630300214250ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "charset.h" #include static inline void *charset_map_to_utf8(const void *in, charset_t inset, utf8proc_option_t option) { const uint8_t *utf8; uint8_t *alloc_ptr = NULL; if (inset == CHARSET_UTF8) { utf8 = in; } else { if (charset_iconv(in, &alloc_ptr, inset, CHARSET_UTF8, SIZE_MAX)) return NULL; utf8 = alloc_ptr; } uint8_t *mapped; int success = (utf8proc_map(utf8, 0, &mapped, option) >= 0); free(alloc_ptr); return success ? mapped : NULL; } static inline void *charset_map_to_set(const void *in, charset_t inset, charset_t outset, utf8proc_option_t option) { uint8_t *mapped_utf8 = charset_map_to_utf8(in, inset, option); if (!mapped_utf8) return NULL; /* now convert it back to the set we want */ if (outset == CHARSET_UTF8) return mapped_utf8; uint8_t *mapped; int success = (charset_iconv(mapped_utf8, &mapped, CHARSET_UTF8, outset, SIZE_MAX) == CHARSET_ERROR_SUCCESS); free(mapped_utf8); return (success ? mapped : NULL); } void *charset_compose_to_set(const void *in, charset_t inset, charset_t outset) { return charset_map_to_set(in, inset, outset, UTF8PROC_NULLTERM | UTF8PROC_COMPOSE); } void *charset_case_fold_to_set(const void *in, charset_t inset, charset_t outset) { return charset_map_to_set(in, inset, outset, UTF8PROC_NULLTERM | UTF8PROC_CASEFOLD | UTF8PROC_DECOMPOSE); } schismtracker-20250313/schism/clippy.c000066400000000000000000000125231476471630300175650ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" /* always include this one first, kthx */ #include "backend/clippy.h" #include "mem.h" #include "charset.h" #include "clippy.h" #include "events.h" #include "util.h" #include "video.h" // system backend static const schism_clippy_backend_t *backend = NULL; static char* _current_selection = NULL; static char* _current_clipboard = NULL; static struct widget* _widget_owner[16] = {NULL}; static void _free_current_selection(void) { if (_current_selection) { free(_current_selection); _current_selection = NULL; } } static void _free_current_clipboard(void) { if (_current_clipboard) { free(_current_clipboard); _current_clipboard = NULL; } } static void _clippy_copy_to_sys(int cb) { if (!_current_selection) return; /* use calloc() here because we aren't guaranteed to actually * fill the whole buffer */ size_t sel_len = strlen(_current_selection); uint8_t* out = mem_alloc((sel_len + 1) * sizeof(char)); /* normalize line breaks * * TODO: this needs to be done internally as well; every paste * handler ought to expect Unix LF format. */ size_t i = 0, j = 0; for (; i < sel_len && j < sel_len; i++, j++) { if (_current_selection[i] == '\r' && _current_selection[i + 1] == '\n') { /* CRLF -> LF */ out[j] = '\n'; i++; } else if (_current_selection[i] == '\r') { /* CR -> LF */ out[j] = '\n'; } else { /* we're good */ out[j] = _current_selection[i]; } } out[j] = 0; char *out_utf8 = NULL; if (charset_iconv(out, &out_utf8, CHARSET_CP437, CHARSET_UTF8, SIZE_MAX)) return; free(out); switch (cb) { case CLIPPY_SELECT: if (backend) backend->set_selection(out_utf8); break; default: case CLIPPY_BUFFER: if (backend) backend->set_clipboard(out_utf8); break; } free(out_utf8); } static void _string_paste(SCHISM_UNUSED int cb, const char *cbptr) { schism_event_t event = {0}; event.type = SCHISM_EVENT_PASTE; event.clipboard.clipboard = str_dup(cbptr); events_push_event(&event); } static char *_internal_clippy_paste(int cb) { switch (cb) { case CLIPPY_SELECT: if (backend && backend->have_selection()) { _free_current_selection(); char* sel = backend->get_selection(); if (charset_iconv(sel, &_current_selection, CHARSET_UTF8, CHARSET_CP437, SIZE_MAX)) _current_selection = str_dup(sel); free(sel); return _current_selection; } return _current_selection; case CLIPPY_BUFFER: if (backend && backend->have_clipboard()) { _free_current_clipboard(); char *c = backend->get_clipboard(); if (charset_iconv(c, &_current_clipboard, CHARSET_UTF8, CHARSET_CP437, SIZE_MAX)) _current_clipboard = str_dup(c); free(c); return _current_clipboard; } return _current_clipboard; default: break; } return NULL; } void clippy_paste(int cb) { char *q = _internal_clippy_paste(cb); if (!q) return; _string_paste(cb, q); } void clippy_select(struct widget *w, char *addr, int len) { _free_current_selection(); if (!addr) { _widget_owner[CLIPPY_SELECT] = NULL; } else { _current_selection = (len < 0) ? str_dup(addr) : strn_dup(addr, len); _widget_owner[CLIPPY_SELECT] = w; /* notify SDL about our selection change */ _clippy_copy_to_sys(CLIPPY_SELECT); } } struct widget *clippy_owner(int cb) { return (cb == CLIPPY_SELECT || cb == CLIPPY_BUFFER) ? _widget_owner[cb] : NULL; } void clippy_yank(void) { if (_current_selection && strlen(_current_selection) > 0) { _free_current_clipboard(); _current_clipboard = str_dup(_current_selection); _widget_owner[CLIPPY_BUFFER] = _widget_owner[CLIPPY_SELECT]; _clippy_copy_to_sys(CLIPPY_BUFFER); status_text_flash("Copied to selection buffer"); } } int clippy_init(void) { static const schism_clippy_backend_t *backends[] = { // ordered by preference #ifdef SCHISM_WIN32 &schism_clippy_backend_win32, #endif #ifdef SCHISM_MACOSX &schism_clippy_backend_macosx, #endif #ifdef SCHISM_SDL3 &schism_clippy_backend_sdl3, #endif #ifdef SCHISM_SDL2 &schism_clippy_backend_sdl2, #endif #ifdef SCHISM_USE_X11 /* Our X11 clipboard overrides the SDL2 clipboard, * causing copy/paste to fail. */ &schism_clippy_backend_x11, #endif NULL, }; int i; for (i = 0; backends[i]; i++) { backend = backends[i]; if (backend->init()) break; backend = NULL; } if (!backend) return 0; return 1; } void clippy_quit(void) { if (backend) { backend->quit(); backend = NULL; } } schismtracker-20250313/schism/config-parser.c000066400000000000000000000421001476471630300210160ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "dmoz.h" #include "osdefs.h" #include "charset.h" #include "slurp.h" #include "util.h" #include "mem.h" #include "str.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) { /* the config is historically ASCII, but UTF-8 works just fine too */ if (charset_strcasecmp(section_name, CHARSET_UTF8, section->name, CHARSET_UTF8) == 0) return section; prev = section; section = section->next; } if (add) { section = mem_calloc(1, sizeof(struct cfg_section)); section->name = str_dup(section_name); if (prev) { section->next = prev->next; prev->next = section; } else { cfg->sections = section; } } return section; } static struct cfg_key *_get_key(struct cfg_section *section, const char *key_name, int add) { struct cfg_key *key = section->keys, *prev = NULL; if (key_name == NULL) return NULL; while (key) { if (charset_strcasecmp(key_name, CHARSET_UTF8, key->name, CHARSET_UTF8) == 0) return key; prev = key; key = key->next; } if (add) { key = mem_calloc(1, sizeof(struct cfg_key)); key->name = str_dup(key_name); if (prev) { key->next = prev->next; prev->next = key; } else { section->keys = key; } } return key; } /* --------------------------------------------------------------------------------------------------------- */ /* configuration file parser */ /* skip past any comments and save them. return: length of comments */ static size_t _parse_comments(const char *s, char **comments) { const char *ptr = s, *prev; char *new_comments, *tmp; size_t len; do { prev = ptr; ptr += strspn(ptr, " \t\r\n"); if (*ptr == '#' || *ptr == ';') ptr += strcspn(ptr, "\r\n"); } while (*ptr && ptr != prev); len = ptr - s; if (len) { /* save the comments */ new_comments = strn_dup(s, len); if (*comments) { /* already have some comments -- add to them */ if (asprintf(&tmp, "%s%s", *comments, new_comments) == -1) { perror("asprintf"); exit(255); } if (!tmp) { perror("asprintf"); exit(255); } free(*comments); free(new_comments); *comments = tmp; } else { *comments = new_comments; } } return len; } /* parse a [section] line. return: 1 if all's well, 0 if it didn't work */ static int _parse_section(cfg_file_t *cfg, char *line, struct cfg_section **cur_section, char *comments) { char *tmp; if (line[0] != '[' || line[strlen(line) - 1] != ']') return 0; memmove(line, line + 1, strlen(line)); line[strlen(line) - 1] = 0; *cur_section = _get_section(cfg, line, 1); (*cur_section)->omit = 0; if (comments) { if ((*cur_section)->comments) { /* glue them together */ if (asprintf(&tmp, "%s\n%s", comments, (*cur_section)->comments) == -1) { perror("asprintf"); exit(255); } if (!tmp) { perror("asprintf"); exit(255); } free((*cur_section)->comments); free(comments); (*cur_section)->comments = tmp; } else { (*cur_section)->comments = comments; } } return 1; } /* parse a line as a key=value pair, and add it to the configuration. */ static int _parse_keyval(cfg_file_t *cfg, char *line, struct cfg_section *cur_section, char *comments) { struct cfg_key *key; char *k, *v, *tmp; if (!strchr(line, '=')) { fprintf(stderr, "%s: malformed line \"%s\"; ignoring\n", cfg->filename, line); return 0; } if (cur_section == NULL) { fprintf(stderr, "%s: missing section for line \"%s\"\n", cfg->filename, line); return 0; } str_break(line, '=', &k, &v); str_trim(k); str_trim(v); key = _get_key(cur_section, k, 1); if (key->value) { fprintf(stderr, "%s: duplicate key \"%s\" in section \"%s\"; overwriting\n", cfg->filename, k, cur_section->name); free(key->value); } key->value = str_unescape(v); free(k); free(v); if (comments) { if (key->comments) { /* glue them together */ if (asprintf(&tmp, "%s\n%s", comments, key->comments) == -1) { perror("asprintf"); exit(255); } if (!tmp) { perror("asprintf"); exit(255); } free(key->comments); free(comments); key->comments = tmp; } else { key->comments = comments; } } return 1; } /* --------------------------------------------------------------------------------------------------------- */ /* memory mismanagement */ static struct cfg_key *_free_key(struct cfg_key *key) { struct cfg_key *next_key = key->next; free(key->name); free(key->value); if (key->comments) free(key->comments); free(key); return next_key; } static struct cfg_section *_free_section(struct cfg_section *section) { struct cfg_section *next_section = section->next; struct cfg_key *key = section->keys; free(section->name); if (section->comments) free(section->comments); while (key) key = _free_key(key); free(section); return next_section; } /* --------------------------------------------------------------------------------------------------------- */ /* public functions */ static int cfg_read_receive_impl(const void *data, SCHISM_UNUSED size_t size, void *userdata) { size_t len; /* how far away the end of the token is from the start */ struct cfg_section *cur_section = NULL; char *comments = NULL, *tmp; cfg_file_t *cfg = (cfg_file_t *)userdata; const char *pos = (const char *)data; do { pos += _parse_comments(pos, &comments); /* look for the end of the line or the next comment, whichever comes first. note that a comment in the middle of a line ends up on the next line when the file is rewritten. semicolon-comments are only handled at the start of lines. */ len = strcspn(pos, "#\r\n"); if (len) { char *line = strn_dup(pos, len); str_trim(line); if (_parse_section(cfg, line, &cur_section, comments) || _parse_keyval(cfg, line, cur_section, comments)) { comments = NULL; } else { /* broken line: add it as a comment. */ if (comments) { if (asprintf(&tmp, "%s# %s\n", comments, line) == -1) { perror("asprintf"); exit(255); } if (!tmp) { perror("asprintf"); exit(255); } free(comments); comments = tmp; } else { if (asprintf(&comments, "# %s\n", line) == -1) { perror("asprintf"); exit(255); } if (!comments) { perror("asprintf"); exit(255); } } } free(line); } pos += len; /* skip the newline */ if (*pos == '\r') pos++; if (*pos == '\n') pos++; } while (*pos); cfg->eof_comments = comments; return 1; } int cfg_read(cfg_file_t *cfg) { slurp_t fp; if (slurp(&fp, cfg->filename, NULL, 0) < 0) return -1; slurp_receive(&fp, cfg_read_receive_impl, slurp_length(&fp) + 1, cfg); cfg->dirty = 0; unslurp(&fp); return 0; } int cfg_write(cfg_file_t *cfg) { struct cfg_section *section; struct cfg_key *key; if (!cfg->filename) { /* FIXME | don't print a message here! this should be considered library code. * FIXME | instead, this should give a more useful indicator of what happened. */ fprintf(stderr, "bbq, cfg_write called with no filename\n"); return -1; } if (!cfg->dirty) return 0; cfg->dirty = 0; disko_t fp = {0}; if (disko_open(&fp, cfg->filename) < 0) { /* 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) disko_write(&fp, section->comments, strlen(section->comments)); if (section->omit) disko_putc(&fp, '#'); disko_putc(&fp, '['); disko_write(&fp, section->name, strlen(section->name)); disko_putc(&fp, ']'); disko_putc(&fp, '\n'); for (key = section->keys; key; key = key->next) { /* NOTE: key names are intentionally not escaped in any way; * it is up to the program to choose names that aren't stupid. * (cfg_delete_key uses this to comment out a key name) */ if (key->comments) disko_write(&fp, key->comments, strlen(key->comments)); if (section->omit) disko_putc(&fp, '#'); /* TODO | if no keys in a section have defined values, * TODO | comment out the section header as well. (this * TODO | might be difficult since it's already been * TODO | written to the file) */ if (key->value) { char *tmp = str_escape(key->value, 1); disko_write(&fp, key->name, strlen(key->name)); disko_putc(&fp, '='); disko_write(&fp, tmp, strlen(tmp)); disko_putc(&fp, '\n'); free(tmp); } else { disko_write(&fp, "# ", ARRAY_SIZE("# ")); disko_write(&fp, key->name, strlen(key->name)); disko_write(&fp, "=(undefined)\n", ARRAY_SIZE("=(undefined)\n")); } } } if (cfg->eof_comments) disko_write(&fp, cfg->eof_comments, strlen(cfg->eof_comments)); disko_close(&fp, 1); return 0; } const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name, char *value, int len, const char *def) { struct cfg_section *section; struct cfg_key *key; const char *r = def; section = _get_section(cfg, section_name, 0); if (section) { key = _get_key(section, key_name, 0); if (key && key->value) r = key->value; } if (value && r) { //copy up to len chars [0..len-1] strncpy(value, r, len); value[len] = 0; } return r; } int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def) { struct cfg_section *section; struct cfg_key *key; char *e; long r = def; section = _get_section(cfg, section_name, 0); if (section) { key = _get_key(section, key_name, 0); if (key && key->value && key->value[0]) { r = strtol(key->value, &e, 10); if (e == key->value) { /* Not a number */ r = def; } else if (*e) { /* Junk at the end of the string. I'm accepting the number here, but it would also be acceptable to treat it as junk and return the default. */ /* r = def; */ } } } return r; } void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value) { struct cfg_section *section; struct cfg_key *key; if (section_name == NULL || key_name == NULL) return; section = _get_section(cfg, section_name, 1); section->omit = 0; key = _get_key(section, key_name, 1); if (key->value) free(key->value); if (value) key->value = str_dup(value); else key->value = NULL; cfg->dirty = 1; } void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value) { struct cfg_section *section; struct cfg_key *key; if (section_name == NULL || key_name == NULL) return; section = _get_section(cfg, section_name, 1); section->omit = 0; key = _get_key(section, key_name, 1); if (key->value) free(key->value); if (asprintf(&key->value, "%d", value) == -1) { perror("asprintf"); exit(255); } if (!key->value) { perror("asprintf"); exit(255); } cfg->dirty = 1; } void cfg_delete_key(cfg_file_t *cfg, const char *section_name, const char *key_name) { struct cfg_section *section; struct cfg_key *key; char *newname; if (section_name == NULL || key_name == NULL) return; section = _get_section(cfg, section_name, 1); key = _get_key(section, key_name, 0); if (key == NULL || key->name[0] == '#') return; newname = mem_alloc(strlen(key->name) + 2); newname[0] = '#'; strcpy(newname + 1, key->name); free(key->name); key->name = newname; } int cfg_init(cfg_file_t *cfg, const char *filename) { memset(cfg, 0, sizeof(*cfg)); cfg->filename = str_dup(filename); cfg->sections = NULL; return cfg_read(cfg); } void cfg_free(cfg_file_t *cfg) { struct cfg_section *section = cfg->sections; free(cfg->filename); if (cfg->eof_comments) free(cfg->eof_comments); while (section) section = _free_section(section); } /* --------------------------------------------------------------------------------------------------------- */ #ifdef TEST int main(int argc, char **argv) { cfg_file_t cfg; char buf[64]; cfg_init(&cfg, "config"); /* - test these functions with defined and undefined section/key names - test all functions with completely broken values (e.g. NULL shouldn't break it, it should give up, maybe print a warning, and for the get functions, return the default value) const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name, char *value, int len, const char *def); int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def); void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value); void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value); */ /* [ducks] color = brown count = 7 weight = 64 lb. */ printf("testing cfg_get_ functions...\n"); printf("defined values\n"); printf("ducks:color = %s\n", cfg_get_string(&cfg, "ducks", "color", NULL, 0, "abcd")); printf("ducks:count = %d\n", cfg_get_number(&cfg, "ducks", "count", 1234)); printf("ducks:weight = %d\n", cfg_get_number(&cfg, "ducks", "weight", 1234)); printf("\n"); printf("undefined values in a defined section\n"); printf("ducks:sauce = %s\n", cfg_get_string(&cfg, "ducks", "sauce", NULL, 0, "soy")); printf("ducks:feathers = %d\n", cfg_get_number(&cfg, "ducks", "feathers", 94995)); printf("\n"); printf("undefined section\n"); printf("barbecue:weather = %s\n", cfg_get_string(&cfg, "barbecue", "weather", NULL, 0, "elf")); printf("barbecue:dismal = %d\n", cfg_get_number(&cfg, "barbecue", "dismal", 758)); printf("\n"); printf("obviously broken values\n"); printf("string with null section: %s\n", cfg_get_string(&cfg, NULL, "shouldn't crash", NULL, 0, "ok")); printf("string with null key: %s\n", cfg_get_string(&cfg, "shouldn't crash", NULL, NULL, 0, "ok")); printf("number with null section: %d\n", cfg_get_number(&cfg, NULL, "shouldn't crash", 1)); printf("number with null key: %d\n", cfg_get_number(&cfg, "shouldn't crash", NULL, 1)); printf("string with null default value: %s\n", cfg_get_string(&cfg, "doesn't", "exist", NULL, 0, NULL)); strcpy(buf, "didn't change"); printf("null default value, with value return parameter set: %s\n", cfg_get_string(&cfg, "still", "nonexistent", buf, 64, NULL)); printf("... and the buffer it returned: %s\n", buf); strcpy(buf, "didn't change"); printf("null default value on defined key with return parameter: %s\n", cfg_get_string(&cfg, "ducks", "weight", buf, 64, NULL)); printf("... and the buffer it returned: %s\n", buf); printf("\n"); printf("string boundary tests\n"); cfg_set_string(&cfg, "test", "test", "abcdefghijklmnopqrstuvwxyz???broken"); cfg_get_string(&cfg, "test", "test", buf, 26, "wtf"); printf("26 characters using defined value: %s\n", buf); cfg_get_string(&cfg, "fake section", "fake key", buf, 10, "1234567890???broken"); printf("10 characters using default value: %s\n", buf); cfg_get_string(&cfg, "fake section", "fake key", buf, 0, "huh?"); printf("zero-length buffer (this should be an empty string) \"%s\"\n", buf); printf("\n"); printf("testing cfg_set_ functions...\n"); printf("string in new section\n"); cfg_set_string(&cfg, "toast", "is", "tasty"); printf("string with new key in existing section\n"); cfg_set_string(&cfg, "toast", "tastes", "good"); printf("number in new section\n"); cfg_set_number(&cfg, "cowboy", "hats", 3); printf("number with new key in existing section\n"); cfg_set_number(&cfg, "cowboy", "boots", 4); printf("string with null section\n"); cfg_set_string(&cfg, NULL, "shouldn't", "crash"); printf("string with null key\n"); cfg_set_string(&cfg, "shouldn't", NULL, "crash"); printf("string with null value\n"); cfg_set_string(&cfg, "shouldn't", "crash", NULL); printf("re-reading that null string should return default value: %s\n", cfg_get_string(&cfg, "shouldn't", "crash", NULL, 0, "it does")); printf("number with null section\n"); cfg_set_number(&cfg, NULL, "don't segfault", 42); printf("number with null key\n"); cfg_set_number(&cfg, "don't segfault", NULL, 42); cfg_dump(&cfg); cfg_free(&cfg); return 0; } #endif /* TEST */ schismtracker-20250313/schism/config.c000066400000000000000000000352151476471630300175350ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "config.h" #include "keyboard.h" #include "util.h" #include "mem.h" #include "str.h" #include "palettes.h" #include #include #include "config-parser.h" #include "dmoz.h" #include "osdefs.h" /* --------------------------------------------------------------------- */ /* config settings */ // cfg_dir_modules, cfg_dir_samples, and cfg_dir_instruments all are only // allocated here because otherwise we'd need to refactor the entire text // entry widget stuff to be able to use dynamic buffers. char cfg_dir_modules[SCHISM_PATH_MAX + 1], cfg_dir_samples[SCHISM_PATH_MAX + 1], cfg_dir_instruments[SCHISM_PATH_MAX + 1], *cfg_dir_dotschism = NULL, *cfg_font = NULL; char cfg_video_interpolation[8]; char cfg_video_format[9]; int cfg_video_fullscreen = 0; int cfg_video_want_fixed = 0; int cfg_video_want_fixed_width = 0; int cfg_video_want_fixed_height = 0; int cfg_video_mousecursor = MOUSE_EMULATED; int cfg_video_width, cfg_video_height; int cfg_video_hardware = 0; int cfg_video_want_menu_bar = 1; // If these are set to zero, it means to use the // system key repeat or the default fallback values. int cfg_kbd_repeat_delay = 0; int cfg_kbd_repeat_rate = 0; // Date & time formats int cfg_str_date_format = STR_DATE_FORMAT_DEFAULT; int cfg_str_time_format = STR_TIME_FORMAT_DEFAULT; /* --------------------------------------------------------------------- */ static const char *schism_dotfolders[] = { #if defined(SCHISM_WIN32) || defined(SCHISM_MACOS) || defined(SCHISM_MACOSX) || defined(SCHISM_OS2) "Schism Tracker", #elif defined(SCHISM_WII) || defined(SCHISM_WIIU) ".", #else # ifdef __HAIKU__ "config/settings/schism", # else ".config/schism", # endif ".schism", #endif }; void cfg_init_dir(void) { #if defined(__amigaos4__) str_realloc(&cfg_dir_dotschism, "PROGDIR:", sizeof("PROGDIR:")); #else char *portable_file = NULL; char *app_dir = dmoz_get_exe_directory(); if (app_dir) portable_file = dmoz_path_concat(app_dir, "portable.txt"); if (portable_file && dmoz_path_is_file(portable_file)) { printf("In portable mode.\n"); cfg_dir_dotschism = str_dup(app_dir); } else { int found = 0; char *dot_dir = dmoz_get_dot_directory(); for (size_t i = 0; i < ARRAY_SIZE(schism_dotfolders); i++) { cfg_dir_dotschism = dmoz_path_concat(dot_dir, schism_dotfolders[i]); if (dmoz_path_is_directory(cfg_dir_dotschism)) { found = 1; break; } } if (!found) { cfg_dir_dotschism = dmoz_path_concat(dot_dir, schism_dotfolders[0]); printf("Creating directory %s\n", cfg_dir_dotschism); printf("Schism Tracker uses this directory to store your settings.\n"); if (dmoz_path_mkdir_recursive(cfg_dir_dotschism, 0777) != 0) { perror("Error creating directory"); fprintf(stderr, "Everything will still work, but preferences will not be saved.\n"); } } free(dot_dir); } free(app_dir); free(portable_file); #endif } /* --------------------------------------------------------------------- */ static void cfg_load_palette(cfg_file_t *cfg) { const char *palette_text = cfg_get_string(cfg, "General", "palette_cur", NULL, 0, NULL); if (palette_text && strlen(palette_text) >= 48) set_palette_from_string(palette_text); palette_load_preset(cfg_get_number(cfg, "General", "palette", 2)); } static void cfg_save_palette(cfg_file_t *cfg) { cfg_set_number(cfg, "General", "palette", current_palette_index); char palette_text[48 + 1] = {0}; palette_to_string(0, palette_text); cfg_set_string(cfg, "General", "palette_cur", palette_text); } /* --------------------------------------------------------------------------------------------------------- */ void cfg_load(void) { char *tmp; const char *ptr; int i; cfg_file_t cfg; tmp = dmoz_path_concat(cfg_dir_dotschism, "config"); cfg_init(&cfg, tmp); free(tmp); /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ cfg_get_string(&cfg, "Video", "interpolation", cfg_video_interpolation, ARRAY_SIZE(cfg_video_interpolation) - 1, "nearest"); cfg_get_string(&cfg, "Video", "format", cfg_video_format, ARRAY_SIZE(cfg_video_format) - 1, ""); cfg_video_width = cfg_get_number(&cfg, "Video", "width", 640); cfg_video_height = cfg_get_number(&cfg, "Video", "height", 400); cfg_video_fullscreen = !!cfg_get_number(&cfg, "Video", "fullscreen", 0); cfg_video_want_fixed = cfg_get_number(&cfg, "Video", "want_fixed", 0); cfg_video_want_fixed_width = cfg_get_number(&cfg, "Video", "want_fixed_width", 640 * 5); cfg_video_want_fixed_height = cfg_get_number(&cfg, "Video", "want_fixed_height", 400 * 6); cfg_video_mousecursor = cfg_get_number(&cfg, "Video", "mouse_cursor", MOUSE_EMULATED); cfg_video_mousecursor = CLAMP(cfg_video_mousecursor, 0, MOUSE_MAX_STATE); cfg_video_hardware = cfg_get_number(&cfg, "Video", "hardware", 1); cfg_video_want_menu_bar = !!cfg_get_number(&cfg, "Video", "want_menu_bar", 1); tmp = dmoz_get_home_directory(); cfg_get_string(&cfg, "Directories", "modules", cfg_dir_modules, ARRAY_SIZE(cfg_dir_modules) - 1, tmp); cfg_get_string(&cfg, "Directories", "samples", cfg_dir_samples, ARRAY_SIZE(cfg_dir_samples) - 1, tmp); cfg_get_string(&cfg, "Directories", "instruments", cfg_dir_instruments, ARRAY_SIZE(cfg_dir_instruments) - 1, tmp); free(tmp); ptr = cfg_get_string(&cfg, "Directories", "module_pattern", NULL, 0, NULL); if (ptr) { strncpy(cfg_module_pattern, ptr, ARRAY_SIZE(cfg_module_pattern) - 1); cfg_module_pattern[ARRAY_SIZE(cfg_module_pattern) - 1] = 0; } ptr = cfg_get_string(&cfg, "Directories", "export_pattern", NULL, 0, NULL); if (ptr) { strncpy(cfg_export_pattern, ptr, ARRAY_SIZE(cfg_export_pattern) - 1); cfg_export_pattern[ARRAY_SIZE(cfg_export_pattern) - 1] = 0; } ptr = cfg_get_string(&cfg, "General", "numlock_setting", NULL, 0, NULL); if (!ptr) status.fix_numlock_setting = NUMLOCK_GUESS; else if (strcasecmp(ptr, "on") == 0) status.fix_numlock_setting = NUMLOCK_ALWAYS_ON; else if (strcasecmp(ptr, "off") == 0) status.fix_numlock_setting = NUMLOCK_ALWAYS_OFF; else status.fix_numlock_setting = NUMLOCK_HONOR; cfg_kbd_repeat_delay = cfg_get_number(&cfg, "General", "key_repeat_delay", 0); cfg_kbd_repeat_rate = cfg_get_number(&cfg, "General", "key_repeat_rate", 0); ptr = cfg_get_string(&cfg, "General", "date_format", NULL, 0, NULL); if (ptr) { if (!strcasecmp(ptr, "mmmmdyyyy")) { cfg_str_date_format = STR_DATE_FORMAT_MMMMDYYYY; } else if (!strcasecmp(ptr, "dmmmmyyyy")) { cfg_str_date_format = STR_DATE_FORMAT_DMMMMYYYY; } else if (!strcasecmp(ptr, "yyyymmmmdd")) { cfg_str_date_format = STR_DATE_FORMAT_YYYYMMMMDD; } else if (!strcasecmp(ptr, "mdyyyy")) { cfg_str_date_format = STR_DATE_FORMAT_MDYYYY; } else if (!strcasecmp(ptr, "dmyyyy")) { cfg_str_date_format = STR_DATE_FORMAT_DMYYYY; } else if (!strcasecmp(ptr, "yyyymd")) { cfg_str_date_format = STR_DATE_FORMAT_YYYYMD; } else if (!strcasecmp(ptr, "mmddyyyy")) { cfg_str_date_format = STR_DATE_FORMAT_MMDDYYYY; } else if (!strcasecmp(ptr, "ddmmyyyy")) { cfg_str_date_format = STR_DATE_FORMAT_DDMMYYYY; } else if (!strcasecmp(ptr, "yyyymmdd")) { cfg_str_date_format = STR_DATE_FORMAT_YYYYMMDD; } else if (!strcasecmp(ptr, "iso8601")) { cfg_str_date_format = STR_DATE_FORMAT_ISO8601; } } ptr = cfg_get_string(&cfg, "General", "time_format", NULL, 0, NULL); if (ptr) { if (!strcasecmp(ptr, "12hr")) { cfg_str_time_format = STR_TIME_FORMAT_12HR; } else if (!strcasecmp(ptr, "24hr")) { cfg_str_time_format = STR_TIME_FORMAT_24HR; } } // Poll the system if it's available if (cfg_str_date_format == STR_DATE_FORMAT_DEFAULT || cfg_str_time_format == STR_TIME_FORMAT_DEFAULT) { str_date_format_t date; str_time_format_t time; if (os_get_locale_format(&date, &time)) { if (cfg_str_date_format == STR_DATE_FORMAT_DEFAULT) cfg_str_date_format = date; if (cfg_str_time_format == STR_TIME_FORMAT_DEFAULT) cfg_str_time_format = time; } } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ cfg_load_info(&cfg); cfg_load_patedit(&cfg); cfg_load_audio(&cfg); cfg_load_midi(&cfg); cfg_load_disko(&cfg); cfg_load_dmoz(&cfg); /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ if (cfg_get_number(&cfg, "General", "classic_mode", 0)) status.flags |= CLASSIC_MODE; else status.flags &= ~CLASSIC_MODE; if (cfg_get_number(&cfg, "General", "make_backups", 1)) status.flags |= MAKE_BACKUPS; else status.flags &= ~MAKE_BACKUPS; if (cfg_get_number(&cfg, "General", "numbered_backups", 0)) status.flags |= NUMBERED_BACKUPS; else status.flags &= ~NUMBERED_BACKUPS; i = cfg_get_number(&cfg, "General", "time_display", TIME_PLAY_ELAPSED); /* default to play/elapsed for invalid values */ if (i < 0 || i >= TIME_PLAYBACK) i = TIME_PLAY_ELAPSED; status.time_display = i; i = cfg_get_number(&cfg, "General", "vis_style", VIS_OSCILLOSCOPE); /* default to oscilloscope for invalid values */ if (i < 0 || i >= VIS_SENTINEL) i = VIS_OSCILLOSCOPE; status.vis_style = i; kbd_sharp_flat_toggle(cfg_get_number(&cfg, "General", "accidentals_as_flats", 0) == 1); #ifdef SCHISM_MACOSX # define DEFAULT_META 1 #else # define DEFAULT_META 0 #endif if (cfg_get_number(&cfg, "General", "meta_is_ctrl", DEFAULT_META)) status.flags |= META_IS_CTRL; else status.flags &= ~META_IS_CTRL; if (cfg_get_number(&cfg, "General", "altgr_is_alt", 1)) status.flags |= ALTGR_IS_ALT; else status.flags &= ~ALTGR_IS_ALT; if (cfg_get_number(&cfg, "Video", "lazy_redraw", 0)) status.flags |= LAZY_REDRAW; else status.flags &= ~LAZY_REDRAW; if (cfg_get_number(&cfg, "General", "midi_like_tracker", 0)) status.flags |= MIDI_LIKE_TRACKER; else status.flags &= ~MIDI_LIKE_TRACKER; str_realloc(&cfg_font, cfg_get_string(&cfg, "General", "font", cfg_font, 0, "font.cfg"), 0); cfg_load_palette(&cfg); /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ cfg_free(&cfg); } void cfg_midipage_save(void) { char *ptr; cfg_file_t cfg; ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); cfg_init(&cfg, ptr); free(ptr); cfg_save_midi(&cfg); cfg_write(&cfg); cfg_free(&cfg); } void cfg_save(void) { char *ptr; cfg_file_t cfg; ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); cfg_init(&cfg, ptr); free(ptr); // this wart is here because Storlek is retarded cfg_delete_key(&cfg, "Directories", "filename_pattern"); cfg_set_string(&cfg, "Directories", "modules", cfg_dir_modules); cfg_set_string(&cfg, "Directories", "samples", cfg_dir_samples); cfg_set_string(&cfg, "Directories", "instruments", cfg_dir_instruments); /* No, it's not a directory, but whatever. */ cfg_set_string(&cfg, "Directories", "module_pattern", cfg_module_pattern); cfg_set_string(&cfg, "Directories", "export_pattern", cfg_export_pattern); cfg_save_info(&cfg); cfg_save_patedit(&cfg); cfg_save_audio(&cfg); cfg_save_palette(&cfg); cfg_save_disko(&cfg); cfg_save_dmoz(&cfg); cfg_write(&cfg); cfg_free(&cfg); } void cfg_save_output(void) { char *ptr; cfg_file_t cfg; ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); cfg_init(&cfg, ptr); free(ptr); cfg_save_audio_playback(&cfg); cfg_write(&cfg); cfg_free(&cfg); } void cfg_atexit_save(void) { char *ptr; cfg_file_t cfg; ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); cfg_init(&cfg, ptr); free(ptr); cfg_atexit_save_audio(&cfg); /* TODO: move these config options to video.c, this is lame :) Or put everything here, which is what the note in audio_loadsave.cc says. Very well, I contradict myself. */ cfg_set_string(&cfg, "Video", "interpolation", cfg_video_interpolation); cfg_set_number(&cfg, "Video", "fullscreen", !!(video_is_fullscreen())); cfg_set_number(&cfg, "Video", "mouse_cursor", video_mousecursor_visible()); cfg_set_number(&cfg, "Video", "lazy_redraw", !!(status.flags & LAZY_REDRAW)); cfg_set_number(&cfg, "Video", "hardware", !!(video_is_hardware())); cfg_set_number(&cfg, "Video", "want_menu_bar", !!cfg_video_want_menu_bar); cfg_set_number(&cfg, "General", "vis_style", status.vis_style); cfg_set_number(&cfg, "General", "time_display", status.time_display); cfg_set_number(&cfg, "General", "classic_mode", !!(status.flags & CLASSIC_MODE)); cfg_set_number(&cfg, "General", "make_backups", !!(status.flags & MAKE_BACKUPS)); cfg_set_number(&cfg, "General", "numbered_backups", !!(status.flags & NUMBERED_BACKUPS)); cfg_set_number(&cfg, "General", "accidentals_as_flats", (kbd_sharp_flat_state() == KBD_SHARP_FLAT_FLATS)); cfg_set_number(&cfg, "General", "meta_is_ctrl", !!(status.flags & META_IS_CTRL)); cfg_set_number(&cfg, "General", "altgr_is_alt", !!(status.flags & ALTGR_IS_ALT)); cfg_set_number(&cfg, "General", "midi_like_tracker", !!(status.flags & MIDI_LIKE_TRACKER)); /* Say, whose bright idea was it to make this a string setting? The config file is human editable but that's mostly for developer convenience and debugging purposes. These sorts of things really really need to be options in the GUI so that people don't HAVE to touch the settings. Then we can just use an enum (and we *could* in theory include comments to the config by default listing what the numbers are, but that shouldn't be necessary in most cases. */ switch (status.fix_numlock_setting) { case NUMLOCK_ALWAYS_ON: cfg_set_string(&cfg, "General", "numlock_setting", "on"); break; case NUMLOCK_ALWAYS_OFF: cfg_set_string(&cfg, "General", "numlock_setting", "off"); break; case NUMLOCK_HONOR: cfg_set_string(&cfg, "General", "numlock_setting", "system"); break; case NUMLOCK_GUESS: /* leave empty */ break; }; /* hm... most of the time probably nothing's different, so saving the config file here just serves to make the backup useless. maybe add a 'dirty' flag to the config parser that checks if any settings are actually *different* from those in the file? */ cfg_write(&cfg); cfg_free(&cfg); } schismtracker-20250313/schism/dialog.c000066400000000000000000000345461476471630300175350ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "dialog.h" #include "vgamem.h" #include "widget.h" #include "song.h" #include "page.h" #include "keyboard.h" #include "mem.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", __func__);\ 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, DEFAULT_FG, 2); /* then the rest of the stuff */ if (dialogs[d].draw_const) dialogs[d].draw_const(); if (dialogs[d].text) draw_text(dialogs[d].text, dialogs[d].text_x, 27, 0, 2); n = dialogs[d].total_widgets; while (n) { n--; widget_draw_widget(dialogs[d].widgets + n, n == dialogs[d].selected_widget); } } } /* --------------------------------------------------------------------- */ void dialog_destroy(void) { int d; if (num_dialogs == 0) return; d = num_dialogs - 1; if (dialogs[d].type != DIALOG_CUSTOM) { free(dialogs[d].text); free(dialogs[d].widgets); } num_dialogs--; if (num_dialogs) { d--; widgets = dialogs[d].widgets; selected_widget = &(dialogs[d].selected_widget); total_widgets = &(dialogs[d].total_widgets); status.dialog_type = dialogs[d].type; } else { widgets = ACTIVE_PAGE.widgets; selected_widget = &(ACTIVE_PAGE.selected_widget); total_widgets = &(ACTIVE_PAGE.total_widgets); status.dialog_type = DIALOG_NONE; } /* it's up to the calling function to redraw the page */ } void dialog_destroy_all(void) { while (num_dialogs) dialog_destroy(); } /* --------------------------------------------------------------------- */ /* default callbacks */ void dialog_yes(void *data) { void (*action) (void *); ENSURE_DIALOG(return); action = dialogs[num_dialogs - 1].action_yes; if (!data) data = dialogs[num_dialogs - 1].data; dialog_destroy(); if (action) action(data); status.flags |= NEED_UPDATE; } void dialog_no(void *data) { void (*action) (void *); ENSURE_DIALOG(return); action = dialogs[num_dialogs - 1].action_no; if (!data) data = dialogs[num_dialogs - 1].data; dialog_destroy(); if (action) action(data); status.flags |= NEED_UPDATE; } void dialog_cancel(void *data) { void (*action) (void *); ENSURE_DIALOG(return); action = dialogs[num_dialogs - 1].action_cancel; if (!data) data = dialogs[num_dialogs - 1].data; dialog_destroy(); if (action) action(data); status.flags |= NEED_UPDATE; } void dialog_yes_NULL(void) { dialog_yes(NULL); } void dialog_no_NULL(void) { dialog_no(NULL); } void dialog_cancel_NULL(void) { dialog_cancel(NULL); } /* --------------------------------------------------------------------- */ int dialog_handle_key(struct key_event * k) { struct dialog *d = dialogs + num_dialogs - 1; ENSURE_DIALOG(return 0); if (d->handle_key && d->handle_key(k)) return 1; /* this SHOULD be handling on k->state press but the widget key handler is stealing that key. */ if (k->state == KEY_RELEASE && NO_MODIFIER(k->mod)) { switch (k->sym) { case SCHISM_KEYSYM_y: switch (status.dialog_type) { case DIALOG_YES_NO: case DIALOG_OK_CANCEL: dialog_yes(d->data); return 1; default: break; } break; case SCHISM_KEYSYM_n: switch (status.dialog_type) { case DIALOG_YES_NO: /* in Impulse Tracker, 'n' means cancel, not "no"! (results in different behavior on sample quality convert dialog) */ if (!(status.flags & CLASSIC_MODE)) { dialog_no(d->data); return 1; } SCHISM_FALLTHROUGH; case DIALOG_OK_CANCEL: dialog_cancel(d->data); return 1; default: break; } break; case SCHISM_KEYSYM_c: switch (status.dialog_type) { case DIALOG_YES_NO: case DIALOG_OK_CANCEL: break; default: return 0; } SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_ESCAPE: dialog_cancel(d->data); return 1; case SCHISM_KEYSYM_o: switch (status.dialog_type) { case DIALOG_YES_NO: case DIALOG_OK_CANCEL: break; default: return 0; } SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_RETURN: dialog_yes(d->data); return 1; default: break; } } return 0; } /* --------------------------------------------------------------------- */ /* these get called from dialog_create below */ static void dialog_create_ok(int textlen) { int d = num_dialogs; /* make the dialog as wide as either the ok button or the text, * whichever is more */ dialogs[d].text_x = 40 - (textlen / 2); if (textlen > 21) { dialogs[d].x = dialogs[d].text_x - 2; dialogs[d].w = textlen + 4; } else { dialogs[d].x = 26; dialogs[d].w = 29; } dialogs[d].h = 8; dialogs[d].y = 25; dialogs[d].widgets = (struct widget *)mem_alloc(sizeof(struct widget)); dialogs[d].total_widgets = 1; widget_create_button(dialogs[d].widgets + 0, 36, 30, 6, 0, 0, 0, 0, 0, dialog_yes_NULL, "OK", 3); } static void dialog_create_ok_cancel(int textlen) { int d = num_dialogs; /* the ok/cancel buttons (with the borders and all) are 21 chars, * so if the text is shorter, it needs a bit of padding. */ dialogs[d].text_x = 40 - (textlen / 2); if (textlen > 21) { dialogs[d].x = dialogs[d].text_x - 4; dialogs[d].w = textlen + 8; } else { dialogs[d].x = 26; dialogs[d].w = 29; } dialogs[d].h = 8; dialogs[d].y = 25; dialogs[d].widgets = mem_calloc(2, sizeof(struct widget)); dialogs[d].total_widgets = 2; widget_create_button(dialogs[d].widgets + 0, 31, 30, 6, 0, 0, 1, 1, 1, dialog_yes_NULL, "OK", 3); widget_create_button(dialogs[d].widgets + 1, 42, 30, 6, 1, 1, 0, 0, 0, dialog_cancel_NULL, "Cancel", 1); } static void dialog_create_yes_no(int textlen) { int d = num_dialogs; dialogs[d].text_x = 40 - (textlen / 2); if (textlen > 21) { dialogs[d].x = dialogs[d].text_x - 4; dialogs[d].w = textlen + 8; } else { dialogs[d].x = 26; dialogs[d].w = 29; } dialogs[d].h = 8; dialogs[d].y = 25; dialogs[d].widgets = mem_calloc(2, sizeof(struct widget)); dialogs[d].total_widgets = 2; widget_create_button(dialogs[d].widgets + 0, 30, 30, 7, 0, 0, 1, 1, 1, dialog_yes_NULL, "Yes", 3); widget_create_button(dialogs[d].widgets + 1, 42, 30, 6, 1, 1, 0, 0, 0, dialog_no_NULL, "No", 3); } /* --------------------------------------------------------------------- */ /* type can be DIALOG_OK, DIALOG_OK_CANCEL, or DIALOG_YES_NO * default_widget: 0 = ok/yes, 1 = cancel/no */ struct dialog *dialog_create(int type, const char *text, void (*action_yes) (void *data), void (*action_no) (void *data), int default_widget, void *data) { int textlen = strlen(text); int d = num_dialogs; #ifndef NDEBUG if ((type & DIALOG_BOX) == 0) { fprintf(stderr, "dialog_create called with bogus dialog type %d\n", type); return NULL; } #endif /* FIXME | hmm... a menu should probably be hiding itself when a widget gets selected. */ if (status.dialog_type & DIALOG_MENU) menu_hide(); dialogs[d].text = str_dup(text); dialogs[d].data = data; dialogs[d].action_yes = action_yes; dialogs[d].action_no = action_no; dialogs[d].action_cancel = NULL; /* ??? */ dialogs[d].selected_widget = default_widget; dialogs[d].draw_const = NULL; dialogs[d].handle_key = NULL; switch (type) { case DIALOG_OK: dialog_create_ok(textlen); break; case DIALOG_OK_CANCEL: dialog_create_ok_cancel(textlen); break; case DIALOG_YES_NO: dialog_create_yes_no(textlen); break; default: #ifndef NDEBUG fprintf(stderr, "this man should not be seen\n"); #endif type = DIALOG_OK_CANCEL; dialog_create_ok_cancel(textlen); break; } dialogs[d].type = type; widgets = dialogs[d].widgets; selected_widget = &(dialogs[d].selected_widget); total_widgets = &(dialogs[d].total_widgets); num_dialogs++; status.dialog_type = type; status.flags |= NEED_UPDATE; return &dialogs[d]; } /* --------------------------------------------------------------------- */ /* this will probably die painfully if two threads try to make custom dialogs at the same time */ struct dialog *dialog_create_custom(int x, int y, int w, int h, struct widget *dialog_widgets, int dialog_total_widgets, int dialog_selected_widget, void (*draw_const) (void), void *data) { struct dialog *d = dialogs + num_dialogs; /* FIXME | see dialog_create */ if (status.dialog_type & DIALOG_MENU) menu_hide(); num_dialogs++; d->type = DIALOG_CUSTOM; d->x = x; d->y = y; d->w = w; d->h = h; d->widgets = dialog_widgets; d->selected_widget = dialog_selected_widget; d->total_widgets = dialog_total_widgets; d->draw_const = draw_const; d->text = NULL; d->data = data; d->action_yes = NULL; d->action_no = NULL; d->action_cancel = NULL; d->handle_key = NULL; status.dialog_type = DIALOG_CUSTOM; widgets = d->widgets; selected_widget = &(d->selected_widget); total_widgets = &(d->total_widgets); status.flags |= NEED_UPDATE; return d; } /* --------------------------------------------------------------------- */ /* Other prompt stuff */ static const char *numprompt_title, *numprompt_secondary; static int numprompt_smp_pos1, numprompt_smp_pos2; /* used by the sample prompt */ static int numprompt_titlelen; /* used by the number prompt */ static char numprompt_buf[4]; static struct widget numprompt_widgets[2]; static void (*numprompt_finish)(int n); /* this is bound to the textentry's activate callback. since this dialog might be called from another dialog as well as from a page, it can't use the normal dialog_yes handler -- it needs to destroy the prompt dialog first so that ACTIVE_WIDGET points to whatever thumbbar actually triggered the dialog box. */ static void numprompt_value(void) { char *eptr; long n = strtol(numprompt_buf, &eptr, 10); dialog_destroy(); if (eptr > numprompt_buf && eptr[0] == '\0') numprompt_finish(n); } static void numprompt_draw_const(void) { int wx = numprompt_widgets[0].x; int wy = numprompt_widgets[0].y; int ww = numprompt_widgets[0].width; draw_text(numprompt_title, wx - numprompt_titlelen - 1, wy, 3, 2); draw_box(wx - 1, wy - 1, wx + ww, wy + 1, BOX_THICK | BOX_INNER | BOX_INSET); } void numprompt_create(const char *prompt, void (*finish)(int n), char initvalue) { int y = 26; // an indisputable fact of life int dlgwidth, dlgx, entryx; numprompt_title = prompt; numprompt_titlelen = strlen(prompt); numprompt_buf[0] = initvalue; numprompt_buf[1] = '\0'; /* Dialog is made up of border, padding (2 left, 1 right), frame around the text entry, the entry itself, and the prompt; the text entry is offset from the left of the dialog by 4 chars (padding + borders) plus the length of the prompt. */ dlgwidth = 2 + 3 + 2 + 4 + numprompt_titlelen; dlgx = (80 - dlgwidth) / 2; entryx = dlgx + 4 + numprompt_titlelen; widget_create_textentry(numprompt_widgets + 0, entryx, y, 4, 0, 0, 0, NULL, numprompt_buf, 3); numprompt_widgets[0].activate = numprompt_value; numprompt_widgets[0].d.textentry.cursor_pos = initvalue ? 1 : 0; numprompt_finish = finish; dialog_create_custom(dlgx, y - 2, dlgwidth, 5, numprompt_widgets, 1, 0, numprompt_draw_const, NULL); } static int strtonum99(const char *s) { // aaarghhhh int n = 0; if (!s || !*s) return -1; if (s[1]) { // two chars int c = tolower(*s); if (c >= '0' && c <= '9') n = c - '0'; else if (c >= 'a' && c <= 'g') n = c - 'a' + 10; else if (c >= 'h' && c <= 'z') n = c - 'h' + 10; else return -1; n *= 10; s++; } return *s >= '0' && *s <= '9' ? n + *s - '0' : -1; } static void smpprompt_value(SCHISM_UNUSED void *data) { int n = strtonum99(numprompt_buf); numprompt_finish(n); } static void smpprompt_draw_const(void) { int wx = numprompt_widgets[0].x; int wy = numprompt_widgets[0].y; int ww = numprompt_widgets[0].width; draw_text(numprompt_title, numprompt_smp_pos1, 25, 0, 2); draw_text(numprompt_secondary, numprompt_smp_pos2, 27, 0, 2); draw_box(wx - 1, wy - 1, wx + ww, wy + 1, BOX_THICK | BOX_INNER | BOX_INSET); } void smpprompt_create(const char *title, const char *prompt, void (*finish)(int n)) { struct dialog *dialog; numprompt_title = title; numprompt_secondary = prompt; numprompt_smp_pos1 = (81 - strlen(title)) / 2; numprompt_smp_pos2 = 41 - strlen(prompt); numprompt_buf[0] = '\0'; widget_create_textentry(numprompt_widgets + 0, 42, 27, 3, 1, 1, 1, NULL, numprompt_buf, 2); widget_create_button(numprompt_widgets + 1, 36, 30, 6, 0, 0, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); numprompt_finish = finish; dialog = dialog_create_custom(26, 23, 29, 10, numprompt_widgets, 2, 0, smpprompt_draw_const, NULL); dialog->action_yes = smpprompt_value; } schismtracker-20250313/schism/disko.c000066400000000000000000000603551476471630300174040ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "config-parser.h" #include "config.h" #include "dialog.h" #include "disko.h" #include "dmoz.h" #include "it.h" #include "page.h" #include "song.h" #include "util.h" #include "vgamem.h" #include "osdefs.h" #include "mem.h" #include "str.h" #include "player/sndfile.h" #include "player/cmixer.h" #include "player/snd_gm.h" #include "player/snd_fm.h" #define DW_BUFFER_SIZE 65536 static void _disko_midi_out_raw(SCHISM_UNUSED song_t *csf, SCHISM_UNUSED const unsigned char *data, SCHISM_UNUSED uint32_t len, SCHISM_UNUSED uint32_t delay); // --------------------------------------------------------------------------- static unsigned int disko_output_rate = 44100; static unsigned int disko_output_bits = 16; static unsigned int disko_output_channels = 2; void cfg_load_disko(cfg_file_t *cfg) { disko_output_rate = cfg_get_number(cfg, "Diskwriter", "rate", 44100); disko_output_bits = cfg_get_number(cfg, "Diskwriter", "bits", 16); disko_output_channels = cfg_get_number(cfg, "Diskwriter", "channels", 2); } void cfg_save_disko(cfg_file_t *cfg) { cfg_set_number(cfg, "Diskwriter", "rate", disko_output_rate); cfg_set_number(cfg, "Diskwriter", "bits", disko_output_bits); cfg_set_number(cfg, "Diskwriter", "channels", disko_output_channels); } // --------------------------------------------------------------------------- // stdio backend static void _dw_stdio_write(disko_t *ds, const void *buf, size_t len) { if (fwrite(buf, len, 1, ds->file) != 1) disko_seterror(ds, errno); } static void _dw_stdio_seek(disko_t *ds, int64_t pos, int whence) { #ifdef HAVE_NONPOSIX_FSEEK // On some platforms, fseek() does not conform to the POSIX // ideas of what should happen when you seek beyond EOF. On // those odd platforms we'll "fake" the behavior by seeking // to the end of the file and appending NUL bytes until we // reach the desired position. // // Ideally, we would save the requested position, and shove // the NUL byte crap into _dw_stdio_write, but this is good // enough for now. long start = ftell(ds->file); if (fseek(ds->file, 0, SEEK_END) < 0) { disko_seterror(ds, errno); return; } long end = ftell(ds->file); //seek back to where we started if (fseek(ds->file, start, SEEK_SET) < 0) { disko_seterror(ds, errno); return; } switch (whence) { default: case SEEK_SET: break; case SEEK_CUR: pos += start; break; case SEEK_END: pos += end; break; } if (fseek(ds->file, MIN(pos, end), SEEK_SET) < 0) { disko_seterror(ds, errno); return; } while (end++ < pos) { if (fputc('\0', ds->file) != EOF) continue; disko_seterror(ds, errno); return; } #else if (fseek(ds->file, pos, whence) < 0) disko_seterror(ds, errno); #endif } static int64_t _dw_stdio_tell(disko_t *ds) { int64_t pos = ftell(ds->file); if (pos < 0) disko_seterror(ds, errno); return pos; } // --------------------------------------------------------------------------- // memory backend // 0 => memory error, abandon ship static int _dw_bufcheck(disko_t *ds, size_t extend) { if (ds->pos + extend <= ds->length) return 1; ds->length = MAX(ds->length, ds->pos + extend); if (ds->length >= ds->allocated) { size_t newsize = MAX(ds->allocated + DW_BUFFER_SIZE, ds->length); uint8_t *new = realloc(ds->data, newsize); if (!new) { // Eek free(ds->data); ds->data = NULL; disko_seterror(ds, errno); return 0; } memset(new + ds->allocated, 0, newsize - ds->allocated); ds->data = new; ds->allocated = newsize; } return 1; } static void _dw_mem_write(disko_t *ds, const void *buf, size_t len) { if (_dw_bufcheck(ds, len)) { memcpy(ds->data + ds->pos, buf, len); ds->pos += len; } } static void _dw_mem_seek(disko_t *ds, int64_t offset, int whence) { // mostly from slurp_seek switch (whence) { default: case SEEK_SET: break; case SEEK_CUR: offset += ds->pos; break; case SEEK_END: offset += ds->length; break; } if (offset < 0) { disko_seterror(ds, EINVAL); return; } /* note: seeking doesn't cause a buffer resize. This is consistent with the behavior of stdio streams. Consider: FILE *f = fopen("f", "w"); fseek(f, 1000, SEEK_SET); fclose(f); This will produce a zero-byte file; the size is not extended until data is written. */ ds->pos = offset; } static int64_t _dw_mem_tell(disko_t *ds) { return (int64_t)ds->pos; } // --------------------------------------------------------------------------- void disko_write(disko_t *ds, const void *buf, size_t len) { if (len != 0 && !ds->error) ds->_write(ds, buf, len); } void disko_putc(disko_t *ds, unsigned char c) { disko_write(ds, &c, sizeof(c)); } void disko_seek(disko_t *ds, int64_t pos, int whence) { if (!ds->error) ds->_seek(ds, pos, whence); } /* used by multi-write */ static void disko_seekcur(disko_t *ds, int64_t pos) { disko_seek(ds, pos, SEEK_CUR); } int64_t disko_tell(disko_t *ds) { if (!ds->error) return ds->_tell(ds); return -1; } // align on a byte boundary of size `bytes' void disko_align(disko_t *ds, uint32_t bytes) { int64_t pos = disko_tell(ds); disko_seek(ds, (bytes - (pos % bytes)) % bytes, SEEK_CUR); } void disko_seterror(disko_t *ds, int err) { // Don't set an error if one already exists, and don't allow clearing an error value ds->error = errno = ds->error ? ds->error : err ? err : EINVAL; } // --------------------------------------------------------------------------- int disko_open(disko_t *ds, const char *filename) { if (!filename) return -1; #ifdef HAVE_ACCESS /* FIXME - make a replacement access() */ // Attempt to honor read-only (since we're writing them in such a roundabout way) if (access(filename, W_OK) != 0 && errno != ENOENT) return -1; #endif if (!ds) return -1; memset(ds, 0, sizeof(*ds)); ds->filename = str_dup(filename); if (asprintf(&ds->tempname, "%sXXXXXX", filename) < 0) { free(ds->filename); return -1; } ds->file = mkfstemp(ds->tempname); if (!ds->file) { free(ds->tempname); free(ds->filename); return -1; } setvbuf(ds->file, NULL, _IOFBF, DW_BUFFER_SIZE); ds->_write = _dw_stdio_write; ds->_seek = _dw_stdio_seek; ds->_tell = _dw_stdio_tell; return 0; } /* weird stupid magic numbers: * backup == 0, no backup * backup == 1, backup with ~ * else, backup with numberings */ int disko_close(disko_t *ds, int backup) { int err = ds->error; // try to preserve the *first* error set, because it's most likely to be interesting if (fclose(ds->file) == EOF && !err) { err = errno; } else if (!err) { // back up the old file if (backup) dmoz_path_make_backup(ds->filename, (backup != 1)); if (dmoz_path_rename(ds->tempname, ds->filename, 1) < 0) err = errno; } // If anything failed so far, kill off the temp file // // FIXME we need a dmoz_path_remove, because unlink() // is a stub on mac os, and windows will interpret // the path as ANSI instead of unicode if (err) unlink(ds->tempname); free(ds->tempname); free(ds->filename); if (err) { errno = err; return DW_ERROR; } else { return DW_OK; } } int disko_memopen(disko_t *ds) { if (!ds) return -1; memset(ds, 0, sizeof(*ds)); ds->data = calloc(DW_BUFFER_SIZE, sizeof(uint8_t)); if (!ds->data) return -1; ds->allocated = DW_BUFFER_SIZE; ds->_write = _dw_mem_write; ds->_seek = _dw_mem_seek; ds->_tell = _dw_mem_tell; return 0; } int disko_memclose(disko_t *ds, int keep_buffer) { int err = ds->error; if (!keep_buffer || err) free(ds->data); if (err) { errno = err; return DW_ERROR; } else { return DW_OK; } } // --------------------------------------------------------------------------- static void _export_setup(song_t *dwsong, int *bps) { song_lock_audio(); /* install our own */ memcpy(dwsong, current_song, sizeof(song_t)); /* shadow it */ // !!! FIXME: We should not be messing with this stuff here! dwsong->opl = NULL; // Prevent the current_song OPL being closed GM_Reset(dwsong, 1); // Reset the MIDI stuff to our own... csf_init_midi(dwsong, _disko_midi_out_raw); dwsong->multi_write = NULL; /* should be null already, but to be sure... */ csf_set_current_order(dwsong, 0); /* rather indirect way of resetting playback variables */ csf_set_wave_config(dwsong, disko_output_rate, disko_output_bits, (dwsong->flags & SONG_NOSTEREO) ? 1 : disko_output_channels); dwsong->mix_flags |= (SNDMIX_DIRECTTODISK | SNDMIX_NOBACKWARDJUMPS); dwsong->repeat_count = -1; // FIXME do this right dwsong->buffer_count = 0; dwsong->flags &= ~(SONG_PAUSED | SONG_PATTERNLOOP | SONG_ENDREACHED); dwsong->stop_at_order = -1; dwsong->stop_at_row = -1; // diskwriter should always output with best available quality, which // means using all available voices. dwsong->max_voices = MAX_VOICES; *bps = dwsong->mix_channels * ((dwsong->mix_bits_per_sample + 7) / 8); song_unlock_audio(); } static void _export_teardown(void) { // do nothing :) } // --------------------------------------------------------------------------- static int close_and_bind(song_t *dwsong, disko_t *ds, song_sample_t *sample, int bps) { disko_t dsshadow = *ds; uint32_t flags; if (disko_memclose(ds, 1) == DW_ERROR) return DW_ERROR; // kill the sample csf_destroy_sample(current_song, sample - current_song->samples); // and now, read in the data #ifdef WORDS_BIGENDIAN flags = SF_BE; #else flags = SF_LE; #endif switch (dwsong->mix_channels) { case 1: flags |= SF_M; break; case 2: flags |= SF_SI; break; default: free(dsshadow.data); return DW_ERROR; } // I think this is right? IDFK switch (dwsong->mix_bits_per_sample) { case 8: flags |= SF_PCMU | SF_8; break; case 16: flags |= SF_PCMS | SF_16; break; case 24: flags |= SF_PCMS | SF_24; break; case 32: flags |= SF_PCMS | SF_32; break; default: free(dsshadow.data); return DW_ERROR; } sample->length = dsshadow.length / bps; sample->c5speed = dwsong->mix_frequency; sample->volume = 64 * 4; sample->global_volume = 64; sample->panning = 32 * 4; { slurp_t slurp; slurp_memstream_free(&slurp, dsshadow.data, dsshadow.length); csf_read_sample(sample, flags, &slurp); unslurp(&slurp); } return DW_OK; } int disko_writeout_sample(int smpnum, int pattern, int dobind) { int ret = DW_OK; song_t dwsong; song_sample_t *sample; uint8_t buf[DW_BUFFER_SIZE]; disko_t ds; int bps; if (smpnum < 1 || smpnum >= MAX_SAMPLES) return DW_ERROR; if (disko_memopen(&ds) < 0) return DW_ERROR; _export_setup(&dwsong, &bps); dwsong.repeat_count = -1; // FIXME do this right csf_loop_pattern(&dwsong, pattern, 0); do { disko_write(&ds, buf, csf_read(&dwsong, buf, sizeof(buf)) * bps); if (ds.length >= (size_t) (MAX_SAMPLE_LENGTH * bps)) { /* roughly 3 minutes at 44khz -- surely big enough (?) */ ds.length = MAX_SAMPLE_LENGTH * bps; dwsong.flags |= SONG_ENDREACHED; } } while (!(dwsong.flags & SONG_ENDREACHED)); sample = current_song->samples + smpnum; if (close_and_bind(&dwsong, &ds, sample, bps) == DW_OK) { sprintf(sample->name, "Pattern %03d", pattern); if (dobind) { /* This is hideous */ sample->name[23] = 0xff; sample->name[24] = pattern; } } else { /* Balls. Something died. */ ret = DW_ERROR; } _export_teardown(); return ret; } int disko_multiwrite_samples(int firstsmp, int pattern) { int err = 0; song_t dwsong; song_sample_t *sample; uint8_t buf[DW_BUFFER_SIZE]; disko_t ds[MAX_CHANNELS] = {0}; int bps; size_t smpsize = 0; int smpnum = CLAMP(firstsmp, 1, MAX_SAMPLES); int n; _export_setup(&dwsong, &bps); dwsong.repeat_count = -1; // FIXME do this right csf_loop_pattern(&dwsong, pattern, 0); dwsong.multi_write = calloc(MAX_CHANNELS, sizeof(struct multi_write)); if (!dwsong.multi_write) err = errno ? errno : ENOMEM; if (!err) { for (n = 0; n < MAX_CHANNELS; n++) { if (disko_memopen(&ds[n]) < 0) { err = errno ? errno : EINVAL; break; } } } if (err) { /* you might think this code is insane, and you might be correct ;) but it's structured like this to keep all the early-termination handling HERE. */ _export_teardown(); err = err ? err : errno; free(dwsong.multi_write); for (n = 0; n < MAX_CHANNELS; n++) disko_memclose(&ds[n], 0); errno = err; return DW_ERROR; } for (n = 0; n < MAX_CHANNELS; n++) { dwsong.multi_write[n].data = &ds[n]; /* Dumb casts. (written this way to make the definition of song_t independent of disko) */ dwsong.multi_write[n].write = (void(*)(void*, const uint8_t*, size_t))disko_write; dwsong.multi_write[n].silence = (void(*)(void*, long))disko_seekcur; } do { /* buf is used as temp space for converting the individual channel buffers from 32-bit. the output is being handled well inside the mixer, so we don't have to do any actual writing here, but we DO need to make sure nothing died... */ smpsize += csf_read(&dwsong, buf, sizeof(buf)); if (smpsize >= MAX_SAMPLE_LENGTH) { /* roughly 3 minutes at 44khz -- surely big enough (?) */ smpsize = MAX_SAMPLE_LENGTH; dwsong.flags |= SONG_ENDREACHED; break; } for (n = 0; n < MAX_CHANNELS; n++) { if (ds[n].error) { // Kill the write, but leave the other files alone dwsong.flags |= SONG_ENDREACHED; break; } } } while (!(dwsong.flags & SONG_ENDREACHED)); for (n = 0; n < MAX_CHANNELS; n++) { if (!dwsong.multi_write[n].used) { /* this channel was completely empty - don't bother with it */ disko_memclose(&ds[n], 0); continue; } ds[n].length = MIN(ds[n].length, smpsize * bps); smpnum = csf_first_blank_sample(current_song, smpnum); if (smpnum < 0) break; sample = current_song->samples + smpnum; if (close_and_bind(&dwsong, &ds[n], sample, bps) == DW_OK) { sprintf(sample->name, "Pattern %03d, channel %02d", pattern, n + 1); } else { /* Balls. Something died. */ err = errno; } } for (; n < MAX_CHANNELS; n++) { if (disko_memclose(&ds[n], 0) != DW_OK) err = errno; } _export_teardown(); free(dwsong.multi_write); if (err) { errno = err; return DW_ERROR; } else { return DW_OK; } } // --------------------------------------------------------------------------- static song_t export_dwsong; static int export_bps; static disko_t *export_ds[MAX_CHANNELS + 1]; /* only [0] is used unless multichannel */ static const struct save_format *export_format = NULL; /* NULL == not running */ static struct widget diskodlg_widgets[1]; static size_t est_len; static int prgh; static timer_ticks_t export_start_time; static int canceled = 0; /* this sucks, but so do I */ static int disko_finish(void); static void diskodlg_draw(void) { int sec, pos; char buf[32]; if (!export_ds[0]) { /* what are we doing here?! */ dialog_destroy_all(); log_appendf(4, "disk export dialog was eaten by a grue!"); return; } sec = export_ds[0]->length / export_dwsong.mix_frequency; pos = export_ds[0]->length * 64 / est_len; snprintf(buf, 32, "Exporting song...%6d:%02d", sec / 60, sec % 60); buf[31] = '\0'; draw_text(buf, 27, 27, 0, 2); draw_fill_chars(24, 30, 55, 30, DEFAULT_FG, 0); draw_vu_meter(24, 30, 32, pos, prgh, prgh); draw_box(23, 29, 56, 31, BOX_THIN | BOX_INNER | BOX_INSET); } static void diskodlg_cancel(SCHISM_UNUSED void *ignored) { canceled = 1; export_dwsong.flags |= SONG_ENDREACHED; if (!export_ds[0]) { log_appendf(4, "export was already dead on the inside"); return; } for (int n = 0; export_ds[n]; n++) disko_seterror(export_ds[n], EINTR); /* The next disko_sync will notice the (artifical) error status and call disko_finish, which will clean up all the files. 'canceled' prevents disko_finish from making a second call to dialog_destroy (since this function is already being called in response to the dialog being canceled) and also affects the message it prints at the end. */ } static void disko_dialog_setup(size_t len); // this needs to be done to work around stupid inconsistent key-up code static void diskodlg_reset(SCHISM_UNUSED void *ignored) { disko_dialog_setup(est_len); } static void disko_dialog_setup(size_t len) { struct dialog *d = dialog_create_custom(22, 25, 36, 8, diskodlg_widgets, 0, 0, diskodlg_draw, NULL); d->action_yes = diskodlg_reset; d->action_no = diskodlg_reset; d->action_cancel = diskodlg_cancel; canceled = 0; /* stupid */ est_len = len; uint32_t r = (rand() >> 8) & 63; if (r <= 7) { prgh = 6; } else if (r <= 18) { prgh = 3; } else if (r <= 31) { prgh = 5; } else { prgh = 4; } } // --------------------------------------------------------------------------- static char *get_filename(const char *template, int n) { char *s, *sub, buf[4]; s = str_dup(template); sub = strcasestr(s, "%c"); if (!sub) { errno = EINVAL; free(s); return NULL; } str_from_num99(n, buf); sub[0] = buf[0]; sub[1] = buf[1]; return s; } int disko_export_song(const char *filename, const struct save_format *format) { int err = 0; int numfiles, n; if (export_format) { log_appendf(4, "Another export is already active"); errno = EAGAIN; return DW_ERROR; } // Stop any playing song before exporting to keep old behavior song_stop(); export_start_time = timer_ticks(); numfiles = format->f.export.multi ? MAX_CHANNELS : 1; _export_setup(&export_dwsong, &export_bps); if (numfiles > 1) { export_dwsong.multi_write = calloc(numfiles, sizeof(struct multi_write)); if (!export_dwsong.multi_write) err = errno ? errno : ENOMEM; } memset(export_ds, 0, sizeof(export_ds)); for (n = 0; n < numfiles; n++) { if (numfiles > 1) { char *tmp = get_filename(filename, n + 1); if (tmp) { export_ds[n] = calloc(1, sizeof(*export_ds[n])); disko_open(export_ds[n], tmp); free(tmp); } } else { export_ds[n] = calloc(1, sizeof(*export_ds[n])); disko_open(export_ds[n], filename); } if (!(export_ds[n] && format->f.export.head(export_ds[n], export_dwsong.mix_bits_per_sample, export_dwsong.mix_channels, export_dwsong.mix_frequency) == DW_OK)) { err = errno ? errno : EINVAL; break; } } if (err) { _export_teardown(); free(export_dwsong.multi_write); for (n = 0; export_ds[n]; n++) { disko_seterror(export_ds[n], err); /* keep from writing a bunch of useless files */ disko_close(export_ds[n], 0); } errno = err ? err : EINVAL; log_perror(filename); return DW_ERROR; } if (numfiles > 1) { for (n = 0; n < numfiles; n++) { export_dwsong.multi_write[n].data = export_ds[n]; /* Dumb casts, again */ export_dwsong.multi_write[n].write = (void(*)(void*, const uint8_t*, size_t))format->f.export.body; export_dwsong.multi_write[n].silence = (void(*)(void*, long))format->f.export.silence; } } log_appendf(5, " %" PRIu32 " Hz, %" PRIu32 " bit, %s", export_dwsong.mix_frequency, export_dwsong.mix_bits_per_sample, export_dwsong.mix_channels == 1 ? "mono" : "stereo"); export_format = format; status.flags |= DISKWRITER_ACTIVE; /* tell main to care about us */ uint32_t s = (csf_get_length(&export_dwsong) * export_dwsong.mix_frequency); disko_dialog_setup(s ? s : 1); return DW_OK; } /* main calls this periodically when the .wav exporter is busy */ int disko_sync(void) { uint8_t buf[DW_BUFFER_SIZE]; size_t frames; int n; if (!export_format) { log_appendf(4, "disko_sync: unexplained bacon"); return DW_SYNC_ERROR; /* no writer running (why are we here?) */ } frames = csf_read(&export_dwsong, buf, sizeof(buf)); if (!export_dwsong.multi_write) export_format->f.export.body(export_ds[0], buf, frames * export_bps); /* always check if something died, multi-write or not */ for (n = 0; export_ds[n]; n++) { if (export_ds[n]->error) { disko_finish(); return DW_SYNC_ERROR; } } /* update the progress bar (kind of messy, yes...) */ export_ds[0]->length += frames; status.flags |= NEED_UPDATE; if (export_dwsong.flags & SONG_ENDREACHED) { disko_finish(); return DW_SYNC_DONE; } else { return DW_SYNC_MORE; } } static int disko_finish(void) { int ret = DW_OK, n, tmp; int num_files = 0; size_t total_size = 0; // in bytes size_t samples_0; if (!export_format) { log_appendf(4, "disko_finish: unexplained eggs"); return DW_ERROR; /* no writer running (why are we here?) */ } if (!canceled) dialog_destroy(); samples_0 = export_ds[0]->length; for (n = 0; export_ds[n]; n++) { if (export_dwsong.multi_write && !export_dwsong.multi_write[n].used) { /* this channel was completely empty - don't bother with it */ disko_seterror(export_ds[n], EINVAL); /* kludge */ disko_close(export_ds[n], 0); } else { /* there was noise on this channel */ num_files++; if (export_format->f.export.tail(export_ds[n]) != DW_OK) { disko_seterror(export_ds[n], errno); } else { disko_seek(export_ds[n], 0, SEEK_END); total_size += disko_tell(export_ds[n]); } tmp = disko_close(export_ds[n], 0); if (ret == DW_OK) ret = tmp; } } memset(export_ds, 0, sizeof(export_ds)); _export_teardown(); free(export_dwsong.multi_write); export_format = NULL; status.flags &= ~DISKWRITER_ACTIVE; /* please unsubscribe me from your mailing list */ switch (ret) { case DW_OK: { const timer_ticks_t elapsed_ms = (timer_ticks() - export_start_time); log_appendf(5, " %.2f MiB (%" PRIuSZ ":%02" PRIuSZ ") written in %" PRIu64 ".%02" PRIu64 " sec", total_size / 1048576.0, samples_0 / disko_output_rate / 60, (samples_0 / disko_output_rate) % 60, (uint64_t)(elapsed_ms / 1000), (uint64_t)(elapsed_ms / 10 % 100)); break; } case DW_ERROR: /* hey, what was the filename? oops */ if (canceled) log_appendf(5, " Canceled"); else log_perror(" Write error"); break; default: log_appendf(5, " Internal error exporting song"); break; } return ret; } // --------------------------------------------------------------------------- struct pat2smp { int pattern, sample, bind; }; static void pat2smp_single(void *data) { struct pat2smp *ps = data; if (disko_writeout_sample(ps->sample, ps->pattern, ps->bind) == DW_OK) { set_page(PAGE_SAMPLE_LIST); } else { log_perror("Sample write"); status_text_flash("Error writing to sample"); } free(ps); } static void pat2smp_multi(void *data) { struct pat2smp *ps = data; if (disko_multiwrite_samples(ps->sample, ps->pattern) == DW_OK) { set_page(PAGE_SAMPLE_LIST); } else { log_perror("Sample multi-write"); status_text_flash("Error writing to samples"); } free(ps); } void song_pattern_to_sample(int pattern, int split, int bind) { struct pat2smp *ps; int n; if (split && bind) { log_appendf(4, "song_pattern_to_sample: internal error!"); return; } if (pattern < 0 || pattern >= MAX_PATTERNS) { return; } /* this is horrid */ for (n = 1; n < MAX_SAMPLES; n++) { song_sample_t *samp = song_get_sample(n); if (!samp) continue; if (((unsigned char) samp->name[23]) != 0xFF) continue; if (((unsigned char) samp->name[24]) != pattern) continue; status_text_flash("Pattern %d already linked to sample %d", pattern, n); return; } ps = mem_alloc(sizeof(struct pat2smp)); ps->pattern = pattern; int samp = sample_get_current(); ps->sample = samp ? samp : 1; ps->bind = bind; if (split) { /* Nothing to confirm, as this never overwrites samples */ pat2smp_multi(ps); } else { if (current_song->samples[ps->sample].data == NULL) { pat2smp_single(ps); } else { dialog_create(DIALOG_OK_CANCEL, "This will replace the current sample.", pat2smp_single, free, 1, ps); } } } // --------------------------------------------------------------------------- // MIDI export (unused for now) static void _disko_midi_out_raw(SCHISM_UNUSED song_t *csf, SCHISM_UNUSED const unsigned char *data, SCHISM_UNUSED uint32_t len, SCHISM_UNUSED uint32_t delay) { // nothing } schismtracker-20250313/schism/dmoz.c000066400000000000000000001566211476471630300172460ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* No, this has nothing whatsoever to do with dmoz.org, except for the 'directory' part. :) */ #include "headers.h" #include "it.h" #include "config-parser.h" #include "config.h" #include "charset.h" #include "song.h" #include "dmoz.h" #include "slurp.h" #include "util.h" #include "osdefs.h" #include "loadso.h" #include "mem.h" #include "str.h" #include "backend/dmoz.h" #include "fmt.h" #include #include #ifdef __amigaos4__ # include #endif #ifdef SCHISM_WIN32 # include # include # include # include #elif defined(SCHISM_MACOS) # include # include # include # include # include "macos-dirent.h" #elif defined(SCHISM_OS2) # include // TODO we shouldn't need this # define INCL_DOSFILEMGR # include #elif defined(HAVE_DIRENT_H) # include #else # error Unknown platform. Add code for this platform, or make a wrapper for dirent.h. #endif #ifdef SCHISM_WII #include // isfs is pretty much useless, but it might be interesting to browse it I guess static const char *devices[] = { "sd:/", "isfs:/", NULL, }; #elif defined(SCHISM_WIIU) static const char *devices[] = { "fs:/vol/external01", NULL, }; #endif #if defined(__amigaos4__) # define FALLBACK_DIR "." /* not used... */ #elif defined(SCHISM_WIN32) || defined(SCHISM_OS2) # define FALLBACK_DIR "C:\\" #elif defined(SCHISM_WII) # define FALLBACK_DIR "isfs:/" // always exists, seldom useful #elif defined(SCHISM_WIIU) # define FALLBACK_DIR "fs:/" // useless but "valid" #elif defined(SCHISM_MACOS) // resolves to the current working directory. // fairly useless but "always" exists ;) # define FALLBACK_DIR ":" #else /* POSIX? */ # define FALLBACK_DIR "/" #endif // backend for stuff static const schism_dmoz_backend_t *backend = NULL; /* --------------------------------------------------------------------------------------------------------- */ /* constants */ /* note: this has do be split up like this; otherwise it gets read as '\x9ad' which is the Wrong Thing. */ #define TITLE_DIRECTORY "\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a" \ "Directory\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a" #define TITLE_LIBRARY "\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9aLibrary\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a" #define DESCR_DIRECTORY "Directory" #define DESCR_UNKNOWN "Unknown sample format" /* memory allocation: how many files/dirs are allocated at a time */ #define FILE_BLOCK_SIZE 256 #define DIR_BLOCK_SIZE 32 /* --------------------------------------------------------------------------------------------------------- */ /* file format tables */ #define READ_INFO(t) fmt_##t##_read_info, static const fmt_read_info_func read_info_funcs[] = { #include "fmt-types.h" NULL /* This needs to be at the bottom of the list! */ }; /* --------------------------------------------------------------------------------------------------------- */ /* sorting stuff */ typedef int (*dmoz_fcmp_t) (const dmoz_file_t *a, const dmoz_file_t *b); typedef int (*dmoz_dcmp_t) (const dmoz_dir_t *a, const dmoz_dir_t *b); #define _DECL_CMP(name) \ static int dmoz_fcmp_##name(const dmoz_file_t *a, const dmoz_file_t *b); \ static int dmoz_dcmp_##name(const dmoz_dir_t *a, const dmoz_dir_t *b); _DECL_CMP(strcmp) _DECL_CMP(strcasecmp) _DECL_CMP(strverscmp) _DECL_CMP(strcaseverscmp) static int dmoz_fcmp_timestamp(const dmoz_file_t *a, const dmoz_file_t *b); static dmoz_fcmp_t dmoz_file_cmp = dmoz_fcmp_strcaseverscmp; static dmoz_dcmp_t dmoz_dir_cmp = dmoz_dcmp_strcaseverscmp; static struct { const char *name; dmoz_fcmp_t fcmp; dmoz_dcmp_t dcmp; } compare_funcs[] = { {"strcmp", dmoz_fcmp_strcmp, dmoz_dcmp_strcmp}, {"strcasecmp", dmoz_fcmp_strcasecmp, dmoz_dcmp_strcasecmp}, {"strverscmp", dmoz_fcmp_strverscmp, dmoz_dcmp_strverscmp}, {"timestamp", dmoz_fcmp_timestamp, dmoz_dcmp_strverscmp}, {"strcaseverscmp", dmoz_fcmp_strcaseverscmp, dmoz_dcmp_strcaseverscmp}, {NULL, NULL, NULL} }; /* --------------------------------------------------------------------------------------------------------- */ /* "selected" and cache */ struct dmoz_cache { struct dmoz_cache *next; char *path; char *cache_filen; char *cache_dirn; }; static struct dmoz_cache *cache_top = NULL; void dmoz_cache_update(const char *path, dmoz_filelist_t *fl, dmoz_dirlist_t *dl) { char *fn, *dn; if (fl && fl->selected > -1 && fl->selected < fl->num_files && fl->files[fl->selected]) fn = fl->files[fl->selected]->base; else fn = NULL; if (dl && dl->selected > -1 && dl->selected < dl->num_dirs && dl->dirs[dl->selected]) dn = dl->dirs[dl->selected]->base; else dn = NULL; dmoz_cache_update_names(path,fn,dn); } void dmoz_cache_update_names(const char *path, const char *filen, const char *dirn) { struct dmoz_cache *p, *lp; char *q; q = str_dup(path); lp = NULL; filen = filen ? dmoz_path_get_basename(filen) : NULL; dirn = dirn ? dmoz_path_get_basename(dirn) : NULL; if (filen && strcmp(filen, "..") == 0) filen = NULL; if (dirn && strcmp(dirn, "..") == 0) dirn = NULL; for (p = cache_top; p; p = p->next) { if (strcmp(p->path,q)==0) { free(q); if (filen) { free(p->cache_filen); p->cache_filen = str_dup(filen); } if (dirn) { free(p->cache_dirn); p->cache_dirn = str_dup(dirn); } if (lp) { lp->next = p->next; /* !lp means we're already cache_top */ p->next = cache_top; cache_top = p; } return; } lp = p; } p = mem_alloc(sizeof(struct dmoz_cache)); p->path = q; p->cache_filen = filen ? str_dup(filen) : NULL; p->cache_dirn = dirn ? str_dup(dirn) : NULL; p->next = cache_top; cache_top = p; } void dmoz_cache_lookup(const char *path, dmoz_filelist_t *fl, dmoz_dirlist_t *dl) { struct dmoz_cache *p; int i; if (fl) fl->selected = 0; if (dl) dl->selected = 0; for (p = cache_top; p; p = p->next) { if (strcmp(p->path,path) == 0) { if (fl && p->cache_filen) { for (i = 0; i < fl->num_files; i++) { if (!fl->files[i]) continue; if (strcmp(fl->files[i]->base, p->cache_filen) == 0) { fl->selected = i; break; } } } if (dl && p->cache_dirn) { for (i = 0; i < dl->num_dirs; i++) { if (!dl->dirs[i]) continue; if (strcmp(dl->dirs[i]->base, p->cache_dirn) == 0) { dl->selected = i; break; } } } break; } } } /* --------------------------------------------------------------------------------------------------------- */ /* get info about paths */ #ifdef SCHISM_MACOS /* Okay this is a bit dumb: * * Classic Mac OS's standard file API doesn't take in paths. * This is similar to the way the Wii U's filesystem API works, * and both suck and should be killed with fire. * * unfortunately, unlike the Wii U, there is no compatibility * layer provided by the standard library we're using, which * means we have to do all of this stupid conversion ourselves. * * - paper */ int dmoz_path_from_fsspec(const void *pvref, char **path) { OSErr err = noErr; FSSpec spec = *(const FSSpec *)pvref; // just dynamically allocate, whatever *path = mem_calloc(1, sizeof(char)); // the length of path INCLUDING the nul terminator size_t path_len = 1; short index = 0; do { CInfoPBRec cinfo; cinfo.dirInfo.ioNamePtr = spec.name; cinfo.dirInfo.ioVRefNum = spec.vRefNum; cinfo.dirInfo.ioFDirIndex = index; cinfo.dirInfo.ioDrDirID = spec.parID; err = PBGetCatInfoSync(&cinfo); if (err == noErr) { unsigned char name_len = spec.name[0]; // reallocate and move existing data *path = mem_realloc(*path, path_len + name_len + 1); memmove(*path + name_len + 1, *path, path_len); path_len += name_len + 1; // copy to the start of the buffer memcpy(*path + 1, &spec.name[1], name_len); (*path)[0] = ':'; // from here on out, ignore the input file name index = -1; // move up to the parent spec.parID = cinfo.dirInfo.ioDrParID; } } while (err == noErr && spec.parID != fsRtParID); // remove the preceding colon memmove(*path, *path + 1, --path_len); // paranoia... (*path)[path_len] = '\0'; // we're done here return (err == noErr); } int dmoz_path_to_fsspec(const char *path, void *pvref) { OSErr err = noErr; FSSpec spec = *(const FSSpec *)pvref; Str255 name; // Must be long enough for a partial pathname consisting of two segments (64 bytes) const char* p = path; const char* pEnd; size_t segLen; // Find the end of the path segment for (pEnd = ++p; *pEnd && *pEnd != ':'; ++pEnd); segLen = pEnd - p; // Try to find a volume that matches this name for (ItemCount volIndex = 1; err == noErr; ++volIndex) { FSVolumeRefNum volRefNum; HParamBlockRec hfsParams; name[0] = 0; hfsParams.volumeParam.ioNamePtr = name; hfsParams.volumeParam.ioVRefNum = 0; hfsParams.volumeParam.ioVolIndex = volIndex; err = PBHGetVInfoSync(&hfsParams); volRefNum = hfsParams.volumeParam.ioVRefNum; // Compare against our path segment if (err == noErr && segLen == StrLength(name)) { // FIXME FIXME READ ALL ABOUT IT: // HFS paths are not in codepage 437. The only reason it's like this here // is because we need a simple mostly-ASCII-compatible 8-bit fixed width // encoding, which codepage 437 just happens to be. if (!charset_strncasecmp(&name[1], CHARSET_CP437, path, CHARSET_UTF8, name[0])) { // we found our volume: fill in the spec err = FSMakeFSSpec(volRefNum, fsRtDirID, NULL, &spec); break; } } } p = pEnd; // okay, now we have the root directory. // NOW, we have to parse the rest of the path. // I have no idea if '.' or '..' are actual real filenames on Mac OS. while (err == noErr && *p) { switch (*p) { case ':': // skip any path separators ++p; break; case '.': // "current directory" or "parent directory" if (p[1] == '/' || p[1] == 0) { // "current directory" ++p; break; } else if (p[1] == '.' && (p[2] == '/' || p[2] == '\0')) { // "parent directory" p += 2; // Get the parent of our parent CInfoPBRec catInfo; catInfo.dirInfo.ioNamePtr = NULL; catInfo.dirInfo.ioVRefNum = spec.vRefNum; catInfo.dirInfo.ioFDirIndex = -1; catInfo.dirInfo.ioDrDirID = spec.parID; err = PBGetCatInfoSync(&catInfo); // Check that we didn't go too far if (err != noErr || catInfo.dirInfo.ioDrParID == fsRtParID) return false; // update the FSSpec if (err == noErr) err = FSMakeFSSpec(spec.vRefNum, catInfo.dirInfo.ioDrDirID, NULL, &spec); break; } SCHISM_FALLTHROUGH; default: { // Find the end of the path segment for (pEnd = p; *pEnd && *pEnd != ':'; ++pEnd) ; segLen = pEnd - p; // Check for name length overflow (XXX: what is this magic constant) if (segLen > 31) return 0; // Make a partial pathname from our current spec to the new object unsigned char *partial = &name[1]; // Partial filename leads with : *partial++ = ':'; const unsigned char *specName = spec.name; // Copy in spec name for (int cnt = *specName++; cnt > 0; --cnt) // vulgarities in my for loop *partial++ = *specName++; *partial++ = ':'; // Separator while (p != pEnd) *partial++ = *p++; // copy in the new element name[0] = partial - &name[1]; // Set the name length // Update the spec err = FSMakeFSSpec(spec.vRefNum, spec.parID, name, &spec); break; } } } // copy the result memcpy(pvref, &spec, sizeof(spec)); return err == noErr; } #endif const char *dmoz_path_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 *dmoz_path_get_extension(const char *filename) { filename = dmoz_path_get_basename(filename); const char *extension = strrchr(filename, '.'); if (!extension) { /* no extension? bummer. point to the \0 at the end of the string. */ extension = strchr(filename, '\0'); } return extension; } /* this function should output as you expect * e.g. if input is / it returns NULL, * if input is /home it returns / and * if input is /home/ it returns / * * the equivalent windows paths also work here */ char *dmoz_path_get_parent_directory(const char *dirname) { if (!dirname || !dirname[0]) return NULL; /* need the root path, including the separator */ const char* root = strchr(dirname, DIR_SEPARATOR); if (!root) return NULL; root++; /* okay, now we need to find the final token */ const char* pos = root + strlen(root) - 1; if (IS_DIR_SEPARATOR(*pos)) /* strip off an excess separator, if any */ pos--; while (--pos > root) { if (IS_DIR_SEPARATOR(*pos)) break; } ptrdiff_t n = pos - dirname; /* sanity check */ if (n <= 0) return NULL; char *ret = mem_alloc((n + 1) * sizeof(char)); memcpy(ret, dirname, n * sizeof(char)); ret[n] = '\0'; if (!strcmp(dirname, ret) || !ret[0]) { free(ret); return NULL; } return ret; } /* 0 = success, !0 = failed (check errno) */ int dmoz_path_make_backup(const char *filename, int numbered) { char *buf = mem_alloc(strlen(filename) + 7 + 1); if (numbered) { /* If some crazy person needs more than 65536 backup files, they probably have more serious issues to tend to. */ int n = 1, ret; do { sprintf(buf, "%s.%d~", filename, n++); ret = dmoz_path_rename(filename, buf, 0); } while (ret != 0 && errno == EEXIST && n < 65536); free(buf); return ret; } else { int ret; sprintf(buf, "%s~", filename); ret = dmoz_path_rename(filename, buf, 1); free(buf); return ret; } } #ifdef SCHISM_WIN32 # define DMOZ_PATH_RENAME_IMPL(TYPE, CHARSET, SUFFIX, CHMOD) \ static int dmoz_path_rename##SUFFIX(const char *old, const char *new, int overwrite) \ { \ int res = -1; \ \ TYPE *old_w = NULL, *new_w = NULL; \ if (!charset_iconv(old, &old_w, CHARSET_UTF8, CHARSET, SIZE_MAX) \ && !charset_iconv(new, &new_w, CHARSET_UTF8, CHARSET, SIZE_MAX)) { \ if (MoveFile##SUFFIX(old_w, new_w)) { \ win32_filecreated_callback(new); \ res = 0; \ } else if (overwrite) { \ if (MoveFileEx##SUFFIX(old_w, new_w, MOVEFILE_REPLACE_EXISTING)) { \ /* no callback here; file already existed */ \ res = 0; \ } else { \ CHMOD(new_w, 0777); \ CHMOD(old_w, 0777); \ \ SetFileAttributes##SUFFIX(new_w, FILE_ATTRIBUTE_NORMAL); \ SetFileAttributes##SUFFIX(new_w, FILE_ATTRIBUTE_TEMPORARY); \ \ /* wee */ \ if (MoveFile##SUFFIX(old_w, new_w) || (DeleteFile##SUFFIX(new_w) && MoveFile##SUFFIX(old_w, new_w))) { \ win32_filecreated_callback(new); \ res = 0; \ } \ } \ } \ } \ \ free(new_w); \ free(old_w); \ \ return res; \ } # ifdef SCHISM_WIN32_COMPILE_ANSI DMOZ_PATH_RENAME_IMPL(char, CHARSET_ANSI, A, _chmod) # endif DMOZ_PATH_RENAME_IMPL(WCHAR, CHARSET_WCHAR_T, W, _wchmod) # undef DMOZ_PATH_RENAME_IMPL #endif /* 0 = success, !0 = failed (check errno) */ int dmoz_path_rename(const char *old, const char *new, int overwrite) { #ifdef SCHISM_WIN32 // this is a mess, extracted from the old code // schism had in util.c // // much of this is probably unnecessary for // newer versions of windows, but I've kept // it here for builds compiled for ancient // windows. int ret = -1; DWORD em = SetErrorMode(0); # ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & UINT32_C(0x80000000)) { ret = dmoz_path_renameA(old, new, overwrite); } else # endif { ret = dmoz_path_renameW(old, new, overwrite); } switch (GetLastError()) { case ERROR_ALREADY_EXISTS: case ERROR_FILE_EXISTS: if (!overwrite) errno = EEXIST; break; default: break; } // reset this... SetErrorMode(em); return ret; #elif defined(SCHISM_MACOS) // This is stupid, but it's what MacPerl does, so I guess // this is probbaly the only way we can pull this off // sanely. // // Note: there is in fact a HFS function to do exactly this // (namely PBHMoveRename) but unfortunately I think it only // actually works on network shares which is completely // useless for us. OSErr err = noErr; unsigned char pold[256], pnew[256]; int truncated; str_to_pascal(old, pold, &truncated); if (truncated) { errno = ENAMETOOLONG; return -1; } str_to_pascal(new, pnew, &truncated); if (truncated) { errno = ENAMETOOLONG; return -1; } FSSpec old_spec, new_spec; err = FSMakeFSSpec(0, 0, pold, &old_spec); switch (err) { case noErr: break; case nsvErr: case fnfErr: errno = ENOENT; return -1; default: log_appendf(4, "Unknown FSMakeFSSpec error: %d", (int)err); return -1; } err = FSMakeFSSpec(0, 0, pnew, &new_spec); if (err == fnfErr) { // Create the output file err = FSpCreate(&new_spec, '?\??\?', 'TEXT', smSystemScript); if (err != noErr) { log_appendf(4, "FSpCreate: %d", (int)err); return -1; } FSpCreateResFile(&new_spec, '?\??\?', 'TEXT', smSystemScript); err = ResError(); if (err != noErr) { log_appendf(4, "FSpCreateResFile: %d\n", (int)err); return -1; } } else if (overwrite && err == noErr) { // do nothing, exchange the contents and delete } else { // handle error value and set error value appropriately switch (err) { case noErr: errno = EEXIST; return -1; case nsvErr: errno = ENOTDIR; return -1; default: log_appendf(4, "Unknown FSMakeFSSpec error: %d", (int)err); return -1; } } // Exchange the data in the two files err = FSpExchangeFiles(&old_spec, &new_spec); if (err != noErr) { log_appendf(4, "FSpExchangeFiles: %d", (int)err); return -1; } err = FSpDelete(&old_spec); if (err != noErr) { log_appendf(4, "FSpDelete: %d", (int)err); return -1; } return 0; #else # ifdef SCHISM_OS2 // XXX maybe this should be the regular implementation too? if (!overwrite && (access(new, F_OK) == -1)) { errno = EEXIST; return -1; } # else if (!overwrite) { if (link(old, new) == -1) return -1; /* This can occur when people are using a system with * broken link() semantics, or if the user can create files * that he cannot remove. these systems are decidedly not POSIX.1 * but they may try to compile schism, and we won't know they * are broken unless we warn them. */ if (unlink(old) == -1) fprintf(stderr, "link() succeeded, but unlink() failed. something is very wrong\n"); return 0; } else # endif { int r = rename(old, new); /* Broken rename()? Try smashing the old file first, * and hope *that* doesn't also fail ;) */ if (r != 0 && errno == EEXIST && (unlink(old) != 0 || rename(old, new) == -1)) return -1; return r; } #endif } // recursive mkdir (equivalent to `mkdir -p`) int dmoz_path_mkdir_recursive(const char *path, int mode) { size_t i; int first = 0; // ugly hack: skip the first separator char *npath = dmoz_path_normal(path); if (!npath) { printf("nope\n"); return -1; } for (i = 0; npath[i]; i++) { struct stat st; if (!IS_DIR_SEPARATOR(npath[i])) continue; if (!first) { first = 1; continue; } // cut npath[i] = '\0'; printf("%s\n", npath); if (os_stat(npath, &st) < 0) { // path doesn't exist if (os_mkdir(npath, mode)) { free(npath); // errno is already useful here return -1; } } else { // path exists already, make sure it's a directory if (!S_ISDIR(st.st_mode)) { free(npath); errno = ENOTDIR; return -1; } } // paste npath[i] = DIR_SEPARATOR; } if (os_mkdir(npath, mode)) { free(npath); // errno is already useful here return -1; } free(npath); return 0; } int dmoz_path_is_file(const char *filename) { struct stat buf; if (os_stat(filename, &buf) == -1) { /* Well, at least we tried. */ return 0; } return S_ISREG(buf.st_mode); } int dmoz_path_is_directory(const char *filename) { struct stat buf; if (os_stat(filename, &buf) == -1) { /* Well, at least we tried. */ return 0; } return S_ISDIR(buf.st_mode); } unsigned long long dmoz_path_get_file_size(const char *filename) { struct stat buf; if (os_stat(filename, &buf) < 0) return EOF; if (S_ISDIR(buf.st_mode)) { errno = EISDIR; return EOF; } return buf.st_size; } /* --------------------------------------------------------------------------------------------------------- */ /* directories... */ #ifdef SCHISM_WIN32 // Functions that only exist on new systems; see dmoz_win32_get_csidl_directory static HRESULT (WINAPI *WIN32_SHGetFolderPathW)(HWND hwnd,int csidl,HANDLE hToken,DWORD dwFlags,LPWSTR pszPath) = NULL; static BOOL (WINAPI *WIN32_SHGetSpecialFolderPathA)(HWND hwnd, LPSTR pszPath, int csidl, BOOL fCreate) = NULL; static BOOL (WINAPI *WIN32_SHGetSpecialFolderPathW)(HWND hwnd, LPWSTR pszPath, int csidl, BOOL fCreate) = NULL; #endif char *dmoz_get_current_directory(void) { #ifdef SCHISM_MACOS FSSpec spec; if (FSMakeFSSpec(0, 0, NULL, &spec) == noErr) { char *path; if (dmoz_path_from_fsspec(&spec, &path)) return path; } #else # ifdef SCHISM_WIN32 { wchar_t buf[PATH_MAX + 1] = {L'\0'}; char *buf_utf8 = NULL; if (_wgetcwd(buf, PATH_MAX) && !charset_iconv(buf, &buf_utf8, CHARSET_WCHAR_T, CHARSET_UTF8, PATH_MAX + 1)) return buf_utf8; } # endif /* Double the buffer size until getcwd() succeeds or we run out * of memory. Not using get_current_dir_name() and * getcwd(NULL, n) to only use methods defined by POSIX. */ size_t n = SCHISM_PATH_MAX; // good starting point? char *buf = malloc(n); while (buf && !getcwd(buf, n)) { n *= 2; free(buf); buf = malloc(n); if (!buf) break; } if (buf) return buf; #endif return str_dup("."); } #ifdef SCHISM_WIN32 // SHGetFolderPath only exists under Windows XP and newer. // To combat this, we only use SHGetFolderPath if it // actually exists. Otherwise, we fallback to the obsolete // (and unsupported!) SHGetSpecialFolderPath, which exists // under systems with at least Internet Explorer 4 installed. // If that also doesn't work, we read the relevant registry // keys. If *that* doesn't work, we'll fallback to standard // environment variables. THEN we fallback to FALLBACK_DIR. // wow this sucks #define DMOZ_WIN32_GET_CSIDL_DIRECTORY_IMPL(TYPE, CHARSET, SUFFIX, GETENV, LITERAL) \ static inline SCHISM_ALWAYS_INLINE char *dmoz_win32_get_csidl_directory##SUFFIX(int csidl, const TYPE *registry, const TYPE *envvar) \ { \ { \ TYPE buf[MAX_PATH]; \ \ if (WIN32_SHGetSpecialFolderPath##SUFFIX && WIN32_SHGetSpecialFolderPath##SUFFIX(NULL, buf, csidl, 1)) { \ char *utf8; \ if (!charset_iconv(buf, &utf8, CHARSET, CHARSET_UTF8, sizeof(buf))) \ return utf8; \ } \ } \ \ if (registry) { \ static const TYPE *regpaths[] = { \ LITERAL##"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders", \ LITERAL##"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", /* Win95 */ \ }; \ int i; \ for (i = 0; i < ARRAY_SIZE(regpaths); i++) {\ HKEY shell_folders; \ if (RegOpenKeyEx##SUFFIX(HKEY_CURRENT_USER, regpaths[i], 0, KEY_READ, &shell_folders) != ERROR_SUCCESS) \ continue; \ \ DWORD type; \ DWORD length; \ \ if (RegQueryValueEx##SUFFIX(shell_folders, registry, NULL, &type, NULL, &length) != ERROR_SUCCESS \ || (type != REG_EXPAND_SZ && type != REG_SZ)) \ continue; \ \ TYPE *data = mem_alloc(length); \ \ if (RegQueryValueEx##SUFFIX(shell_folders, registry, NULL, NULL, (LPBYTE)data, &length) != ERROR_SUCCESS) { \ free(data); \ continue; \ } \ \ if (type == REG_EXPAND_SZ) {\ TYPE expanded[MAX_PATH]; \ if (ExpandEnvironmentStrings##SUFFIX((void *)data, expanded, ARRAY_SIZE(expanded))) { \ char *utf8; \ if (!charset_iconv(expanded, &utf8, CHARSET, CHARSET_UTF8, sizeof(expanded))) { \ free(data); \ return utf8; \ } \ } \ } else if (type == REG_SZ) { \ char *utf8; \ if (!charset_iconv(data, &utf8, CHARSET, CHARSET_UTF8, length)) { \ free(data); \ return utf8; \ } \ } \ \ free(data); \ } \ } \ \ if (envvar) { \ TYPE *ptr = GETENV(envvar); \ if (ptr) { \ char *utf8; \ \ if (!charset_iconv(ptr, &utf8, CHARSET, CHARSET_UTF8, MAX_PATH * sizeof(TYPE))) \ return utf8; \ } \ } \ \ return NULL; \ } DMOZ_WIN32_GET_CSIDL_DIRECTORY_IMPL(CHAR, CHARSET_ANSI, A, getenv, /* none */) DMOZ_WIN32_GET_CSIDL_DIRECTORY_IMPL(WCHAR, CHARSET_WCHAR_T, W, _wgetenv, L) #undef DMOZ_WIN32_GET_CSIDL_DIRECTORY_IMPL // Don't use this function directly! see the macro defined below static char *dmoz_win32_get_csidl_directory(int csidl, const wchar_t *registryw, const char *registry, const wchar_t *envvarw, const char *envvar) { #ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & UINT32_C(0x80000000)) { char *utf8 = dmoz_win32_get_csidl_directoryA(csidl, registry, envvar); if (utf8) return utf8; } else #endif { // Windows NT. { // special case: SHGetFolderPathW wchar_t bufw[MAX_PATH]; char *utf8; if (WIN32_SHGetFolderPathW && WIN32_SHGetFolderPathW(NULL, csidl, NULL, 0, bufw) == S_OK && !charset_iconv(bufw, &utf8, CHARSET_WCHAR_T, CHARSET_UTF8, MAX_PATH)) return utf8; } char *utf8 = dmoz_win32_get_csidl_directoryW(csidl, registryw, envvarw); if (utf8) return utf8; } // we'll get em next time return NULL; } // this just makes stuff simpler. // registry and envvar need to be compile time constants, // but really they should already be that anyway # define DMOZ_GET_WIN32_DIRECTORY(csidl, registry, envvar) dmoz_win32_get_csidl_directory(csidl, L ## registry, registry, L ## envvar, envvar) #elif defined(SCHISM_MACOS) static char *dmoz_macos_find_folder(OSType folder_type) { FSSpec spec; short vrefnum; long dir_id; // FIXME: i don't know if this first value should be kOnSystemDisk // or some other constant, since we want to work with multiple users if (FindFolder(kOnSystemDisk, folder_type, kCreateFolder, &vrefnum, &dir_id) == noErr && FSMakeFSSpec(vrefnum, dir_id, NULL, &spec) == noErr) { char *path; if (dmoz_path_from_fsspec(&spec, &path)) return path; } return NULL; } #endif char *dmoz_get_home_directory(void) { #if defined(__amigaos4__) return str_dup("PROGDIR:"); #elif defined(SCHISM_WIN32) // NOTE: USERPROFILE is not the documents folder, it's the user folder... char *ptr = DMOZ_GET_WIN32_DIRECTORY(CSIDL_PERSONAL, "Personal", "USERPROFILE"); if (ptr) return ptr; #elif defined(SCHISM_MACOS) // Taking heed from Windows, default to the Documents Folder. // // kDocumentsFolderType is supported by Mac OS 8 or later; I believe // we're already limited to just Mac OS 9 because we're using the // Multiprocessing library, so I won't add it as a fallback here. char *ptr = dmoz_macos_find_folder(kDocumentsFolderType); if (ptr) return ptr; #elif defined(SCHISM_OS2) { // First try these (this is what SDL does...) char *ptr; ptr = getenv("HOME"); if (ptr) return str_dup(ptr); ptr = getenv("ETC"); if (ptr) return str_dup(ptr); } #else char *ptr = getenv("HOME"); if (ptr) return str_dup(ptr); #endif /* hmm. fall back to the current dir */ char* path = dmoz_get_current_directory(); if (!strcmp(path, ".")) return path; free(path); /* still don't have a directory? sheesh. */ return str_dup(FALLBACK_DIR); } char *dmoz_get_dot_directory(void) { #ifdef SCHISM_WIN32 char *ptr = DMOZ_GET_WIN32_DIRECTORY(CSIDL_APPDATA, "AppData", "APPDATA"); if (ptr) return ptr; // else fall back to home (mesopotamian era windows I guess) #elif defined(SCHISM_MACOS) char *ptr = dmoz_macos_find_folder(kPreferencesFolderType); if (ptr) return ptr; // fall back to home (what?) #elif defined(SCHISM_MACOSX) char *ptr; ptr = macosx_get_application_support_dir(); if (ptr) return ptr; // this should never happen but prepare for it if it does char *home = dmoz_get_home_directory(); ptr = dmoz_path_concat(home, "Library/Application Support"); free(home); if (ptr) return ptr; #endif return dmoz_get_home_directory(); } char *dmoz_get_exe_directory(void) { if (backend && backend->get_exe_path) { char *path = backend->get_exe_path(); if (path) return path; } // unknown, don't care return NULL; } /* --------------------------------------------------------------------------------------------------------- */ /* path string hacking */ /* This function should: - strip out any parent directory references ("/sheep/../goat" => "/goat") - switch slashes to backslashes for MS systems ("c:/winnt" => "c:\\winnt") - condense multiple slashes into one ("/sheep//goat" => "/sheep/goat") - remove any trailing slashes [Amiga note: is foo:/bar the same as foo:bar, is it like /bar, or something else?] */ char *dmoz_path_normal(const char *path) { char stub_char; char *result, *p, *q, *base, *dotdot; int rooted; int count; /* The result cannot be larger than the input PATH. */ result = str_dup(path); rooted = dmoz_path_is_absolute(path, &count); base = result + (rooted ? count : 0); stub_char = rooted ? DIR_SEPARATOR : '.'; #if defined(SCHISM_WIN32) || defined(SCHISM_OS2) /* Stupid hack -- fix up any initial slashes in the absolute part of the path. (The rest of them will be handled as the path components are processed.) */ for (q = result; q < base; q++) if (*q == '/') *q = '\\'; #endif /* invariants: base points to the portion of the path we want to modify p points at beginning of path element we're considering. q points just past the last path element we wrote (no slash). dotdot points just past the point where .. cannot backtrack any further (no slash). */ p = q = dotdot = base; while (*p) { if (IS_DIR_SEPARATOR(p[0])) { /* null element */ p++; } else if (p[0] == '.' && (!p[1] || IS_DIR_SEPARATOR(p[1]))) { /* . and ./ */ p += 1; /* don't count the separator in case it is nul */ } else if (p[0] == '.' && p[1] == '.' && (!p[2] || IS_DIR_SEPARATOR(p[2]))) { /* .. and ../ */ p += 2; /* skip `..' */ if (q > dotdot) { /* can backtrack */ while (--q > dotdot && !IS_DIR_SEPARATOR(*q)) { /* nothing */ } } else if (!rooted) { /* /.. is / but ./../ is .. */ if (q != base) *q++ = DIR_SEPARATOR; *q++ = '.'; *q++ = '.'; dotdot = q; } } else { /* real path element */ /* add separator if not at start of work portion of result */ if (q != base) *q++ = DIR_SEPARATOR; while (*p && !IS_DIR_SEPARATOR(*p)) *q++ = *p++; } } /* Empty string is really ``.'' or `/', depending on what we started with. */ if (q == result) *q++ = stub_char; *q = '\0'; return result; } // count is only meaningful if this function returns 1. // otherwise, the value should be discarded. int dmoz_path_is_absolute(const char *path, int *count) { if (!path || !*path) return 0; #if defined(SCHISM_WIN32) || defined(SCHISM_OS2) if (isalpha(path[0]) && path[1] == ':') { if (count) *count = IS_DIR_SEPARATOR(path[2]) ? 3 : 2; return 1; } #elif defined(__amigaos4__) /* Entirely a guess -- could some fine Amiga user please tell me if this is right or not? */ char *colon = strchr(path, ':'), *slash = strchr(path, '/'); if (colon && (colon < slash || (colon && !slash && colon[1] == '\0'))) { int x = colon - path + 1; if (count) *count = x; return !!x; } #elif defined(SCHISM_WII) || defined(SCHISM_WIIU) char *colon = strchr(path, ':'), *slash = strchr(path, '/'); if (colon + 1 == slash) { int x = slash - path + 1; if (count) *count = x; return !!x; } #elif defined(SCHISM_MACOS) /* From Apple's documentation: * A leading colon indicates a relative path, otherwise * the first path component denotes the volume. * This is pretty much the opposite of what every other * OS does, and means the code below is simply wrong. */ if (IS_DIR_SEPARATOR(path[0])) return 0; if (count) *count = 0; return 1; #endif /* presumably, /foo (or \foo) is an absolute path on all platforms */ if (!IS_DIR_SEPARATOR(path[0])) return 0; /* POSIX says to allow two leading slashes, but not more. * (This also catches win32 \\share\blah\blah semantics) */ if (count) *count = ((IS_DIR_SEPARATOR(path[1]) && !IS_DIR_SEPARATOR(path[2])) ? 2 : 1); return 1; } /* See dmoz_path_concat_len. This function is a convenience for when the lengths aren't already known. */ char *dmoz_path_concat(const char *a, const char *b) { return dmoz_path_concat_len(a, b, strlen(a), strlen(b)); } /* Concatenate two paths. Additionally, if 'b' is an absolute path, ignore 'a' and return a copy of 'b'. */ char *dmoz_path_concat_len(const char *a, const char *b, int alen, int blen) { char *ret, *p; #ifndef SCHISM_MACOS /* blah, this is dumb */ if (dmoz_path_is_absolute(b, NULL)) return str_dup(b); #endif // allocates enough space for a, b, a separator, and a NUL terminator // (the terminator is included with ARRAY_SIZE(literal)) p = ret = mem_alloc(alen + blen + ARRAY_SIZE(DIR_SEPARATOR_STR)); if (alen) { char last = a[alen - 1]; memcpy(p, a, alen); p += alen; /* need a slash? */ #if defined(__amigaos4__) if (last != ':' && last != '/') *(p++) = '/'; #else if (!IS_DIR_SEPARATOR(last)) { memcpy(ret + alen, DIR_SEPARATOR_STR, ARRAY_SIZE(DIR_SEPARATOR_STR) - 1); p += ARRAY_SIZE(DIR_SEPARATOR_STR) - 1; } #endif } memcpy(p, b, blen); p += blen; *p = '\0'; return ret; } char *dmoz_path_pretty_name(const char *filename) { char *ret, *temp; const char *ptr; int len; ptr = strrchr(filename, DIR_SEPARATOR); ptr = ((ptr && ptr[1]) ? ptr + 1 : filename); len = strrchr(ptr, '.') - ptr; if (len <= 0) { ret = str_dup(ptr); } else { ret = mem_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 */ str_trim(ret); return ret; } /* --------------------------------------------------------------------------------------------------------- */ /* memory management */ static void allocate_more_files(dmoz_filelist_t *flist) { if (flist->alloc_size == 0) { flist->alloc_size = FILE_BLOCK_SIZE; flist->files = (dmoz_file_t **)mem_alloc(FILE_BLOCK_SIZE * sizeof(dmoz_file_t *)); } else { flist->alloc_size *= 2; flist->files = (dmoz_file_t **)mem_realloc(flist->files, flist->alloc_size * sizeof(dmoz_filelist_t *)); } } static void allocate_more_dirs(dmoz_dirlist_t *dlist) { if (dlist->alloc_size == 0) { dlist->alloc_size = DIR_BLOCK_SIZE; dlist->dirs = (dmoz_dir_t **)mem_alloc(DIR_BLOCK_SIZE * sizeof(dmoz_dir_t *)); } else { dlist->alloc_size *= 2; dlist->dirs = (dmoz_dir_t **)mem_realloc(dlist->dirs, dlist->alloc_size * sizeof(dmoz_dir_t *)); } } static void free_file(dmoz_file_t *file) { if (!file) return; if (file->smp_filename != file->base && file->smp_filename != file->title) { free(file->smp_filename); } free(file->path); free(file->base); if (file->type & TYPE_EXT_DATA_MASK) { if (file->artist) free(file->artist); free(file->title); /* if (file->sample) { if (file->sample->data) csf_free_sample(file->sample->data); free(file->sample); } */ } free(file); } static void free_dir(dmoz_dir_t *dir) { if (!dir) return; free(dir->path); free(dir->base); free(dir); } void dmoz_free(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist) { int n; if (flist) { for (n = 0; n < flist->num_files; n++) free_file(flist->files[n]); free(flist->files); flist->files = NULL; flist->num_files = 0; flist->alloc_size = 0; } if (dlist) { for (n = 0; n < dlist->num_dirs; n++) free_dir(dlist->dirs[n]); free(dlist->dirs); dlist->dirs = NULL; dlist->num_dirs = 0; dlist->alloc_size = 0; } } static int current_dmoz_file = 0; static dmoz_filelist_t *current_dmoz_filelist = NULL; static int (*current_dmoz_filter)(dmoz_file_t *) = NULL; static int *current_dmoz_file_pointer = NULL; static void (*dmoz_worker_onmove)(void) = NULL; int dmoz_worker(void) { dmoz_file_t *nf; if (!current_dmoz_filelist || !current_dmoz_filter) return 0; if (current_dmoz_file >= current_dmoz_filelist->num_files) { current_dmoz_filelist = NULL; current_dmoz_filter = NULL; if (dmoz_worker_onmove) dmoz_worker_onmove(); return 0; } if (!current_dmoz_filter(current_dmoz_filelist->files[ current_dmoz_file ])) { if (current_dmoz_filelist->num_files == current_dmoz_file+1) { current_dmoz_filelist->num_files--; current_dmoz_filelist = NULL; current_dmoz_filter = NULL; if (dmoz_worker_onmove) dmoz_worker_onmove(); return 0; } nf = current_dmoz_filelist->files[ current_dmoz_file ]; memmove(¤t_dmoz_filelist->files[ current_dmoz_file ], ¤t_dmoz_filelist->files[ current_dmoz_file+1 ], sizeof(dmoz_file_t *) * (current_dmoz_filelist->num_files - current_dmoz_file)); free_file(nf); current_dmoz_filelist->num_files--; if (current_dmoz_file_pointer && *current_dmoz_file_pointer >= current_dmoz_file) { (*current_dmoz_file_pointer) = (*current_dmoz_file_pointer) - 1; if (dmoz_worker_onmove) dmoz_worker_onmove(); } if (current_dmoz_file_pointer && *current_dmoz_file_pointer >= current_dmoz_filelist->num_files) { (*current_dmoz_file_pointer) = (current_dmoz_filelist->num_files-1); if (dmoz_worker_onmove) dmoz_worker_onmove(); } status.flags |= NEED_UPDATE; } else { current_dmoz_file++; } return 1; } /* filters a filelist and removes rejected entries. this works in-place so it can't generate error conditions. */ void dmoz_filter_filelist(dmoz_filelist_t *flist, int (*grep)(dmoz_file_t *f), int *pointer, void (*fn)(void)) { current_dmoz_filelist = flist; current_dmoz_filter = grep; current_dmoz_file = 0; current_dmoz_file_pointer = pointer; dmoz_worker_onmove = fn; } /* TODO: - create a one-shot filter that runs all its files at once - make these filters not actually drop the files from the list, but instead set the hidden flag - add a 'num_unfiltered' variable to the struct that indicates the total number */ /* --------------------------------------------------------------------------------------------------------- */ /* adding to the lists */ dmoz_file_t *dmoz_add_file(dmoz_filelist_t *flist, char *path, char *base, struct stat *st, int sort_order) { dmoz_file_t *file = mem_calloc(1, sizeof(dmoz_file_t)); file->path = path; file->base = base; file->sort_order = sort_order; file->sampsize = 0; file->instnum = -1; if (st == NULL || S_ISDIR(st->st_mode)) { file->type = TYPE_DIRECTORY; /* have to fill everything in for directories */ file->description = DESCR_DIRECTORY; file->title = str_dup(TITLE_DIRECTORY); } else if (S_ISREG(st->st_mode)) { file->type = TYPE_FILE_MASK; /* really ought to have a separate TYPE_UNCHECKED_FILE... */ } else { file->type = TYPE_NON_REGULAR; } if (st) { file->timestamp = st->st_mtime; file->filesize = st->st_size; } else { file->timestamp = 0; file->filesize = 0; } if (flist->num_files >= flist->alloc_size) allocate_more_files(flist); flist->files[flist->num_files++] = file; return file; } dmoz_dir_t *dmoz_add_dir(dmoz_dirlist_t *dlist, char *path, char *base, int sort_order) { dmoz_dir_t *dir = mem_calloc(1, sizeof(dmoz_dir_t)); dir->path = path; dir->base = base; dir->sort_order = sort_order; if (dlist->num_dirs >= dlist->alloc_size) allocate_more_dirs(dlist); dlist->dirs[dlist->num_dirs++] = dir; return dir; } void dmoz_add_file_or_dir(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist, char *path, char *base, struct stat *st, int sort_order) { if (dlist) dmoz_add_dir(dlist, path, base, sort_order); else dmoz_add_file(flist, path, base, st, sort_order); } /* --------------------------------------------------------------------------------------------------------- */ /* sorting */ #define _DEF_CMP_CHARSET(name) \ static int dmoz_fcmp_##name(const dmoz_file_t *a, const dmoz_file_t *b) \ { \ return charset_##name(a->base, CHARSET_CHAR, b->base, CHARSET_CHAR); \ } \ static int dmoz_dcmp_##name(const dmoz_dir_t *a, const dmoz_dir_t *b) \ { \ return charset_##name(a->base, CHARSET_CHAR, b->base, CHARSET_CHAR); \ } _DEF_CMP_CHARSET(strcmp) _DEF_CMP_CHARSET(strcasecmp) _DEF_CMP_CHARSET(strverscmp) _DEF_CMP_CHARSET(strcaseverscmp) #undef _DEF_CMP_CHARSET /* timestamp only works for files, so can't macro-def it */ static int dmoz_fcmp_timestamp(const dmoz_file_t *a, const dmoz_file_t *b) { return b->timestamp - a->timestamp; } static int qsort_cmp_file(const void *_a, const void *_b) { const dmoz_file_t *a = *(const dmoz_file_t **) _a; const dmoz_file_t *b = *(const dmoz_file_t **) _b; if ((b->type & TYPE_HIDDEN) && !(a->type & TYPE_HIDDEN)) return -1; /* b goes first */ if ((a->type & TYPE_HIDDEN) && !(b->type & TYPE_HIDDEN)) return 1; /* b goes first */ if (a->sort_order < b->sort_order) return -1; /* a goes first */ if (b->sort_order < a->sort_order) return 1; /* b goes first */ return (*dmoz_file_cmp)(a, b); } static int qsort_cmp_dir(const void *_a, const void *_b) { const dmoz_dir_t *a = *(const dmoz_dir_t **) _a; const dmoz_dir_t *b = *(const dmoz_dir_t **) _b; if (a->sort_order < b->sort_order) return -1; /* a goes first */ if (b->sort_order < a->sort_order) return 1; /* b goes first */ return (*dmoz_dir_cmp)(a, b); } void dmoz_sort(dmoz_filelist_t *flist, dmoz_dirlist_t *dlist) { qsort(flist->files, flist->num_files, sizeof(dmoz_file_t *), qsort_cmp_file); if (dlist) qsort(dlist->dirs, dlist->num_dirs, sizeof(dmoz_dir_t *), qsort_cmp_dir); } void cfg_load_dmoz(cfg_file_t *cfg) { const char *ptr; int i; ptr = cfg_get_string(cfg, "Directories", "sort_with", NULL, 0, NULL); if (ptr) { for (i = 0; compare_funcs[i].name; i++) { if (strcasecmp(compare_funcs[i].name, ptr) == 0) { dmoz_file_cmp = compare_funcs[i].fcmp; dmoz_dir_cmp = compare_funcs[i].dcmp; break; } } } } void cfg_save_dmoz(cfg_file_t *cfg) { int i; for (i = 0; compare_funcs[i].name; i++) { if (dmoz_file_cmp == compare_funcs[i].fcmp) { cfg_set_string(cfg, "Directories", "sort_with", compare_funcs[i].name); break; } } } /* --------------------------------------------------------------------------------------------------------- */ /* platform-specific directory navigation */ /* TODO: stat these? (certainly not critical, but would be nice) */ static void add_platform_dirs(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist) { char *ptr; #if defined(__amigaos4__) /* Amiga OS volume list from Juha Niemimäki */ struct DosList *pList; char *pTemp, *pString; int i, order = -1024; if ((pList = IDOS->LockDosList(LDF_VOLUMES | LDF_READ))) { while ((pList = IDOS->NextDosEntry(pList, LDF_VOLUMES))) { pTemp = pList->dol_Name << 2; if (pTemp && pTemp[0] > 0) { pString = calloc(pTemp[0] + 1, sizeof(char)); if (pString) { /* for (i = 0; i < pTemp[0]; i++) * pString[i] = pTemp[i + 1]; */ memcpy(pString, pTemp + 1, pTemp[0]); pString[pTemp[0]] = '\0'; dmoz_add_file_or_dir(flist, dlist, pString, str_dup(pString), NULL, order++); } } } IDOS->UnLockDosList(LDF_VOLUMES); } #elif defined(SCHISM_WIN32) || defined(SCHISM_OS2) # ifdef SCHISM_WIN32 const DWORD x = GetLogicalDrives(); UINT em = SetErrorMode(0); # elif defined(SCHISM_OS2) ULONG drive, x; DosQueryCurrentDisk(&drive, &x); # endif char sbuf[] = "A:\\"; for (; x && sbuf[0] <= 'Z'; sbuf[0]++) { if ((x >> (sbuf[0] - 'A')) & 1) { dmoz_add_file_or_dir(flist, dlist, str_dup(sbuf), str_dup(sbuf), NULL, -(1024 - 'A' - sbuf[0])); } } # ifdef SCHISM_WIN32 em = SetErrorMode(em); # endif #elif defined(SCHISM_WII) || defined(SCHISM_WIIU) int i; for (i = 0; devices[i]; i++) { DIR *dir = opendir(devices[i]); if (!dir) continue; closedir(dir); dmoz_add_file_or_dir(flist, dlist, str_dup(devices[i]), str_dup(devices[i]), NULL, -(1024 - i)); } #elif defined(SCHISM_MACOS) for (ItemCount index = 1; ; index++) { unsigned char ppath[256]; ppath[0] = 0; HParamBlockRec hfsParams = { .volumeParam = { .ioNamePtr = ppath, .ioVRefNum = 0, .ioVolIndex = index, }, }; OSErr err = PBHGetVInfoSync(&hfsParams); if (err != noErr) break; // FIXME: Need conversion to & from UTF-8 dmoz_add_file_or_dir(flist, dlist, strn_dup(&ppath[1], ppath[0]), strn_dup(&ppath[1], ppath[0]), NULL, -(1024 - index + 1)); } #else /* assume POSIX */ /* char *home; home = get_home_directory();*/ dmoz_add_file_or_dir(flist, dlist, str_dup("/"), str_dup("/"), NULL, -1024); /* dmoz_add_file_or_dir(flist, dlist, home, str_dup("~"), NULL, -5); */ #endif /* platform */ ptr = dmoz_path_get_parent_directory(path); if (ptr) dmoz_add_file_or_dir(flist, dlist, ptr, str_dup(".."), NULL, -10); } /* --------------------------------------------------------------------------------------------------------- */ /* on success, this will fill the lists and return 0. if something goes wrong, it adds a 'stub' entry for the root directory, and returns -1. */ int dmoz_read(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist, int (*load_library)(const char *path, dmoz_filelist_t *flist, dmoz_dirlist_t *dlist)) { #ifdef SCHISM_WIN32 DWORD attrib; # ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & 0x80000000U) { // Windows 9x char* path_a = NULL; if (charset_iconv(path, &path_a, CHARSET_UTF8, CHARSET_ANSI, SIZE_MAX)) return -1; attrib = GetFileAttributesA(path_a); free(path_a); } else # endif { // Windows NT wchar_t* path_w = NULL; if (charset_iconv(path, &path_w, CHARSET_UTF8, CHARSET_WCHAR_T, SIZE_MAX)) return -1; attrib = GetFileAttributesW(path_w); free(path_w); } const size_t pathlen = strlen(path); if (attrib & FILE_ATTRIBUTE_DIRECTORY) { union { # ifdef SCHISM_WIN32_COMPILE_ANSI WIN32_FIND_DATAA a; # endif WIN32_FIND_DATAW w; } ffd; HANDLE find = NULL; { char* searchpath_n = dmoz_path_concat_len(path, "*", strlen(path), 1); if (!searchpath_n) return -1; # ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & 0x80000000U) { char *searchpath; if (!charset_iconv(searchpath_n, &searchpath, CHARSET_UTF8, CHARSET_ANSI, SIZE_MAX)) { find = FindFirstFileA(searchpath, &ffd.a); free(searchpath); } } else # endif { wchar_t* searchpath; if (!charset_iconv(searchpath_n, &searchpath, CHARSET_UTF8, CHARSET_WCHAR_T, SIZE_MAX)) { find = FindFirstFileW(searchpath, &ffd.w); free(searchpath); } } free(searchpath_n); } // give up if (find == INVALID_HANDLE_VALUE) { add_platform_dirs(path, flist, dlist); return -1; } /* do-while to process the first file... */ for (;;) { DWORD file_attrib = 0; char *filename = NULL; char *fullpath = NULL; # ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & 0x80000000U) { // ANSI if (FindNextFileA(find, &ffd.a)) { file_attrib = ffd.a.dwFileAttributes; if (charset_iconv(ffd.a.cFileName, &filename, CHARSET_ANSI, CHARSET_UTF8, sizeof(ffd.a.cFileName))) continue; fullpath = dmoz_path_concat_len(path, filename, pathlen, strlen(filename)); } else { // ... break; } } else # endif { if (FindNextFileW(find, &ffd.w)) { file_attrib = ffd.w.dwFileAttributes; if (charset_iconv(ffd.w.cFileName, &filename, CHARSET_WCHAR_T, CHARSET_UTF8, sizeof(ffd.w.cFileName))) continue; fullpath = dmoz_path_concat_len(path, filename, pathlen, strlen(filename)); } else { break; } } if (!strcmp(filename, ".") || !strcmp(filename, "..") || filename[strlen(filename)-1] == '~' || file_attrib & FILE_ATTRIBUTE_HIDDEN) { free(filename); free(fullpath); continue; } struct stat st; if (os_stat(fullpath, &st) < 0) { /* doesn't exist? */ log_perror(fullpath); free(fullpath); free(filename); continue; /* better luck next time */ } st.st_mtime = MAX(0, st.st_mtime); if (file_attrib & FILE_ATTRIBUTE_DIRECTORY) { dmoz_add_file_or_dir(flist, dlist, fullpath, filename, &st, 0); } else if (file_attrib == INVALID_FILE_ATTRIBUTES) { free(fullpath); free(filename); } else { dmoz_add_file(flist, fullpath, filename, &st, 1); } } FindClose(find); add_platform_dirs(path, flist, dlist); } else if (attrib == INVALID_FILE_ATTRIBUTES) { add_platform_dirs(path, flist, dlist); return -1; } else { /* file? probably. * make sure when editing this code to keep it in sync * with the below code for non-Windows platforms */ if (load_library && !load_library(path, flist, dlist)) { char* ptr = dmoz_path_get_parent_directory(path); if (ptr) dmoz_add_file_or_dir(flist, dlist, ptr, str_dup("."), NULL, -10); else add_platform_dirs(path, flist, dlist); } } /* sort */ dmoz_sort(flist, dlist); return 0; #else DIR *dir; struct dirent *ent; char *ptr; struct stat st; int pathlen, namlen, lib = 0, err = 0; if (!path || !*path) path = "/"; pathlen = strlen(path); #ifdef SCHISM_WII /* and Wii U maybe? */ /* awful hack: libfat's file reads bail if a device is given without a slash. */ if (strchr(path, ':') != NULL && strchr(path, '/') == NULL) { int i; for (i = 0; devices[i]; i++) { if (strncmp(path, devices[i], pathlen) == 0) { path = devices[i]; break; } } } #endif dir = opendir(path); if (dir) { while ((ent = readdir(dir)) != NULL) { namlen = strlen(ent->d_name); /* ignore hidden/backup files (TODO: make this code more portable; some OSes have different ideas of whether a file is hidden) */ if (ent->d_name[0] == '.') continue; if (ent->d_name[namlen - 1] == '~') continue; { char *utf8; #ifdef SCHISM_OS2 utf8 = charset_iconv_easy(ent->d_name, CHARSET_DOSCP, CHARSET_UTF8); #else utf8 = ent->d_name; #endif ptr = dmoz_path_concat_len(path, utf8, pathlen, namlen); #ifdef SCHISM_OS2 free(utf8); #endif } if (os_stat(ptr, &st) < 0) { /* doesn't exist? */ log_perror(ptr); free(ptr); continue; /* better luck next time */ } if (st.st_mtime < 0) st.st_mtime = 0; if (S_ISDIR(st.st_mode)) dmoz_add_file_or_dir(flist, dlist, ptr, str_dup(ent->d_name), &st, 0); else if (S_ISREG(st.st_mode)) dmoz_add_file(flist, ptr, str_dup(ent->d_name), &st, 1); else free(ptr); } closedir(dir); } else if (errno == ENOTDIR) { /* oops, it's a file! -- load it as a library */ if (load_library && load_library(path, flist, dlist) != 0) err = errno; else lib = 1; } else { /* opendir failed? that's unpossible! */ err = errno; } /* more directories! * If this is actually a file, make a fake "." that actually points to the directory. * If something weird happens when trying to get the directory name, this falls back * to add_platform_dirs to keep from getting "stuck". */ if (lib && (ptr = dmoz_path_get_parent_directory(path)) != NULL) dmoz_add_file_or_dir(flist, dlist, ptr, str_dup("."), NULL, -10); else add_platform_dirs(path, flist, dlist); /* finally... sort it */ dmoz_sort(flist, dlist); if (err) { errno = err; return -1; } else { return 0; } #endif } /* --------------------------------------------------------------------------------------------------------- */ 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; if (file->filesize == 0) return FINF_EMPTY; if (slurp(&t, file->path, NULL, file->filesize) < 0) return FINF_ERRNO; file->artist = NULL; file->title = NULL; file->smp_defvol = 64; file->smp_gblvol = 64; for (const fmt_read_info_func *func = read_info_funcs; *func; func++) { slurp_rewind(&t); if ((*func) (file, &t)) { if (file->artist) str_trim(file->artist); if (file->title == NULL) file->title = str_dup(""); /* or the basename? */ str_trim(file->title); break; } } unslurp(&t); return file->title ? FINF_SUCCESS : FINF_UNSUPPORTED; } /* return: 1 on success, 0 on error. in either case, it fills the data in with *something*. */ int dmoz_filter_ext_data(dmoz_file_t *file) { int ret; if ((file->type & TYPE_EXT_DATA_MASK) || (file->type == TYPE_DIRECTORY)) { /* nothing to do */ return 1; } ret = file_info_get(file); switch (ret) { case FINF_SUCCESS: return 1; case FINF_UNSUPPORTED: file->description = "Unsupported file format"; /* used to be "Unsupported module format" */ break; case FINF_EMPTY: file->description = "Empty file"; break; case FINF_ERRNO: /* It would be nice to use the error string for the description, but there doesn't seem to be any easy/portable way to do that without dynamically allocating it (since strerror might return a static buffer), and str_dup'ing EVERY description is kind of a waste of memory. */ log_perror(file->base); file->description = "File error"; break; default: /* shouldn't ever happen */ file->description = "Internal error"; break; } file->type = TYPE_UNKNOWN; file->title = str_dup(""); return 0; } /* same as dmoz_filter_ext_data, except without the filtering effect when used with dmoz_filter_filelist */ int dmoz_fill_ext_data(dmoz_file_t *file) { dmoz_filter_ext_data(file); return 1; } /* ------------------------------------------------------------------------ */ #ifdef SCHISM_WIN32 static void *lib_shell32 = NULL; #endif int dmoz_init(void) { static const schism_dmoz_backend_t *backends[] = { // ordered by preference #ifdef SCHISM_WIN32 &schism_dmoz_backend_win32, #endif #ifdef SCHISM_MACOSX &schism_dmoz_backend_macosx, #endif #ifdef SCHISM_SDL3 &schism_dmoz_backend_sdl3, #endif #ifdef SCHISM_SDL2 &schism_dmoz_backend_sdl2, #endif NULL, }; int i; for (i = 0; backends[i]; i++) { backend = backends[i]; if (backend->init()) break; backend = NULL; } #ifdef SCHISM_WIN32 lib_shell32 = loadso_object_load("shell32.dll"); if (lib_shell32) { WIN32_SHGetFolderPathW = loadso_function_load(lib_shell32, "SHGetFolderPathW"); WIN32_SHGetSpecialFolderPathW = loadso_function_load(lib_shell32, "SHGetSpecialFolderPathW"); WIN32_SHGetSpecialFolderPathA = loadso_function_load(lib_shell32, "SHGetSpecialFolderPathA"); if (!WIN32_SHGetSpecialFolderPathA) WIN32_SHGetSpecialFolderPathA = loadso_function_load(lib_shell32, "SHGetSpecialFolderPath"); } #endif if (!backend) return 0; return 1; } void dmoz_quit(void) { if (backend) { backend->quit(); backend = NULL; } #ifdef SCHISM_WIN32 if (lib_shell32) loadso_object_unload(lib_shell32); #endif } schismtracker-20250313/schism/events.c000066400000000000000000000110301476471630300175610ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Set up the event queue */ #include "headers.h" #include "events.h" #include "mem.h" #include "osdefs.h" #include "config.h" // keyboard crap #include "mt.h" #include "backend/events.h" const schism_events_backend_t *events_backend = NULL; /* ------------------------------------------------------ */ static mt_mutex_t *queue_mutex = NULL; #define EVENTQUEUE_CAPACITY 128 static struct { int front, rear, size; schism_event_t events[EVENTQUEUE_CAPACITY]; } queue = {0}; static inline int queue_enqueue(const schism_event_t *event) { if (queue.size == EVENTQUEUE_CAPACITY) return 0; queue.events[queue.rear] = *event; queue.rear = (queue.rear + 1) % EVENTQUEUE_CAPACITY; queue.size++; return 1; } static inline int queue_dequeue(schism_event_t *event) { if (!queue.size) return 0; *event = queue.events[queue.front]; queue.front = (queue.front + 1) % EVENTQUEUE_CAPACITY; queue.size--; return 1; } /* ------------------------------------------------------ */ // Called back by the video backend; int events_init(const schism_events_backend_t *backend) { if (!backend) return 0; events_backend = backend; if (!events_backend->init()) return 0; if (cfg_kbd_repeat_delay && cfg_kbd_repeat_rate) { // Override everything. kbd_set_key_repeat(cfg_kbd_repeat_delay, cfg_kbd_repeat_rate); } else if (!(events_backend->flags & SCHISM_EVENTS_BACKEND_HAS_KEY_REPEAT)) { // Ok, we have to manually configure key repeat. int delay = cfg_kbd_repeat_delay, rate = cfg_kbd_repeat_rate; if ((!delay || !rate) && !os_get_key_repeat(&delay, &rate)) { delay = 500; rate = 30; } kbd_set_key_repeat(delay, rate); } queue_mutex = mt_mutex_create(); if (!queue_mutex) { os_show_message_box("Critical error!", "Failed to create event queue mutex..."); return 0; } return 1; } void events_quit(void) { if (queue_mutex) mt_mutex_delete(queue_mutex); if (events_backend) { events_backend->quit(); events_backend = NULL; } } int events_have_event(void) { mt_mutex_lock(queue_mutex); if (queue.size) { mt_mutex_unlock(queue_mutex); return 1; } // try pumping the events. events_backend->pump_events(); if (queue.size) { mt_mutex_unlock(queue_mutex); return 1; } mt_mutex_unlock(queue_mutex); return 0; } void events_pump_events(void) { // eh events_backend->pump_events(); } int events_poll_event(schism_event_t *event) { if (!event) return events_have_event(); mt_mutex_lock(queue_mutex); if (queue_dequeue(event)) { mt_mutex_unlock(queue_mutex); return 1; } // try pumping the events. events_backend->pump_events(); if (queue_dequeue(event)) { mt_mutex_unlock(queue_mutex); return 1; } mt_mutex_unlock(queue_mutex); // welp return 0; } // implicitly fills in the timestamp int events_push_event(const schism_event_t *event) { // An array of event filters. The reason this is used *here* // instead of in main is because we need to filter X11 events // *as they are pumped*, and putting win32 and macosx events // here is just a side effect of that. static int (*const event_filters[])(schism_event_t *event) = { #ifdef SCHISM_WIN32 win32_event, #endif #ifdef SCHISM_MACOSX macosx_event, #endif #ifdef SCHISM_USE_X11 x11_event, #endif NULL, }; int i; schism_event_t e = *event; e.common.timestamp = timer_ticks(); for (i = 0; event_filters[i]; i++) if (!event_filters[i](&e)) return 1; mt_mutex_lock(queue_mutex); queue_enqueue(&e); mt_mutex_unlock(queue_mutex); return 1; } schism_keymod_t events_get_keymod_state(void) { return events_backend->keymod_state(); } schismtracker-20250313/schism/fakemem.c000066400000000000000000000065261476471630300177000ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "song.h" #include "it.h" #include "fakemem.h" static int _cache_ok = 0; void memused_songchanged(void) { _cache_ok = 0; } /* packed patterns */ unsigned int memused_patterns(void) { unsigned int i, nm, rows, q; static unsigned int p_cached; song_note_t *ptr; if (_cache_ok & 1) return p_cached; _cache_ok |= 1; q = 0; nm = csf_get_num_patterns(current_song); for (i = 0; i < nm; i++) { if (csf_pattern_is_empty(current_song, i)) continue; rows = song_get_pattern(i, &ptr); q += (rows*256); } return p_cached = q; } unsigned int memused_clipboard(void) { unsigned int q = 0; static unsigned int c_cached; if (_cache_ok & 2) return c_cached; _cache_ok |= 2; memused_get_pattern_saved(&q, NULL); c_cached = q*256; return c_cached; } unsigned int memused_history(void) { static unsigned int h_cached; unsigned int q = 0; if (_cache_ok & 4) return h_cached; _cache_ok |= 4; memused_get_pattern_saved(NULL, &q); return h_cached = (q * 256); } unsigned int memused_samples(void) { song_sample_t *s; static unsigned int s_cache; unsigned int q, qs; int i; if (_cache_ok & 8) return s_cache; _cache_ok |= 8; q = 0; for (i = 0; i < MAX_SAMPLES; i++) { s = song_get_sample(i); qs = s->length; if (s->flags & CHN_STEREO) qs *= 2; if (s->flags & CHN_16BIT) qs *= 2; q += qs; } 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 < MAX_INSTRUMENTS; i++) { if (csf_instrument_is_empty(current_song->instruments[i])) continue; q += 512; } return i_cache = q; } unsigned int memused_songmessage(void) { static unsigned int m_cache; if (_cache_ok & 32) return m_cache; _cache_ok |= 32; return m_cache = strlen(current_song->message); } /* this makes an effort to calculate about how much memory IT would report is being taken up by the current song. it's pure, unadulterated crack, but the routines are useful for schism mode :) */ static unsigned int _align4k(unsigned int q) { return ((q + 0xfff) & ~0xfff); } unsigned int memused_ems(void) { return _align4k(memused_samples()) + _align4k(memused_history()) + _align4k(memused_patterns()); } unsigned int memused_lowmem(void) { return memused_songmessage() + memused_instruments() + memused_clipboard(); } schismtracker-20250313/schism/fonts.c000066400000000000000000000136741476471630300174260ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "dmoz.h" #include "config.h" #include "fonts.h" #include "util.h" #include "osdefs.h" /* --------------------------------------------------------------------- */ /* globals */ uint8_t font_normal[2048]; /* There's no way to change the other fontsets at the moment. * (other than recompiling, of course) */ uint8_t font_alt[2048]; uint8_t font_half_data[1024]; uint8_t *font_data = font_normal; /* this only needs to be global for itf */ /* int font_width = 8, font_height = 8; */ /* --------------------------------------------------------------------- */ /* ITF loader */ static inline void make_half_width_middot(void) { /* this copies the left half of char 184 in the normal font (two * half-width dots) to char 173 of the half-width font (the * middot), and the right half to char 184. thus, putting * together chars 173 and 184 of the half-width font will * produce the equivalent of 184 of the full-width font. */ font_half_data[173 * 4 + 0] = (font_normal[184 * 8 + 0] & 0xf0) | (font_normal[184 * 8 + 1] & 0xf0) >> 4; font_half_data[173 * 4 + 1] = (font_normal[184 * 8 + 2] & 0xf0) | (font_normal[184 * 8 + 3] & 0xf0) >> 4; font_half_data[173 * 4 + 2] = (font_normal[184 * 8 + 4] & 0xf0) | (font_normal[184 * 8 + 5] & 0xf0) >> 4; font_half_data[173 * 4 + 3] = (font_normal[184 * 8 + 6] & 0xf0) | (font_normal[184 * 8 + 7] & 0xf0) >> 4; font_half_data[184 * 4 + 0] = (font_normal[184 * 8 + 0] & 0xf) << 4 | (font_normal[184 * 8 + 1] & 0xf); font_half_data[184 * 4 + 1] = (font_normal[184 * 8 + 2] & 0xf) << 4 | (font_normal[184 * 8 + 3] & 0xf); font_half_data[184 * 4 + 2] = (font_normal[184 * 8 + 4] & 0xf) << 4 | (font_normal[184 * 8 + 5] & 0xf); font_half_data[184 * 4 + 3] = (font_normal[184 * 8 + 6] & 0xf) << 4 | (font_normal[184 * 8 + 7] & 0xf); } /* just the non-itf chars */ void font_reset_lower(void) { memcpy(font_normal, font_default_lower, 1024); } /* just the itf chars */ void font_reset_upper(void) { memcpy(font_normal + 1024, font_default_upper_itf, 1024); make_half_width_middot(); } /* all together now! */ void font_reset(void) { memcpy(font_normal, font_default_lower, 1024); memcpy(font_normal + 1024, font_default_upper_itf, 1024); make_half_width_middot(); } /* or kill the upper chars as well */ void font_reset_bios(void) { font_reset_lower(); memcpy(font_normal + 1024, font_default_upper_alt, 1024); make_half_width_middot(); } /* ... or just one character */ void font_reset_char(int ch) { uint8_t *base; int cx; ch <<= 3; cx = ch; if (ch >= 1024) { base = (uint8_t *) font_default_upper_itf; cx -= 1024; } else { base = (uint8_t *) font_default_lower; } /* update them both... */ memcpy(font_normal + ch, base + cx, 8); /* update */ make_half_width_middot(); } /* --------------------------------------------------------------------- */ static int squeeze_8x16_font(slurp_t *fp) { uint8_t data_8x16[4096]; int n; if (slurp_read(fp, data_8x16, 4096) != 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) { uint8_t data[4]; char *font_file; { char *font_dir = dmoz_path_concat(cfg_dir_dotschism, "fonts"); font_file = dmoz_path_concat(font_dir, filename); free(font_dir); } slurp_t fp = {0}; if (slurp(&fp, font_file, NULL, 0) < 0) { free(font_file); return -1; } size_t len = slurp_length(&fp); switch (len) { case 2048: /* raw font data */ break; case 2050: /* *probably* an ITF */ slurp_seek(&fp, 2048, SEEK_SET); if (slurp_read(&fp, data, 2) != 2) { unslurp(&fp); free(font_file); return -1; } if (data[1] != 0x2 || (data[0] != 0x12 && data[0] != 9)) { unslurp(&fp); free(font_file); return -1; } slurp_rewind(&fp); break; case 4096: /* raw font data, 8x16 */ if (squeeze_8x16_font(&fp) == 0) { make_half_width_middot(); unslurp(&fp); free(font_file); return 0; } else { unslurp(&fp); free(font_file); return -1; } break; default: unslurp(&fp); free(font_file); return -1; } if (slurp_read(&fp, font_normal, 2048) != 2048) { unslurp(&fp); free(font_file); return -1; } make_half_width_middot(); unslurp(&fp); free(font_file); return 0; } int font_save(const char *filename) { uint8_t ver[2] = { 0x12, 0x2 }; char *font_file; { char *font_dir = dmoz_path_concat(cfg_dir_dotschism, "fonts"); font_file = dmoz_path_concat(font_dir, filename); free(font_dir); } disko_t fp = {0}; if (disko_open(&fp, font_file) < 0) { free(font_file); return -1; } disko_write(&fp, font_normal, 2048); disko_write(&fp, ver, 2); disko_close(&fp, 0); free(font_file); return 0; } void font_init(void) { memcpy(font_half_data, font_half_width, 1024); if (font_load(cfg_font) != 0) font_reset(); memcpy(font_alt, font_default_lower, 1024); memcpy(font_alt + 1024, font_default_upper_alt, 1024); } schismtracker-20250313/schism/ieee-float.c000066400000000000000000000313001476471630300202710ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Routines to portably operate on IEEE floating point numbers. */ #include "headers.h" #include "bswap.h" #include "ieee-float.h" /* These are used for hardware encoding/decoding of floating point numbers. * Note that even if these types are available and conform to IEEE 754, * this doesn't mean that these operations are done in hardware. */ #if (SIZEOF_FLOAT == 4) typedef float float32; # define HAVE_FLOAT32 #elif (SIZEOF_DOUBLE == 4) typedef double float32; # define HAVE_FLOAT32 #elif (SIZEOF_LONG_DOUBLE == 4) typedef long double float32; # define HAVE_FLOAT32 #endif #if (SIZEOF_FLOAT == 8) typedef float float64; # define HAVE_FLOAT64 #elif (SIZEOF_DOUBLE == 8) typedef double float64; # define HAVE_FLOAT64 #elif (SIZEOF_LONG_DOUBLE == 8) typedef long double float64; # define HAVE_FLOAT64 #endif // FIXME: Intel has 80-bit precision, but many compilers // define long double as 128-bit and simply don't use the // other 48 bits. #if (SIZEOF_FLOAT == 10) typedef float float80; # define HAVE_FLOAT80 #elif (SIZEOF_DOUBLE == 10) typedef double float80; # define HAVE_FLOAT80 #elif (SIZEOF_LONG_DOUBLE == 10) typedef long double float80; # define HAVE_FLOAT80 #endif /* --------------------------------------------------------------------- */ /* 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. */ /* hacked together to use uintXX_t instead of unsigned long and friends */ #ifndef HUGE_VAL # define HUGE_VAL HUGE #endif /* HUGE_VAL */ #define FloatToUnsigned(f) ((uint32_t) (((int32_t) (f - 2147483648.0)) + 2147483647L + 1)) #define UnsignedToFloat(u) (((double) ((int32_t) (u - 2147483647L - 1))) + 2147483648.0) /**************************************************************** * Single precision IEEE floating-point conversion routines ****************************************************************/ #define SEXP_MAX 255 #define SEXP_OFFSET 127 #define SEXP_SIZE 8 #define SEXP_POSITION (32-SEXP_SIZE-1) double float_decode_ieee_32(const unsigned char bytes[4]) { #if defined(__STDC_IEC_559__) && defined(HAVE_FLOAT32) union { uint32_t u; float32 f; } x; memcpy(&x, bytes, 4); x.u = bswapBE32(x.u); return (double)x.f; #else double f; uint32_t mantissa, expon; uint32_t bits; bits = ((uint32_t)(bytes[0] & 0xFF) << 24) | ((uint32_t)(bytes[1] & 0xFF) << 16) | ((uint32_t)(bytes[2] & 0xFF) << 8) | (uint32_t)(bytes[3] & 0xFF); /* Assemble bytes into a long */ if ((bits & 0x7FFFFFFF) == 0) { f = 0; } else { expon = (bits & 0x7F800000) >> SEXP_POSITION; if (expon == SEXP_MAX) { /* Infinity or NaN */ f = HUGE_VAL; /* Map NaN's to infinity */ } else { if (expon == 0) { /* Denormalized number */ mantissa = (bits & 0x7fffff); f = ldexp((double)mantissa, expon - SEXP_OFFSET - SEXP_POSITION + 1); } else { /* Normalized number */ mantissa = (bits & 0x7fffff) + 0x800000; /* Insert hidden bit */ f = ldexp((double)mantissa, expon - SEXP_OFFSET - SEXP_POSITION); } } } return (bits & 0x80000000) ? -f : f; #endif } /****************************************************************/ void float_encode_ieee_32(double num, unsigned char bytes[4]) { #if defined(__STDC_IEC_559__) && defined(HAVE_FLOAT32) union { uint32_t u; float32 f; } x; x.f = num; x.u = bswapBE32(x.u); memcpy(bytes, &x, 4); #else uint32_t sign; register uint32_t bits; if (num < 0) { /* Can't distinguish a negative zero */ sign = 0x80000000; num *= -1; } else { sign = 0; } if (num == 0) { bits = 0; } else { double fMant; int expon; fMant = frexp(num, &expon); if ((expon > (SEXP_MAX-SEXP_OFFSET+1)) || !(fMant < 1)) { /* NaN's and infinities fail second test */ bits = sign | 0x7F800000; /* +/- infinity */ } else { long mantissa; if (expon < -(SEXP_OFFSET-2)) { /* Smaller than normalized */ int shift = (SEXP_POSITION+1) + (SEXP_OFFSET-2) + expon; if (shift < 0) { /* Way too small: flush to zero */ bits = sign; } else { /* Nonzero denormalized number */ mantissa = (long)(fMant * (1L << shift)); bits = sign | mantissa; } } else { /* Normalized number */ mantissa = (long)floor(fMant * (1L << (SEXP_POSITION+1))); mantissa -= (1L << SEXP_POSITION); /* Hide MSB */ bits = sign | ((long)((expon + SEXP_OFFSET - 1)) << SEXP_POSITION) | mantissa; } } } bytes[0] = bits >> 24; /* Copy to byte string */ bytes[1] = bits >> 16; bytes[2] = bits >> 8; bytes[3] = bits; #endif } /**************************************************************** * Double precision IEEE floating-point conversion routines ****************************************************************/ #define DEXP_MAX 2047 #define DEXP_OFFSET 1023 #define DEXP_SIZE 11 #define DEXP_POSITION (32-DEXP_SIZE-1) double float_decode_ieee_64(const unsigned char bytes[8]) { #if defined(__STDC_IEC_559__) && defined(HAVE_FLOAT64) union { float64 f; uint64_t u; } x; memcpy(&x, bytes, 8); x.u = bswapBE64(x.u); return (double)x.f; #else double f; uint32_t mantissa, expon; uint32_t first, second; first = ((uint32_t)(bytes[0] & 0xFF) << 24) | ((uint32_t)(bytes[1] & 0xFF) << 16) | ((uint32_t)(bytes[2] & 0xFF) << 8) | (uint32_t)(bytes[3] & 0xFF); second= ((uint32_t)(bytes[4] & 0xFF) << 24) | ((uint32_t)(bytes[5] & 0xFF) << 16) | ((uint32_t)(bytes[6] & 0xFF) << 8) | (uint32_t)(bytes[7] & 0xFF); if (first == 0 && second == 0) { f = 0; } else { expon = (first & 0x7FF00000) >> DEXP_POSITION; if (expon == DEXP_MAX) { /* Infinity or NaN */ f = HUGE_VAL; /* Map NaN's to infinity */ } else { if (expon == 0) { /* Denormalized number */ mantissa = (first & 0x000FFFFF); f = ldexp((double)mantissa, expon - DEXP_OFFSET - DEXP_POSITION + 1); f += ldexp(UnsignedToFloat(second), expon - DEXP_OFFSET - DEXP_POSITION + 1 - 32); } else { /* Normalized number */ mantissa = (first & 0x000FFFFF) + 0x00100000; /* Insert hidden bit */ f = ldexp((double)mantissa, expon - DEXP_OFFSET - DEXP_POSITION); f += ldexp(UnsignedToFloat(second), expon - DEXP_OFFSET - DEXP_POSITION - 32); } } } return (first & 0x80000000) ? -f : f; #endif } /****************************************************************/ void float_encode_ieee_64(double num, unsigned char bytes[8]) { #if defined(__STDC_IEC_559__) && defined(HAVE_FLOAT64) union { uint64_t u; float64 f; } x; x.f = num; x.u = bswapBE64(x.u); memcpy(bytes, &x, 8); #else uint32_t sign; uint32_t first, second; if (num < 0) { /* Can't distinguish a negative zero */ sign = 0x80000000; num *= -1; } else { sign = 0; } if (num == 0) { first = 0; second = 0; } else { double fMant, fsMant; int expon; fMant = frexp(num, &expon); if ((expon > (DEXP_MAX-DEXP_OFFSET+1)) || !(fMant < 1)) { /* NaN's and infinities fail second test */ first = sign | 0x7FF00000; /* +/- infinity */ second = 0; } else { uint32_t mantissa; if (expon < -(DEXP_OFFSET-2)) { /* Smaller than normalized */ int shift = (DEXP_POSITION+1) + (DEXP_OFFSET-2) + expon; if (shift < 0) { /* Too small for something in the MS word */ first = sign; shift += 32; if (shift < 0) { /* Way too small: flush to zero */ second = 0; } else { /* Pretty small demorn */ second = FloatToUnsigned(floor(ldexp(fMant, shift))); } } else { /* Nonzero denormalized number */ fsMant = ldexp(fMant, shift); mantissa = (uint32_t)floor(fsMant); first = sign | mantissa; second = FloatToUnsigned(floor(ldexp(fsMant - mantissa, 32))); } } else { /* Normalized number */ fsMant = ldexp(fMant, DEXP_POSITION+1); mantissa = (uint32_t)floor(fsMant); mantissa -= (1L << DEXP_POSITION); /* Hide MSB */ fsMant -= (1L << DEXP_POSITION); first = sign | ((uint32_t)((expon + DEXP_OFFSET - 1)) << DEXP_POSITION) | mantissa; second = FloatToUnsigned(floor(ldexp(fsMant - mantissa, 32))); } } } bytes[0] = first >> 24; bytes[1] = first >> 16; bytes[2] = first >> 8; bytes[3] = first; bytes[4] = second >> 24; bytes[5] = second >> 16; bytes[6] = second >> 8; bytes[7] = second; #endif } /* ------------------------------------------------------------------------ */ double float_decode_ieee_80(const unsigned char bytes[10]) { #if defined(__STDC_IEC_559__) && defined(HAVE_FLOAT80) union { unsigned char b[10]; float80 f; } x; memcpy(&x, bytes, 10); # ifndef WORDS_BIGENDIAN # define SWAP(y, z) do { unsigned char u = x.b[y]; x.b[y] = x.b[z]; x.b[y] = u; } while (0) SWAP(0, 9); SWAP(1, 8); SWAP(2, 7); SWAP(3, 6); SWAP(4, 5); # undef SWAP # endif return x.f; #else double f; int expon; uint32_t hiMant, loMant; expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF); hiMant = ((uint32_t) (bytes[2] & 0xFF) << 24) | ((uint32_t) (bytes[3] & 0xFF) << 16) | ((uint32_t) (bytes[4] & 0xFF) << 8) | ((uint32_t) (bytes[5] & 0xFF)); loMant = ((uint32_t) (bytes[6] & 0xFF) << 24) | ((uint32_t) (bytes[7] & 0xFF) << 16) | ((uint32_t) (bytes[8] & 0xFF) << 8) | ((uint32_t) (bytes[9] & 0xFF)); if (expon == 0 && hiMant == 0 && loMant == 0) { f = 0; } else if (expon == 0x7FFF) { /* Infinity or NaN */ f = HUGE_VAL; } else { expon -= 16383; f = ldexp(UnsignedToFloat(hiMant), expon -= 31); f += ldexp(UnsignedToFloat(loMant), expon -= 32); } if (bytes[0] & 0x80) return -f; else return f; #endif } void float_encode_ieee_80(double num, unsigned char bytes[10]) { #if defined(__STDC_IEC_559__) && defined(HAVE_FLOAT80) union { unsigned char b[10]; float80 f; } x; x.f = num; # ifndef WORDS_BIGENDIAN # define SWAP(y, z) do { unsigned char u = x.b[y]; x.b[y] = x.b[z]; x.b[y] = u; } while (0) SWAP(0, 9); SWAP(1, 8); SWAP(2, 7); SWAP(3, 6); SWAP(4, 5); # undef SWAP # endif memcpy(bytes, &x, 10); #else double fMant, fsMant; int expon; int32_t sign; uint32_t hiMant, loMant; if (num < 0) { sign = 0x8000; num *= -1; } else { sign = 0; } if (num == 0) { expon = 0; hiMant = 0; loMant = 0; } else { fMant = frexp(num, &expon); if ((expon > 16384) || !(fMant < 1)) { /* Infinity or NaN */ expon = sign | 0x7FFF; hiMant = 0; loMant = 0; /* infinity */ } else { /* Finite */ expon += 16382; if (expon < 0) { /* denormalized */ fMant = ldexp(fMant, expon); expon = 0; } expon |= sign; fMant = ldexp(fMant, 32); fsMant = floor(fMant); hiMant = FloatToUnsigned(fsMant); fMant = ldexp(fMant - fsMant, 32); fsMant = floor(fMant); loMant = FloatToUnsigned(fsMant); } } bytes[0] = expon >> 8; bytes[1] = expon; bytes[2] = hiMant >> 24; bytes[3] = hiMant >> 16; bytes[4] = hiMant >> 8; bytes[5] = hiMant; bytes[6] = loMant >> 24; bytes[7] = loMant >> 16; bytes[8] = loMant >> 8; bytes[9] = loMant; #endif } schismtracker-20250313/schism/isysev.c000066400000000000000000001111141476471630300176030ustar00rootroot00000000000000#include #include #include #include "util.h" #include "tree.h" /* TODO: string descriptions for joystick and midi keyboard events joystick axes and hats should emit synthetic repeated 'press' events come up with an api for handling unicode data velocity for joystick and midi should really be keeping the trees balanced... meh less stupid data structure than a linked list for the keysym translator? put the keymaps themselves into a tree of some sort, indexed by name. this way, it's possible to bind a key to change the current keymap -- e.g. bind Ctrl-X to a "set keymap" function with data="Ctrl-X map" need to make a list of all the things that can have associated keymaps... All of the modifier keys MIGHT additionally be presentable as keys themselves. (e.g. for FT2 style playback control bindings) However, if the platform doesn't support this, too bad. For the keyboard, most of the time the 'keycode' is a plain SDL keysym value. However, if the keysym was zero, the scancode is used instead, with the high bit set. Most of the time SDL does provide a keysym, but for some weird keys it might not. Scancodes are inherently non-portable, so this is only to allow extra keys to be bound to something. If the entire keycode value is zero, then we have no idea what key was pressed. (This happens for input methods e.g. scim that only send a unicode character.) For instrument list keyjazz, keydown events should keep track of the channel that got assigned to each note, and keyup should look up that channel and send a keyoff. Keymap files are split into sections denoted by [brackets] Each section defines a specific keymap. Note that this file can use the same parser as the config file. Widgets and pages use keymaps with certain names by default, but you can define keymaps with pretty much any name you want. the event parser is fairly liberal and can understand many different representations of keys. some examples follow: ; the Global map is always active, and receives all events not handled by a prior keymap [Global] F5=song_start_set_infopage Ctrl+F5 = song_start F8=song_stop kp_multiply = set_octave +1 MIDI/1 #87 = song_loop_current_pattern ; following event is actually the fourth button on the second joystick, but in numeric format. doing things ; this way is supported but not recommended, and is really more for debugging/troubleshooting than anything. @2/2 #3 = song_loop_pattern keyboard/0 shift+f9 = page_switch message_editor f9=page_switch load_module f10=page_switch save_module ^S = song_save escape = main_menu_toggle [Pattern Editor] M-q = pat_transpose +1 M-a = pat_transpose -1 M-S-q = pat_transpose +12 M-S-a = pat_transpose -12 [Sample List] keyboard/0 meta+A = smp_sign_convert Keymap lookup order: (prefix) | widget -> page -> global The "prefix" map is user-defined by the kmap_set_prefix binding, and is normally empty. This map can be used to implement multi-key events. For example: [Global] Ctrl-X = kmap_set_prefix ctrlx_map [ctrlx_map] Ctrl-S = song_save Ctrl-C = quit This effectively binds the Ctrl-X keys like Emacs, and also allows for "regular" bindings of those keys in other maps (e.g. Ctrl-C can still be bound to centralise cursor, and so forth) If a prefix map is active, no other keymaps are searched; in addition, prefix maps only last for one key. -- if (prefix_map && ev.bits.release) clear_prefix_map(); Additionally, a keymap can "inherit" keys from another map as follows. In this example, events not handled by the "hamster" keymap are checked against the pattern editor map: [hamster] @inherit = Pattern Editor One could conceivably define a key in the Pattern Editor map to load the hamster map, and a reciprocating key in the monsquaz map that changes it back. (FT2's "record mode" -- as well as IT's own capslock+key behavior -- could be implemented this way.) * Need a function to do this: it should replace the keymap that owns the key that was pressed to trigger the function. That is, if the keymap replace was bound to a key in the Pattern Editor map, the new keymap loaded replaces the pointer that was previously pointing to the Pattern Editor map. This way each keymap "layer" is independent and none of them can interfere with each other. Somehow the keymap bindings need to know the context they're being called in. For example, it would be entirely inappropriate to bind the left arrow to a thumbbar value adjust function from within the pattern editor keymap. */ // ------------------------------------------------------------------------------------------------------------ // Superficially similar to SDL's event structure, but packed much tighter. #pragma pack(push, 1) typedef union { struct { unsigned int dev_type : 4; // SKDEV_TYPE_whatever unsigned int dev_id : 4; // which device? (1->n; 0 is a pseudo "all" device) // note: not all "press" events have a corresponding "release" unsigned int release : 1; // 1 for key-up // next three fields are only relevant for the pc keyboard unsigned int repeat : 1; // 1 for synthetic key-repeat unsigned int unicode : 1; // 1 if character maps to printable unicode unsigned int modifier : 5; // ctrl, alt, shift unsigned int keycode : 16; // keyboard keysym/scancode } bits; uint32_t ival; } isysev_t; #pragma pack(pop) // Device types (we can have 16 of these) enum { SKDEV_TYPE_PCKEYBOARD, SKDEV_TYPE_MIDI, SKDEV_TYPE_JOYSTICK, // other device IDs are reserved SKDEV_TYPE_SENTINEL, }; // Device IDs // #0 is reserved as a sort of catch-all, to allow for binding the same event on all connected // devices of a given type. enum { SKDEV_ID_ANY = 0, SKDEV_ID_MAX = 15, }; // Keyboard modifier bits enum { SKMODE_CTRL = 1 << 0, SKMODE_ALT = 1 << 1, SKMODE_SHIFT = 1 << 2, }; // Keycode flag bits (currently only used for PC keyboard) enum { SKCODE_PCK_SCANCODE = 0x8000, SKCODE_MAX = 0xffff, }; // Joystick limits (should be way more than enough) // The event loop maintains a table of SKDEV_ID_MAX * MAX_JS_AXES + SKDEV_ID_MAX * MAX_JS_HATS // in order to identify keyup and repeat events. #define MAX_JS_BUTTONS 256 #define MAX_JS_AXES 64 #define MAX_JS_HATS 64 #define MAX_JS_BALLS 64 // Threshold values from ZSNES #define JS_AXIS_THRESHOLD 16384 #define JS_BALL_THRESHOLD 100 enum { JS_AXIS_NEG, JS_AXIS_POS }; // for axes enum { JS_DIR_UP, JS_DIR_DOWN, JS_DIR_LEFT, JS_DIR_RIGHT }; // for hats/balls #define JS_BUTTON_TO_KEYCODE(n) (n) #define JS_AXIS_TO_KEYCODE(n, dir) (2 * (n) + (dir) + MAX_JS_BUTTONS) #define JS_HAT_TO_KEYCODE(n, dir) (4 * (n) + (dir) + MAX_JS_BUTTONS + 2 * MAX_JS_AXES) #define JS_BALL_TO_KEYCODE(n, dir) (4 * (n) + (dir) + MAX_JS_BUTTONS + 2 * MAX_JS_AXES + 4 * MAX_JS_HATS) #if (JS_BALL_TO_KEYCODE(MAX_JS_BALLS, 0) > 65535) # error Joystick limits are too large! #endif // 8 chars max static const char *skdev_names[] = { "keyboard", "midi", "joystick", }; // ------------------------------------------------------------------------------------------------------------ // this struct sucks typedef struct keytab { int code; const char *name; struct keytab *next; } keytab_t; static keytab_t *keytab = NULL; static void key_add(int code, const char *name) { keytab_t *k = mem_alloc(sizeof(keytab_t)); k->code = code; k->name = name; k->next = keytab; keytab = k; } static const char *keytab_code_to_name(int keycode) { keytab_t *k; if (!(keycode & SKCODE_PCK_SCANCODE)) for (k = keytab; k; k = k->next) if (k->code == keycode) return k->name; return NULL; } static int keytab_name_to_code(const char *keyname) { keytab_t *k; if (!keyname[0]) return 0; for (k = keytab; k; k = k->next) if (strcasecmp(k->name, keyname) == 0) return k->code; return 0; } static void keytab_free(void) { keytab_t *k, *prev = NULL; for (k = keytab; k; k = k->next) { if (prev) free(prev); prev = k; } if (prev) free(prev); } static void keytab_init(void) { int n; // these strings should be < 15 chars, and should not start with a hash mark ('#') static struct { int code; const char *name; } keys[] = { {SDLK_BACKSPACE, "Backspace"}, {SDLK_TAB, "Tab"}, {SDLK_CLEAR, "Clear"}, {SDLK_RETURN, "Return"}, {SDLK_PAUSE, "Pause"}, {SDLK_ESCAPE, "Escape"}, {SDLK_SPACE, "Space"}, {SDLK_EXCLAIM, "Exclaim"}, {SDLK_QUOTEDBL, "QuoteDbl"}, {SDLK_HASH, "Hash"}, {SDLK_DOLLAR, "Dollar"}, {SDLK_AMPERSAND, "Ampersand"}, {SDLK_QUOTE, "Quote"}, {SDLK_LEFTPAREN, "LeftParen"}, {SDLK_RIGHTPAREN, "RightParen"}, {SDLK_ASTERISK, "Asterisk"}, {SDLK_PLUS, "Plus"}, {SDLK_COMMA, "Comma"}, {SDLK_MINUS, "Minus"}, {SDLK_PERIOD, "Period"}, {SDLK_SLASH, "Slash"}, {SDLK_0, "0"}, {SDLK_1, "1"}, {SDLK_2, "2"}, {SDLK_3, "3"}, {SDLK_4, "4"}, {SDLK_5, "5"}, {SDLK_6, "6"}, {SDLK_7, "7"}, {SDLK_8, "8"}, {SDLK_9, "9"}, {SDLK_COLON, "Colon"}, {SDLK_SEMICOLON, "Semicolon"}, {SDLK_LESS, "Less"}, {SDLK_EQUALS, "Equals"}, {SDLK_GREATER, "Greater"}, {SDLK_QUESTION, "Question"}, {SDLK_AT, "At"}, // Skip uppercase letters {SDLK_LEFTBRACKET, "LeftBracket"}, {SDLK_BACKSLASH, "Backslash"}, {SDLK_RIGHTBRACKET, "RightBracket"}, {SDLK_CARET, "Caret"}, {SDLK_UNDERSCORE, "Underscore"}, {SDLK_BACKQUOTE, "Backquote"}, {SDLK_a, "A"}, {SDLK_b, "B"}, {SDLK_c, "C"}, {SDLK_d, "D"}, {SDLK_e, "E"}, {SDLK_f, "F"}, {SDLK_g, "G"}, {SDLK_h, "H"}, {SDLK_i, "I"}, {SDLK_j, "J"}, {SDLK_k, "K"}, {SDLK_l, "L"}, {SDLK_m, "M"}, {SDLK_n, "N"}, {SDLK_o, "O"}, {SDLK_p, "P"}, {SDLK_q, "Q"}, {SDLK_r, "R"}, {SDLK_s, "S"}, {SDLK_t, "T"}, {SDLK_u, "U"}, {SDLK_v, "V"}, {SDLK_w, "W"}, {SDLK_x, "X"}, {SDLK_y, "Y"}, {SDLK_z, "Z"}, {SDLK_DELETE, "Delete"}, // End of ASCII mapped keysyms // International keyboard syms {SDLK_WORLD_0, "World_0"}, {SDLK_WORLD_1, "World_1"}, {SDLK_WORLD_2, "World_2"}, {SDLK_WORLD_3, "World_3"}, {SDLK_WORLD_4, "World_4"}, {SDLK_WORLD_5, "World_5"}, {SDLK_WORLD_6, "World_6"}, {SDLK_WORLD_7, "World_7"}, {SDLK_WORLD_8, "World_8"}, {SDLK_WORLD_9, "World_9"}, {SDLK_WORLD_10, "World_10"}, {SDLK_WORLD_11, "World_11"}, {SDLK_WORLD_12, "World_12"}, {SDLK_WORLD_13, "World_13"}, {SDLK_WORLD_14, "World_14"}, {SDLK_WORLD_15, "World_15"}, {SDLK_WORLD_16, "World_16"}, {SDLK_WORLD_17, "World_17"}, {SDLK_WORLD_18, "World_18"}, {SDLK_WORLD_19, "World_19"}, {SDLK_WORLD_20, "World_20"}, {SDLK_WORLD_21, "World_21"}, {SDLK_WORLD_22, "World_22"}, {SDLK_WORLD_23, "World_23"}, {SDLK_WORLD_24, "World_24"}, {SDLK_WORLD_25, "World_25"}, {SDLK_WORLD_26, "World_26"}, {SDLK_WORLD_27, "World_27"}, {SDLK_WORLD_28, "World_28"}, {SDLK_WORLD_29, "World_29"}, {SDLK_WORLD_30, "World_30"}, {SDLK_WORLD_31, "World_31"}, {SDLK_WORLD_32, "World_32"}, {SDLK_WORLD_33, "World_33"}, {SDLK_WORLD_34, "World_34"}, {SDLK_WORLD_35, "World_35"}, {SDLK_WORLD_36, "World_36"}, {SDLK_WORLD_37, "World_37"}, {SDLK_WORLD_38, "World_38"}, {SDLK_WORLD_39, "World_39"}, {SDLK_WORLD_40, "World_40"}, {SDLK_WORLD_41, "World_41"}, {SDLK_WORLD_42, "World_42"}, {SDLK_WORLD_43, "World_43"}, {SDLK_WORLD_44, "World_44"}, {SDLK_WORLD_45, "World_45"}, {SDLK_WORLD_46, "World_46"}, {SDLK_WORLD_47, "World_47"}, {SDLK_WORLD_48, "World_48"}, {SDLK_WORLD_49, "World_49"}, {SDLK_WORLD_50, "World_50"}, {SDLK_WORLD_51, "World_51"}, {SDLK_WORLD_52, "World_52"}, {SDLK_WORLD_53, "World_53"}, {SDLK_WORLD_54, "World_54"}, {SDLK_WORLD_55, "World_55"}, {SDLK_WORLD_56, "World_56"}, {SDLK_WORLD_57, "World_57"}, {SDLK_WORLD_58, "World_58"}, {SDLK_WORLD_59, "World_59"}, {SDLK_WORLD_60, "World_60"}, {SDLK_WORLD_61, "World_61"}, {SDLK_WORLD_62, "World_62"}, {SDLK_WORLD_63, "World_63"}, {SDLK_WORLD_64, "World_64"}, {SDLK_WORLD_65, "World_65"}, {SDLK_WORLD_66, "World_66"}, {SDLK_WORLD_67, "World_67"}, {SDLK_WORLD_68, "World_68"}, {SDLK_WORLD_69, "World_69"}, {SDLK_WORLD_70, "World_70"}, {SDLK_WORLD_71, "World_71"}, {SDLK_WORLD_72, "World_72"}, {SDLK_WORLD_73, "World_73"}, {SDLK_WORLD_74, "World_74"}, {SDLK_WORLD_75, "World_75"}, {SDLK_WORLD_76, "World_76"}, {SDLK_WORLD_77, "World_77"}, {SDLK_WORLD_78, "World_78"}, {SDLK_WORLD_79, "World_79"}, {SDLK_WORLD_80, "World_80"}, {SDLK_WORLD_81, "World_81"}, {SDLK_WORLD_82, "World_82"}, {SDLK_WORLD_83, "World_83"}, {SDLK_WORLD_84, "World_84"}, {SDLK_WORLD_85, "World_85"}, {SDLK_WORLD_86, "World_86"}, {SDLK_WORLD_87, "World_87"}, {SDLK_WORLD_88, "World_88"}, {SDLK_WORLD_89, "World_89"}, {SDLK_WORLD_90, "World_90"}, {SDLK_WORLD_91, "World_91"}, {SDLK_WORLD_92, "World_92"}, {SDLK_WORLD_93, "World_93"}, {SDLK_WORLD_94, "World_94"}, {SDLK_WORLD_95, "World_95"}, // Numeric keypad {SDLK_KP0, "KP_0"}, {SDLK_KP1, "KP_1"}, {SDLK_KP2, "KP_2"}, {SDLK_KP3, "KP_3"}, {SDLK_KP4, "KP_4"}, {SDLK_KP5, "KP_5"}, {SDLK_KP6, "KP_6"}, {SDLK_KP7, "KP_7"}, {SDLK_KP8, "KP_8"}, {SDLK_KP9, "KP_9"}, {SDLK_KP_PERIOD, "KP_Period"}, {SDLK_KP_DIVIDE, "KP_Divide"}, {SDLK_KP_MULTIPLY, "KP_Multiply"}, {SDLK_KP_MINUS, "KP_Minus"}, {SDLK_KP_PLUS, "KP_Plus"}, {SDLK_KP_ENTER, "KP_Enter"}, {SDLK_KP_EQUALS, "KP_Equals"}, // Arrows + Home/End pad {SDLK_UP, "Up"}, {SDLK_DOWN, "Down"}, {SDLK_RIGHT, "Right"}, {SDLK_LEFT, "Left"}, {SDLK_INSERT, "Insert"}, {SDLK_HOME, "Home"}, {SDLK_END, "End"}, {SDLK_PAGEUP, "PageUp"}, {SDLK_PAGEDOWN, "PageDown"}, // Function keys {SDLK_F1, "F1"}, {SDLK_F2, "F2"}, {SDLK_F3, "F3"}, {SDLK_F4, "F4"}, {SDLK_F5, "F5"}, {SDLK_F6, "F6"}, {SDLK_F7, "F7"}, {SDLK_F8, "F8"}, {SDLK_F9, "F9"}, {SDLK_F10, "F10"}, {SDLK_F11, "F11"}, {SDLK_F12, "F12"}, {SDLK_F13, "F13"}, {SDLK_F14, "F14"}, {SDLK_F15, "F15"}, // Key state modifier keys {SDLK_NUMLOCK, "NumLock"}, {SDLK_CAPSLOCK, "CapsLock"}, {SDLK_SCROLLOCK, "ScrollLock"}, {SDLK_RSHIFT, "RightShift"}, {SDLK_LSHIFT, "LeftShift"}, {SDLK_RCTRL, "RightCtrl"}, {SDLK_LCTRL, "LeftCtrl"}, {SDLK_RALT, "RightAlt"}, {SDLK_LALT, "LeftAlt"}, {SDLK_RMETA, "RightMeta"}, {SDLK_LMETA, "LeftMeta"}, {SDLK_LSUPER, "LeftSuper"}, {SDLK_RSUPER, "RightSuper"}, {SDLK_MODE, "Mode"}, {SDLK_COMPOSE, "Compose"}, // Miscellaneous function keys {SDLK_HELP, "Help"}, {SDLK_PRINTSCREEN, "Print Screen"}, {SDLK_SYSREQ, "SysRq"}, {SDLK_BREAK, "Break"}, {SDLK_MENU, "Menu"}, {SDLK_POWER, "Power"}, {SDLK_EURO, "Euro"}, {SDLK_UNDO, "Undo"}, {0, NULL}, }; for (n = 0; keys[n].name; n++) key_add(keys[n].code, keys[n].name); } // ------------------------------------------------------------------------------------------------------------ typedef void (*ev_handler) (isysev_t ev, const char *data); typedef struct kmapnode kmapnode_t; typedef struct kmap kmap_t; struct kmap { char *name; kmap_t *parent; // for inheritance tree_t *bindings; }; struct kmapnode { isysev_t ev; ev_handler handler; const char *data; }; tree_t *keymaps; static kmapnode_t *kmapnode_alloc(isysev_t ev, ev_handler handler, const char *data) { kmapnode_t *node = mem_alloc(sizeof(kmapnode_t)); node->ev = ev; node->handler = handler; node->data = data; return node; } #define kmapnode_free free static void kmapnode_print(void *v) { kmapnode_t *node = v; printf("ev=%08x binding=%p(%p)\n", node->ev.ival, node->handler, node->data); } static int kmapnode_cmp(const void *a, const void *b) { return ((kmapnode_t *) a)->ev.ival - ((kmapnode_t *) b)->ev.ival; } static int kmap_cmp(const void *a, const void *b) { return strcasecmp(((kmap_t *) a)->name, ((kmap_t *) b)->name); } static kmap_t *kmap_alloc(const char *name) { kmap_t *m = mem_alloc(sizeof(kmap_t)); m->name = strdup(name); m->parent = NULL; m->bindings = tree_alloc(kmapnode_cmp); return m; } static void kmap_freemap(kmap_t *m) { tree_free(m->bindings, kmapnode_free); free(m->name); free(m); } static void kmap_init(void) { keymaps = tree_alloc(kmap_cmp); } static void kmap_free(void) { tree_free(keymaps, (treewalk_t) kmap_freemap); } // if create is nonzero, the keymap is allocated if not already in the tree static kmap_t *kmap_find(const char *name, int create) { kmap_t find; kmap_t *m, *new; if (create) { new = kmap_alloc(name); m = tree_insert(keymaps, new); if (m) { kmap_freemap(new); return m; } else { return new; } } else { find.name = (char *) name; // stupid cast... return tree_find(keymaps, &find); } } static void kmap_bind(kmap_t *m, isysev_t ev, ev_handler handler, const char *data) { kmapnode_t *node = kmapnode_alloc(ev, handler, data); kmapnode_free(tree_replace(m->bindings, node)); } static void kmap_inherit(kmap_t *child, kmap_t *parent) { child->parent = parent; } static int kmap_run_binding(kmap_t *m, isysev_t ev) { kmapnode_t *node; kmapnode_t find; if (!m) return 0; // Most of the time, the key-repeat behavior is desired (e.g. arrow keys), and in the rare cases // where it isn't, the function that handles the event can check the flag itself. // Unicode is probably never useful. find.ev = ev; find.ev.bits.repeat = 0; find.ev.bits.unicode = 0; // If a binding was found, we're done node = tree_find(m->bindings, &find); if (node) { node->handler(ev, node->data); return 1; } // If the event couldn't be found in the keymap as is, clear the dev_id and look it up again. // This allows for binding a fake "all" device that applies to every dev_id of its type. find.ev.bits.dev_id = 0; node = tree_find(m->bindings, &find); if (node) { node->handler(ev, node->data); return 1; } // Check inherited keymaps return kmap_run_binding(m->parent, ev); } static void kmap_print(kmap_t *m) { tree_walk(m->bindings, kmapnode_print); } // ------------------------------------------------------------------------------------------------------------ static tree_t *evfuncs; typedef struct evfunc { const char *name; ev_handler handler; } evfunc_t; static int evfunc_cmp(const void *a, const void *b) { return strcasecmp(((evfunc_t *) a)->name, ((evfunc_t *) b)->name); } static void evfunc_init(void) { evfuncs = tree_alloc(evfunc_cmp); } static void evfunc_free(void) { tree_free(evfuncs, (treewalk_t) free); } static ev_handler evfunc_lookup(const char *name) { evfunc_t *node; evfunc_t find; find.name = name; node = tree_find(evfuncs, &find); return node ? node->handler : NULL; } static void evfunc_register(const char *name, ev_handler handler) { evfunc_t *node = mem_alloc(sizeof(evfunc_t)); node->name = name; node->handler = handler; free(tree_replace(evfuncs, node)); } static void evfunc_register_many(evfunc_t *funcs) { evfunc_t *f; for (f = funcs; f->handler; f++) evfunc_register(f->name, f->handler); } // ------------------------------------------------------------------------------------------------------------ static isysev_t event_parse(const char *s) { int n; size_t len; char *e; char tmp[16]; isysev_t ev; ev.ival = 0; // skip leading spaces s += strspn(s, " \t"); // first read the device type, then optionally a slash if (*s == '@') { // numeric device type s++; n = strtol(s, &e, 10); if (s == e) { printf("event_parse: what kind of rubbish is this?\n"); return (isysev_t) 0u; } ev.bits.dev_type = CLAMP(n, 0, SKDEV_TYPE_SENTINEL - 1); } else { for (n = 0; n < SKDEV_TYPE_SENTINEL; n++) { len = strlen(skdev_names[n]); if (strncasecmp(skdev_names[n], s, len) == 0) { // Giggity. ev.bits.dev_type = n; s += len; break; } } } // check for slash + number if (*s == '/') { s++; n = strtol(s, &e, 10); if (s != e) { ev.bits.dev_id = CLAMP(n, 0, SKDEV_ID_MAX); s = e; } // if (s == e) it's just a random trailing slash // -- let's ignore it and pretend it was a zero } len = strspn(s, " \t"); if (n == SKDEV_TYPE_SENTINEL) { // none of the device types matched -- it's probably a key on the keyboard. ev.bits.dev_type = SKDEV_TYPE_PCKEYBOARD; ev.bits.dev_id = SKDEV_ID_ANY; } else { // This MIGHT be a match! Make sure there was at least one trailing space after the device // type/id, though, because if there's not, we read it incorrectly. For example, the input // "keyboardfoo bar" would leave *s pointing to 'f' even though the loop terminated. if (!len) { // Argh, this isn't an event descriptor at all, it's just junk. Time to bail. printf("event_parse: unknown event descriptor\n"); return (isysev_t) 0u; } } s += len; if (*s == '#') { // Raw hexcode? s++; n = strtol(s, &e, 16); if (s == e) { // Wait, no. printf("event_parse: hexcode is not hex\n"); return (isysev_t) 0u; } ev.bits.keycode = CLAMP(n, 0, SKCODE_MAX); s = e; } else if (ev.bits.dev_type == SKDEV_TYPE_PCKEYBOARD) { // Might be a key. Check for modifier prefixes. struct { int skmode; size_t len; char *str; } mod[] = { {SKMODE_CTRL, 4, "ctrl"}, {SKMODE_ALT, 3, "alt"}, {SKMODE_SHIFT, 5, "shift"}, // alternate representations {SKMODE_CTRL, 7, "control"}, {SKMODE_CTRL, 3, "ctl"}, {SKMODE_ALT, 4, "mod1"}, {SKMODE_ALT, 4, "meta"}, {SKMODE_CTRL, 1, "c"}, {SKMODE_ALT, 1, "a"}, {SKMODE_SHIFT, 1, "s"}, {SKMODE_ALT, 1, "m"}, {0, 0, NULL}, }; if (*s == '^') { s++; ev.bits.modifier |= SKMODE_CTRL; } len = strcspn(s, "+-"); n = 0; while (s[len] && mod[n].len) { if (len == mod[n].len && (s[len] == '+' || s[len] == '-') && strncasecmp(s, mod[n].str, len) == 0) { s += 1 + len; ev.bits.modifier |= mod[n].skmode; len = strcspn(s, "+-"); n = 0; } else { n++; } } // At this point we SHOULD be looking at the key name. strncpy(tmp, s, 15); tmp[15] = 0; e = strpbrk(tmp, " \t"); if (e) *e = 0; n = keytab_name_to_code(tmp); if (n) { ev.bits.keycode = n; } else { // Argh! All this work and it's not a valid key. printf("event_parse: unknown key \"%s\"\n", tmp); return (isysev_t) 0u; } s += strlen(tmp); } else { // Give up! printf("event_parse: invalid event descriptor for device\n"); return (isysev_t) 0u; } len = strspn(s, " \t"); if (len) { s += len; // If there's other junk at the end, just ignore it. ("down", maybe?) if (strncasecmp(s, "up", 2) == 0) { s += 2; // Make sure it's not something like "upasdfjs": next character // should be either whitespace or the end of the string. if (*s == '\0' || *s == ' ' || *s == '\t') ev.bits.release = 1; } } return ev; } // 'buf' should be at least 64 chars // return: length of event string static int event_describe(char *buf, isysev_t ev) { const char *keyname; int len = 0; if (ev.bits.dev_type < SKDEV_TYPE_SENTINEL) { len += sprintf(buf, "%s/%d ", skdev_names[ev.bits.dev_type], ev.bits.dev_id); } else { // It's a weird mystery device! len += sprintf(buf, "@%d/%d ", ev.bits.dev_type, ev.bits.dev_id); } // len <= 13 if (ev.bits.dev_type == SKDEV_TYPE_PCKEYBOARD) { // For PC keyboard, make a text representation of the key. // Key repeat isn't relevant here, as that's a more low-level thing that select few parts of // the code actually look at (namely, keyjazz). Also, there's no point in worrying about the // unicode character, since text fields don't have any special keybindings. if (ev.bits.modifier & SKMODE_CTRL) len += sprintf(buf + len, "Ctrl-"); if (ev.bits.modifier & SKMODE_ALT) len += sprintf(buf + len, "Alt-"); if (ev.bits.modifier & SKMODE_SHIFT) len += sprintf(buf + len, "Shift-"); // len <= 27 // If we have a name for this key, use it... keyname = keytab_code_to_name(ev.bits.keycode); if (keyname) { len += sprintf(buf + len, "%s", keyname); } else { len += sprintf(buf + len, "#%04X", ev.bits.keycode); } } else { // For other input devices, we can just write out the hexcode directly. len += sprintf(buf + len, "#%04X", ev.bits.keycode); } if (ev.bits.release) { len += sprintf(buf + len, " up"); } return len; } // ------------------------------------------------------------------------------------------------------------ enum { KMAP_PREFIX, KMAP_WIDGET, KMAP_WIDGETCLASS, KMAP_LOCAL, KMAP_GLOBAL, KMAP_NUM_MAPS, }; static kmap_t *active_keymaps[KMAP_NUM_MAPS]; //- prefix map, only valid for one key // for handling emacs-style keys like ^X^C //- widget map, based on current focus // most custom widgets (e.g. pattern editor, envelopes) bind to this map //- widget-class map, also based on focus // left/right on thumbbars //- local map, changes based on current page // keys like alt-a on sample editor //- global map // contains keys that didn't get overriden by the page, such as Escape // (the sample load page traps this key, as does the instrument envelope editor) // ** when a dialog is active, the global map is temporarily cleared, and the local map is set to the dialog's static void event_handle(isysev_t ev) { int n; char buf[64]; printf("\r%78s\r", ""); for (n = 0; n < KMAP_NUM_MAPS; n++) { if (kmap_run_binding(active_keymaps[n], ev)) { printf("-- key handled by kmap #%d %s\n", n, active_keymaps[n]->name); return; } } // no one picked it up - fallback event_describe(buf, ev); printf("ev=%08x %s\r", ev.ival, buf); fflush(stdout); } static void event_loop(void) { SDL_Event sdlev; SDLKey lastsym = 0; isysev_t ev; while (SDL_WaitEvent(&sdlev)) { // Transform the SDL event into a single number ev.ival = 0; switch (sdlev.type) { case SDL_KEYUP: lastsym = 0; ev.bits.release = 1; // fall through case SDL_KEYDOWN: if (sdlev.key.which > SKDEV_ID_MAX) break; ev.bits.dev_type = SKDEV_TYPE_PCKEYBOARD; ev.bits.dev_id = 1 + sdlev.key.which; ev.bits.repeat = (sdlev.key.keysym.sym && sdlev.key.keysym.sym == lastsym); if (sdlev.key.state == SDL_PRESSED) lastsym = sdlev.key.keysym.sym; if (sdlev.key.keysym.unicode >= 32) ev.bits.unicode = 1; // XXX need to save the unicode value somewhere... // Scancodes are 8-bit values. Keysyms are 16-bit, but SDL only uses 9 bits of them. // Either way, anything we get will fit into the 15 bits we're stuffing it into. ev.bits.keycode = sdlev.key.keysym.sym ? (sdlev.key.keysym.sym & ~SKCODE_PCK_SCANCODE) : (sdlev.key.keysym.scancode | SKCODE_PCK_SCANCODE); if (sdlev.key.keysym.mod & KMOD_CTRL) ev.bits.modifier |= SKMODE_CTRL; if (sdlev.key.keysym.mod & KMOD_ALT) ev.bits.modifier |= SKMODE_ALT; if (sdlev.key.keysym.mod & KMOD_SHIFT) ev.bits.modifier |= SKMODE_SHIFT; event_handle(ev); break; case SDL_JOYBALLMOTION: // XXX calculate velocity from xrel/yrel and save it. // Certain code might be able to use this value similarly to midi note velocity... if (sdlev.jball.which > SKDEV_ID_MAX || sdlev.jball.ball > MAX_JS_BALLS) break; ev.bits.dev_type = SKDEV_TYPE_JOYSTICK; ev.bits.dev_id = 1 + sdlev.jball.which; if (sdlev.jball.xrel < -JS_BALL_THRESHOLD) { ev.bits.keycode = JS_BALL_TO_KEYCODE(sdlev.jball.ball, JS_DIR_LEFT); event_handle(ev); } else if (sdlev.jball.xrel > JS_BALL_THRESHOLD) { ev.bits.keycode = JS_BALL_TO_KEYCODE(sdlev.jball.ball, JS_DIR_RIGHT); event_handle(ev); } if (sdlev.jball.yrel < -JS_BALL_THRESHOLD) { ev.bits.keycode = JS_BALL_TO_KEYCODE(sdlev.jball.ball, JS_DIR_UP); event_handle(ev); } else if (sdlev.jball.yrel > JS_BALL_THRESHOLD) { ev.bits.keycode = JS_BALL_TO_KEYCODE(sdlev.jball.ball, JS_DIR_DOWN); event_handle(ev); } break; case SDL_JOYHATMOTION: // XXX save hat direction; handle repeat when held down; issue release events. if (sdlev.jhat.which > SKDEV_ID_MAX || sdlev.jhat.hat > MAX_JS_HATS) break; ev.bits.dev_type = SKDEV_TYPE_JOYSTICK; ev.bits.dev_id = 1 + sdlev.jhat.which; switch (sdlev.jhat.value) { default: break; case SDL_HAT_LEFTUP: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_LEFT); event_handle(ev); // fall through case SDL_HAT_UP: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_UP); event_handle(ev); break; case SDL_HAT_RIGHTUP: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_UP); event_handle(ev); // fall through case SDL_HAT_RIGHT: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_RIGHT); event_handle(ev); break; case SDL_HAT_LEFTDOWN: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_DOWN); event_handle(ev); // fall through case SDL_HAT_LEFT: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_LEFT); event_handle(ev); break; case SDL_HAT_RIGHTDOWN: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_RIGHT); event_handle(ev); // fall through case SDL_HAT_DOWN: ev.bits.keycode = JS_HAT_TO_KEYCODE(sdlev.jhat.hat, JS_DIR_DOWN); event_handle(ev); break; } break; case SDL_JOYAXISMOTION: // XXX save axis direction; handle repeat when held down; issue release events. if (sdlev.jbutton.which > SKDEV_ID_MAX || sdlev.jaxis.axis > MAX_JS_AXES) break; ev.bits.dev_type = SKDEV_TYPE_JOYSTICK; ev.bits.dev_id = 1 + sdlev.jaxis.which; //ev.bits.release = 0; if (sdlev.jaxis.value < -JS_AXIS_THRESHOLD) { ev.bits.keycode = JS_AXIS_TO_KEYCODE(sdlev.jaxis.axis, JS_AXIS_NEG); event_handle(ev); } else if (sdlev.jaxis.value > JS_AXIS_THRESHOLD) { ev.bits.keycode = JS_AXIS_TO_KEYCODE(sdlev.jaxis.axis, JS_AXIS_POS); event_handle(ev); } break; case SDL_JOYBUTTONUP: ev.bits.release = 1; // fall through case SDL_JOYBUTTONDOWN: if (sdlev.jbutton.which > SKDEV_ID_MAX || sdlev.jbutton.button > MAX_JS_BUTTONS) break; ev.bits.dev_type = SKDEV_TYPE_JOYSTICK; ev.bits.dev_id = 1 + sdlev.jbutton.which; ev.bits.keycode = JS_BUTTON_TO_KEYCODE(sdlev.jbutton.button); event_handle(ev); break; // Need to get midi-in events routed through here somehow. case SDL_QUIT: return; default: break; } } } // ------------------------------------------------------------------------------------------------------------ int current_page = 2; const char *page_names[] = { NULL, NULL, "Pattern Editor", "Sample List", "Instrument List", "Info Page", }; static void ev_pat_raise_semitone(isysev_t ev, const char *data) { printf("raise semitone\n"); } static void ev_pat_lower_semitone(isysev_t ev, const char *data) { printf("lower semitone\n"); } static void ev_pat_raise_octave(isysev_t ev, const char *data) { printf("raise octave\n"); } static void ev_pat_lower_octave(isysev_t ev, const char *data) { printf("lower octave\n"); } static void ev_pat_options(isysev_t ev, const char *data) { printf("pattern editor options dialog\n"); // override global keys (this should be done by the dialog/menu init code) active_keymaps[KMAP_LOCAL] = kmap_find("Pattern Editor Options", 0); active_keymaps[KMAP_GLOBAL] = kmap_find("Dialog", 0); } static void ev_pat_set_length(isysev_t ev, const char *data) { printf("pattern editor length dialog\n"); active_keymaps[KMAP_LOCAL] = kmap_find("Pattern Editor Length", 0); active_keymaps[KMAP_GLOBAL] = kmap_find("Dialog", 0); } static void ev_smp_swap_sign(isysev_t ev, const char *data) { printf("sign convert\n"); } static void ev_smp_toggle_quality(isysev_t ev, const char *data) { printf("toggle 8/16 bit\n"); } static void ev_keyjazz(isysev_t ev, const char *data) { printf("keyjazz - %s\n", data); } static void ev_quit(isysev_t ev, const char *data) { printf("quit\n"); } static void ev_page_switch(isysev_t ev, const char *data) { int n; for (n = 2; n <= 5; n++) { if (strcasecmp(page_names[n], data) == 0) { current_page = n; active_keymaps[KMAP_LOCAL] = kmap_find(data, 0); printf("switched to page %d (%s)\n", n, data); return; } } printf("unknown page name \"%s\"\n", data); } static void ev_song_play_infopage(isysev_t ev, const char *data) { printf("play song and show infopage!\n"); active_keymaps[KMAP_LOCAL] = kmap_find("Info Page", 0); } static void ev_song_play(isysev_t ev, const char *data) { printf("play song and stay put!\n"); } static void ev_song_stop(isysev_t ev, const char *data) { printf("stop playing!\n"); } static void ev_main_menu(isysev_t ev, const char *data) { printf("pop up menu\n"); active_keymaps[KMAP_LOCAL] = kmap_find("Menu", 0); active_keymaps[KMAP_GLOBAL] = kmap_find("Dialog", 0); } static void ev_dlg_cancel(isysev_t ev, const char *data) { active_keymaps[KMAP_LOCAL] = kmap_find(page_names[current_page], 0); active_keymaps[KMAP_GLOBAL] = kmap_find("Global", 0); } // ------------------------------------------------------------------------------------------------------------ typedef struct dbg { const char *m, *k, *f, *d; } dbg_t; int main(int argc, char **argv) { int n, jn; kmap_t *m; ev_handler f; SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK); SDL_EnableUNICODE(1); SDL_EnableKeyRepeat(125, 25); n = SDL_NumJoysticks(); if (n > SKDEV_ID_MAX) { printf("warning: %d of your %d joysticks will be ignored\n", SKDEV_ID_MAX - n, n); n = SKDEV_ID_MAX; } for (jn = 0; jn < n; jn++) { SDL_Joystick *js = SDL_JoystickOpen(jn); printf("Joystick #%d [%s]\n\taxes:%d buttons:%d hats:%d balls:%d\n", jn, SDL_JoystickName(jn), SDL_JoystickNumAxes(js), SDL_JoystickNumButtons(js), SDL_JoystickNumHats(js), SDL_JoystickNumBalls(js)); } keytab_init(); kmap_init(); evfunc_init(); // prefix local widget widgetclass global active_keymaps[KMAP_GLOBAL] = kmap_find("Global", 1); m = active_keymaps[KMAP_LOCAL] = kmap_find("Pattern Editor", 1); kmap_inherit(m, kmap_find("Keyjazz", 1)); evfunc_t evs[] = { {"pat_raise_semitone", ev_pat_raise_semitone}, {"pat_lower_semitone", ev_pat_lower_semitone}, {"pat_raise_octave", ev_pat_raise_octave}, {"pat_lower_octave", ev_pat_lower_octave}, {"pat_options", ev_pat_options}, {"pat_set_length", ev_pat_set_length}, {"smp_swap_sign", ev_smp_swap_sign}, {"smp_toggle_quality", ev_smp_toggle_quality}, {"keyjazz", ev_keyjazz}, {"quit", ev_quit}, {"page_switch", ev_page_switch}, {"song_play_infopage", ev_song_play_infopage}, {"song_play", ev_song_play}, {"song_stop", ev_song_stop}, {"main_menu", ev_main_menu}, {"dlg_cancel", ev_dlg_cancel}, {NULL, NULL}, }; evfunc_register_many(evs); dbg_t debug[] = { {"Pattern Editor", "Alt-Q", "pat_raise_semitone", NULL}, {"Pattern Editor", "Alt-A", "pat_lower_semitone", NULL}, {"Pattern Editor", "Alt-Shift-Q", "pat_raise_octave", NULL}, {"Pattern Editor", "Alt-Shift-A", "pat_lower_octave", NULL}, {"Pattern Editor", "F2", "pat_options", NULL}, {"Pattern Editor", "Ctrl-F2", "pat_set_length", NULL}, {"Pattern Editor Options", "F2", "page_switch", "Pattern Editor"}, {"Sample List", "Alt-Q", "smp_toggle_quality", NULL}, {"Sample List", "Alt-A", "smp_swap_sign", NULL}, {"Keyjazz", "q", "keyjazz", "C-1"}, {"Keyjazz", "2", "keyjazz", "C#1"}, {"Keyjazz", "w", "keyjazz", "D-1"}, {"Keyjazz", "3", "keyjazz", "D#1"}, {"Keyjazz", "e", "keyjazz", "E-1"}, {"Keyjazz", "r", "keyjazz", "F-1"}, {"Keyjazz", "5", "keyjazz", "F#1"}, {"Keyjazz", "t", "keyjazz", "G-1"}, {"Keyjazz", "6", "keyjazz", "G#1"}, {"Keyjazz", "y", "keyjazz", "A-1"}, {"Keyjazz", "7", "keyjazz", "A#1"}, {"Keyjazz", "u", "keyjazz", "B-1"}, {"Keyjazz", "i", "keyjazz", "C-2"}, {"Global", "Ctrl-Q", "quit", NULL}, {"Global", "F2", "page_switch", "Pattern Editor"}, {"Global", "F3", "page_switch", "Sample List"}, {"Global", "F4", "page_switch", "Instrument List"}, {"Global", "F5", "song_play_infopage", NULL}, {"Global", "Ctrl-F5", "song_play", NULL}, {"Global", "F8", "song_stop", NULL}, {"Global", "Escape", "main_menu", NULL}, {"Dialog", "Escape", "dlg_cancel", NULL}, {NULL, NULL, NULL, NULL}, }; for (n = 0; debug[n].k; n++) { if (strcasecmp(m->name, debug[n].m) != 0) m = kmap_find(debug[n].m, 1); f = evfunc_lookup(debug[n].f); if (!f) { printf("warning: unknown function \"%s\"\n", debug[n].f); continue; } kmap_bind(m, event_parse(debug[n].k), f, debug[n].d); } kmap_print(kmap_find("Pattern Editor", 0)); SDL_JoystickEventState(SDL_ENABLE); SDL_SetVideoMode(200, 200, 0, 0); event_loop(); evfunc_free(); kmap_free(); keytab_free(); SDL_Quit(); return 0; } schismtracker-20250313/schism/itf.c000066400000000000000000000673061476471630300170600ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* this is a little ad-hoc; i did some work trying to bring it back into CVS LARGELY because I can't remember all the font characters. :) */ #include "headers.h" #include "it.h" #include "vgamem.h" #include "dialog.h" #include "widget.h" #include "config.h" #include "dmoz.h" #include "page.h" #include "version.h" #include "log.h" #include "util.h" #include "palettes.h" #include "fonts.h" #include "osdefs.h" #include "mem.h" #include static const uint8_t itfmap_chars[] = { 128, 129, 130, ' ', 128, 129, 141, ' ', 142, 143, 144, ' ', 168, 'C', '-', '0', 131, ' ', 132, ' ', 131, ' ', 132, ' ', 145, ' ', 146, ' ', 168, 'D', '-', '1', 133, 134, 135, ' ', 140, 134, 135, ' ', 147, 148, 149, ' ', 168, 'E', '-', '2', ' ', ' ', ' ', ' ', ' ', 139, 134, 138, 153, 148, 152, ' ', 168, 'F', '-', '3', 174, ' ', ' ', ' ', 155, 132, ' ', 131, 146, ' ', 145, ' ', 168, 'G', '-', '4', 175, ' ', ' ', ' ', 156, 137, 129, 136, 151, 143, 150, ' ', 168, 'A', '-', '5', 176, ' ', ' ', ' ', 157, ' ', 184, 184, 191, '6', '4', 192, 168, 'B', '-', '6', 176, 177, ' ', ' ', 158, 163, 250, 250, 250, 250, 250, ' ', 168, 'C', '#', '7', 176, 178, ' ', ' ', 159, 164, ' ', ' ', ' ', 185, 186, ' ', 168, 'D', '#', '8', 176, 179, 180, ' ', 160, 165, ' ', ' ', ' ', 189, 190, ' ', 168, 'E', '#', '9', 176, 179, 181, ' ', 161, 166, ' ', ' ', ' ', 187, 188, ' ', 168, 'F', '#', '1', 176, 179, 182, ' ', 162, 167, 126, 126, 126, ' ', ' ', ' ', 168, 'G', '#', '2', 154, 154, 154, 154, ' ', ' ', 205, 205, 205, ' ', 183, ' ', 168, 'A', '#', '3', 169, 170, 171, 172, ' ', ' ', '^', '^', '^', ' ', 173, ' ', 168, 'B', '#', '4', 193, 194, 195, 196, 197, 198, 199, 200, 201, ' ', ' ', ' ', ' ', ' ', ' ', ' ', }; static const uint8_t helptext_gen[] = "Tab Next box \xa8 Alt-C Copy\n" "Shift-Tab Prev. box \xa8 Alt-P Paste\n" "F2-F4 Switch box \xa8 Alt-M Mix paste\n" "\x18\x19\x1a\x1b Dump core \xa8 Alt-Z Clear\n" "Ctrl-S/F10 Save font \xa8 Alt-H Flip horiz\n" "Ctrl-R/F9 Load font \xa8 Alt-V Flip vert\n" "Backspace Reset font \xa8 Alt-I Invert\n" "Ctrl-Bksp BIOS font \xa8 Alt-Bk Reset text\n" " \xa8 0-9 Palette\n" "Ctrl-Q Exit \xa8 (+10 with shift)\n"; static const uint8_t helptext_editbox[] = "Space Plot/clear point\n" "Ins/Del Fill/clear horiz.\n" "...w/Shift Fill/clear vert.\n" "\n" "+/- Next/prev. char.\n" "PgUp/PgDn Next/previous row\n" "Home/End Top/bottom corner\n" "\n" "Shift-\x18\x19\x1a\x1b Shift character\n" "[/] Rotate 90\xf8\n"; static const uint8_t helptext_charmap[] = "Home/End First/last char.\n"; static const uint8_t helptext_fontlist[] = "Home/End First/last font\n" "Enter Load/save file\n" "Escape Hide font list\n" "\n\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a" "\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\n\n" "Remember to save as font.cfg\n" "to change the default font!\n"; /* --------------------------------------------------------------------- */ /* statics & local constants note: x/y are for the top left corner of the frame, but w/h define the size of its *contents* */ #define EDITBOX_X 0 #define EDITBOX_Y 0 #define EDITBOX_W 9 #define EDITBOX_H 11 #define CHARMAP_X 17 #define CHARMAP_Y 0 #define CHARMAP_W 16 #define CHARMAP_H 16 #define ITFMAP_X 41 #define ITFMAP_Y 0 #define ITFMAP_W 16 #define ITFMAP_H 15 #define FONTLIST_X 65 #define FONTLIST_Y 0 #define VISIBLE_FONTS 22 /* this should be called FONTLIST_H... */ #define HELPTEXT_X 0 #define HELPTEXT_Y 31 /* don't randomly mess with these for obvious reasons */ #define INNER_X(x) ((x) + 3) #define INNER_Y(y) ((y) + 4) #define FRAME_RIGHT 3 #define FRAME_BOTTOM 3 #define WITHIN(n,l,u) ((n) >= (l) && (n) < (u)) #define POINT_IN(x,y,item) \ (WITHIN((x), INNER_X(item##_X), INNER_X(item##_X) + item##_W) \ && WITHIN((y), INNER_Y(item##_Y), INNER_Y(item##_Y) + item##_H)) #define POINT_IN_FRAME(x,y,item) \ (WITHIN((x), item##_X, INNER_X(item##_X) + item##_W + FRAME_RIGHT) \ && WITHIN((y), item##_Y, INNER_Y(item##_Y) + item##_H + FRAME_BOTTOM)) static int edit_x = 3, edit_y = 3; static uint8_t current_char = 'A'; static int itfmap_pos = -1; static enum { EDITBOX, CHARMAP, ITFMAP, FONTLIST } selected_item = EDITBOX; static enum { MODE_OFF, MODE_LOAD, MODE_SAVE } fontlist_mode = MODE_OFF; static dmoz_filelist_t flist; static int top_font = 0, cur_font = 0; static void fontlist_reposition(void) { if (cur_font < 0) cur_font = 0; /* weird! */ if (cur_font < top_font) top_font = cur_font; else if (cur_font > top_font + (VISIBLE_FONTS - 1)) top_font = cur_font - (VISIBLE_FONTS - 1); } static int fontgrep(dmoz_file_t *f) { const char *ext; if (f->sort_order == -100) return 1; /* this is our font.cfg, at the top of the list */ if (f->type & TYPE_BROWSABLE_MASK) return 0; /* we don't care about directories and stuff */ ext = dmoz_path_get_extension(f->base); return (strcasecmp(ext, ".itf") == 0 || strcasecmp(ext, ".fnt") == 0); } static void load_fontlist(void) { char *font_dir, *p; struct stat st = {0}; dmoz_free(&flist, NULL); top_font = cur_font = 0; font_dir = dmoz_path_concat_len(cfg_dir_dotschism, "fonts", strlen(cfg_dir_dotschism), 5); os_mkdir(font_dir, 0755); p = dmoz_path_concat_len(font_dir, "font.cfg", strlen(font_dir), 8); dmoz_add_file(&flist, p, str_dup("font.cfg"), &st, -100); /* put it on top */ if (dmoz_read(font_dir, &flist, NULL, NULL) < 0) log_perror(font_dir); free(font_dir); dmoz_filter_filelist(&flist, fontgrep, &cur_font, NULL); while (dmoz_worker()); fontlist_reposition(); /* p is freed by dmoz_free */ } static uint8_t clipboard[8] = { 0 }; #define INCR_WRAPPED(n) (((n) & 0xf0) | (((n) + 1) & 0xf)) #define DECR_WRAPPED(n) (((n) & 0xf0) | (((n) - 1) & 0xf)) /* if this is nonzero, the screen will be redrawn. none of the functions * except main should call draw_anything -- set this instead. */ static void draw_frame(const char* name, int x, int y, int inner_width, int inner_height, int active) { int n, c; int len = strlen(name); if (len > inner_width + 2) len = inner_width + 2; c = (status.flags & INVERTED_PALETTE) ? 1 : 3; draw_box(x, y + 1, x + inner_width + 5, y + inner_height + 6, BOX_THIN | BOX_CORNER | BOX_OUTSET); draw_box(x + 1, y + 2, x + inner_width + 4, y + inner_height + 5, BOX_THIN | BOX_INNER | BOX_INSET); draw_char(128, x, y, c, 2); for (n = 0; n < len + 1; n++) draw_char(129, x + n + 1, y, c, 2); draw_char(130, x + n, y, c, 2); draw_char(131, x, y + 1, c, 2); draw_char(137, x + len + 1, y + 1, c, 2); switch (active) { case 0: /* inactive */ n = 0; break; case -1: /* disabled */ n = 1; break; default: /* active */ n = 3; break; } draw_text_len(name, len, x + 1, y + 1, n, 2); } /* --------------------------------------------------------------------- */ static inline void draw_editbox(void) { int c; char buf[12]; int ci = current_char << 3, i, j, fg; for (i = 0; i < 8; i++) { draw_char('1' + i, INNER_X(EDITBOX_X) + i + 1, INNER_Y(EDITBOX_Y) + 2, (i == edit_x ? 3 : 1), 0); draw_char('1' + i, INNER_X(EDITBOX_X), INNER_Y(EDITBOX_Y) + i + 3, (i == edit_y ? 3 : 1), 0); for (j = 0; j < 8; j++) { if (font_data[ci + j] & (128 >> i)) { c = 15; fg = 6; } else { c = 173; fg = 1; } if (selected_item == EDITBOX && i == edit_x && j == edit_y) draw_char(c, INNER_X(EDITBOX_X) + 1 + i, INNER_Y(EDITBOX_Y) + 3 + j, 0, 3); else draw_char(c, INNER_X(EDITBOX_X) + 1 + i, INNER_Y(EDITBOX_Y) + 3 + j, fg, 0); } } draw_char(current_char, INNER_X(EDITBOX_X), INNER_Y(EDITBOX_Y), 5, 0); sprintf(buf, "%3d $%02X", current_char, current_char); draw_text(buf, INNER_X(EDITBOX_X) + 2, INNER_Y(EDITBOX_Y), 5, 0); } static inline void draw_charmap(void) { int n = 256; if (selected_item == CHARMAP) { while (n) { n--; draw_char(n, INNER_X(CHARMAP_X) + n % 16, INNER_Y(CHARMAP_Y) + n / 16, (n == current_char ? 0 : 1), (n == current_char ? 3 : 0)); } } else { while (n) { n--; draw_char(n, INNER_X(CHARMAP_X) + n % 16, INNER_Y(CHARMAP_Y) + n / 16, (n == current_char ? 3 : 1), 0); } } } static inline void draw_itfmap(void) { int n, fg, bg; uint8_t *ptr; if (itfmap_pos < 0 || itfmap_chars[itfmap_pos] != current_char) { ptr = (unsigned char *) memchr((char *) itfmap_chars, current_char, sizeof(itfmap_chars)); if (ptr == NULL) itfmap_pos = -1; else itfmap_pos = ptr - itfmap_chars; } for (n = 0; n < 240; n++) { fg = 1; bg = 0; if (n == itfmap_pos) { if (selected_item == ITFMAP) { fg = 0; bg = 3; } else { fg = 3; } } draw_char(itfmap_chars[n], INNER_X(ITFMAP_X) + n % 16, INNER_Y(ITFMAP_Y) + n / 16, fg, bg); } } static inline void draw_fontlist(void) { int x, pos = 0, n = top_font, cfg, cbg; dmoz_file_t *f; char *ptr; if (selected_item == FONTLIST) { cfg = 0; cbg = 3; } else { cfg = 3; cbg = 0; } if (top_font < 0) top_font = 0; if (n < 0) n = 0; while (n < flist.num_files && pos < VISIBLE_FONTS) { x = 1; f = flist.files[n]; if (!f) break; ptr = f->base; if (n == cur_font) { draw_char(183, INNER_X(FONTLIST_X), INNER_Y(FONTLIST_Y) + pos, cfg, cbg); while (x < 9 && *ptr && (n == 0 || *ptr != '.')) { draw_char(*ptr, INNER_X(FONTLIST_X) + x, INNER_Y(FONTLIST_Y) + pos, cfg, cbg); x++; ptr++; } while (x < 9) { draw_char(0, INNER_X(FONTLIST_X) + x, INNER_Y(FONTLIST_Y) + pos, cfg, cbg); x++; } } else { draw_char(173, INNER_X(FONTLIST_X), INNER_Y(FONTLIST_Y) + pos, 2, 0); while (x < 9 && *ptr && (n == 0 || *ptr != '.')) { draw_char(*ptr, INNER_X(FONTLIST_X) + x, INNER_Y(FONTLIST_Y) + pos, 5, 0); x++; ptr++; } while (x < 9) { draw_char(0, INNER_X(FONTLIST_X) + x, INNER_Y(FONTLIST_Y) + pos, 5, 0); x++; } } n++; pos++; } } static inline void draw_helptext(void) { const uint8_t *ptr = helptext_gen; const uint8_t *eol; int line; int column; for (line = INNER_Y(HELPTEXT_Y); *ptr; line++) { eol = (unsigned char *) strchr((char *) ptr, '\n'); if (!eol) eol = (unsigned char *) strchr((char *) ptr, '\0'); for (column = INNER_X(HELPTEXT_X); ptr < eol; ptr++, column++) draw_char(*ptr, column, line, 12, 0); ptr++; } for (line = 0; line < 10; line++) draw_char(168, INNER_X(HELPTEXT_X) + 43, INNER_Y(HELPTEXT_Y) + line, 12, 0); /* context sensitive stuff... oooh :) */ switch (selected_item) { case EDITBOX: ptr = helptext_editbox; break; case CHARMAP: case ITFMAP: ptr = helptext_charmap; break; case FONTLIST: ptr = helptext_fontlist; break; } for (line = INNER_Y(HELPTEXT_Y); *ptr; line++) { eol = (unsigned char *) strchr((char *) ptr, '\n'); if (!eol) eol = (unsigned char *) strchr((char *) ptr, '\0'); draw_char(168, INNER_X(HELPTEXT_X) + 43, line, 12, 0); for (column = INNER_X(HELPTEXT_X) + 45; ptr < eol; ptr++, column++) draw_char(*ptr, column, line, 12, 0); ptr++; } draw_text(ver_short_copyright, 77 - strlen(ver_short_copyright), 46, 1, 0); } static inline void draw_time(void) { char buf[16]; sprintf(buf, "%.2d:%.2d:%.2d", status.tmnow.tm_hour, status.tmnow.tm_min, status.tmnow.tm_sec); draw_text(buf, 3, 46, 1, 0); } extern unsigned int color_set[16]; static void draw_screen(void) { draw_fill_chars(0,0,79,49,DEFAULT_FG,0); 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: case MODE_SAVE: draw_frame((fontlist_mode == MODE_LOAD) ? "Load/Browse" : "Save As...", FONTLIST_X, FONTLIST_Y, 9, VISIBLE_FONTS, !!(selected_item == FONTLIST)); draw_fontlist(); break; default: /* Off? (I sure hope so!) */ break; } draw_frame("Quick Help", HELPTEXT_X, HELPTEXT_Y, 74, 12, -1); draw_helptext(); draw_time(); } static void handle_key_editbox(struct key_event * k) { uint8_t tmp[8] = { 0 }; int ci = current_char << 3; int n, bit; uint8_t *ptr = font_data + ci; switch (k->sym) { case SCHISM_KEYSYM_UP: if (k->mod & SCHISM_KEYMOD_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 SCHISM_KEYSYM_DOWN: if (k->mod & SCHISM_KEYMOD_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 SCHISM_KEYSYM_LEFT: if (k->mod & SCHISM_KEYMOD_SHIFT) { for (n = 0; n < 8; n++, ptr++) *ptr = (*ptr >> 7) | (*ptr << 1); } else { if (--edit_x < 0) edit_x = 7; } break; case SCHISM_KEYSYM_RIGHT: if (k->mod & SCHISM_KEYMOD_SHIFT) { for (n = 0; n < 8; n++, ptr++) *ptr = (*ptr << 7) | (*ptr >> 1); } else { edit_x = (edit_x + 1) % 8; } break; case SCHISM_KEYSYM_HOME: edit_x = edit_y = 0; break; case SCHISM_KEYSYM_END: edit_x = edit_y = 7; break; case SCHISM_KEYSYM_SPACE: ptr[edit_y] ^= (128 >> edit_x); break; case SCHISM_KEYSYM_INSERT: if (k->mod & SCHISM_KEYMOD_SHIFT) { for (n = 0; n < 8; n++) ptr[n] |= (128 >> edit_x); } else { ptr[edit_y] = 255; } break; case SCHISM_KEYSYM_DELETE: if (k->mod & SCHISM_KEYMOD_SHIFT) { for (n = 0; n < 8; n++) ptr[n] &= ~(128 >> edit_x); } else { ptr[edit_y] = 0; } break; case SCHISM_KEYSYM_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 SCHISM_KEYSYM_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 SCHISM_KEYSYM_PLUS: case SCHISM_KEYSYM_EQUALS: current_char++; break; case SCHISM_KEYSYM_MINUS: case SCHISM_KEYSYM_UNDERSCORE: current_char--; break; case SCHISM_KEYSYM_PAGEUP: current_char -= 16; break; case SCHISM_KEYSYM_PAGEDOWN: current_char += 16; break; default: return; } status.flags |= NEED_UPDATE; } static void handle_key_charmap(struct key_event * k) { switch (k->sym) { case SCHISM_KEYSYM_UP: current_char -= 16; break; case SCHISM_KEYSYM_DOWN: current_char += 16; break; case SCHISM_KEYSYM_LEFT: current_char = DECR_WRAPPED(current_char); break; case SCHISM_KEYSYM_RIGHT: current_char = INCR_WRAPPED(current_char); break; case SCHISM_KEYSYM_HOME: current_char = 0; break; case SCHISM_KEYSYM_END: current_char = 255; break; default: return; } status.flags |= NEED_UPDATE; } static void handle_key_itfmap(struct key_event * k) { switch (k->sym) { case SCHISM_KEYSYM_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 SCHISM_KEYSYM_DOWN: if (itfmap_pos < 0) itfmap_pos = 16; else itfmap_pos = (itfmap_pos + 16) % 240; current_char = itfmap_chars[itfmap_pos]; break; case SCHISM_KEYSYM_LEFT: if (itfmap_pos < 0) itfmap_pos = 15; else itfmap_pos = DECR_WRAPPED(itfmap_pos); current_char = itfmap_chars[itfmap_pos]; break; case SCHISM_KEYSYM_RIGHT: if (itfmap_pos < 0) itfmap_pos = 0; else itfmap_pos = INCR_WRAPPED(itfmap_pos); current_char = itfmap_chars[itfmap_pos]; break; case SCHISM_KEYSYM_HOME: current_char = itfmap_chars[0]; itfmap_pos = 0; break; case SCHISM_KEYSYM_END: current_char = itfmap_chars[239]; itfmap_pos = 239; break; default: return; } status.flags |= NEED_UPDATE; } static void confirm_font_save_ok(void *vf) { char *f = vf; if (font_save(f) != 0) return; selected_item = EDITBOX; } static void handle_key_fontlist(struct key_event * k) { int new_font = cur_font; switch (k->sym) { case SCHISM_KEYSYM_HOME: new_font = 0; break; case SCHISM_KEYSYM_END: new_font = flist.num_files - 1; break; case SCHISM_KEYSYM_UP: new_font--; break; case SCHISM_KEYSYM_DOWN: new_font++; break; case SCHISM_KEYSYM_PAGEUP: new_font -= VISIBLE_FONTS; break; case SCHISM_KEYSYM_PAGEDOWN: new_font += VISIBLE_FONTS; break; case SCHISM_KEYSYM_ESCAPE: selected_item = EDITBOX; fontlist_mode = MODE_OFF; break; case SCHISM_KEYSYM_RETURN: if (k->state == KEY_PRESS) return; switch (fontlist_mode) { case MODE_LOAD: if (cur_font < flist.num_files && flist.files[cur_font] && font_load(flist.files[cur_font]->base) != 0) font_reset(); break; case MODE_SAVE: if (cur_font < flist.num_files && flist.files[cur_font]) { if (strcasecmp(flist.files[cur_font]->base,"font.cfg") != 0) { dialog_create(DIALOG_OK_CANCEL, "Overwrite font file?", confirm_font_save_ok, NULL, 1, flist.files[cur_font]->base); return; } confirm_font_save_ok(flist.files[cur_font]->base); } selected_item = EDITBOX; /* fontlist_mode = MODE_OFF; */ break; default: /* should never happen */ return; } break; default: return; } if (new_font != cur_font) { new_font = CLAMP(new_font, 0, flist.num_files - 1); if (new_font == cur_font) return; cur_font = new_font; fontlist_reposition(); } status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ static void handle_mouse_editbox(struct key_event *k) { int n, ci = current_char << 3, xrel, yrel; uint8_t *ptr = font_data + ci; xrel = k->x - INNER_X(EDITBOX_X); yrel = k->y - INNER_Y(EDITBOX_Y); if (xrel > 0 && yrel > 2) { edit_x = xrel - 1; edit_y = yrel - 3; switch (k->mouse_button) { case MOUSE_BUTTON_LEFT: /* set */ ptr[edit_y] |= (128 >> edit_x); break; case MOUSE_BUTTON_MIDDLE: /* invert */ if (k->state == KEY_RELEASE) return; ptr[edit_y] ^= (128 >> edit_x); break; case MOUSE_BUTTON_RIGHT: /* clear */ ptr[edit_y] &= ~(128 >> edit_x); break; } } else if (xrel == 0 && yrel == 2) { /* clicking at the origin modifies the entire character */ switch (k->mouse_button) { case MOUSE_BUTTON_LEFT: /* set */ for (n = 0; n < 8; n++) ptr[n] = 255; break; case MOUSE_BUTTON_MIDDLE: /* invert */ if (k->state == KEY_RELEASE) return; for (n = 0; n < 8; n++) ptr[n] ^= 255; break; case MOUSE_BUTTON_RIGHT: /* clear */ for (n = 0; n < 8; n++) ptr[n] = 0; break; } } else if (xrel == 0 && yrel > 2) { edit_y = yrel - 3; switch (k->mouse_button) { case MOUSE_BUTTON_LEFT: /* set */ ptr[edit_y] = 255; break; case MOUSE_BUTTON_MIDDLE: /* invert */ if (k->state == KEY_RELEASE) return; ptr[edit_y] ^= 255; break; case MOUSE_BUTTON_RIGHT: /* clear */ ptr[edit_y] = 0; break; } } else if (yrel == 2 && xrel > 0) { edit_x = xrel - 1; switch (k->mouse_button) { case MOUSE_BUTTON_LEFT: /* set */ for (n = 0; n < 8; n++) ptr[n] |= (128 >> edit_x); break; case MOUSE_BUTTON_MIDDLE: /* invert */ if (k->state == KEY_RELEASE) return; for (n = 0; n < 8; n++) ptr[n] ^= (128 >> edit_x); break; case MOUSE_BUTTON_RIGHT: /* clear */ for (n = 0; n < 8; n++) ptr[n] &= ~(128 >> edit_x); break; } } } static void handle_mouse_charmap(struct key_event *k) { int xrel = k->x - INNER_X(CHARMAP_X), yrel = k->y - INNER_Y(CHARMAP_Y); if (!k->mouse) return; current_char = 16 * yrel + xrel; } static void handle_mouse_itfmap(struct key_event *k) { int xrel = k->x - INNER_X(ITFMAP_X), yrel = k->y - INNER_Y(ITFMAP_Y); if (!k->mouse) return; itfmap_pos = 16 * yrel + xrel; current_char = itfmap_chars[itfmap_pos]; } static void handle_mouse(struct key_event * k) { int x = k->x, y = k->y; if (POINT_IN_FRAME(x, y, EDITBOX)) { selected_item = EDITBOX; if (POINT_IN(x, y, EDITBOX)) handle_mouse_editbox(k); } else if (POINT_IN_FRAME(x, y, CHARMAP)) { selected_item = CHARMAP; if (POINT_IN(x, y, CHARMAP)) handle_mouse_charmap(k); } else if (POINT_IN_FRAME(x, y, ITFMAP)) { selected_item = ITFMAP; if (POINT_IN(x, y, ITFMAP)) handle_mouse_itfmap(k); } else { //printf("stray click\n"); return; } status.flags |= NEED_UPDATE; } static int fontedit_handle_key(struct key_event * k) { int n, ci = current_char << 3; uint8_t *ptr = font_data + ci; if (k->mouse == MOUSE_SCROLL_UP || k->mouse == MOUSE_SCROLL_DOWN) { /* err... */ return 0; } if (k->mouse == MOUSE_CLICK) { handle_mouse(k); return 1; } /* kp is special */ switch (k->sym) { case SCHISM_KEYSYM_KP_0: if (k->state == KEY_RELEASE) return 1; k->sym += 10; /* fall through */ case SCHISM_KEYSYM_KP_1: case SCHISM_KEYSYM_KP_2: case SCHISM_KEYSYM_KP_3: case SCHISM_KEYSYM_KP_4: case SCHISM_KEYSYM_KP_5: case SCHISM_KEYSYM_KP_6: case SCHISM_KEYSYM_KP_7: case SCHISM_KEYSYM_KP_8: case SCHISM_KEYSYM_KP_9: if (k->state == KEY_RELEASE) return 1; n = k->sym - SCHISM_KEYSYM_KP_1; if (k->mod & SCHISM_KEYMOD_SHIFT) n += 10; palette_load_preset(n); palette_apply(); status.flags |= NEED_UPDATE; return 1; default: break; }; switch (k->sym) { case '0': if (k->state == KEY_RELEASE) return 1; k->sym += 10; /* fall through */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (k->state == KEY_RELEASE) return 1; n = k->sym - '1'; if (k->mod & SCHISM_KEYMOD_SHIFT) n += 10; palette_load_preset(n); palette_apply(); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_F2: if (k->state == KEY_RELEASE) return 1; selected_item = EDITBOX; status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_F3: if (k->state == KEY_RELEASE) return 1; selected_item = CHARMAP; status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_F4: if (k->state == KEY_RELEASE) return 1; selected_item = ITFMAP; status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_TAB: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_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 SCHISM_KEYSYM_c: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_ALT) { memcpy(clipboard, ptr, 8); return 1; } break; case SCHISM_KEYSYM_p: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_ALT) { memcpy(ptr, clipboard, 8); status.flags |= NEED_UPDATE; return 1; } break; case SCHISM_KEYSYM_m: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_CTRL) { video_mousecursor(video_mousecursor_visible() ? MOUSE_DISABLED : MOUSE_EMULATED); return 1; } else if (k->mod & SCHISM_KEYMOD_ALT) { for (n = 0; n < 8; n++) ptr[n] |= clipboard[n]; status.flags |= NEED_UPDATE; return 1; } break; case SCHISM_KEYSYM_z: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_ALT) { memset(ptr, 0, 8); status.flags |= NEED_UPDATE; return 1; } break; case SCHISM_KEYSYM_h: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_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 SCHISM_KEYSYM_v: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_ALT) { for (n = 0; n < 4; n++) { uint8_t r = ptr[n]; ptr[n] = ptr[7 - n]; ptr[7 - n] = r; } status.flags |= NEED_UPDATE; return 1; } break; case SCHISM_KEYSYM_i: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_ALT) { for (n = 0; n < 8; n++) font_data[ci + n] ^= 255; status.flags |= NEED_UPDATE; return 1; } break; /* ----------------------------------------------------- */ case SCHISM_KEYSYM_l: case SCHISM_KEYSYM_r: if (k->state == KEY_RELEASE) return 1; if (!(k->mod & SCHISM_KEYMOD_CTRL)) break; /* fall through */ case SCHISM_KEYSYM_F9: if (k->state == KEY_RELEASE) return 1; load_fontlist(); fontlist_mode = MODE_LOAD; selected_item = FONTLIST; status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_s: if (k->state == KEY_RELEASE) return 1; if (!(k->mod & SCHISM_KEYMOD_CTRL)) break; /* fall through */ case SCHISM_KEYSYM_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 SCHISM_KEYSYM_BACKSPACE: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_CTRL) { font_reset_bios(); } else if (k->mod & SCHISM_KEYMOD_ALT) { font_reset_char(current_char); } else { font_reset_upper(); } status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_RETURN: return 0; case SCHISM_KEYSYM_q: if (k->mod & SCHISM_KEYMOD_CTRL) return 0; if (k->state == KEY_RELEASE) return 1; break; default: if (k->state == KEY_RELEASE) return 1; break; } switch (selected_item) { case EDITBOX: handle_key_editbox(k); break; case CHARMAP: handle_key_charmap(k); break; case ITFMAP: handle_key_itfmap(k); break; case FONTLIST: handle_key_fontlist(k); break; default: break; } return 1; } static struct widget fontedit_widget_hack[1]; static int fontedit_key_hack(struct key_event *k) { switch (k->sym) { case SCHISM_KEYSYM_r: case SCHISM_KEYSYM_l: case SCHISM_KEYSYM_s: case SCHISM_KEYSYM_c: case SCHISM_KEYSYM_p: case SCHISM_KEYSYM_m: case SCHISM_KEYSYM_z: case SCHISM_KEYSYM_v: case SCHISM_KEYSYM_h: case SCHISM_KEYSYM_i: case SCHISM_KEYSYM_q: case SCHISM_KEYSYM_w: case SCHISM_KEYSYM_F1: case SCHISM_KEYSYM_F2: case SCHISM_KEYSYM_F3: case SCHISM_KEYSYM_F4: case SCHISM_KEYSYM_F5: case SCHISM_KEYSYM_F6: case SCHISM_KEYSYM_F7: case SCHISM_KEYSYM_F8: case SCHISM_KEYSYM_F9: case SCHISM_KEYSYM_F10: case SCHISM_KEYSYM_F11: case SCHISM_KEYSYM_F12: return fontedit_handle_key(k); case SCHISM_KEYSYM_RETURN: if (status.dialog_type & (DIALOG_MENU|DIALOG_BOX)) return 0; if (selected_item == FONTLIST) { handle_key_fontlist(k); return 1; } default: break; }; return 0; } static void do_nil(void) {} void fontedit_load_page(struct page *page) { page->title = ""; page->draw_full = draw_screen; page->total_widgets = 1; page->pre_handle_key = fontedit_key_hack; page->widgets = fontedit_widget_hack; widget_create_other(fontedit_widget_hack, 0, fontedit_handle_key, NULL, do_nil); } schismtracker-20250313/schism/keyboard.c000066400000000000000000000464751476471630300201020ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "keyboard.h" #include "charset.h" #include "song.h" #include "page.h" #include "osdefs.h" #include "timer.h" #include "mem.h" #include "str.h" #include /* --------------------------------------------------------------------- */ static const char *note_names_up[12] = { "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" }; static const char note_names_short_up[12] = "cCdDefFgGaAb"; static const char *note_names_down[12] = { "C-", "Db", "D-", "Eb", "E-", "F-", "Gb", "G-", "Ab", "A-", "Bb", "B-" }; static const char note_names_short_down[12] = "CdDeEFgGaAbB"; static const char **note_names = note_names_up; static const char *note_names_short = note_names_short_up; /* --------------------------------------------------------------------- */ static int current_octave = 4; /* --------------------------------------------------------------------- */ /* this is used in a couple of other places... maybe it should be in some * general-stuff file? */ const char hexdigits[16] = "0123456789ABCDEF"; /* extra non-IT effects: * '!' = volume '$' = keyoff * '&' = setenvposition * '('/')' = noteslide up/down (IMF) */ static const char effects[] = ".JFEGHLKRXODB!CQATI?SMNVW$UY?P&Z()?"; static const char ptm_effects[] = ".0123456789ABCDRFFT????GHK?YXPLZ()?"; /* --------------------------------------------------------------------- */ kbd_sharp_flat_t kbd_sharp_flat_state(void) { return (note_names == note_names_up) ? KBD_SHARP_FLAT_SHARPS : KBD_SHARP_FLAT_FLATS; } void kbd_sharp_flat_toggle(kbd_sharp_flat_t e) { switch (e) { case KBD_SHARP_FLAT_TOGGLE: kbd_sharp_flat_toggle((note_names == note_names_up) ? KBD_SHARP_FLAT_FLATS : KBD_SHARP_FLAT_SHARPS); return; default: case KBD_SHARP_FLAT_SHARPS: status_text_flash("Displaying accidentals as sharps (#)"); note_names = note_names_up; note_names_short = note_names_short_up; break; case KBD_SHARP_FLAT_FLATS: status_text_flash("Displaying accidentals as flats (b)"); note_names = note_names_down; note_names_short = note_names_short_down; break; } } char get_effect_char(int effect) { if (effect < 0 || effect > 34) { log_appendf(4, "get_effect_char: effect %d out of range", effect); return '?'; } return effects[effect]; } int get_ptm_effect_number(char effect) { const char *ptr; if (effect >= 'a' && effect <= 'z') effect -= 32; ptr = strchr(ptm_effects, effect); return ptr ? (ptr - effects) : -1; } int get_effect_number(char effect) { const char *ptr; if (effect >= 'a' && effect <= 'z') { effect -= 32; } else if (!((effect >= '0' && effect <= '9') || (effect >= 'A' && effect <= 'Z') || (effect == '.'))) { /* don't accept pseudo-effects */ if (status.flags & CLASSIC_MODE) return -1; } ptr = strchr(effects, effect); return ptr ? ptr - effects : -1; } int kbd_get_effect_number(struct key_event *k) { if (!NO_CAM_MODS(k->mod)) return -1; switch (k->sym) { #define QZA(n) case SCHISM_KEYSYM_ ## 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 SCHISM_KEYSYM_PERIOD: case SCHISM_KEYSYM_KP_PERIOD: return get_effect_number('.'); case SCHISM_KEYSYM_1: if (!(k->mod & SCHISM_KEYMOD_SHIFT)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_EXCLAIM: return get_effect_number('!'); case SCHISM_KEYSYM_4: if (!(k->mod & SCHISM_KEYMOD_SHIFT)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_DOLLAR: return get_effect_number('$'); case SCHISM_KEYSYM_7: if (!(k->mod & SCHISM_KEYMOD_SHIFT)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_AMPERSAND: return get_effect_number('&'); default: return -1; }; } /* --------------------------------------------------------------------- */ void kbd_key_translate(struct key_event *k) { /* This assumes a US keyboard layout. There's no real "easy" way * to solve this besides possibly using system-specific translate * functions, but that's clunky and a lot of systems don't even * provide that IIRC. */ k->orig_sym = k->sym; if (k->mod & SCHISM_KEYMOD_SHIFT) { switch (k->sym) { case SCHISM_KEYSYM_COMMA: k->sym = SCHISM_KEYSYM_LESS; break; case SCHISM_KEYSYM_PERIOD: k->sym = SCHISM_KEYSYM_GREATER; break; case SCHISM_KEYSYM_4: k->sym = SCHISM_KEYSYM_DOLLAR; break; case SCHISM_KEYSYM_EQUALS: k->sym = SCHISM_KEYSYM_PLUS; break; case SCHISM_KEYSYM_SEMICOLON: k->sym = SCHISM_KEYSYM_COLON; break; case SCHISM_KEYSYM_8: k->sym = SCHISM_KEYSYM_ASTERISK; break; default: break; }; } if (k->mod & SCHISM_KEYMOD_GUI) { k->mod = ((k->mod & ~SCHISM_KEYMOD_GUI) | ((status.flags & META_IS_CTRL) ? SCHISM_KEYMOD_CTRL : SCHISM_KEYMOD_ALT)); } if ((k->mod & SCHISM_KEYMOD_MODE) && (status.flags & ALTGR_IS_ALT)) { /* Treat AltGr as Alt (delt) */ k->mod = ((k->mod & ~SCHISM_KEYMOD_MODE) | SCHISM_KEYMOD_ALT); } if (k->mod & SCHISM_KEYMOD_NUM) { switch (k->sym) { case SCHISM_KEYSYM_KP_0: k->sym = SCHISM_KEYSYM_0; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_1: k->sym = SCHISM_KEYSYM_1; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_2: k->sym = SCHISM_KEYSYM_2; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_3: k->sym = SCHISM_KEYSYM_3; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_4: k->sym = SCHISM_KEYSYM_4; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_5: k->sym = SCHISM_KEYSYM_5; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_6: k->sym = SCHISM_KEYSYM_6; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_7: k->sym = SCHISM_KEYSYM_7; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_8: k->sym = SCHISM_KEYSYM_8; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_9: k->sym = SCHISM_KEYSYM_9; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_PERIOD: k->sym = SCHISM_KEYSYM_PERIOD; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_DIVIDE: k->sym = SCHISM_KEYSYM_SLASH; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_MULTIPLY: k->sym = SCHISM_KEYSYM_ASTERISK; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_MINUS: k->sym = SCHISM_KEYSYM_MINUS; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_PLUS: k->sym = SCHISM_KEYSYM_PLUS; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_ENTER: k->sym = SCHISM_KEYSYM_RETURN; k->mod &= ~SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_KP_EQUALS: k->sym = SCHISM_KEYSYM_EQUALS; k->mod &= ~SCHISM_KEYMOD_NUM; break; default: break; }; } else { switch (k->sym) { case SCHISM_KEYSYM_KP_0: k->sym = SCHISM_KEYSYM_INSERT; break; case SCHISM_KEYSYM_KP_4: k->sym = SCHISM_KEYSYM_LEFT; break; case SCHISM_KEYSYM_KP_6: k->sym = SCHISM_KEYSYM_RIGHT; break; case SCHISM_KEYSYM_KP_2: k->sym = SCHISM_KEYSYM_DOWN; break; case SCHISM_KEYSYM_KP_8: k->sym = SCHISM_KEYSYM_UP; break; case SCHISM_KEYSYM_KP_9: k->sym = SCHISM_KEYSYM_PAGEUP; break; case SCHISM_KEYSYM_KP_3: k->sym = SCHISM_KEYSYM_PAGEDOWN; break; case SCHISM_KEYSYM_KP_7: k->sym = SCHISM_KEYSYM_HOME; break; case SCHISM_KEYSYM_KP_1: k->sym = SCHISM_KEYSYM_END; break; case SCHISM_KEYSYM_KP_PERIOD: k->sym = SCHISM_KEYSYM_DELETE; break; case SCHISM_KEYSYM_KP_DIVIDE: k->sym = SCHISM_KEYSYM_SLASH; break; case SCHISM_KEYSYM_KP_MULTIPLY: k->sym = SCHISM_KEYSYM_ASTERISK; break; case SCHISM_KEYSYM_KP_MINUS: k->sym = SCHISM_KEYSYM_MINUS; break; case SCHISM_KEYSYM_KP_PLUS: k->sym = SCHISM_KEYSYM_PLUS; break; case SCHISM_KEYSYM_KP_ENTER: k->sym = SCHISM_KEYSYM_RETURN; break; case SCHISM_KEYSYM_KP_EQUALS: k->sym = SCHISM_KEYSYM_EQUALS; break; default: break; }; } // do nothing } int numeric_key_event(struct key_event *k, int kponly) { if (kponly) { switch (k->sym) { case SCHISM_KEYSYM_KP_0: return 0; case SCHISM_KEYSYM_KP_1: return 1; case SCHISM_KEYSYM_KP_2: return 2; case SCHISM_KEYSYM_KP_3: return 3; case SCHISM_KEYSYM_KP_4: return 4; case SCHISM_KEYSYM_KP_5: return 5; case SCHISM_KEYSYM_KP_6: return 6; case SCHISM_KEYSYM_KP_7: return 7; case SCHISM_KEYSYM_KP_8: return 8; case SCHISM_KEYSYM_KP_9: return 9; default: break; }; return -1; } switch (k->sym) { case SCHISM_KEYSYM_0: case SCHISM_KEYSYM_KP_0: return 0; case SCHISM_KEYSYM_1: case SCHISM_KEYSYM_KP_1: return 1; case SCHISM_KEYSYM_2: case SCHISM_KEYSYM_KP_2: return 2; case SCHISM_KEYSYM_3: case SCHISM_KEYSYM_KP_3: return 3; case SCHISM_KEYSYM_4: case SCHISM_KEYSYM_KP_4: return 4; case SCHISM_KEYSYM_5: case SCHISM_KEYSYM_KP_5: return 5; case SCHISM_KEYSYM_6: case SCHISM_KEYSYM_KP_6: return 6; case SCHISM_KEYSYM_7: case SCHISM_KEYSYM_KP_7: return 7; case SCHISM_KEYSYM_8: case SCHISM_KEYSYM_KP_8: return 8; case SCHISM_KEYSYM_9: case SCHISM_KEYSYM_KP_9: return 9; default: break; }; return -1; } /* --------------------------------------------------------------------- */ char *get_volume_string(int volume, int volume_effect, char *buf) { const char cmd_table[16] = "...CDAB$H<>GFE"; buf[2] = 0; if (volume_effect < 0 || volume_effect > 13) { log_appendf(4, "get_volume_string: volume effect %d out" " of range", volume_effect); buf[0] = buf[1] = '?'; return buf; } /* '$'=vibratospeed, '<'=panslideleft, '>'=panslideright */ switch (volume_effect) { case VOLFX_NONE: buf[0] = buf[1] = '\xAD'; break; case VOLFX_VOLUME: case VOLFX_PANNING: /* Yeah, a bit confusing :) * The display stuff makes the distinction here with * a different color for panning. */ str_from_num(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] = '\xAD'; break; case NOTE_CUT: buf[0] = buf[1] = buf[2] = '\x5E'; break; case NOTE_OFF: buf[0] = buf[1] = buf[2] = '\xCD'; break; case NOTE_FADE: /* this is sure to look "out of place" to anyone ... yeah, no kidding. /storlek */ #if 0 buf[0] = 185; buf[1] = 186; buf[2] = 185; #else buf[0] = buf[1] = buf[2] = '\x7E'; #endif break; default: note--; buf[0] = note_names[note % 12][0]; buf[1] = note_names[note % 12][1]; buf[2] = note / 12 + '0'; } 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] = '\xAD'; break; case NOTE_CUT: buf[0] = buf[1] = '\x5E'; break; case NOTE_OFF: buf[0] = buf[1] = '\xCD'; break; case NOTE_FADE: buf[0] = buf[1] = '\x7E'; break; default: note--; buf[0] = note_names_short[note % 12]; buf[1] = note / 12 + '0'; } buf[2] = 0; return buf; } /* --------------------------------------------------------------------- */ int kbd_get_current_octave(void) { return current_octave; } void kbd_set_current_octave(int new_octave) { new_octave = CLAMP(new_octave, 0, 8); current_octave = new_octave; /* a full screen update for one lousy letter... */ status.flags |= NEED_UPDATE; } int kbd_char_to_99(struct key_event *k) { int c; if (!NO_CAM_MODS(k->mod)) return -1; c = tolower(k->sym); if (c >= 'h' && c <= 'z') return 10 + c - 'h'; return kbd_char_to_hex(k); } int kbd_char_to_hex(struct key_event *k) { if (!NO_CAM_MODS(k->mod)) return -1; switch (k->orig_sym) { case SCHISM_KEYSYM_KP_0: if (!(k->mod & SCHISM_KEYMOD_NUM)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_0: return 0; case SCHISM_KEYSYM_KP_1: if (!(k->mod & SCHISM_KEYMOD_NUM)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_1: return 1; case SCHISM_KEYSYM_KP_2: if (!(k->mod & SCHISM_KEYMOD_NUM)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_2: return 2; case SCHISM_KEYSYM_KP_3: if (!(k->mod & SCHISM_KEYMOD_NUM)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_3: return 3; case SCHISM_KEYSYM_KP_4: if (!(k->mod & SCHISM_KEYMOD_NUM)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_4: return 4; case SCHISM_KEYSYM_KP_5: if (!(k->mod & SCHISM_KEYMOD_NUM)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_5: return 5; case SCHISM_KEYSYM_KP_6: if (!(k->mod & SCHISM_KEYMOD_NUM)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_6: return 6; case SCHISM_KEYSYM_KP_7: if (!(k->mod & SCHISM_KEYMOD_NUM)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_7: return 7; case SCHISM_KEYSYM_KP_8: if (!(k->mod & SCHISM_KEYMOD_NUM)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_8: return 8; case SCHISM_KEYSYM_KP_9: if (!(k->mod & SCHISM_KEYMOD_NUM)) return -1; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_9: return 9; case SCHISM_KEYSYM_a: return 10; case SCHISM_KEYSYM_b: return 11; case SCHISM_KEYSYM_c: return 12; case SCHISM_KEYSYM_d: return 13; case SCHISM_KEYSYM_e: return 14; case SCHISM_KEYSYM_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. */ int kbd_get_note(struct key_event *k) { int note; if (!NO_CAM_MODS(k->mod)) return -1; if (k->sym == SCHISM_KEYSYM_KP_1 || k->sym == SCHISM_KEYSYM_KP_PERIOD) if (!(k->mod & SCHISM_KEYMOD_NUM)) return -1; switch (k->scancode) { case SCHISM_SCANCODE_GRAVE: if (k->mod & SCHISM_KEYMOD_SHIFT) return NOTE_FADE; SCHISM_FALLTHROUGH; case SCHISM_SCANCODE_NONUSHASH: /* for delt */ case SCHISM_SCANCODE_KP_HASH: return NOTE_OFF; case SCHISM_SCANCODE_1: return NOTE_CUT; case SCHISM_SCANCODE_PERIOD: return 0; /* clear */ case SCHISM_SCANCODE_Z: note = 1; break; case SCHISM_SCANCODE_S: note = 2; break; case SCHISM_SCANCODE_X: note = 3; break; case SCHISM_SCANCODE_D: note = 4; break; case SCHISM_SCANCODE_C: note = 5; break; case SCHISM_SCANCODE_V: note = 6; break; case SCHISM_SCANCODE_G: note = 7; break; case SCHISM_SCANCODE_B: note = 8; break; case SCHISM_SCANCODE_H: note = 9; break; case SCHISM_SCANCODE_N: note = 10; break; case SCHISM_SCANCODE_J: note = 11; break; case SCHISM_SCANCODE_M: note = 12; break; case SCHISM_SCANCODE_Q: note = 13; break; case SCHISM_SCANCODE_2: note = 14; break; case SCHISM_SCANCODE_W: note = 15; break; case SCHISM_SCANCODE_3: note = 16; break; case SCHISM_SCANCODE_E: note = 17; break; case SCHISM_SCANCODE_R: note = 18; break; case SCHISM_SCANCODE_5: note = 19; break; case SCHISM_SCANCODE_T: note = 20; break; case SCHISM_SCANCODE_6: note = 21; break; case SCHISM_SCANCODE_Y: note = 22; break; case SCHISM_SCANCODE_7: note = 23; break; case SCHISM_SCANCODE_U: note = 24; break; case SCHISM_SCANCODE_I: note = 25; break; case SCHISM_SCANCODE_9: note = 26; break; case SCHISM_SCANCODE_O: note = 27; break; case SCHISM_SCANCODE_0: note = 28; break; case SCHISM_SCANCODE_P: note = 29; break; default: return -1; }; note += (12 * current_octave); return CLAMP(note, 1, 120); } int kbd_get_alnum(struct key_event *k) { if (k->sym >= 127) return 0; if (k->mod & SCHISM_KEYMOD_SHIFT) { const char shifted_digits[] = ")!@#$%^&*("; // comical profanity switch (k->orig_sym) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': return toupper(k->sym); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return shifted_digits[k->sym - '0']; case '[': return '{'; case ']': return '}'; case ';': return ':'; case '=': return '+'; case '-': return '_'; case '`': return '~'; case ',': return '<'; case '.': return '>'; case '/': return '?'; case '\\': return '|'; case '\'': return '"'; // maybe key_translate handled it default: return k->sym; // shift + some weird key = ??? } } // hm return k->sym; } /* -------------------------------------------------- */ /* emulate SDL 1.2 style key repeat */ static int key_repeat_delay = 0; static int key_repeat_rate = 0; static int key_repeat_enabled = 0; static timer_ticks_t key_repeat_next_tick = 0; static struct key_event cached_key_event = {0}; int kbd_key_repeat_enabled(void) { return key_repeat_enabled; } void kbd_handle_key_repeat(void) { if (!key_repeat_next_tick || !key_repeat_enabled) return; const timer_ticks_t now = timer_ticks(); if (timer_ticks_passed(now, key_repeat_next_tick)) { /* handle key functions have the ability to * change the values of the key_event structure. * * see: issue #465 */ struct key_event kk = cached_key_event; handle_key(&kk); key_repeat_next_tick = now + key_repeat_rate; } } void kbd_cache_key_repeat(struct key_event* kk) { if (!key_repeat_enabled) return; if (cached_key_event.text) free((uint8_t*)cached_key_event.text); cached_key_event = *kk; cached_key_event.is_repeat = 1; /* need to duplicate this as well */ if (cached_key_event.text) cached_key_event.text = str_dup(cached_key_event.text); key_repeat_next_tick = timer_ticks() + key_repeat_delay + key_repeat_rate; } void kbd_empty_key_repeat(void) { if (!key_repeat_enabled) return; if (cached_key_event.text) { free((void *)cached_key_event.text); cached_key_event.text = NULL; } key_repeat_next_tick = 0; } void kbd_set_key_repeat(int delay, int rate) { /* I don't know why this check is here but i'm keeping it to * retain compatibility */ if (delay) { key_repeat_delay = delay; key_repeat_rate = rate; key_repeat_enabled = 1; } } /* -------------------------------------------------- */ schismtracker-20250313/schism/loadso.c000066400000000000000000000124171476471630300175500ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "charset.h" #include "loadso.h" #include "mem.h" #ifdef SCHISM_WIN32 # include #elif defined(SCHISM_OS2) # define INCL_DOS # include # include #elif defined(HAVE_LIBDL) # include #endif void *loadso_object_load(const char *sofile) { #ifdef SCHISM_WIN32 HMODULE module = NULL; // Suppress "library failed to load" errors on Win9x and NT 4 DWORD em = SetErrorMode(0); SetErrorMode(em | SEM_FAILCRITICALERRORS); if (GetVersion() & 0x80000000) { // Windows 9x char *ansi; if (!charset_iconv(sofile, &ansi, CHARSET_UTF8, CHARSET_ANSI, SIZE_MAX)) { module = LoadLibraryA(ansi); free(ansi); } } else { // Windows NT wchar_t *unicode; if (!charset_iconv(sofile, &unicode, CHARSET_UTF8, CHARSET_WCHAR_T, SIZE_MAX)) { module = LoadLibraryW(unicode); free(unicode); } } if (!module) module = LoadLibraryA(sofile); SetErrorMode(em); if (module) return module; #elif defined(SCHISM_OS2) CHAR error_str[256]; HMODULE module; ULONG rc; // TODO add charset_iconv support for OS/2 rc = DosLoadModule(error_str, sizeof(error_str), sofile, &module); if (rc != NO_ERROR && !strrchr(sofile, '\\') && !strrchr(sofile, '/')) { /* strip .DLL extension and retry if there is no path * (this is what SDL does; I suppose it won't hurt here) */ size_t len = strlen(sofile); if (len > 4 && charset_strcasecmp(&sofile[len - 4], CHARSET_UTF8, ".dll", CHARSET_UTF8)) { char *sofile_strip = strn_dup(sofile, len - 4); rc = DosLoadModule(error_str, sizeof(error_str), sofile, &module); free(sofile_strip); } } if (rc == NO_ERROR) return (void *)module; #elif defined(HAVE_LIBDL) void *object = dlopen(sofile, RTLD_NOW | RTLD_LOCAL); if (object) return object; #endif return NULL; } void *loadso_function_load(void *handle, const char *name) { #ifdef SCHISM_WIN32 void *symbol; symbol = GetProcAddress(handle, name); if (symbol) return symbol; #elif defined(SCHISM_OS2) ULONG rc; PFN pfn; rc = DosQueryProcAddr((HMODULE)handle, 0, name, &pfn); if (rc != NO_ERROR) { char *p; if (asprintf(&p, "_%s", name) >= 0) { rc = DosQueryProcAddr((HMODULE)handle, 0, p, &pfn); free(p); } } if (rc == NO_ERROR) return (void *)pfn; #elif defined(HAVE_LIBDL) void *symbol; symbol = dlsym(handle, name); if (symbol) return symbol; // some platforms require extra work. char *p; if (asprintf(&p, "_%s", name) >= 0) { symbol = dlsym(handle, p); free(p); } if (symbol) return symbol; #endif // doh! return NULL; } void loadso_object_unload(void *object) { #ifdef SCHISM_WIN32 if (object) FreeLibrary(object); #elif defined(HAVE_LIBDL) if (object) dlclose(object); #endif } /* ------------------------------------------------------------------------ */ static const char *loadso_libtool_fmts[] = { #ifdef SCHISM_WIN32 "lib%s-%d.dll", #elif defined(SCHISM_MACOSX) "lib%s.%d.dylib", #else "lib%s.so.%d", #endif NULL, }; static void *library_load_revision(const char *name, int revision) { int i; void *res = NULL; for (i = 0; loadso_libtool_fmts[i] && !res; i++) { char *buf; if (asprintf(&buf, loadso_libtool_fmts[i], name, revision) < 0) continue; res = loadso_object_load(buf); free(buf); } return res; } #undef LIBTOOL_FMT /* loads a library using the current and age versions * as documented by GNU libtool. MAKE SURE the objects * you are importing are actually there in the library! * (i.e. check return values) */ static void *library_load_libtool(const char *name, int current, int age) { int i; void *res = NULL; for (i = 0; i <= age; i++) if ((res = library_load_revision(name, current - i))) break; return res; } /* ------------------------------------------------------------------------ */ static const char *loadso_lib_fmts[] = { #ifdef SCHISM_WIN32 "lib%s.dll", "%s.dll", #elif defined(SCHISM_MACOSX) //"lib%s.dylib", #else //"lib%s.so", #endif NULL, }; void *library_load(const char *name, int current, int age) { void *object = NULL; object = library_load_libtool(name, current, age); if (object) return object; // ... int i; for (i = 0; loadso_lib_fmts[i] && !object; i++) { char *buf; if (asprintf(&buf, loadso_lib_fmts[i], name) < 0) continue; object = loadso_object_load(buf); free(buf); } return object; } schismtracker-20250313/schism/main.c000066400000000000000000000717031476471630300172160ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* This is the first thing I *ever* did with SDL. :) */ /* Warning: spaghetti follows.... it just kind of ended up this way. In other news, TODO: clean this mess on a rainy day. and yes, that in fact rhymed :P */ #include "headers.h" #include "backend/events.h" #include "events.h" #include "clippy.h" #include "disko.h" #include "fakemem.h" #include "config.h" #include "version.h" #include "song.h" #include "midi.h" #include "dmoz.h" #include "charset.h" #include "keyboard.h" #include "palettes.h" #include "fonts.h" #include "dialog.h" #include "widget.h" #include "fmt.h" #include "timer.h" #include "mt.h" #include "mem.h" #include "osdefs.h" #include #include #if !defined(SCHISM_WIN32) && !defined(SCHISM_OS2) # include #endif #if !defined(__amigaos4__) && !defined(SCHISM_WII) # define ENABLE_HOOKS 1 #endif #define NATIVE_SCREEN_WIDTH 640 #define NATIVE_SCREEN_HEIGHT 400 /* --------------------------------------------------------------------- */ /* globals */ enum { EXIT_HOOK = 1, EXIT_SAVECFG = 4, EXIT_SDLQUIT = 16, }; static int shutdown_process = 0; static const char *video_driver = NULL; static const char *audio_driver = NULL; static const char *audio_device = NULL; static int want_fullscreen = -1; static int did_classic = 0; /* --------------------------------------------------------------------- */ #define SDL_INIT_FLAGS SDL_INIT_TIMER | SDL_INIT_VIDEO static void check_update(void); void toggle_display_fullscreen(void) { video_fullscreen(-1); status.flags |= (NEED_UPDATE); } /* --------------------------------------------------------------------- */ #if ENABLE_HOOKS static void run_startup_hook(void) { os_run_hook(cfg_dir_dotschism, "startup-hook", NULL); } static void run_disko_complete_hook(void) { os_run_hook(cfg_dir_dotschism, "diskwriter-hook", NULL); } static void run_exit_hook(void) { os_run_hook(cfg_dir_dotschism, "exit-hook", NULL); } #endif /* --------------------------------------------------------------------- */ /* arg parsing */ /* filename of song to load on startup, or NULL for none */ #ifdef SCHISM_MACOSX /* this gets written to by the custom main function on macosx */ char *initial_song = NULL; #else static char *initial_song = NULL; #endif /* initial module directory */ static char *initial_dir = NULL; /* diskwrite? */ static char *diskwrite_to = NULL; /* startup flags */ enum { SF_PLAY = 1, /* -p: start playing after loading initial_song */ SF_HOOKS = 2, /* --no-hooks: don't run startup/exit scripts */ SF_FONTEDIT = 4, SF_CLASSIC = 8, SF_NETWORK = 16, SF_HEADLESS = 32, }; static int startup_flags = SF_HOOKS | SF_NETWORK; /* getopt ids */ #define SHORT_OPTIONS "a:v:fFpPh" // these should correspond to the characters below (trailing colon indicates argument) enum { // short options O_SDL_AUDIODRIVER = 'a', O_SDL_VIDEODRIVER = 'v', O_FULLSCREEN = 'f', O_NO_FULLSCREEN = 'F', O_PLAY = 'p', O_NO_PLAY = 'P', O_HELP = 'h', // ids for long options with no corresponding short option O_VIDEO_YUVLAYOUT = 256, O_VIDEO_RESOLUTION, O_VIDEO_STRETCH, O_NO_VIDEO_STRETCH, O_VIDEO_DEPTH, #if USE_NETWORK O_NETWORK, O_NO_NETWORK, #endif O_CLASSIC_MODE, O_NO_CLASSIC_MODE, O_FONTEDIT, O_NO_FONTEDIT, #if ENABLE_HOOKS O_HOOKS, O_NO_HOOKS, #endif O_DISKWRITE, O_DEBUG, O_VERSION, O_HEADLESS, }; #define USAGE "Usage: %s [OPTIONS] [DIRECTORY] [FILE]\n" // Remember to update the manpage when changing the command-line options! static void parse_options(int argc, char **argv) { struct option long_options[] = { {"audio-driver", 1, NULL, O_SDL_AUDIODRIVER}, {"video-driver", 1, NULL, O_SDL_VIDEODRIVER}, #if USE_NETWORK {"network", 0, NULL, O_NETWORK}, {"no-network", 0, NULL, O_NO_NETWORK}, #endif {"classic", 0, NULL, O_CLASSIC_MODE}, {"no-classic", 0, NULL, O_NO_CLASSIC_MODE}, {"fullscreen", 0, NULL, O_FULLSCREEN}, {"no-fullscreen", 0, NULL, O_NO_FULLSCREEN}, {"play", 0, NULL, O_PLAY}, {"no-play", 0, NULL, O_NO_PLAY}, {"diskwrite", 1, NULL, O_DISKWRITE}, {"font-editor", 0, NULL, O_FONTEDIT}, {"no-font-editor", 0, NULL, O_NO_FONTEDIT}, #if ENABLE_HOOKS {"hooks", 0, NULL, O_HOOKS}, {"no-hooks", 0, NULL, O_NO_HOOKS}, #endif {"headless", 0, NULL, O_HEADLESS}, {"version", 0, NULL, O_VERSION}, {"help", 0, NULL, O_HELP}, {NULL, 0, NULL, 0}, }; int opt; while ((opt = getopt_long(argc, argv, SHORT_OPTIONS, long_options, NULL)) != -1) { switch (opt) { case O_SDL_AUDIODRIVER: audio_parse_driver_spec(optarg, (char**)&audio_driver, (char**)&audio_device); break; case O_SDL_VIDEODRIVER: video_driver = str_dup(optarg); break; #if USE_NETWORK case O_NETWORK: startup_flags |= SF_NETWORK; break; case O_NO_NETWORK: startup_flags &= ~SF_NETWORK; break; #endif case O_CLASSIC_MODE: startup_flags |= SF_CLASSIC; did_classic = 1; break; case O_NO_CLASSIC_MODE: startup_flags &= ~SF_CLASSIC; did_classic = 1; break; case O_FULLSCREEN: want_fullscreen = 1; break; case O_NO_FULLSCREEN: want_fullscreen = 0; break; case O_PLAY: startup_flags |= SF_PLAY; break; case O_NO_PLAY: startup_flags &= ~SF_PLAY; break; case O_FONTEDIT: startup_flags |= SF_FONTEDIT; break; case O_NO_FONTEDIT: startup_flags &= ~SF_FONTEDIT; break; case O_DISKWRITE: diskwrite_to = optarg; break; #if ENABLE_HOOKS case O_HOOKS: startup_flags |= SF_HOOKS; break; case O_NO_HOOKS: startup_flags &= ~SF_HOOKS; break; #endif case O_HEADLESS: startup_flags |= SF_HEADLESS; break; case O_VERSION: puts(schism_banner(0)); puts(ver_short_copyright); exit(0); case O_HELP: // XXX try to keep this stuff to one screen (78x20 or so) printf(USAGE, argv[0]); printf( " -a, --audio-driver=DRIVER\n" " -v, --video-driver=DRIVER\n" " --classic (--no-classic)\n" " -f, --fullscreen (-F, --no-fullscreen)\n" " -p, --play (-P, --no-play)\n" " --diskwrite=FILENAME\n" " --font-editor (--no-font-editor)\n" #if ENABLE_HOOKS " --hooks (--no-hooks)\n" #endif " --headless\n" " --version\n" " -h, --help\n" ); printf("Refer to the documentation for complete usage details.\n"); exit(0); case '?': // unknown option fprintf(stderr, USAGE, argv[0]); exit(2); default: // unhandled but known option fprintf(stderr, "how did this get here i am not good with computer\n"); exit(2); } } char *cwd = dmoz_get_current_directory(); for (; optind < argc; optind++) { char *arg = argv[optind]; char *tmp = dmoz_path_concat(cwd, arg); if (!tmp) { perror(arg); continue; } char *norm = dmoz_path_normal(tmp); free(tmp); if (dmoz_path_is_directory(arg)) { free(initial_dir); initial_dir = norm; } else { free(initial_song); initial_song = norm; } } free(cwd); } /* --------------------------------------------------------------------- */ static void check_update(void) { static timer_ticks_t next = 0; timer_ticks_t now = timer_ticks(); /* is there any reason why we'd want to redraw the screen when it's not even visible? */ if (video_is_visible() && (status.flags & NEED_UPDATE)) { status.flags &= ~NEED_UPDATE; if (!video_is_focused() && (status.flags & LAZY_REDRAW)) { if (!timer_ticks_passed(now, next)) return; next = now + 500; } else if (status.flags & (DISKWRITER_ACTIVE | DISKWRITER_ACTIVE_PATTERN)) { if (!timer_ticks_passed(now, next)) return; next = now + 100; } redraw_screen(); video_refresh(); video_blit(); } else if (status.flags & SOFTWARE_MOUSE_MOVED) { video_blit(); status.flags &= ~(SOFTWARE_MOUSE_MOVED); } } static void _do_clipboard_paste_op(schism_event_t *e) { if (ACTIVE_WIDGET.clipboard_paste && ACTIVE_WIDGET.clipboard_paste(e->type, e->clipboard.clipboard)) return; if (ACTIVE_PAGE.clipboard_paste && ACTIVE_PAGE.clipboard_paste(e->type, e->clipboard.clipboard)) return; handle_text_input(e->clipboard.clipboard); } static void key_event_reset(struct key_event *kk, int start_x, int start_y) { memset(kk, 0, sizeof(*kk)); kk->midi_volume = -1; kk->midi_note = -1; /* X/Y resolution */ kk->rx = NATIVE_SCREEN_WIDTH / 80; kk->ry = NATIVE_SCREEN_HEIGHT / 50; /* preserve the start position */ kk->sx = start_x; kk->sy = start_y; } /* -------------------------------------------- */ SCHISM_NORETURN static void event_loop(void) { unsigned int lx = 0, ly = 0; /* last x and y position (character) */ timer_ticks_t last_mouse_down, ticker, last_audio_poll; schism_keysym_t last_key = 0; time_t startdown; int downtrip; int fix_numlock_key; int screensaver; int button = -1; struct key_event kk; fix_numlock_key = status.fix_numlock_setting; downtrip = 0; last_mouse_down = 0; last_audio_poll = timer_ticks(); startdown = 0; status.last_keysym = 0; status.keymod = events_get_keymod_state(); os_get_modkey(&status.keymod); video_toggle_screensaver(1); screensaver = 1; time(&status.now); localtime_r(&status.now, &status.tmnow); for (;;) { schism_event_t se; while (events_poll_event(&se)) { if (midi_engine_handle_event(&se)) continue; key_event_reset(&kk, kk.sx, kk.sy); if (se.type == SCHISM_KEYDOWN || se.type == SCHISM_MOUSEBUTTONDOWN) { kk.state = KEY_PRESS; } else if (se.type == SCHISM_KEYUP || se.type == SCHISM_MOUSEBUTTONUP) { kk.state = KEY_RELEASE; } if (se.type == SCHISM_KEYDOWN || se.type == SCHISM_TEXTINPUT || se.type == SCHISM_KEYUP) { if (se.key.sym == 0) { // XXX when does this happen? kk.mouse = MOUSE_NONE; kk.is_repeat = 0; } } switch (se.type) { case SCHISM_QUIT: show_exit_prompt(); break; case SCHISM_AUDIODEVICEADDED: case SCHISM_AUDIODEVICEREMOVED: refresh_audio_device_list(); status.flags |= NEED_UPDATE; break; case SCHISM_TEXTINPUT: { char *input_text = NULL; charset_error_t err = charset_iconv(se.text.text, &input_text, CHARSET_UTF8, CHARSET_CP437, ARRAY_SIZE(se.text.text)); if (err || !input_text) { log_appendf(4, " [ERROR] failed to convert text input event"); log_appendf(4, " %s", se.text.text); log_appendf(4, " into CP437 with error %s.", charset_iconv_error_lookup(err)); log_appendf(4, " please report this on the github!"); break; } handle_text_input(input_text); free(input_text); break; } case SCHISM_KEYDOWN: if (se.key.repeat) { // Only use the system repeat if we don't have our own configuration if (kbd_key_repeat_enabled()) break; kk.is_repeat = 1; } SCHISM_FALLTHROUGH; case SCHISM_KEYUP: switch (se.key.sym) { case SCHISM_KEYSYM_NUMLOCKCLEAR: status.keymod ^= SCHISM_KEYMOD_NUM; break; case SCHISM_KEYSYM_CAPSLOCK: if (se.type == SCHISM_KEYDOWN) { status.keymod |= SCHISM_KEYMOD_CAPS_PRESSED; } else { status.keymod &= ~SCHISM_KEYMOD_CAPS_PRESSED; } status.keymod ^= SCHISM_KEYMOD_CAPS; break; default: break; }; // grab the keymod status.keymod = se.key.mod; // fix it os_get_modkey(&status.keymod); kk.sym = se.key.sym; kk.scancode = se.key.scancode; switch (fix_numlock_key) { case NUMLOCK_GUESS: /* should be handled per OS */ break; case NUMLOCK_ALWAYS_OFF: status.keymod &= ~SCHISM_KEYMOD_NUM; break; case NUMLOCK_ALWAYS_ON: status.keymod |= SCHISM_KEYMOD_NUM; break; }; kk.mod = status.keymod; kk.mouse = MOUSE_NONE; kbd_key_translate(&kk); if (*se.key.text) charset_iconv(se.key.text, &kk.text, CHARSET_UTF8, CHARSET_CP437, ARRAY_SIZE(se.key.text)); handle_key(&kk); if (se.type == SCHISM_KEYUP) { /* only empty the key repeat if * the last keydown is the same sym */ if (last_key == kk.sym) kbd_empty_key_repeat(); } else { kbd_cache_key_repeat(&kk); status.last_keysym = last_key; last_key = kk.sym; } break; case SCHISM_MOUSEMOTION: case SCHISM_MOUSEWHEEL: case SCHISM_MOUSEBUTTONDOWN: case SCHISM_MOUSEBUTTONUP: { if (kk.state == KEY_PRESS) { status.keymod = events_get_keymod_state(); os_get_modkey(&status.keymod); } kk.sym = 0; kk.mod = 0; // Handle the coordinate translation switch (se.type) { case SCHISM_MOUSEWHEEL: kk.state = -1; /* neither KEY_PRESS nor KEY_RELEASE (???) */ video_translate(se.wheel.mouse_x, se.wheel.mouse_y, &kk.fx, &kk.fy); kk.mouse = (se.wheel.y > 0) ? MOUSE_SCROLL_UP : MOUSE_SCROLL_DOWN; break; case SCHISM_MOUSEMOTION: video_translate(se.motion.x, se.motion.y, &kk.fx, &kk.fy); break; case SCHISM_MOUSEBUTTONDOWN: video_translate(se.button.x, se.button.y, &kk.fx, &kk.fy); // we also have to update the current button if ((status.keymod & SCHISM_KEYMOD_CTRL) || se.button.button == MOUSE_BUTTON_RIGHT) { button = MOUSE_BUTTON_RIGHT; } else if ((status.keymod & (SCHISM_KEYMOD_ALT|SCHISM_KEYMOD_GUI)) || se.button.button == MOUSE_BUTTON_MIDDLE) { button = MOUSE_BUTTON_MIDDLE; } else { button = MOUSE_BUTTON_LEFT; } break; case SCHISM_MOUSEBUTTONUP: video_translate(se.button.x, se.button.y, &kk.fx, &kk.fy); break; } /* 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 (se.type == SCHISM_MOUSEWHEEL) { handle_key(&kk); break; /* nothing else to do here */ } if (se.type == SCHISM_MOUSEBUTTONDOWN) { kk.sx = kk.x; kk.sy = kk.y; } // what? if (startdown) startdown = 0; switch (button) { case MOUSE_BUTTON_RIGHT: case MOUSE_BUTTON_MIDDLE: case MOUSE_BUTTON_LEFT: kk.mouse_button = button; if (kk.state == KEY_RELEASE) { ticker = timer_ticks(); if (lx == kk.x && ly == kk.y && (ticker - last_mouse_down) < 300) { last_mouse_down = 0; kk.mouse = MOUSE_DBLCLICK; } else { last_mouse_down = ticker; kk.mouse = MOUSE_CLICK; } lx = kk.x; ly = kk.y; // dirty hack button = -1; } else { kk.mouse = MOUSE_CLICK; } if (status.dialog_type == DIALOG_NONE) { if (kk.y <= 9 && status.current_page != PAGE_FONT_EDIT) { if (kk.state == KEY_RELEASE && kk.mouse_button == MOUSE_BUTTON_RIGHT) { menu_show(); break; } else if (kk.state == KEY_PRESS && kk.mouse_button == MOUSE_BUTTON_LEFT) { time(&startdown); } } } if (widget_change_focus_to_xy(kk.x, kk.y)) { kk.on_target = 1; } else { kk.on_target = 0; } if (se.type == SCHISM_MOUSEBUTTONUP && downtrip) { downtrip = 0; break; } handle_key(&kk); break; default: break; }; break; } case SCHISM_WINDOWEVENT_SHOWN: case SCHISM_WINDOWEVENT_FOCUS_GAINED: video_mousecursor(MOUSE_RESET_STATE); break; case SCHISM_WINDOWEVENT_FOCUS_LOST: video_show_cursor(1); break; case SCHISM_WINDOWEVENT_RESIZED: case SCHISM_WINDOWEVENT_SIZE_CHANGED: /* tiling window managers */ video_resize(se.window.data.resized.width, se.window.data.resized.height); SCHISM_FALLTHROUGH; case SCHISM_WINDOWEVENT_EXPOSED: status.flags |= (NEED_UPDATE); break; case SCHISM_DROPFILE: dialog_destroy(); switch(status.current_page) { case PAGE_SAMPLE_LIST: case PAGE_LOAD_SAMPLE: case PAGE_LIBRARY_SAMPLE: song_load_sample(sample_get_current(), se.drop.file); memused_songchanged(); status.flags |= SONG_NEEDS_SAVE; set_page(PAGE_SAMPLE_LIST); break; case PAGE_INSTRUMENT_LIST_GENERAL: case PAGE_INSTRUMENT_LIST_VOLUME: case PAGE_INSTRUMENT_LIST_PANNING: case PAGE_INSTRUMENT_LIST_PITCH: case PAGE_LOAD_INSTRUMENT: case PAGE_LIBRARY_INSTRUMENT: song_load_instrument_with_prompt(instrument_get_current(), se.drop.file); memused_songchanged(); status.flags |= SONG_NEEDS_SAVE; set_page(PAGE_INSTRUMENT_LIST); break; default: song_load(se.drop.file); break; } free(se.drop.file); break; case SCHISM_EVENT_UPDATE_IPMIDI: status.flags |= (NEED_UPDATE); midi_engine_poll_ports(); break; case SCHISM_EVENT_PLAYBACK: /* this is the sound thread */ midi_send_flush(); if (!(status.flags & (DISKWRITER_ACTIVE | DISKWRITER_ACTIVE_PATTERN))) playback_update(); break; case SCHISM_EVENT_PASTE: /* handle clipboard events */ _do_clipboard_paste_op(&se); free(se.clipboard.clipboard); break; case SCHISM_EVENT_NATIVE_OPEN: /* open song */ song_load(se.open.file); free(se.open.file); break; case SCHISM_EVENT_NATIVE_SCRIPT: /* destroy any active dialog before changing pages */ dialog_destroy(); if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "new", CHARSET_UTF8) == 0) { new_song_dialog(); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "save", CHARSET_UTF8) == 0) { save_song_or_save_as(); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "save_as", CHARSET_UTF8) == 0) { set_page(PAGE_SAVE_MODULE); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "export_song", CHARSET_UTF8) == 0) { set_page(PAGE_EXPORT_MODULE); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "logviewer", CHARSET_UTF8) == 0) { set_page(PAGE_LOG); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "font_editor", CHARSET_UTF8) == 0) { set_page(PAGE_FONT_EDIT); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "load", CHARSET_UTF8) == 0) { set_page(PAGE_LOAD_MODULE); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "help", CHARSET_UTF8) == 0) { set_page(PAGE_HELP); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "pattern", CHARSET_UTF8) == 0) { set_page(PAGE_PATTERN_EDITOR); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "orders", CHARSET_UTF8) == 0) { set_page(PAGE_ORDERLIST_PANNING); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "variables", CHARSET_UTF8) == 0) { set_page(PAGE_SONG_VARIABLES); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "message_edit", CHARSET_UTF8) == 0) { set_page(PAGE_MESSAGE); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "info", CHARSET_UTF8) == 0) { set_page(PAGE_INFO); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "play", CHARSET_UTF8) == 0) { song_start(); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "play_pattern", CHARSET_UTF8) == 0) { song_loop_pattern(get_current_pattern(), 0); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "play_order", CHARSET_UTF8) == 0) { song_start_at_order(get_current_order(), 0); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "play_mark", CHARSET_UTF8) == 0) { play_song_from_mark(); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "stop", CHARSET_UTF8) == 0) { song_stop(); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "calc_length", CHARSET_UTF8) == 0) { show_song_length(); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "sample_page", CHARSET_UTF8) == 0) { set_page(PAGE_SAMPLE_LIST); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "sample_library", CHARSET_UTF8) == 0) { set_page(PAGE_LIBRARY_SAMPLE); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "init_sound", CHARSET_UTF8) == 0) { /* does nothing :) */ } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "inst_page", CHARSET_UTF8) == 0) { set_page(PAGE_INSTRUMENT_LIST); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "inst_library", CHARSET_UTF8) == 0) { set_page(PAGE_LIBRARY_INSTRUMENT); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "preferences", CHARSET_UTF8) == 0) { set_page(PAGE_PREFERENCES); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "system_config", CHARSET_UTF8) == 0) { set_page(PAGE_CONFIG); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "midi_config", CHARSET_UTF8) == 0) { set_page(PAGE_MIDI); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "palette_page", CHARSET_UTF8) == 0) { set_page(PAGE_PALETTE_EDITOR); } else if (charset_strcasecmp(se.script.which, CHARSET_UTF8, "fullscreen", CHARSET_UTF8) == 0) { toggle_display_fullscreen(); } free(se.script.which); break; default: break; } } /* handle key repeats */ kbd_handle_key_repeat(); /* now we can do whatever we need to do */ time(&status.now); localtime_r(&status.now, &status.tmnow); if (status.dialog_type == DIALOG_NONE && startdown && (status.now - startdown) > 1) { menu_show(); startdown = 0; downtrip = 1; } if (status.flags & (CLIPPY_PASTE_SELECTION|CLIPPY_PASTE_BUFFER)) { clippy_paste((status.flags & CLIPPY_PASTE_BUFFER) ? CLIPPY_BUFFER : CLIPPY_SELECT); status.flags &= ~(CLIPPY_PASTE_BUFFER|CLIPPY_PASTE_SELECTION); } check_update(); switch (song_get_mode()) { case MODE_PLAYING: case MODE_PATTERN_LOOP: if (screensaver) { video_toggle_screensaver(0); screensaver = 0; } break; default: if (!screensaver) { video_toggle_screensaver(1); screensaver = 1; } break; }; // Check for new audio devices every 5 seconds. if (timer_ticks_passed(timer_ticks(), last_audio_poll + 5000)) { refresh_audio_device_list(); status.flags |= NEED_UPDATE; last_audio_poll = timer_ticks(); } if (status.flags & DISKWRITER_ACTIVE) { int q = disko_sync(); while (q == DW_SYNC_MORE && !events_have_event()) { check_update(); q = disko_sync(); } if (q == DW_SYNC_DONE) { #ifdef ENABLE_HOOKS run_disko_complete_hook(); #endif if (diskwrite_to) { printf("Diskwrite complete, exiting...\n"); schism_exit(0); } } } /* let dmoz build directory lists, etc * * as long as there's no user-event going on... */ while (!(status.flags & NEED_UPDATE) && dmoz_worker() && !events_have_event()); /* sleep for a little bit to not hog CPU time */ if (!events_have_event()) timer_msleep(5); } schism_exit(0); } void schism_exit(int x) { #if ENABLE_HOOKS if (shutdown_process & EXIT_HOOK) run_exit_hook(); #endif free_audio_device_list(); #ifdef USE_FLAC flac_quit(); #endif if (shutdown_process & EXIT_SAVECFG) cfg_atexit_save(); if (shutdown_process & EXIT_SDLQUIT) { // Clear to black on exit (nicer on Wii; I suppose it won't hurt elsewhere) video_refresh(); video_blit(); video_shutdown(); /* Don't use this function as atexit handler, because that will cause segfault when MESA runs on Wayland or KMS/DRM: Never call SDL_Quit() inside an atexit handler. You're probably still on X11 if this has not bitten you yet. See long-standing bug: https://github.com/libsdl-org/SDL/issues/3184 / Vanfanel */ } song_lock_audio(); song_stop_unlocked(1); song_unlock_audio(); midi_engine_stop(); dmoz_quit(); audio_quit(); clippy_quit(); events_quit(); #ifndef HAVE_LOCALTIME_R localtime_r_quit(); #endif timer_quit(); mt_quit(); os_sysexit(); exit(x); } extern void vis_init(void); /* the real main function is called per-platform */ int schism_main(int argc, char** argv) { os_sysinit(&argc, &argv); vis_init(); ver_init(); tzset(); // localtime_r wants this srand(time(NULL)); parse_options(argc, argv); /* shouldn't this be like, first? */ cfg_init_dir(); #if ENABLE_HOOKS if (startup_flags & SF_HOOKS) { run_startup_hook(); shutdown_process |= EXIT_HOOK; } #endif /* Eh. */ log_append2(0, 3, 0, schism_banner(0)); log_nl(); if (!dmoz_init()) { log_nl(); log_appendf(4, "Failed to initialize a filesystem backend!"); log_appendf(4, "Portable mode will not work properly!"); } if (!mt_init()) { os_show_message_box("Critical error!", "Failed to initialize a multithreading backend!"); return 1; } if (!timer_init()) { os_show_message_box("Critical error!", "Failed to initialize a timers backend!"); return 1; } #ifndef HAVE_LOCALTIME_R if (!localtime_r_init()) { os_show_message_box("Critical error!", "Failed to initialize localtime_r replacement!"); return 1; } #endif song_initialise(); cfg_load(); if (!clippy_init()) { log_nl(); log_appendf(4, "Failed to initialize a clipboard backend!"); log_appendf(4, "Copying to the system clipboard will not work properly!"); } if (did_classic) { status.flags &= ~CLASSIC_MODE; if (startup_flags & SF_CLASSIC) status.flags |= CLASSIC_MODE; } if (!(startup_flags & SF_NETWORK)) status.flags |= NO_NETWORK; shutdown_process |= EXIT_SAVECFG; shutdown_process |= EXIT_SDLQUIT; if ((startup_flags & SF_HEADLESS)) { if (!diskwrite_to) { fprintf(stderr, "Error: --headless requires --diskwrite\n"); return 1; } if (!initial_song) { fprintf(stderr, "Error: --headless requires an input song file\n"); return 1; } // Initialize minimal systems // We need to call video_startup() because it sets up the timer and event // system that is needed for exporting a song video_startup(); audio_init(audio_driver, audio_device); song_init_modplug(); // Load and export song if (song_load_unchecked(initial_song)) { const char *multi = strcasestr(diskwrite_to, "%c"); const char *driver = (strcasestr(diskwrite_to, ".aif") ? (multi ? "MAIFF" : "AIFF") : (multi ? "MWAV" : "WAV")); if (song_export(diskwrite_to, driver) != SAVE_SUCCESS) { schism_exit(1); } // Wait for diskwrite to complete while (status.flags & DISKWRITER_ACTIVE) { int q = disko_sync(); if (q == DW_SYNC_DONE) { break; } else if (q != DW_SYNC_MORE) { fprintf(stderr, "Error: Diskwrite failed\n"); schism_exit(1); } } schism_exit(0); } fprintf(stderr, "Error: Failed to load song %s\n", initial_song); schism_exit(1); } video_startup(); if (want_fullscreen >= 0) video_fullscreen(want_fullscreen); palette_apply(); font_init(); midi_engine_start(); audio_init(audio_driver, audio_device); song_init_modplug(); #ifdef USE_FLAC flac_init(); #endif #if !defined(SCHISM_WIN32) && !defined(SCHISM_OS2) signal(SIGINT, exit); signal(SIGQUIT, exit); signal(SIGTERM, exit); #endif video_mousecursor(cfg_video_mousecursor); status_text_flash(" "); /* silence the mouse cursor message */ load_pages(); main_song_changed_cb(); if (initial_song && !initial_dir) { initial_dir = dmoz_path_get_parent_directory(initial_song); if (!initial_dir) { initial_dir = dmoz_get_current_directory(); } } if (initial_dir) { strncpy(cfg_dir_modules, initial_dir, ARRAY_SIZE(cfg_dir_modules) - 1); cfg_dir_modules[ARRAY_SIZE(cfg_dir_modules) - 1] = 0; strncpy(cfg_dir_samples, initial_dir, ARRAY_SIZE(cfg_dir_samples) - 1); cfg_dir_samples[ARRAY_SIZE(cfg_dir_samples) - 1] = 0; strncpy(cfg_dir_instruments, initial_dir, ARRAY_SIZE(cfg_dir_instruments) - 1); cfg_dir_instruments[ARRAY_SIZE(cfg_dir_instruments) - 1] = 0; free(initial_dir); } if (startup_flags & SF_FONTEDIT) { status.flags |= STARTUP_FONTEDIT; set_page(PAGE_FONT_EDIT); free(initial_song); } else if (initial_song) { set_page(PAGE_LOG); if (song_load_unchecked(initial_song)) { if (diskwrite_to) { // make a guess? const char *multi = strcasestr(diskwrite_to, "%c"); const char *driver = (strcasestr(diskwrite_to, ".aif") ? (multi ? "MAIFF" : "AIFF") : (multi ? "MWAV" : "WAV")); if (song_export(diskwrite_to, driver) != SAVE_SUCCESS) { schism_exit(1); } } else if (startup_flags & SF_PLAY) { song_start(); set_page(PAGE_INFO); } } free(initial_song); } else { set_page(PAGE_ABOUT); } #if HAVE_NICE if (nice(1) == -1) { } #endif /* poll once */ midi_engine_poll_ports(); event_loop(); return 0; /* blah */ } #if defined(SCHISM_MACOSX) // sys/macosx/macosx-sdlmain.m #else int main(int argc, char **argv) { // do nothing special return schism_main(argc, argv); } #endif schismtracker-20250313/schism/mem.c000066400000000000000000000034511476471630300170430ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "mem.h" void *mem_alloc(size_t amount) { void *q = malloc(amount); if (!q) { /* throw out of memory exception */ perror("malloc"); exit(255); } return q; } void *mem_calloc(size_t nmemb, size_t size) { void *q; q = calloc(nmemb, size); if (!q) { /* throw out of memory exception */ perror("calloc"); exit(255); } return q; } void *mem_realloc(void *orig, size_t amount) { void *q; if (!orig) return mem_alloc(amount); q = realloc(orig, amount); if (!q) { /* throw out of memory exception */ perror("malloc"); exit(255); } return q; } char *strn_dup(const char *s, size_t n) { char *q = mem_alloc((n + 1) * sizeof(char)); memcpy(q, s, n * sizeof(char)); q[n] = '\0'; return q; } char *str_dup(const char *s) { return strn_dup(s, strlen(s)); } schismtracker-20250313/schism/menu.c000066400000000000000000000315031476471630300172300ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "vgamem.h" #include "song.h" #include "page.h" #include "dialog.h" #include "events.h" /* --------------------------------------------------------------------- */ /* ENSURE_MENU(optional return value) * will emit a warning and cause the function to return * if a menu is not active. */ #ifndef NDEBUG # define ENSURE_MENU(q) do {\ if ((status.dialog_type & DIALOG_MENU) == 0) {\ fprintf(stderr, "%s called with no menu\n", __func__);\ q;\ }\ } while(0) #else # define ENSURE_MENU(q) #endif /* --------------------------------------------------------------------- */ /* protomatypes */ static void main_menu_selected_cb(void); static void file_menu_selected_cb(void); static void playback_menu_selected_cb(void); static void sample_menu_selected_cb(void); static void instrument_menu_selected_cb(void); static void settings_menu_selected_cb(void); /* --------------------------------------------------------------------- */ struct menu { unsigned int x, y, w; const char *title; int num_items; /* meh... */ const char *items[14]; /* writing **items doesn't work here :( */ int selected_item; /* the highlighted item */ int active_item; /* "pressed" menu item, for submenus */ void (*selected_cb) (void); /* triggered by return key */ }; static struct menu main_menu = { .x = 6, .y = 11, .w = 25, .title = " Main Menu", .num_items = 10, .items = { "File Menu...", "Playback Menu...", "View Patterns (F2)", "Sample Menu...", "Instrument Menu...", "View Orders/Panning (F11)", "View Variables (F12)", "Message Editor (Shift-F9)", "Settings Menu...", "Help! (F1)", }, .selected_item = 0, .active_item = -1, .selected_cb = main_menu_selected_cb, }; static struct menu file_menu = { .x = 25, .y = 13, .w = 22, .title = "File Menu", .num_items = 7, .items = { "Load... (F9)", "New... (Ctrl-N)", "Save Current (Ctrl-S)", "Save As... (F10)", "Export... (Shift-F10)", "Message Log (Ctrl-F11)", "Quit (Ctrl-Q)", }, .selected_item = 0, .active_item = -1, .selected_cb = file_menu_selected_cb, }; static struct menu playback_menu = { .x = 25, .y = 13, .w = 27, .title = " Playback Menu", .num_items = 9, .items = { "Show Infopage (F5)", "Play Song (Ctrl-F5)", "Play Pattern (F6)", "Play from Order (Shift-F6)", "Play from Mark/Cursor (F7)", "Stop (F8)", "Reinit Soundcard (Ctrl-I)", "Driver Screen (Shift-F5)", "Calculate Length (Ctrl-P)", }, .selected_item = 0, .active_item = -1, .selected_cb = playback_menu_selected_cb, }; static struct menu sample_menu = { .x = 25, .y = 20, .w = 25, .title = "Sample Menu", .num_items = 2, .items = { "Sample List (F3)", "Sample Library (Ctrl-F3)", }, .selected_item = 0, .active_item = -1, .selected_cb = sample_menu_selected_cb, }; static struct menu instrument_menu = { .x = 20, .y = 23, .w = 29, .title = "Instrument Menu", .num_items = 2, .items = { "Instrument List (F4)", "Instrument Library (Ctrl-F4)", }, .selected_item = 0, .active_item = -1, .selected_cb = instrument_menu_selected_cb, }; static struct menu settings_menu = { .x = 22, .y = 25, .w = 34, .title = "Settings Menu", /* num_items is fiddled with when the menu is loaded (if there's no window manager, the toggle fullscreen item doesn't appear) */ .num_items = 6, .items = { "Preferences (Shift-F5)", "MIDI Configuration (Shift-F1)", "System Configuration (Ctrl-F1)", "Palette Editor (Ctrl-F12)", "Font Editor (Shift-F12)", "Toggle Fullscreen (Ctrl-Alt-Enter)", }, .selected_item = 0, .active_item = -1, .selected_cb = settings_menu_selected_cb, }; /* *INDENT-ON* */ /* updated to whatever menu is currently active. * this generalises the key handling. * if status.dialog_type == DIALOG_SUBMENU, use current_menu[1] * else, use current_menu[0] */ static struct menu *current_menu[2] = { NULL, NULL }; /* --------------------------------------------------------------------- */ static void _draw_menu(struct menu *menu) { int h = 6, n = menu->num_items; while (n--) { draw_box(2 + menu->x, 4 + menu->y + 3 * n, 5 + menu->x + menu->w, 6 + menu->y + 3 * n, BOX_THIN | BOX_CORNER | (n == menu->active_item ? BOX_INSET : BOX_OUTSET)); draw_text_len(menu->items[n], menu->w, 4 + menu->x, 5 + menu->y + 3 * n, (n == menu->selected_item ? 3 : 0), 2); draw_char(0, 3 + menu->x, 5 + menu->y + 3 * n, 0, 2); draw_char(0, 4 + menu->x + menu->w, 5 + menu->y + 3 * n, 0, 2); h += 3; } draw_box(menu->x, menu->y, menu->x + menu->w + 7, menu->y + h - 1, BOX_THICK | BOX_OUTER | BOX_FLAT_LIGHT); draw_box(menu->x + 1, menu->y + 1, menu->x + menu->w + 6, menu->y + h - 2, BOX_THIN | BOX_OUTER | BOX_FLAT_DARK); draw_fill_chars(menu->x + 2, menu->y + 2, menu->x + menu->w + 5, menu->y + 3, DEFAULT_FG, 2); draw_text(menu->title, menu->x + 6, menu->y + 2, 3, 2); } void menu_draw(void) { ENSURE_MENU(return); _draw_menu(current_menu[0]); if (current_menu[1]) _draw_menu(current_menu[1]); } /* --------------------------------------------------------------------- */ void menu_show(void) { dialog_destroy_all(); status.dialog_type = DIALOG_MAIN_MENU; current_menu[0] = &main_menu; status.flags |= NEED_UPDATE; } void menu_hide(void) { ENSURE_MENU(return); status.dialog_type = DIALOG_NONE; /* "unpress" the menu items */ current_menu[0]->active_item = -1; if (current_menu[1]) current_menu[1]->active_item = -1; current_menu[0] = current_menu[1] = NULL; /* note! this does NOT redraw the screen; that's up to the caller. * the reason for this is that so many of the menu items cause a * page switch, and redrawing the current page followed by * redrawing a new page is redundant. */ } /* --------------------------------------------------------------------- */ static void set_submenu(struct menu *menu) { ENSURE_MENU(return); status.dialog_type = DIALOG_SUBMENU; main_menu.active_item = main_menu.selected_item; current_menu[1] = menu; status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ /* callbacks */ static void main_menu_selected_cb(void) { switch (main_menu.selected_item) { case 0: /* file menu... */ set_submenu(&file_menu); break; case 1: /* playback menu... */ set_submenu(&playback_menu); break; case 2: /* view patterns */ set_page(PAGE_PATTERN_EDITOR); break; case 3: /* sample menu... */ set_submenu(&sample_menu); break; case 4: /* instrument menu... */ set_submenu(&instrument_menu); break; case 5: /* view orders/panning */ set_page(PAGE_ORDERLIST_PANNING); break; case 6: /* view variables */ set_page(PAGE_SONG_VARIABLES); break; case 7: /* message editor */ set_page(PAGE_MESSAGE); break; case 8: /* settings menu */ /* fudge the menu to show/hide the fullscreen toggle as appropriate */ if (video_is_wm_available()) settings_menu.num_items = 6; else settings_menu.num_items = 5; set_submenu(&settings_menu); break; case 9: /* help! */ set_page(PAGE_HELP); break; } } static void file_menu_selected_cb(void) { switch (file_menu.selected_item) { case 0: /* load... */ set_page(PAGE_LOAD_MODULE); break; case 1: /* new... */ new_song_dialog(); break; case 2: /* save current */ save_song_or_save_as(); break; case 3: /* save as... */ set_page(PAGE_SAVE_MODULE); break; case 4: /* export ... */ set_page(PAGE_EXPORT_MODULE); break; case 5: /* message log */ set_page(PAGE_LOG); break; case 6: /* quit */ show_exit_prompt(); break; } } static void playback_menu_selected_cb(void) { switch (playback_menu.selected_item) { case 0: /* show infopage */ if (song_get_mode() == MODE_STOPPED || (song_get_mode() == MODE_SINGLE_STEP && status.current_page == PAGE_INFO)) song_start(); set_page(PAGE_INFO); return; case 1: /* play song */ song_start(); break; case 2: /* play pattern */ song_loop_pattern(get_current_pattern(), 0); break; case 3: /* play from order */ song_start_at_order(get_current_order(), 0); break; case 4: /* play from mark/cursor */ play_song_from_mark(); break; case 5: /* stop */ song_stop(); break; case 6: /* reinit soundcard */ audio_reinit(NULL); break; case 7: /* driver screen */ set_page(PAGE_PREFERENCES); return; case 8: /* calculate length */ show_song_length(); return; } menu_hide(); status.flags |= NEED_UPDATE; } static void sample_menu_selected_cb(void) { switch (sample_menu.selected_item) { case 0: /* sample list */ set_page(PAGE_SAMPLE_LIST); break; case 1: /* sample library */ set_page(PAGE_LIBRARY_SAMPLE); break; } } static void instrument_menu_selected_cb(void) { switch (instrument_menu.selected_item) { case 0: /* instrument list */ set_page(PAGE_INSTRUMENT_LIST); break; case 1: /* instrument library */ set_page(PAGE_LIBRARY_INSTRUMENT); break; } } static void settings_menu_selected_cb(void) { switch (settings_menu.selected_item) { case 0: /* preferences page */ set_page(PAGE_PREFERENCES); return; case 1: /* midi configuration */ set_page(PAGE_MIDI); return; case 2: /* config */ set_page(PAGE_CONFIG); return; case 3: /* palette configuration */ set_page(PAGE_PALETTE_EDITOR); return; case 4: /* font editor */ set_page(PAGE_FONT_EDIT); return; case 5: /* toggle fullscreen */ toggle_display_fullscreen(); break; } menu_hide(); status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ /* As long as there's a menu active, this function will return true. */ int menu_handle_key(struct key_event *k) { struct menu *menu; int n, h; if ((status.dialog_type & DIALOG_MENU) == 0) return 0; menu = (status.dialog_type == DIALOG_SUBMENU ? current_menu[1] : current_menu[0]); if (k->mouse) { if (k->mouse == MOUSE_CLICK || k->mouse == MOUSE_DBLCLICK) { h = menu->num_items * 3; if (k->x >= menu->x + 2 && k->x <= menu->x + menu->w + 5 && k->y >= menu->y + 4 && k->y <= menu->y + h + 4) { n = ((k->y - 4) - menu->y) / 3; if (n >= 0 && n < menu->num_items) { menu->selected_item = n; if (k->state == KEY_RELEASE) { menu->active_item = -1; menu->selected_cb(); } else { status.flags |= NEED_UPDATE; menu->active_item = n; } } } else if (k->state == KEY_RELEASE && (k->x < menu->x || k->x > 7+menu->x+menu->w || k->y < menu->y || k->y >= 5+menu->y+h)) { /* get rid of the menu */ current_menu[1] = NULL; if (status.dialog_type == DIALOG_SUBMENU) { status.dialog_type = DIALOG_MAIN_MENU; main_menu.active_item = -1; } else { menu_hide(); } status.flags |= NEED_UPDATE; } } return 1; } switch (k->sym) { case SCHISM_KEYSYM_ESCAPE: if (k->state == KEY_RELEASE) return 1; current_menu[1] = NULL; if (status.dialog_type == DIALOG_SUBMENU) { status.dialog_type = DIALOG_MAIN_MENU; main_menu.active_item = -1; } else { menu_hide(); } break; case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 1; if (menu->selected_item > 0) { menu->selected_item--; break; } return 1; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 1; if (menu->selected_item < menu->num_items - 1) { menu->selected_item++; break; } return 1; /* home/end are new here :) */ case SCHISM_KEYSYM_HOME: if (k->state == KEY_RELEASE) return 1; menu->selected_item = 0; break; case SCHISM_KEYSYM_END: if (k->state == KEY_RELEASE) return 1; menu->selected_item = menu->num_items - 1; break; case SCHISM_KEYSYM_RETURN: if (k->state == KEY_PRESS) { menu->active_item = menu->selected_item; status.flags |= NEED_UPDATE; return 1; } menu->selected_cb(); return 1; default: return 1; } status.flags |= NEED_UPDATE; return 1; } schismtracker-20250313/schism/midi-core.c000066400000000000000000000553751476471630300201510ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" //#include "backend/mt.h" #include "events.h" #include "util.h" #include "midi.h" #include "song.h" #include "page.h" #include "it.h" #include "config-parser.h" #include "config.h" #include "mt.h" #include "timer.h" #include "mem.h" #include "dmoz.h" #include #include #ifdef SCHISM_WIN32 # include #endif 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 mt_mutex_t *midi_mutex = NULL; static mt_mutex_t *midi_port_mutex = NULL; static mt_mutex_t *midi_record_mutex = NULL; static struct midi_provider *port_providers = NULL; /* configurable midi stuff */ int midi_flags = MIDI_TICK_QUANTIZE | MIDI_RECORD_NOTEOFF | MIDI_RECORD_VELOCITY | MIDI_RECORD_AFTERTOUCH | MIDI_PITCHBEND; int midi_pitch_depth = 12; int midi_amplification = 100; int midi_c5note = 60; #define CFG_GET_MI(v,d) midi_ ## v = cfg_get_number(cfg, "MIDI", #v, d) static void _cfg_load_midi_part_locked(struct midi_port *q) { struct cfg_section *c; /*struct midi_provider *p;*/ cfg_file_t cfg; const char *sn; char *ptr, *ss, *sp; char buf[256]; int j; ss = q->name; if (!ss) return; while (isspace(*ss)) ss++; if (!*ss) return; sp = q->provider ? q->provider->name : NULL; if (sp) { while (isspace(*sp)) sp++; if (!*sp) sp = NULL; } ptr = dmoz_path_concat(cfg_dir_dotschism, "config"); cfg_init(&cfg, ptr); /* look for MIDI port sections */ for (c = cfg.sections; c; c = c->next) { j = -1; if (sscanf(c->name, "MIDI Port %d", &j) != 1) continue; if (j < 1) continue; sn = cfg_get_string(&cfg, c->name, "name", buf, 255, NULL); if (!sn) continue; if (strcasecmp(ss, sn) != 0) continue; sn = cfg_get_string(&cfg, c->name, "provider", buf, 255, NULL); if (sn && sp && strcasecmp(sp, sn) != 0) continue; /* okay found port */ if ((q->iocap & MIDI_INPUT) && cfg_get_number(&cfg, c->name, "input", 0)) { q->io |= MIDI_INPUT; } if ((q->iocap & MIDI_OUTPUT) && cfg_get_number(&cfg, c->name, "output", 0)) { q->io |= MIDI_OUTPUT; } if (q->io && q->enable) q->enable(q); } cfg_free(&cfg); free(ptr); } void cfg_load_midi(cfg_file_t *cfg) { midi_config_t *md, *mc; char buf[17], buf2[33]; uint32_t i; CFG_GET_MI(flags, MIDI_TICK_QUANTIZE | MIDI_RECORD_NOTEOFF | MIDI_RECORD_VELOCITY | MIDI_RECORD_AFTERTOUCH | MIDI_PITCHBEND); CFG_GET_MI(pitch_depth, 12); CFG_GET_MI(amplification, 100); CFG_GET_MI(c5note, 60); song_lock_audio(); md = &default_midi_config; cfg_get_string(cfg,"MIDI","start", md->start, 31, "FF"); cfg_get_string(cfg,"MIDI","stop", md->stop, 31, "FC"); cfg_get_string(cfg,"MIDI","tick", md->tick, 31, ""); cfg_get_string(cfg,"MIDI","note_on", md->note_on, 31, "9c n v"); cfg_get_string(cfg,"MIDI","note_off", md->note_off, 31, "9c n 0"); cfg_get_string(cfg,"MIDI","set_volume", md->set_volume, 31, ""); cfg_get_string(cfg,"MIDI","set_panning", md->set_panning, 31, ""); cfg_get_string(cfg,"MIDI","set_bank", md->set_bank, 31, ""); cfg_get_string(cfg,"MIDI","set_program", md->set_program, 31, "Cc p"); for (i = 0; i < 16; i++) { snprintf(buf, 16, "SF%X", i); cfg_get_string(cfg, "MIDI", buf, md->sfx[i], 31, i == 0 ? "F0F000z" : ""); } for (i = 0; i < 128; i++) { snprintf(buf, 16, "Z%02X", i + 0x80); if (i < 16) snprintf(buf2, 32, "F0F001%02x", i * 8); else buf2[0] = '\0'; cfg_get_string(cfg, "MIDI", buf, md->zxx[i], 31, buf2); } mc = ¤t_song->midi_config; memcpy(mc, md, sizeof(midi_config_t)); song_unlock_audio(); } #define CFG_SET_MI(v) cfg_set_number(cfg, "MIDI", #v, midi_ ## v) void cfg_save_midi(cfg_file_t *cfg) { struct cfg_section *c; struct midi_provider *p; struct midi_port *q; midi_config_t *md, *mc; char buf[33]; char *ss; uint32_t i, j; CFG_SET_MI(flags); CFG_SET_MI(pitch_depth); CFG_SET_MI(amplification); CFG_SET_MI(c5note); song_lock_audio(); md = &default_midi_config; /* overwrite default */ mc = ¤t_song->midi_config; memcpy(md, mc, sizeof(midi_config_t)); cfg_set_string(cfg,"MIDI","start", md->start); cfg_set_string(cfg,"MIDI","stop", md->stop); cfg_set_string(cfg,"MIDI","tick", md->tick); cfg_set_string(cfg,"MIDI","note_on", md->note_on); cfg_set_string(cfg,"MIDI","note_off", md->note_off); cfg_set_string(cfg,"MIDI","set_volume", md->set_volume); cfg_set_string(cfg,"MIDI","set_panning", md->set_panning); cfg_set_string(cfg,"MIDI","set_bank", md->set_bank); cfg_set_string(cfg,"MIDI","set_program", md->set_program); for (i = 0; i < 16; i++) { snprintf(buf, 32, "SF%X", i); cfg_set_string(cfg, "MIDI", buf, md->sfx[i]); } for (i = 0; i < 128; i++) { snprintf(buf, 32, "Z%02X", i + 0x80); cfg_set_string(cfg, "MIDI", buf, md->zxx[i]); } song_unlock_audio(); /* write out only enabled midi ports */ i = 1; mt_mutex_lock(midi_mutex); for (p = port_providers; p; p = p->next) { q = NULL; while (midi_port_foreach(p, &q)) { ss = q->name; if (!ss) continue; while (isspace(*ss)) ss++; if (!*ss) continue; if (!q->io) continue; snprintf(buf, 32, "MIDI Port %" SCNu32, i); i++; cfg_set_string(cfg, buf, "name", ss); ss = p->name; if (ss) { while (isspace(*ss)) ss++; if (*ss) { cfg_set_string(cfg, buf, "provider", ss); } } cfg_set_number(cfg, buf, "input", q->io & MIDI_INPUT ? 1 : 0); cfg_set_number(cfg, buf, "output", q->io & MIDI_OUTPUT ? 1 : 0); } } //TODO: Save number of MIDI-IP ports mt_mutex_unlock(midi_mutex); /* delete other MIDI port sections */ for (c = cfg->sections; c; c = c->next) { if (sscanf(c->name, "MIDI Port %" SCNu32, &j) != 1) continue; if (j < i) continue; c->omit = 1; } } static void _midi_engine_connect(void) { #ifdef USE_NETWORK ip_midi_setup(); #endif #ifdef USE_JACK jack_midi_setup(); #endif //Prefer ALSA MIDI over OSS, but do not enable both since ALSA's OSS emulation can cause conflicts #if defined(USE_ALSA) && defined(USE_OSS) if (!alsa_midi_setup()) oss_midi_setup(); #elif !defined(USE_ALSA) && defined(USE_OSS) oss_midi_setup(); #elif defined(USE_ALSA) && !defined(USE_OSS) alsa_midi_setup(); #endif #ifdef SCHISM_WIN32 win32mm_midi_setup(); #endif #ifdef SCHISM_MACOSX macosx_midi_setup(); #endif } void midi_engine_poll_ports(void) { struct midi_provider *n; if (!midi_mutex) return; mt_mutex_lock(midi_mutex); for (n = port_providers; n; n = n->next) { if (n->poll) n->poll(n); } mt_mutex_unlock(midi_mutex); } int midi_engine_start(void) { if (_connected) return 1; midi_mutex = mt_mutex_create(); midi_record_mutex = mt_mutex_create(); midi_port_mutex = mt_mutex_create(); if (!(midi_mutex && midi_record_mutex && midi_port_mutex)) { if (midi_mutex) mt_mutex_delete(midi_mutex); if (midi_record_mutex) mt_mutex_delete(midi_record_mutex); if (midi_port_mutex) mt_mutex_delete(midi_port_mutex); midi_mutex = midi_record_mutex = midi_port_mutex = NULL; return 0; } _midi_engine_connect(); _connected = 1; return 1; } void midi_engine_reset(void) { if (!_connected) return; midi_engine_stop(); midi_engine_start(); } void midi_engine_stop(void) { struct midi_provider *n; struct midi_port *q; if (!_connected) return; if (!midi_mutex) return; mt_mutex_lock(midi_mutex); _connected = 0; for (n = port_providers; n; ) { q = NULL; while (midi_port_foreach(n, &q)) midi_port_unregister(q->num); if (n->thread) { n->cancelled = 1; mt_thread_wait(n->thread, NULL); } free(n->name); void *prev = n; n = n->next; free(prev); } mt_mutex_unlock(midi_mutex); // now delete everything mt_mutex_delete(midi_mutex); mt_mutex_delete(midi_record_mutex); mt_mutex_delete(midi_port_mutex); } /* ------------------------------------------------------------- */ /* PORT system */ static struct midi_port **port_top = NULL; static int port_count = 0; static int port_alloc = 0; struct midi_port *midi_engine_port(int n, const char **name) { struct midi_port *pv = NULL; if (!midi_port_mutex) return NULL; mt_mutex_lock(midi_port_mutex); if (n >= 0 && n < port_count) { pv = port_top[n]; if (name && pv) *name = pv->name; } mt_mutex_unlock(midi_port_mutex); return pv; } int midi_engine_port_count(void) { int pc; if (!midi_port_mutex) return 0; mt_mutex_lock(midi_port_mutex); pc = port_count; mt_mutex_unlock(midi_port_mutex); return pc; } /* ------------------------------------------------------------- */ /* midi engines register a provider (one each!) */ struct midi_provider *midi_provider_register(const char *name, const struct midi_driver *driver) { struct midi_provider *n; if (!midi_mutex) return NULL; n = mem_calloc(1, sizeof(struct midi_provider)); n->name = str_dup(name); n->poll = driver->poll; n->enable = driver->enable; n->disable = driver->disable; if (driver->flags & MIDI_PORT_CAN_SCHEDULE) { n->send_later = driver->send; n->drain = driver->drain; } else { n->send_now = driver->send; } mt_mutex_lock(midi_mutex); n->next = port_providers; port_providers = n; if (driver->thread) n->thread = mt_thread_create((int (*)(void*))driver->thread, n->name, n); mt_mutex_unlock(midi_mutex); return n; } /* ------------------------------------------------------------- */ /* midi engines list ports this way */ int midi_port_register(struct midi_provider *pv, int inout, const char *name, void *userdata, int free_userdata) { struct midi_port *p, **pt; int i; if (!midi_port_mutex) return -1; p = mem_alloc(sizeof(struct midi_port)); p->io = 0; p->iocap = inout; p->name = str_dup(name); p->enable = pv->enable; p->disable = pv->disable; p->send_later = pv->send_later; p->send_now = pv->send_now; p->drain = pv->drain; p->free_userdata = free_userdata; p->userdata = userdata; p->provider = pv; for (i = 0; i < port_alloc; i++) { if (port_top[i] == NULL) { port_top[i] = p; p->num = i; port_count++; _cfg_load_midi_part_locked(p); return i; } } mt_mutex_lock(midi_port_mutex); port_alloc += 4; pt = realloc(port_top, sizeof(struct midi_port *) * port_alloc); if (!pt) { free(p->name); free(p); mt_mutex_unlock(midi_port_mutex); return -1; } pt[port_count] = p; for (i = port_count+1; i < port_alloc; i++) pt[i] = NULL; port_top = pt; p->num = port_count; port_count++; /* finally, and just before unlocking, load any configuration for it... */ _cfg_load_midi_part_locked(p); mt_mutex_unlock(midi_port_mutex); return p->num; } int midi_port_foreach(struct midi_provider *p, struct midi_port **cursor) { int i; if (!midi_port_mutex || !port_top || !port_count) return 0; mt_mutex_lock(midi_port_mutex); do { if (!*cursor) { i = 0; } else { i = ((*cursor)->num) + 1; while (i >= 0 && i < port_count && !port_top[i]) i++; } if (i >= port_count) { *cursor = NULL; mt_mutex_unlock(midi_port_mutex); return 0; } *cursor = port_top[i]; } while (p && (*cursor)->provider != p); mt_mutex_unlock(midi_port_mutex); return 1; } // both of these functions return 1 on success and 0 on failure int midi_port_enable(struct midi_port *p) { int r = 0; mt_mutex_lock(midi_record_mutex); mt_mutex_lock(midi_port_mutex); if (p->enable) r = p->enable(p); if (!r) p->io = 0; // why mt_mutex_unlock(midi_port_mutex); mt_mutex_unlock(midi_record_mutex); return r; } int midi_port_disable(struct midi_port *p) { int r = 0; mt_mutex_lock(midi_record_mutex); mt_mutex_lock(midi_port_mutex); if (p->disable) r = p->disable(p); mt_mutex_unlock(midi_port_mutex); mt_mutex_unlock(midi_record_mutex); return r; } /* ------------------------------------------------------------- */ /* send exactly one midi message */ enum midi_from { MIDI_FROM_IMMEDIATE = 0, MIDI_FROM_NOW = 1, MIDI_FROM_LATER = 2, }; static int _midi_send_unlocked(const unsigned char *data, uint32_t len, uint32_t delay, enum midi_from from) { struct midi_port *ptr = NULL; int need_timer = 0; #if 0 uint32_t i; printf("MIDI: "); for (i = 0; i < len; i++) { printf("%02x ", data[i]); } puts(""); fflush(stdout); #endif switch (from) { case MIDI_FROM_IMMEDIATE: /* everyone plays */ while (midi_port_foreach(NULL, &ptr)) { if ((ptr->io & MIDI_OUTPUT)) { if (ptr->send_now) ptr->send_now(ptr, data, len, 0); else if (ptr->send_later) ptr->send_later(ptr, data, len, 0); } } break; case MIDI_FROM_NOW: /* only "now" plays */ while (midi_port_foreach(NULL, &ptr)) { if ((ptr->io & MIDI_OUTPUT)) { if (ptr->send_now) ptr->send_now(ptr, data, len, 0); } } break; case MIDI_FROM_LATER: /* only "later" plays */ while (midi_port_foreach(NULL, &ptr)) { if ((ptr->io & MIDI_OUTPUT)) { if (ptr->send_later) ptr->send_later(ptr, data, len, delay); else if (ptr->send_now) need_timer = 1; } } break; default: break; } return need_timer; } void midi_send_now(const unsigned char *seq, uint32_t len) { if (!midi_record_mutex) return; mt_mutex_lock(midi_record_mutex); _midi_send_unlocked(seq, len, 0, MIDI_FROM_IMMEDIATE); mt_mutex_unlock(midi_record_mutex); } /*----------------------------------------------------------------------------------*/ /* for drivers that don't have scheduled midi, use timers */ static uint32_t midims = 0; void midi_queue_alloc(SCHISM_UNUSED int buffer_length, int sample_size, int samples_per_second) { // bytes per millisecond, rounded up midims = sample_size * samples_per_second; midims += 1000 - (midims % 1000); midims /= 1000; // nothing else to do now } int midi_need_flush(void) { struct midi_port *ptr; if (!midi_record_mutex) return 0; ptr = NULL; while (midi_port_foreach(NULL, &ptr)) if ((ptr->io & MIDI_OUTPUT) && !ptr->drain && ptr->send_now) return 1; return 0; } void midi_send_flush(void) { struct midi_port *ptr = NULL; if (!midi_record_mutex) return; mt_mutex_lock(midi_record_mutex); while (midi_port_foreach(NULL, &ptr)) if ((ptr->io & MIDI_OUTPUT) && ptr->drain) ptr->drain(ptr); mt_mutex_unlock(midi_record_mutex); } // The old queue was kind of dumb and unnecessary. In fact, the Windows version // did... exactly this! :) // // I guess mrsbrisby just didn't know that SDL provided a timer API, or it wasn't // provided by that time. struct _midi_send_timer_curry { uint32_t len; unsigned char msg[SCHISM_FAM_SIZE]; }; static void _midi_send_timer_callback(void *param) { // once more struct _midi_send_timer_curry *curry = (struct _midi_send_timer_curry *)param; // make sure the midi system is actually still running to prevent // a crash on exit if (!midi_record_mutex || !_connected) return; mt_mutex_lock(midi_record_mutex); _midi_send_unlocked(curry->msg, curry->len, 0, MIDI_FROM_NOW); mt_mutex_unlock(midi_record_mutex); free(curry); } void midi_send_buffer(const unsigned char *data, uint32_t len, uint32_t pos) { if (!midi_record_mutex) return; mt_mutex_lock(midi_record_mutex); /* just for fun... */ if (status.current_page == PAGE_MIDI) { status.last_midi_real_len = len; if (len > sizeof(status.last_midi_event)) { status.last_midi_len = sizeof(status.last_midi_event); } else { status.last_midi_len = len; } memcpy(status.last_midi_event, data, status.last_midi_len); status.last_midi_port = NULL; status.last_midi_tick = timer_ticks(); status.flags |= NEED_UPDATE | MIDI_EVENT_CHANGED; } if (midims > 0) { // should always be true but I'm paranoid pos /= midims; if (pos > 0) { if (_midi_send_unlocked(data, len, pos, MIDI_FROM_LATER)) { // ok, we need a timer. struct _midi_send_timer_curry *curry = mem_alloc(sizeof(*curry) + len); memcpy(curry->msg, data, len); curry->len = len; // one s'more timer_oneshot(pos, _midi_send_timer_callback, curry); } } else { // put the bread in the basket midi_send_now(data, len); } } mt_mutex_unlock(midi_record_mutex); } // Get the length of a MIDI event in bytes uint8_t midi_event_length(uint8_t first_byte) { switch(first_byte & 0xF0) { case 0xC0: case 0xD0: return 2; case 0xF0: switch(first_byte) { case 0xF1: case 0xF3: return 2; case 0xF2: return 3; default: return 1; } break; default: return 3; } } /*----------------------------------------------------------------------------------*/ void midi_port_unregister(int num) { struct midi_port *q; int i; if (!midi_port_mutex) return; mt_mutex_lock(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); if (q->name) free(q->name); free(q); port_count--; memmove(port_top + i, port_top + i + 1, sizeof(*port_top) * (port_count - i)); port_top[port_count] = NULL; break; } } mt_mutex_unlock(midi_port_mutex); } void midi_received_cb(struct midi_port *src, unsigned char *data, uint32_t 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... */ mt_mutex_lock(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; status.last_midi_tick = timer_ticks(); mt_mutex_unlock(midi_record_mutex); /* pass through midi events when on midi page */ if (status.current_page == PAGE_MIDI) { midi_send_now(data,len); status.flags |= NEED_UPDATE; } cmd = ((*data) & 0xF0) >> 4; if (cmd == 0x8 || (cmd == 0x9 && data[2] == 0)) { midi_event_note(MIDI_NOTEOFF, data[0] & 15, data[1], 0); } else if (cmd == 0x9) { midi_event_note(MIDI_NOTEON, data[0] & 15, data[1], data[2]); } else if (cmd == 0xA) { midi_event_note(MIDI_KEYPRESS, data[0] & 15, data[1], data[2]); } else if (cmd == 0xB) { midi_event_controller(data[0] & 15, data[1], data[2]); } else if (cmd == 0xC) { midi_event_program(data[0] & 15, data[1]); } else if (cmd == 0xD) { midi_event_aftertouch(data[0] & 15, data[1]); } else if (cmd == 0xE) { midi_event_pitchbend(data[0] & 15, data[1]); } else if (cmd == 0xF) { switch ((*data & 15)) { case 0: /* sysex */ if (len <= 2) return; midi_event_sysex(data+1, len-2); break; case 6: /* tick */ midi_event_tick(); break; default: /* something else */ midi_event_system((*data & 15), (data[1]) | (data[2] << 8) | (data[3] << 16)); break; } } } void midi_event_note(enum midi_note mnstatus, int channel, int note, int velocity) { schism_event_t event = {0}; event.type = SCHISM_EVENT_MIDI_NOTE; event.midi_note.mnstatus = mnstatus; event.midi_note.channel = channel; event.midi_note.note = note; event.midi_note.velocity = velocity; events_push_event(&event); } void midi_event_controller(int channel, int param, int value) { schism_event_t event = {0}; event.type = SCHISM_EVENT_MIDI_CONTROLLER, event.midi_controller.value = value; event.midi_controller.param = param; event.midi_controller.channel = channel; events_push_event(&event); } void midi_event_program(int channel, int value) { schism_event_t event = {0}; event.type = SCHISM_EVENT_MIDI_PROGRAM; event.midi_program.value = value; event.midi_program.channel = channel; events_push_event(&event); } void midi_event_aftertouch(int channel, int value) { schism_event_t event = {0}; event.type = SCHISM_EVENT_MIDI_AFTERTOUCH; event.midi_aftertouch.value = value; event.midi_aftertouch.channel = channel; events_push_event(&event); } void midi_event_pitchbend(int channel, int value) { schism_event_t event = {0}; event.type = SCHISM_EVENT_MIDI_PITCHBEND; event.midi_pitchbend.value = value; event.midi_pitchbend.channel = channel; events_push_event(&event); } void midi_event_system(int argv, int param) { schism_event_t event = {0}; event.type = SCHISM_EVENT_MIDI_SYSTEM; event.midi_system.argv = argv; event.midi_system.param = param; events_push_event(&event); } void midi_event_tick(void) { schism_event_t event = {0}; event.type = SCHISM_EVENT_MIDI_TICK; events_push_event(&event); } void midi_event_sysex(const unsigned char *data, uint32_t len) { schism_event_t event = {0}; event.type = SCHISM_EVENT_MIDI_SYSEX; event.midi_sysex.len = len; event.midi_sysex.packet = (unsigned char *)strn_dup((const char *)data, len); events_push_event(&event); } int midi_engine_handle_event(schism_event_t *ev) { struct key_event kk = {.is_synthetic = 0}; if (midi_flags & MIDI_DISABLE_RECORD) return 0; switch (ev->type) { case SCHISM_EVENT_MIDI_NOTE: if (ev->midi_note.mnstatus == MIDI_NOTEON) { kk.state = KEY_PRESS; } else { if (!(midi_flags & MIDI_RECORD_NOTEOFF)) { /* don't record noteoff? okay... */ break; } kk.state = KEY_RELEASE; } kk.midi_channel = ev->midi_note.channel + 1; kk.midi_note = ((ev->midi_note.note)+1 + midi_c5note) - 60; if (midi_flags & MIDI_RECORD_VELOCITY) kk.midi_volume = (ev->midi_note.velocity); else kk.midi_volume = 128; kk.midi_volume = (kk.midi_volume * midi_amplification) / 100; handle_key(&kk); return 1; case SCHISM_EVENT_MIDI_PITCHBEND: /* wheel */ kk.midi_channel = ev->midi_pitchbend.channel+1; kk.midi_volume = -1; kk.midi_note = -1; kk.midi_bend = ev->midi_pitchbend.value; handle_key(&kk); return 1; case SCHISM_EVENT_MIDI_CONTROLLER: /* controller events */ return 1; case SCHISM_EVENT_MIDI_SYSTEM: switch (ev->midi_system.argv) { case 0x8: /* MIDI tick */ break; case 0xA: /* MIDI start */ case 0xB: /* MIDI continue */ song_start(); break; case 0xC: /* MIDI stop */ case 0xF: /* MIDI reset */ /* this is helpful when miditracking */ song_stop(); break; }; return 1; case SCHISM_EVENT_MIDI_SYSEX: /* but missing the F0 and the stop byte (F7) */ /* tfw midi ports just hand us friggin packets yo */ free(ev->midi_sysex.packet); return 1; default: return 0; } return 1; } schismtracker-20250313/schism/midi-ip.c000066400000000000000000000254101476471630300176140ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "midi.h" #include "it.h" #include "util.h" #include "events.h" #include "mt.h" // mutexes #ifdef USE_NETWORK #ifdef SCHISM_WIN32 #include #include #else #include #include #include #include #include #include #endif #define DEFAULT_IP_PORT_COUNT 5 #define MIDI_IP_BASE 21928 #define MAX_DGRAM_SIZE 1280 #ifndef SCHISM_WIN32 static int wakeup[2]; #endif static int real_num_ports = 0; static int num_ports = 0; static int out_fd = -1; static int *port_fd = NULL; static int *state = NULL; static mt_mutex_t *blocker = NULL; static void do_wake_main(void) { schism_event_t e = { .type = SCHISM_EVENT_UPDATE_IPMIDI, }; events_push_event(&e); } static void do_wake_midi(void) { #ifdef SCHISM_WIN32 /* anyone want to suggest how this is done? XXX */ #else if (write(wakeup[1], "\x1", 1) == 1) { /* fortify is stupid */ } #endif } static int _get_fd(int pb, int isout) { struct ip_mreq mreq = {0}; struct sockaddr_in asin = {0}; unsigned char *ipcopy; int fd, opt; #if !defined(PF_INET) && defined(AF_INET) #define PF_INET AF_INET #endif fd = socket(PF_INET, SOCK_DGRAM, 0); if (fd == -1) return -1; opt = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(int)); /* don't loop back what we generate */ opt = !isout; if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (void*)&opt, sizeof(opt)) < 0) { #ifdef SCHISM_WIN32 closesocket(fd); #else close(fd); #endif return -1; } opt = 31; if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&opt, sizeof(opt)) < 0) { #ifdef SCHISM_WIN32 closesocket(fd); #else close(fd); #endif return -1; } ipcopy = (unsigned char *)&mreq.imr_multiaddr; ipcopy[0] = 225; ipcopy[1] = ipcopy[2] = 0; ipcopy[3] = 37; if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&mreq, sizeof(mreq)) < 0) { #ifdef SCHISM_WIN32 closesocket(fd); #else close(fd); #endif return -1; } opt = 1; if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (void*)&opt, sizeof(opt)) < 0) { #ifdef SCHISM_WIN32 closesocket(fd); #else close(fd); #endif return -1; } asin.sin_family = AF_INET; ipcopy = (unsigned char *)&asin.sin_addr; if (!isout) { /* all 0s is inaddr_any; but this is for listening */ #ifdef SCHISM_WIN32 //JosepMa:On my machine, using the 225.0.0.37 address caused bind to fail. //Didn't look too much to find why. ipcopy[0] = ipcopy[1] = ipcopy[2] = ipcopy[3] = 0; #else ipcopy[0] = 225; ipcopy[1] = ipcopy[2] = 0; ipcopy[3] = 37; #endif asin.sin_port = htons(MIDI_IP_BASE+pb); } if (bind(fd, (struct sockaddr *)&asin, sizeof(asin)) < 0) { #ifdef SCHISM_WIN32 int asdf = WSAGetLastError(); perror("binderror"); switch(asdf) { case WSANOTINITIALISED: perror("WSANOTINITIALISED");break; case WSAENETDOWN: perror("WSAENETDOWN");break; case WSAEFAULT: perror("WSAEFAULT");break; case WSAEINVAL: perror("WSAEINVAL");break; case WSAEINPROGRESS: perror("WSAEINPROGRESS");break; case WSAENOTSOCK: perror("WSAENOTSOCK");break; case WSAEACCES: perror("WSAEACCES");break; case WSAEADDRINUSE: perror("WSAEADDRINUSE");break; case WSAEADDRNOTAVAIL: perror("WSAEADDRNOTAVAIL");break; case WSAENOBUFS: perror("WSAENOBUFS");break; default: perror("default");break; } closesocket(fd); #else close(fd); #endif return -1; } return fd; } void ip_midi_setports(int n) { if (out_fd == -1) return; if (status.flags & NO_NETWORK) return; mt_mutex_lock(blocker); num_ports = n; mt_mutex_unlock(blocker); do_wake_midi(); } static void _readin(struct midi_provider *p, int en, int fd) { struct midi_port *ptr, *src; static unsigned char buffer[65536]; static struct sockaddr_in asin; socklen_t slen = sizeof(asin); int r; r = recvfrom(fd, (char*)buffer, sizeof(buffer), 0, (struct sockaddr *)&asin, &slen); if (r > 0) { ptr = src = NULL; while (midi_port_foreach(p, &ptr)) { int n = INT_SHAPED_PTR(ptr->userdata); if (n == en) src = ptr; } midi_received_cb(src, buffer, r); } } static int _ip_thread(struct midi_provider *p) { #ifdef SCHISM_WIN32 struct timeval tv; #else static unsigned char buffer[4096]; #endif fd_set rfds; int *tmp2, *tmp; int i, m; while (!p->cancelled) { mt_mutex_lock(blocker); m = (volatile int)num_ports; //If no ports, wait and try again if (m > real_num_ports) { /* need more ports */ tmp = malloc(2 * (m * sizeof(int))); if (tmp) { tmp2 = tmp + m; for (i = 0; i < real_num_ports; i++) { tmp[i] = port_fd[i]; tmp2[i] = state[i]; } free(port_fd); port_fd = tmp; state = tmp2; for (i = real_num_ports; i < m; i++) { state[i] = 0; if ((port_fd[i] = _get_fd(i,0)) == -1) { m = i+1; break; } } real_num_ports = num_ports = m; } mt_mutex_unlock(blocker); do_wake_main(); } else if (m < real_num_ports) { for (i = m; i < real_num_ports; i++) { #ifdef SCHISM_WIN32 closesocket(port_fd[i]); #else close(port_fd[i]); #endif port_fd[i] = -1; } real_num_ports = num_ports = m; mt_mutex_unlock(blocker); do_wake_main(); } else { mt_mutex_unlock(blocker); if (!real_num_ports) { //Since the thread is not finished in this case (maybe it should), //we put a delay to prevent the thread using all the cpu. timer_msleep(1); } } FD_ZERO(&rfds); m = 0; for (i = 0; i < real_num_ports; i++) { FD_SET(port_fd[i], &rfds); if (port_fd[i] > m) m = port_fd[i]; } #ifdef SCHISM_WIN32 tv.tv_sec = 1; tv.tv_usec = 0; #else FD_SET(wakeup[0], &rfds); if (wakeup[0] > m) m = wakeup[0]; #endif do { i = select(m+1, &rfds, NULL, NULL, #ifdef SCHISM_WIN32 &tv #else NULL #endif ); #ifdef SCHISM_WIN32 if (i == SOCKET_ERROR ) { perror("selectError:"); int asdf = WSAGetLastError(); switch(asdf) { case WSANOTINITIALISED: perror("WSANOTINITIALISED");break; case WSAEFAULT: perror("WSAEFAULT");break; case WSAENETDOWN: perror("WSAENETDOWN");break; case WSAEINVAL: perror("WSAEINVAL");break; case WSAEINTR: perror("WSAEINTR");break; case WSAEINPROGRESS: perror("WSAEINPROGRESS");break; case WSAENOTSOCK: perror("WSAENOTSOCK");break; default: perror("default");break; } } #endif } while (i == -1 && errno == EINTR); #ifndef SCHISM_WIN32 if (FD_ISSET(wakeup[0], &rfds)) { if (read(wakeup[0], buffer, sizeof(buffer)) == -1) { /* fortify is stupid */ } } #endif for (i = 0; i < real_num_ports; i++) { if (FD_ISSET(port_fd[i], &rfds)) { if (state[i] & 1) _readin(p, i, port_fd[i]); } } } return 0; } static int _ip_start(struct midi_port *p) { int n = INT_SHAPED_PTR(p->userdata); mt_mutex_lock(blocker); if (p->io & MIDI_INPUT) state[n] |= 1; if (p->io & MIDI_OUTPUT) state[n] |= 2; mt_mutex_unlock(blocker); do_wake_midi(); return 1; } static int _ip_stop(struct midi_port *p) { int n = INT_SHAPED_PTR(p->userdata); mt_mutex_lock(blocker); if (p->io & MIDI_INPUT) state[n] &= (~1); if (p->io & MIDI_OUTPUT) state[n] &= (~2); mt_mutex_unlock(blocker); do_wake_midi(); return 1; } static void _ip_send(struct midi_port *p, const unsigned char *data, uint32_t len, SCHISM_UNUSED uint32_t delay) { struct sockaddr_in asin = {0}; unsigned char *ipcopy; int n = INT_SHAPED_PTR(p->userdata); int ss; if (len == 0) return; if (!(state[n] & 2)) return; /* blah... */ asin.sin_family = AF_INET; ipcopy = (unsigned char *)&asin.sin_addr.s_addr; ipcopy[0] = 225; ipcopy[1] = ipcopy[2] = 0; ipcopy[3] = 37; asin.sin_port = htons(MIDI_IP_BASE+n); while (len) { ss = (len > MAX_DGRAM_SIZE) ? MAX_DGRAM_SIZE : len; if (sendto(out_fd, (const char*)data, ss, 0, (struct sockaddr *)&asin,sizeof(asin)) < 0) { state[n] &= (~2); /* turn off output */ break; } len -= ss; data += ss; } } static void _ip_poll(struct midi_provider *p) { static int last_buildout = 0; struct midi_port *ptr; char *buffer; long i = 0; long m; mt_mutex_lock(blocker); m = (volatile int)real_num_ports; if (m < last_buildout) { ptr = NULL; while (midi_port_foreach(p, &ptr)) { i = INT_SHAPED_PTR(ptr->userdata); if (i >= m) { midi_port_unregister(ptr->num); //port_top[i] (the address where ptr points to) is freed in midi_port_unregister. //So clear it to avoid midi_port_foreach crashing on next round ptr = NULL; } } last_buildout = m; } else if (m > last_buildout) { for (i = last_buildout; i < m; i++) { buffer = NULL; if (asprintf(&buffer, " Multicast/IP MIDI %ld", i+1) == -1) { perror("asprintf"); exit(255); } if (!buffer) { perror("asprintf"); exit(255); } midi_port_register(p, MIDI_INPUT | MIDI_OUTPUT, buffer, PTR_SHAPED_INT((intptr_t)i), 0); } last_buildout = m; } mt_mutex_unlock(blocker); } int ip_midi_setup(void) { static struct midi_driver driver; if (status.flags & NO_NETWORK) return 0; blocker = mt_mutex_create(); if (!blocker) return 0; #ifndef SCHISM_WIN32 if (pipe(wakeup) == -1) { return 0; } fcntl(wakeup[0], F_SETFL, fcntl(wakeup[0], F_GETFL, 0) | O_NONBLOCK); fcntl(wakeup[1], F_SETFL, fcntl(wakeup[1], F_GETFL, 0) | O_NONBLOCK); #endif if (out_fd == -1) { out_fd = _get_fd(-1, 1); if (out_fd == -1) return 0; } driver.flags = 0; driver.poll = _ip_poll; driver.thread = _ip_thread; driver.send = _ip_send; driver.enable = _ip_start; driver.disable = _ip_stop; //TODO: Save number of MIDI-IP ports ip_midi_setports(DEFAULT_IP_PORT_COUNT); if (!midi_provider_register("IP", &driver)) return 0; return 1; } int ip_midi_getports(void) { int tmp; if (out_fd == -1) return 0; if (status.flags & NO_NETWORK) return 0; mt_mutex_lock(blocker); tmp = (volatile int)real_num_ports; mt_mutex_unlock(blocker); return tmp; } #else int ip_midi_getports(void) { return 0; } void ip_midi_setports(SCHISM_UNUSED int n) { } #endif schismtracker-20250313/schism/mplink.c000066400000000000000000000506571476471630300175710ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "song.h" #include "slurp.h" // ------------------------------------------------------------------------ // variables song_t *current_song = NULL; // ------------------------------------------------------------------------ // song information unsigned int song_get_length_to(int order, int row) { unsigned int t; song_lock_audio(); current_song->stop_at_order = order; current_song->stop_at_row = row; t = csf_get_length(current_song); current_song->stop_at_order = current_song->stop_at_row = -1; song_unlock_audio(); return t; } void song_get_at_time(unsigned int seconds, int *order, int *row) { if (!seconds) { if (order) *order = 0; if (row) *row = 0; } else { song_lock_audio(); current_song->stop_at_order = MAX_ORDERS; current_song->stop_at_row = 255; /* unpossible */ current_song->stop_at_time = seconds; csf_get_length(current_song); if (order) *order = current_song->stop_at_order; if (row) *row = current_song->stop_at_row; current_song->stop_at_order = current_song->stop_at_row = -1; current_song->stop_at_time = 0; song_unlock_audio(); } } song_sample_t *song_get_sample(int n) { if (n >= MAX_SAMPLES) return NULL; return current_song->samples + n; } song_instrument_t *song_get_instrument(int n) { if (n >= MAX_INSTRUMENTS) return NULL; // Make a new instrument if it doesn't exist. if (!current_song->instruments[n]) { current_song->instruments[n] = csf_allocate_instrument(); } return (song_instrument_t *) current_song->instruments[n]; } // this is a fairly gross way to do what should be such a simple thing int song_get_instrument_number(song_instrument_t *inst) { if (inst) for (int n = 1; n < MAX_INSTRUMENTS; n++) if (inst == ((song_instrument_t *) current_song->instruments[n])) return n; return 0; } song_channel_t *song_get_channel(int n) { if (n >= MAX_CHANNELS) return NULL; return (song_channel_t *) current_song->channels + n; } song_voice_t *song_get_mix_channel(int n) { if (n >= MAX_VOICES) return NULL; return (song_voice_t *) current_song->voices + n; } int song_get_mix_state(uint32_t **channel_list) { if (channel_list) *channel_list = current_song->voice_mix; return MIN(current_song->num_voices, current_song->max_voices); } // ------------------------------------------------------------------------ // For all of these, channel is ZERO BASED. // (whereas in the pattern editor etc. it's one based) static int channel_states[64]; // saved ("real") mute settings; nonzero = muted static inline void _save_state(int channel) { channel_states[channel] = current_song->voices[channel].flags & CHN_MUTE; } void song_save_channel_states(void) { int n = 64; while (n-- > 0) _save_state(n); } static inline void _fix_mutes_like(int chan) { int i; for (i = 0; i < MAX_VOICES; i++) { if (i == chan) continue; if (((int)current_song->voices[i].master_channel) != (chan+1)) continue; current_song->voices[i].flags = (current_song->voices[i].flags & (~(CHN_MUTE))) | (current_song->voices[chan].flags & (CHN_MUTE)); } } void song_set_channel_mute(int channel, int muted) { if (muted) { current_song->channels[channel].flags |= CHN_MUTE; current_song->voices[channel].flags |= CHN_MUTE; } else { current_song->channels[channel].flags &= ~CHN_MUTE; current_song->voices[channel].flags &= ~CHN_MUTE; _save_state(channel); } _fix_mutes_like(channel); } // I don't think this is useful besides undoing a channel solo (a few lines // below), but I'm making it extern anyway for symmetry. void song_restore_channel_states(void) { int n = 64; while (n-- > 0) song_set_channel_mute(n, channel_states[n]); } void song_toggle_channel_mute(int channel) { // i'm just going by the playing channel's state... // if the actual channel is muted but not the playing one, // tough luck :) song_set_channel_mute(channel, (current_song->voices[channel].flags & CHN_MUTE) == 0); } static int _soloed(int channel) { int n = 64; // if this channel is muted, it obviously isn't soloed if (current_song->voices[channel].flags & CHN_MUTE) return 0; while (n-- > 0) { if (n == channel) continue; if (!(current_song->voices[n].flags & CHN_MUTE)) return 0; } return 1; } void song_handle_channel_solo(int channel) { int n = 64; if (_soloed(channel)) { song_restore_channel_states(); } else { while (n-- > 0) song_set_channel_mute(n, n != channel); } } // returned channel number is ONE-based // (to make it easier to work with in the pattern editor and info page) int song_find_last_channel(void) { int n = 64; while (channel_states[--n]) if (n == 0) return 64; return n + 1; } // ------------------------------------------------------------------------ // calculates row of offset from passed row. // sets actual pattern number, row and optional pattern buffer. // returns length of selected patter, or 0 on error. // if song mode is pattern loop (MODE_PATTERN_LOOP), offset is mod calculated // in current pattern. int song_get_pattern_offset(int * n, song_note_t ** buf, int * row, int offset) { int tot; if (song_get_mode() & MODE_PATTERN_LOOP) { // just wrap around current rows *row = (*row + offset) % song_get_rows_in_pattern(*n); return song_get_pattern(*n, buf); } tot = song_get_rows_in_pattern(*n); while (offset + *row > tot) { offset -= tot; (*n)++; tot = song_get_rows_in_pattern(*n); if (!tot) { return 0; } } *row += offset; return song_get_pattern(*n, buf); } // returns length of the pattern, or 0 on error. (this can be used to // get a pattern's length by passing NULL for buf.) int song_get_pattern(int n, song_note_t ** buf) { if (n >= MAX_PATTERNS) return 0; if (buf) { if (!current_song->patterns[n]) { current_song->pattern_size[n] = 64; current_song->pattern_alloc_size[n] = 64; current_song->patterns[n] = csf_allocate_pattern(current_song->pattern_size[n]); } *buf = current_song->patterns[n]; } else { if (!current_song->patterns[n]) return 64; } return current_song->pattern_size[n]; } song_note_t *song_pattern_allocate_copy(int patno, int *rows) { int len = current_song->pattern_size[patno]; song_note_t *olddata = current_song->patterns[patno]; song_note_t *newdata = NULL; if (olddata) { newdata = csf_allocate_pattern(len); memcpy(newdata, olddata, len * sizeof(song_note_t) * 64); } if (rows) *rows = len; return newdata; } void song_pattern_install(int patno, song_note_t *n, int rows) { song_lock_audio(); song_note_t *olddata = current_song->patterns[patno]; csf_free_pattern(olddata); current_song->patterns[patno] = n; current_song->pattern_alloc_size[patno] = rows; current_song->pattern_size[patno] = rows; song_unlock_audio(); } // ------------------------------------------------------------------------ int song_next_order_for_pattern(int pat) { int i, ord = current_song->current_order; ord = CLAMP(ord, 0, 255); for (i = ord; i < 255; i++) { if (current_song->orderlist[i] == pat) { return i; } } for (i = 0; i < ord; i++) { if (current_song->orderlist[i] == pat) { return i; } } return -1; } int song_get_rows_in_pattern(int pattern) { if (pattern > MAX_PATTERNS) return 0; return (current_song->pattern_size[pattern] ? current_song->pattern_size[pattern] : 64) - 1; } // ------------------------------------------------------------------------ void song_pattern_resize(int pattern, int newsize) { song_lock_audio(); int oldsize = current_song->pattern_alloc_size[pattern]; status.flags |= SONG_NEEDS_SAVE; if (!current_song->patterns[pattern] && newsize != 64) { current_song->patterns[pattern] = csf_allocate_pattern(newsize); current_song->pattern_alloc_size[pattern] = newsize; } else if (oldsize < newsize) { song_note_t *olddata = current_song->patterns[pattern]; song_note_t *newdata = csf_allocate_pattern(newsize); if (olddata) { memcpy(newdata, olddata, 64 * sizeof(song_note_t) * MIN(newsize, oldsize)); csf_free_pattern(olddata); } current_song->patterns[pattern] = newdata; current_song->pattern_alloc_size[pattern] = MAX(newsize,oldsize); } current_song->pattern_size[pattern] = newsize; song_unlock_audio(); } // ------------------------------------------------------------------------ void song_set_initial_speed(int new_speed) { current_song->initial_speed = CLAMP(new_speed, 1, 255); } void song_set_initial_tempo(int new_tempo) { current_song->initial_tempo = CLAMP(new_tempo, 31, 255); } void song_set_initial_global_volume(int new_vol) { current_song->initial_global_volume = CLAMP(new_vol, 0, 128); } void song_set_mixing_volume(int new_vol) { current_song->mixing_volume = CLAMP(new_vol, 0, 128); } void song_set_separation(int new_sep) { current_song->pan_separation = CLAMP(new_sep, 0, 128); } int song_is_stereo(void) { if (current_song->flags & SONG_NOSTEREO) return 0; return 1; } void song_toggle_stereo(void) { current_song->flags ^= SONG_NOSTEREO; song_vars_sync_stereo(); } void song_toggle_mono(void) { current_song->flags ^= SONG_NOSTEREO; song_vars_sync_stereo(); } void song_set_mono(void) { current_song->flags |= SONG_NOSTEREO; song_vars_sync_stereo(); } void song_set_stereo(void) { current_song->flags &= ~SONG_NOSTEREO; song_vars_sync_stereo(); } int song_has_old_effects(void) { return !!(current_song->flags & SONG_ITOLDEFFECTS); } void song_set_old_effects(int value) { if (value) current_song->flags |= SONG_ITOLDEFFECTS; else current_song->flags &= ~SONG_ITOLDEFFECTS; } int song_has_compatible_gxx(void) { return !!(current_song->flags & SONG_COMPATGXX); } void song_set_compatible_gxx(int value) { if (value) current_song->flags |= SONG_COMPATGXX; else current_song->flags &= ~SONG_COMPATGXX; } int song_has_linear_pitch_slides(void) { return !!(current_song->flags & SONG_LINEARSLIDES); } void song_set_linear_pitch_slides(int value) { if (value) current_song->flags |= SONG_LINEARSLIDES; else current_song->flags &= ~SONG_LINEARSLIDES; } int song_is_instrument_mode(void) { return !!(current_song->flags & SONG_INSTRUMENTMODE); } void song_set_instrument_mode(int value) { int oldvalue = song_is_instrument_mode(); int i, j; if (value && !oldvalue) { current_song->flags |= SONG_INSTRUMENTMODE; for (i = 0; i < MAX_INSTRUMENTS; i++) { if (!current_song->instruments[i]) continue; /* fix wiped notes */ for (j = 0; j < 128; j++) { if (current_song->instruments[i]->note_map[j] < 1 || current_song->instruments[i]->note_map[j] > 120) current_song->instruments[i]->note_map[j] = j+1; } } } else if (!value && oldvalue) { current_song->flags &= ~SONG_INSTRUMENTMODE; } } int song_get_current_instrument(void) { return (song_is_instrument_mode() ? instrument_get_current() : sample_get_current()); } // ------------------------------------------------------------------------ void song_exchange_samples(int a, int b) { if (a == b) return; song_lock_audio(); song_sample_t tmp; memcpy(&tmp, current_song->samples + a, sizeof(song_sample_t)); memcpy(current_song->samples + a, current_song->samples + b, sizeof(song_sample_t)); memcpy(current_song->samples + b, &tmp, sizeof(song_sample_t)); status.flags |= SONG_NEEDS_SAVE; song_unlock_audio(); } void song_copy_instrument(int dst, int src) { if (src == dst) return; song_lock_audio(); song_get_instrument(dst); song_get_instrument(src); *(current_song->instruments[dst]) = *(current_song->instruments[src]); status.flags |= SONG_NEEDS_SAVE; song_unlock_audio(); } void song_exchange_instruments(int a, int b) { if (a == b) return; song_instrument_t *tmp; song_lock_audio(); tmp = current_song->instruments[a]; current_song->instruments[a] = current_song->instruments[b]; current_song->instruments[b] = tmp; status.flags |= SONG_NEEDS_SAVE; song_unlock_audio(); } // instrument, sample, whatever. static void _swap_instruments_in_patterns(int a, int b) { for (int pat = 0; pat < MAX_PATTERNS; pat++) { song_note_t *note = current_song->patterns[pat]; if (note == NULL) continue; for (int n = 0; n < 64 * current_song->pattern_size[pat]; n++, note++) { if (note->instrument == a) note->instrument = b; else if (note->instrument == b) note->instrument = a; } } } void song_swap_samples(int a, int b) { if (a == b) return; song_lock_audio(); if (song_is_instrument_mode()) { // ... or should this be done even in sample mode? for (int n = 1; n < MAX_INSTRUMENTS; n++) { song_instrument_t *ins = current_song->instruments[n]; if (ins == NULL) continue; // sizeof(ins->sample_map)... for (int s = 0; s < 128; s++) { if (ins->sample_map[s] == (unsigned int)a) ins->sample_map[s] = (unsigned int)b; else if (ins->sample_map[s] == (unsigned int)b) ins->sample_map[s] = (unsigned int)a; } } } else { _swap_instruments_in_patterns(a, b); } song_unlock_audio(); song_exchange_samples(a, b); } void song_swap_instruments(int a, int b) { if (a == b) return; if (song_is_instrument_mode()) { song_lock_audio(); _swap_instruments_in_patterns(a, b); song_unlock_audio(); } song_exchange_instruments(a, b); } static void _adjust_instruments_in_patterns(int start, int delta) { int pat, n; for (pat = 0; pat < MAX_PATTERNS; pat++) { song_note_t *note = current_song->patterns[pat]; if (note == NULL) continue; for (n = 0; n < 64 * current_song->pattern_size[pat]; n++, note++) { if (note->instrument >= start) note->instrument = CLAMP(note->instrument + delta, 0, MAX_SAMPLES - 1); } } } static void _adjust_samples_in_instruments(int start, int delta) { int n, s; for (n = 1; n < MAX_INSTRUMENTS; n++) { song_instrument_t *ins = current_song->instruments[n]; if (ins == NULL) continue; // sizeof... for (s = 0; s < 128; s++) { if (ins->sample_map[s] >= (unsigned int) start) { ins->sample_map[s] = (unsigned int) CLAMP( ((int) ins->sample_map[s]) + delta, 0, MAX_SAMPLES - 1); } } } } void song_init_instrument_from_sample(int insn, int samp) { if (!csf_instrument_is_empty(current_song->instruments[insn])) return; if (current_song->samples[samp].data == NULL) return; song_get_instrument(insn); song_instrument_t *ins = current_song->instruments[insn]; if (!ins) return; /* eh? */ csf_init_instrument(ins, samp); // IT doesn't set instrument filenames unless loading an instrument from disk //memcpy(ins->filename, current_song->samples[samp].filename, 12); memcpy(ins->name, current_song->samples[samp].name, 32); } void song_init_instruments(int qq) { for (int n = 1; n < MAX_INSTRUMENTS; n++) { if (qq > -1 && qq != n) continue; song_init_instrument_from_sample(n,n); } } void song_insert_sample_slot(int n) { if (current_song->samples[MAX_SAMPLES - 1].data != NULL) return; status.flags |= SONG_NEEDS_SAVE; song_lock_audio(); memmove(current_song->samples + n + 1, current_song->samples + n, (MAX_SAMPLES - n - 1) * sizeof(song_sample_t)); memset(current_song->samples + n, 0, sizeof(song_sample_t)); current_song->samples[n].c5speed = 8363; current_song->samples[n].volume = 64 * 4; current_song->samples[n].global_volume = 64; if (song_is_instrument_mode()) _adjust_samples_in_instruments(n, 1); else _adjust_instruments_in_patterns(n, 1); song_unlock_audio(); } void song_remove_sample_slot(int n) { if (current_song->samples[n].data != NULL) return; song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; memmove(current_song->samples + n, current_song->samples + n + 1, (MAX_SAMPLES - n - 1) * sizeof(song_sample_t)); memset(current_song->samples + MAX_SAMPLES - 1, 0, sizeof(song_sample_t)); current_song->samples[MAX_SAMPLES - 1].c5speed = 8363; current_song->samples[MAX_SAMPLES - 1].volume = 64 * 4; current_song->samples[MAX_SAMPLES - 1].global_volume = 64; if (song_is_instrument_mode()) _adjust_samples_in_instruments(n, -1); else _adjust_instruments_in_patterns(n, -1); song_unlock_audio(); } void song_insert_instrument_slot(int n) { int i; if (!csf_instrument_is_empty(current_song->instruments[MAX_INSTRUMENTS - 1])) return; status.flags |= SONG_NEEDS_SAVE; song_lock_audio(); for (i = MAX_INSTRUMENTS - 1; i > n; i--) current_song->instruments[i] = current_song->instruments[i-1]; current_song->instruments[n] = NULL; _adjust_instruments_in_patterns(n, 1); song_unlock_audio(); } void song_remove_instrument_slot(int n) { int i; if (!csf_instrument_is_empty(current_song->instruments[n])) return; song_lock_audio(); for (i = n; i < MAX_INSTRUMENTS; i++) current_song->instruments[i] = current_song->instruments[i+1]; current_song->instruments[MAX_INSTRUMENTS - 1] = NULL; _adjust_instruments_in_patterns(n, -1); song_unlock_audio(); } void song_wipe_instrument(int n) { /* wee .... */ if (csf_instrument_is_empty(current_song->instruments[n])) return; if (!current_song->instruments[n]) return; status.flags |= SONG_NEEDS_SAVE; song_lock_audio(); csf_free_instrument(current_song->instruments[n]); current_song->instruments[n] = NULL; song_unlock_audio(); } // Returns 1 if sample `n` is used by the specified instrument; 0 otherwise. static int _song_sample_used_by_instrument(int n, const song_instrument_t *instrument) { size_t i; for (i = 0; i < sizeof(instrument->sample_map); i++) { if (instrument->sample_map[i] == n) { return 1; } } return 0; } // Returns 1 if sample `n` is used by at least two instruments; 0 otherwise. static int _song_sample_used_by_many_instruments(int n) { const song_instrument_t *instrument; int found; int i; found = 0; for (i = 1; i < MAX_INSTRUMENTS+1; i++) { instrument = current_song->instruments[i]; if (instrument != NULL) { if (_song_sample_used_by_instrument(n, instrument)) { if (found) { return 1; } found = 1; } } } return 0; } // n: The index of the instrument to delete (base-1). // preserve_samples: If 0, delete all samples used by instrument. // If 1, only delete samples that no other instruments use. void song_delete_instrument(int n, int preserve_samples) { unsigned long i; int j; if (!current_song->instruments[n]) return; // 128? really? for (i = 0; i < 128; i++) { j = current_song->instruments[n]->sample_map[i]; if (j) { if (!preserve_samples || !_song_sample_used_by_many_instruments(j)) { song_clear_sample(j); } } } song_wipe_instrument(n); } void song_replace_sample(int num, int with) { int i, j; song_instrument_t *ins; song_note_t *note; if (num < 1 || num > MAX_SAMPLES || with < 1 || with > MAX_SAMPLES) return; if (song_is_instrument_mode()) { // for each instrument, for each note in the keyboard table, replace 'smp' with 'with' for (i = 1; i < MAX_INSTRUMENTS; i++) { ins = current_song->instruments[i]; if (!ins) continue; for (j = 0; j < 128; j++) { if ((int) ins->sample_map[j] == num) ins->sample_map[j] = with; } } } else { // for each pattern, for each note, replace 'smp' with 'with' for (i = 0; i < MAX_PATTERNS; i++) { note = current_song->patterns[i]; if (!note) continue; for (j = 0; j < 64 * current_song->pattern_size[i]; j++, note++) { if (note->instrument == num) note->instrument = with; } } } } void song_replace_instrument(int num, int with) { int i, j; song_note_t *note; if (num < 1 || num > MAX_INSTRUMENTS || with < 1 || with > MAX_INSTRUMENTS || !song_is_instrument_mode()) return; // for each pattern, for each note, replace 'ins' with 'with' for (i = 0; i < MAX_PATTERNS; i++) { note = current_song->patterns[i]; if (!note) continue; for (j = 0; j < 64 * current_song->pattern_size[i]; j++, note++) { if (note->instrument == num) note->instrument = with; } } } schismtracker-20250313/schism/mt.c000066400000000000000000000062541476471630300167110ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "mem.h" #include "timer.h" #include "backend/mt.h" static const schism_mt_backend_t *mt_backend = NULL; mt_thread_t *mt_thread_create(schism_thread_function_t func, const char *name, void *userdata) { return mt_backend->thread_create(func, name, userdata); } void mt_thread_wait(mt_thread_t *thread, int *status) { mt_backend->thread_wait(thread, status); } void mt_thread_set_priority(int priority) { mt_backend->thread_set_priority(priority); } // returns the current thread's ID mt_thread_id_t mt_thread_id(void) { return mt_backend->thread_id(); } // --------------------------------------------------------------------------- mt_mutex_t *mt_mutex_create(void) { return mt_backend->mutex_create(); } void mt_mutex_delete(mt_mutex_t *mutex) { mt_backend->mutex_delete(mutex); } void mt_mutex_lock(mt_mutex_t *mutex) { mt_backend->mutex_lock(mutex); } void mt_mutex_unlock(mt_mutex_t *mutex) { mt_backend->mutex_unlock(mutex); } // --------------------------------------------------------------------------- mt_cond_t *mt_cond_create(void) { return mt_backend->cond_create(); } void mt_cond_delete(mt_cond_t *cond) { mt_backend->cond_delete(cond); } void mt_cond_signal(mt_cond_t *cond) { mt_backend->cond_signal(cond); } void mt_cond_wait(mt_cond_t *cond, mt_mutex_t *mutex) { mt_backend->cond_wait(cond, mutex); } void mt_cond_wait_timeout(mt_cond_t *cond, mt_mutex_t *mutex, uint32_t timeout) { mt_backend->cond_wait_timeout(cond, mutex, timeout); } // --------------------------------------------------------------------------- int mt_init(void) { static const schism_mt_backend_t *backends[] = { // ordered by preference #ifdef SCHISM_WIN32 &schism_mt_backend_win32, #endif #ifdef SCHISM_MACOS &schism_mt_backend_macos, #endif #ifdef SCHISM_SDL3 &schism_mt_backend_sdl3, #endif #ifdef SCHISM_SDL2 &schism_mt_backend_sdl2, #endif #ifdef SCHISM_SDL12 &schism_mt_backend_sdl12, #endif NULL, }; int i; for (i = 0; backends[i]; i++) { if (backends[i]->init()) { mt_backend = backends[i]; break; } } if (!mt_backend) return 0; return 1; } void mt_quit(void) { if (mt_backend) { mt_backend->quit(); mt_backend = NULL; } }schismtracker-20250313/schism/page.c000066400000000000000000001456561476471630300172170ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "backend/timer.h" #include "it.h" #include "vgamem.h" #include "keyboard.h" #include "song.h" #include "page.h" #include "charset.h" #include "util.h" #include "midi.h" #include "version.h" #include "video.h" #include "fakemem.h" #include "fonts.h" #include "dialog.h" #include "widget.h" #include "mem.h" #include "str.h" #include "config.h" /* --------------------------------------------------------------------- */ /* globals */ struct tracker_status status = { .current_page = PAGE_BLANK, .previous_page = PAGE_BLANK, .current_help_index = HELP_GLOBAL, .dialog_type = DIALOG_NONE, .flags = 0, .time_display = TIME_PLAY_ELAPSED, .vis_style = VIS_VU_METER, .last_midi_event = "", // everything else set to 0/NULL/etc. }; struct page pages[PAGE_MAX] = {0}; struct widget *widgets = NULL; int *selected_widget = NULL; int *total_widgets = NULL; static int fontedit_return_page = PAGE_PATTERN_EDITOR; /* --------------------------------------------------------------------- */ static struct { int h, m, s; } current_time = {0, 0, 0}; extern int playback_tracing; /* scroll lock */ extern int midi_playback_tracing; /* return 1 -> the time changed; need to redraw */ static int check_time(void) { static int last_o = -1, last_r = -1, last_timep = -1; time_t timep = 0; int h, m, s; enum tracker_time_display td = status.time_display; int is_playing = song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP); int row, order; switch (td) { case TIME_PLAY_ELAPSED: td = (is_playing ? TIME_PLAYBACK : TIME_ELAPSED); break; case TIME_PLAY_CLOCK: td = (is_playing ? TIME_PLAYBACK : TIME_CLOCK); break; case TIME_PLAY_OFF: td = (is_playing ? TIME_PLAYBACK : TIME_OFF); break; default: break; } switch (td) { case TIME_OFF: h = m = s = 0; break; case TIME_PLAYBACK: h = (m = (s = song_get_current_time()) / 60) / 60; break; case TIME_ELAPSED: h = (m = (s = timer_ticks() / 1000) / 60) / 60; break; case TIME_ABSOLUTE: /* absolute time shows the time of the current cursor position in the pattern editor :) */ if (status.current_page == PAGE_PATTERN_EDITOR) { row = get_current_row(); order = song_next_order_for_pattern(get_current_pattern()); } else { order = get_current_order(); row = 0; } if (order < 0) { s = m = h = 0; } else { if (last_o == order && last_r == row) { timep = last_timep; } else { last_timep = timep = song_get_length_to(order, row); last_o = order; last_r = row; } s = timep % 60; m = (timep / 60) % 60; h = (timep / 3600); } break; default: /* this will never happen */ case TIME_CLOCK: /* Impulse Tracker doesn't have this, but I always wanted it, so here 'tis. */ h = status.tmnow.tm_hour; m = status.tmnow.tm_min; s = status.tmnow.tm_sec; break; } if (h == current_time.h && m == current_time.m && s == current_time.s) { return 0; } current_time.h = h; current_time.m = m; current_time.s = s; return 1; } static inline void draw_time(void) { char buf[27]; 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? */ if (status.time_display == TIME_CLOCK) { str_time_from_tm(&status.tmnow, buf, cfg_str_time_format); const size_t buflen = strlen(buf); if (buflen > 0) { draw_text(buf, 69 + MIN(9, 9 - (ptrdiff_t)buflen), 9, 0, 2); return; } } sprintf(buf, "%3d:%02d:%02d", current_time.h % 1000, current_time.m % 60, current_time.s % 60); draw_text(buf, 69, 9, 0, 2); } /* --------------------------------------------------------------------- */ static void draw_page_title(void) { int x, tpos, tlen = strlen(ACTIVE_PAGE.title); if (tlen > 0) { tpos = 41 - ((tlen + 1) / 2); for (x = 1; x < tpos - 1; x++) draw_char(154, x, 11, 1, 2); draw_char(0, tpos - 1, 11, 1, 2); draw_text(ACTIVE_PAGE.title, tpos, 11, 0, 2); draw_char(0, tpos + tlen, 11, 1, 2); for (x = tpos + tlen + 1; x < 79; x++) draw_char(154, x, 11, 1, 2); } else { for (x = 1; x < 79; x++) draw_char(154, x, 11, 1, 2); } } /* --------------------------------------------------------------------- */ /* Not that happy with the way this function developed, but well, it still * works. Maybe someday I'll make it suck less. */ static void draw_page(void) { int n = ACTIVE_PAGE.total_widgets; if (ACTIVE_PAGE.draw_full) { ACTIVE_PAGE.draw_full(); } else { draw_page_title(); if (ACTIVE_PAGE.draw_const) ACTIVE_PAGE.draw_const(); if (ACTIVE_PAGE.predraw_hook) ACTIVE_PAGE.predraw_hook(); } /* this doesn't use widgets[] because it needs to draw the page's * widgets whether or not a dialog is active */ while (n--) widget_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(); } /* --------------------------------------------------------------------- */ 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] = {0}; static const int new_song_groups[4][3] = { {0, 1, -1}, {2, 3, -1}, {4, 5, -1}, {6, 7, -1} }; static void new_song_ok(SCHISM_UNUSED void *data) { int flags = 0; if (new_song_widgets[0].d.togglebutton.state) flags |= KEEP_PATTERNS; if (new_song_widgets[2].d.togglebutton.state) flags |= KEEP_SAMPLES; if (new_song_widgets[4].d.togglebutton.state) flags |= KEEP_INSTRUMENTS; if (new_song_widgets[6].d.togglebutton.state) flags |= KEEP_ORDERLIST; song_new(flags); } static void new_song_draw_const(void) { draw_text("New Song", 36, 21, 3, 2); draw_text("Patterns", 26, 24, 0, 2); draw_text("Samples", 27, 27, 0, 2); draw_text("Instruments", 23, 30, 0, 2); draw_text("Order List", 24, 33, 0, 2); } void new_song_dialog(void) { struct dialog *dialog; /* only create everything if it hasn't been set up already */ if (new_song_widgets[0].width == 0) { widget_create_togglebutton(new_song_widgets + 0, 35, 24, 6, 0, 2, 1, 1, 1, NULL, "Keep", 2, new_song_groups[0]); widget_create_togglebutton(new_song_widgets + 1, 45, 24, 7, 1, 3, 0, 0, 0, NULL, "Clear", 2, new_song_groups[0]); widget_create_togglebutton(new_song_widgets + 2, 35, 27, 6, 0, 4, 3, 3, 3, NULL, "Keep", 2, new_song_groups[1]); widget_create_togglebutton(new_song_widgets + 3, 45, 27, 7, 1, 5, 2, 2, 2, NULL, "Clear", 2, new_song_groups[1]); widget_create_togglebutton(new_song_widgets + 4, 35, 30, 6, 2, 6, 5, 5, 5, NULL, "Keep", 2, new_song_groups[2]); widget_create_togglebutton(new_song_widgets + 5, 45, 30, 7, 3, 7, 4, 4, 4, NULL, "Clear", 2, new_song_groups[2]); widget_create_togglebutton(new_song_widgets + 6, 35, 33, 6, 4, 8, 7, 7, 7, NULL, "Keep", 2, new_song_groups[3]); widget_create_togglebutton(new_song_widgets + 7, 45, 33, 7, 5, 9, 6, 6, 6, NULL, "Clear", 2, new_song_groups[3]); widget_create_button(new_song_widgets + 8, 28, 36, 8, 6, 8, 9, 9, 9, dialog_yes_NULL, "OK", 4); widget_create_button(new_song_widgets + 9, 41, 36, 8, 6, 9, 8, 8, 8, dialog_cancel_NULL, "Cancel", 2); widget_togglebutton_set(new_song_widgets, 1, 0); widget_togglebutton_set(new_song_widgets, 3, 0); widget_togglebutton_set(new_song_widgets, 5, 0); widget_togglebutton_set(new_song_widgets, 7, 0); } dialog = dialog_create_custom(21, 20, 38, 19, new_song_widgets, 10, 8, new_song_draw_const, NULL); dialog->action_yes = new_song_ok; } /* --------------------------------------------------------------------------------------------------------- */ /* This is an ugly monster. */ /* Jesus, you're right. WTF is all this? I'm lost. :/ -storlek */ static int _mp_active = 0; static struct widget _mpw[1]; static void (*_mp_setv)(int v) = NULL; static void (*_mp_setv_noplay)(int v) = NULL; static const char *_mp_text = ""; static int _mp_text_x, _mp_text_y; static void _mp_draw(void) { const char *name = NULL; int n, i; if (_mp_text[0] == '!') { /* inst */ n = instrument_get_current(); if (n) name = song_get_instrument(n)->name; else name = "(No Instrument)"; } else if (_mp_text[0] == '@') { /* samp */ n = sample_get_current(); if (n > 0) name = song_get_sample(n)->name; else name = "(No Sample)"; } else { name = _mp_text; } i = strlen(name); draw_fill_chars(_mp_text_x, _mp_text_y, _mp_text_x + 17, _mp_text_y, DEFAULT_FG, 2); draw_text_len( name, 17, _mp_text_x, _mp_text_y, 0, 2); if (i < 17 && name == _mp_text) { draw_char(':', _mp_text_x + i, _mp_text_y, 0, 2); } draw_box(_mp_text_x, _mp_text_y + 1, _mp_text_x + 14, _mp_text_y + 3, BOX_THIN | BOX_INNER | BOX_INSET); } static void _mp_change(void) { if (_mp_setv) _mp_setv(_mpw[0].d.thumbbar.value); if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { if (_mp_setv_noplay) _mp_setv_noplay(_mpw[0].d.thumbbar.value); } _mp_active = 2; } static void _mp_finish(SCHISM_UNUSED void *ign) { if (_mp_active) { dialog_destroy_all(); _mp_active = 0; } } static void minipop_slide(int cv, const char *name, int min, int max, void (*setv)(int v), void (*setv_noplay)(int v), int midx, int midy) { if (_mp_active == 1) { _mp_active = 2; return; } _mp_text = name; _mp_text_x = midx - 9; _mp_text_y = midy - 2; _mp_setv = setv; _mp_setv_noplay = setv_noplay; widget_create_thumbbar(_mpw, midx - 8, midy, 13, 0, 0, 0, _mp_change, min, max); _mpw[0].d.thumbbar.value = CLAMP(cv, min, max); _mpw[0].depressed = 1; /* maybe it just needs some zoloft? */ dialog_create_custom(midx - 10, midy - 3, 20, 6, _mpw, 1, 0, _mp_draw, NULL); /* warp mouse to position of slider knob */ if (max == 0) max = 1; /* prevent division by zero */ video_warp_mouse( video_width()*((midx - 8)*8 + (cv - min)*96.0/(max - min) + 1)/640, video_height()*midy*8/400.0 + 4); _mp_active = 1; status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------------------------------------------- */ /* text input handler */ void handle_text_input(const char* text_input) { if (widget_handle_text_input(text_input)) return; if (!(status.dialog_type & DIALOG_BOX) && ACTIVE_PAGE.handle_text_input) ACTIVE_PAGE.handle_text_input(text_input); } /* --------------------------------------------------------------------------------------------------------- */ /* returns 1 if the key was handled */ static int handle_key_global(struct key_event * k) { int i, ins_mode; if (_mp_active == 2 && (k->mouse == MOUSE_CLICK && k->state == KEY_RELEASE)) { status.flags |= NEED_UPDATE; dialog_destroy_all(); _mp_active = 0; // eat it... return 1; } if ((!_mp_active) && k->state == KEY_PRESS && k->mouse == MOUSE_CLICK) { if (k->x >= 63 && k->x <= 77 && k->y >= 6 && k->y <= 7) { status.vis_style++; status.vis_style %= VIS_SENTINEL; status.flags |= NEED_UPDATE; return 1; } else if (k->y == 5 && k->x == 50) { minipop_slide(kbd_get_current_octave(), "Octave", 0, 8, kbd_set_current_octave, NULL, 50, 5); return 1; } else if (k->y == 4 && k->x >= 50 && k->x <= 52) { minipop_slide(song_get_current_speed(), "Speed", 1, 255, song_set_current_speed, song_set_initial_speed, 51, 4); return 1; } else if (k->y == 4 && k->x >= 54 && k->x <= 56) { minipop_slide(song_get_current_tempo(), "Tempo", 32, 255, song_set_current_tempo, song_set_initial_tempo, 55, 4); return 1; } else if (k->y == 3 && k->x >= 50 && k-> x <= 77) { if (page_is_instrument_list(status.current_page) || status.current_page == PAGE_SAMPLE_LIST || (!(status.flags & CLASSIC_MODE) && (status.current_page == PAGE_ORDERLIST_PANNING || status.current_page == PAGE_ORDERLIST_VOLUMES))) ins_mode = 0; else ins_mode = song_is_instrument_mode(); if (ins_mode) { minipop_slide(instrument_get_current(), "Instrument", status.current_page == PAGE_INSTRUMENT_LIST ? 1 : 0, 99 /* FIXME */, instrument_set, NULL, 58, 3); } else { minipop_slide(sample_get_current(), "Sample", status.current_page == PAGE_SAMPLE_LIST ? 1 : 0, 99 /* FIXME */, sample_set, NULL, 58, 3); } } else if (k->x >= 12 && k->x <= 18) { if (k->y == 7) { minipop_slide(get_current_row(), "Row", 0, song_get_rows_in_pattern(get_current_pattern()), set_current_row, NULL, 14, 7); return 1; } else if (k->y == 6) { minipop_slide(get_current_pattern(), "Pattern", 0, csf_get_num_patterns(current_song), set_current_pattern, NULL, 14, 6); return 1; } else if (k->y == 5) { minipop_slide(get_current_order(), "Order", 0, csf_get_num_orders(current_song), set_current_order, NULL, 14, 5); return 1; } } } else if ((!_mp_active) && k->mouse == MOUSE_DBLCLICK) { if (k->y == 4 && k->x >= 11 && k->x <= 28) { set_page(PAGE_SAVE_MODULE); return 1; } else if (k->y == 3 && k->x >= 11 && k->x <= 35) { set_page(PAGE_SONG_VARIABLES); return 1; } } /* shortcut */ if (k->mouse != MOUSE_NONE) { return 0; } /* first, check the truly global keys (the ones that still work if * a dialog's open) */ switch (k->sym) { case SCHISM_KEYSYM_RETURN: if ((k->mod & SCHISM_KEYMOD_CTRL) && k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_PRESS) return 1; toggle_display_fullscreen(); return 1; } break; case SCHISM_KEYSYM_m: if (k->mod & SCHISM_KEYMOD_CTRL) { if (k->state == KEY_RELEASE) return 1; video_mousecursor(MOUSE_CYCLE_STATE); return 1; } break; case SCHISM_KEYSYM_d: if (k->mod & SCHISM_KEYMOD_CTRL) { if (k->state == KEY_RELEASE) return 1; /* argh */ const int grabbed = !video_is_input_grabbed(); video_set_input_grabbed(grabbed); status_text_flash(grabbed ? "Mouse and keyboard grabbed, press Ctrl+D to release" : "Mouse and keyboard released"); return 1; } break; case SCHISM_KEYSYM_i: /* reset audio stuff? */ if (k->mod & SCHISM_KEYMOD_CTRL) { if (k->state == KEY_RELEASE) return 1; audio_reinit(NULL); return 1; } break; case SCHISM_KEYSYM_e: /* This should reset everything display-related. */ if (k->mod & SCHISM_KEYMOD_CTRL) { if (k->state == KEY_RELEASE) return 1; font_init(); status.flags |= NEED_UPDATE; return 1; } break; case SCHISM_KEYSYM_HOME: if (!(k->mod & SCHISM_KEYMOD_ALT)) break; if (status.flags & DISKWRITER_ACTIVE) break; if (k->state == KEY_RELEASE) return 0; kbd_set_current_octave(kbd_get_current_octave() - 1); return 1; case SCHISM_KEYSYM_END: if (!(k->mod & SCHISM_KEYMOD_ALT)) break; if (status.flags & DISKWRITER_ACTIVE) break; if (k->state == KEY_RELEASE) return 0; kbd_set_current_octave(kbd_get_current_octave() + 1); return 1; default: break; } /* next, if there's no dialog, check the rest of the keys */ if (status.flags & DISKWRITER_ACTIVE) return 0; switch (k->sym) { case SCHISM_KEYSYM_q: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) { if (k->mod & SCHISM_KEYMOD_SHIFT) schism_exit(0); show_exit_prompt(); } return 1; } break; case SCHISM_KEYSYM_n: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) new_song_dialog(); return 1; } break; case SCHISM_KEYSYM_g: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) show_song_timejump(); return 1; } break; case SCHISM_KEYSYM_p: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) show_song_length(); return 1; } break; case SCHISM_KEYSYM_F1: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_CONFIG); } else if (k->mod & SCHISM_KEYMOD_SHIFT) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(status.current_page == PAGE_MIDI ? PAGE_MIDI_OUTPUT : PAGE_MIDI); } else if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_HELP); } else { break; } return 1; case SCHISM_KEYSYM_F2: if (k->mod & SCHISM_KEYMOD_CTRL) { if (status.current_page == PAGE_PATTERN_EDITOR) { _mp_finish(NULL); if (k->state == KEY_PRESS && status.dialog_type == DIALOG_NONE) { pattern_editor_length_edit(); } return 1; } if (status.dialog_type != DIALOG_NONE) return 0; } else if (NO_MODIFIER(k->mod)) { if (status.current_page == PAGE_PATTERN_EDITOR) { if (k->state == KEY_PRESS) { if (status.dialog_type & DIALOG_MENU) { return 0; } else if (status.dialog_type != DIALOG_NONE) { dialog_yes_NULL(); status.flags |= NEED_UPDATE; } else { _mp_finish(NULL); pattern_editor_display_options(); } } } else { if (status.dialog_type != DIALOG_NONE) return 0; _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_PATTERN_EDITOR); } return 1; } break; case SCHISM_KEYSYM_F3: if (status.dialog_type != DIALOG_NONE) return 0; if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_SAMPLE_LIST); } else { _mp_finish(NULL); if (k->mod & SCHISM_KEYMOD_CTRL) set_page(PAGE_LIBRARY_SAMPLE); break; } return 1; case SCHISM_KEYSYM_F4: if (status.dialog_type != DIALOG_NONE) return 0; if (NO_MODIFIER(k->mod)) { if (status.current_page == PAGE_INSTRUMENT_LIST) return 0; _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_INSTRUMENT_LIST); } else { if (k->mod & SCHISM_KEYMOD_SHIFT) return 0; _mp_finish(NULL); if (k->mod & SCHISM_KEYMOD_CTRL) set_page(PAGE_LIBRARY_INSTRUMENT); break; } return 1; case SCHISM_KEYSYM_F5: if (k->mod & SCHISM_KEYMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) song_start(); } else if (k->mod & SCHISM_KEYMOD_SHIFT) { if (status.dialog_type != DIALOG_NONE) return 0; _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_PREFERENCES); } else if (NO_MODIFIER(k->mod)) { if (song_get_mode() == MODE_STOPPED || (song_get_mode() == MODE_SINGLE_STEP && status.current_page == PAGE_INFO)) { _mp_finish(NULL); if (k->state == KEY_PRESS) song_start(); } if (k->state == KEY_PRESS) { if (status.dialog_type != DIALOG_NONE) return 0; _mp_finish(NULL); set_page(PAGE_INFO); } } else { break; } return 1; case SCHISM_KEYSYM_F6: if (k->mod & SCHISM_KEYMOD_SHIFT) { _mp_finish(NULL); if (k->state == KEY_PRESS) song_start_at_order(get_current_order(), 0); } else if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) song_loop_pattern(get_current_pattern(), 0); } else { break; } return 1; case SCHISM_KEYSYM_F7: if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) play_song_from_mark(); } else { break; } return 1; case SCHISM_KEYSYM_F8: if (k->mod & SCHISM_KEYMOD_SHIFT) { if (k->state == KEY_PRESS) song_pause(); } else if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) song_stop(); status.flags |= NEED_UPDATE; } else { break; } return 1; case SCHISM_KEYSYM_F9: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & SCHISM_KEYMOD_SHIFT) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_MESSAGE); } else if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_LOAD_MODULE); } else { break; } return 1; case SCHISM_KEYSYM_l: case SCHISM_KEYSYM_r: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_RELEASE) set_page(PAGE_LOAD_MODULE); } else { break; } return 1; case SCHISM_KEYSYM_s: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_RELEASE) save_song_or_save_as(); } else { break; } return 1; case SCHISM_KEYSYM_w: /* Ctrl-W _IS_ in IT, and hands don't leave home row :) */ if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_RELEASE) set_page(PAGE_SAVE_MODULE); } else { break; } return 1; case SCHISM_KEYSYM_F10: if (status.dialog_type != DIALOG_NONE) return 0; if (k->mod & SCHISM_KEYMOD_ALT) break; if (k->mod & SCHISM_KEYMOD_CTRL) break; _mp_finish(NULL); if (k->mod & SCHISM_KEYMOD_SHIFT) { if (k->state == KEY_PRESS) set_page(PAGE_EXPORT_MODULE); } else { if (k->state == KEY_PRESS) set_page(PAGE_SAVE_MODULE); } return 1; case SCHISM_KEYSYM_F11: if (status.dialog_type != DIALOG_NONE) return 0; if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (status.current_page == PAGE_ORDERLIST_PANNING) { if (k->state == KEY_PRESS) set_page(PAGE_ORDERLIST_VOLUMES); } else { if (k->state == KEY_PRESS) set_page(PAGE_ORDERLIST_PANNING); } } else if (k->mod & SCHISM_KEYMOD_CTRL) { if (k->state == KEY_PRESS) { _mp_finish(NULL); if (status.current_page == PAGE_LOG) { show_about(); } else { set_page(PAGE_LOG); } } } else if (k->state == KEY_PRESS && (k->mod & SCHISM_KEYMOD_ALT)) { _mp_finish(NULL); if (song_toggle_orderlist_locked()) status_text_flash("Order list locked"); else status_text_flash("Order list unlocked"); } else { break; } return 1; case SCHISM_KEYSYM_F12: if (status.dialog_type != DIALOG_NONE) return 0; if ((k->mod & SCHISM_KEYMOD_ALT) && status.current_page == PAGE_INFO) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_WATERFALL); } else if (k->mod & SCHISM_KEYMOD_CTRL) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_PALETTE_EDITOR); } else if (k->mod & SCHISM_KEYMOD_SHIFT) { _mp_finish(NULL); if (k->state == KEY_PRESS) { fontedit_return_page = status.current_page; set_page(PAGE_FONT_EDIT); } } else if (NO_MODIFIER(k->mod)) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_SONG_VARIABLES); } else { break; } return 1; /* hack alert */ case SCHISM_KEYSYM_f: if (!(k->mod & SCHISM_KEYMOD_CTRL)) return 0; /* fall through */ case SCHISM_KEYSYM_SCROLLLOCK: if (status.dialog_type != DIALOG_NONE) return 0; _mp_finish(NULL); if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_PRESS) { midi_flags ^= (MIDI_DISABLE_RECORD); status_text_flash("MIDI Input %s", (midi_flags & MIDI_DISABLE_RECORD) ? "Disabled" : "Enabled"); } return 1; } else { /* os x steals plain scroll lock for brightness, * so catch ctrl+scroll lock here as well */ if (k->state == KEY_PRESS) { midi_playback_tracing = (playback_tracing = !playback_tracing); status_text_flash("Playback tracing %s", (playback_tracing ? "enabled" : "disabled")); } return 1; } return 1; case SCHISM_KEYSYM_PAUSE: if ((k->mod & SCHISM_KEYMOD_LSHIFT) && (k->mod & SCHISM_KEYMOD_LALT) && (k->mod & SCHISM_KEYMOD_RALT) && (k->mod & SCHISM_KEYMOD_RCTRL)) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_TIME_INFORMATION); return 1; } return 0; case SCHISM_KEYSYM_t: if ((k->mod & SCHISM_KEYMOD_CTRL) && (k->mod & SCHISM_KEYMOD_ALT)) { _mp_finish(NULL); if (k->state == KEY_PRESS) set_page(PAGE_TIME_INFORMATION); return 1; } return 0; default: if (status.dialog_type != DIALOG_NONE) return 0; break; } /* got a bit ugly here, sorry */ if (k->mod & SCHISM_KEYMOD_ALT) { switch (k->sym) { case SCHISM_KEYSYM_F1: i = 0; break; case SCHISM_KEYSYM_F2: i = 1; break; case SCHISM_KEYSYM_F3: i = 2; break; case SCHISM_KEYSYM_F4: i = 3; break; case SCHISM_KEYSYM_F5: i = 4; break; case SCHISM_KEYSYM_F6: i = 5; break; case SCHISM_KEYSYM_F7: i = 6; break; case SCHISM_KEYSYM_F8: i = 7; break; default: return 0; }; if (k->state == KEY_RELEASE) return 1; song_toggle_channel_mute(i); status.flags |= NEED_UPDATE; return 1; } /* oh well */ return 0; } static int _handle_ime(struct key_event *k) { int c, m; static int alt_numpad = 0; static int alt_numpad_c = 0; static int digraph_n = 0; static int digraph_c = 0; static uint32_t cs_unicode = 0; static int cs_unicode_c = 0; if (ACTIVE_PAGE.selected_widget > -1 && ACTIVE_PAGE.selected_widget < ACTIVE_PAGE.total_widgets && ACTIVE_PAGE.widgets[ACTIVE_PAGE.selected_widget].accept_text) { if (digraph_n == -1 && k->state == KEY_RELEASE) { digraph_n = 0; } else if (!(status.flags & CLASSIC_MODE) && (k->sym == SCHISM_KEYSYM_LCTRL || k->sym == SCHISM_KEYSYM_RCTRL)) { if (k->state == KEY_RELEASE && digraph_n >= 0) { digraph_n++; if (digraph_n >= 2) status_text_flash_bios("Enter digraph:"); } } else if (k->sym == SCHISM_KEYSYM_LSHIFT || k->sym == SCHISM_KEYSYM_RSHIFT) { /* do nothing */ } else if (!NO_MODIFIER((k->mod&~SCHISM_KEYMOD_SHIFT)) || (c=(k->text) ? ((uint8_t)*k->text) : k->sym) == 0 || digraph_n < 2) { if (k->state == KEY_PRESS && k->mouse == MOUSE_NONE) { if (digraph_n > 0) status_text_flash(" "); digraph_n = -1; } } else if (digraph_n >= 2) { if (k->state == KEY_RELEASE) return 1; if (!digraph_c) { digraph_c = c; status_text_flash_bios("Enter digraph: %c", c); } else { uint8_t digraph_input[2] = {0}; digraph_input[0] = char_digraph(digraph_c, c); if (digraph_input[0]) { status_text_flash_bios("Enter digraph: %c%c -> %c", digraph_c, c, digraph_input[0]); } else { status_text_flash_bios("Enter digraph: %c%c -> INVALID", digraph_c, c); } digraph_n = digraph_c = 0; if (*digraph_input) handle_text_input((const char *)digraph_input); } return 1; } else { if (digraph_n > 0) status_text_flash(" "); digraph_n = 0; } /* ctrl+shift -> unicode character */ if (k->sym==SCHISM_KEYSYM_LCTRL || k->sym==SCHISM_KEYSYM_RCTRL || k->sym==SCHISM_KEYSYM_LSHIFT || k->sym==SCHISM_KEYSYM_RSHIFT) { if (k->state == KEY_RELEASE) { if (cs_unicode_c > 0) { uint8_t unicode[2] = {0}; unicode[0] = (uint8_t)(char_unicode_to_cp437(cs_unicode)); if (unicode[0] >= 32) { status_text_flash_bios("Enter Unicode: U+%04" PRIX32 " -> %" PRIu8, cs_unicode, unicode[0]); handle_text_input((const char *)unicode); } else { status_text_flash_bios("Enter Unicode: U+%04" PRIX32 " -> INVALID", cs_unicode); } cs_unicode = cs_unicode_c = 0; alt_numpad = alt_numpad_c = 0; digraph_n = digraph_c = 0; } return 1; } } else if (!(status.flags & CLASSIC_MODE) && (k->mod & SCHISM_KEYMOD_CTRL) && (k->mod & SCHISM_KEYMOD_SHIFT)) { if (cs_unicode_c >= 0) { /* bleh... */ m = k->mod; k->mod = 0; c = kbd_char_to_hex(k); k->mod = m; if (c == -1) { cs_unicode = cs_unicode_c = -1; } else { if (k->state == KEY_PRESS) return 1; cs_unicode *= 16; cs_unicode += c; cs_unicode_c++; digraph_n = digraph_c = 0; status_text_flash_bios("Enter Unicode: U+%04X", cs_unicode); return 1; } } } else { if (k->sym==SCHISM_KEYSYM_LCTRL || k->sym==SCHISM_KEYSYM_RCTRL || k->sym==SCHISM_KEYSYM_LSHIFT || k->sym==SCHISM_KEYSYM_RSHIFT) { return 1; } cs_unicode = cs_unicode_c = 0; } /* alt+numpad -> char number */ if (k->sym == SCHISM_KEYSYM_LALT || k->sym == SCHISM_KEYSYM_RALT || k->sym == SCHISM_KEYSYM_LGUI || k->sym == SCHISM_KEYSYM_RGUI) { if (k->state == KEY_RELEASE && alt_numpad_c > 0 && (alt_numpad & 255) > 0) {\ if (alt_numpad < 32) return 0; uint8_t unicode[2] = {0}; unicode[0] = (alt_numpad & 255); if (!(status.flags & CLASSIC_MODE)) status_text_flash_bios("Enter DOS/ASCII: %d -> %c", (int)unicode[0], (int)unicode[0]); handle_text_input((const char *)unicode); alt_numpad = alt_numpad_c = 0; digraph_n = digraph_c = 0; cs_unicode = cs_unicode_c = 0; return 1; } } else if (k->mod & SCHISM_KEYMOD_ALT && !(k->mod & (SCHISM_KEYMOD_CTRL|SCHISM_KEYMOD_SHIFT))) { if (alt_numpad_c >= 0) { m = k->mod; k->mod = 0; c = numeric_key_event(k, 1); /* kp only */ k->mod = m; if (c == -1 || c > 9) { alt_numpad = alt_numpad_c = -1; } else { if (k->state == KEY_PRESS) return 1; alt_numpad *= 10; alt_numpad += c; alt_numpad_c++; if (!(status.flags & CLASSIC_MODE)) status_text_flash_bios("Enter DOS/ASCII: %d", (int)alt_numpad); return 1; } } } else { alt_numpad = alt_numpad_c = 0; } } else { cs_unicode = cs_unicode_c = 0; alt_numpad = alt_numpad_c = 0; digraph_n = digraph_c = 0; } return 0; } /* this is the important one */ void handle_key(struct key_event *k) { if (_handle_ime(k)) return; /* okay... */ if (!(status.flags & DISKWRITER_ACTIVE) && ACTIVE_PAGE.pre_handle_key) { if (ACTIVE_PAGE.pre_handle_key(k)) return; } if (handle_key_global(k)) return; if (!(status.flags & DISKWRITER_ACTIVE) && menu_handle_key(k)) return; if (widget_handle_key(k)) return; /* now check a couple other keys. */ switch (k->sym) { case SCHISM_KEYSYM_LEFT: if (k->state == KEY_RELEASE) return; if (status.flags & DISKWRITER_ACTIVE) return; if ((k->mod & SCHISM_KEYMOD_CTRL) && status.current_page != PAGE_PATTERN_EDITOR) { _mp_finish(NULL); if (song_get_mode() == MODE_PLAYING) song_set_current_order(song_get_current_order() - 1); return; } break; case SCHISM_KEYSYM_RIGHT: if (k->state == KEY_RELEASE) return; if (status.flags & DISKWRITER_ACTIVE) return; if ((k->mod & SCHISM_KEYMOD_CTRL) && status.current_page != PAGE_PATTERN_EDITOR) { _mp_finish(NULL); if (song_get_mode() == MODE_PLAYING) song_set_current_order(song_get_current_order() + 1); return; } break; case SCHISM_KEYSYM_ESCAPE: /* TODO | Page key handlers should return true/false depending on if the key was handled TODO | (same as with other handlers), and the escape key check should go *after* the TODO | page gets a chance to grab it. This way, the load sample page can switch back TODO | to the sample list on escape like it's supposed to. (The status.current_page TODO | checks above won't be necessary, either.) */ if (NO_MODIFIER(k->mod) && status.dialog_type == DIALOG_NONE && status.current_page != PAGE_LOAD_SAMPLE && status.current_page != PAGE_LOAD_INSTRUMENT) { if (k->state == KEY_RELEASE) return; if (_mp_active) { _mp_finish(NULL); return; } menu_show(); return; } break; case SCHISM_KEYSYM_SLASH: if (k->state == KEY_RELEASE) return; if (status.flags & DISKWRITER_ACTIVE) return; kbd_set_current_octave(kbd_get_current_octave() - 1); break; case SCHISM_KEYSYM_ASTERISK: if (k->state == KEY_RELEASE) return; if (status.flags & DISKWRITER_ACTIVE) return; kbd_set_current_octave(kbd_get_current_octave() + 1); break; case SCHISM_KEYSYM_LEFTBRACKET: if (k->state == KEY_RELEASE) break; if (status.flags & DISKWRITER_ACTIVE) return; if (k->mod & SCHISM_KEYMOD_SHIFT) { song_set_current_speed(song_get_current_speed() - 1); status_text_flash("Speed set to %d frames per row", song_get_current_speed()); if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { song_set_initial_speed(song_get_current_speed()); } } else if ((k->mod & SCHISM_KEYMOD_CTRL) && !(status.flags & CLASSIC_MODE)) { song_set_current_tempo(song_get_current_tempo() - 1); status_text_flash("Tempo set to %d frames per row", song_get_current_tempo()); if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { song_set_initial_tempo(song_get_current_tempo()); } } else if (NO_MODIFIER(k->mod)) { song_set_current_global_volume(song_get_current_global_volume() - 1); status_text_flash("Global volume set to %d", song_get_current_global_volume()); if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { song_set_initial_global_volume(song_get_current_global_volume()); } } return; case SCHISM_KEYSYM_RIGHTBRACKET: if (k->state == KEY_RELEASE) break; if (status.flags & DISKWRITER_ACTIVE) return; if (k->mod & SCHISM_KEYMOD_SHIFT) { song_set_current_speed(song_get_current_speed() + 1); status_text_flash("Speed set to %d frames per row", song_get_current_speed()); if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { song_set_initial_speed(song_get_current_speed()); } } else if ((k->mod & SCHISM_KEYMOD_CTRL) && !(status.flags & CLASSIC_MODE)) { song_set_current_tempo(song_get_current_tempo() + 1); status_text_flash("Tempo set to %d frames per row", song_get_current_tempo()); if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { song_set_initial_tempo(song_get_current_tempo()); } } else if (NO_MODIFIER(k->mod)) { song_set_current_global_volume(song_get_current_global_volume() + 1); status_text_flash("Global volume set to %d", song_get_current_global_volume()); if (!(song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP))) { song_set_initial_global_volume(song_get_current_global_volume()); } } return; default: break; } /* and if we STILL didn't handle the key, pass it to the page. * (or dialog, if one's active) */ if (status.dialog_type & DIALOG_BOX) { dialog_handle_key(k); } else { if (status.flags & DISKWRITER_ACTIVE) return; if (ACTIVE_PAGE.handle_key) ACTIVE_PAGE.handle_key(k); } } /* --------------------------------------------------------------------- */ static void draw_top_info_const(void) { int n, tl, br; if (status.flags & INVERTED_PALETTE) { tl = 3; br = 1; } else { tl = 1; br = 3; } draw_text(schism_banner(status.flags & CLASSIC_MODE), (80 - strlen(schism_banner(status.flags & CLASSIC_MODE))) / 2, 1, 0, 2); draw_text("Song Name", 2, 3, 0, 2); draw_text("File Name", 2, 4, 0, 2); draw_text("Order", 6, 5, 0, 2); draw_text("Pattern", 4, 6, 0, 2); draw_text("Row", 8, 7, 0, 2); draw_text("Speed/Tempo", 38, 4, 0, 2); draw_text("Octave", 43, 5, 0, 2); draw_text("F1...Help F9.....Load", 21, 6, 0, 2); draw_text("ESC..Main Menu F5/F8..Play / Stop", 21, 7, 0, 2); /* the neat-looking (but incredibly ugly to draw) borders */ draw_char(128, 30, 4, br, 2); draw_char(128, 57, 4, br, 2); draw_char(128, 19, 5, br, 2); draw_char(128, 51, 5, br, 2); draw_char(129, 36, 4, br, 2); draw_char(129, 50, 6, br, 2); draw_char(129, 17, 8, br, 2); draw_char(129, 18, 8, br, 2); draw_char(131, 37, 3, br, 2); draw_char(131, 78, 3, br, 2); draw_char(131, 19, 6, br, 2); draw_char(131, 19, 7, br, 2); draw_char(132, 49, 3, tl, 2); draw_char(132, 49, 4, tl, 2); draw_char(132, 49, 5, tl, 2); draw_char(134, 75, 2, tl, 2); draw_char(134, 76, 2, tl, 2); draw_char(134, 77, 2, tl, 2); draw_char(136, 37, 4, br, 2); draw_char(136, 78, 4, br, 2); draw_char(136, 30, 5, br, 2); draw_char(136, 57, 5, br, 2); draw_char(136, 51, 6, br, 2); draw_char(136, 19, 8, br, 2); draw_char(137, 49, 6, br, 2); draw_char(137, 11, 8, br, 2); draw_char(138, 37, 2, tl, 2); draw_char(138, 78, 2, tl, 2); draw_char(139, 11, 2, tl, 2); draw_char(139, 49, 2, tl, 2); for (n = 0; n < 5; n++) { draw_char(132, 11, 3 + n, tl, 2); draw_char(129, 12 + n, 8, br, 2); draw_char(134, 12 + n, 2, tl, 2); draw_char(129, 20 + n, 5, br, 2); draw_char(129, 31 + n, 4, br, 2); draw_char(134, 32 + n, 2, tl, 2); draw_char(134, 50 + n, 2, tl, 2); draw_char(129, 52 + n, 5, br, 2); draw_char(129, 58 + n, 4, br, 2); draw_char(134, 70 + n, 2, tl, 2); } for (; n < 10; n++) { draw_char(134, 12 + n, 2, tl, 2); draw_char(129, 20 + n, 5, br, 2); draw_char(134, 50 + n, 2, tl, 2); draw_char(129, 58 + n, 4, br, 2); } for (; n < 20; n++) { draw_char(134, 12 + n, 2, tl, 2); draw_char(134, 50 + n, 2, tl, 2); draw_char(129, 58 + n, 4, br, 2); } draw_text("Time", 63, 9, 0, 2); draw_char('/', 15, 5, 1, 0); draw_char('/', 15, 6, 1, 0); draw_char('/', 15, 7, 1, 0); draw_char('/', 53, 4, 1, 0); draw_char(':', 52, 3, 7, 0); } /* --------------------------------------------------------------------- */ void update_current_instrument(void) { int ins_mode, n; char *name = NULL; char buf[4]; if (page_is_instrument_list(status.current_page) || status.current_page == PAGE_SAMPLE_LIST || status.current_page == PAGE_LOAD_SAMPLE || status.current_page == PAGE_LIBRARY_SAMPLE || (!(status.flags & CLASSIC_MODE) && (status.current_page == PAGE_ORDERLIST_PANNING || status.current_page == PAGE_ORDERLIST_VOLUMES))) ins_mode = 0; else ins_mode = song_is_instrument_mode(); if (ins_mode) { draw_text("Instrument", 39, 3, 0, 2); n = instrument_get_current(); if (n > 0) name = song_get_instrument(n)->name; } else { draw_text(" Sample", 39, 3, 0, 2); n = sample_get_current(); if (n > 0) name = song_get_sample(n)->name; } if (n > 0) { draw_text(str_from_num99(n, buf), 50, 3, 5, 0); draw_text_len(name, 25, 53, 3, 5, 0); } else { draw_text("..", 50, 3, 5, 0); draw_text(".........................", 53, 3, 5, 0); } } static void redraw_top_info(void) { char buf[8]; update_current_instrument(); draw_text_len(song_get_basename(), 18, 12, 4, 5, 0); draw_text_len(current_song->title, 25, 12, 3, 5, 0); if ((status.flags & (CLASSIC_MODE | SONG_NEEDS_SAVE)) == SONG_NEEDS_SAVE) draw_char('+', 29, 4, 4, 0); update_current_order(); update_current_pattern(); update_current_row(); draw_text(str_from_num(3, song_get_current_speed(), buf), 50, 4, 5, 0); draw_text(str_from_num(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, DEFAULT_FG, 0); } static int _vis_virgin = 1; static struct vgamem_overlay vis_overlay = { 63, 6, 77, 7, NULL, 0, 0, 0, }; static void vis_fft(void) { int i; int32_t y; /*this is the size of vis_overlay.width*/ unsigned char outfft[120]; if (_vis_virgin) { vgamem_ovl_alloc(&vis_overlay); _vis_virgin = 0; } _draw_vis_box(); song_lock_audio(); vgamem_ovl_clear(&vis_overlay,0); fft_get_columns(120, outfft, 0); for (i = 0; i < 120; i++) { y = outfft[i]; /* reduce range */ y = rshift_signed(y, 3); if (y > 15) y = 15; if (y > 0) vgamem_ovl_drawline(&vis_overlay, i, 15 - y, i, 15, 5); } vgamem_ovl_apply(&vis_overlay); song_unlock_audio(); } static void vis_oscilloscope(void) { if (_vis_virgin) { vgamem_ovl_alloc(&vis_overlay); _vis_virgin = 0; } _draw_vis_box(); song_lock_audio(); int out_chns = (status.vis_style == VIS_MONOSCOPE) ? 1 : audio_output_channels; switch (audio_output_bits) { case 8: draw_sample_data_rect_8(&vis_overlay,(void *)audio_buffer, audio_buffer_samples, audio_output_channels,out_chns); break; case 16: draw_sample_data_rect_16(&vis_overlay,audio_buffer, audio_buffer_samples, audio_output_channels,out_chns); break; case 32: draw_sample_data_rect_32(&vis_overlay,(void *)audio_buffer, audio_buffer_samples, audio_output_channels,out_chns); break; default: break; } song_unlock_audio(); } static void vis_vu_meter(void) { int left, right; song_get_vu_meter(&left, &right); left >>= 1; right >>= 1; _draw_vis_box(); draw_vu_meter(63, 6, 15, left, 5, 4); draw_vu_meter(63, 7, 15, right, 5, 4); } static void vis_fakemem(void) { char buf[32]; unsigned int conv; unsigned int ems; if (status.flags & CLASSIC_MODE) { ems = memused_ems(); if (ems > 67108864) ems = 0; else ems = 67108864 - ems; conv = memused_lowmem(); if (conv > 524288) conv = 0; else conv = 524288 - conv; conv >>= 10; ems >>= 10; sprintf(buf, "FreeMem %uk", conv); draw_text(buf, 63, 6, 0, 2); sprintf(buf, "FreeEMS %uk", ems); draw_text(buf, 63, 7, 0, 2); } else { sprintf(buf, " Song %uk", (unsigned)( (memused_patterns() +memused_instruments() +memused_songmessage()) >> 10)); draw_text(buf, 63, 6, 0, 2); sprintf(buf, "Samples %uk", (unsigned)(memused_samples() >> 10)); draw_text(buf, 63, 7, 0, 2); } } static inline void draw_vis(void) { if (status.flags & CLASSIC_MODE) { /* classic mode requires fakemem display */ vis_fakemem(); return; } switch (status.vis_style) { case VIS_FAKEMEM: vis_fakemem(); break; case VIS_OSCILLOSCOPE: case VIS_MONOSCOPE: vis_oscilloscope(); break; case VIS_VU_METER: vis_vu_meter(); break; case VIS_FFT: vis_fft(); break; default: case VIS_OFF: break; } } /* this completely redraws everything. */ void redraw_screen(void) { int n; char buf[4]; if (!ACTIVE_PAGE.draw_full) { draw_fill_chars(0,0,79,49, DEFAULT_FG,2); /* border around the whole screen */ draw_char(128, 0, 0, 3, 2); for (n = 79; n > 49; n--) draw_char(129, n, 0, 3, 2); do { draw_char(129, n, 0, 3, 2); draw_char(131, 0, n, 3, 2); } while (--n); draw_top_info_const(); redraw_top_info(); } if (!ACTIVE_PAGE.draw_full) { draw_vis(); draw_time(); draw_text(str_from_num(3, song_get_current_speed(), buf), 50, 4, 5, 0); draw_text(str_from_num(3, song_get_current_tempo(), buf), 54, 4, 5, 0); status_text_redraw(); } draw_page(); } /* important :) */ void playback_update(void) { /* the order here is significant -- check_time has side effects */ if (check_time() || song_get_mode()) status.flags |= NEED_UPDATE; if (ACTIVE_PAGE.playback_update) ACTIVE_PAGE.playback_update(); } /* --------------------------------------------------------------------- */ static void _set_from_f3(void) { switch (status.previous_page) { case PAGE_ORDERLIST_PANNING: case PAGE_ORDERLIST_VOLUMES: if (status.flags & CLASSIC_MODE) return; // XXX is this correct? SCHISM_FALLTHROUGH; case PAGE_SAMPLE_LIST: if (song_is_instrument_mode()) instrument_synchronize_to_sample(); else instrument_set(sample_get_current()); }; } static void _set_from_f4(void) { switch (status.previous_page) { case PAGE_ORDERLIST_PANNING: case PAGE_ORDERLIST_VOLUMES: if (status.flags & CLASSIC_MODE) break; case PAGE_SAMPLE_LIST: case PAGE_LOAD_SAMPLE: case PAGE_LIBRARY_SAMPLE: return; /* * storlek says pattern editor syncs... case PAGE_PATTERN_EDITOR: */ }; if (song_is_instrument_mode()) { sample_synchronize_to_instrument(); } } void set_page(int new_page) { int prev_page = status.current_page; if (new_page != prev_page) status.previous_page = prev_page; status.current_page = new_page; _set_from_f3(); _set_from_f4(); if (new_page != PAGE_HELP) status.current_help_index = ACTIVE_PAGE.help_index; if (status.dialog_type & DIALOG_MENU) { menu_hide(); } else if (status.dialog_type != DIALOG_NONE) { return; } /* update the pointers */ widgets = ACTIVE_PAGE.widgets; selected_widget = &(ACTIVE_PAGE.selected_widget); total_widgets = &(ACTIVE_PAGE.total_widgets); if (ACTIVE_PAGE.set_page) ACTIVE_PAGE.set_page(); status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ void load_pages(void) { blank_load_page(pages + PAGE_BLANK); help_load_page(pages + PAGE_HELP); pattern_editor_load_page(pages + PAGE_PATTERN_EDITOR); sample_list_load_page(pages + PAGE_SAMPLE_LIST); instrument_list_general_load_page(pages + PAGE_INSTRUMENT_LIST_GENERAL); instrument_list_volume_load_page(pages + PAGE_INSTRUMENT_LIST_VOLUME); instrument_list_panning_load_page(pages + PAGE_INSTRUMENT_LIST_PANNING); instrument_list_pitch_load_page(pages + PAGE_INSTRUMENT_LIST_PITCH); info_load_page(pages + PAGE_INFO); preferences_load_page(pages + PAGE_PREFERENCES); midi_load_page(pages + PAGE_MIDI); midiout_load_page(pages + PAGE_MIDI_OUTPUT); fontedit_load_page(pages + PAGE_FONT_EDIT); load_module_load_page(pages + PAGE_LOAD_MODULE); save_module_load_page(pages + PAGE_SAVE_MODULE, 0); orderpan_load_page(pages + PAGE_ORDERLIST_PANNING); ordervol_load_page(pages + PAGE_ORDERLIST_VOLUMES); song_vars_load_page(pages + PAGE_SONG_VARIABLES); palette_load_page(pages + PAGE_PALETTE_EDITOR); message_load_page(pages + PAGE_MESSAGE); log_load_page(pages + PAGE_LOG); load_sample_load_page(pages + PAGE_LOAD_SAMPLE); library_sample_load_page(pages + PAGE_LIBRARY_SAMPLE); load_instrument_load_page(pages + PAGE_LOAD_INSTRUMENT); library_instrument_load_page(pages + PAGE_LIBRARY_INSTRUMENT); waterfall_load_page(pages + PAGE_WATERFALL); about_load_page(pages+PAGE_ABOUT); config_load_page(pages + PAGE_CONFIG); save_module_load_page(pages + PAGE_EXPORT_MODULE, 1); timeinfo_load_page(pages + PAGE_TIME_INFORMATION); widgets = pages[PAGE_BLANK].widgets; selected_widget = &(pages[PAGE_BLANK].selected_widget); total_widgets = &(pages[PAGE_BLANK].total_widgets); } /* --------------------------------------------------------------------- */ /* this function's name sucks, but I don't know what else to call it. */ void main_song_changed_cb(void) { int n; /* perhaps this should be in page_patedit.c? */ set_current_order(0); n = current_song->orderlist[0]; if (n > 199) n = 0; set_current_pattern(n); set_current_row(0); song_save_channel_states(); for (n = ARRAY_SIZE(pages) - 1; n >= 0; n--) { if (pages[n].song_changed_cb) pages[n].song_changed_cb(); } /* TODO | print some message like "new song created" if there's * TODO | no filename, and thus no file. (but DON'T print it the * TODO | very first time this is called) */ status.flags |= NEED_UPDATE; memused_songchanged(); } /* --------------------------------------------------------------------- */ /* not sure where else to toss this crap */ static void savecheck(void (*ok)(void *data), void (*cancel)(void *data), void *data) { if (status.flags & SONG_NEEDS_SAVE) { dialog_create(DIALOG_OK_CANCEL, "Current module not saved. Proceed?", ok, cancel, 1, data); } else { ok(data); } } static void exit_ok_confirm(SCHISM_UNUSED void *data) { schism_exit(0); } static void exit_ok(SCHISM_UNUSED void *data) { savecheck(exit_ok_confirm, NULL, NULL); } static void real_load_ok(void *filename) { if (song_load_unchecked(filename)) { set_page((song_get_mode() == MODE_PLAYING) ? PAGE_INFO : PAGE_LOG); } else { set_page(PAGE_LOG); } free(filename); } void song_load(const char *filename) { savecheck(real_load_ok, free, str_dup(filename)); } void show_exit_prompt(void) { /* This used to kill all open dialogs, but that doesn't seem to be necessary. Do keep in mind though, a dialog *might* exist when this function is called (for example, if the WM sends a close request). */ if (status.current_page == PAGE_ABOUT) { /* haven't even started up yet; don't bother confirming */ schism_exit(0); } else if (status.current_page == PAGE_FONT_EDIT) { if (status.flags & STARTUP_FONTEDIT) { dialog_create(DIALOG_OK_CANCEL, "Exit Font Editor?", exit_ok_confirm, NULL, 0, NULL); } else { /* don't ask, just go away */ dialog_destroy_all(); set_page(fontedit_return_page); } } else if (status.dialog_type != DIALOG_OK_CANCEL) { /* don't draw an exit prompt on top of an existing one */ dialog_create(DIALOG_OK_CANCEL, ((status.flags & CLASSIC_MODE) ? "Exit Impulse Tracker?" : "Exit Schism Tracker?"), exit_ok, NULL, 0, NULL); } } static struct widget _timejump_widgets[4]; static int _tj_num1 = 0, _tj_num2 = 0; static int _timejump_keyh(struct key_event *k) { if (k->sym == SCHISM_KEYSYM_BACKSPACE) { if (*selected_widget == 1 && _timejump_widgets[1].d.numentry.value == 0) { if (k->state == KEY_RELEASE) widget_change_focus_to(0); return 1; } } if (k->sym == SCHISM_KEYSYM_COLON || k->sym == SCHISM_KEYSYM_SEMICOLON) { if (k->state == KEY_RELEASE) { if (*selected_widget == 0) { widget_change_focus_to(1); } } return 1; } return 0; } static void _timejump_draw(void) { draw_text("Jump to time:", 30, 26, 0, 2); draw_char(':', 46, 26, 3, 0); draw_box(43, 25, 49, 27, BOX_THIN | BOX_INNER | BOX_INSET); } static void _timejump_ok(SCHISM_UNUSED void *ign) { unsigned long sec; int no, np, nr; sec = (_timejump_widgets[0].d.numentry.value * 60) + _timejump_widgets[1].d.numentry.value; song_get_at_time(sec, &no, &nr); set_current_order(no); np = current_song->orderlist[no]; if (np < 200) { set_current_pattern(np); set_current_row(nr); set_page(PAGE_PATTERN_EDITOR); } } void show_song_timejump(void) { struct dialog *d; _tj_num1 = _tj_num2 = 0; widget_create_numentry(_timejump_widgets+0, 44, 26, 2, 0, 2, 1, NULL, 0, 21, &_tj_num1); widget_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; widget_create_button(_timejump_widgets+2, 30, 29, 8, 0, 2, 2, 3, 3, (void(*)(void))_timejump_ok, "OK", 4); widget_create_button(_timejump_widgets+3, 42, 29, 8, 1, 3, 3, 3, 0, dialog_cancel_NULL, "Cancel", 2); d = dialog_create_custom(26, 24, 30, 8, _timejump_widgets, 4, 0, _timejump_draw, NULL); d->handle_key = _timejump_keyh; d->action_yes = _timejump_ok; } void show_length_dialog(const char *label, unsigned int length) { char *buf; if (asprintf(&buf, "%s: %3u:%02u:%02u", label, length / 3600, (length / 60) % 60, length % 60) == -1) { perror("asprintf"); return; } dialog_create(DIALOG_OK, buf, free, free, 0, buf); } void show_song_length(void) { show_length_dialog("Total song time", csf_get_length(current_song)); } /* FIXME this is an illogical place to put this but whatever, i just want to get the frigging thing to build */ void set_previous_instrument(void) { if (song_is_instrument_mode()) instrument_set(instrument_get_current() - 1); else sample_set(sample_get_current() - 1); } void set_next_instrument(void) { if (song_is_instrument_mode()) instrument_set(instrument_get_current() + 1); else sample_set(sample_get_current() + 1); } schismtracker-20250313/schism/page_about.c000066400000000000000000000136501476471630300203750ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "vgamem.h" #include "it.h" #include "page.h" #include "video.h" #include "song.h" #include "version.h" #include "dialog.h" #include "widget.h" #include "keyboard.h" /* Eventual TODO: draw the pattern data in the Schism logo in a different color than the words */ #include "auto/logoit.h" #include "auto/logoschism.h" static int fake_driver = 0; static struct logo_data { uint32_t *pixels; int width; int height; } it_logo = {0}, schism_logo = {0}; static struct widget widgets_about[1]; static struct vgamem_overlay logo_image = { 23, 17, 58, 24, NULL, 0, 0, 0, }; static int _fixup_ignore_globals(struct key_event *k) { if (k->mouse && k->y > 20) return 0; switch (k->sym) { case SCHISM_KEYSYM_LEFT: case SCHISM_KEYSYM_RIGHT: case SCHISM_KEYSYM_DOWN: case SCHISM_KEYSYM_UP: case SCHISM_KEYSYM_TAB: case SCHISM_KEYSYM_RETURN: case SCHISM_KEYSYM_ESCAPE: /* use default handler */ return 0; case SCHISM_KEYSYM_F2: case SCHISM_KEYSYM_F5: case SCHISM_KEYSYM_F9: case SCHISM_KEYSYM_F10: // Ctrl + these keys does not lead to a new screen if (k->mod & SCHISM_KEYMOD_CTRL) break; // Fall through. case SCHISM_KEYSYM_F1: case SCHISM_KEYSYM_F3: case SCHISM_KEYSYM_F4: case SCHISM_KEYSYM_F11: case SCHISM_KEYSYM_F12: // Ignore Alt and so on. if (k->mod & (SCHISM_KEYMOD_ALT | SCHISM_KEYMOD_SHIFT)) break; dialog_destroy(); return 0; default: break; } /* this way, we can't pull up help here */ return 1; } static void _draw_full(void) { draw_fill_chars(0,0,79,49,DEFAULT_FG,0); } void about_load_page(struct page *page) { page->title = ""; page->total_widgets = 0; page->widgets = NULL; page->pre_handle_key = _fixup_ignore_globals; page->help_index = HELP_COPYRIGHT; page->draw_full = _draw_full; page->set_page = show_about; } static void about_close(SCHISM_UNUSED void *data) { if (status.current_page == PAGE_ABOUT) set_page(PAGE_LOAD_MODULE); status.flags |= NEED_UPDATE; } static void about_draw_const(void) { char buf[81]; if (status.current_page == PAGE_ABOUT) { /* redraw outer part */ draw_box(11,16, 68, 34, BOX_THIN | BOX_OUTER | BOX_FLAT_DARK); } if (status.flags & CLASSIC_MODE) { draw_box(25,25, 56, 30, BOX_THIN | BOX_OUTER | BOX_FLAT_DARK); draw_text("Sound Card Setup", 32, 26, 0, 2); if (strcasecmp(song_audio_driver(), "dummy") == 0) { draw_text("No sound card detected", 29, 28, 0, 2); } else { switch (fake_driver) { case 0: draw_text("Sound Blaster 16 detected", 26, 28, 0, 2); draw_text("Port 220h, IRQ 7, DMA 5", 26, 29, 0, 2); break; case 1: /* FIXME: The GUS driver displays the memory settings a bit differently from the SB. If we're "supporting" it, we should probably keep the rest of the UI consistent with our choice. (Also: no love for the AWE cards?) Alternately, it would be totally awesome to probe the system for the actual name and parameters of the card in use :) */ draw_text("Gravis UltraSound detected", 26, 28, 0, 2); draw_text("Port 240h, IRQ 5, 1024k RAM", 26, 29, 0, 2); break; }; } } else { snprintf(buf, 80, "Using %s on %s", song_audio_driver(), video_driver_name()); buf[80] = 0; draw_text(buf, (80 - strlen(buf)) / 2, 25, 0, 2); /* build date? */ draw_text(ver_short_copyright, 15, 27, 1, 2); draw_text(ver_short_based_on, 15, 28, 1, 2); /* XXX if we allow key remapping, need to reflect the *real* help key here */ draw_text("Press F1 for copyright and full credits", 15, 29, 1, 2); } vgamem_ovl_apply(&logo_image); } void show_about(void) { static int didit = 0; struct dialog *d; struct logo_data logo; int x, y; fake_driver = (rand() & 3) ? 0 : 1; if (!didit) { vgamem_ovl_alloc(&logo_image); xpmdata(_logo_it_xpm, &it_logo.pixels, &it_logo.width, &it_logo.height); xpmdata(_logo_schism_xpm, &schism_logo.pixels, &schism_logo.width, &schism_logo.height); didit=1; } logo = (status.flags & CLASSIC_MODE) ? it_logo : schism_logo; /* this is currently pretty gross */ vgamem_ovl_clear(&logo_image, 2); if (logo.pixels) { int c = (status.flags & CLASSIC_MODE) ? 11 : 0; for (y = 0; y < logo.height; y++) { for (x = 0; x < logo.width; x++) { if (logo.pixels[x]) { vgamem_ovl_drawpixel(&logo_image, x+2, y+6, c); } } vgamem_ovl_drawpixel(&logo_image, x, y+6, 2); vgamem_ovl_drawpixel(&logo_image, x+1, y+6, 2); logo.pixels += logo.width; } } widget_create_button(widgets_about + 0, 33,32, 12, 0,0,0,0,0, dialog_yes_NULL, "Continue", 3); d = dialog_create_custom(11,16, 58, 19, widgets_about, 1, 0, about_draw_const, NULL); d->action_yes = about_close; d->action_no = about_close; d->action_cancel = about_close; /* okay, in just a moment, we're going to the module page. * if your modules dir is large enough, this causes an annoying pause. * to defeat this, we start scanning *NOW*. this makes startup "feel" * faster. */ status.flags |= DIR_MODULES_CHANGED; pages[PAGE_LOAD_MODULE].set_page(); } schismtracker-20250313/schism/page_blank.c000066400000000000000000000033121476471630300203440ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "page.h" #include "widget.h" /* --------------------------------------------------------------------- */ static struct widget widgets_blank[1]; /* --------------------------------------------------------------------- */ static int blank_page_handle_key(SCHISM_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; widget_create_other(widgets_blank + 0, 0, blank_page_handle_key, NULL, blank_page_redraw); } schismtracker-20250313/schism/page_config.c000066400000000000000000000275351476471630300205370ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "charset.h" #include "config.h" #include "keyboard.h" #include "song.h" #include "page.h" #include "osdefs.h" #include "palettes.h" #include "fonts.h" #include "dialog.h" #include "widget.h" #include "vgamem.h" #include "player/snd_gm.h" #include "disko.h" /* --------------------------------------------------------------------- */ #define SAVED_AT_EXIT "System configuration will be saved at exit" static void config_set_page(void); static struct widget widgets_config[32]; static const char *const time_displays[] = { "Off", "Play / Elapsed", "Play / Clock", "Play / Off", "Elapsed", "Clock", "Absolute", NULL }; static const char *const vis_styles[] = { "Off", "Memory Stats", "Oscilloscope", "VU Meter", "Monoscope", "Spectrum", NULL }; static const char *const sharp_flat[] = { "Sharps (#)", "Flats (b)", NULL }; static const char *const output_channels[] = { "Mono", "Stereo", NULL }; static int sample_rate_cursor = 0; static const char *const bit_rates[] = { "8 Bit", "16 Bit", /*"24 Bit",*/ "32 Bit", NULL }; static const char *const midi_modes[] = { "IT semantics", "Tracker semantics", NULL }; static const int video_fs_group[] = { 9, 10, -1 }; static const int video_menu_bar_group[] = { 16, 17, -1 }; static int video_group[] = { 11, 12, 13, -1 }; static int video_renderer_group[] = { 14, 15, -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; switch (widgets_config[2].d.menutoggle.state) { case 0: audio_settings.bits = 8; break; default: case 1: audio_settings.bits = 16; break; case 2: audio_settings.bits = 32; break; } audio_settings.channels = widgets_config[3].d.menutoggle.state+1; song_init_modplug(); status_text_flash(SAVED_AT_EXIT); } static void change_ui_settings(void) { status.vis_style = widgets_config[4].d.menutoggle.state; status.time_display = widgets_config[7].d.menutoggle.state; if (widgets_config[5].d.toggle.state) { status.flags |= CLASSIC_MODE; } else { status.flags &= ~CLASSIC_MODE; } kbd_sharp_flat_toggle(widgets_config[6].d.menutoggle.state ? KBD_SHARP_FLAT_FLATS : KBD_SHARP_FLAT_SHARPS); GM_Reset(current_song, 0); if (widgets_config[8].d.toggle.state) { status.flags |= MIDI_LIKE_TRACKER; } else { status.flags &= ~MIDI_LIKE_TRACKER; } status.flags |= NEED_UPDATE; status_text_flash(SAVED_AT_EXIT); } static int countdown = 10; static time_t started = 0; static char video_revert_interpolation[8] = {'\0'}; static int video_revert_fs = 0; static int video_revert_hw = 0; static void video_mode_keep(SCHISM_UNUSED void*ign) { status_text_flash(SAVED_AT_EXIT); config_set_page(); status.flags |= NEED_UPDATE; } static void video_mode_cancel(SCHISM_UNUSED void*ign) { if (*video_revert_interpolation) video_setup(video_revert_interpolation); if (video_is_fullscreen() != video_revert_fs) video_fullscreen(-1); if (video_is_hardware() != video_revert_hw) video_set_hardware(video_revert_hw); palette_apply(); font_init(); config_set_page(); status.flags |= NEED_UPDATE; } static void video_dialog_draw_const(void) { char buf[80]; time_t now; time(&now); if (now != started) { countdown--; time(&started); /* err... */ status.flags |= NEED_UPDATE; if (countdown == 0) { dialog_destroy(); video_mode_cancel(NULL); return; } } draw_text("Your video settings have been changed.", 21,19,0,2); sprintf(buf, "In %2d seconds, your changes will be", countdown); draw_text(buf, 23, 21, 0, 2); draw_text("reverted to the last known-good", 21, 22, 0, 2); draw_text("settings.", 21, 23, 0, 2); draw_text("To use the new video mode, and make", 21, 24, 0, 2); draw_text("it default, select OK.", 21, 25, 0, 2); } static struct widget video_dialog_widgets[2]; static void video_change_dialog(void) { struct dialog *d; strncpy(video_revert_interpolation, cfg_video_interpolation, 7); video_revert_fs = video_is_fullscreen(); video_revert_hw = video_is_hardware(); countdown = 10; time(&started); widget_create_button(video_dialog_widgets+0, 28,28,8, 0, 0, 0, 1, 1, dialog_yes_NULL, "OK", 4); widget_create_button(video_dialog_widgets+1, 42,28,8, 1, 1, 0, 1, 0, dialog_cancel_NULL, "Cancel", 2); d = dialog_create_custom(20, 17, 40, 14, video_dialog_widgets, 2, 1, video_dialog_draw_const, NULL); d->action_yes = video_mode_keep; d->action_no = video_mode_cancel; d->action_cancel = video_mode_cancel; } static void change_video_settings(void) { const char *new_video_interpolation; int new_fs_flag; int hw; int interp_changed; int fs_changed; int hw_changed; new_video_interpolation = widgets_config[11].d.togglebutton.state ? "nearest" : widgets_config[12].d.togglebutton.state ? "linear" : widgets_config[13].d.togglebutton.state ? "best" : "nearest"; new_fs_flag = widgets_config[9].d.togglebutton.state; hw = widgets_config[14].d.togglebutton.state; interp_changed = charset_strcasecmp(new_video_interpolation, CHARSET_UTF8, cfg_video_interpolation, CHARSET_UTF8); fs_changed = (new_fs_flag != video_is_fullscreen()); hw_changed = (hw != video_is_hardware()); if (!interp_changed && !fs_changed && !hw_changed) return; video_change_dialog(); if (charset_strcasecmp(new_video_interpolation, CHARSET_UTF8, cfg_video_interpolation, CHARSET_UTF8)) video_setup(new_video_interpolation); if (new_fs_flag != video_is_fullscreen()) toggle_display_fullscreen(); if (hw != video_is_hardware()) video_set_hardware(hw); palette_apply(); font_init(); } static void change_menu_bar_settings(void) { cfg_video_want_menu_bar = !!widgets_config[16].d.togglebutton.state; video_toggle_menu(!video_is_fullscreen()); } /* --------------------------------------------------------------------- */ static void config_draw_const(void) { int n; draw_text("Channel Limit",4,15, 0, 2); draw_text("Mixing Rate",6,16, 0, 2); draw_text("Sample Size",6,17, 0, 2); draw_text("Output Channels",2,18, 0, 2); draw_text("Visualization",4,20, 0, 2); draw_text("Classic Mode",5,21, 0, 2); draw_text("Accidentals",6,22, 0, 2); draw_text("Time Display",5,23, 0, 2); draw_text("MIDI mode", 8,25, 0, 2); draw_text("Video Scaling:", 2, 28, 0, 2); draw_text("Video Rendering:", 2, 40, 0, 2); draw_text("Full Screen:", 38, 28, 0, 2); if (video_have_menu()) draw_text("Menu Bar:", 38, 32, 0, 2); draw_fill_chars(18, 15, 34, 25, DEFAULT_FG, 0); draw_box(17,14,35,26, BOX_THIN | BOX_INNER | BOX_INSET); for (n = 18; n < 35; n++) { draw_char(154, n, 19, 3, 0); draw_char(154, n, 24, 3, 0); } } static void config_set_page(void) { widgets_config[0].d.thumbbar.value = audio_settings.channel_limit; widgets_config[1].d.numentry.value = audio_settings.sample_rate; switch (audio_settings.bits) { case 8: widgets_config[2].d.menutoggle.state = 0; break; default: case 16: widgets_config[2].d.menutoggle.state = 1; break; case 32: widgets_config[2].d.menutoggle.state = 2; break; } widgets_config[3].d.menutoggle.state = audio_settings.channels-1; widgets_config[4].d.menutoggle.state = status.vis_style; widgets_config[5].d.toggle.state = !!(status.flags & CLASSIC_MODE); widgets_config[6].d.menutoggle.state = (kbd_sharp_flat_state() == KBD_SHARP_FLAT_FLATS); widgets_config[7].d.menutoggle.state = status.time_display; widgets_config[8].d.toggle.state = !!(status.flags & MIDI_LIKE_TRACKER); widgets_config[9].d.togglebutton.state = video_is_fullscreen(); widgets_config[10].d.togglebutton.state = !video_is_fullscreen(); const char* hint = cfg_video_interpolation; widgets_config[11].d.togglebutton.state = (!hint || *hint == '0' || charset_strcasecmp(hint, CHARSET_UTF8, "nearest", CHARSET_UTF8) == 0); widgets_config[12].d.togglebutton.state = (!hint || *hint == '1' || charset_strcasecmp(hint, CHARSET_UTF8, "linear", CHARSET_UTF8) == 0); widgets_config[13].d.togglebutton.state = (!hint || *hint == '2' || charset_strcasecmp(hint, CHARSET_UTF8, "best", CHARSET_UTF8) == 0); widgets_config[14].d.togglebutton.state = video_is_hardware(); widgets_config[15].d.togglebutton.state = !video_is_hardware(); if (video_have_menu()) { widgets_config[16].d.togglebutton.state = !!cfg_video_want_menu_bar; widgets_config[17].d.togglebutton.state = !cfg_video_want_menu_bar; } } /* --------------------------------------------------------------------- */ void config_load_page(struct page *page) { page->title = "System Configuration (Ctrl-F1)"; page->draw_const = config_draw_const; page->set_page = config_set_page; page->total_widgets = 16; page->widgets = widgets_config; page->help_index = HELP_GLOBAL; widget_create_thumbbar(widgets_config+0, 18, 15, 17, 0,1,1, change_mixer_limits, 4, 256); widget_create_numentry(widgets_config+1, 18, 16, 7, 0,2,2, change_mixer_limits, 4000, 192000, &sample_rate_cursor); widget_create_menutoggle(widgets_config+2, 18, 17, 1,3,2,2,3, change_mixer_limits, bit_rates); widget_create_menutoggle(widgets_config+3, 18, 18, 2,4,3,3,4, change_mixer_limits, output_channels); //// widget_create_menutoggle(widgets_config+4, 18, 20, 3,5,4,4,5, change_ui_settings, vis_styles); widget_create_toggle(widgets_config+5, 18, 21, 4,6,5,5,6, change_ui_settings); widget_create_menutoggle(widgets_config+6, 18, 22, 5,7,6,6,7, change_ui_settings, sharp_flat); widget_create_menutoggle(widgets_config+7, 18, 23, 6,8,7,7,8, change_ui_settings, time_displays); //// widget_create_menutoggle(widgets_config+8, 18, 25, 7,11,8,8,11, change_ui_settings, midi_modes); //// widget_create_togglebutton(widgets_config+9, 44, 30, 5, 8,9,11,10,10, change_video_settings, "Yes", 2, video_fs_group); widget_create_togglebutton(widgets_config+10, 54, 30, 5, 10,10,9,10,0, change_video_settings, "No", 2, video_fs_group); //// widget_create_togglebutton(widgets_config+11, 6, 30, 26, 8,12,11,9,12, change_video_settings, "Nearest", 2, video_group); widget_create_togglebutton(widgets_config+12, 6, 33, 26, 11,13,12,9,13, change_video_settings, "Linear", 2, video_group); widget_create_togglebutton(widgets_config+13, 6, 36, 26, 12,14,13,9,14, change_video_settings, "Best", 2, video_group); //// widget_create_togglebutton(widgets_config+14, 6, 42, 26, 13,15,14,9,15, change_video_settings, "Hardware", 2, video_renderer_group); widget_create_togglebutton(widgets_config+15, 6, 45, 26, 14,15,15,9,16, change_video_settings, "Software", 2, video_renderer_group); //// if (video_have_menu()) { widget_create_togglebutton(widgets_config+16, 44, 34, 5, 8,9,11,10,10, change_menu_bar_settings, "Yes", 2, video_menu_bar_group); widget_create_togglebutton(widgets_config+17, 54, 34, 5, 10,10,9,10,0, change_menu_bar_settings, "No", 2, video_menu_bar_group); page->total_widgets += 2; } } schismtracker-20250313/schism/page_help.c000066400000000000000000000200651476471630300202110ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Well, this page is just a big hack factory, but it's at least an * improvement over the message editor :P */ #include "headers.h" #include "it.h" #include "page.h" #include "widget.h" #include "vgamem.h" #include "keyboard.h" #include "mem.h" #include "str.h" /* --------------------------------------------------------------------- */ /* Line type characters (the marker at the start of each line) */ enum { LTYPE_NORMAL = '|', LTYPE_BIOS = '+', LTYPE_SCHISM = ':', LTYPE_SCHISM_BIOS = ';', LTYPE_CLASSIC = '!', LTYPE_SEPARATOR = '%', LTYPE_DISABLED = '#', LTYPE_GRAPHIC = '=', }; /* Types that should be hidden from view in classic/non-classic mode */ #define LINE_SCHISM_HIDDEN(p) (0[p] == LTYPE_CLASSIC) #define LINE_CLASSIC_HIDDEN(p) (0[p] == LTYPE_SCHISM || 0[p] == LTYPE_SCHISM_BIOS) /* Types that should be rendered with the standard font */ #define LINE_BIOS(p) (0[p] == LTYPE_BIOS || 0[p] == LTYPE_SCHISM_BIOS) static struct widget widgets_help[2]; /* Pointers to the start of each line, and total line counts, for each help text, for both classic and "normal" mode. For example, help_cache[HELP_PATTERN_EDITOR][0].lines[3][5] is the fifth character of the third line of the non-classic-mode help for the pattern editor. Each line is terminated by some combination of \r and \n, or \0. */ static struct { const char **lines; int num_lines; } help_cache[HELP_NUM_ITEMS][2] = {{{NULL, 0}}}; /* Shortcuts for sanity -- this will point to the currently applicable help. */ #define CURRENT_HELP_LINECACHE (help_cache[status.current_help_index][!!(status.flags & CLASSIC_MODE)].lines) #define CURRENT_HELP_LINECOUNT (help_cache[status.current_help_index][!!(status.flags & CLASSIC_MODE)].num_lines) /* should always point to the currently applicable help text -- cached to prevent repetitively checking things that aren't going to change */ static const char **lines = NULL; static int num_lines = 0; static int top_line = 0; static const char blank_line[] = {LTYPE_NORMAL, '\0'}; static const char separator_line[] = {LTYPE_SEPARATOR, '\0'}; static int help_text_lastpos[HELP_NUM_ITEMS] = {0}; /* This isn't defined in an .h file since it's only used here. */ extern const char *help_text[]; /* --------------------------------------------------------------------- */ static void help_draw_const(void) { draw_box(1, 12, 78, 45, BOX_THICK | BOX_INNER | BOX_INSET); if (status.dialog_type == DIALOG_NONE) widget_change_focus_to(1); } static void help_redraw(void) { int n, pos, x; int lp; const char **ptr; const uint8_t graphic_chars[] = {0, 0x89, 0x8f, 0x96, 0x84, 0, 0x91, 0x8b, 0x86, 0x8a}; char ch; draw_fill_chars(2, 13, 77, 44, DEFAULT_FG, 0); ptr = lines + top_line; for (pos = 13, n = top_line; pos < 45; pos++, n++) { switch (**ptr) { default: lp = strcspn(*ptr+1, "\015\012"); if (LINE_BIOS(*ptr)) { draw_text_bios_len(*ptr + 1, lp, 2, pos, 6, 0); } else { draw_text_len(*ptr + 1, lp, 2, pos, **ptr == LTYPE_DISABLED ? 7 : 6, 0); } break; case LTYPE_GRAPHIC: lp = strcspn(*ptr + 1, "\015\012"); for (x = 1; x <= lp; x++) { ch = ptr[0][x]; if (ch >= '1' && ch <= '9') ch = graphic_chars[ch - '0']; draw_char(ch, x + 1, pos, 6, 0); } break; case LTYPE_SEPARATOR: for (x = 2; x < 78; x++) draw_char(154, x, pos, 6, 0); break; } ptr++; } } /* --------------------------------------------------------------------- */ static void _help_close(void) { set_page(status.previous_page); } static int help_handle_key(struct key_event * k) { int new_line = top_line; if (status.dialog_type != DIALOG_NONE) return 0; if (k->mouse == MOUSE_SCROLL_UP) { new_line -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { new_line += MOUSE_SCROLL_LINES; } else if (k->mouse != MOUSE_NONE) { return 0; } switch (k->sym) { case SCHISM_KEYSYM_ESCAPE: if (k->state == KEY_RELEASE) return 1; set_page(status.previous_page); return 1; case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 1; new_line--; break; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 1; new_line++; break; case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return 1; new_line -= 32; break; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return 1; new_line += 32; break; case SCHISM_KEYSYM_HOME: if (k->state == KEY_RELEASE) return 1; new_line = 0; break; case SCHISM_KEYSYM_END: if (k->state == KEY_RELEASE) return 1; new_line = num_lines - 32; break; default: if (k->mouse != MOUSE_NONE) { if (k->state == KEY_RELEASE) return 1; } else { return 0; } } new_line = CLAMP(new_line, 0, num_lines - 32); if (new_line != top_line) { top_line = new_line; help_text_lastpos[status.current_help_index] = top_line; status.flags |= NEED_UPDATE; } return 1; } /* --------------------------------------------------------------------- */ static void help_set_page(void) { const char *ptr; int local_lines = 0, global_lines = 0, cur_line = 0; int have_local_help = (status.current_help_index != HELP_GLOBAL); widget_change_focus_to(1); top_line = help_text_lastpos[status.current_help_index]; lines = CURRENT_HELP_LINECACHE; if (lines) { num_lines = CURRENT_HELP_LINECOUNT; return; } /* how many lines? */ global_lines = str_get_num_lines(help_text[HELP_GLOBAL]); if (have_local_help) { local_lines = str_get_num_lines(help_text[status.current_help_index]); num_lines = local_lines + global_lines + 5; } else { num_lines = global_lines + 2; } /* allocate the array */ lines = CURRENT_HELP_LINECACHE = mem_calloc(num_lines + 1, sizeof(char *)); /* page help text */ if (have_local_help) { ptr = help_text[status.current_help_index]; while (local_lines--) { if (status.flags & CLASSIC_MODE) { if (!LINE_CLASSIC_HIDDEN(ptr)) lines[cur_line++] = ptr; } else { if (!LINE_SCHISM_HIDDEN(ptr)) lines[cur_line++] = ptr; } ptr = strpbrk(ptr, "\015\012"); if (*ptr == 13) ptr++; if (*ptr == 10) ptr++; } lines[cur_line++] = blank_line; lines[cur_line++] = separator_line; } lines[cur_line++] = blank_line; /* global help text */ ptr = help_text[HELP_GLOBAL]; while (global_lines--) { if (status.flags & CLASSIC_MODE) { if (!LINE_CLASSIC_HIDDEN(ptr)) lines[cur_line++] = ptr; } else { if (!LINE_SCHISM_HIDDEN(ptr)) lines[cur_line++] = ptr; } ptr = strpbrk(ptr, "\015\012"); if (*ptr == 13) ptr++; if (*ptr == 10) ptr++; } lines[cur_line++] = blank_line; if (have_local_help) lines[cur_line++] = separator_line; lines[cur_line] = NULL; CURRENT_HELP_LINECOUNT = num_lines = cur_line; } /* --------------------------------------------------------------------- */ void help_load_page(struct page *page) { page->title = "Help"; page->draw_const = help_draw_const; page->set_page = help_set_page; page->total_widgets = 2; page->widgets = widgets_help; page->pre_handle_key = help_handle_key; widget_create_other(widgets_help + 0, 0, help_handle_key, NULL, help_redraw); widget_create_button(widgets_help + 1, 35,47,8, 0, 1, 1,1, 0, _help_close, "Done", 3); } schismtracker-20250313/schism/page_info.c000066400000000000000000001176651476471630300202310ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "config.h" #include "vgamem.h" #include "song.h" #include "page.h" #include "widget.h" #include "pattern-view.h" #include "config-parser.h" #include "keyboard.h" #include "str.h" #include /* --------------------------------------------------------------------- */ static struct widget widgets_info[1]; /* nonzero => use velocity bars */ static int velocity_mode = 0; /* nonzero => instrument names */ static int instrument_names = 0; /* --------------------------------------------------------------------- */ /* window setup */ struct info_window_type { const char *id; void (*draw) (int base, int height, int active, int first_channel); void (*click) (int x, int y, int num_vis_channel, int first_channel); /* if this is set, the first row contains actual text (not just the top part of a box) */ int first_row; /* how many channels are shown -- just use 0 for windows that don't show specific channel info. for windows that put the channels vertically (i.e. sample names) this should be the amount to ADD to the height to get the number of channels, so it should be NEGATIVE. (example: the sample name view uses the first position for the top of the box and the last position for the bottom, so it uses -2.) confusing, almost to the point of being painful, but it works. (ok, i admit, it's not the most brilliant idea i ever had ;) */ int channels; }; struct info_window { int type; int height; int first_channel; }; static int selected_window = 0; static int num_windows = 3; static int selected_channel = 1; /* five, because that's Impulse Tracker's maximum */ #define MAX_WINDOWS 5 static struct info_window windows[MAX_WINDOWS] = { {0, 19, 1}, /* samples (18 channels displayed) */ {8, 3, 1}, /* active channels */ {5, 15, 1}, /* 24chn track view */ }; /* --------------------------------------------------------------------- */ /* the various stuff that can be drawn... */ static void info_draw_technical(int base, int height, int active, int first_channel) { int smp, pos, fg, c = first_channel; char buf[16]; const char *ptr; /* FVl - 0-128, final calculated volume, taking everything into account: (sample volume, sample global volume, instrument volume, inst. global volume, volume envelope, volume swing, fadeout, channel volume, song global volume, effects (I/Q/R) Vl - 0-64, sample volume / volume column (also affected by I/Q/R) CV - 0-64, channel volume (M/N) SV - 0-64, sample global volume + inst global volume Fde - 0-512, HALF the fade (initially 1024, and subtracted by instrument fade value each tick when fading out) Pn - 0-64 (or "Su"), final channel panning + pan swing + pitch/pan + current pan envelope value! + Yxx (note: suggests that Xxx panning is reduced to 64 values when it's applied?) PE - 0-64, pan envelope note: this value is not changed if pan env is turned off (e.g. with S79) -- so it's copied all of the above are still set to valid values in sample mode */ draw_fill_chars(5, base + 1, 29, base + height - 2, DEFAULT_FG, 0); draw_box(4, base, 30, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); draw_text("Frequency", 6, base, 2, 1); draw_text("Position", 17, base, 2, 1); draw_text("Smp", 27, base, 2, 1); draw_fill_chars(32, base + 1, 56, base + height - 2, DEFAULT_FG, 0); draw_box(31, base, 57, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); draw_text("FVl", 32, base, 2, 1); draw_text("Vl", 36, base, 2, 1); draw_text("CV", 39, base, 2, 1); draw_text("SV", 42, base, 2, 1); draw_text("VE", 45, base, 2, 1); draw_text("Fde", 48, base, 2, 1); draw_text("Pn", 52, base, 2, 1); draw_text("PE", 55, base, 2, 1); if (song_is_instrument_mode()) { draw_fill_chars(59, base + 1, 65, base + height - 2, DEFAULT_FG, 0); draw_box(58, base, 66, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); draw_text("NNA", 59, base, 2, 1); draw_text("Tot", 63, base, 2, 1); } for (pos = base + 1; pos < base + height - 1; pos++, c++) { song_channel_t *channel = current_song->channels + c - 1; song_voice_t *voice = current_song->voices + c - 1; if (c == selected_channel) { fg = (channel->flags & CHN_MUTE) ? 6 : 3; } else { if (channel->flags & CHN_MUTE) fg = 2; else fg = active ? 1 : 0; } draw_text(str_from_num99(c, buf), 2, pos, fg, 2); /* channel number */ draw_char(168, 15, pos, 2, 0); draw_char(168, 26, pos, 2, 0); draw_char(168, 35, pos, 2, 0); draw_char(168, 38, pos, 2, 0); draw_char(168, 41, pos, 2, 0); draw_char(168, 44, pos, 2, 0); draw_char(168, 47, pos, 2, 0); draw_char(168, 51, pos, 2, 0); draw_char(168, 54, pos, 2, 0); if (song_is_instrument_mode()) { draw_text("---\xa8", 59, pos, 2, 0); /* will be overwritten if something's playing */ /* count how many voices claim this channel */ int nv, tot; for (nv = tot = 0; nv < MAX_VOICES; nv++) { song_voice_t *v = current_song->voices + nv; if (v->master_channel == (unsigned int) c && ((v->current_sample_data && v->length) || (v->flags & CHN_ADLIB))) tot++; } if ((voice->current_sample_data && voice->length) || (voice->flags & CHN_ADLIB)) tot++; draw_text(str_from_num(3, tot, buf), 63, pos, 2, 0); } if (((voice->current_sample_data && voice->length) || (voice->flags & CHN_ADLIB)) && voice->ptr_sample) { // again with the hacks... smp = voice->ptr_sample - current_song->samples; if (smp <= 0 || smp >= MAX_SAMPLES) continue; } else { continue; } // Frequency sprintf(buf, "%10" PRIu32, voice->sample_freq); draw_text(buf, 5, pos, 2, 0); // Position sprintf(buf, "%10" PRIu32, voice->position); draw_text(buf, 16, pos, 2, 0); draw_text(str_from_num(3, smp, buf), 27, pos, 2, 0); // Smp draw_text(str_from_num(3, voice->final_volume / 128, buf), 32, pos, 2, 0); // FVl draw_text(str_from_num(2, voice->volume >> 2, buf), 36, pos, 2, 0); // Vl draw_text(str_from_num(2, voice->global_volume, buf), 39, pos, 2, 0); // CV draw_text(str_from_num(2, voice->ptr_sample->global_volume, buf), 42, pos, 2, 0); // SV // FIXME: VE means volume envelope. Also, voice->instrument_volume is actually sample global volume draw_text(str_from_num(2, voice->instrument_volume, buf), 45, pos, 2, 0); // VE draw_text(str_from_num(3, voice->fadeout_volume / 128, buf), 48, pos, 2, 0); // Fde // Pn if (voice->flags & CHN_SURROUND) draw_text("Su", 52, pos, 2, 0); else draw_text(str_from_num(2, voice->panning >> 2, buf), 52, pos, 2, 0); draw_text(str_from_num(2, voice->final_panning >> 2, buf), 55, pos, 2, 0); // PE if (song_is_instrument_mode()) { switch (voice->nna) { case NNA_NOTECUT: ptr = "Cut"; break; case NNA_CONTINUE: ptr = "Con"; break; case NNA_NOTEOFF: ptr = "Off"; break; case NNA_NOTEFADE: ptr = "Fde"; break; default: ptr = "???"; break; }; draw_text(ptr, 59, pos, 2, 0); } } } static void info_draw_samples(int base, int height, int active, int first_channel) { int vu, smp, ins, n, pos, fg, fg2, c; char buf[8]; char *ptr; draw_fill_chars(5, base + 1, 28, base + height - 2, DEFAULT_FG, 0); draw_fill_chars(31, base + 1, 61, base + height - 2, DEFAULT_FG, 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, DEFAULT_FG, 0); draw_box(63, base, 73, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); } else { draw_fill_chars(63, base, 73, base + height, DEFAULT_FG, 2); } if (song_get_mode() == MODE_STOPPED) { for (pos = base + 1, c = first_channel; pos < base + height - 1; pos++, c++) { song_channel_t *channel = song_get_channel(c - 1); if (c == selected_channel) { fg = (channel->flags & CHN_MUTE) ? 6 : 3; } else { if (channel->flags & CHN_MUTE) continue; fg = active ? 1 : 0; } draw_text(str_from_num(2, c, buf), 2, pos, fg, 2); } return; } for (pos = base + 1, c = first_channel; pos < base + height - 1; pos++, c++) { song_voice_t *voice = current_song->voices + c - 1; /* always draw the channel number */ if (c == selected_channel) { fg = (voice->flags & CHN_MUTE) ? 6 : 3; draw_text(str_from_num(2, c, buf), 2, pos, fg, 2); } else if (!(voice->flags & CHN_MUTE)) { fg = active ? 1 : 0; draw_text(str_from_num(2, c, buf), 2, pos, fg, 2); } if ((!(voice->current_sample_data && voice->length) && !(voice->flags & CHN_ADLIB))) continue; /* first box: vu meter */ if (velocity_mode) vu = voice->final_volume >> 8; else vu = voice->vu_meter >> 2; if (voice->flags & CHN_MUTE) { fg = 1; fg2 = 2; } else { fg = 5; fg2 = 4; } draw_vu_meter(5, pos, 24, vu, fg, fg2); /* second box: sample number/name */ ins = song_get_instrument_number(voice->ptr_instrument); /* figuring out the sample number is an ugly hack... considering all the crap that's copied to the channel, i'm surprised that the sample and instrument numbers aren't in there somewhere... */ if (voice->ptr_sample) smp = voice->ptr_sample - current_song->samples; else smp = ins = 0; if(smp < 0 || smp >= MAX_SAMPLES) smp = ins = 0; /* This sample is not in the sample array */ if (smp) { draw_text(str_from_num99(smp, buf), 31, pos, 6, 0); if (ins) { draw_char('/', 33, pos, 6, 0); draw_text(str_from_num99(ins, buf), 34, pos, 6, 0); n = 36; } else { n = 33; } if (voice->volume == 0) fg = 4; else if (voice->flags & (CHN_KEYOFF | CHN_NOTEFADE)) fg = 7; else fg = 6; draw_char(':', n++, pos, fg, 0); if (instrument_names && voice->ptr_instrument) { ptr = voice->ptr_instrument->name; } else { ptr = current_song->samples[smp].name; } draw_text_len(ptr, 25, n, pos, 6, 0); } else if (ins && voice->ptr_instrument && voice->ptr_instrument->midi_channel_mask) { // XXX why? what? if (voice->ptr_instrument->midi_channel_mask >= 0x10000) { draw_text(str_from_num(2, ((c-1) % 16)+1, buf), 31, pos, 6, 0); } else { int ch = 0; while(!(voice->ptr_instrument->midi_channel_mask & (1 << ch))) ++ch; draw_text(str_from_num(2, ch, buf), 31, pos, 6, 0); } draw_char('/', 33, pos, 6, 0); draw_text(str_from_num99(ins, buf), 34, pos, 6, 0); n = 36; if (voice->volume == 0) fg = 4; else if (voice->flags & (CHN_KEYOFF | CHN_NOTEFADE)) fg = 7; else fg = 6; draw_char(':', n++, pos, fg, 0); ptr = voice->ptr_instrument->name; draw_text_len( ptr, 25, n, pos, 6, 0); } else { continue; } /* last box: panning. this one's much easier than the * other two, thankfully :) */ if (song_is_stereo()) { if (!voice->ptr_sample) { /* nothing... */ } else if (voice->flags & CHN_SURROUND) { draw_text("Surround", 64, pos, 2, 0); } else if (voice->final_panning >> 2 == 0) { draw_text("Left", 64, pos, 2, 0); } else if ((voice->final_panning + 3) >> 2 == 64) { draw_text("Right", 68, pos, 2, 0); } else { draw_thumb_bar(64, pos, 9, 0, 256, voice->final_panning, 0); } } } } static void _draw_fill_notes(int col, int first_row, int height, int num_channels, int channel_width, int separator, draw_note_func draw_note, int bg) { int row_pos, chan_pos; for (row_pos = first_row; row_pos < first_row + height; row_pos++) { for (chan_pos = 0; chan_pos < num_channels - 1; chan_pos++) { draw_note(col + channel_width * chan_pos, row_pos, blank_note, -1, 6, bg); if (separator) draw_char(168, (col - 1 + channel_width * (chan_pos + 1)), row_pos, 2, bg); } draw_note(col + channel_width * chan_pos, row_pos, blank_note, -1, 6, bg); } } static void _draw_track_view(int base, int height, int first_channel, int num_channels, int channel_width, int separator, draw_note_func draw_note) { /* way too many variables */ int current_row = song_get_current_row(); int current_order = song_get_current_order(); const song_note_t *note; // These can't be const because of song_get_pattern, but song_get_pattern is stupid and smells funny. song_note_t *cur_pattern, *prev_pattern, *next_pattern; const song_note_t *pattern; /* points to either {cur,prev,next}_pattern */ int cur_pattern_rows = 0, prev_pattern_rows = 0, next_pattern_rows = 0; int total_rows; /* same as {cur,prev_next}_pattern_rows */ int chan_pos, row, row_pos, rows_before; char buf[4]; if (separator) channel_width++; #if 0 /* can't do this here -- each view does channel numbers differently, don't draw on top of them */ draw_box(4, base, 5 + num_channels * channel_width - !!separator, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); #endif switch (song_get_mode()) { case MODE_PATTERN_LOOP: prev_pattern_rows = next_pattern_rows = cur_pattern_rows = song_get_pattern(song_get_playing_pattern(), &cur_pattern); prev_pattern = next_pattern = cur_pattern; break; case MODE_PLAYING: if (current_song->orderlist[current_order] >= 200) { /* this does, in fact, happen. just pretend that * it's stopped :P */ default: /* stopped */ draw_fill_chars(5, base + 1, 4 + num_channels * channel_width - !!separator, base + height - 2, DEFAULT_FG, 0); return; } cur_pattern_rows = song_get_pattern(current_song->orderlist[current_order], &cur_pattern); if (current_order > 0 && current_song->orderlist[current_order - 1] < 200) prev_pattern_rows = song_get_pattern(current_song->orderlist[current_order - 1], &prev_pattern); else prev_pattern = NULL; if (current_order < 255 && current_song->orderlist[current_order + 1] < 200) next_pattern_rows = song_get_pattern(current_song->orderlist[current_order + 1], &next_pattern); else next_pattern = NULL; break; } /* -2 for the top and bottom border, -1 because if there are an even number * of rows visible, the current row is drawn above center. */ rows_before = (height - 3) / 2; /* "fake" channels (hack for 64-channel view) */ if (num_channels > 64) { _draw_fill_notes(5 + 64, base + 1, height - 2, num_channels - 64, channel_width, separator, draw_note, 0); _draw_fill_notes(5 + 64, base + 1 + rows_before, 1, num_channels - 64, channel_width, separator, draw_note, 14); num_channels = 64; } /* draw the area above the current row */ pattern = cur_pattern; total_rows = cur_pattern_rows; row = current_row - 1; row_pos = base + rows_before; while (row_pos > base) { if (row < 0) { if (prev_pattern == NULL) { _draw_fill_notes(5, base + 1, row_pos - base, num_channels, channel_width, separator, draw_note, 0); break; } pattern = prev_pattern; total_rows = prev_pattern_rows; row = total_rows - 1; } draw_text(str_from_num(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(str_from_num(3, current_row, buf), 1, row_pos, 0, 2); note = pattern + 64 * current_row + first_channel - 1; for (chan_pos = 0; chan_pos < num_channels - 1; chan_pos++) { draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 14); if (separator) draw_char(168, (4 + channel_width * (chan_pos + 1)), row_pos, 2, 14); note++; } draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 14); /* draw the area under the current row */ row = current_row + 1; row_pos++; while (row_pos < base + height - 1) { if (row >= total_rows) { if (next_pattern == NULL) { _draw_fill_notes(5, row_pos, base + height - row_pos - 1, num_channels, channel_width, separator, draw_note, 0); break; } pattern = next_pattern; total_rows = next_pattern_rows; row = 0; } draw_text(str_from_num(3, row, buf), 1, row_pos, 0, 2); note = pattern + 64 * row + first_channel - 1; for (chan_pos = 0; chan_pos < num_channels - 1; chan_pos++) { draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 0); if (separator) draw_char(168, (4 + channel_width * (chan_pos + 1)), row_pos, 2, 0); note++; } draw_note(5 + channel_width * chan_pos, row_pos, note, -1, 6, 0); row++; row_pos++; } } static void info_draw_track_5(int base, int height, int active, int first_channel) { int chan, chan_pos, fg; draw_box(4, base, 74, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); for (chan = first_channel, chan_pos = 0; chan_pos < 5; chan++, chan_pos++) { if (current_song->channels[chan - 1].flags & CHN_MUTE) fg = (chan == selected_channel ? 6 : 1); else fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); draw_channel_header_13(chan, 5 + 14 * chan_pos, base, fg); } _draw_track_view(base, height, first_channel, 5, 13, 1, draw_note_13); } static void info_draw_track_8(int base, int height, int active, int first_channel) { int chan, chan_pos, fg; char buf[4]; draw_box(4, base, 76, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); for (chan = first_channel, chan_pos = 0; chan_pos < 8; chan++, chan_pos++) { if (current_song->channels[chan - 1].flags & CHN_MUTE) fg = (chan == selected_channel ? 6 : 1); else fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); draw_char(0, 6 + 9 * chan_pos, base, 1, 1); draw_char(0, 6 + 9 * chan_pos + 1, base, 1, 1); draw_text(str_from_num(2, chan, buf), 6 + 9 * chan_pos + 2, base, fg, 1); draw_char(0, 6 + 9 * chan_pos + 4, base, 1, 1); draw_char(0, 6 + 9 * chan_pos + 5, base, 1, 1); } _draw_track_view(base, height, first_channel, 8, 8, 1, draw_note_8); } static void info_draw_track_10(int base, int height, int active, int first_channel) { int chan, chan_pos, fg; char buf[4]; draw_box(4, base, 75, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); for (chan = first_channel, chan_pos = 0; chan_pos < 10; chan++, chan_pos++) { if (current_song->channels[chan - 1].flags & CHN_MUTE) fg = (chan == selected_channel ? 6 : 1); else fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); draw_char(0, 5 + 7 * chan_pos, base, 1, 1); draw_char(0, 5 + 7 * chan_pos + 1, base, 1, 1); draw_text(str_from_num(2, chan, buf), 5 + 7 * chan_pos + 2, base, fg, 1); draw_char(0, 5 + 7 * chan_pos + 4, base, 1, 1); draw_char(0, 5 + 7 * chan_pos + 5, base, 1, 1); } _draw_track_view(base, height, first_channel, 10, 7, 0, draw_note_7); } static void info_draw_track_12(int base, int height, int active, int first_channel) { int chan, chan_pos, fg; char buf[4]; draw_box(4, base, 77, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); for (chan = first_channel, chan_pos = 0; chan_pos < 12; chan++, chan_pos++) { if (current_song->channels[chan - 1].flags & CHN_MUTE) fg = (chan == selected_channel ? 6 : 1); else fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); /* draw_char(0, 5 + 6 * chan_pos, base, 1, 1); */ draw_char(0, 5 + 6 * chan_pos + 1, base, 1, 1); draw_text(str_from_num(2, chan, buf), 5 + 6 * chan_pos + 2, base, fg, 1); draw_char(0, 5 + 6 * chan_pos + 4, base, 1, 1); /* draw_char(0, 5 + 6 * chan_pos + 5, base, 1, 1); */ } _draw_track_view(base, height, first_channel, 12, 6, 0, draw_note_6); } static void info_draw_track_18(int base, int height, int active, int first_channel) { int chan, chan_pos, fg; char buf[4]; draw_box(4, base, 76, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); for (chan = first_channel, chan_pos = 0; chan_pos < 18; chan++, chan_pos++) { if (current_song->channels[chan - 1].flags & CHN_MUTE) fg = (chan == selected_channel ? 6 : 1); else fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); draw_text(str_from_num(2, chan, buf), 5 + 4 * chan_pos + 1, base, fg, 1); } _draw_track_view(base, height, first_channel, 18, 3, 1, draw_note_3); } static void info_draw_track_24(int base, int height, int active, int first_channel) { int chan, chan_pos, fg; char buf[4]; draw_box(4, base, 77, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); for (chan = first_channel, chan_pos = 0; chan_pos < 24; chan++, chan_pos++) { if (current_song->channels[chan - 1].flags & CHN_MUTE) fg = (chan == selected_channel ? 6 : 1); else fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); draw_text(str_from_num(2, chan, buf), 5 + 3 * chan_pos + 1, base, fg, 1); } _draw_track_view(base, height, first_channel, 24, 3, 0, draw_note_3); } static void info_draw_track_36(int base, int height, int active, int first_channel) { int chan, chan_pos, fg; char buf[4]; draw_box(4, base, 77, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); for (chan = first_channel, chan_pos = 0; chan_pos < 36; chan++, chan_pos++) { if (current_song->channels[chan - 1].flags & CHN_MUTE) fg = (chan == selected_channel ? 6 : 1); else fg = (chan == selected_channel ? 3 : (active ? 2 : 0)); draw_text(str_from_num(2, chan, buf), 5 + 2 * chan_pos, base, fg, 1); } _draw_track_view(base, height, first_channel, 36, 2, 0, draw_note_2); } static void info_draw_track_64(int base, int height, int active, int first_channel) { int chan, chan_pos, fg; /* IT draws nine more blank "channels" on the right */ int nchan = (status.flags & CLASSIC_MODE) ? 73 : 64; assert(first_channel == 1); draw_box(4, base, nchan + 5, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); for (chan = first_channel, chan_pos = 0; chan_pos < 64; chan++, chan_pos++) { if (current_song->channels[chan - 1].flags & CHN_MUTE) fg = (chan == selected_channel ? 14 : 9); else fg = (chan == selected_channel ? 3 : (active ? 10 : 8)); draw_half_width_chars(chan / 10 + '0', chan % 10 + '0', 5 + chan_pos, base, fg, 1, fg, 1); } for (; chan_pos < nchan; chan_pos++) draw_char(0, 5 + chan_pos, base, 1, 1); _draw_track_view(base, height, first_channel, nchan, 1, 0, draw_note_1); } static void info_draw_channels(int base, SCHISM_UNUSED int height, int active, SCHISM_UNUSED int first_channel) { char buf[32]; int fg = (active ? 3 : 0); snprintf(buf, 32, "Active Channels: %d (%d)", song_get_playing_channels(), song_get_max_channels()); draw_text(buf, 2, base, fg, 2); snprintf(buf, 32, "Global Volume: %d", song_get_current_global_volume()); draw_text(buf, 4, base + 1, fg, 2); } /* Yay it works, only took me forever and a day to get it right. */ static void info_draw_note_dots(int base, int height, int active, int first_channel) { int fg, v; int c, pos; uint32_t n; song_voice_t *voice; char buf[4]; uint8_t d, dn; uint8_t dot_field[73][36] = { {0} }; // f#2 -> f#8 = 73 columns draw_fill_chars(5, base + 1, 77, base + height - 2, DEFAULT_FG, 0); draw_box(4, base, 78, base + height - 1, BOX_THICK | BOX_INNER | BOX_INSET); for (n = 0; n < MAX_VOICES; n++) { voice = current_song->voices + n; /* 31 = f#2, 103 = f#8. (i hope ;) */ if (!(voice->ptr_sample && voice->note >= 31 && voice->note <= 103)) continue; pos = voice->master_channel ? voice->master_channel : (1 + n); if (pos < first_channel) continue; pos -= first_channel; if (pos > height - 1) continue; fg = (voice->flags & CHN_MUTE) ? 1 : ((voice->ptr_sample - current_song->samples) % 4 + 2); if (velocity_mode || (status.flags & CLASSIC_MODE)) v = (voice->final_volume + 2047) >> 11; else v = (voice->vu_meter + 31) >> 5; d = dot_field[voice->note - 31][pos]; dn = (v << 4) | fg; if (dn > d) dot_field[voice->note - 31][pos] = dn; } for (c = first_channel, pos = 0; pos < height - 2; pos++, c++) { for (n = 0; n < 73; n++) { d = dot_field[n][pos] ? dot_field[n][pos] : 0x06; fg = d & 0xf; v = d >> 4; draw_char(v + 193, n + 5, pos + base + 1, fg, 0); } if (c == selected_channel) { fg = (current_song->channels[c - 1].flags & CHN_MUTE) ? 6 : 3; } else { if (current_song->channels[c - 1].flags & CHN_MUTE) continue; fg = active ? 1 : 0; } draw_text(str_from_num(2, c, buf), 2, pos + base + 1, fg, 2); } } /* --------------------------------------------------------------------- */ /* click receivers */ static void click_chn_x(int x, int w, int skip, int fc) { while (x > 0 && fc <= 64) { if (x < w) { selected_channel = CLAMP(fc, 1, 64); return; } fc++; x -= w; x -= skip; } } static void click_chn_is_x(int x, SCHISM_UNUSED int y, int nc, int fc) { if (x < 5) return; x -= 4; switch (nc) { case 5: click_chn_x(x, 13, 1, fc); break; case 10: click_chn_x(x, 7, 0, fc); break; case 12: click_chn_x(x, 6, 0, fc); break; case 18: click_chn_x(x, 3, 1, fc); break; case 24: click_chn_x(x, 3, 0, fc); break; case 36: click_chn_x(x, 2, 0, fc); break; case 64: click_chn_x(x, 1, 0, fc); break; }; } static void click_chn_is_y_nohead(SCHISM_UNUSED int x, int y, SCHISM_UNUSED int nc, int fc) { selected_channel = CLAMP(y+fc, 1, 64); } static void click_chn_is_y(SCHISM_UNUSED int x, int y, SCHISM_UNUSED int nc, int fc) { if (!y) return; selected_channel = CLAMP((y+fc)-1, 1, 64); } static void click_chn_nil(SCHISM_UNUSED int x, SCHISM_UNUSED int y, SCHISM_UNUSED int nc, SCHISM_UNUSED int fc) { /* do nothing */ } /* --------------------------------------------------------------------- */ /* declarations of the window types */ #define TRACK_VIEW(n) {"track" # n, info_draw_track_##n, click_chn_is_x, 1, n} static const struct info_window_type window_types[] = { {"samples", info_draw_samples, click_chn_is_y_nohead, 0, -2}, TRACK_VIEW(5), TRACK_VIEW(8), TRACK_VIEW(10), TRACK_VIEW(12), TRACK_VIEW(18), TRACK_VIEW(24), TRACK_VIEW(36), TRACK_VIEW(64), {"global", info_draw_channels, click_chn_nil, 1, 0}, {"dots", info_draw_note_dots, click_chn_is_y_nohead, 0, -2}, {"tech", info_draw_technical, click_chn_is_y, 1, -2}, }; #undef TRACK_VIEW #define NUM_WINDOW_TYPES ((int)ARRAY_SIZE(window_types)) /* --------------------------------------------------------------------- */ static void _fix_channels(int n) { struct info_window *w = windows + n; int channels = window_types[w->type].channels; if (channels == 0) return; if (channels < 0) { channels += w->height; if (n == 0 && !(window_types[w->type].first_row)) { /* crappy hack (to squeeze in an extra row on the top window) */ channels++; } } if (selected_channel < w->first_channel) w->first_channel = selected_channel; else if (selected_channel >= (w->first_channel + channels)) w->first_channel = selected_channel - channels + 1; w->first_channel = CLAMP(w->first_channel, 1, 65 - channels); } static int info_handle_click(int x, int y) { int n; if (y < 13) return 0; /* NA */ y -= 13; for (n = 0; n < num_windows; n++) { if (y < windows[n].height) { window_types[windows[n].type].click( x, y, window_types[windows[n].type].channels, windows[n].first_channel); return 1; } y -= windows[n].height; } return 0; } static void recalculate_windows(void) { int n, pos; pos = 13; for (n = 0; n < num_windows - 1; n++) { _fix_channels(n); pos += windows[n].height; if (pos > 50) { /* Too big? Throw out the rest of the windows. */ num_windows = n; } } assert(num_windows > 0); windows[n].height = 50 - pos; _fix_channels(n); } /* --------------------------------------------------------------------------------------------------------- */ /* settings */ void cfg_save_info(cfg_file_t *cfg) { // for 5 windows, roughly 12 chars per window, this is way more than enough char buf[256] = ""; char *s = buf; int rem = sizeof(buf) - 1; int len; int i; for (i = 0; i < num_windows; i++) { len = snprintf(s, rem, " %s %d", window_types[windows[i].type].id, windows[i].height); if (!len) { // this should not ever happen break; } rem -= len; s += len; } buf[255] = '\0'; // (don't write the first space to the config) cfg_set_string(cfg, "Info Page", "layout", buf + 1); } static void cfg_load_info_old(cfg_file_t *cfg) { char key[] = "windowX"; int i; num_windows = cfg_get_number(cfg, "Info Page", "num_windows", -1); if (num_windows <= 0 || num_windows > MAX_WINDOWS) num_windows = -1; for (i = 0; i < num_windows; i++) { int tmp; key[6] = i + '0'; tmp = cfg_get_number(cfg, "Info Page", key, -1); if (tmp == -1) { num_windows = -1; break; } windows[i].type = tmp >> 8; if (windows[i].type >= 2) { // compensate for added 8-channel view windows[i].type++; } windows[i].height = tmp & 0xff; if (windows[i].type < 0 || windows[i].type >= NUM_WINDOW_TYPES || windows[i].height < 3) { /* Broken window? */ num_windows = -1; break; } } /* last window's size < 3 lines? */ if (num_windows == -1) { /* Fall back to defaults */ num_windows = 3; windows[0].type = 0; /* samples */ windows[0].height = 19; windows[1].type = 9; /* active channels */ windows[1].height = 3; windows[2].type = 6; /* 24chn track view */ windows[2].height = 15; } for (i = 0; i < num_windows; i++) { windows[i].first_channel = 1; } recalculate_windows(); if (status.current_page == PAGE_INFO) status.flags |= NEED_UPDATE; } void cfg_load_info(cfg_file_t *cfg) { int n; char buf[256]; char *left, *right; size_t len; if (!cfg_get_string(cfg, "Info Page", "layout", buf, 255, NULL)) { cfg_load_info_old(cfg); return; } left = buf; num_windows = 0; do { left += strspn(left, " \t"); len = strcspn(left, " \t"); if (!len) { break; } left[len] = '\0'; // chop it into pieces windows[num_windows].first_channel = 1; windows[num_windows].type = -1; for (n = 0; n < NUM_WINDOW_TYPES; n++) { if (strcasecmp(window_types[n].id, left) == 0) { windows[num_windows].type = n; break; } } // (a pythonic for...else would be lovely right about here) if (windows[num_windows].type == -1) { break; } right = left + len + 1; left[len] = '\0'; n = strtol(right, &left, 10); if (!left || left == right || n < 3) { // failed to parse any digits, or number is too small break; } windows[num_windows++].height = n; } while (num_windows < MAX_WINDOWS - 1); recalculate_windows(); if (status.current_page == PAGE_INFO) status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ static void info_page_redraw(void) { int n, height, pos = (window_types[windows[0].type].first_row ? 13 : 12); for (n = 0; n < num_windows - 1; n++) { height = windows[n].height; if (pos == 12) height++; window_types[windows[n].type].draw(pos, height, (n == selected_window), windows[n].first_channel); pos += height; } /* the last window takes up all the rest of the screen */ window_types[windows[n].type].draw(pos, 50 - pos, (n == selected_window), windows[n].first_channel); } /* --------------------------------------------------------------------- */ static int info_page_handle_key(struct key_event * k) { int n, p, order; if (k->mouse == MOUSE_CLICK || k->mouse == MOUSE_DBLCLICK) { p = selected_channel; n = info_handle_click(k->x, k->y); if (k->mouse == MOUSE_DBLCLICK) { if (p == selected_channel) { set_current_channel(selected_channel); order = song_get_current_order(); if (song_get_mode() == MODE_PLAYING) { n = current_song->orderlist[order]; } else { n = song_get_playing_pattern(); } if (n < 200) { set_current_order(order); set_current_pattern(n); set_current_row(song_get_current_row()); set_page(PAGE_PATTERN_EDITOR); } } } return n; } /* hack to render this useful :) */ if (k->sym == SCHISM_KEYSYM_KP_9) { k->sym = SCHISM_KEYSYM_F9; } else if (k->sym == SCHISM_KEYSYM_KP_0) { k->sym = SCHISM_KEYSYM_F10; } switch (k->sym) { case SCHISM_KEYSYM_g: if (k->state == KEY_PRESS) return 1; set_current_channel(selected_channel); order = song_get_current_order(); if (song_get_mode() == MODE_PLAYING) { n = current_song->orderlist[order]; } else { n = song_get_playing_pattern(); } if (n < 200) { set_current_order(order); set_current_pattern(n); set_current_row(song_get_current_row()); set_page(PAGE_PATTERN_EDITOR); } return 1; case SCHISM_KEYSYM_v: if (k->state == KEY_RELEASE) return 1; velocity_mode = !velocity_mode; status_text_flash("Using %s bars", (velocity_mode ? "velocity" : "volume")); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_i: if (k->state == KEY_RELEASE) return 1; instrument_names = !instrument_names; status_text_flash("Using %s names", (instrument_names ? "instrument" : "sample")); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_r: if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_RELEASE) return 1; song_flip_stereo(); status_text_flash("Left/right outputs reversed"); return 1; } return 0; case SCHISM_KEYSYM_EQUALS: if (!(k->mod & SCHISM_KEYMOD_SHIFT)) return 0; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_PLUS: if (k->state == KEY_RELEASE) return 1; if (song_get_mode() == MODE_PLAYING) { song_set_current_order(song_get_current_order() + 1); } return 1; case SCHISM_KEYSYM_MINUS: if (k->state == KEY_RELEASE) return 1; if (song_get_mode() == MODE_PLAYING) { song_set_current_order(song_get_current_order() - 1); } return 1; case SCHISM_KEYSYM_q: if (k->state == KEY_RELEASE) return 1; song_toggle_channel_mute(selected_channel - 1); orderpan_recheck_muted_channels(); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_s: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_ALT) { song_toggle_stereo(); status_text_flash("Stereo %s", song_is_stereo() ? "Enabled" : "Disabled"); } else { song_handle_channel_solo(selected_channel - 1); orderpan_recheck_muted_channels(); } status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_SPACE: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; song_toggle_channel_mute(selected_channel - 1); if (selected_channel < 64) selected_channel++; orderpan_recheck_muted_channels(); break; case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_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 SCHISM_KEYSYM_LEFT: if (!NO_MODIFIER(k->mod) && !(k->mod & SCHISM_KEYMOD_ALT)) return 0; if (k->state == KEY_RELEASE) return 1; if (selected_channel > 1) selected_channel--; break; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_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 SCHISM_KEYSYM_RIGHT: if (!NO_MODIFIER(k->mod) && !(k->mod & SCHISM_KEYMOD_ALT)) return 0; if (k->state == KEY_RELEASE) return 1; if (selected_channel < 64) selected_channel++; break; case SCHISM_KEYSYM_HOME: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; selected_channel = 1; break; case SCHISM_KEYSYM_END: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; selected_channel = song_find_last_channel(); break; case SCHISM_KEYSYM_INSERT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; /* add a new window, unless there's already five (the maximum) or if the current window isn't big enough to split in half. */ if (num_windows == MAX_WINDOWS || (windows[selected_window].height < 6)) { return 1; } num_windows++; /* shift the windows under the current one down */ memmove(windows + selected_window + 1, windows + selected_window, ((num_windows - selected_window - 1) * sizeof(*windows))); /* split the height between the two windows */ n = windows[selected_window].height; windows[selected_window].height = n / 2; windows[selected_window + 1].height = n / 2; if ((n & 1) && num_windows != 2) { /* odd number? compensate. (the selected window gets the extra line) */ windows[selected_window + 1].height++; } break; case SCHISM_KEYSYM_DELETE: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; /* delete the current window and give the extra space to the next window down. if this is the only window, well then don't delete it ;) */ if (num_windows == 1) return 1; n = windows[selected_window].height + windows[selected_window + 1].height; /* shift the windows under the current one up */ memmove(windows + selected_window, windows + selected_window + 1, ((num_windows - selected_window - 1) * sizeof(*windows))); /* fix the current window's height */ windows[selected_window].height = n; num_windows--; if (selected_window == num_windows) selected_window--; break; case SCHISM_KEYSYM_PAGEUP: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; n = windows[selected_window].type; if (n == 0) n = NUM_WINDOW_TYPES; n--; windows[selected_window].type = n; break; case SCHISM_KEYSYM_PAGEDOWN: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; windows[selected_window].type = (windows[selected_window].type + 1) % NUM_WINDOW_TYPES; break; case SCHISM_KEYSYM_TAB: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_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 SCHISM_KEYSYM_F9: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_ALT) { song_toggle_channel_mute(selected_channel - 1); orderpan_recheck_muted_channels(); return 1; } return 0; case SCHISM_KEYSYM_F10: if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_RELEASE) return 1; song_handle_channel_solo(selected_channel - 1); orderpan_recheck_muted_channels(); return 1; } return 0; default: return 0; } recalculate_windows(); status.flags |= NEED_UPDATE; return 1; } /* --------------------------------------------------------------------- */ static void info_page_playback_update(void) { if (song_get_mode() != MODE_STOPPED) status.flags |= NEED_UPDATE; } void info_load_page(struct page *page) { page->title = "Info Page (F5)"; page->playback_update = info_page_playback_update; page->total_widgets = 1; page->widgets = widgets_info; page->help_index = HELP_INFO_PAGE; widget_create_other(widgets_info + 0, 0, info_page_handle_key, NULL, info_page_redraw); } schismtracker-20250313/schism/page_instruments.c000066400000000000000000002701661476471630300216650ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* This is getting almost as disturbing as the pattern editor. */ #include "headers.h" #include "config.h" #include "it.h" #include "page.h" #include "song.h" #include "dmoz.h" #include "video.h" #include "keyboard.h" #include "fakemem.h" #include "widget.h" #include "dialog.h" #include "vgamem.h" #include "osdefs.h" #include "mem.h" #include "str.h" #include /* --------------------------------------------------------------------- */ /* just one global variable... */ int instrument_list_subpage = PAGE_INSTRUMENT_LIST_GENERAL; /* --------------------------------------------------------------------- */ /* ... but tons o' ugly statics */ static struct widget widgets_general[18]; static struct widget widgets_volume[17]; static struct widget widgets_panning[19]; static struct widget widgets_pitch[20]; /* rastops for envelope */ static struct vgamem_overlay env_overlay = { 32, 18, 65, 25, NULL, 0, 0, 0 }; /* toggled when pressing "," on the note table's sample field * more of a boolean than a bit mask -delt. */ static int note_sample_mask = 1; static struct widget *get_page_widgets(void) { switch (instrument_list_subpage) { case PAGE_INSTRUMENT_LIST_GENERAL: return widgets_general; case PAGE_INSTRUMENT_LIST_VOLUME: return widgets_volume; case PAGE_INSTRUMENT_LIST_PANNING: return widgets_panning; case PAGE_INSTRUMENT_LIST_PITCH: return widgets_pitch; }; return widgets_general; } static const int subpage_switches_group[5] = { 1, 2, 3, 4, -1 }; static const int nna_group[5] = { 6, 7, 8, 9, -1 }; static const int dct_group[5] = { 10, 11, 12, 13, -1 }; static const int dca_group[4] = { 14, 15, 16, -1 }; static const char *const pitch_envelope_states[] = { "Off", "On Pitch", "On Filter", NULL }; static int top_instrument = 1; static int current_instrument = 1; static int _altswap_lastvis = 99; // for alt-down instrument-swapping static int instrument_cursor_pos = 25; /* "play" mode */ static int note_trans_top_line = 0; static int note_trans_sel_line = 0; static int note_trans_cursor_pos = 0; /* shared by all the numentries on a page * (0 = volume, 1 = panning, 2 = pitch) */ static int numentry_cursor_pos[3] = { 0 }; static int current_node_vol = 0; static int current_node_pan = 0; static int current_node_pitch = 0; static int envelope_edit_mode = 0; static int envelope_mouse_edit = 0; static int envelope_tick_limit = 0; static void set_subpage(int page); /* playback */ static int last_note = 61; /* C-5 */ /* strange saved envelopes */ static song_envelope_t saved_env[10]; static unsigned int flags[10] = {0}; /* --------------------------------------------------------------------------------------------------------- */ static void save_envelope(int slot, song_envelope_t *e, unsigned int sec) { song_instrument_t *ins; slot = ((unsigned)slot)%10; ins = song_get_instrument(current_instrument); memcpy(&saved_env[slot], e, sizeof(song_envelope_t)); switch (sec) { case ENV_VOLUME: flags[slot] = ins->flags & (ENV_VOLUME|ENV_VOLSUSTAIN|ENV_VOLLOOP|ENV_VOLCARRY); break; case ENV_PANNING: flags[slot] = ((ins->flags & ENV_PANNING) ? ENV_VOLUME : 0) | ((ins->flags & ENV_PANSUSTAIN) ? ENV_VOLSUSTAIN : 0) | ((ins->flags & ENV_PANLOOP) ? ENV_VOLLOOP : 0) | ((ins->flags & ENV_PANCARRY) ? ENV_VOLCARRY : 0) | (ins->flags & ENV_SETPANNING); break; case ENV_PITCH: flags[slot] = ((ins->flags & ENV_PITCH) ? ENV_VOLUME : 0) | ((ins->flags & ENV_PITCHSUSTAIN) ? ENV_VOLSUSTAIN : 0) | ((ins->flags & ENV_PITCHLOOP) ? ENV_VOLLOOP : 0) | ((ins->flags & ENV_PITCHCARRY) ? ENV_VOLCARRY : 0) | (ins->flags & ENV_FILTER); break; }; } static void restore_envelope(int slot, song_envelope_t *e, unsigned int sec) { song_instrument_t *ins; song_lock_audio(); slot = ((unsigned)slot)%10; ins = song_get_instrument(current_instrument); memcpy(e, &saved_env[slot], sizeof(song_envelope_t)); switch (sec) { case ENV_VOLUME: ins->flags &= ~(ENV_VOLUME|ENV_VOLSUSTAIN|ENV_VOLLOOP|ENV_VOLCARRY); ins->flags |= (flags[slot] & (ENV_VOLUME|ENV_VOLSUSTAIN|ENV_VOLLOOP|ENV_VOLCARRY)); break; case ENV_PANNING: ins->flags &= ~(ENV_PANNING|ENV_PANSUSTAIN|ENV_PANLOOP|ENV_PANCARRY|ENV_SETPANNING); if (flags[slot] & ENV_VOLUME) ins->flags |= ENV_PANNING; if (flags[slot] & ENV_VOLSUSTAIN) ins->flags |= ENV_PANSUSTAIN; if (flags[slot] & ENV_VOLLOOP) ins->flags |= ENV_PANLOOP; if (flags[slot] & ENV_VOLCARRY) ins->flags |= ENV_PANCARRY; ins->flags |= (flags[slot] & ENV_SETPANNING); break; case ENV_PITCH: ins->flags &= ~(ENV_PITCH|ENV_PITCHSUSTAIN|ENV_PITCHLOOP|ENV_PITCHCARRY|ENV_FILTER); if (flags[slot] & ENV_VOLUME) ins->flags |= ENV_PITCH; if (flags[slot] & ENV_VOLSUSTAIN) ins->flags |= ENV_PITCHSUSTAIN; if (flags[slot] & ENV_VOLLOOP) ins->flags |= ENV_PITCHLOOP; if (flags[slot] & ENV_VOLCARRY) ins->flags |= ENV_PITCHCARRY; ins->flags |= (flags[slot] & ENV_FILTER); break; }; song_unlock_audio(); status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------------------------------------------- */ static void instrument_list_draw_list(void); /* --------------------------------------------------------------------------------------------------------- */ static int _last_vis_inst(void) { int i, j, n; n = 99; j = 0; /* 65 is first visible sample on last page */ for (i = 65; i < MAX_INSTRUMENTS; i++) { if (!csf_instrument_is_empty(current_song->instruments[i])) { j = i; } } while ((j + 34) > n) n += 34; return MIN(n, MAX_INSTRUMENTS - 1); } /* the actual list */ static void instrument_list_reposition(void) { if (current_instrument < top_instrument) { top_instrument = current_instrument; if (top_instrument < 1) { top_instrument = 1; } } else if (current_instrument > top_instrument + 34) { top_instrument = current_instrument - 34; } } int instrument_get_current(void) { return current_instrument; } void instrument_set(int n) { int new_ins = n; song_instrument_t *ins; if (page_is_instrument_list(status.current_page)) { new_ins = CLAMP(n, 1, _last_vis_inst()); } else { new_ins = CLAMP(n, 0, _last_vis_inst()); } if (current_instrument == new_ins) return; envelope_edit_mode = 0; current_instrument = new_ins; instrument_list_reposition(); ins = song_get_instrument(current_instrument); current_node_vol = ins->vol_env.nodes ? CLAMP(current_node_vol, 0, ins->vol_env.nodes - 1) : 0; current_node_pan = ins->pan_env.nodes ? CLAMP(current_node_vol, 0, ins->pan_env.nodes - 1) : 0; current_node_pitch = ins->pitch_env.nodes ? CLAMP(current_node_vol, 0, ins->pan_env.nodes - 1) : 0; status.flags |= NEED_UPDATE; } void instrument_synchronize_to_sample(void) { song_instrument_t *ins; int sample = sample_get_current(); int n, pos; /* 1. if the instrument with the same number as the current sample * has the sample in its sample_map, change to that instrument. */ ins = song_get_instrument(sample); for (pos = 0; pos < 120; pos++) { if ((int)(ins->sample_map[pos]) == sample) { instrument_set(sample); return; } } /* 2. look through the instrument list for the first instrument * that uses the selected sample. */ for (n = 1; n < 100; n++) { if (n == sample) continue; ins = song_get_instrument(n); for (pos = 0; pos < 120; pos++) { if ((int)(ins->sample_map[pos]) == sample) { instrument_set(n); return; } } } /* 3. if no instruments are using the sample, just change to the * same-numbered instrument. */ instrument_set(sample); } /* --------------------------------------------------------------------- */ static int instrument_list_add_char(int c) { song_instrument_t *ins; if (c < 32) return 0; ins = song_get_instrument(current_instrument); text_add_char(ins->name, c, &instrument_cursor_pos, 25); if (instrument_cursor_pos == 25) instrument_cursor_pos--; get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; return 1; } static void instrument_list_delete_char(void) { song_instrument_t *ins = song_get_instrument(current_instrument); text_delete_char(ins->name, &instrument_cursor_pos, 25); get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; } static void instrument_list_delete_next_char(void) { song_instrument_t *ins = song_get_instrument(current_instrument); text_delete_next_char(ins->name, &instrument_cursor_pos, 25); get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; } static void clear_instrument_text(void) { song_instrument_t *ins = song_get_instrument(current_instrument); memset(ins->filename, 0, 14); memset(ins->name, 0, 26); if (instrument_cursor_pos != 25) instrument_cursor_pos = 0; get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ static void do_swap_instrument(int n) { if (n >= 1 && n <= _last_vis_inst()) { song_swap_instruments(current_instrument, n); } } static void do_exchange_instrument(int n) { if (n >= 1 && n <= _last_vis_inst()) { song_exchange_instruments(current_instrument, n); } } static void do_copy_instrument(int n) { if (n >= 1 && n <= _last_vis_inst()) { song_copy_instrument(current_instrument, n); } } static void do_replace_instrument(int n) { if (n >= 1 && n <= _last_vis_inst()) { song_replace_instrument(current_instrument, n); } } /* --------------------------------------------------------------------- */ static void instrument_list_draw_list(void) { int pos, n; song_instrument_t *ins; int selected = (ACTIVE_PAGE.selected_widget == 0); int is_current; int ss, cl = 0, cr = 0; int is_playing[MAX_INSTRUMENTS]; char buf[4]; ss = -1; song_get_playing_instruments(is_playing); for (pos = 0, n = top_instrument; pos < 35; pos++, n++) { ins = song_get_instrument(n); is_current = (n == current_instrument); if (ins->played) draw_char(is_playing[n] > 1 ? 183 : 173, 1, 13 + pos, is_playing[n] ? 3 : 1, 2); draw_text(str_from_num99(n, buf), 2, 13 + pos, 0, 2); if (instrument_cursor_pos < 25) { /* it's in edit mode */ if (is_current) { draw_text_len(ins->name, 25, 5, 13 + pos, 6, 14); if (selected) { draw_char(ins->name[instrument_cursor_pos], 5 + instrument_cursor_pos, 13 + pos, 0, 3); } } else { draw_text_len(ins->name, 25, 5, 13 + pos, 6, 0); } } else { draw_text_len(ins->name, 25, 5, 13 + pos, ((is_current && selected) ? 0 : 6), (is_current ? (selected ? 3 : 14) : 0)); } if (ss == n) { draw_text_len(ins->name + cl, (cr-cl)+1, 5 + cl, 13 + pos, (is_current ? 3 : 11), 8); } } } static int instrument_list_handle_text_input_on_list(const char* text) { int success = 0; for (; *text; text++) if (instrument_cursor_pos < 25 && instrument_list_add_char(*(unsigned char *)text)) success = 1; return success; } static int instrument_list_handle_key_on_list(struct key_event * k) { int new_ins = current_instrument; if (k->state == KEY_PRESS && k->mouse != MOUSE_NONE && k->y >= 13 && k->y <= 47 && k->x >= 5 && k->x <= 30) { if (k->mouse == MOUSE_CLICK) { new_ins = (k->y - 13) + top_instrument; if (instrument_cursor_pos < 25) instrument_cursor_pos = MIN(k->x - 5, 24); status.flags |= NEED_UPDATE; } else if (k->mouse == MOUSE_DBLCLICK) { /* this doesn't seem to work, but I think it'd be more useful if double click switched to edit mode */ if (instrument_cursor_pos < 25) { instrument_cursor_pos = 25; get_page_widgets()->accept_text = 0; } else { set_page(PAGE_LOAD_INSTRUMENT); } status.flags |= NEED_UPDATE; return 1; } else if (k->mouse == MOUSE_SCROLL_UP) { top_instrument -= MOUSE_SCROLL_LINES; if (top_instrument < 1) top_instrument = 1; status.flags |= NEED_UPDATE; return 1; } else if (k->mouse == MOUSE_SCROLL_DOWN) { top_instrument += MOUSE_SCROLL_LINES; if (top_instrument > (_last_vis_inst()-34)) top_instrument = _last_vis_inst()-34; status.flags |= NEED_UPDATE; return 1; } } else { switch (k->sym) { case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_ALT) { if (current_instrument > 1) { new_ins = current_instrument - 1; song_swap_instruments(current_instrument, new_ins); } } else if (!NO_MODIFIER(k->mod)) { return 0; } else { new_ins--; } break; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_ALT) { // restrict position to the "old" value of _last_vis_inst() // (this is entirely for aesthetic reasons) if (status.last_keysym != SCHISM_KEYSYM_DOWN && !k->is_repeat) _altswap_lastvis = _last_vis_inst(); if (current_instrument < _altswap_lastvis) { new_ins = current_instrument + 1; song_swap_instruments(current_instrument, new_ins); } } else if (!NO_MODIFIER(k->mod)) { return 0; } else { new_ins++; } break; case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) new_ins = 1; else new_ins -= 16; break; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) new_ins = _last_vis_inst(); else new_ins += 16; break; case SCHISM_KEYSYM_HOME: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; if (instrument_cursor_pos < 25) { instrument_cursor_pos = 0; get_page_widgets()->accept_text = 1; status.flags |= NEED_UPDATE; } return 1; case SCHISM_KEYSYM_END: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; if (instrument_cursor_pos < 24) { instrument_cursor_pos = 24; get_page_widgets()->accept_text = 1; status.flags |= NEED_UPDATE; } return 1; case SCHISM_KEYSYM_LEFT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; if (instrument_cursor_pos < 25 && instrument_cursor_pos > 0) { instrument_cursor_pos--; get_page_widgets()->accept_text = 1; status.flags |= NEED_UPDATE; } return 1; case SCHISM_KEYSYM_RIGHT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; if (instrument_cursor_pos == 25) { get_page_widgets()->accept_text = 0; widget_change_focus_to(1); } else if (instrument_cursor_pos < 24) { get_page_widgets()->accept_text = 1; instrument_cursor_pos++; status.flags |= NEED_UPDATE; } return 1; case SCHISM_KEYSYM_RETURN: if (k->state == KEY_PRESS) return 0; if (instrument_cursor_pos < 25) { instrument_cursor_pos = 25; get_page_widgets()->accept_text = 0; status.flags |= NEED_UPDATE; } else { get_page_widgets()->accept_text = 1; set_page(PAGE_LOAD_INSTRUMENT); } return 1; case SCHISM_KEYSYM_ESCAPE: if ((k->mod & SCHISM_KEYMOD_SHIFT) || instrument_cursor_pos < 25) { if (k->state == KEY_RELEASE) return 1; instrument_cursor_pos = 25; get_page_widgets()->accept_text = 0; status.flags |= NEED_UPDATE; return 1; } return 0; case SCHISM_KEYSYM_BACKSPACE: if (k->state == KEY_RELEASE) return 0; if (instrument_cursor_pos == 25) return 0; if ((k->mod & (SCHISM_KEYMOD_CTRL | SCHISM_KEYMOD_ALT)) == 0) instrument_list_delete_char(); else if (k->mod & SCHISM_KEYMOD_CTRL) instrument_list_add_char(127); return 1; case SCHISM_KEYSYM_INSERT: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_ALT) { song_insert_instrument_slot(current_instrument); status.flags |= NEED_UPDATE; return 1; } return 0; case SCHISM_KEYSYM_DELETE: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_ALT) { song_remove_instrument_slot(current_instrument); status.flags |= NEED_UPDATE; return 1; } else if ((k->mod & SCHISM_KEYMOD_CTRL) == 0) { if (instrument_cursor_pos == 25) return 0; instrument_list_delete_next_char(); return 1; } return 0; default: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_ALT) { if (k->sym == SCHISM_KEYSYM_c) { clear_instrument_text(); return 1; } } else if ((k->mod & SCHISM_KEYMOD_CTRL) == 0) { if (instrument_cursor_pos < 25) { if (k->text) return instrument_list_handle_text_input_on_list(k->text); } else if (k->sym == SCHISM_KEYSYM_SPACE) { instrument_cursor_pos = 0; get_page_widgets()->accept_text = 0; status.flags |= NEED_UPDATE; memused_songchanged(); return 1; } } return 0; }; } new_ins = CLAMP(new_ins, 1, _last_vis_inst()); if (new_ins != current_instrument) { instrument_set(new_ins); status.flags |= NEED_UPDATE; memused_songchanged(); } return 1; } /* --------------------------------------------------------------------- */ /* note translation table */ static void note_trans_reposition(void) { if (note_trans_sel_line < note_trans_top_line) { note_trans_top_line = note_trans_sel_line; } else if (note_trans_sel_line > note_trans_top_line + 31) { note_trans_top_line = note_trans_sel_line - 31; } } static void note_trans_draw(void) { int pos, n; int is_selected = (ACTIVE_PAGE.selected_widget == 5); int bg, sel_bg = (is_selected ? 14 : 0); song_instrument_t *ins = song_get_instrument(current_instrument); char buf[4]; for (pos = 0, n = note_trans_top_line; pos < 32; pos++, n++) { bg = ((n == note_trans_sel_line) ? sel_bg : 0); /* invalid notes are translated to themselves (and yes, this edits the actual instrument) */ if (ins->note_map[n] < 1 || ins->note_map[n] > 120) ins->note_map[n] = n + 1; draw_text(get_note_string(n + 1, buf), 32, 16 + pos, 2, bg); draw_char(168, 35, 16 + pos, 2, bg); draw_text(get_note_string(ins->note_map[n], buf), 36, 16 + pos, 2, bg); if (is_selected && n == note_trans_sel_line) { if (note_trans_cursor_pos == 0) draw_char(buf[0], 36, 16 + pos, 0, 3); else if (note_trans_cursor_pos == 1) draw_char(buf[2], 38, 16 + pos, 0, 3); } draw_char(0, 39, 16 + pos, 2, bg); if (ins->sample_map[n]) { str_from_num99(ins->sample_map[n], buf); } else { buf[0] = buf[1] = '\xAD'; buf[2] = 0; } draw_text(buf, 40, 16 + pos, 2, bg); if (is_selected && n == note_trans_sel_line) { if (note_trans_cursor_pos == 2) draw_char(buf[0], 40, 16 + pos, 0, 3); else if (note_trans_cursor_pos == 3) draw_char(buf[1], 41, 16 + pos, 0, 3); } } /* draw the little mask thingy at the bottom. Could optimize this.... -delt. Sure can! This could share the same track-view functions that the pattern editor ought to be using. -Storlek */ if (is_selected && !(status.flags & CLASSIC_MODE)) { switch (note_trans_cursor_pos) { case 0: draw_char(171, 36, 48, 3, 2); draw_char(171, 37, 48, 3, 2); draw_char(169, 38, 48, 3, 2); if (note_sample_mask) { draw_char(169, 40, 48, 3, 2); draw_char(169, 41, 48, 3, 2); } break; case 1: draw_char(169, 38, 48, 3, 2); if (note_sample_mask) { draw_char(170, 40, 48, 3, 2); draw_char(170, 41, 48, 3, 2); } break; case 2: case 3: draw_char(note_sample_mask ? 171 : 169, 40, 48, 3, 2); draw_char(note_sample_mask ? 171 : 169, 41, 48, 3, 2); break; }; } } static void instrument_note_trans_transpose(song_instrument_t *ins, int dir) { int i; for (i = 0; i < 120; i++) { ins->note_map[i] = CLAMP(ins->note_map[i]+dir, 1, 120); } } static void instrument_note_trans_insert(song_instrument_t *ins, int pos) { int i; for (i = 119; i > pos; i--) { ins->note_map[i] = ins->note_map[i-1]; ins->sample_map[i] = ins->sample_map[i-1]; } if (pos) { ins->note_map[pos] = ins->note_map[pos-1]+1; } else { ins->note_map[0] = 1; } } static void instrument_note_trans_delete(song_instrument_t *ins, int pos) { int i; for (i = pos; i < 120; i++) { ins->note_map[i] = ins->note_map[i+1]; ins->sample_map[i] = ins->sample_map[i+1]; } ins->note_map[119] = ins->note_map[118]+1; } static int note_trans_handle_key(struct key_event * k) { int prev_line = note_trans_sel_line; int new_line = prev_line; int prev_pos = note_trans_cursor_pos; int new_pos = prev_pos; song_instrument_t *ins = song_get_instrument(current_instrument); int c, n; if (k->mouse == MOUSE_CLICK && k->mouse_button == MOUSE_BUTTON_MIDDLE) { if (k->state == KEY_RELEASE) status.flags |= CLIPPY_PASTE_SELECTION; return 1; } else if (k->mouse == MOUSE_SCROLL_UP || k->mouse == MOUSE_SCROLL_DOWN) { if (k->state == KEY_PRESS) { note_trans_top_line += (k->mouse == MOUSE_SCROLL_UP) ? -3 : 3; note_trans_top_line = CLAMP(note_trans_top_line, 0, 119 - 31); status.flags |= NEED_UPDATE; } return 1; } else if (k->mouse != MOUSE_NONE) { if (k->x >= 32 && k->x <= 41 && k->y >= 16 && k->y <= 47) { new_line = note_trans_top_line + k->y - 16; if (new_line == prev_line) { switch (k->x - 36) { case 2: new_pos = 1; break; case 4: new_pos = 2; break; case 5: new_pos = 3; break; default: new_pos = 0; break; }; } } } else if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_RELEASE) return 0; switch (k->sym) { case SCHISM_KEYSYM_UP: instrument_note_trans_transpose(ins, 1); break; case SCHISM_KEYSYM_DOWN: instrument_note_trans_transpose(ins, -1); break; case SCHISM_KEYSYM_INSERT: instrument_note_trans_insert(ins, note_trans_sel_line); break; case SCHISM_KEYSYM_DELETE: instrument_note_trans_delete(ins, note_trans_sel_line); break; case SCHISM_KEYSYM_n: n = note_trans_sel_line - 1; // the line to copy *from* if (n < 0 || ins->note_map[n] == NOTE_LAST) break; ins->note_map[note_trans_sel_line] = ins->note_map[n] + 1; ins->sample_map[note_trans_sel_line] = ins->sample_map[n]; new_line++; break; case SCHISM_KEYSYM_p: n = note_trans_sel_line + 1; // the line to copy *from* if (n > (NOTE_LAST - NOTE_FIRST) || ins->note_map[n] == NOTE_FIRST) break; ins->note_map[note_trans_sel_line] = ins->note_map[n] - 1; ins->sample_map[note_trans_sel_line] = ins->sample_map[n]; new_line--; break; case SCHISM_KEYSYM_a: c = sample_get_current(); for (n = 0; n < (NOTE_LAST - NOTE_FIRST + 1); n++) ins->sample_map[n] = c; if (k->mod & SCHISM_KEYMOD_SHIFT) { // Copy the name too. memcpy(ins->name, current_song->samples[c].name, 32); } break; default: return 0; } } else { switch (k->sym) { case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) sample_set(sample_get_current () - 1); if (!NO_MODIFIER(k->mod)) return 0; if (--new_line < 0) { widget_change_focus_to(1); return 1; } break; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) sample_set(sample_get_current () + 1); if (!NO_MODIFIER(k->mod)) return 0; new_line++; break; case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { instrument_set(current_instrument - 1); return 1; } new_line -= 16; break; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { instrument_set(current_instrument + 1); return 1; } new_line += 16; break; case SCHISM_KEYSYM_HOME: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_line = 0; break; case SCHISM_KEYSYM_END: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_line = 119; break; case SCHISM_KEYSYM_LEFT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_pos--; break; case SCHISM_KEYSYM_RIGHT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_pos++; break; case SCHISM_KEYSYM_RETURN: if (k->state == KEY_PRESS) return 0; if (!NO_MODIFIER(k->mod)) return 0; sample_set(ins->sample_map[note_trans_sel_line]); get_page_widgets()->accept_text = (instrument_cursor_pos == 25 ? 0 : 1); return 1; case SCHISM_KEYSYM_LESS: case SCHISM_KEYSYM_SEMICOLON: case SCHISM_KEYSYM_COLON: if (k->state == KEY_RELEASE) return 0; sample_set(sample_get_current() - 1); return 1; case SCHISM_KEYSYM_GREATER: case SCHISM_KEYSYM_QUOTE: case SCHISM_KEYSYM_QUOTEDBL: if (k->state == KEY_RELEASE) return 0; sample_set(sample_get_current() + 1); return 1; default: if (k->state == KEY_RELEASE) return 0; switch (note_trans_cursor_pos) { case 0: /* note */ n = kbd_get_note(k); if (!NOTE_IS_NOTE(n)) return 0; ins->note_map[note_trans_sel_line] = n; if (note_sample_mask || (status.flags & CLASSIC_MODE)) ins->sample_map[note_trans_sel_line] = sample_get_current(); new_line++; break; case 1: /* octave */ c = kbd_char_to_hex(k); if (c < 0 || c > 9) return 0; n = ins->note_map[note_trans_sel_line]; n = ((n - 1) % 12) + (12 * c) + 1; ins->note_map[note_trans_sel_line] = n; new_line++; break; /* Made it possible to enter H to R letters on 1st digit for expanded sample slots. -delt. */ case 2: /* instrument, first digit */ case 3: /* instrument, second digit */ if (k->sym == SCHISM_KEYSYM_SPACE) { ins->sample_map[note_trans_sel_line] = sample_get_current(); new_line++; break; } if ((k->sym == SCHISM_KEYSYM_PERIOD && NO_MODIFIER(k->mod)) || k->sym == SCHISM_KEYSYM_DELETE) { ins->sample_map[note_trans_sel_line] = 0; new_line += (k->sym == SCHISM_KEYSYM_PERIOD) ? 1 : 0; break; } if (k->sym == SCHISM_KEYSYM_COMMA && NO_MODIFIER(k->mod)) { note_sample_mask = note_sample_mask ? 0 : 1; break; } n = ins->sample_map[note_trans_sel_line]; if (note_trans_cursor_pos == 2) { c = kbd_char_to_99(k); if (c < 0) return 0; n = (c * 10) + (n % 10); new_pos++; } else { c = kbd_char_to_hex(k); if (c < 0 || c > 9) return 0; n = ((n / 10) * 10) + c; new_pos--; new_line++; } n = MIN(n, MAX_SAMPLES - 1); ins->sample_map[note_trans_sel_line] = n; sample_set(n); break; } break; } } new_line = CLAMP(new_line, 0, 119); note_trans_cursor_pos = CLAMP(new_pos, 0, 3); if (new_line != prev_line) { note_trans_sel_line = new_line; note_trans_reposition(); } /* this causes unneeded redraws in some cases... oh well :P */ status.flags |= NEED_UPDATE; return 1; } /* --------------------------------------------------------------------------------------------------------- */ /* envelope helper functions */ static void _env_draw_axes(int middle) { int n, y = middle ? 31 : 62; for (n = 0; n < 64; n += 2) vgamem_ovl_drawpixel(&env_overlay, 3, n, 12); for (n = 0; n < 256; n += 2) vgamem_ovl_drawpixel(&env_overlay, 1 + n, y, 12); } static void _env_draw_node(int x, int y, int on) { int c = (status.flags & CLASSIC_MODE) ? 12 : 5; vgamem_ovl_drawpixel(&env_overlay, x - 1, y - 1, c); vgamem_ovl_drawpixel(&env_overlay, x - 1, y, c); vgamem_ovl_drawpixel(&env_overlay, x - 1, y + 1, c); vgamem_ovl_drawpixel(&env_overlay, x, y - 1, c); vgamem_ovl_drawpixel(&env_overlay, x, y, c); vgamem_ovl_drawpixel(&env_overlay, x, y + 1, c); vgamem_ovl_drawpixel(&env_overlay, x + 1, y - 1,c); vgamem_ovl_drawpixel(&env_overlay, x + 1, y,c); vgamem_ovl_drawpixel(&env_overlay, x + 1, y + 1,c); if (on) { vgamem_ovl_drawpixel(&env_overlay, x - 3, y - 1,c); vgamem_ovl_drawpixel(&env_overlay, x - 3, y,c); vgamem_ovl_drawpixel(&env_overlay, x - 3, y + 1,c); vgamem_ovl_drawpixel(&env_overlay, x + 3, y - 1,c); vgamem_ovl_drawpixel(&env_overlay, x + 3, y,c); vgamem_ovl_drawpixel(&env_overlay, x + 3, y + 1,c); } } static void _env_draw_loop(int xs, int xe, int sustain) { int y = 0; int c = (status.flags & CLASSIC_MODE) ? 12 : 3; if (sustain) { while (y < 62) { /* unrolled once */ vgamem_ovl_drawpixel(&env_overlay, xs, y, c); vgamem_ovl_drawpixel(&env_overlay, xe, y, c); y++; vgamem_ovl_drawpixel(&env_overlay, xs, y, 0); vgamem_ovl_drawpixel(&env_overlay, xe, y, 0); y++; vgamem_ovl_drawpixel(&env_overlay, xs, y, c); vgamem_ovl_drawpixel(&env_overlay, xe, y, c); y++; vgamem_ovl_drawpixel(&env_overlay, xs, y, 0); vgamem_ovl_drawpixel(&env_overlay, xe, y, 0); y++; } } else { while (y < 62) { vgamem_ovl_drawpixel(&env_overlay, xs, y, 0); vgamem_ovl_drawpixel(&env_overlay, xe, y, 0); y++; vgamem_ovl_drawpixel(&env_overlay, xs, y, c); vgamem_ovl_drawpixel(&env_overlay, xe, y, c); y++; vgamem_ovl_drawpixel(&env_overlay, xs, y, c); vgamem_ovl_drawpixel(&env_overlay, xe, y, c); y++; vgamem_ovl_drawpixel(&env_overlay, xs, y, 0); vgamem_ovl_drawpixel(&env_overlay, xe, y, 0); y++; } } } static void _env_draw(const song_envelope_t *env, int middle, int current_node, int env_on, int loop_on, int sustain_on, int env_num) { song_voice_t *channel; uint32_t *channel_list; char buf[16]; unsigned int envpos[3]; int x, y, n, m, c; int last_x = 0, last_y = 0; int max_ticks = 50; while (env->ticks[env->nodes - 1] >= max_ticks) max_ticks *= 2; vgamem_ovl_clear(&env_overlay, 0); /* draw the axis lines */ _env_draw_axes(middle); for (n = 0; n < env->nodes; n++) { x = 4 + env->ticks[n] * 256 / max_ticks; /* 65 values are being crammed into 62 pixels => have to lose three pixels somewhere. * This is where IT compromises -- I don't quite get how the lines are drawn, though, * because it changes for each value... (apart from drawing 63 and 64 the same way) */ y = env->values[n]; if (y > 63) y--; if (y > 42) y--; if (y > 21) y--; y = 62 - y; _env_draw_node(x, y, n == current_node); if (last_x) vgamem_ovl_drawline(&env_overlay, last_x, last_y, x, y, 12); last_x = x; last_y = y; } if (sustain_on) _env_draw_loop(4 + env->ticks[env->sustain_start] * 256 / max_ticks, 4 + env->ticks[env->sustain_end] * 256 / max_ticks, 1); if (loop_on) _env_draw_loop(4 + env->ticks[env->loop_start] * 256 / max_ticks, 4 + env->ticks[env->loop_end] * 256 / max_ticks, 0); if (env_on) { max_ticks = env->ticks[env->nodes-1]; m = max_ticks ? song_get_mix_state(&channel_list) : 0; while (m--) { channel = song_get_mix_channel(channel_list[m]); if (channel->ptr_instrument != song_get_instrument(current_instrument)) continue; envpos[0] = channel->vol_env_position; envpos[1] = channel->pan_env_position; envpos[2] = channel->pitch_env_position; x = 4 + (envpos[env_num] * (last_x-4) / max_ticks); if (x > last_x) x = last_x; c = (status.flags & CLASSIC_MODE) ? 12 : ((channel->flags & (CHN_KEYOFF | CHN_NOTEFADE)) ? 8 : 6); for (y = 0; y < 62; y++) vgamem_ovl_drawpixel(&env_overlay, x, y, c); } } draw_fill_chars(65, 18, 76, 25, DEFAULT_FG, 0); vgamem_ovl_apply(&env_overlay); sprintf(buf, "Node %d/%d", current_node, env->nodes); draw_text(buf, 66, 19, 2, 0); sprintf(buf, "Tick %d", env->ticks[current_node]); draw_text(buf, 66, 21, 2, 0); sprintf(buf, "Value %d", (int)(env->values[current_node] - (middle ? 32 : 0))); draw_text(buf, 66, 23, 2, 0); } /* return: the new current node */ static int _env_node_add(song_envelope_t *env, int current_node, int override_tick, int override_value) { int newtick, newvalue; status.flags |= SONG_NEEDS_SAVE; if (env->nodes > 24 || current_node == env->nodes - 1) return current_node; newtick = (env->ticks[current_node] + env->ticks[current_node + 1]) / 2; newvalue = (env->values[current_node] + env->values[current_node + 1]) / 2; if (override_tick > -1 && override_value > -1) { newtick = override_tick; newvalue = override_value; } else if (newtick == env->ticks[current_node] || newtick == env->ticks[current_node + 1]) { printf("Not enough room!\n"); return current_node; } env->nodes++; memmove(env->ticks + current_node + 1, env->ticks + current_node, (env->nodes - current_node - 1) * sizeof(env->ticks[0])); memmove(env->values + current_node + 1, env->values + current_node, (env->nodes - current_node - 1) * sizeof(env->values[0])); env->ticks[current_node + 1] = newtick; env->values[current_node + 1] = newvalue; if (env->loop_end > current_node) env->loop_end++; if (env->loop_start > current_node) env->loop_start++; if (env->sustain_end > current_node) env->sustain_end++; if (env->sustain_start > current_node) env->sustain_start++; return current_node; } /* return: the new current node */ static int _env_node_remove(song_envelope_t *env, int current_node) { status.flags |= SONG_NEEDS_SAVE; if (current_node == 0 || env->nodes < 3) return current_node; memmove(env->ticks + current_node, env->ticks + current_node + 1, (env->nodes - current_node - 1) * sizeof(env->ticks[0])); memmove(env->values + current_node, env->values + current_node + 1, (env->nodes - current_node - 1) * sizeof(env->values[0])); env->nodes--; if (env->loop_start >= env->nodes) env->loop_start = env->nodes - 1; else if (env->loop_start > current_node) env->loop_start--; if (env->loop_end >= env->nodes) env->loop_end = env->nodes - 1; else if (env->loop_end > current_node) env->loop_end--; if (env->sustain_start >= env->nodes) env->sustain_start = env->nodes - 1; else if (env->sustain_start > current_node) env->sustain_start--; if (env->sustain_end >= env->nodes) env->sustain_end = env->nodes - 1; else if (env->sustain_end > current_node) env->sustain_end--; if (current_node >= env->nodes) current_node = env->nodes - 1; return current_node; } static void do_pre_loop_cut(void *ign) { song_envelope_t *env = (song_envelope_t *)ign; unsigned int bt; int i; bt = env->ticks[env->loop_start]; for (i = env->loop_start; i < 32; i++) { env->ticks[i - env->loop_start] = env->ticks[i] - bt; env->values[i - env->loop_start] = env->values[i]; } env->nodes -= env->loop_start; if (env->sustain_start > env->loop_start) { env->sustain_start -= env->loop_start; } else { env->sustain_start = 0; } if (env->sustain_end > env->loop_start) { env->sustain_end -= env->loop_start; } else { env->sustain_end = 0; } if (env->loop_end > env->loop_start) { env->loop_end -= env->loop_start; } else { env->loop_end = 0; } env->loop_start = 0; if (env->loop_start > env->loop_end) env->loop_end = env->loop_start; if (env->sustain_start > env->sustain_end) env->sustain_end = env->sustain_start; status.flags |= NEED_UPDATE; } static void do_post_loop_cut(void *ign) { song_envelope_t *env = (song_envelope_t *)ign; env->nodes = env->loop_end+1; } static void env_resize(song_envelope_t *env, int ticks) { int old = env->ticks[env->nodes - 1]; int n, t; if (ticks > 9999) ticks = 9999; for (n = 1; n < env->nodes; n++) { t = env->ticks[n] * ticks / old; env->ticks[n] = MAX(t, env->ticks[n - 1] + 1); } status.flags |= NEED_UPDATE; } static struct widget env_resize_widgets[2]; static int env_resize_cursor; static void do_env_resize(void *data) { env_resize((song_envelope_t *) data, env_resize_widgets[0].d.numentry.value); } static void env_resize_draw_const(void) { draw_text("Resize Envelope", 34, 24, 3, 2); draw_text("New Length", 31, 27, 0, 2); draw_box(41, 26, 49, 28, BOX_THICK | BOX_INNER | BOX_INSET); } static void env_resize_dialog(song_envelope_t *env) { struct dialog *dialog; env_resize_cursor = 0; widget_create_numentry(env_resize_widgets + 0, 42, 27, 7, 0, 1, 1, NULL, 0, 9999, &env_resize_cursor); env_resize_widgets[0].d.numentry.value = env->ticks[env->nodes - 1]; widget_create_button(env_resize_widgets + 1, 36, 30, 6, 0, 1, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); dialog = dialog_create_custom(26, 22, 29, 11, env_resize_widgets, 2, 0, env_resize_draw_const, env); dialog->action_yes = do_env_resize; } static struct widget env_adsr_widgets[4]; static int env_adsr_cursor = 0; static void do_env_adsr(void *data) { // FIXME | move env flags into the envelope itself, where they should be in the first place. // FIXME | then this nonsense can go away. song_instrument_t *ins = (song_instrument_t *) data; song_envelope_t *env = &ins->vol_env; int a = env_adsr_widgets[0].d.thumbbar.value; int d = env_adsr_widgets[1].d.thumbbar.value; int s = env_adsr_widgets[2].d.thumbbar.value; int r = env_adsr_widgets[3].d.thumbbar.value; int v1 = MAX(a, a * a / 16); int v2 = MAX(v1 + d * d / 16, v1 + d); int v3 = MAX(v2 + r * r / 4, v2 + r); int n = 0; if (a) { env->ticks[n] = 0; env->values[n++] = 0; } if (d) { env->ticks[n] = v1; env->values[n++] = 64; } env->sustain_start = env->sustain_end = n; env->ticks[n] = v2; env->values[n++] = s / 2; env->ticks[n] = v3; env->values[n++] = 0; env->nodes = n; for (n = 0; n < env->nodes - 1; n++) if (env->ticks[n] >= env->ticks[n + 1]) env->ticks[n + 1] = env->ticks[n] + 1; ins->flags |= ENV_VOLSUSTAIN | ENV_VOLUME; // arghhhhh } static void env_adsr_draw_const(void) { draw_text("Envelope Generator", 32, 22, 0, 2); draw_text("Attack", 27, 24, 0, 2); draw_text("Decay", 28, 25, 0, 2); draw_text("Sustain", 26, 26, 0, 2); draw_text("Release", 26, 27, 0, 2); draw_box(33, 23, 51, 28, BOX_THICK | BOX_INNER | BOX_INSET); } static void env_adsr_dialog(SCHISM_UNUSED song_envelope_t *env) { struct dialog *dialog; song_instrument_t *ins = song_get_instrument(current_instrument); // ARGHHH env_adsr_cursor = 0; widget_create_thumbbar(env_adsr_widgets + 0, 34, 24, 17, 4, 1, 4, NULL, 0, 128); widget_create_thumbbar(env_adsr_widgets + 1, 34, 25, 17, 0, 2, 4, NULL, 0, 128); widget_create_thumbbar(env_adsr_widgets + 2, 34, 26, 17, 1, 3, 4, NULL, 0, 128); widget_create_thumbbar(env_adsr_widgets + 3, 34, 27, 17, 2, 4, 4, NULL, 0, 128); widget_create_button(env_adsr_widgets + 4, 36, 30, 6, 3, 0, 4, 4, 0, dialog_cancel_NULL, "Cancel", 1); dialog = dialog_create_custom(25, 21, 31, 12, env_adsr_widgets, 5, 0, env_adsr_draw_const, ins); dialog->action_yes = do_env_adsr; } /* the return value here is actually a bitmask: r & 1 => the key was handled r & 2 => the envelope changed (i.e., it should be enabled) */ static int _env_handle_key_viewmode(struct key_event *k, song_envelope_t *env, int *current_node, unsigned int sec) { int new_node = *current_node; int n; switch (k->sym) { case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 0; widget_change_focus_to(1); return 1; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 0; widget_change_focus_to(6); return 1; case SCHISM_KEYSYM_LEFT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_node--; break; case SCHISM_KEYSYM_RIGHT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_node++; break; case SCHISM_KEYSYM_INSERT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; *current_node = _env_node_add(env, *current_node, -1, -1); status.flags |= NEED_UPDATE; return 1 | 2; case SCHISM_KEYSYM_DELETE: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; *current_node = _env_node_remove(env, *current_node); status.flags |= NEED_UPDATE; return 1 | 2; case SCHISM_KEYSYM_SPACE: if (!NO_MODIFIER(k->mod)) return 0; if (k->is_repeat) return 1; if (k->state == KEY_PRESS) { song_keydown(KEYJAZZ_NOINST, current_instrument, last_note, 64, KEYJAZZ_CHAN_CURRENT); return 1; } else if (k->state == KEY_RELEASE) { song_keyup(KEYJAZZ_NOINST, current_instrument, last_note); return 1; } return 0; case SCHISM_KEYSYM_RETURN: if (k->state == KEY_PRESS) return 0; if (!NO_MODIFIER(k->mod)) return 0; envelope_edit_mode = 1; status.flags |= NEED_UPDATE; return 1 | 2; case SCHISM_KEYSYM_l: if (k->state == KEY_PRESS) return 0; if (!(k->mod & SCHISM_KEYMOD_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 SCHISM_KEYSYM_b: if (k->state == KEY_PRESS) return 0; if (!(k->mod & SCHISM_KEYMOD_ALT)) return 0; if (env->loop_start > 0) { dialog_create(DIALOG_OK_CANCEL, "Cut envelope?", do_pre_loop_cut, NULL, 1, env); return 1; } return 0; // F/G for key symmetry with pattern double/halve block // E for symmetry with sample resize case SCHISM_KEYSYM_f: if (k->state == KEY_PRESS) return 0; if (!(k->mod & SCHISM_KEYMOD_ALT)) return 0; env_resize(env, env->ticks[env->nodes - 1] * 2); return 1; case SCHISM_KEYSYM_g: if (k->state == KEY_PRESS) return 0; if (!(k->mod & SCHISM_KEYMOD_ALT)) return 0; env_resize(env, env->ticks[env->nodes - 1] / 2); return 1; case SCHISM_KEYSYM_e: if (k->state == KEY_PRESS) return 0; if (!(k->mod & SCHISM_KEYMOD_ALT)) return 0; env_resize_dialog(env); return 1; case SCHISM_KEYSYM_z: if (k->state == KEY_PRESS) return 0; if (!(k->mod & SCHISM_KEYMOD_ALT)) return 0; env_adsr_dialog(env); return 1; default: if (k->state == KEY_PRESS) return 0; n = numeric_key_event(k, 0); if (n > -1) { if (k->mod & (SCHISM_KEYMOD_ALT | SCHISM_KEYMOD_CTRL)) { save_envelope(n, env, sec); status_text_flash("Envelope copied into slot %d", n); } else if (k->mod & SCHISM_KEYMOD_SHIFT) { restore_envelope(n, env, sec); if (!(status.flags & CLASSIC_MODE)) status_text_flash("Pasted envelope from slot %d", n); } return 1; } return 0; } new_node = CLAMP(new_node, 0, env->nodes - 1); if (*current_node != new_node) { *current_node = new_node; status.flags |= NEED_UPDATE; } return 1; } /* mouse handling routines for envelope */ static int _env_handle_mouse(struct key_event *k, song_envelope_t *env, int *current_node) { int x, y, i; int max_ticks = 50; if (k->mouse != MOUSE_CLICK) return 0; if (k->state == KEY_RELEASE) { /* mouse release */ if (envelope_mouse_edit) { video_set_mousecursor_shape(CURSOR_SHAPE_ARROW); if (current_node && *current_node) { for (i = 0; i < env->nodes-1; i++) { if (*current_node == i) continue; if (env->ticks[ *current_node ] == env->ticks[i] && env->values[ *current_node ] == env->values[i]) { status_text_flash("Removed node %d", (int)(*current_node)); status.flags |= SONG_NEEDS_SAVE; *current_node = _env_node_remove(env, *current_node); break; } } } status.flags |= NEED_UPDATE; } memused_songchanged(); envelope_mouse_edit = 0; return 1; } while (env->ticks[env->nodes - 1] >= max_ticks) max_ticks *= 2; if (envelope_mouse_edit) { video_set_mousecursor_shape(CURSOR_SHAPE_CROSSHAIR); if (k->fx < 259) x = 0; else x = (k->fx - 259) * max_ticks / 256; y = 64 - (k->fy - 144); if (y > 63) y++; if (y > 42) y++; if (y > 21) y++; if (y > 64) y = 64; if (y < 0) y = 0; if (*current_node && env->ticks[ (*current_node)-1 ] >= x) { x = env->ticks[ (*current_node)-1 ]+1; } if (*current_node < (env->nodes-1)) { if (env->ticks[ (*current_node)+1 ] <= x) { x = env->ticks[ (*current_node)+1 ]-1; } } if (env->ticks[*current_node] == x && env->ticks[*current_node] == y) { return 1; } if (x < 0) x = 0; if (x > envelope_tick_limit) x = envelope_tick_limit; if (x > 9999) x = 9999; if (*current_node) env->ticks[ *current_node ] = x; env->values[ *current_node ] = y; status.flags |= SONG_NEEDS_SAVE; status.flags |= NEED_UPDATE; } else { int n; int dist, dx, dy; int best_dist = 0; int best_dist_node; best_dist_node = -1; if (k->x < 32 || k->y < 18 || k->x > 32+45 || k->y > 18+8) return 0; for (n = 0; n < env->nodes; n++) { x = 259 + env->ticks[n] * 256 / max_ticks; y = env->values[n]; if (y > 63) y--; if (y > 42) y--; if (y > 21) y--; y = 206 - y; dx = abs(x - (int) k->fx); dy = abs(y - (int) k->fy); dist = i_sqrt((dx*dx)+(dy*dy)); if (best_dist_node == -1 || dist < best_dist) { if (dist <= 5) { best_dist = dist; best_dist_node = n; } } } if (best_dist_node == -1) { x = (k->fx - 259) * max_ticks / 256; y = 64 - (k->fy - 144); if (y > 63) y++; if (y > 42) y++; if (y > 21) y++; if (y > 64) y = 64; if (y < 0) y = 0; if (x > 0 && x < max_ticks) { *current_node = 0; for (i = 1; i < env->nodes; i++) { /* something too close */ if (env->ticks[i] <= x) *current_node = i; if (abs(env->ticks[i] - x) < 2) return 0; } best_dist_node = (_env_node_add(env, *current_node, x, y))+1; status_text_flash("Created node %d", best_dist_node); } if (best_dist_node == -1) return 0; } envelope_tick_limit = env->ticks[env->nodes - 1] * 2; envelope_mouse_edit = 1; *current_node = best_dist_node; status.flags |= SONG_NEEDS_SAVE; status.flags |= NEED_UPDATE; return 1; } return 0; } /* - this function is only ever called when the envelope is in edit mode - envelope_edit_mode is only ever assigned a true value once, in _env_handle_key_viewmode. - when _env_handle_key_viewmode enables envelope_edit_mode, it indicates in its return value that the envelope should be enabled. - therefore, the envelope will always be enabled when this function is called, so there is no reason to indicate a change in the envelope here. */ static int _env_handle_key_editmode(struct key_event *k, song_envelope_t *env, int *current_node) { int new_node = *current_node, new_tick = env->ticks[*current_node], new_value = env->values[*current_node]; /* TODO: when does adding/removing a node alter loop points? */ switch (k->sym) { case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_ALT) new_value += 16; else new_value++; break; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_ALT) new_value -= 16; else new_value--; break; case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_value += 16; break; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_value -= 16; break; case SCHISM_KEYSYM_LEFT: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_CTRL) new_node--; else if (k->mod & SCHISM_KEYMOD_ALT) new_tick -= 16; else new_tick--; break; case SCHISM_KEYSYM_RIGHT: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_CTRL) new_node++; else if (k->mod & SCHISM_KEYMOD_ALT) new_tick += 16; else new_tick++; break; case SCHISM_KEYSYM_TAB: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_SHIFT) new_tick -= 16; else new_tick += 16; break; case SCHISM_KEYSYM_HOME: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_tick = 0; break; case SCHISM_KEYSYM_END: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_tick = 10000; break; case SCHISM_KEYSYM_INSERT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; *current_node = _env_node_add(env, *current_node, -1, -1); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_DELETE: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; *current_node = _env_node_remove(env, *current_node); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_SPACE: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; song_keyup(KEYJAZZ_NOINST, current_instrument, last_note); song_keydown(KEYJAZZ_NOINST, current_instrument, last_note, 64, KEYJAZZ_CHAN_CURRENT); return 1; case SCHISM_KEYSYM_RETURN: if (k->state == KEY_PRESS) return 0; if (!NO_MODIFIER(k->mod)) return 0; envelope_edit_mode = 0; memused_songchanged(); status.flags |= NEED_UPDATE; break; default: return 0; } new_node = CLAMP(new_node, 0, env->nodes - 1); if (new_node != *current_node) { status.flags |= NEED_UPDATE; *current_node = new_node; return 1; } new_tick = (new_node == 0) ? 0 : CLAMP(new_tick, env->ticks[new_node - 1] + 1, ((new_node == env->nodes - 1) ? 10000 : env->ticks[new_node + 1]) - 1); if (new_tick != env->ticks[new_node]) { env->ticks[*current_node] = new_tick; status.flags |= SONG_NEEDS_SAVE; status.flags |= NEED_UPDATE; return 1; } new_value = CLAMP(new_value, 0, 64); if (new_value != (int)env->values[new_node]) { env->values[*current_node] = (unsigned int)new_value; status.flags |= SONG_NEEDS_SAVE; status.flags |= NEED_UPDATE; return 1; } return 1; } /* --------------------------------------------------------------------------------------------------------- */ /* envelope stuff (draw()'s and handle_key()'s) */ static void _draw_env_label(const char *env_name, int is_selected) { int pos = 33; pos += draw_text(env_name, pos, 16, is_selected ? 3 : 0, 2); pos += draw_text(" Envelope", pos, 16, is_selected ? 3 : 0, 2); if (envelope_edit_mode || envelope_mouse_edit) draw_text(" (Edit)", pos, 16, is_selected ? 3 : 0, 2); } static void volume_envelope_draw(void) { int is_selected = (ACTIVE_PAGE.selected_widget == 5); song_instrument_t *ins = song_get_instrument(current_instrument); _draw_env_label("Volume", is_selected); _env_draw(&ins->vol_env, 0, current_node_vol, ins->flags & ENV_VOLUME, ins->flags & ENV_VOLLOOP, ins->flags & ENV_VOLSUSTAIN, 0); } static void panning_envelope_draw(void) { int is_selected = (ACTIVE_PAGE.selected_widget == 5); song_instrument_t *ins = song_get_instrument(current_instrument); _draw_env_label("Panning", is_selected); _env_draw(&ins->pan_env, 1, current_node_pan, ins->flags & ENV_PANNING, ins->flags & ENV_PANLOOP, ins->flags & ENV_PANSUSTAIN, 1); } static void pitch_envelope_draw(void) { int is_selected = (ACTIVE_PAGE.selected_widget == 5); song_instrument_t *ins = song_get_instrument(current_instrument); _draw_env_label("Frequency", is_selected); _env_draw(&ins->pitch_env, (ins->flags & ENV_FILTER) ? 0 : 1, current_node_pitch, ins->flags & (ENV_PITCH|ENV_FILTER), ins->flags & ENV_PITCHLOOP, ins->flags & ENV_PITCHSUSTAIN, 2); } static int volume_envelope_handle_key(struct key_event * k) { song_instrument_t *ins = song_get_instrument(current_instrument); int r; if (_env_handle_mouse(k, &ins->vol_env, ¤t_node_vol)) { ins->flags |= ENV_VOLUME; return 1; } if (envelope_edit_mode) r = _env_handle_key_editmode(k, &ins->vol_env, ¤t_node_vol); else r = _env_handle_key_viewmode(k, &ins->vol_env, ¤t_node_vol, ENV_VOLUME); if (r & 2) { r ^= 2; ins->flags |= ENV_VOLUME; } return r; } static int panning_envelope_handle_key(struct key_event * k) { song_instrument_t *ins = song_get_instrument(current_instrument); int r; if (_env_handle_mouse(k, &ins->pan_env, ¤t_node_pan)) { ins->flags |= ENV_PANNING; return 1; } if (envelope_edit_mode) r = _env_handle_key_editmode(k, &ins->pan_env, ¤t_node_pan); else r = _env_handle_key_viewmode(k, &ins->pan_env, ¤t_node_pan, ENV_PANNING); if (r & 2) { r ^= 2; ins->flags |= ENV_PANNING; } return r; } static int pitch_envelope_handle_key(struct key_event * k) { song_instrument_t *ins = song_get_instrument(current_instrument); int r; if (_env_handle_mouse(k, &ins->pitch_env, ¤t_node_pitch)) { ins->flags |= ENV_PITCH; return 1; } if (envelope_edit_mode) r = _env_handle_key_editmode(k, &ins->pitch_env, ¤t_node_pitch); else r = _env_handle_key_viewmode(k, &ins->pitch_env, ¤t_node_pitch, ENV_PITCH); if (r & 2) { r ^= 2; ins->flags |= ENV_PITCH; } return r; } /* --------------------------------------------------------------------------------------------------------- */ /* pitch-pan center */ static int pitch_pan_center_handle_key(struct key_event *k) { song_instrument_t *ins = song_get_instrument(current_instrument); int ppc = ins->pitch_pan_center; if (k->state == KEY_RELEASE) return 0; switch (k->sym) { case SCHISM_KEYSYM_LEFT: if (!NO_MODIFIER(k->mod)) return 0; ppc--; break; case SCHISM_KEYSYM_RIGHT: if (!NO_MODIFIER(k->mod)) return 0; ppc++; break; default: if ((k->mod & (SCHISM_KEYMOD_CTRL | SCHISM_KEYMOD_ALT)) == 0) { ppc = kbd_get_note(k); if (ppc < 1 || ppc > 120) return 0; ppc--; break; } return 0; } if ((unsigned int)ppc != ins->pitch_pan_center && ppc >= 0 && ppc < 120) { ins->pitch_pan_center = (unsigned int)ppc; status.flags |= NEED_UPDATE; } return 1; } static void pitch_pan_center_draw(void) { char buf[4]; int selected = (ACTIVE_PAGE.selected_widget == 16); song_instrument_t *ins = song_get_instrument(current_instrument); draw_text(get_note_string(ins->pitch_pan_center + 1, buf), 54, 45, selected ? 3 : 2, 0); } /* ----------------------------------------------------------------------------- */ /* generic ITI saving routines */ /* filename can be NULL, in which case the instrument filename is used (quick save) */ struct instrument_save_data { char *path; /* char *options? */ const char *format; }; static void save_instrument_free_data(void *ptr) { struct instrument_save_data *data = (struct instrument_save_data *) ptr; if (data->path) free(data->path); free(data); } static void do_save_instrument(void *ptr) { struct instrument_save_data *data = (struct instrument_save_data *) ptr; song_save_instrument(data->path, data->format, song_get_instrument(current_instrument), current_instrument); save_instrument_free_data(ptr); } static void instrument_save(const char *filename, const char *format) { song_instrument_t *penv = song_get_instrument(current_instrument); char *ptr; struct stat buf; if (filename) { ptr = (char *) dmoz_path_concat(cfg_dir_instruments, filename); } else if (penv->filename[0]) { ptr = (char *) dmoz_path_concat(cfg_dir_instruments, penv->filename); } else { status_text_flash("Error: Instrument %d NOT saved! (No Filename?)", current_instrument); return; } struct instrument_save_data *data = mem_alloc(sizeof(*data)); data->path = ptr; data->format = format; if (!os_stat(ptr, &buf)) { if (S_ISDIR(buf.st_mode)) { status_text_flash("%s is a directory", filename); return; } else if (S_ISREG(buf.st_mode)) { dialog_create(DIALOG_OK_CANCEL, "Overwrite file?", do_save_instrument, save_instrument_free_data, 1, data); return; } else { status_text_flash("%s is not a regular file", filename); return; } } else { do_save_instrument(data); } } /* ----------------------------------------------------------------------------- */ /* export instrument dialog */ static struct widget export_instrument_widgets[4]; static char export_instrument_filename[SCHISM_NAME_MAX + 1] = ""; static int export_instrument_format = 0; static int num_save_formats = 0; static void do_export_instrument(SCHISM_UNUSED void *data) { instrument_save(export_instrument_filename, instrument_save_formats[export_instrument_format].label); } static void export_instrument_list_draw(void) { int n, focused = (*selected_widget == 3); draw_fill_chars(53, 24, 56, 31, DEFAULT_FG, 0); for (n = 0; instrument_save_formats[n].label; n++) { int fg = 6, bg = 0; if (focused && n == export_instrument_format) { fg = 0; bg = 3; } else if (n == export_instrument_format) { bg = 14; } draw_text_len(instrument_save_formats[n].label, 4, 53, 24 + n, fg, bg); } } static int export_instrument_list_handle_key(struct key_event * k) { int new_format = export_instrument_format; if (k->state == KEY_RELEASE) return 0; switch (k->sym) { case SCHISM_KEYSYM_UP: if (!NO_MODIFIER(k->mod)) return 0; new_format--; break; case SCHISM_KEYSYM_DOWN: if (!NO_MODIFIER(k->mod)) return 0; new_format++; break; case SCHISM_KEYSYM_PAGEUP: case SCHISM_KEYSYM_HOME: if (!NO_MODIFIER(k->mod)) return 0; new_format = 0; break; case SCHISM_KEYSYM_PAGEDOWN: case SCHISM_KEYSYM_END: if (!NO_MODIFIER(k->mod)) return 0; new_format = num_save_formats - 1; break; case SCHISM_KEYSYM_TAB: if (k->mod & SCHISM_KEYMOD_SHIFT) { widget_change_focus_to(0); return 1; } /* fall through */ case SCHISM_KEYSYM_LEFT: case SCHISM_KEYSYM_RIGHT: if (!NO_MODIFIER(k->mod)) return 0; widget_change_focus_to(0); /* should focus 0/1/2 depending on what's closest */ return 1; default: return 0; } new_format = CLAMP(new_format, 0, num_save_formats - 1); if (new_format != export_instrument_format) { /* update the option string */ export_instrument_format = new_format; status.flags |= NEED_UPDATE; } return 1; } static void export_instrument_draw_const(void) { draw_text("Export Instrument", 34, 21, 0, 2); draw_text("Filename", 24, 24, 0, 2); draw_box(32, 23, 51, 25, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(52, 23, 57, 32, BOX_THICK | BOX_INNER | BOX_INSET); } static void export_instrument_dialog(void) { song_instrument_t *instrument = song_get_instrument(current_instrument); struct dialog *dialog; widget_create_textentry(export_instrument_widgets + 0, 33, 24, 18, 0, 1, 3, NULL, export_instrument_filename, ARRAY_SIZE(export_instrument_filename) - 1); widget_create_button(export_instrument_widgets + 1, 31, 35, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); widget_create_button(export_instrument_widgets + 2, 42, 35, 6, 3, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); widget_create_other(export_instrument_widgets + 3, 0, export_instrument_list_handle_key, NULL, export_instrument_list_draw); strncpy(export_instrument_filename, instrument->filename, ARRAY_SIZE(export_instrument_filename) - 1); export_instrument_filename[ARRAY_SIZE(export_instrument_filename) - 1] = 0; dialog = dialog_create_custom(21, 20, 39, 18, export_instrument_widgets, 4, 0, export_instrument_draw_const, NULL); dialog->action_yes = do_export_instrument; } static void do_delete_inst(SCHISM_UNUSED void *ign) { song_delete_instrument(current_instrument, 0); } static void do_delete_inst_preserve(SCHISM_UNUSED void *ign) { song_delete_instrument(current_instrument, 1); } static void instrument_list_handle_alt_key(struct key_event *k) { /* song_instrument_t *ins = song_get_instrument(current_instrument); */ if (k->state == KEY_RELEASE) return; switch (k->sym) { case SCHISM_KEYSYM_n: song_toggle_multichannel_mode(); return; case SCHISM_KEYSYM_o: instrument_save(NULL, "ITI"); return; case SCHISM_KEYSYM_r: smpprompt_create("Replace instrument with:", "Instrument", do_replace_instrument); return; case SCHISM_KEYSYM_s: // extra space to align the text like IT smpprompt_create("Swap instrument with: ", "Instrument", do_swap_instrument); return; case SCHISM_KEYSYM_x: smpprompt_create("Exchange instrument with:", "Instrument", do_exchange_instrument); return; case SCHISM_KEYSYM_p: smpprompt_create("Copy instrument:", "Instrument", do_copy_instrument); return; case SCHISM_KEYSYM_w: song_wipe_instrument(current_instrument); break; case SCHISM_KEYSYM_d: if (k->mod & SCHISM_KEYMOD_SHIFT) { dialog_create(DIALOG_OK_CANCEL, "Delete Instrument? (preserve shared samples)", do_delete_inst_preserve, NULL, 1, NULL); } else { dialog_create(DIALOG_OK_CANCEL, "Delete Instrument?", do_delete_inst, NULL, 1, NULL); } return; case SCHISM_KEYSYM_t: export_instrument_dialog(); break; default: return; } status.flags |= NEED_UPDATE; } static int instrument_list_pre_handle_key(struct key_event * k) { // Only handle plain F4 key when no dialog is active. if (status.dialog_type != DIALOG_NONE || k->sym != SCHISM_KEYSYM_F4 || (k->mod & (SCHISM_KEYMOD_CTRL | SCHISM_KEYMOD_ALT))) return 0; if (k->state == KEY_RELEASE) return 1; if (song_is_instrument_mode()) { int csamp = sample_get_current(); sample_synchronize_to_instrument(); if (csamp != sample_get_current()) { status.flags |= NEED_UPDATE; return 0; } } if (k->mod & SCHISM_KEYMOD_SHIFT) { switch (status.current_page) { default: case PAGE_INSTRUMENT_LIST_VOLUME: set_subpage(PAGE_INSTRUMENT_LIST_GENERAL); break; case PAGE_INSTRUMENT_LIST_PANNING: set_subpage(PAGE_INSTRUMENT_LIST_VOLUME); break; case PAGE_INSTRUMENT_LIST_PITCH: set_subpage(PAGE_INSTRUMENT_LIST_PANNING); break; case PAGE_INSTRUMENT_LIST_GENERAL: set_subpage(PAGE_INSTRUMENT_LIST_PITCH); break; } } else { switch (status.current_page) { default: case PAGE_INSTRUMENT_LIST_PITCH: set_subpage(PAGE_INSTRUMENT_LIST_GENERAL); break; case PAGE_INSTRUMENT_LIST_GENERAL: set_subpage(PAGE_INSTRUMENT_LIST_VOLUME); break; case PAGE_INSTRUMENT_LIST_VOLUME: set_subpage(PAGE_INSTRUMENT_LIST_PANNING); break; case PAGE_INSTRUMENT_LIST_PANNING: set_subpage(PAGE_INSTRUMENT_LIST_PITCH); break; } } return 1; } static void instrument_list_handle_key(struct key_event * k) { switch (k->sym) { case SCHISM_KEYSYM_COMMA: if (NO_MODIFIER(k->mod)) { if (!(status.flags & CLASSIC_MODE) && ACTIVE_PAGE.selected_widget == 5) return; } SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_LESS: if (k->state == KEY_RELEASE) return; song_change_current_play_channel(-1, 0); return; case SCHISM_KEYSYM_PERIOD: if (NO_MODIFIER(k->mod)) { if (!(status.flags & CLASSIC_MODE) && ACTIVE_PAGE.selected_widget == 5) return; } SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_GREATER: if (k->state == KEY_RELEASE) return; song_change_current_play_channel(1, 0); return; case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return; instrument_set(current_instrument - 1); break; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return; instrument_set(current_instrument + 1); break; case SCHISM_KEYSYM_ESCAPE: if ((k->mod & SCHISM_KEYMOD_SHIFT) || instrument_cursor_pos < 25) { if (k->state == KEY_RELEASE) return; instrument_cursor_pos = 25; get_page_widgets()->accept_text = 0; widget_change_focus_to(0); status.flags |= NEED_UPDATE; return; } return; default: if (k->mod & (SCHISM_KEYMOD_ALT)) { instrument_list_handle_alt_key(k); } else { int n, v; if (k->midi_note > -1) { n = k->midi_note; if (k->midi_volume > -1) { v = k->midi_volume / 2; } else { v = 64; } } else { v = 64; n = kbd_get_note(k); if (n <= 0 || n > 120) return; } if (k->state == KEY_RELEASE) { song_keyup(KEYJAZZ_NOINST, current_instrument, n); status.last_keysym = 0; } else if (!k->is_repeat) { song_keydown(KEYJAZZ_NOINST, current_instrument, n, v, KEYJAZZ_CHAN_AUTO); } last_note = n; } return; } } /* --------------------------------------------------------------------- */ static void set_subpage(int page) { int widget = ACTIVE_PAGE.selected_widget; int b = 1; switch (page) { case PAGE_INSTRUMENT_LIST_GENERAL: b = 1; break; case PAGE_INSTRUMENT_LIST_VOLUME: b = 2; break; case PAGE_INSTRUMENT_LIST_PANNING: b = 3; break; case PAGE_INSTRUMENT_LIST_PITCH: b = 4; break; default: return; }; widget_togglebutton_set(pages[page].widgets, b, 0); set_page(page); if (widget >= ACTIVE_PAGE.total_widgets) widget = ACTIVE_PAGE.total_widgets - 1; ACTIVE_PAGE.selected_widget = widget; instrument_list_subpage = page; status.flags |= NEED_UPDATE; } static void change_subpage(void) { int widget = ACTIVE_PAGE.selected_widget; int p[] = { PAGE_INSTRUMENT_LIST_GENERAL, PAGE_INSTRUMENT_LIST_VOLUME, PAGE_INSTRUMENT_LIST_PANNING, PAGE_INSTRUMENT_LIST_PITCH, }; set_subpage(p[CLAMP(widget - 1, 0, 3)]); } /* --------------------------------------------------------------------- */ /* predraw hooks... */ static void instrument_list_general_predraw_hook(void) { song_instrument_t *ins = song_get_instrument(current_instrument); widget_togglebutton_set(widgets_general, 6 + (ins->nna % 4), 0); widget_togglebutton_set(widgets_general, 10 + (ins->dct % 4), 0); widget_togglebutton_set(widgets_general, 14 + (ins->dca % 3), 0); widgets_general[17].d.textentry.text = ins->filename; } static void instrument_list_volume_predraw_hook(void) { song_instrument_t *ins = song_get_instrument(current_instrument); widgets_volume[6].d.toggle.state = !!(ins->flags & ENV_VOLUME); widgets_volume[7].d.toggle.state = !!(ins->flags & ENV_VOLCARRY); widgets_volume[8].d.toggle.state = !!(ins->flags & ENV_VOLLOOP); widgets_volume[11].d.toggle.state = !!(ins->flags & ENV_VOLSUSTAIN); /* FIXME: this is the wrong place for this. ... and it's probably not even right -- how does Impulse Tracker handle loop constraints? See below for panning/pitch envelopes; same deal there. */ if (ins->vol_env.loop_start > ins->vol_env.loop_end) ins->vol_env.loop_end = ins->vol_env.loop_start; if (ins->vol_env.sustain_start > ins->vol_env.sustain_end) ins->vol_env.sustain_end = ins->vol_env.sustain_start; widgets_volume[9].d.numentry.max = ins->vol_env.nodes - 1; widgets_volume[10].d.numentry.max = ins->vol_env.nodes - 1; widgets_volume[12].d.numentry.max = ins->vol_env.nodes - 1; widgets_volume[13].d.numentry.max = ins->vol_env.nodes - 1; widgets_volume[9].d.numentry.value = ins->vol_env.loop_start; widgets_volume[10].d.numentry.value = ins->vol_env.loop_end; widgets_volume[12].d.numentry.value = ins->vol_env.sustain_start; widgets_volume[13].d.numentry.value = ins->vol_env.sustain_end; /* current_song hack: shifting values all over the place here, ugh */ widgets_volume[14].d.thumbbar.value = ins->global_volume; widgets_volume[15].d.thumbbar.value = ins->fadeout >> 5; widgets_volume[16].d.thumbbar.value = ins->vol_swing; } static void instrument_list_panning_predraw_hook(void) { song_instrument_t *ins = song_get_instrument(current_instrument); widgets_panning[6].d.toggle.state = !!(ins->flags & ENV_PANNING); widgets_panning[7].d.toggle.state = !!(ins->flags & ENV_PANCARRY); widgets_panning[8].d.toggle.state = !!(ins->flags & ENV_PANLOOP); widgets_panning[11].d.toggle.state = !!(ins->flags & ENV_PANSUSTAIN); if (ins->pan_env.loop_start > ins->pan_env.loop_end) ins->pan_env.loop_end = ins->pan_env.loop_start; if (ins->pan_env.sustain_start > ins->pan_env.sustain_end) ins->pan_env.sustain_end = ins->pan_env.sustain_start; widgets_panning[9].d.numentry.max = ins->pan_env.nodes - 1; widgets_panning[10].d.numentry.max = ins->pan_env.nodes - 1; widgets_panning[12].d.numentry.max = ins->pan_env.nodes - 1; widgets_panning[13].d.numentry.max = ins->pan_env.nodes - 1; widgets_panning[9].d.numentry.value = ins->pan_env.loop_start; widgets_panning[10].d.numentry.value = ins->pan_env.loop_end; widgets_panning[12].d.numentry.value = ins->pan_env.sustain_start; widgets_panning[13].d.numentry.value = ins->pan_env.sustain_end; widgets_panning[14].d.toggle.state = !!(ins->flags & ENV_SETPANNING); widgets_panning[15].d.thumbbar.value = ins->panning >> 2; /* (widgets_panning[16] is the pitch-pan center) */ widgets_panning[17].d.thumbbar.value = ins->pitch_pan_separation; widgets_panning[18].d.thumbbar.value = ins->pan_swing; } static void instrument_list_pitch_predraw_hook(void) { song_instrument_t *ins = song_get_instrument(current_instrument); widgets_pitch[6].d.menutoggle.state = ((ins->flags & ENV_PITCH) ? ((ins->flags & ENV_FILTER) ? 2 : 1) : 0); widgets_pitch[7].d.toggle.state = !!(ins->flags & ENV_PITCHCARRY); widgets_pitch[8].d.toggle.state = !!(ins->flags & ENV_PITCHLOOP); widgets_pitch[11].d.toggle.state = !!(ins->flags & ENV_PITCHSUSTAIN); if (ins->pitch_env.loop_start > ins->pitch_env.loop_end) ins->pitch_env.loop_end = ins->pitch_env.loop_start; if (ins->pitch_env.sustain_start > ins->pitch_env.sustain_end) ins->pitch_env.sustain_end = ins->pitch_env.sustain_start; widgets_pitch[9].d.numentry.max = ins->pitch_env.nodes - 1; widgets_pitch[10].d.numentry.max = ins->pitch_env.nodes - 1; widgets_pitch[12].d.numentry.max = ins->pitch_env.nodes - 1; widgets_pitch[13].d.numentry.max = ins->pitch_env.nodes - 1; widgets_pitch[9].d.numentry.value = ins->pitch_env.loop_start; widgets_pitch[10].d.numentry.value = ins->pitch_env.loop_end; widgets_pitch[12].d.numentry.value = ins->pitch_env.sustain_start; widgets_pitch[13].d.numentry.value = ins->pitch_env.sustain_end; if (ins->ifc & 0x80) widgets_pitch[14].d.thumbbar.value = ins->ifc & 0x7f; else widgets_pitch[14].d.thumbbar.value = -1; if (ins->ifr & 0x80) widgets_pitch[15].d.thumbbar.value = ins->ifr & 0x7f; else widgets_pitch[15].d.thumbbar.value = -1; /* printf("ins%02d: ch%04d pgm%04d bank%06d drum%04d\n", current_instrument, ins->midi_channel, ins->midi_program, ins->midi_bank, ins->midi_drum_key); */ widgets_pitch[16].d.bitset.value = ins->midi_channel_mask; widgets_pitch[17].d.thumbbar.value = (signed char) ins->midi_program; widgets_pitch[18].d.thumbbar.value = (signed char) (ins->midi_bank & 0xff); widgets_pitch[19].d.thumbbar.value = (signed char) (ins->midi_bank >> 8); /* what is midi_drum_key for? */ } /* --------------------------------------------------------------------- */ /* update values in song */ static void instrument_list_general_update_values(void) { song_instrument_t *ins = song_get_instrument(current_instrument); status.flags |= SONG_NEEDS_SAVE; for (ins->nna = 4; ins->nna--;) if (widgets_general[ins->nna + 6].d.togglebutton.state) break; for (ins->dct = 4; ins->dct--;) if (widgets_general[ins->dct + 10].d.togglebutton.state) break; for (ins->dca = 3; ins->dca--;) if (widgets_general[ins->dca + 14].d.togglebutton.state) break; } static void update_filename(void) { status.flags |= SONG_NEEDS_SAVE; } #define CHECK_SET(a,b,c) if (a != b) { a = b; c; } static void instrument_list_volume_update_values(void) { song_instrument_t *ins = song_get_instrument(current_instrument); status.flags |= SONG_NEEDS_SAVE; ins->flags &= ~(ENV_VOLUME | ENV_VOLCARRY | ENV_VOLLOOP | ENV_VOLSUSTAIN); if (widgets_volume[6].d.toggle.state) ins->flags |= ENV_VOLUME; if (widgets_volume[7].d.toggle.state) ins->flags |= ENV_VOLCARRY; if (widgets_volume[8].d.toggle.state) ins->flags |= ENV_VOLLOOP; if (widgets_volume[11].d.toggle.state) ins->flags |= ENV_VOLSUSTAIN; CHECK_SET(ins->vol_env.loop_start, widgets_volume[9].d.numentry.value, ins->flags |= ENV_VOLLOOP); CHECK_SET(ins->vol_env.loop_end, widgets_volume[10].d.numentry.value, ins->flags |= ENV_VOLLOOP); CHECK_SET(ins->vol_env.sustain_start, widgets_volume[12].d.numentry.value, ins->flags |= ENV_VOLSUSTAIN); CHECK_SET(ins->vol_env.sustain_end, widgets_volume[13].d.numentry.value, ins->flags |= ENV_VOLSUSTAIN); /* more ugly shifts */ ins->global_volume = widgets_volume[14].d.thumbbar.value; ins->fadeout = widgets_volume[15].d.thumbbar.value << 5; ins->vol_swing = widgets_volume[16].d.thumbbar.value; song_update_playing_instrument(current_instrument); } static void instrument_list_panning_update_values(void) { song_instrument_t *ins = song_get_instrument(current_instrument); int n; status.flags |= SONG_NEEDS_SAVE; ins->flags &= ~(ENV_PANNING | ENV_PANCARRY | ENV_PANLOOP | ENV_PANSUSTAIN | ENV_SETPANNING); if (widgets_panning[6].d.toggle.state) ins->flags |= ENV_PANNING; if (widgets_panning[7].d.toggle.state) ins->flags |= ENV_PANCARRY; if (widgets_panning[8].d.toggle.state) ins->flags |= ENV_PANLOOP; if (widgets_panning[11].d.toggle.state) ins->flags |= ENV_PANSUSTAIN; if (widgets_panning[14].d.toggle.state) ins->flags |= ENV_SETPANNING; CHECK_SET(ins->pan_env.loop_start, widgets_panning[9].d.numentry.value, ins->flags |= ENV_PANLOOP); CHECK_SET(ins->pan_env.loop_end, widgets_panning[10].d.numentry.value, ins->flags |= ENV_PANLOOP); CHECK_SET(ins->pan_env.sustain_start, widgets_panning[12].d.numentry.value, ins->flags |= ENV_PANSUSTAIN); CHECK_SET(ins->pan_env.sustain_end, widgets_panning[13].d.numentry.value, ins->flags |= ENV_PANSUSTAIN); n = widgets_panning[15].d.thumbbar.value << 2; if (ins->panning != (unsigned int)n) { ins->panning = (unsigned int)n; ins->flags |= ENV_SETPANNING; } /* (widgets_panning[16] is the pitch-pan center) */ ins->pitch_pan_separation = widgets_panning[17].d.thumbbar.value; ins->pan_swing = widgets_panning[18].d.thumbbar.value; song_update_playing_instrument(current_instrument); } static void instrument_list_pitch_update_values(void) { song_instrument_t *ins = song_get_instrument(current_instrument); status.flags |= SONG_NEEDS_SAVE; ins->flags &= ~(ENV_PITCH | ENV_PITCHCARRY | ENV_PITCHLOOP | ENV_PITCHSUSTAIN | ENV_FILTER); switch (widgets_pitch[6].d.menutoggle.state) { case 2: ins->flags |= ENV_FILTER; SCHISM_FALLTHROUGH; case 1: ins->flags |= ENV_PITCH; break; } if (widgets_pitch[7].d.toggle.state) ins->flags |= ENV_PITCHCARRY; if (widgets_pitch[8].d.toggle.state) ins->flags |= ENV_PITCHLOOP; if (widgets_pitch[11].d.toggle.state) ins->flags |= ENV_PITCHSUSTAIN; CHECK_SET(ins->pitch_env.loop_start, widgets_pitch[9].d.numentry.value, ins->flags |= ENV_PITCHLOOP); CHECK_SET(ins->pitch_env.loop_end, widgets_pitch[10].d.numentry.value, ins->flags |= ENV_PITCHLOOP); CHECK_SET(ins->pitch_env.sustain_start, widgets_pitch[12].d.numentry.value, ins->flags |= ENV_PITCHSUSTAIN); CHECK_SET(ins->pitch_env.sustain_end, widgets_pitch[13].d.numentry.value, ins->flags |= ENV_PITCHSUSTAIN); if (widgets_pitch[14].d.thumbbar.value > -1) { ins->ifc = widgets_pitch[14].d.thumbbar.value | 0x80; } else { ins->ifc = 0x7f; } if (widgets_pitch[15].d.thumbbar.value > -1) { ins->ifr = widgets_pitch[15].d.thumbbar.value | 0x80; } else { ins->ifr = 0x7f; } ins->midi_channel_mask = widgets_pitch[16].d.bitset.value; ins->midi_program = widgets_pitch[17].d.thumbbar.value; ins->midi_bank = ((widgets_pitch[19].d.thumbbar.value << 8) | (widgets_pitch[18].d.thumbbar.value & 0xff)); song_update_playing_instrument(current_instrument); } /* --------------------------------------------------------------------- */ /* draw_const functions */ static void instrument_list_draw_const(void) { draw_box(4, 12, 30, 48, BOX_THICK | BOX_INNER | BOX_INSET); } static void instrument_list_general_draw_const(void) { int n; instrument_list_draw_const(); draw_box(31, 15, 42, 48, BOX_THICK | BOX_INNER | BOX_INSET); /* Kind of a hack, and not really useful, but... :) */ if (status.flags & CLASSIC_MODE) { draw_box(55, 46, 73, 48, BOX_THICK | BOX_INNER | BOX_INSET); draw_text(" ", 69, 47, 1, 0); } else { draw_box(55, 46, 69, 48, BOX_THICK | BOX_INNER | BOX_INSET); } draw_text("New Note Action", 54, 17, 0, 2); draw_text("Duplicate Check Type & Action", 47, 32, 0, 2); draw_text("Filename", 47, 47, 0, 2); for (n = 0; n < 35; n++) { draw_char(134, 44 + n, 15, 0, 2); draw_char(134, 44 + n, 30, 0, 2); draw_char(154, 44 + n, 45, 0, 2); } } static void instrument_list_volume_draw_const(void) { instrument_list_draw_const(); draw_fill_chars(57, 28, 62, 29, DEFAULT_FG, 0); draw_fill_chars(57, 32, 62, 34, DEFAULT_FG, 0); draw_fill_chars(57, 37, 62, 39, DEFAULT_FG, 0); draw_box(31, 17, 77, 26, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 27, 63, 30, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 31, 63, 35, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 36, 63, 40, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 41, 71, 44, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 45, 71, 47, BOX_THICK | BOX_INNER | BOX_INSET); draw_text("Volume Envelope", 38, 28, 0, 2); draw_text("Carry", 48, 29, 0, 2); draw_text("Envelope Loop", 40, 32, 0, 2); draw_text("Loop Begin", 43, 33, 0, 2); draw_text("Loop End", 45, 34, 0, 2); draw_text("Sustain Loop", 41, 37, 0, 2); draw_text("SusLoop Begin", 40, 38, 0, 2); draw_text("SusLoop End", 42, 39, 0, 2); draw_text("Global Volume", 40, 42, 0, 2); draw_text("Fadeout", 46, 43, 0, 2); draw_text("Volume Swing %", 39, 46, 0, 2); } static void instrument_list_panning_draw_const(void) { instrument_list_draw_const(); draw_fill_chars(57, 28, 62, 29, DEFAULT_FG, 0); draw_fill_chars(57, 32, 62, 34, DEFAULT_FG, 0); draw_fill_chars(57, 37, 62, 39, DEFAULT_FG, 0); draw_fill_chars(57, 42, 62, 45, DEFAULT_FG, 0); draw_box(31, 17, 77, 26, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 27, 63, 30, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 31, 63, 35, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 36, 63, 40, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 41, 63, 48, BOX_THICK | BOX_INNER | BOX_INSET); draw_text("Panning Envelope", 37, 28, 0, 2); draw_text("Carry", 48, 29, 0, 2); draw_text("Envelope Loop", 40, 32, 0, 2); draw_text("Loop Begin", 43, 33, 0, 2); draw_text("Loop End", 45, 34, 0, 2); draw_text("Sustain Loop", 41, 37, 0, 2); draw_text("SusLoop Begin", 40, 38, 0, 2); draw_text("SusLoop End", 42, 39, 0, 2); draw_text("Default Pan", 42, 42, 0, 2); draw_text("Pan Value", 44, 43, 0, 2); draw_text("Pitch-Pan Center", 37, 45, 0, 2); draw_text("Pitch-Pan Separation", 33, 46, 0, 2); if (status.flags & CLASSIC_MODE) { /* Hmm. The 's' in swing isn't capitalised. ;) */ draw_text("Pan swing", 44, 47, 0, 2); } else { draw_text("Pan Swing", 44, 47, 0, 2); } draw_text("\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a", 54, 44, 2, 0); } static void instrument_list_pitch_draw_const(void) { instrument_list_draw_const(); draw_fill_chars(57, 28, 62, 29, DEFAULT_FG, 0); draw_fill_chars(57, 32, 62, 34, DEFAULT_FG, 0); draw_fill_chars(57, 37, 62, 39, DEFAULT_FG, 0); draw_box(31, 17, 77, 26, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 27, 63, 30, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 31, 63, 35, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 36, 63, 40, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(53, 41, 71, 48, BOX_THICK | BOX_INNER | BOX_INSET); draw_text("Frequency Envelope", 35, 28, 0, 2); draw_text("Carry", 48, 29, 0, 2); draw_text("Envelope Loop", 40, 32, 0, 2); draw_text("Loop Begin", 43, 33, 0, 2); draw_text("Loop End", 45, 34, 0, 2); draw_text("Sustain Loop", 41, 37, 0, 2); draw_text("SusLoop Begin", 40, 38, 0, 2); draw_text("SusLoop End", 42, 39, 0, 2); draw_text("Default Cutoff", 36, 42, 0, 2); draw_text("Default Resonance", 36, 43, 0, 2); draw_text("MIDI Channels", 36, 44, 0, 2); draw_text("MIDI Program", 36, 45, 0, 2); draw_text("MIDI Bank Low", 36, 46, 0, 2); draw_text("MIDI Bank High", 36, 47, 0, 2); } /* --------------------------------------------------------------------- */ /* load_page functions */ static void _load_page_common(struct page *page, struct widget *page_widgets) { int i; memset(saved_env, 0, sizeof(saved_env)); for (i = 0; i < 10; i++) { saved_env[i].nodes = 2; saved_env[i].ticks[0] = 0; saved_env[i].ticks[1] = 100; saved_env[i].values[0] = 32; saved_env[i].values[1] = 32; } vgamem_ovl_alloc(&env_overlay); page->title = "Instrument List (F4)"; page->pre_handle_key = instrument_list_pre_handle_key; page->handle_key = instrument_list_handle_key; page->widgets = page_widgets; page->help_index = HELP_INSTRUMENT_LIST; page->set_page = instrument_list_reposition; /* the first five widgets are the same for all four pages. */ /* 0 = instrument list */ widget_create_other(page_widgets + 0, 1, instrument_list_handle_key_on_list, instrument_list_handle_text_input_on_list, instrument_list_draw_list); page_widgets[0].accept_text = (instrument_cursor_pos == 25 ? 0 : 1); page_widgets[0].x = 5; page_widgets[0].y = 13; page_widgets[0].width = 24; page_widgets[0].height = 34; /* 1-4 = subpage switches */ widget_create_togglebutton(page_widgets + 1, 32, 13, 7, 1, 5, 0, 2, 2, change_subpage, "General", 1, subpage_switches_group); widget_create_togglebutton(page_widgets + 2, 44, 13, 7, 2, 5, 1, 3, 3, change_subpage, "Volume", 1, subpage_switches_group); widget_create_togglebutton(page_widgets + 3, 56, 13, 7, 3, 5, 2, 4, 4, change_subpage, "Panning", 1, subpage_switches_group); widget_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 */ widget_create_other(widgets_general + 5, 6, note_trans_handle_key, NULL, note_trans_draw); widgets_general[5].x = 32; widgets_general[5].y = 16; widgets_general[5].width = 9; widgets_general[5].height = 31; widgets_general[5].next.down = 6; /* 6-9 = nna toggles */ widget_create_togglebutton(widgets_general + 6, 46, 19, 29, 2, 7, 5, 0, 0, instrument_list_general_update_values, "Note Cut", 2, nna_group); widget_create_togglebutton(widgets_general + 7, 46, 22, 29, 6, 8, 5, 0, 0, instrument_list_general_update_values, "Continue", 2, nna_group); widget_create_togglebutton(widgets_general + 8, 46, 25, 29, 7, 9, 5, 0, 0, instrument_list_general_update_values, "Note Off", 2, nna_group); widget_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 */ widget_create_togglebutton(widgets_general + 10, 46, 34, 12, 9, 11, 5, 14, 14, instrument_list_general_update_values, "Disabled", 2, dct_group); widget_create_togglebutton(widgets_general + 11, 46, 37, 12, 10, 12, 5, 15, 15, instrument_list_general_update_values, "Note", 2, dct_group); widget_create_togglebutton(widgets_general + 12, 46, 40, 12, 11, 13, 5, 16, 16, instrument_list_general_update_values, "Sample", 2, dct_group); widget_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 */ widget_create_togglebutton(widgets_general + 14, 62, 34, 13, 9, 15, 10, 0, 0, instrument_list_general_update_values, "Note Cut", 2, dca_group); widget_create_togglebutton(widgets_general + 15, 62, 37, 13, 14, 16, 11, 0, 0, instrument_list_general_update_values, "Note Off", 2, dca_group); widget_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... */ widget_create_textentry(widgets_general + 17, 56, 47, 13, 13, 17, 0, update_filename, NULL, 12); } static int _fixup_mouse_instpage_volume(struct key_event *k) { song_instrument_t *ins = song_get_instrument(current_instrument); if (envelope_mouse_edit && ins) { if (_env_handle_mouse(k, &ins->vol_env, ¤t_node_vol)) { ins->flags |= ENV_VOLUME; return 1; } } if ((k->sym == SCHISM_KEYSYM_l || k->sym == SCHISM_KEYSYM_b) && (k->mod & SCHISM_KEYMOD_ALT)) { return _env_handle_key_viewmode(k, &ins->vol_env, ¤t_node_vol, ENV_VOLUME); } return instrument_list_pre_handle_key(k); } void instrument_list_volume_load_page(struct page *page) { _load_page_common(page, widgets_volume); page->pre_handle_key = _fixup_mouse_instpage_volume; page->draw_const = instrument_list_volume_draw_const; page->predraw_hook = instrument_list_volume_predraw_hook; page->total_widgets = 17; /* 5 = volume envelope */ widget_create_other(widgets_volume + 5, 0, volume_envelope_handle_key, NULL, volume_envelope_draw); widgets_volume[5].x = 32; widgets_volume[5].y = 18; widgets_volume[5].width = 45; widgets_volume[5].height = 8; widgets_volume[5].next.down = 6; /* 6-7 = envelope switches */ widget_create_toggle(widgets_volume + 6, 54, 28, 5, 7, 0, 0, 0, instrument_list_volume_update_values); widget_create_toggle(widgets_volume + 7, 54, 29, 6, 8, 0, 0, 0, instrument_list_volume_update_values); /* 8-10 envelope loop settings */ widget_create_toggle(widgets_volume + 8, 54, 32, 7, 9, 0, 0, 0, instrument_list_volume_update_values); widget_create_numentry(widgets_volume + 9, 54, 33, 3, 8, 10, 0, instrument_list_volume_update_values, 0, 1, numentry_cursor_pos + 0); widget_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 */ widget_create_toggle(widgets_volume + 11, 54, 37, 10, 12, 0, 0, 0, instrument_list_volume_update_values); widget_create_numentry(widgets_volume + 12, 54, 38, 3, 11, 13, 0, instrument_list_volume_update_values, 0, 1, numentry_cursor_pos + 0); widget_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 */ widget_create_thumbbar(widgets_volume + 14, 54, 42, 17, 13, 15, 0, instrument_list_volume_update_values, 0, 128); widget_create_thumbbar(widgets_volume + 15, 54, 43, 17, 14, 16, 0, instrument_list_volume_update_values, 0, 256); widget_create_thumbbar(widgets_volume + 16, 54, 46, 17, 15, 16, 0, instrument_list_volume_update_values, 0, 100); } static int _fixup_mouse_instpage_panning(struct key_event *k) { song_instrument_t *ins = song_get_instrument(current_instrument); if (envelope_mouse_edit && ins) { if (_env_handle_mouse(k, &ins->pan_env, ¤t_node_pan)) { ins->flags |= ENV_PANNING; return 1; } } if ((k->sym == SCHISM_KEYSYM_l || k->sym == SCHISM_KEYSYM_b) && (k->mod & SCHISM_KEYMOD_ALT)) { return _env_handle_key_viewmode(k, &ins->pan_env, ¤t_node_pan, ENV_PANNING); } return instrument_list_pre_handle_key(k); } void instrument_list_panning_load_page(struct page *page) { _load_page_common(page, widgets_panning); page->pre_handle_key = _fixup_mouse_instpage_panning; page->draw_const = instrument_list_panning_draw_const; page->predraw_hook = instrument_list_panning_predraw_hook; page->total_widgets = 19; /* 5 = panning envelope */ widget_create_other(widgets_panning + 5, 0, panning_envelope_handle_key, NULL, panning_envelope_draw); widgets_panning[5].x = 32; widgets_panning[5].y = 18; widgets_panning[5].width = 45; widgets_panning[5].height = 8; widgets_panning[5].next.down = 6; /* 6-7 = envelope switches */ widget_create_toggle(widgets_panning + 6, 54, 28, 5, 7, 0, 0, 0, instrument_list_panning_update_values); widget_create_toggle(widgets_panning + 7, 54, 29, 6, 8, 0, 0, 0, instrument_list_panning_update_values); /* 8-10 envelope loop settings */ widget_create_toggle(widgets_panning + 8, 54, 32, 7, 9, 0, 0, 0, instrument_list_panning_update_values); widget_create_numentry(widgets_panning + 9, 54, 33, 3, 8, 10, 0, instrument_list_panning_update_values, 0, 1, numentry_cursor_pos + 1); widget_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 */ widget_create_toggle(widgets_panning + 11, 54, 37, 10, 12, 0, 0, 0, instrument_list_panning_update_values); widget_create_numentry(widgets_panning + 12, 54, 38, 3, 11, 13, 0, instrument_list_panning_update_values, 0, 1, numentry_cursor_pos + 1); widget_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 */ widget_create_toggle(widgets_panning + 14, 54, 42, 13, 15, 0, 0, 0, instrument_list_panning_update_values); widget_create_thumbbar(widgets_panning + 15, 54, 43, 9, 14, 16, 0, instrument_list_panning_update_values, 0, 64); /* 16 = pitch-pan center */ widget_create_other(widgets_panning + 16, 0, pitch_pan_center_handle_key, NULL, pitch_pan_center_draw); widgets_panning[16].next.up = 15; widgets_panning[16].next.down = 17; /* 17-18 = other panning stuff */ widget_create_thumbbar(widgets_panning + 17, 54, 46, 9, 16, 18, 0, instrument_list_panning_update_values, -32, 32); widget_create_thumbbar(widgets_panning + 18, 54, 47, 9, 17, 18, 0, instrument_list_panning_update_values, 0, 64); } static int _fixup_mouse_instpage_pitch(struct key_event *k) { song_instrument_t *ins = song_get_instrument(current_instrument); if (envelope_mouse_edit && ins) { if (_env_handle_mouse(k, &ins->pitch_env, ¤t_node_pitch)) { ins->flags |= ENV_PITCH; return 1; } } if ((k->sym == SCHISM_KEYSYM_l || k->sym == SCHISM_KEYSYM_b) && (k->mod & SCHISM_KEYMOD_ALT)) { return _env_handle_key_viewmode(k, &ins->pitch_env, ¤t_node_pitch, ENV_PITCH); } return instrument_list_pre_handle_key(k); } void instrument_list_pitch_load_page(struct page *page) { static int midi_channel_selection_cursor_position = 0; _load_page_common(page, widgets_pitch); page->pre_handle_key = _fixup_mouse_instpage_pitch; page->draw_const = instrument_list_pitch_draw_const; page->predraw_hook = instrument_list_pitch_predraw_hook; page->total_widgets = 20; /* 5 = pitch envelope */ widget_create_other(widgets_pitch + 5, 0, pitch_envelope_handle_key, NULL, pitch_envelope_draw); widgets_pitch[5].x = 32; widgets_pitch[5].y = 18; widgets_pitch[5].width = 45; widgets_pitch[5].height = 8; widgets_pitch[5].next.down = 6; /* 6-7 = envelope switches */ widget_create_menutoggle(widgets_pitch + 6, 54, 28, 5, 7, 0, 0, 0, instrument_list_pitch_update_values, pitch_envelope_states); widget_create_toggle(widgets_pitch + 7, 54, 29, 6, 8, 0, 0, 0, instrument_list_pitch_update_values); /* 8-10 envelope loop settings */ widget_create_toggle(widgets_pitch + 8, 54, 32, 7, 9, 0, 0, 0, instrument_list_pitch_update_values); widget_create_numentry(widgets_pitch + 9, 54, 33, 3, 8, 10, 0, instrument_list_pitch_update_values, 0, 1, numentry_cursor_pos + 2); widget_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 */ widget_create_toggle(widgets_pitch + 11, 54, 37, 10, 12, 0, 0, 0, instrument_list_pitch_update_values); widget_create_numentry(widgets_pitch + 12, 54, 38, 3, 11, 13, 0, instrument_list_pitch_update_values, 0, 1, numentry_cursor_pos + 2); widget_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 */ widget_create_thumbbar(widgets_pitch + 14, 54, 42, 17, 13, 15, 0, instrument_list_pitch_update_values, -1, 127); widget_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 */ widget_create_bitset(widgets_pitch + 16, 54, 44, 17, 15, 17, 0, instrument_list_pitch_update_values, 17, " 1 2 3 4 5 6 7 8 9P\0""111213141516M\0", ".\0.\0.\0.\0.\0.\0.\0.\0.\0p\0.\0.\0.\0.\0.\0.\0m\0", &midi_channel_selection_cursor_position ); widgets_pitch[16].d.bitset.activation_keys = "123456789pabcdefm"; widget_create_thumbbar(widgets_pitch + 17, 54, 45, 17, 16, 18, 0, instrument_list_pitch_update_values, -1, 127); widget_create_thumbbar(widgets_pitch + 18, 54, 46, 17, 17, 19, 0, instrument_list_pitch_update_values, -1, 127); widget_create_thumbbar(widgets_pitch + 19, 54, 47, 17, 18, 19, 0, instrument_list_pitch_update_values, -1, 127); widgets_pitch[17].d.thumbbar.text_at_min = "Off"; widgets_pitch[18].d.thumbbar.text_at_min = "Off"; widgets_pitch[19].d.thumbbar.text_at_min = "Off"; /* count how many formats there really are */ num_save_formats = 0; while (instrument_save_formats[num_save_formats].label) num_save_formats++; } schismtracker-20250313/schism/page_loadinst.c000066400000000000000000000341441476471630300211010ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "config.h" #include "charset.h" #include "song.h" #include "page.h" #include "dmoz.h" #include "log.h" #include "fakemem.h" #include "dialog.h" #include "widget.h" #include "vgamem.h" #include "osdefs.h" #include "str.h" #include #include #include #include /* --------------------------------------------------------------------------------------------------------- */ /* the locals */ static struct widget widgets_loadinst[1]; static char inst_cwd[SCHISM_PATH_MAX] = {0}; /* --------------------------------------------------------------------------------------------------------- */ /* files: file type color displayed title notes --------- ----- --------------- ----- unchecked 4 IT uses color 6 for these directory 5 "........Directory........" dots are char 154 (same for libraries) sample 3 libraries 6 ".........Library........." IT uses color 3. maybe use module name here? unknown 2 any regular file that's not recognized */ static int top_file = 0; static time_t directory_mtime; static int _library_mode = 0; static dmoz_filelist_t flist; #define current_file flist.selected static int slash_search_mode = -1; static char slash_search_str[SCHISM_PATH_MAX]; /* get a color index from a dmoz_file_t 'type' field */ static inline int get_type_color(int type) { if (type == TYPE_DIRECTORY) return 5; if (!(type & TYPE_EXT_DATA_MASK)) return 4; /* unchecked */ if (type & TYPE_BROWSABLE_MASK) return 6; /* library */ if (type == TYPE_UNKNOWN) return 2; return 3; /* sample */ } static void clear_directory(void) { dmoz_free(&flist, NULL); } static int instgrep(dmoz_file_t *f) { dmoz_fill_ext_data(f); return f->type & (TYPE_INST_MASK | TYPE_BROWSABLE_MASK); } /* --------------------------------------------------------------------------------------------------------- */ static void file_list_reposition(void) { if (current_file >= flist.num_files) current_file = flist.num_files-1; if (current_file < 0) current_file = 0; if (current_file < top_file) top_file = current_file; else if (current_file > top_file + 34) top_file = current_file - 34; } static void read_directory(void) { struct stat st; clear_directory(); if (os_stat(inst_cwd, &st) < 0) directory_mtime = 0; else directory_mtime = st.st_mtime; /* if the stat call failed, this will probably break as well, but at the very least, it'll add an entry for the root directory. */ if (dmoz_read(inst_cwd, &flist, NULL, dmoz_read_instrument_library) < 0) log_perror(inst_cwd); dmoz_filter_filelist(&flist,instgrep, ¤t_file, file_list_reposition); dmoz_cache_lookup(inst_cwd, &flist, NULL); file_list_reposition(); } /* return: 1 = success, 0 = failure TODO: provide some sort of feedback if something went wrong. */ static int change_dir(const char *dir) { char *ptr = dmoz_path_normal(dir); struct stat buf; if (!ptr) return 0; dmoz_cache_update(inst_cwd, &flist, NULL); if (os_stat(ptr, &buf) == 0 && S_ISDIR(buf.st_mode)) { strncpy(cfg_dir_instruments, ptr, ARRAY_SIZE(cfg_dir_instruments) - 1); cfg_dir_instruments[ARRAY_SIZE(cfg_dir_instruments) - 1] = 0; } strncpy(inst_cwd, ptr, ARRAY_SIZE(inst_cwd) - 1); inst_cwd[ARRAY_SIZE(inst_cwd) - 1] = 0; free(ptr); read_directory(); return 1; } /* --------------------------------------------------------------------------------------------------------- */ static void load_instrument_draw_const(void) { draw_fill_chars(6, 13, 67, 47, DEFAULT_FG, 0); draw_box(50, 12, 61, 48, BOX_THIN | BOX_INNER | BOX_SHADE_NONE); 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 && os_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; 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(str_from_num(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_utf8_len(file->base ? file->base : "", 18, 32, pos, fg, bg); if (file->base && slash_search_mode > -1) { if (charset_strncasecmp(file->base, CHARSET_CHAR, slash_search_str, CHARSET_CP437, slash_search_mode) == 0) { size_t len = charset_strncasecmplen(file->base, CHARSET_CHAR, slash_search_str, CHARSET_CP437, slash_search_mode); draw_text_utf8_len(file->base, MIN(len, 18), 32, pos, 3, 1); } } if (file->sampsize > 1) { sprintf(sbuf, "%d Samples", file->sampsize); draw_text_len(sbuf, 10, 51, pos, fg, bg); } else if (file->sampsize == 1) { draw_text("1 Sample ", 51, pos, fg, bg); } else if (file->type & TYPE_MODULE_MASK) { draw_text("\x9a\x9a""Module\x9a\x9a", 51, pos, fg, bg); } else { draw_text(" ", 51, pos, fg, bg); } if (file->filesize > 1048576) { sprintf(sbuf, "%lum", (unsigned long)(file->filesize / 1048576)); } else if (file->filesize > 1024) { sprintf(sbuf, "%luk", (unsigned long)(file->filesize / 1024)); } else if (file->filesize > 0) { sprintf(sbuf, "%lu", (unsigned long)(file->filesize)); } else { *sbuf = 0; } draw_text_len(sbuf, 6, 62, pos, fg, bg); } /* draw the info for the current file (or directory...) */ while (pos < 48) draw_char(168, 31, pos++, 2, 0); } static void do_enable_inst(SCHISM_UNUSED void *d) { song_set_instrument_mode(1); main_song_changed_cb(); set_page(PAGE_INSTRUMENT_LIST); memused_songchanged(); } static void dont_enable_inst(SCHISM_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; j = charset_strncasecmplen(f->base, CHARSET_CHAR, slash_search_str, CHARSET_CP437, slash_search_mode); if (bl < j) { bl = j; b = i; } } if (bl > -1) { current_file = b; file_list_reposition(); } } /* on the file list, that is */ static void handle_enter_key(void) { dmoz_file_t *file; int cur = instrument_get_current(); if (current_file < 0 || current_file >= flist.num_files) return; file = flist.files[current_file]; dmoz_cache_update(inst_cwd, &flist, NULL); if (file->type & TYPE_BROWSABLE_MASK) { change_dir(file->path); status.flags |= NEED_UPDATE; } else if (file->type & TYPE_INST_MASK) { if (_library_mode) return; status.flags |= SONG_NEEDS_SAVE; if (file->instnum > -1) { song_load_instrument_ex(cur, NULL, file->path, file->instnum); } else { song_load_instrument(cur, file->path); } if (!song_is_instrument_mode()) { dialog_create(DIALOG_YES_NO, "Enable instrument mode?", do_enable_inst, dont_enable_inst, 0, NULL); } else { set_page(PAGE_INSTRUMENT_LIST); } memused_songchanged(); } /* TODO */ } static void do_delete_file(SCHISM_UNUSED void *data) { int old_top_file, old_current_file; char *ptr; if (current_file < 0 || current_file >= flist.num_files) return; ptr = flist.files[current_file]->path; /* would be neat to send it to the trash can if there is one */ unlink(ptr); /* remember the list positions */ old_top_file = top_file; old_current_file = current_file; read_directory(); /* put the list positions back */ top_file = old_top_file; current_file = old_current_file; /* edge case: if this was the last file, move the cursor up */ if (current_file >= flist.num_files) current_file = flist.num_files - 1; file_list_reposition(); } static int file_list_handle_text_input(const char *text) { dmoz_file_t* f = flist.files[current_file]; for (; *text; text++) { if (*text >= 32 && (slash_search_mode > -1 || (f && (f->type & TYPE_DIRECTORY)))) { if (slash_search_mode < 0) slash_search_mode = 0; if (slash_search_mode + 1 < (int)ARRAY_SIZE(slash_search_str)) { slash_search_str[slash_search_mode++] = *text; reposition_at_slash_search(); status.flags |= NEED_UPDATE; } return 1; } } return 0; } static int file_list_handle_key(struct key_event * k) { int new_file = current_file; new_file = CLAMP(new_file, 0, flist.num_files - 1); if (k->mouse != MOUSE_NONE) { if (k->x >= 6 && k->x <= 67 && k->y >= 13 && k->y <= 47) { slash_search_mode = -1; if (k->mouse == MOUSE_SCROLL_UP) { new_file -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { new_file += MOUSE_SCROLL_LINES; } else { new_file = top_file + (k->y - 13); } } } switch (k->sym) { case SCHISM_KEYSYM_UP: new_file--; slash_search_mode = -1; break; case SCHISM_KEYSYM_DOWN: new_file++; slash_search_mode = -1; break; case SCHISM_KEYSYM_PAGEUP: new_file -= 35; slash_search_mode = -1; break; case SCHISM_KEYSYM_PAGEDOWN: new_file += 35; slash_search_mode = -1; break; case SCHISM_KEYSYM_HOME: new_file = 0; slash_search_mode = -1; break; case SCHISM_KEYSYM_END: new_file = flist.num_files - 1; slash_search_mode = -1; break; case SCHISM_KEYSYM_ESCAPE: if (slash_search_mode < 0) { if (k->state == KEY_RELEASE && NO_MODIFIER(k->mod)) set_page(PAGE_SAMPLE_LIST); return 1; } /* else fall through */ case SCHISM_KEYSYM_RETURN: if (slash_search_mode < 0) { if (k->state == KEY_PRESS) return 0; handle_enter_key(); slash_search_mode = -1; } else { if (k->state == KEY_PRESS) return 1; slash_search_mode = -1; status.flags |= NEED_UPDATE; return 1; } return 1; case SCHISM_KEYSYM_DELETE: if (k->state == KEY_RELEASE) return 1; slash_search_mode = -1; if (flist.num_files > 0) dialog_create(DIALOG_OK_CANCEL, "Delete file?", do_delete_file, NULL, 1, NULL); return 1; case SCHISM_KEYSYM_BACKSPACE: if (slash_search_mode > -1) { if (k->state == KEY_RELEASE) return 1; slash_search_mode--; status.flags |= NEED_UPDATE; reposition_at_slash_search(); return 1; } SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_SLASH: if (slash_search_mode < 0) { if (k->state == KEY_PRESS) return 0; slash_search_mode = 0; status.flags |= NEED_UPDATE; return 1; } SCHISM_FALLTHROUGH; default: if (k->text) return file_list_handle_text_input(k->text); if (!k->mouse) return 0; } if (k->mouse == MOUSE_CLICK) { if (k->state == KEY_RELEASE) return 0; } else if (k->mouse == MOUSE_DBLCLICK) { handle_enter_key(); return 1; } else { /* prevent moving the cursor twice from a single key press */ if (k->state == KEY_RELEASE) return 1; } new_file = CLAMP(new_file, 0, flist.num_files - 1); if (new_file != current_file) { current_file = new_file; file_list_reposition(); status.flags |= NEED_UPDATE; } return 1; } static void load_instrument_handle_key(struct key_event * k) { if (k->state == KEY_RELEASE) return; if (k->sym == SCHISM_KEYSYM_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; widget_create_other(widgets_loadinst + 0, 0, file_list_handle_key, file_list_handle_text_input, file_list_draw); widgets_loadinst[0].accept_text = 1; } void library_instrument_load_page(struct page *page) { page->title = "Instrument Library (Ctrl-F4)"; page->draw_const = load_instrument_draw_const; page->set_page = library_instrument_set_page; page->handle_key = load_instrument_handle_key; page->total_widgets = 1; page->widgets = widgets_loadinst; page->help_index = HELP_GLOBAL; } schismtracker-20250313/schism/page_loadmodule.c000066400000000000000000000763631476471630300214220ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "config.h" #include "charset.h" #include "song.h" #include "page.h" #include "dmoz.h" #include "log.h" #include "fmt.h" /* only needed for SAVE_SUCCESS ... */ #include "widget.h" #include "dialog.h" #include "vgamem.h" #include "osdefs.h" #include "mem.h" #include "str.h" #include #include #include #include #include "disko.h" /* --------------------------------------------------------------------- */ /* the locals */ static int modgrep(dmoz_file_t *f); static struct widget widgets_loadmodule[5]; static struct widget widgets_exportmodule[16]; static struct widget widgets_savemodule[16]; static struct widget *widgets_exportsave; static int top_file = 0, top_dir = 0; static time_t directory_mtime; static dmoz_filelist_t flist; static dmoz_dirlist_t dlist; #define current_file flist.selected #define current_dir dlist.selected /* filename_entry is generally a glob pattern, but typing a file/path name directly and hitting enter will load the file. glob_list is a split-up bunch of globs that gets updated if enter is pressed while filename_entry contains a '*' or '?' character. dirname_entry is copied from the module directory (off the vars page) when this page is loaded, and copied back when the directory is changed. in general the two variables will be the same, but editing the text directly won't screw up the directory listing or anything. (hitting enter will cause the changed callback, which will copy the text from dirname_entry to the actual configured string and update the current directory.) */ /* impulse tracker's glob list: *.it; *.xm; *.s3m; *.mtm; *.669; *.mod unsupported formats that the title reader knows about, even though we can't load them: *.f2r; *.liq; *.dtm; *.ntk; *.mf formats that might be supported, but which i have never seen and thus don't actually care about: *.dbm; *.dsm; *.psm other formats that i wouldn't bother presenting in the loader even if we could load them: *.mid; *.wav; *.mp3; *.ogg; *.sid; *.umx formats that modplug pretends to support, but fails hard: *.ams TODO: scroller hack on selected filename */ #define GLOB_CLASSIC "*.it; *.xm; *.s3m; *.mtm; *.669; *.mod" #define GLOB_DEFAULT GLOB_CLASSIC "; *.dsm; *.mdl; *.mt2; *.stm; *.stx; *.far; *.ult; *.med; *.ptm; *.okt; *.amf; *.dmf; *.imf; *.sfx; *.mus; *.mid" /* These are stored as CP437 */ static char filename_entry[SCHISM_PATH_MAX + 1] = {0}; static char dirname_entry[SCHISM_PATH_MAX + 1] = {0}; char cfg_module_pattern[SCHISM_PATH_MAX + 1] = GLOB_DEFAULT; char cfg_export_pattern[SCHISM_PATH_MAX + 1] = "*.wav; *.aiff; *.aif"; static char **glob_list = NULL; static char glob_list_src[SCHISM_PATH_MAX + 1] = {0}; // the pattern used to make glob_list (this is an icky hack) /* --------------------------------------------------------------------- */ static char **semicolon_split(const char *i) { int n = 1; const char *j; char *a, *z, **o, **p; if (!i) return NULL; i += strspn(i, "; \t"); if (!*i) return NULL; /* how many MIGHT we have? */ for (j = i; j; j = strchr(j + 1, ';')) n++; o = p = mem_calloc(n, sizeof(char *)); a = str_dup(i); do { *p++ = a; z = strchr(a, ';'); if (!z) z = strchr(a, 0); /* trim whitespace */ do { z--; } while (isblank(*z)); z++; /* find start of the next one */ a = z; a += strspn(a, "; \t"); *z = 0; } while (*a); return o; } /* --------------------------------------------------------------------- */ /* page-dependent stuff (load or save) */ /* there should be a more useful way to determine which page to set. i.e., if there were errors/warnings, show the log; otherwise, switch to the blank page (or, for the loader, maybe the previously set page if classic mode is off?) idea for return codes: 0 = couldn't load/save, error dumped to log 1 = no warnings or errors were printed. 2 = there were warnings, but the song was still loaded/saved. */ static void handle_file_entered_L(const char *ptr) { dmoz_filelist_t tmp = {0}; struct stat sb; /* these shenanigans force the file to take another trip... */ if (os_stat(ptr, &sb) == -1) return; dmoz_add_file(&tmp, str_dup(ptr), str_dup(ptr), &sb, 0); dmoz_free(&tmp, NULL); song_load(ptr); } static void loadsave_song_changed(void) { int r = 4; /* what? */ int i; const char *ext; const char *ptr = song_get_filename(); if (!ptr) return; ext = dmoz_path_get_extension(ptr); if (ext[0] && ext[1]) { for (i = 0; song_save_formats[i].label; i++) { if (charset_strcasecmp(ext, CHARSET_CHAR, song_save_formats[i].ext, CHARSET_CHAR) == 0) { /* ugh :) offset to the button for the file type on the save module page is (position in diskwriter driver array) + 4 */ r = i + 4; break; } } } widget_togglebutton_set(widgets_savemodule, r, 0); } /* NOTE: ptr should be dynamically allocated, or NULL */ static void do_save_song(char *ptr) { int ret, export = (status.current_page == PAGE_EXPORT_MODULE); const char *filename = ptr ? ptr : song_get_filename(); const char *seltype = NULL; struct widget *widget; set_page(PAGE_LOG); // 4 is the index of the first file-type button for (widget = (export ? widgets_exportmodule : widgets_savemodule) + 4; widget->type == WIDGET_TOGGLEBUTTON; widget++) { if (widget->d.togglebutton.state) { // Aha! seltype = widget->d.togglebutton.text; break; } } if (!seltype) { // No button was selected? (should never happen) log_appendf(4, "No file format selected?"); ret = SAVE_INTERNAL_ERROR; } else if (export) { ret = song_export(filename, seltype); } else { ret = song_save(filename, seltype); } if (ret != SAVE_SUCCESS) dialog_create(DIALOG_OK, "Could not save file", NULL, NULL, 0, NULL); free(ptr); } void save_song_or_save_as(void) { const char *f = song_get_filename(); if (f && *f) { do_save_song(str_dup(f)); } else { set_page(PAGE_SAVE_MODULE); } } static void do_save_song_overwrite(void *ptr) { struct stat st; if (!(status.flags & CLASSIC_MODE)) { // say what? do_save_song(ptr); return; } if (os_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 (os_stat(cfg_dir_modules, &st) == 0) { directory_mtime = st.st_mtime; } } static void handle_file_entered_S(const char *name) { struct stat buf; if (os_stat(name, &buf) < 0) { if (errno == ENOENT) { do_save_song(str_dup(name)); } else { log_perror(name); } } else { if (S_ISDIR(buf.st_mode)) { /* TODO: maybe change the current directory in this case? */ log_appendf(4, "%s: Is a directory", name); } else if (S_ISREG(buf.st_mode)) { dialog_create(DIALOG_OK_CANCEL, "Overwrite file?", do_save_song_overwrite, free, 1, str_dup(name)); } else { /* log_appendf(4, "%s: Not overwriting non-regular file", ptr); */ dialog_create(DIALOG_OK, "Not a regular file", NULL, NULL, 0, NULL); } } } static void (*handle_file_entered)(const char *); /* --------------------------------------------------------------------- */ /* get a color index from a dmoz_file_t 'type' field */ static inline int get_type_color(int type) { /* 7 unknown 3 it 5 s3m 6 xm 2 mod 4 other 7 sample */ switch (type) { case TYPE_MODULE_MOD: return 2; case TYPE_MODULE_S3M: return 5; case TYPE_MODULE_XM: return 6; case TYPE_MODULE_IT: return 3; case TYPE_SAMPLE_COMPR: return 4; /* mp3/ogg 'sample'... i think */ default: return 7; } } static void clear_directory(void) { dmoz_free(&flist, &dlist); } static int modgrep(dmoz_file_t *f) { int i = 0; if (!glob_list) return 1; for (i = 0; glob_list[i]; i++) if (charset_fnmatch(glob_list[i], CHARSET_CHAR, f->base, CHARSET_CHAR, CHARSET_FNM_PERIOD | CHARSET_FNM_CASEFOLD) == 0) return 1; return 0; } /* --------------------------------------------------------------------- */ static void file_list_reposition(void) { if (current_file >= flist.num_files) current_file = flist.num_files-1; if (current_file < 0) current_file = 0; if (current_file < top_file) top_file = current_file; else if (current_file > top_file + 30) top_file = current_file - 30; status.flags |= NEED_UPDATE; } static void dir_list_reposition(void) { if (current_dir >= dlist.num_dirs) current_dir = dlist.num_dirs-1; if (current_dir < 0) current_dir = 0; if (current_dir < top_dir) top_dir = current_dir; else if (current_dir > top_dir + 21) top_dir = current_dir - 21; status.flags |= NEED_UPDATE; } static void read_directory(void) { struct stat st; clear_directory(); if (os_stat(cfg_dir_modules, &st) < 0) directory_mtime = 0; else directory_mtime = st.st_mtime; /* if the stat call failed, this will probably break as well, but at the very least, it'll add an entry for the root directory. */ if (dmoz_read(cfg_dir_modules, &flist, &dlist, NULL) < 0) log_perror(cfg_dir_modules); dmoz_filter_filelist(&flist, modgrep, ¤t_file, file_list_reposition); while (dmoz_worker()); /* don't do it asynchronously */ dmoz_cache_lookup(cfg_dir_modules, &flist, &dlist); // background the title checker dmoz_filter_filelist(&flist, dmoz_fill_ext_data, ¤t_file, file_list_reposition); file_list_reposition(); dir_list_reposition(); } /* --------------------------------------------------------------------- */ static void set_glob(const char *globspec) { if (glob_list) { free(*glob_list); free(glob_list); } strncpy(glob_list_src, globspec, ARRAY_SIZE(glob_list_src) - 1); glob_list_src[ARRAY_SIZE(glob_list_src) - 1] = '\0'; glob_list = semicolon_split(glob_list_src); /* this is kinda lame. dmoz should have a way to reload the list without rereading the directory. could be done with a "visible" flag, which affects the list's sort order, along with adjusting the file count... */ read_directory(); } static void set_default_glob(int set_filename) { const char *s = (status.current_page == PAGE_EXPORT_MODULE) ? cfg_export_pattern : cfg_module_pattern; if (set_filename) { void *out = charset_iconv_easy(s, CHARSET_CHAR, CHARSET_CP437); if (out) { strncpy(filename_entry, out, ARRAY_SIZE(filename_entry) - 1); free(out); } } set_glob(s); } /* --------------------------------------------------------------------- */ static char search_text[SCHISM_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, DEFAULT_FG, 0); draw_text_bios_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, 0, 6); } } static void search_update(void) { int n; if (search_text_length > 25) search_first_char = search_text_length - 25; else search_first_char = 0; /* go through the file/dir list (whatever one is selected) and * find the first entry matching the text */ if (*selected_widget == 0) { for (n = 0; n < flist.num_files; n++) { if (charset_strncasecmp(flist.files[n]->base, CHARSET_CHAR, search_text, CHARSET_CP437, search_text_length) == 0) { current_file = n; file_list_reposition(); break; } } } else { for (n = 0; n < dlist.num_dirs; n++) { if (charset_strncasecmp(dlist.dirs[n]->base, CHARSET_CHAR, search_text, CHARSET_CP437, search_text_length) == 0) { current_dir = n; dir_list_reposition(); break; } } } status.flags |= NEED_UPDATE; } static int search_text_add_char(uint8_t c) { if (c < 32) return 0; if (search_text_length + 1 >= (int)ARRAY_SIZE(search_text)) return 1; search_text[search_text_length++] = c; search_text[search_text_length] = 0; search_update(); return 1; } static void search_text_delete_char(void) { if (search_text_length == 0) return; search_text[--search_text_length] = 0; if (search_text_length > 25) search_first_char = search_text_length - 25; else search_first_char = 0; status.flags |= NEED_UPDATE; } static void search_text_clear(void) { search_text[0] = search_text_length = search_first_char = 0; status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ /* return: 1 = success, 0 = failure TODO: provide some sort of feedback if something went wrong. */ static int change_dir(const char *dir) { char *ptr = dmoz_path_normal(dir); if (!ptr) return 0; dmoz_cache_update(cfg_dir_modules, &flist, &dlist); strncpy(cfg_dir_modules, ptr, ARRAY_SIZE(cfg_dir_modules) - 1); cfg_dir_modules[ARRAY_SIZE(cfg_dir_modules) - 1] = 0; // TODO charset_strncpy ? void *out = charset_iconv_easy(ptr, CHARSET_CHAR, CHARSET_CP437); if (out) { strncpy(dirname_entry, out, ARRAY_SIZE(dirname_entry) - 1); free(out); } free(ptr); /* probably not all of this is needed everywhere */ search_text_clear(); read_directory(); return 1; } /* --------------------------------------------------------------------- */ /* unfortunately, there's not enough room with this layout for labels by * the search box and file information. :( */ static void both_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, 49, 44, BOX_THICK | BOX_INNER | BOX_INSET); /* file list */ draw_box(50, 36, 77, 38, BOX_THICK | BOX_INNER | BOX_INSET); /* search */ draw_box(50, 39, 77, 44, BOX_THICK | BOX_INNER | BOX_INSET); /* file info */ draw_box(12, 45, 77, 48, BOX_THICK | BOX_INNER | BOX_INSET); /* filename and directory input */ draw_fill_chars(13, 46, 76, 47, DEFAULT_FG, 0); } static void load_module_draw_const(void) { both_module_draw_const(); /* dir list */ draw_box(50, 12, 77, 35, BOX_THICK | BOX_INNER | BOX_INSET); draw_fill_chars(51, 37, 76, 37, DEFAULT_FG, 0); } static void save_module_draw_const(void) { both_module_draw_const(); /* dir list */ draw_box(50, 12, 68, 35, BOX_THICK | BOX_INNER | BOX_INSET); draw_fill_chars(51, 37, 67, 37, DEFAULT_FG, 0); } /* --------------------------------------------------------------------- */ 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, 48, 43, DEFAULT_FG, 0); if (flist.num_files > 0) { if (top_file < 0) top_file = 0; if (current_file < 0) current_file = 0; for (n = top_file, pos = 13; n < flist.num_files && pos < 44; n++, pos++) { file = flist.files[n]; if (n == current_file && ACTIVE_PAGE.selected_widget == 0) { fg1 = fg2 = 0; bg = 3; } else { fg1 = get_type_color(file->type); fg2 = (file->type & TYPE_MODULE_MASK) ? 3 : 7; bg = 0; } draw_text_utf8_len(file->base ? file->base : "", 20, 3, pos, fg1, bg); draw_char(168, 23, pos, 2, bg); draw_text_len(file->title ? file->title : "", 25, 24, 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, "%09lu", (unsigned long)file->filesize); draw_text_len(buf, 26, 51, 41, 5, 0); draw_text_len(str_from_date(file->timestamp, buf, cfg_str_date_format), 26, 51, 42, 5, 0); draw_text_len(str_from_time(file->timestamp, buf, cfg_str_time_format), 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, 48, 13, DEFAULT_FG, 3); draw_char(168, 23, 13, 2, 3); pos = 14; } else { draw_text("No files.", 3, 13, 7, 0); pos = 13; } draw_fill_chars(51, 40, 76, 43, DEFAULT_FG, 0); } while (pos < 44) draw_char(168, 23, pos++, 2, 0); /* bleh */ search_redraw(); } static void do_delete_file(SCHISM_UNUSED void *data) { int old_top_file, old_current_file, old_top_dir, old_current_dir; char *ptr; if (current_file < 0 || current_file >= flist.num_files) return; ptr = flist.files[current_file]->path; /* would be neat to send it to the trash can if there is one */ unlink(ptr); /* remember the list positions */ old_top_file = top_file; old_current_file = current_file; old_top_dir = top_dir; old_current_dir = current_dir; search_text_clear(); read_directory(); /* put the list positions back */ top_file = old_top_file; current_file = old_current_file; top_dir = old_top_dir; current_dir = old_current_dir; /* edge case: if this was the last file, move the cursor up */ if (current_file >= flist.num_files) current_file = flist.num_files - 1; file_list_reposition(); } static void show_selected_song_length(void) { if (current_file < 0 || current_file >= flist.num_files) return; char *ptr = flist.files[current_file]->path; song_t *song = song_create_load(ptr); if (!song) { log_appendf(4, "%s: %s", ptr, fmt_strerror(errno)); return; } show_length_dialog(dmoz_path_get_basename(ptr), csf_get_length(song)); csf_free(song); } static int file_list_handle_text_input(const char *text) { int success = 0; for (; *text; text++) if (search_text_add_char(*(unsigned char *)text)) success = 1; return success; } static int file_list_handle_key(struct key_event * k) { int new_file = current_file; switch (k->sym) { case SCHISM_KEYSYM_UP: new_file--; break; case SCHISM_KEYSYM_DOWN: new_file++; break; case SCHISM_KEYSYM_PAGEUP: new_file -= 31; break; case SCHISM_KEYSYM_PAGEDOWN: new_file += 31; break; case SCHISM_KEYSYM_HOME: new_file = 0; break; case SCHISM_KEYSYM_END: new_file = flist.num_files - 1; break; case SCHISM_KEYSYM_RETURN: if (k->state == KEY_PRESS) return 1; if (current_file < flist.num_files) { dmoz_cache_update(cfg_dir_modules, &flist, &dlist); handle_file_entered(flist.files[current_file]->path); } search_text_clear(); return 1; case SCHISM_KEYSYM_DELETE: if (k->state == KEY_RELEASE) return 1; if (flist.num_files > 0) dialog_create(DIALOG_OK_CANCEL, "Delete file?", do_delete_file, NULL, 1, NULL); return 1; case SCHISM_KEYSYM_BACKSPACE: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_CTRL) search_text_clear(); else search_text_delete_char(); return 1; case SCHISM_KEYSYM_p: if ((k->mod & SCHISM_KEYMOD_ALT) && k->state == KEY_PRESS) { show_selected_song_length(); return 1; } /* else fall through */ default: if (k->mouse == MOUSE_NONE) { if (k->text) return file_list_handle_text_input(k->text); return 0; } } if (k->mouse != MOUSE_NONE && !(k->x >= 3 && k->x <= 51 && k->y >= 13 && k->y <= 43)) return 0; switch (k->mouse) { case MOUSE_CLICK: if (k->state == KEY_PRESS) return 0; new_file = (k->y - 13) + top_file; break; case MOUSE_DBLCLICK: if (current_file < flist.num_files) { dmoz_cache_update(cfg_dir_modules, &flist, &dlist); handle_file_entered(flist.files[current_file]->path); } search_text_clear(); return 1; case MOUSE_SCROLL_UP: case MOUSE_SCROLL_DOWN: if (k->state == KEY_PRESS) return 0; top_file += (k->mouse == MOUSE_SCROLL_UP) ? -MOUSE_SCROLL_LINES : MOUSE_SCROLL_LINES; /* don't allow scrolling down past either end. this can't be CLAMP'd because the first check might scroll too far back if the list is small. (hrm, should add a BOTTOM_FILE macro or something) */ if (top_file > flist.num_files - 31) top_file = flist.num_files - 31; if (top_file < 0) top_file = 0; status.flags |= NEED_UPDATE; return 1; default: /* prevent moving the cursor twice from a single key press */ if (k->state == KEY_RELEASE) return 1; } new_file = CLAMP(new_file, 0, flist.num_files - 1); if (new_file < 0) new_file = 0; if (new_file != current_file) { current_file = new_file; file_list_reposition(); status.flags |= NEED_UPDATE; } return 1; } /* --------------------------------------------------------------------- */ /* These could check for the current page, but that's kind of weird to do * and I just don't like that idea in general :) */ static void dir_list_draw(int width) { int n, pos, fg, bg; draw_fill_chars(51, 13, 51 + width - 1, 34, DEFAULT_FG, 0); for (n = top_dir, pos = 13; pos < 35; n++, pos++) { if (n < 0) continue; /* er... */ if (n >= dlist.num_dirs) break; if (n == current_dir && ACTIVE_PAGE.selected_widget == 1) { fg = 0; bg = 3; } else { fg = 5; bg = 0; } draw_text_utf8_len(dlist.dirs[n]->base, width, 51, pos, fg, bg); } /* bleh */ search_redraw(); } static void dir_list_draw_load(void) { dir_list_draw(77 - 51); } static void dir_list_draw_exportsave(void) { dir_list_draw(68 - 51); } static int dir_list_handle_text_input(const char *text) { for (; *text && search_text_length < (int)ARRAY_SIZE(search_text) - 1; text++) { if (*text < 32) return 0; search_text[search_text_length++] = *text; search_text[search_text_length] = '\0'; } search_update(); return 1; } static inline int dir_list_handle_key(struct key_event * k, unsigned int width) { int new_dir = current_dir; if (k->mouse != MOUSE_NONE) { if (k->x >= 51 && k->x <= (51 + width - 1) && k->y >= 13 && k->y <= 34) { switch (k->mouse) { case MOUSE_CLICK: new_dir = (k->y - 13) + top_dir; break; case 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; break; case MOUSE_SCROLL_UP: case MOUSE_SCROLL_DOWN: top_dir += (k->mouse == MOUSE_SCROLL_UP) ? -MOUSE_SCROLL_LINES : MOUSE_SCROLL_LINES; if (top_dir > dlist.num_dirs - 21) top_dir = dlist.num_dirs - 21; if (top_dir < 0) top_dir = 0; status.flags |= NEED_UPDATE; break; default: break; } } else { return 0; } } switch (k->sym) { case SCHISM_KEYSYM_UP: new_dir--; break; case SCHISM_KEYSYM_DOWN: new_dir++; break; case SCHISM_KEYSYM_PAGEUP: new_dir -= 21; break; case SCHISM_KEYSYM_PAGEDOWN: new_dir += 21; break; case SCHISM_KEYSYM_HOME: new_dir = 0; break; case SCHISM_KEYSYM_END: new_dir = dlist.num_dirs - 1; break; case SCHISM_KEYSYM_RETURN: if (k->state == KEY_PRESS) return 0; /* reset */ top_file = current_file = 0; if (current_dir >= 0 && current_dir < dlist.num_dirs) change_dir(dlist.dirs[current_dir]->path); if (flist.num_files > 0) *selected_widget = 0; status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_BACKSPACE: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) search_text_clear(); else search_text_delete_char(); return 1; case SCHISM_KEYSYM_SLASH: #ifdef SCHISM_WIN32 case SCHISM_KEYSYM_BACKSLASH: #endif if (k->state == KEY_RELEASE) return 0; if (search_text_length == 0 && current_dir != 0) { // slash -> go to top (root) dir new_dir = 0; } else if (current_dir > 0 && current_dir < dlist.num_dirs) { change_dir(dlist.dirs[current_dir]->path); status.flags |= NEED_UPDATE; return 1; } break; default: if (k->mouse == MOUSE_NONE) { if (k->text) return dir_list_handle_text_input(k->text); return 0; } } if (k->mouse == MOUSE_CLICK) { if (k->state == KEY_PRESS) return 0; } else { if (k->state == KEY_RELEASE) return 0; } new_dir = CLAMP(new_dir, 0, dlist.num_dirs - 1); if (new_dir != current_dir) { current_dir = new_dir; dir_list_reposition(); status.flags |= NEED_UPDATE; } return 1; } static int dir_list_handle_key_load(struct key_event * k) { return dir_list_handle_key(k, 77 - 51); } static int dir_list_handle_key_exportsave(struct key_event * k) { return dir_list_handle_key(k, 68 - 51); } /* --------------------------------------------------------------------- */ /* these handle when enter is pressed on the file/directory textboxes at the bottom of the screen. */ static void filename_entered(void) { if (strpbrk(filename_entry, "?*")) { set_glob(filename_entry); } else { void *fn = charset_iconv_easy(filename_entry, CHARSET_CP437, CHARSET_CHAR); char *ptr = dmoz_path_concat(cfg_dir_modules, fn); free(fn); handle_file_entered(ptr); free(ptr); } } /* strangely similar to the dir list's code :) */ static void dirname_entered(void) { void *out = charset_iconv_easy(dirname_entry, CHARSET_CP437, CHARSET_CHAR); if (!out) return; if (!change_dir(out)) { free(out); return; } free(out); *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 && os_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 void load_module_set_page(void) { handle_file_entered = handle_file_entered_L; if (update_directory()) pages[PAGE_LOAD_MODULE].selected_widget = (flist.num_files > 0) ? 0 : 1; // Don't reparse the glob if it hasn't changed; that will mess with the cursor position if (!charset_strcasecmp(glob_list_src, CHARSET_CHAR, cfg_module_pattern, CHARSET_CHAR)) strcpy(filename_entry, glob_list_src); else set_default_glob(1); } void load_module_load_page(struct page *page) { clear_directory(); top_file = top_dir = 0; current_file = current_dir = 0; dir_list_reposition(); file_list_reposition(); page->title = "Load Module (F9)"; page->draw_const = load_module_draw_const; page->set_page = load_module_set_page; page->total_widgets = 4; page->widgets = widgets_loadmodule; page->help_index = HELP_GLOBAL; widget_create_other(widgets_loadmodule + 0, 1, file_list_handle_key, file_list_handle_text_input, file_list_draw); widgets_loadmodule[0].accept_text = 1; widgets_loadmodule[0].x = 3; widgets_loadmodule[0].y = 13; widgets_loadmodule[0].width = 44; widgets_loadmodule[0].height = 30; widgets_loadmodule[0].next.left = widgets_loadmodule[0].next.right = 1; widget_create_other(widgets_loadmodule + 1, 2, dir_list_handle_key_load, dir_list_handle_text_input, dir_list_draw_load); widgets_loadmodule[1].accept_text = 1; widgets_loadmodule[1].x = 50; widgets_loadmodule[1].y = 13; widgets_loadmodule[1].width = 27; widgets_loadmodule[1].height = 21; widget_create_textentry(widgets_loadmodule + 2, 13, 46, 64, 0, 3, 3, NULL, filename_entry, ARRAY_SIZE(filename_entry) - 1); widgets_loadmodule[2].activate = filename_entered; widget_create_textentry(widgets_loadmodule + 3, 13, 47, 64, 2, 3, 0, NULL, dirname_entry, ARRAY_SIZE(dirname_entry) - 1); widgets_loadmodule[3].activate = dirname_entered; } /* --------------------------------------------------------------------- */ static void save_module_set_page(void) { handle_file_entered = handle_file_entered_S; update_directory(); /* impulse tracker always resets these; so will i */ set_default_glob(0); filename_entry[0] = 0; pages[PAGE_SAVE_MODULE].selected_widget = 2; widgets_exportsave = (status.current_page == PAGE_EXPORT_MODULE) ? widgets_exportmodule : widgets_savemodule; if (status.current_page == PAGE_EXPORT_MODULE && current_song->orderlist[0] == ORDER_LAST) dialog_create(DIALOG_OK, "You're about to export a blank file...", NULL, NULL, 0, NULL); } void save_module_load_page(struct page *page, int do_export) { int n, c; if (do_export) { page->title = "Export Module (Shift-F10)"; page->widgets = widgets_exportmodule; } else { page->title = "Save Module (F10)"; page->widgets = widgets_savemodule; } widgets_exportsave = page->widgets; /* preload */ clear_directory(); top_file = top_dir = 0; current_file = current_dir = 0; dir_list_reposition(); file_list_reposition(); page->draw_const = save_module_draw_const; page->set_page = save_module_set_page; page->total_widgets = 4; page->help_index = HELP_GLOBAL; page->selected_widget = 2; page->song_changed_cb = loadsave_song_changed; widget_create_other(widgets_exportsave + 0, 1, file_list_handle_key, file_list_handle_text_input, file_list_draw); widgets_exportsave[0].accept_text = 1; widgets_exportsave[0].next.left = 4; widgets_exportsave[0].next.right = widgets_exportsave[0].next.tab = 1; widgets_exportsave[0].x = 3; widgets_exportsave[0].y = 13; widgets_exportsave[0].width = 44; widgets_exportsave[0].height = 30; widget_create_other(widgets_exportsave + 1, 2, dir_list_handle_key_exportsave, dir_list_handle_text_input, dir_list_draw_exportsave); widgets_exportsave[1].accept_text = 1; widgets_exportsave[1].next.right = widgets_exportsave[1].next.tab = 5; widgets_exportsave[1].next.left = 0; widgets_exportsave[1].x = 50; widgets_exportsave[1].y = 13; widgets_exportsave[1].width = 18; widgets_exportsave[1].height = 21; widget_create_textentry(widgets_exportsave + 2, 13, 46, 64, 0, 3, 3, NULL, filename_entry, ARRAY_SIZE(filename_entry) - 1); widgets_exportsave[2].activate = filename_entered; widget_create_textentry(widgets_exportsave + 3, 13, 47, 64, 2, 0, 0, NULL, dirname_entry, ARRAY_SIZE(dirname_entry) - 1); widgets_exportsave[3].activate = dirname_entered; widgets_exportsave[4].d.togglebutton.state = 1; const struct save_format *formats = (do_export ? song_export_formats : song_save_formats); // get the number of formats for (c = 0, n = 0; formats[n].label; n++) { if (formats[n].enabled && !formats[n].enabled()) continue; c++; } // build the filetypes list int *filetypes = mem_alloc((c + 1) * sizeof(int)); for (c = 0, n = 0; formats[n].label; n++) { if (formats[n].enabled && !formats[n].enabled()) continue; filetypes[c] = 4 + c; c++; } filetypes[c] = -1; // create the widgets for (c = 0, n = 0; formats[n].label; n++) { if (formats[n].enabled && !formats[n].enabled()) continue; widget_create_togglebutton(widgets_exportsave + 4 + c, 70, 13 + (3 * c), 5, 4 + (c == 0 ? 0 : (c - 1)), 4 + (c + 1), 1, 2, 2, NULL, formats[n].label, (5 - strlen(formats[n].label)) / 2 + 1, filetypes); widgets_exportsave[4 + c].next.backtab = 1; c++; } widgets_exportsave[4 + c - 1].next.down = 2; page->total_widgets += c; } schismtracker-20250313/schism/page_loadsample.c000066400000000000000000000671341476471630300214120ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "config.h" #include "charset.h" #include "song.h" #include "page.h" #include "dmoz.h" #include "sample-edit.h" #include "keyboard.h" #include "fakemem.h" #include "log.h" #include "widget.h" #include "dialog.h" #include "vgamem.h" #include "osdefs.h" #include "str.h" #include #include #include #include /* --------------------------------------------------------------------------------------------------------- */ /* the locals */ static struct vgamem_overlay sample_image = { 52,25,76,28, NULL, 0, 0, 0, }; static char current_filename[22]; static int sample_speed_pos = 0; static int sample_loop_beg = 0; static int sample_loop_end = 0; static int sample_susloop_beg = 0; static int sample_susloop_end = 0; static int _library_mode = 0; static struct widget widgets_loadsample[15]; static int fake_slot_changed = 0; static int will_move_to = -1; static int fake_slot = KEYJAZZ_NOINST; static const char *const loop_states[] = { "Off", "On Forwards", "On Ping Pong", NULL }; static char samp_cwd[SCHISM_PATH_MAX] = {0}; static void handle_preload(void); /* --------------------------------------------------------------------------------------------------------- */ /* files: file type color displayed title notes --------- ----- --------------- ----- unchecked 4 IT uses color 6 for these directory 5 "........Directory........" dots are char 154 (same for libraries) sample 3 libraries 6 ".........Library........." IT uses color 3. maybe use module name here? unknown 2 any regular file that's not recognized */ static int top_file = 0; static time_t directory_mtime; static dmoz_filelist_t flist; #define current_file (flist.selected) static int search_pos = -1; static char search_str[SCHISM_PATH_MAX]; /* get a color index from a dmoz_file_t 'type' field */ static inline int get_type_color(int type) { if (type == TYPE_DIRECTORY) return 5; if (!(type & TYPE_EXT_DATA_MASK)) return 4; /* unchecked */ if (type & TYPE_BROWSABLE_MASK) return 6; /* library */ if (type == TYPE_UNKNOWN) return 2; return 3; /* sample */ } static void clear_directory(void) { dmoz_free(&flist, NULL); fake_slot = KEYJAZZ_NOINST; fake_slot_changed = 0; } static void file_list_reposition(void) { dmoz_file_t *f; current_file = CLAMP(current_file, 0, flist.num_files - 1); // XXX use CLAMP() here too, I can't brain if (current_file < top_file) top_file = current_file; else if (current_file > top_file + 34) top_file = current_file - 34; if (current_file >= 0 && current_file < flist.num_files) { f = flist.files[current_file]; if (f && f->smp_filename) { strncpy(current_filename, f->smp_filename, ARRAY_SIZE(current_filename) - 1); } else if (f && f->base) { // FIXME void *fn = charset_iconv_easy(f->base, CHARSET_CHAR, CHARSET_CP437); if (fn) { strncpy(current_filename, fn, ARRAY_SIZE(current_filename) - 1); free(fn); } } else { current_filename[0] = '\0'; } widgets_loadsample[1].d.textentry.firstchar = 0; widgets_loadsample[1].d.textentry.cursor_pos = strlen(current_filename); widgets_loadsample[2].d.numentry.value = f ? f->smp_speed : 0; if (f && f->smp_flags & CHN_PINGPONGLOOP) { widgets_loadsample[3].d.menutoggle.state = 2; } else if (f && f->smp_flags & CHN_LOOP) { widgets_loadsample[3].d.menutoggle.state = 1; } else { widgets_loadsample[3].d.menutoggle.state = 0; } widgets_loadsample[4].d.numentry.value = f ? f->smp_loop_start : 0; widgets_loadsample[5].d.numentry.value = f ? f->smp_loop_end : 0; if (f && f->smp_flags & CHN_PINGPONGSUSTAIN) { widgets_loadsample[6].d.menutoggle.state = 2; } else if (f && f->smp_flags & CHN_SUSTAINLOOP) { widgets_loadsample[6].d.menutoggle.state = 1; } else { widgets_loadsample[6].d.menutoggle.state = 0; } widgets_loadsample[7].d.numentry.value = f ? f->smp_sustain_start : 0; widgets_loadsample[8].d.numentry.value = f ? f->smp_sustain_end : 0; widgets_loadsample[9].d.thumbbar.value = f ? f->smp_defvol : 64; widgets_loadsample[10].d.thumbbar.value = f ? f->smp_gblvol : 64; widgets_loadsample[11].d.thumbbar.value = f ? f->smp_vibrato_speed : 0; widgets_loadsample[12].d.thumbbar.value = f ? f->smp_vibrato_depth : 0; widgets_loadsample[13].d.thumbbar.value = f ? f->smp_vibrato_rate : 0; if (f) { /* autoload some files */ if (TYPE_SAMPLE_EXTD == (f->type & TYPE_SAMPLE_EXTD) && f->filesize < 0x4000000 && f->smp_length < 0x1000000) handle_preload(); } } } static void read_directory(void) { struct stat st; clear_directory(); if (os_stat(samp_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(samp_cwd, &flist, NULL, dmoz_read_sample_library) < 0) log_perror(samp_cwd); dmoz_filter_filelist(&flist, dmoz_fill_ext_data, ¤t_file, file_list_reposition); dmoz_cache_lookup(samp_cwd, &flist, NULL); file_list_reposition(); } /* return: 1 = success, 0 = failure TODO: provide some sort of feedback if something went wrong. */ static int change_dir(const char *dir) { char *ptr = dmoz_path_normal(dir); struct stat buf; if (!ptr) return 0; dmoz_cache_update(cfg_dir_samples, &flist, NULL); if (!os_stat(ptr, &buf) && S_ISDIR(buf.st_mode)) { strncpy(cfg_dir_samples, ptr, ARRAY_SIZE(cfg_dir_samples) - 1); // paranoia cfg_dir_samples[ARRAY_SIZE(cfg_dir_samples) - 1] = '\0'; } strncpy(samp_cwd, ptr, ARRAY_SIZE(samp_cwd) - 1); samp_cwd[ARRAY_SIZE(samp_cwd) - 1] = '\0'; free(ptr); read_directory(); return 1; } /* --------------------------------------------------------------------------------------------------------- */ static void load_sample_draw_const(void) { dmoz_file_t *f; song_sample_t *s; char sbuf[64]; draw_box(5, 12, 50, 48, BOX_THICK | BOX_INNER | BOX_INSET); draw_fill_chars(6, 13, 49, 47, DEFAULT_FG, 0); draw_fill_chars(64, 13, 77, 22, DEFAULT_FG, 0); draw_box(62, 32, 72, 35, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(62, 36, 72, 40, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(63, 12, 77, 23, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(51, 24, 77, 29, BOX_THICK | BOX_INNER | BOX_INSET); draw_fill_chars(52, 25, 76, 28, DEFAULT_FG, 0); draw_box(51, 30, 77, 42, BOX_THIN | BOX_INNER | BOX_INSET); draw_fill_chars(59, 44, 76, 47, DEFAULT_FG, 0); draw_box(58, 43, 77, 48, BOX_THICK | BOX_INNER | BOX_INSET); f = NULL; if (current_file >= 0 && current_file < flist.num_files && flist.files[current_file]) { f = flist.files[current_file]; sprintf(sbuf, "%07u", f->smp_length); draw_text_len(sbuf, 13, 64, 22, 2, 0); if (!f->smp_length && !f->smp_filename && !f->smp_flags) { draw_text_len("No sample",13, 64, 21, 2, 0); } else if (f->smp_flags & CHN_STEREO) { draw_text_len( (f->smp_flags & CHN_16BIT ? "16 bit Stereo" : "8 bit Stereo"), 13, 64, 21, 2, 0); } else { draw_text_len( (f->smp_flags & CHN_16BIT ? "16 bit" : "8 bit"), 13, 64, 21, 2, 0); } if (f->description) { draw_text_len(f->description, 18, 59, 44, 5, 0); } else { switch (f->type) { case TYPE_DIRECTORY: draw_text("Directory", 59, 44, 5, 0); break; default: draw_text("Unknown format", 59, 44, 5, 0); break; }; } sprintf(sbuf, "%07ld", (long)f->filesize); draw_text(sbuf, 59, 45, 5,0); str_from_date(f->timestamp, sbuf, cfg_str_date_format); draw_text(sbuf, 59, 46, 5,0); str_from_time(f->timestamp, sbuf, cfg_str_time_format); draw_text(sbuf, 59, 47, 5,0); } /* these are exactly the same as in page_samples.c, apart from * 'quality' and 'length' being one line higher */ draw_text("Filename", 55, 13, 0, 2); draw_text("Speed", 58, 14, 0, 2); draw_text("Loop", 59, 15, 0, 2); draw_text("LoopBeg", 56, 16, 0, 2); draw_text("LoopEnd", 56, 17, 0, 2); draw_text("SusLoop", 56, 18, 0, 2); draw_text("SusLBeg", 56, 19, 0, 2); draw_text("SusLEnd", 56, 20, 0, 2); draw_text("Quality", 56, 21, 0, 2); draw_text("Length", 57, 22, 0, 2); /* these abbreviations are sucky and lame. any suggestions? */ draw_text("Def. Vol.", 53, 33, 0, 2); draw_text("Glb. Vol.", 53, 34, 0, 2); draw_text("Vib.Speed", 53, 37, 0, 2); draw_text("Vib.Depth", 53, 38, 0, 2); draw_text("Vib. Rate", 53, 39, 0, 2); draw_text("Format", 52, 44, 0, 2); draw_text("Size", 54, 45, 0, 2); draw_text("Date", 54, 46, 0, 2); draw_text("Time", 54, 47, 0, 2); if (fake_slot != KEYJAZZ_NOINST) { s = song_get_sample(fake_slot); vgamem_ovl_clear(&sample_image, 0); if (s) draw_sample_data(&sample_image, s); else vgamem_ovl_apply(&sample_image); } } /* --------------------------------------------------------------------------------------------------------- */ static void _common_set_page(void) { struct stat st; if (!*samp_cwd) { strncpy(samp_cwd, cfg_dir_samples, ARRAY_SIZE(samp_cwd) - 1); samp_cwd[ARRAY_SIZE(samp_cwd) - 1] = '\0'; } /* 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 && os_stat(samp_cwd, &st) == 0 && st.st_mtime == directory_mtime) { return; } change_dir(samp_cwd); status.flags &= ~DIR_SAMPLES_CHANGED; fake_slot = KEYJAZZ_NOINST; fake_slot_changed = 0; *selected_widget = 0; search_pos = -1; } static void load_sample_set_page(void) { _library_mode = 0; _common_set_page(); } static void library_sample_set_page(void) { _library_mode = 1; _common_set_page(); } /* --------------------------------------------------------------------------------------------------------- */ static void file_list_draw(void) { int n, pos, fg, bg; char buf[8]; dmoz_file_t *file; /* there's no need to have if (files) { ... } like in the load-module page, because there will always be at least "/" in the list */ if (top_file < 0) top_file = 0; if (current_file < 0) current_file = 0; for (n = top_file, pos = 13; n < flist.num_files && pos < 48; n++, pos++) { file = flist.files[n]; if (n == current_file && ACTIVE_PAGE.selected_widget == 0) { fg = 0; bg = 3; } else { fg = get_type_color(file->type); bg = 0; } draw_text(str_from_num(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_utf8_len(file->base ? file->base : "", 18, 32, pos, fg, bg); /* this is stupid */ if (file->base && search_pos > -1) { if (charset_strncasecmp(file->base, CHARSET_CHAR, search_str, CHARSET_CP437, search_pos) == 0) { size_t len = charset_strncasecmplen(file->base, CHARSET_CHAR, search_str, CHARSET_CP437, search_pos); draw_text_utf8_len(file->base, MIN(len, 18), 32, pos, 3, 1); } } } /* draw the info for the current file (or directory...) */ while (pos < 48) draw_char(168, 31, pos++, 2, 0); } /* --------------------------------------------------------------------------------------------------------- */ /* Nasty mess to load a sample and prompt for stereo convert / create host instrument as necessary. */ static struct widget stereo_cvt_widgets[4]; static void _create_host_ok(void *vpage) { intptr_t page = (intptr_t) vpage; song_create_host_instrument(sample_get_current()); if (page >= 0) set_page(page); } static void _create_host_cancel(void *vpage) { intptr_t page = (intptr_t) vpage; if (page >= 0) set_page(page); } int sample_host_dialog(int newpage) { /* Actually IT defaults to No when the sample slot already had a sample in it, rather than checking if it was assigned to an instrument. Maybe this is better, though? (Not to mention, passing around the extra state that'd be required to do it that way would be kind of messy...) also the double pointer cast sucks. also also, IT says Ok/No here instead of Yes/No... but do I care? */ if (song_is_instrument_mode()) { int used = sample_is_used_by_instrument(sample_get_current()); dialog_create(DIALOG_YES_NO, "Create host instrument?", _create_host_ok, _create_host_cancel, used ? 1 : 0, (void *) (intptr_t) newpage); return 1; } if (newpage >= 0) set_page(newpage); return 0; } static void finish_load(int cur); static void stereo_cvt_complete_left(void) { int cur = sample_get_current(); song_sample_t *smp; smp = song_get_sample(cur); sample_mono_left(smp); dialog_destroy(); finish_load(cur); } static void stereo_cvt_complete_right(void) { int cur = sample_get_current(); song_sample_t *smp; smp = song_get_sample(cur); sample_mono_right(smp); dialog_destroy(); finish_load(cur); } static void stereo_cvt_complete_both(void) { memused_songchanged(); dialog_destroy(); sample_host_dialog(PAGE_SAMPLE_LIST); } static void stereo_cvt_dialog(void) { draw_text("Loading Stereo Sample", 30, 27, 0, 2); } static int stereo_cvt_hk(struct key_event *k) { if (!NO_MODIFIER(k->mod)) return 0; /* trap the default dialog keys - we don't want to escape this dialog without running something */ switch (k->sym) { case SCHISM_KEYSYM_RETURN: printf("why am I here\n"); SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_ESCAPE: case SCHISM_KEYSYM_o: case SCHISM_KEYSYM_c: return 1; case SCHISM_KEYSYM_l: if (k->state == KEY_RELEASE) stereo_cvt_complete_left(); return 1; case SCHISM_KEYSYM_r: if (k->state == KEY_RELEASE) stereo_cvt_complete_right(); return 1; case SCHISM_KEYSYM_s: case SCHISM_KEYSYM_b: if (k->state == KEY_RELEASE) stereo_cvt_complete_both(); return 1; default: return 0; } } static void finish_load(int cur) { song_sample_t *smp; status.flags |= SONG_NEEDS_SAVE; memused_songchanged(); smp = song_get_sample(cur); if (smp->flags & CHN_STEREO) { struct dialog *dd; widget_create_button(stereo_cvt_widgets+0, 27, 30, 6, 0, 0, 2, 1, 1, stereo_cvt_complete_left, "Left", 2); widget_create_button(stereo_cvt_widgets+1, 37, 30, 6, 1, 1, 0, 2, 2, stereo_cvt_complete_both, "Both", 2); widget_create_button(stereo_cvt_widgets+2, 47, 30, 6, 2, 2, 1, 0, 0, stereo_cvt_complete_right, "Right", 1); dd = dialog_create_custom(24, 25, 33, 8, stereo_cvt_widgets, 3, 1, stereo_cvt_dialog, NULL); dd->handle_key = stereo_cvt_hk; return; } sample_host_dialog(PAGE_SAMPLE_LIST); } static void reposition_at_slash_search(void) { dmoz_file_t *f; int i, j, b, bl; if (search_pos < 0) return; bl = b = -1; for (i = 0; i < flist.num_files; i++) { f = flist.files[i]; if (!f || !f->base) continue; j = charset_strncasecmplen(f->base, CHARSET_CHAR, search_str, CHARSET_CP437, search_pos); if (bl < j) { bl = j; b = i; } } if (bl > 0) { current_file = b; file_list_reposition(); } } /* on the file list, that is */ static void handle_enter_key(void) { dmoz_file_t *file; song_sample_t *smp; int cur = sample_get_current(); if (current_file < 0 || current_file >= flist.num_files) return; file = flist.files[current_file]; dmoz_cache_update(cfg_dir_samples, &flist, NULL); dmoz_fill_ext_data(file); if ((file->type & (TYPE_BROWSABLE_MASK|TYPE_INST_MASK)) && !(file->type & TYPE_SAMPLE_MASK)) { change_dir(file->path); status.flags |= NEED_UPDATE; } else if (_library_mode) { return; } else if (file->sample) { /* it's already been loaded, so copy it */ smp = song_get_sample(cur); song_copy_sample(cur, file->sample); strncpy(smp->name, file->title, ARRAY_SIZE(smp->name)); smp->name[25] = 0; void *ptr = charset_iconv_easy(file->base, CHARSET_CHAR, CHARSET_CP437); if (ptr) { strncpy(smp->filename, ptr, ARRAY_SIZE(smp->filename)); free(ptr); } smp->filename[ARRAY_SIZE(smp->filename) - 1] = 0; finish_load(cur); memused_songchanged(); } else if (file->type & TYPE_SAMPLE_MASK) { /* load the sample */ song_load_sample(cur, file->path); finish_load(cur); memused_songchanged(); } } static void do_discard_changes_and_move(SCHISM_UNUSED void *gn) { fake_slot = KEYJAZZ_NOINST; fake_slot_changed = 0; search_pos = -1; current_file = will_move_to; file_list_reposition(); status.flags |= NEED_UPDATE; } static void do_delete_file(SCHISM_UNUSED void *data) { int old_top_file, old_current_file; char *ptr; if (current_file < 0 || current_file >= flist.num_files) return; ptr = flist.files[current_file]->path; /* would be neat to send it to the trash can if there is one */ unlink(ptr); /* remember the list positions */ old_top_file = top_file; old_current_file = current_file; read_directory(); /* put the list positions back */ top_file = old_top_file; current_file = old_current_file; /* edge case: if this was the last file, move the cursor up */ if (current_file >= flist.num_files) current_file = flist.num_files - 1; file_list_reposition(); } static int file_list_handle_text_input(const char *text) { dmoz_file_t* f = flist.files[current_file]; for (; *text; text++) { if (*text >= 32 && (search_pos > -1 || (f && (f->type & TYPE_DIRECTORY)))) { if (search_pos < 0) search_pos = 0; if (search_pos + 1 < (int)ARRAY_SIZE(search_str)) { search_str[search_pos++] = *text; reposition_at_slash_search(); status.flags |= NEED_UPDATE; } return 1; } } return 0; } 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 == SCHISM_KEYSYM_n && (k->mod & SCHISM_KEYMOD_ALT)) { if (k->state == KEY_RELEASE) song_toggle_multichannel_mode(); return 1; } if (k->mouse) { if (k->x >= 6 && k->x <= 49 && k->y >= 13 && k->y <= 47) { search_pos = -1; if (k->mouse == MOUSE_SCROLL_UP) { new_file -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { new_file += MOUSE_SCROLL_LINES; } else { new_file = top_file + (k->y - 13); } } } switch (k->sym) { case SCHISM_KEYSYM_UP: new_file--; search_pos = -1; break; case SCHISM_KEYSYM_DOWN: new_file++; search_pos = -1; break; case SCHISM_KEYSYM_PAGEUP: new_file -= 35; search_pos = -1; break; case SCHISM_KEYSYM_PAGEDOWN: new_file += 35; search_pos = -1; break; case SCHISM_KEYSYM_HOME: new_file = 0; search_pos = -1; break; case SCHISM_KEYSYM_END: new_file = flist.num_files - 1; search_pos = -1; break; case SCHISM_KEYSYM_ESCAPE: if (search_pos < 0) { if (k->state == KEY_RELEASE && NO_MODIFIER(k->mod)) set_page(PAGE_SAMPLE_LIST); return 1; } /* else fall through */ case SCHISM_KEYSYM_RETURN: if (search_pos < 0) { if (k->state == KEY_PRESS) return 0; handle_enter_key(); search_pos = -1; } else { if (k->state == KEY_PRESS) return 1; search_pos = -1; status.flags |= NEED_UPDATE; return 1; } return 1; case SCHISM_KEYSYM_DELETE: if (k->state == KEY_RELEASE) return 1; search_pos = -1; if (flist.num_files > 0) dialog_create(DIALOG_OK_CANCEL, "Delete file?", do_delete_file, NULL, 1, NULL); return 1; case SCHISM_KEYSYM_BACKSPACE: if (search_pos > -1) { if (k->state == KEY_RELEASE) return 1; search_pos--; status.flags |= NEED_UPDATE; reposition_at_slash_search(); return 1; } SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_SLASH: if (search_pos < 0) { if (k->state == KEY_PRESS) return 0; search_pos = 0; status.flags |= NEED_UPDATE; return 1; } SCHISM_FALLTHROUGH; default: if (k->text) file_list_handle_text_input(k->text); if (!k->mouse) return 0; } if (k->mouse == MOUSE_CLICK) { if (k->state == KEY_PRESS) return 0; } else if (k->mouse == MOUSE_DBLCLICK) { handle_enter_key(); return 1; } else { /* prevent moving the cursor twice from a single key press */ if (k->state == KEY_RELEASE) return 1; } new_file = CLAMP(new_file, 0, flist.num_files - 1); if (new_file != current_file) { if (fake_slot != KEYJAZZ_NOINST && fake_slot_changed) { will_move_to = new_file; dialog_create(DIALOG_YES_NO, "Discard Changes?", do_discard_changes_and_move, NULL, 0, NULL); return 1; /* support saving? XXX */ /*"Save Sample?" OK Cancel*/ /*"Discard Changes?" OK Cancel*/ } fake_slot = KEYJAZZ_NOINST; fake_slot_changed = 0; search_pos = -1; current_file = new_file; file_list_reposition(); status.flags |= NEED_UPDATE; } return 1; } static void load_sample_handle_key(struct key_event * k) { int n, v; if (k->state == KEY_PRESS && k->sym == SCHISM_KEYSYM_ESCAPE && NO_MODIFIER(k->mod)) { set_page(PAGE_SAMPLE_LIST); return; } if (!NO_MODIFIER(k->mod)) return; if (k->midi_note > -1) { n = k->midi_note; if (k->midi_volume > -1) { v = k->midi_volume / 2; } else { v = KEYJAZZ_DEFAULTVOL; } } else if (k->is_repeat) { return; } else { n = kbd_get_note(k); v = KEYJAZZ_DEFAULTVOL; if (n <= 0 || n > 120) return; } handle_preload(); if (fake_slot != KEYJAZZ_NOINST) { if (k->state == KEY_PRESS) song_keydown(KEYJAZZ_INST_FAKE, KEYJAZZ_NOINST, n, v, KEYJAZZ_CHAN_CURRENT); else song_keyup(KEYJAZZ_INST_FAKE, KEYJAZZ_NOINST, n); } } /* --------------------------------------------------------------------------------------------------------- */ static void handle_preload(void) { dmoz_file_t *file; if (fake_slot == KEYJAZZ_NOINST && current_file >= 0 && current_file < flist.num_files) { file = flist.files[current_file]; if (file && (file->type & TYPE_SAMPLE_MASK)) { fake_slot_changed = 0; fake_slot = song_preload_sample(file); // either 0 or KEYJAZZ_NOTINST } } } static void handle_rename_op(void) { handle_preload(); } static void handle_load_copy_uint(uint32_t s, uint32_t *d) { if (s != *d) { *d = s; fake_slot_changed = 1; } } static void handle_load_copy(song_sample_t *s) { handle_load_copy_uint(widgets_loadsample[2].d.numentry.value, &s->c5speed); handle_load_copy_uint(widgets_loadsample[4].d.numentry.value, &s->loop_start); handle_load_copy_uint(widgets_loadsample[5].d.numentry.value, &s->loop_end); handle_load_copy_uint(widgets_loadsample[7].d.numentry.value, &s->sustain_start); handle_load_copy_uint(widgets_loadsample[8].d.numentry.value, &s->sustain_end); handle_load_copy_uint(widgets_loadsample[9].d.thumbbar.value, &s->volume); if ((unsigned int)widgets_loadsample[9].d.thumbbar.value == (s->volume>>2)) { s->volume = (widgets_loadsample[9].d.thumbbar.value << 2); fake_slot_changed=1; } handle_load_copy_uint(widgets_loadsample[10].d.thumbbar.value, &s->global_volume); handle_load_copy_uint(widgets_loadsample[11].d.thumbbar.value, &s->vib_rate); handle_load_copy_uint(widgets_loadsample[12].d.thumbbar.value, &s->vib_depth); handle_load_copy_uint(widgets_loadsample[13].d.thumbbar.value, &s->vib_speed); switch (widgets_loadsample[3].d.menutoggle.state) { case 0: if (s->flags & (CHN_LOOP|CHN_PINGPONGLOOP)) { s->flags &= ~(CHN_LOOP|CHN_PINGPONGLOOP); fake_slot_changed=1; } break; case 1: if ((s->flags & (CHN_LOOP|CHN_PINGPONGLOOP)) == CHN_LOOP) { s->flags &= ~(CHN_LOOP|CHN_PINGPONGLOOP); s->flags |= (CHN_LOOP); fake_slot_changed=1; } break; case 2: if ((s->flags & (CHN_LOOP|CHN_PINGPONGLOOP)) == CHN_PINGPONGLOOP) { s->flags &= ~(CHN_LOOP|CHN_PINGPONGLOOP); s->flags |= (CHN_PINGPONGLOOP); fake_slot_changed=1; } break; }; switch (widgets_loadsample[6].d.menutoggle.state) { case 0: if (s->flags & (CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN)) { s->flags &= ~(CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN); fake_slot_changed=1; } break; case 1: if ((s->flags & (CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN)) == CHN_SUSTAINLOOP) { s->flags &= ~(CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN); s->flags |= (CHN_SUSTAINLOOP); fake_slot_changed=1; } break; case 2: if ((s->flags & (CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN)) == CHN_PINGPONGSUSTAIN) { s->flags &= ~(CHN_SUSTAINLOOP|CHN_PINGPONGSUSTAIN); s->flags |= (CHN_PINGPONGSUSTAIN); fake_slot_changed=1; } break; }; } static void handle_load_update(void) { song_sample_t *s; handle_preload(); if (fake_slot != KEYJAZZ_NOINST) { s = song_get_sample(fake_slot); if (s) { handle_load_copy(s); song_update_playing_sample(fake_slot); } } } void load_sample_load_page(struct page *page) { vgamem_ovl_alloc(&sample_image); clear_directory(); widget_create_other(widgets_loadsample + 0, 0, file_list_handle_key, file_list_handle_text_input, file_list_draw); widgets_loadsample[0].accept_text = 1; widgets_loadsample[0].next.tab = 1; widget_create_textentry(widgets_loadsample+1, 64, 13, 13, 1,2, 9, handle_rename_op, current_filename, sizeof(current_filename)-1); sample_speed_pos = 0; widget_create_numentry(widgets_loadsample+2, 64, 14, 7, 1,3, 9, handle_load_update, 0, 9999999, &sample_speed_pos); widget_create_menutoggle(widgets_loadsample+3, 64, 15, 2, 4, 0, 9,9, handle_load_update, loop_states); sample_loop_beg = 0; widget_create_numentry(widgets_loadsample+4, 64, 16, 7, 3,5, 9, handle_load_update, 0, 9999999, &sample_loop_beg); sample_loop_end = 0; widget_create_numentry(widgets_loadsample+5, 64, 17, 7, 4,6, 9, handle_load_update, 0, 9999999, &sample_loop_end); widget_create_menutoggle(widgets_loadsample+6, 64, 18, 5, 7, 0, 9,9, handle_load_update, loop_states); sample_susloop_beg = 0; widget_create_numentry(widgets_loadsample+7, 64, 19, 7, 6,8, 9, handle_load_update, 0, 9999999, &sample_susloop_beg); sample_susloop_end = 0; widget_create_numentry(widgets_loadsample+8, 64, 20, 7, 7,9, 9, handle_load_update, 0, 9999999, &sample_susloop_end); widget_create_thumbbar(widgets_loadsample+9, 63, 33, 9, 8, 10, 0, handle_load_update, 0,64); widget_create_thumbbar(widgets_loadsample+10, 63, 34, 9, 9, 11, 0, handle_load_update, 0,64); widget_create_thumbbar(widgets_loadsample+11, 63, 37, 9, 10, 12, 0, handle_load_update, 0,64); widget_create_thumbbar(widgets_loadsample+12, 63, 38, 9, 11, 13, 0, handle_load_update, 0,32); widget_create_thumbbar(widgets_loadsample+13, 63, 39, 9, 12, 13, 0, handle_load_update, 0,255); page->title = "Load Sample"; page->draw_const = load_sample_draw_const; page->set_page = load_sample_set_page; page->handle_key = load_sample_handle_key; page->total_widgets = 14; page->widgets = widgets_loadsample; page->help_index = HELP_GLOBAL; } void library_sample_load_page(struct page *page) { /* this shares all the widgets from load_sample */ page->title = "Sample Library (Ctrl-F3)"; page->draw_const = load_sample_draw_const; page->set_page = library_sample_set_page; page->handle_key = load_sample_handle_key; page->total_widgets = 14; page->widgets = widgets_loadsample; page->help_index = HELP_GLOBAL; } schismtracker-20250313/schism/page_log.c000066400000000000000000000125751476471630300200510ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* It's lo-og, lo-og, it's big, it's heavy, it's wood! * It's lo-og, lo-og, it's better than bad, it's good! */ #include "headers.h" #include "it.h" #include "page.h" #include "widget.h" #include "vgamem.h" #include "keyboard.h" struct log_line { int color; const char *text; int bios_font; /* Set this flag if the text should be free'd when it is scrolled offscreen. DON'T set it if the text is going to be modified after it is added to the log (e.g. for displaying status information for module loaders like IT); in that case, change the text pointer to some constant value such as "". Also don't try changing must_free after adding a line to the log, since there's a chance that the line scrolled offscreen, and it'd never get free'd. (Also, ignore this comment since there's currently no interface for manipulating individual lines in the log after adding them.) */ int must_free; }; /* --------------------------------------------------------------------- */ static struct widget widgets_log[1]; #define NUM_LINES 1000 static struct log_line lines[NUM_LINES]; static int top_line = 0; static int last_line = -1; /* --------------------------------------------------------------------- */ static void log_draw_const(void) { draw_box(1, 12, 78, 48, BOX_THICK | BOX_INNER | BOX_INSET); draw_fill_chars(2, 13, 77, 47, DEFAULT_FG, 0); } static int log_handle_key(struct key_event * k) { switch (k->sym) { case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 1; top_line--; break; case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return 1; top_line -= 15; break; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 1; top_line++; break; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return 1; top_line += 15; break; case SCHISM_KEYSYM_HOME: if (k->state == KEY_RELEASE) return 1; top_line = 0; break; case SCHISM_KEYSYM_END: if (k->state == KEY_RELEASE) return 1; top_line = last_line; break; default: if (k->state == KEY_PRESS) { if (k->mouse == MOUSE_SCROLL_UP) { top_line -= MOUSE_SCROLL_LINES; break; } else if (k->mouse == MOUSE_SCROLL_DOWN) { top_line += MOUSE_SCROLL_LINES; break; } } return 0; }; top_line = CLAMP(top_line, 0, (last_line-32)); if (top_line < 0) top_line = 0; status.flags |= NEED_UPDATE; return 1; } static void log_redraw(void) { int n, i; i = top_line; for (n = 0; n <= last_line && n < 33; n++, i++) { if (!lines[i].text) continue; if (lines[i].bios_font) { draw_text_bios_len(lines[i].text, 74, 3, 14 + n, lines[i].color, 0); } else { draw_text_len(lines[i].text, 74, 3, 14 + n, lines[i].color, 0); } } } /* --------------------------------------------------------------------- */ void log_load_page(struct page *page) { page->title = "Message Log Viewer (Ctrl-F11)"; page->draw_const = log_draw_const; page->total_widgets = 1; page->widgets = widgets_log; page->help_index = HELP_COPYRIGHT; /* I guess */ widget_create_other(widgets_log + 0, 0, log_handle_key, NULL, log_redraw); } /* --------------------------------------------------------------------- */ void log_append2(int bios_font, int color, int must_free, const char *text) { if (last_line < NUM_LINES - 1) { last_line++; } else { if (lines[0].must_free) free((void *) lines[0].text); memmove(lines, lines + 1, last_line * sizeof(struct log_line)); } lines[last_line].text = text; lines[last_line].color = color; lines[last_line].must_free = must_free; lines[last_line].bios_font = bios_font; top_line = CLAMP(last_line - 32, 0, NUM_LINES-32); if (status.current_page == PAGE_LOG) status.flags |= NEED_UPDATE; } void log_append(int color, int must_free, const char *text) { log_append2(0, color, must_free, text); } void log_nl(void) { log_append(DEFAULT_FG,0,""); } void log_appendf(int color, const char *format, ...) { char *ptr; va_list ap; va_start(ap, format); if (vasprintf(&ptr, format, ap) == -1) { perror("asprintf"); exit(255); } va_end(ap); if (!ptr) { perror("asprintf"); exit(255); } log_append(color, 1, ptr); } void log_underline(int chars) { char buf[75]; chars = CLAMP(chars, 0, (int) sizeof(buf) - 1); buf[chars--] = '\0'; do buf[chars] = 0x81; while (chars--); log_appendf(2, "%s", buf); } void log_perror(const char *prefix) { char *e = strerror(errno); perror(prefix); log_appendf(4, "%s: %s", prefix, e); } schismtracker-20250313/schism/page_message.c000066400000000000000000000523171476471630300207120ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* --->> WARNING <<--- * * This is an excellent example of how NOT to write a text editor. * IMHO, the best way to add a song message is by writing it in some * other program and attaching it to the song with something like * ZaStaR's ITTXT utility (hmm, maybe I should rewrite that, too ^_^) so * I'm not *really* concerned about the fact that this code completely * sucks. Just remember, this ain't Xcode. */ #include "headers.h" #include "song.h" #include "keyboard.h" #include "clippy.h" #include "fakemem.h" #include "widget.h" #include "dialog.h" #include "vgamem.h" #include "str.h" #include #include /* --------------------------------------------------------------------- */ static struct widget widgets_message[1]; static int top_line = 0; static int cursor_line = 0; static int cursor_char = 0; /* this is the absolute cursor position from top of message. * (should be updated whenever cursor_line/cursor_char change) */ static int cursor_pos = 0; static int edit_mode = 0; /* nonzero => message should use the alternate font */ static int message_extfont = 1; /* This is a bit weird... Impulse Tracker usually wraps at 74, but if * the line doesn't have any spaces in it (like in a solid line of * dashes or something) it gets wrapped at 75. I'm using 75 for * everything because it's always nice to have a bit extra space :) */ #define LINE_WRAP 75 /* --------------------------------------------------------------------- */ static int message_handle_key_editmode(struct key_event * k); static int message_handle_text_input_editmode(const char *text); static int message_handle_key_viewmode(struct key_event * k); /* --------------------------------------------------------------------- */ /* returns the number of characters on the nth line of text, setting ptr * to the first character on the line. if it there are fewer than n * lines, ptr is set to the \0 at the end of the string, and the * function returns -1. note: if *ptr == text, weird things will * probably happen, so don't do that. */ static int get_nth_line(char *text, int n, char **ptr) { char *tmp; assert(text != NULL); *ptr = text; while (n > 0) { n--; *ptr = strpbrk(*ptr, "\xd\xa"); if (!(*ptr)) { *ptr = text + strlen(text); return -1; } if ((*ptr)[0] == 13 && (*ptr)[1] == 10) *ptr += 2; else (*ptr)++; } tmp = strpbrk(*ptr, "\xd\xa"); return (tmp ? (unsigned) (tmp - *ptr) : strlen(*ptr)); } static void set_absolute_position(char *text, int pos, int *line, int *ch) { int len; char *ptr; *line = *ch = 0; ptr = NULL; while (pos > 0) { len = get_nth_line(text, *line, &ptr); if (len < 0) { /* end of file */ (*line) = (*line) - 1; if (*line < 0) *line = 0; len = get_nth_line(text, *line, &ptr); *ch = (len < 0) ? 0 : len; pos = 0; } else if (len >= pos) { *ch = pos; pos = 0; } else { pos -= (len + 1); /* EOC */ (*line) = (*line) + 1; } } } static int get_absolute_position(char *text, int line, int character) { int len; char *ptr; ptr = NULL; len = get_nth_line(text, line, &ptr); if (len < 0) { return 0; } /* hrm... what if cursor_char > len? */ return (ptr - text) + character; } /* --------------------------------------------------------------------- */ static void message_reposition(void) { if (cursor_line < top_line) { top_line = cursor_line; } else if (cursor_line > top_line + 34) { top_line = cursor_line - 34; } } /* --------------------------------------------------------------------- */ /* returns 1 if a character was actually added */ static int message_add_char(int newchar, int position) { int len = strlen(current_song->message); if (len == MAX_MESSAGE) { dialog_create(DIALOG_OK, " Song message too long! ", NULL, NULL, 0, NULL); return 0; } if (position < 0 || position > len) { log_appendf(4, "message_add_char: position=%d, len=%d - shouldn't happen!", position, len); return 0; } memmove(current_song->message + position + 1, current_song->message + position, len - position); current_song->message[len + 1] = 0; current_song->message[position] = (unsigned char)newchar; return 1; } /* this returns the new length of the line */ static int message_wrap_line(char *bol_ptr) { char *eol_ptr; char *last_space = NULL; char *tmp = bol_ptr; if (!bol_ptr) /* shouldn't happen, but... */ return 0; eol_ptr = strpbrk(bol_ptr, "\xd\xa"); if (!eol_ptr) eol_ptr = bol_ptr + strlen(bol_ptr); for (;;) { tmp = strpbrk((tmp + 1), " \t"); if (tmp == NULL || tmp > eol_ptr || tmp - bol_ptr > LINE_WRAP) break; last_space = tmp; } if (last_space) { *last_space = 13; return last_space - bol_ptr; } else { /* what, no spaces to cut at? chop it mercilessly. */ if (message_add_char(13, bol_ptr + LINE_WRAP - current_song->message) == 0) /* ack, the message is too long to wrap the line! * gonna have to resort to something ugly. */ bol_ptr[LINE_WRAP] = 13; return LINE_WRAP; } } /* --------------------------------------------------------------------- */ static void text(char *line, int len, int n) { unsigned char ch; int fg = (message_extfont ? 12 : 6); int i; for (i = 0; line[i] && i < len; i++) { ch = line[i]; if (ch == ' ') { draw_char(' ', 2+i, 13+n, 3,0); } else { (message_extfont ? draw_char_bios : draw_char)(ch, 2+i, 13+n, fg, 0); } } } static void message_draw(void) { char *line, *prevline = current_song->message; int len = get_nth_line(current_song->message, top_line, &line); int n, cp, clipl, clipr; int skipc, cutc; draw_fill_chars(2, 13, 77, 47, DEFAULT_FG, 0); if (clippy_owner(CLIPPY_SELECT) == widgets_message) { clipl = widgets_message[0].clip_start; clipr = widgets_message[0].clip_end; if (clipl > clipr) { cp = clipl; clipl = clipr; clipr = cp; } } else { clipl = clipr = -1; } for (n = 0; n < 35; n++) { if (len < 0) { break; } else if (len > 0) { /* FIXME | shouldn't need this check here, * FIXME | because the line should already be * FIXME | short enough to fit */ if (len > LINE_WRAP) len = LINE_WRAP; text(line, len, n); if (clipl > -1) { cp = line - current_song->message; skipc = clipl - cp; cutc = clipr - clipl; if (skipc < 0) { cutc += skipc; /* ... -skipc */ skipc = 0; } if (cutc < 0) cutc = 0; if (cutc > (len-skipc)) cutc = (len-skipc); if (cutc > 0 && skipc < len) { if (message_extfont) draw_text_bios_len(line+skipc, cutc, 2+skipc, 13 + n, 6, 8); else draw_text_len(line+skipc, cutc, 2+skipc, 13 + n, 6, 8); } } } if (edit_mode) { draw_char(20, 2 + len, 13 + n, 1, 0); } prevline = line; len = get_nth_line(prevline, 1, &line); } if (edit_mode && len < 0) { /* end of the message */ len = get_nth_line(prevline, 0, &line); /* FIXME: see above */ if (len > LINE_WRAP) len = LINE_WRAP; draw_char(20, 2 + len, 13 + n - 1, 2, 0); } if (edit_mode) { /* draw the cursor */ len = get_nth_line(current_song->message, cursor_line, &line); /* FIXME: ... ugh */ if (len > LINE_WRAP) len = LINE_WRAP; if (cursor_char > LINE_WRAP + 1) cursor_char = LINE_WRAP + 1; if (cursor_char >= len) { (message_extfont ? draw_char_bios : draw_char) (20, 2 + cursor_char, 13 + (cursor_line - top_line), 0, 3); } else { (message_extfont ? draw_char_bios : draw_char) (line[cursor_char], 2 + cursor_char, 13 + (cursor_line - top_line), 8, 3); } } } /* --------------------------------------------------------------------- */ static inline void message_set_editmode(void) { edit_mode = 1; widgets_message[0].accept_text = 1; top_line = cursor_line = cursor_char = cursor_pos = 0; widgets_message[0].d.other.handle_key = message_handle_key_editmode; widgets_message[0].d.other.handle_text_input = message_handle_text_input_editmode; status.flags |= NEED_UPDATE; } static inline void message_set_viewmode(void) { edit_mode = 0; widgets_message[0].accept_text = 0; widgets_message[0].d.other.handle_key = message_handle_key_viewmode; widgets_message[0].d.other.handle_text_input = NULL; status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ static void message_insert_char(int c) { char *ptr; int n; if (!edit_mode) return; memused_songchanged(); if (c == '\t') { /* Find the number of characters until the next tab stop. * (This is new behaviour; Impulse Tracker just inserts * eight characters regardless of the cursor position.) */ n = 8 - cursor_char % 8; if (cursor_char + n > LINE_WRAP) { message_insert_char('\r'); } else { do { if (!message_add_char(' ', cursor_pos)) break; cursor_char++; cursor_pos++; n--; } while (n); } } else if (c < 32 && c != '\r') { return; } else { if (!message_add_char(c, cursor_pos)) return; cursor_pos++; if (c == '\r') { cursor_char = 0; cursor_line++; } else { cursor_char++; } } if (get_nth_line(current_song->message, cursor_line, &ptr) >= LINE_WRAP) { message_wrap_line(ptr); } if (cursor_char >= LINE_WRAP) { cursor_char = get_nth_line(current_song->message, ++cursor_line, &ptr); cursor_pos = get_absolute_position(current_song->message, cursor_line, cursor_char); } message_reposition(); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void message_delete_char(void) { int len = strlen(current_song->message); char *ptr; if (cursor_pos == 0) return; memmove(current_song->message + cursor_pos - 1, current_song->message + cursor_pos, len - cursor_pos + 1); current_song->message[MAX_MESSAGE] = 0; cursor_pos--; if (cursor_char == 0) { cursor_line--; cursor_char = get_nth_line(current_song->message, cursor_line, &ptr); } else { cursor_char--; } message_reposition(); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void message_delete_next_char(void) { int len = strlen(current_song->message); if (cursor_pos == len) return; memmove(current_song->message + cursor_pos, current_song->message + cursor_pos + 1, len - cursor_pos); current_song->message[MAX_MESSAGE] = 0; status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void message_delete_line(void) { int len; int movelen; char *ptr; len = get_nth_line(current_song->message, cursor_line, &ptr); if (len < 0) return; if (ptr[len] == 13 && ptr[len + 1] == 10) len++; movelen = (current_song->message + strlen(current_song->message) - ptr); if (movelen == 0) return; memmove(ptr, ptr + len + 1, movelen); len = get_nth_line(current_song->message, cursor_line, &ptr); if (cursor_char > len) { cursor_char = len; cursor_pos = get_absolute_position(current_song->message, cursor_line, cursor_char); } message_reposition(); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void message_clear(SCHISM_UNUSED void *data) { current_song->message[0] = 0; memused_songchanged(); message_set_viewmode(); status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ static void prompt_message_clear(void) { dialog_create(DIALOG_OK_CANCEL, "Clear song message?", message_clear, NULL, 1, NULL); } /* --------------------------------------------------------------------- */ static int message_handle_key_viewmode(struct key_event * k) { if (k->state == KEY_PRESS) { if (k->mouse == MOUSE_SCROLL_UP) { top_line -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { top_line += MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_CLICK) { message_set_editmode(); return message_handle_key_editmode(k); } } switch (k->sym) { case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 0; top_line--; break; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 0; top_line++; break; case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return 0; top_line -= 35; break; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; top_line += 35; break; case SCHISM_KEYSYM_HOME: if (k->state == KEY_RELEASE) return 0; top_line = 0; break; case SCHISM_KEYSYM_END: if (k->state == KEY_RELEASE) return 0; top_line = str_get_num_lines(current_song->message) - 34; break; case SCHISM_KEYSYM_t: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { message_extfont = !message_extfont; break; } return 1; case SCHISM_KEYSYM_RETURN: if (k->state == KEY_PRESS) return 0; message_set_editmode(); return 1; default: return 0; } if (top_line < 0) top_line = 0; status.flags |= NEED_UPDATE; return 1; } static void _delete_selection(void) { int len = strlen(current_song->message); int eat; cursor_pos = widgets_message[0].clip_start; if (cursor_pos > widgets_message[0].clip_end) { cursor_pos = widgets_message[0].clip_end; eat = widgets_message[0].clip_start - cursor_pos; } else { eat = widgets_message[0].clip_end - cursor_pos; } clippy_select(NULL, NULL, 0); if (cursor_pos == len) return; memmove(current_song->message + cursor_pos, current_song->message + cursor_pos + eat + 1, ((len - cursor_pos) - eat)+1); current_song->message[MAX_MESSAGE] = 0; set_absolute_position(current_song->message, cursor_pos, &cursor_line, &cursor_char); message_reposition(); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static int message_handle_text_input_editmode(const char *text) { if (clippy_owner(CLIPPY_SELECT) == widgets_message) _delete_selection(); for (; *text; text++) message_insert_char(*text); return 1; } static int message_handle_key_editmode(struct key_event * k) { int line_len, num_lines = -1; int new_cursor_line = cursor_line; int new_cursor_char = cursor_char; char *ptr; int doing_drag = 0; int clipl, clipr, cp; if (k->mouse == MOUSE_SCROLL_UP) { if (k->state == KEY_RELEASE) return 0; new_cursor_line -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { if (k->state == KEY_RELEASE) return 0; new_cursor_line += MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_CLICK && k->mouse_button == 2) { if (k->state == KEY_RELEASE) status.flags |= CLIPPY_PASTE_SELECTION; return 1; } else if (k->mouse == MOUSE_CLICK) { if (k->x >= 2 && k->x <= 77 && k->y >= 13 && k->y <= 47) { new_cursor_line = (k->y - 13) + top_line; new_cursor_char = (k->x - 2); if (k->sx != k->x || k->sy != k->y) { /* yay drag operation */ cp = get_absolute_position(current_song->message, (k->sy-13)+top_line, (k->sx-2)); widgets_message[0].clip_start = cp; doing_drag = 1; } } } line_len = get_nth_line(current_song->message, cursor_line, &ptr); switch (k->sym) { case SCHISM_KEYSYM_UP: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_line--; break; case SCHISM_KEYSYM_DOWN: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_line++; break; case SCHISM_KEYSYM_LEFT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_char--; break; case SCHISM_KEYSYM_RIGHT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_char++; break; case SCHISM_KEYSYM_PAGEUP: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_line -= 35; break; case SCHISM_KEYSYM_PAGEDOWN: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_line += 35; break; case SCHISM_KEYSYM_HOME: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_CTRL) new_cursor_line = 0; else new_cursor_char = 0; break; case SCHISM_KEYSYM_END: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_CTRL) { num_lines = str_get_num_lines(current_song->message); new_cursor_line = num_lines; } else { new_cursor_char = line_len; } break; case SCHISM_KEYSYM_ESCAPE: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; message_set_viewmode(); memused_songchanged(); return 1; case SCHISM_KEYSYM_BACKSPACE: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; if (k->sym && clippy_owner(CLIPPY_SELECT) == widgets_message) { _delete_selection(); } else { message_delete_char(); } return 1; case SCHISM_KEYSYM_DELETE: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; if (clippy_owner(CLIPPY_SELECT) == widgets_message) _delete_selection(); else message_delete_next_char(); return 1; case SCHISM_KEYSYM_RETURN: if (NO_MODIFIER(k->mod)) { if (k->state == KEY_RELEASE) return 1; if (clippy_owner(CLIPPY_SELECT) == widgets_message) _delete_selection(); message_insert_char('\r'); return 1; } return 0; default: /* keybinds... */ if (k->mod & SCHISM_KEYMOD_CTRL) { if (k->state == KEY_RELEASE) return 1; if (k->sym == SCHISM_KEYSYM_t) { message_extfont = !message_extfont; break; } else if (k->sym == SCHISM_KEYSYM_y) { clippy_select(NULL, NULL, 0); message_delete_line(); break; } } else if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_RELEASE) return 1; if (k->sym == SCHISM_KEYSYM_c) { prompt_message_clear(); return 1; } } else if (k->mouse == MOUSE_NONE) { if (k->text) return message_handle_text_input_editmode(k->text); return 0; } if (k->mouse != MOUSE_CLICK) return 0; if (k->state == KEY_RELEASE) return 1; if (!doing_drag) { clippy_select(NULL, NULL, 0); } break; } if (new_cursor_line != cursor_line) { if (num_lines == -1) num_lines = str_get_num_lines(current_song->message); if (new_cursor_line < 0) new_cursor_line = 0; else if (new_cursor_line > num_lines) new_cursor_line = num_lines; /* make sure the cursor doesn't go past the new eol */ line_len = get_nth_line(current_song->message, new_cursor_line, &ptr); if (new_cursor_char > line_len) new_cursor_char = line_len; cursor_char = new_cursor_char; cursor_line = new_cursor_line; } else if (new_cursor_char != cursor_char) { /* we say "else" here ESPECIALLY because the mouse can only come in the top section - not because it's some clever optimization */ if (new_cursor_char < 0) { if (cursor_line == 0) { new_cursor_char = cursor_char; } else { cursor_line--; new_cursor_char = get_nth_line(current_song->message, cursor_line, &ptr); } } else if (new_cursor_char > get_nth_line(current_song->message, cursor_line, &ptr)) { if (cursor_line == str_get_num_lines(current_song->message)) { new_cursor_char = cursor_char; } else { cursor_line++; new_cursor_char = 0; } } cursor_char = new_cursor_char; } message_reposition(); cursor_pos = get_absolute_position(current_song->message, cursor_line, cursor_char); if (doing_drag) { widgets_message[0].clip_end = cursor_pos; clipl = widgets_message[0].clip_start; clipr = widgets_message[0].clip_end; if (clipl > clipr) { cp = clipl; clipl = clipr; clipr = cp; } clippy_select(widgets_message, (current_song->message+clipl), clipr-clipl); } status.flags |= NEED_UPDATE; return 1; } /* --------------------------------------------------------------------- */ static void message_draw_const(void) { draw_box(1, 12, 78, 48, BOX_THICK | BOX_INNER | BOX_INSET); } static void song_changed_cb(void) { char *line, *prevline; int len; edit_mode = 0; widgets_message[0].accept_text = 0; widgets_message[0].d.other.handle_key = message_handle_key_viewmode; top_line = 0; len = get_nth_line(current_song->message, 0, &line); while (len >= 0) { if (len > LINE_WRAP) message_wrap_line(line); prevline = line; len = get_nth_line(prevline, 1, &line); } if (status.current_page == PAGE_MESSAGE) status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ void message_load_page(struct page *page) { page->title = "Message Editor (Shift-F9)"; page->draw_const = message_draw_const; page->song_changed_cb = song_changed_cb; page->total_widgets = 1; page->widgets = widgets_message; page->help_index = HELP_MESSAGE_EDITOR; widget_create_other(widgets_message + 0, 0, message_handle_key_viewmode, NULL, message_draw); widgets_message[0].accept_text = edit_mode; } void message_reset_selection(void) { widgets_message[0].clip_start = widgets_message[0].clip_end = 0; } schismtracker-20250313/schism/page_midi.c000066400000000000000000000246371476471630300202140ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "config.h" #include "keyboard.h" #include "page.h" #include "midi.h" #include "widget.h" #include "vgamem.h" #include "song.h" /* --------------------------------------------------------------------- */ static int top_midi_port = 0; static int current_port = 0; static struct widget widgets_midi[17]; static time_t last_midi_poll = 0; /* --------------------------------------------------------------------- */ static void midi_output_config(void) { set_page(PAGE_MIDI_OUTPUT); } static void update_ip_ports(void) { if (widgets_midi[12].d.thumbbar.value > 0 && (status.flags & NO_NETWORK)) { status_text_flash("Networking is disabled"); widgets_midi[12].d.thumbbar.value = 0; } else { ip_midi_setports(widgets_midi[12].d.thumbbar.value); } last_midi_poll = 0; status.flags |= NEED_UPDATE; } static void update_midi_values(void) { midi_flags = 0 | (widgets_midi[1].d.toggle.state ? MIDI_TICK_QUANTIZE : 0) | (widgets_midi[2].d.toggle.state ? MIDI_BASE_PROGRAM1 : 0) | (widgets_midi[3].d.toggle.state ? MIDI_RECORD_NOTEOFF : 0) | (widgets_midi[4].d.toggle.state ? MIDI_RECORD_VELOCITY : 0) | (widgets_midi[5].d.toggle.state ? MIDI_RECORD_AFTERTOUCH : 0) | (widgets_midi[6].d.toggle.state ? MIDI_CUT_NOTE_OFF : 0) | (widgets_midi[9].d.toggle.state ? MIDI_PITCHBEND : 0) ; if (widgets_midi[11].d.toggle.state) current_song->flags |= SONG_EMBEDMIDICFG; else current_song->flags &= ~SONG_EMBEDMIDICFG; midi_amplification = widgets_midi[7].d.thumbbar.value; midi_c5note = widgets_midi[8].d.thumbbar.value; midi_pitch_depth = widgets_midi[10].d.thumbbar.value; } static void get_midi_config(void) { widgets_midi[1].d.toggle.state = !!(midi_flags & MIDI_TICK_QUANTIZE); widgets_midi[2].d.toggle.state = !!(midi_flags & MIDI_BASE_PROGRAM1); widgets_midi[3].d.toggle.state = !!(midi_flags & MIDI_RECORD_NOTEOFF); widgets_midi[4].d.toggle.state = !!(midi_flags & MIDI_RECORD_VELOCITY); widgets_midi[5].d.toggle.state = !!(midi_flags & MIDI_RECORD_AFTERTOUCH); widgets_midi[6].d.toggle.state = !!(midi_flags & MIDI_CUT_NOTE_OFF); widgets_midi[9].d.toggle.state = !!(midi_flags & MIDI_PITCHBEND); widgets_midi[11].d.toggle.state = !!(current_song->flags & SONG_EMBEDMIDICFG); widgets_midi[7].d.thumbbar.value = midi_amplification; widgets_midi[8].d.thumbbar.value = midi_c5note; widgets_midi[10].d.thumbbar.value = midi_pitch_depth; widgets_midi[12].d.thumbbar.value = ip_midi_getports(); } static void toggle_port(void) { struct midi_port *p; p = midi_engine_port(current_port, NULL); if (p) { status.flags |= NEED_UPDATE; if (p->disable && !midi_port_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; }; midi_port_enable(p); } } static int midi_page_handle_key(struct key_event * k) { int new_port = current_port; int pos; if (k->mouse == MOUSE_SCROLL_UP) { new_port -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { new_port += MOUSE_SCROLL_LINES; } else if (k->mouse) { if (k->x >= 3 && k->x <= 11 && k->y >= 15 && k->y <= 27) { if (k->mouse == MOUSE_DBLCLICK) { if (k->state == KEY_PRESS) return 0; toggle_port(); return 1; } new_port = top_midi_port + (k->y - 15); } else { return 0; } } switch (k->sym) { case SCHISM_KEYSYM_SPACE: if (k->state == KEY_PRESS) return 1; toggle_port(); return 1; case SCHISM_KEYSYM_PAGEUP: new_port -= 13; break; case SCHISM_KEYSYM_PAGEDOWN: new_port += 13; break; case SCHISM_KEYSYM_HOME: new_port = 0; break; case SCHISM_KEYSYM_END: new_port = midi_engine_port_count() - 1; break; case SCHISM_KEYSYM_UP: new_port--; break; case SCHISM_KEYSYM_DOWN: new_port++; break; case SCHISM_KEYSYM_TAB: if (k->state == KEY_RELEASE) return 1; widget_change_focus_to(1); status.flags |= NEED_UPDATE; return 1; default: if (!k->mouse) return 0; break; }; if (k->state == KEY_RELEASE) return 0; if (new_port != current_port) { int sz = midi_engine_port_count() - 1; new_port = CLAMP(new_port, 0, sz); current_port = new_port; if (current_port < top_midi_port) top_midi_port = current_port; pos = current_port - top_midi_port; if (pos > 12) top_midi_port = current_port - 12; if (top_midi_port < 0) top_midi_port = 0; status.flags |= NEED_UPDATE; } return 1; } static void midi_page_redraw(void) { draw_text( "Tick quantize", 6, 30, 0, 2); draw_text( "Base Program 1", 5, 31, 0, 2); draw_text( "Record Note-Off", 4, 32, 0, 2); draw_text( "Record Velocity", 4, 33, 0, 2); draw_text( "Record Aftertouch", 2, 34, 0, 2); draw_text( "Cut note off", 7, 35, 0, 2); draw_fill_chars(23, 30, 24, 35, DEFAULT_FG, 0); draw_box(19,29,25,36, BOX_THIN|BOX_INNER|BOX_INSET); draw_box(52,29,73,32, BOX_THIN|BOX_INNER|BOX_INSET); draw_fill_chars(56, 34, 72, 34, DEFAULT_FG, 0); draw_box(52,33,73,36, BOX_THIN|BOX_INNER|BOX_INSET); draw_fill_chars(56, 38, 72, 38, DEFAULT_FG, 0); draw_box(52,37,73,39, BOX_THIN|BOX_INNER|BOX_INSET); draw_text( "Amplification", 39, 30, 0, 2); draw_text( "C-5 Note-value", 38, 31, 0, 2); draw_text("Output MIDI pitch", 35, 34, 0, 2); draw_text("Pitch wheel depth", 35, 35, 0, 2); draw_text( "Embed MIDI data", 37, 38, 0, 2); draw_text( "IP MIDI ports", 39, 41, 0, 2); draw_box(52,40,73,42, BOX_THIN|BOX_INNER|BOX_INSET); } static void midi_page_draw_portlist(void) { /* XXX this can become outdated with the midi code; it can * and will overflow */ struct midi_port *p; const char *name, *state; char buffer[64]; int i, n, ct, fg, bg; unsigned long j; time_t now = time(NULL); draw_fill_chars(3, 15, 76, 28, DEFAULT_FG, 0); draw_text("MIDI ports:", 2, 13, 0, 2); draw_box(2,14,77,28, BOX_THIN|BOX_INNER|BOX_INSET); if (difftime(now, last_midi_poll) > 10.0) { last_midi_poll = now; midi_engine_poll_ports(); } ct = midi_engine_port_count(); /* make sure this stuff doesn't overflow! */ if (ct > 13 && top_midi_port + 13 >= ct) top_midi_port = ct - 13; current_port = MIN(current_port, ct - 1); for (i = 0; i < 13; i++) { draw_char(168, 12, i + 15, 2, 0); if (top_midi_port + i >= ct) continue; /* err */ p = midi_engine_port(top_midi_port + i, &name); if (current_port == top_midi_port + i && ACTIVE_WIDGET.type == WIDGET_OTHER) { fg = 0; bg = 3; } else { fg = 5; bg = 0; } draw_text_utf8_len(name, 64, 13, 15+i, 5, 0); if (status.flags & MIDI_EVENT_CHANGED && (now - status.last_midi_tick) < 3000 && ((!status.last_midi_port && p->io & MIDI_OUTPUT) || p == status.last_midi_port)) { for (j = n = 0; j < 21 && j < status.last_midi_len; j++) { /* 21 is approx 64/3 */ sprintf(buffer + n, "%02X ", status.last_midi_event[j]); n += 3; } draw_text(buffer, 77 - strlen(buffer), 15+i, status.last_midi_port ? 4 : 10, 0); } switch (p->io) { case 0: state = "Disabled "; break; case MIDI_INPUT: state = " Input "; break; case MIDI_OUTPUT: state = " Output "; break; case MIDI_INPUT | MIDI_OUTPUT: state = " Duplex "; break; default: state = " Enabled?"; break; } draw_text(state, 3, 15 + i, fg, bg); } } /* --------------------------------------------------------------------- */ void midi_load_page(struct page *page) { page->title = "MIDI Screen (Shift-F1)"; page->draw_const = midi_page_redraw; page->song_changed_cb = NULL; page->predraw_hook = NULL; page->playback_update = NULL; page->handle_key = NULL; page->set_page = get_midi_config; page->total_widgets = 15; page->widgets = widgets_midi; page->help_index = HELP_GLOBAL; widget_create_other(widgets_midi + 0, 0, midi_page_handle_key, NULL, midi_page_draw_portlist); widgets_midi[0].x = 2; widgets_midi[0].y = 14; widgets_midi[0].width = 75; widgets_midi[0].height = 15; widget_create_toggle(widgets_midi + 1, 20, 30, 0, 2, 7, 7, 7, update_midi_values); widget_create_toggle(widgets_midi + 2, 20, 31, 1, 3, 8, 8, 8, update_midi_values); widget_create_toggle(widgets_midi + 3, 20, 32, 2, 4, 8, 8, 8, update_midi_values); widget_create_toggle(widgets_midi + 4, 20, 33, 3, 5, 9, 9, 9, update_midi_values); widget_create_toggle(widgets_midi + 5, 20, 34, 4, 6, 9, 9, 9, update_midi_values); widget_create_toggle(widgets_midi + 6, 20, 35, 5, 13, 10, 10, 10, update_midi_values); widget_create_thumbbar(widgets_midi + 7, 53, 30, 20, 0, 8, 1, update_midi_values, 0, 200); widget_create_thumbbar(widgets_midi + 8, 53, 31, 20, 7, 9, 2, update_midi_values, 0, 127); widget_create_toggle(widgets_midi + 9, 53, 34, 8, 10, 5, 5, 5, update_midi_values); widget_create_thumbbar(widgets_midi + 10, 53, 35, 20, 9, 11, 6, update_midi_values, 0, 48); widget_create_toggle(widgets_midi + 11, 53, 38, 10, 12, 13, 13, 13, update_midi_values); widget_create_thumbbar(widgets_midi + 12, 53, 41, 20, 11, 12, 13, update_ip_ports, 0, 128); widget_create_button(widgets_midi + 13, 2, 41, 27, 6, 14, 12, 12, 12, midi_output_config, "MIDI Output Configuration", 2); widget_create_button(widgets_midi + 14, 2, 44, 27, 13, 14, 12, 12, 12, cfg_midipage_save, "Save Output Configuration", 2); } schismtracker-20250313/schism/page_midiout.c000066400000000000000000000113121476471630300207260ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "page.h" #include "keyboard.h" #include "midi.h" #include "song.h" #include "widget.h" #include "vgamem.h" /* --------------------------------------------------------------------- */ static struct widget widgets_midiout[33]; static int zxx_top = 0; static midi_config_t editcfg; /* --------------------------------------------------------------------- */ static void midiout_draw_const(void) { char buf[4] = "SFx"; unsigned int i; draw_text( "MIDI Start", 6, 13, 0, 2); draw_text( "MIDI Stop", 7, 14, 0, 2); draw_text( "MIDI Tick", 7, 15, 0, 2); draw_text( "Note On", 9, 16, 0, 2); draw_text( "Note Off", 8, 17, 0, 2); draw_text( "Change Volume", 3, 18, 0, 2); draw_text( "Change Pan", 6, 19, 0, 2); draw_text( "Bank Select", 5, 20, 0, 2); draw_text("Program Change", 2, 21, 0, 2); draw_text( "Macro SF0", 5, 24, 0, 2); draw_text( "Setup SF1", 5, 25, 0, 2); for (i = 2; i < 16; i++) { buf[2] = hexdigits[i]; draw_text(buf, 13, i + 24, 0, 2); } draw_box(16, 12, 60, 22, BOX_THIN|BOX_INNER|BOX_INSET); draw_box(16, 23, 60, 40, BOX_THIN|BOX_INNER|BOX_INSET); draw_box(16, 41, 60, 49, BOX_THIN|BOX_INNER|BOX_INSET); for (i = 0; i < 7; i++) { sprintf(buf, "Z%02X", i + zxx_top + 0x80); draw_text(buf, 13, i + 42, 0, 2); } } static void copy_out(void) { song_lock_audio(); memcpy(¤t_song->midi_config, &editcfg, sizeof(midi_config_t)); song_unlock_audio(); } static void copy_in(void) { song_lock_audio(); memcpy(&editcfg, ¤t_song->midi_config, sizeof(midi_config_t)); song_unlock_audio(); } static void zxx_setpos(int pos) { int i; /* 128 items, scrolled on 7 lines */ pos = CLAMP(pos, 0, 128 - 7); if (zxx_top == pos) return; zxx_top = pos; for (i = 0; i < 7; i++) widgets_midiout[25 + i].d.textentry.text = editcfg.zxx[zxx_top + i]; status.flags |= NEED_UPDATE; } static int pre_handle_key(struct key_event *k) { if (*selected_widget == 25 && k->sym == SCHISM_KEYSYM_UP) { /* scroll up */ if (k->state == KEY_RELEASE) return 1; if (zxx_top == 0) return 0; /* let the normal key handler catch it and change focus */ zxx_setpos(zxx_top - 1); return 1; } if (*selected_widget == 31 && k->sym == SCHISM_KEYSYM_DOWN) { /* scroll down */ if (k->state == KEY_RELEASE) return 1; zxx_setpos(zxx_top + 1); return 1; } if ((*selected_widget) >= 25) { switch (k->sym) { case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return 1; zxx_setpos(zxx_top - 7); return 1; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return 1; zxx_setpos(zxx_top + 7); return 1; default: break; }; } return 0; } static void midiout_set_page(void) { copy_in(); } void midiout_load_page(struct page *page) { int i; page->title = "MIDI Output Configuration"; page->draw_const = midiout_draw_const; page->set_page = midiout_set_page; page->pre_handle_key = pre_handle_key; page->total_widgets = 32; page->widgets = widgets_midiout; page->help_index = HELP_MIDI_OUTPUT; char *editcfg_top[] = { editcfg.start, editcfg.stop, editcfg.tick, editcfg.note_on, editcfg.note_off, editcfg.set_volume, editcfg.set_panning, editcfg.set_bank, editcfg.set_program, }; for (i = 0; i < 9; i++) { widget_create_textentry(widgets_midiout + i, 17, 13 + i, 43, (i == 0 ? 0 : (i - 1)), i + 1, 9, copy_out, editcfg_top[i], 31); } for (i = 0; i < 16; i++) { widget_create_textentry(widgets_midiout + 9 + i, 17, 24 + i, 43, 9 + i - 1, 9 + i + 1, 25, copy_out, editcfg.sfx[i], 31); } for (i = 0; i < 7; i++) { widget_create_textentry(widgets_midiout + 25 + i, 17, 42 + i, 43, 25 + i - 1, 25 + ((i == 6) ? 6 : (i + 1)), 0, copy_out, editcfg.zxx[i], 31); } } schismtracker-20250313/schism/page_orderpan.c000066400000000000000000000632571476471630300211050ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "song.h" #include "page.h" #include "widget.h" #include "vgamem.h" #include "keyboard.h" #include "str.h" /* --------------------------------------------------------------------- */ static struct widget widgets_orderpan[65], widgets_ordervol[65]; static int top_order = 0; static int current_order = 0; static int orderlist_cursor_pos = 0; static unsigned char saved_orderlist[256]; static int _did_save_orderlist = 0; /* --------------------------------------------------------------------- */ static void orderlist_reposition(void) { if (current_order < top_order) { top_order = current_order; } else if (current_order > top_order + 31) { top_order = current_order - 31; } } /* --------------------------------------------------------------------- */ void update_current_order(void) { char buf[4]; draw_text(str_from_num(3, current_order, buf), 12, 5, 5, 0); draw_text(str_from_num(3, csf_last_order(current_song), buf), 16, 5, 5, 0); } void set_current_order(int order) { current_order = CLAMP(order, 0, 255); orderlist_reposition(); status.flags |= NEED_UPDATE; } int get_current_order(void) { return current_order; } /* --------------------------------------------------------------------- */ /* called from the pattern editor on ctrl-plus/minus */ void prev_order_pattern(void) { int new_order = current_order; int last_pattern = current_song->orderlist[new_order]; do { if (--new_order < 0) { new_order = 0; break; } } while (!(status.flags & CLASSIC_MODE) && last_pattern == current_song->orderlist[new_order] && current_song->orderlist[new_order] == ORDER_SKIP); if (current_song->orderlist[new_order] < 200) { current_order = new_order; orderlist_reposition(); set_current_pattern(current_song->orderlist[new_order]); } } void next_order_pattern(void) { int new_order = current_order; int last_pattern = current_song->orderlist[new_order]; do { if (++new_order > 255) { new_order = 255; break; } } while (!(status.flags & CLASSIC_MODE) && last_pattern == current_song->orderlist[new_order] && current_song->orderlist[new_order] == ORDER_SKIP); if (current_song->orderlist[new_order] < 200) { current_order = new_order; orderlist_reposition(); set_current_pattern(current_song->orderlist[new_order]); } } static void orderlist_cheater(void) { song_note_t *data; int cp, i, best, first; int rows; if (current_song->orderlist[current_order] != ORDER_SKIP && current_song->orderlist[current_order] != ORDER_LAST) { return; } cp = get_current_pattern(); best = first = -1; for (i = 0; i < 199; i++) { if (csf_pattern_is_empty(current_song, i)) { if (first == -1) first = i; if (best == -1) best = i; } else { best = -1; } } if (best == -1) best = first; if (best == -1) return; status_text_flash("Pattern %d copied to pattern %d, order %d", cp, best, current_order); data = song_pattern_allocate_copy(cp, &rows); song_pattern_resize(best, rows); song_pattern_install(best, data, rows); current_song->orderlist[current_order] = best; current_order++; status.flags |= SONG_NEEDS_SAVE; status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ static void get_pattern_string(unsigned char pattern, char *buf) { switch (pattern) { case ORDER_SKIP: buf[0] = buf[1] = buf[2] = '+'; buf[3] = 0; break; case ORDER_LAST: buf[0] = buf[1] = buf[2] = '-'; buf[3] = 0; break; default: str_from_num(3, pattern, buf); break; } } static void orderlist_draw(void) { char buf[4]; int pos, n; int playing_order = (song_get_mode() == MODE_PLAYING ? song_get_current_order() : -1); /* draw the list */ for (pos = 0, n = top_order; pos < 32; pos++, n++) { draw_text(str_from_num(3, n, buf), 2, 15 + pos, (n == playing_order ? 3 : 0), 2); get_pattern_string(current_song->orderlist[n], buf); draw_text(buf, 6, 15 + pos, 2, 0); } /* draw the cursor */ if (ACTIVE_PAGE.selected_widget == 0) { get_pattern_string(current_song->orderlist[current_order], buf); pos = current_order - top_order; draw_char(buf[orderlist_cursor_pos], orderlist_cursor_pos + 6, 15 + pos, 0, 3); } status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ static void orderlist_insert_pos(void) { memmove(current_song->orderlist + current_order + 1, current_song->orderlist + current_order, 255 - current_order); current_song->orderlist[current_order] = ORDER_LAST; status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void orderlist_save(void) { memcpy(saved_orderlist, current_song->orderlist, 255); _did_save_orderlist = 1; } static void orderlist_restore(void) { unsigned char oldlist[256]; if (!_did_save_orderlist) return; memcpy(oldlist, current_song->orderlist, 255); memcpy(current_song->orderlist, saved_orderlist, 255); memcpy(saved_orderlist, oldlist, 255); } static void orderlist_delete_pos(void) { memmove(current_song->orderlist + current_order, current_song->orderlist + current_order + 1, 255 - current_order); current_song->orderlist[255] = ORDER_LAST; status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void orderlist_insert_next(void) { int next_pattern; if (current_order == 0 || current_song->orderlist[current_order - 1] > 199) return; next_pattern = current_song->orderlist[current_order - 1] + 1; if (next_pattern > 199) next_pattern = 199; current_song->orderlist[current_order] = next_pattern; if (current_order < 255) current_order++; orderlist_reposition(); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void orderlist_add_unused_patterns(void) { /* n0 = the first free order * n = orderlist position * p = pattern iterator * np = number of patterns */ int n0, n, p, np = csf_get_num_patterns(current_song); uint8_t used[200] = {0}; /* could be a bitset... */ for (n = 0; n < 255; n++) if (current_song->orderlist[n] < 200) used[current_song->orderlist[n]] = 1; /* after the loop, n == 255 */ while (n >= 0 && current_song->orderlist[n] == 0xff) n--; if (n == -1) n = 0; else n += 2; n0 = n; for (p = 0; p < np; p++) { if (used[p] || csf_pattern_is_empty(current_song, p)) continue; if (n > 255) { /* status_text_flash("No more room in orderlist"); */ break; } current_song->orderlist[n++] = p; } if (n == n0) { status_text_flash("No unused patterns"); } else { set_current_order(n - 1); set_current_order(n0); if (n - n0 == 1) { status_text_flash("1 unused pattern found"); } else { status_text_flash("%d unused patterns found", n - n0); } } status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void orderlist_reorder(void) { /* err, I hope this is going to be done correctly... */ song_note_t *np[256] = {0}; int nplen[256]; unsigned char mapol[256]; int i, j; song_lock_audio(); orderlist_add_unused_patterns(); memset(mapol, ORDER_LAST, sizeof(mapol)); for (i = j = 0; i < 255; i++) { if (current_song->orderlist[i] == ORDER_LAST || current_song->orderlist[i] == ORDER_SKIP) { continue; } if (mapol[ current_song->orderlist[i] ] == ORDER_LAST) { np[j] = song_pattern_allocate_copy(current_song->orderlist[i], &nplen[j]); mapol[ current_song->orderlist[i] ] = j; j++; } /* replace orderlist entry */ current_song->orderlist[i] = mapol[ current_song->orderlist[i] ]; } for (i = 0; i < 200; i++) { if (!np[i]) { song_pattern_install(i, NULL, 64); } else { song_pattern_install(i, np[i], nplen[i]); } } status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; song_stop_unlocked(0); song_unlock_audio(); } static int orderlist_handle_char(char sym) { int c; int cur_pattern; //unsigned char *list; song_note_t *tmp; int n[3] = { 0 }; switch (sym) { case '+': status.flags |= SONG_NEEDS_SAVE; current_song->orderlist[current_order] = ORDER_SKIP; orderlist_cursor_pos = 2; break; case '.': case '-': status.flags |= SONG_NEEDS_SAVE; current_song->orderlist[current_order] = ORDER_LAST; orderlist_cursor_pos = 2; break; default: if (sym >= '0' && sym <= '9') c = sym - '0'; else return 0; status.flags |= SONG_NEEDS_SAVE; cur_pattern = current_song->orderlist[current_order]; if (cur_pattern < 200) { n[0] = cur_pattern / 100; n[1] = cur_pattern / 10 % 10; n[2] = cur_pattern % 10; } n[orderlist_cursor_pos] = c; cur_pattern = n[0] * 100 + n[1] * 10 + n[2]; cur_pattern = CLAMP(cur_pattern, 0, 199); song_get_pattern(cur_pattern, &tmp); /* make sure it exists */ current_song->orderlist[current_order] = cur_pattern; break; }; if (orderlist_cursor_pos == 2) { if (current_order < 255) current_order++; orderlist_cursor_pos = 0; orderlist_reposition(); } else { orderlist_cursor_pos++; } status.flags |= NEED_UPDATE; return 1; } static int orderlist_handle_text_input_on_list(const char *text) { int success = 0; for (; *text; text++) if (!orderlist_handle_char(*text)) success = 1; return success; } static int orderlist_handle_key_on_list(struct key_event * k) { int prev_order = current_order; int new_order = prev_order; int new_cursor_pos = orderlist_cursor_pos; int n, p; if (k->mouse != MOUSE_NONE) { if (k->x >= 6 && k->x <= 8 && k->y >= 15 && k->y <= 46) { /* FIXME adjust top_order, not the cursor */ if (k->mouse == MOUSE_SCROLL_UP) { new_order -= MOUSE_SCROLL_LINES; } else if (k->mouse == MOUSE_SCROLL_DOWN) { new_order += MOUSE_SCROLL_LINES; } else { if (k->state == KEY_PRESS) return 0; new_order = (k->y - 15) + top_order; set_current_order(new_order); new_order = current_order; if (current_song->orderlist[current_order] != ORDER_LAST && current_song->orderlist[current_order] != ORDER_SKIP) { new_cursor_pos = (k->x - 6); } } } } switch (k->sym) { case SCHISM_KEYSYM_BACKSPACE: if (status.flags & CLASSIC_MODE) return 0; if (!(k->mod & SCHISM_KEYMOD_ALT)) return 0; if (k->state == KEY_PRESS) return 1; if (!_did_save_orderlist) return 1; status_text_flash("Restored orderlist"); orderlist_restore(); return 1; case SCHISM_KEYSYM_RETURN: case SCHISM_KEYSYM_KP_ENTER: if (status.flags & CLASSIC_MODE) return 0; if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_PRESS) return 1; status_text_flash("Saved orderlist"); orderlist_save(); return 1; } // else fall through case SCHISM_KEYSYM_g: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_PRESS) return 1; n = current_song->orderlist[new_order]; while (n >= 200 && new_order > 0) n = current_song->orderlist[--new_order]; if (n < 200) { set_current_pattern(n); set_page(PAGE_PATTERN_EDITOR); } return 1; case SCHISM_KEYSYM_TAB: if (k->mod & SCHISM_KEYMOD_SHIFT) { if (k->state == KEY_RELEASE) return 1; widget_change_focus_to(33); } else { if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; widget_change_focus_to(1); } return 1; case SCHISM_KEYSYM_LEFT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_pos--; break; case SCHISM_KEYSYM_RIGHT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_cursor_pos++; break; case SCHISM_KEYSYM_HOME: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_order = 0; break; case SCHISM_KEYSYM_END: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_order = csf_last_order(current_song); if (current_song->orderlist[new_order] != ORDER_LAST) new_order++; break; case SCHISM_KEYSYM_UP: if (k->mod & SCHISM_KEYMOD_CTRL) { if (status.flags & CLASSIC_MODE) return 0; if (k->state == KEY_RELEASE) return 1; sample_set(sample_get_current()-1); status.flags |= NEED_UPDATE; return 1; } if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_order--; break; case SCHISM_KEYSYM_DOWN: if (k->mod & SCHISM_KEYMOD_CTRL) { if (status.flags & CLASSIC_MODE) return 0; if (k->state == KEY_RELEASE) return 1; sample_set(sample_get_current()+1); status.flags |= NEED_UPDATE; return 1; } if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_order++; break; case SCHISM_KEYSYM_PAGEUP: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_order -= 16; break; case SCHISM_KEYSYM_PAGEDOWN: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; new_order += 16; break; case SCHISM_KEYSYM_INSERT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; orderlist_insert_pos(); return 1; case SCHISM_KEYSYM_DELETE: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; orderlist_delete_pos(); return 1; case SCHISM_KEYSYM_F7: if (!(k->mod & SCHISM_KEYMOD_CTRL)) return 0; /* fall through */ case SCHISM_KEYSYM_SPACE: if (k->state == KEY_RELEASE) return 1; song_set_next_order(current_order); status_text_flash("Playing order %d next", current_order); return 1; case SCHISM_KEYSYM_F6: if (k->mod & SCHISM_KEYMOD_SHIFT) { if (k->state == KEY_RELEASE) return 1; song_start_at_order(current_order, 0); return 1; } return 0; case SCHISM_KEYSYM_n: if (k->mod & SCHISM_KEYMOD_SHIFT) { if (k->state == KEY_PRESS) return 1; orderlist_cheater(); return 1; } if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; orderlist_insert_next(); return 1; case SCHISM_KEYSYM_c: if (!NO_MODIFIER(k->mod)) return 0; if (status.flags & CLASSIC_MODE) return 0; if (k->state == KEY_PRESS) return 1; p = get_current_pattern(); for (n = current_order+1; n < 256; n++) { if (current_song->orderlist[n] == p) { new_order = n; break; } } if (n == 256) { for (n = 0; n < current_order; n++) { if (current_song->orderlist[n] == p) { new_order = n; break; } } if (n == current_order) { status_text_flash("Pattern %d not on Order List", p); return 1; } } break; case SCHISM_KEYSYM_r: if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_PRESS) return 1; orderlist_reorder(); return 1; } return 0; case SCHISM_KEYSYM_u: if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_RELEASE) return 1; orderlist_add_unused_patterns(); return 1; } return 0; case SCHISM_KEYSYM_b: if (k->mod & SCHISM_KEYMOD_SHIFT) return 0; /* fall through */ case SCHISM_KEYSYM_o: if (!(k->mod & SCHISM_KEYMOD_CTRL)) return 0; if (k->state == KEY_RELEASE) return 1; song_pattern_to_sample(current_song->orderlist[current_order], !!(k->mod & SCHISM_KEYMOD_SHIFT), !!(k->sym == SCHISM_KEYSYM_b)); return 1; case SCHISM_KEYSYM_LESS: case SCHISM_KEYSYM_SEMICOLON: case SCHISM_KEYSYM_COLON: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; sample_set(sample_get_current()-1); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_GREATER: case SCHISM_KEYSYM_QUOTE: case SCHISM_KEYSYM_QUOTEDBL: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; sample_set(sample_get_current()+1); status.flags |= NEED_UPDATE; return 1; default: if (k->mouse == MOUSE_NONE) { if (!(k->mod & (SCHISM_KEYMOD_CTRL | SCHISM_KEYMOD_ALT)) && k->text) return orderlist_handle_text_input_on_list(k->text); return 0; } } if (new_cursor_pos < 0) new_cursor_pos = 2; else if (new_cursor_pos > 2) new_cursor_pos = 0; if (new_order != prev_order) { set_current_order(new_order); } else if (new_cursor_pos != orderlist_cursor_pos) { orderlist_cursor_pos = new_cursor_pos; } else { return 0; } status.flags |= NEED_UPDATE; return 1; } /* --------------------------------------------------------------------- */ static void order_pan_vol_draw_const(void) { draw_box(5, 14, 9, 47, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(30, 14, 40, 47, BOX_THICK | BOX_INNER | BOX_FLAT_LIGHT); draw_box(64, 14, 74, 47, BOX_THICK | BOX_INNER | BOX_FLAT_LIGHT); draw_char(146, 30, 14, 3, 2); draw_char(145, 40, 14, 3, 2); draw_char(146, 64, 14, 3, 2); draw_char(145, 74, 14, 3, 2); } static void orderpan_draw_const(void) { order_pan_vol_draw_const(); draw_text("L M R", 31, 14, 0, 3); draw_text("L M R", 65, 14, 0, 3); } static void ordervol_draw_const(void) { int n; char buf[16]; int fg; strcpy(buf, "Channel 42"); order_pan_vol_draw_const(); draw_text(" Volumes ", 31, 14, 0, 3); draw_text(" Volumes ", 65, 14, 0, 3); for (n = 1; n <= 32; n++) { fg = 0; if (!(status.flags & CLASSIC_MODE)) { if (ACTIVE_PAGE.selected_widget == n) { fg = 3; } } str_from_num(2, n, buf + 8); draw_text(buf, 20, 14 + n, fg, 2); fg = 0; if (!(status.flags & CLASSIC_MODE)) { if (ACTIVE_PAGE.selected_widget == n+32) { fg = 3; } } str_from_num(2, n + 32, buf + 8); draw_text(buf, 54, 14 + n, fg, 2); } } /* --------------------------------------------------------------------- */ static void order_pan_vol_playback_update(void) { static int last_order = -1; int order = ((song_get_mode() == MODE_STOPPED) ? -1 : song_get_current_order()); if (order != last_order) { last_order = order; status.flags |= NEED_UPDATE; } } /* --------------------------------------------------------------------- */ static void orderpan_update_values_in_song(void) { song_channel_t *chn; int n; status.flags |= SONG_NEEDS_SAVE; for (n = 0; n < 64; n++) { chn = song_get_channel(n); /* yet another modplug hack here! */ chn->panning = widgets_orderpan[n + 1].d.panbar.value * 4; if (widgets_orderpan[n + 1].d.panbar.surround) chn->flags |= CHN_SURROUND; else chn->flags &= ~CHN_SURROUND; song_set_channel_mute(n, widgets_orderpan[n + 1].d.panbar.muted); } } static void ordervol_update_values_in_song(void) { int n; status.flags |= SONG_NEEDS_SAVE; for (n = 0; n < 64; n++) song_get_channel(n)->volume = widgets_ordervol[n + 1].d.thumbbar.value; } /* called when a channel is muted/unmuted by means other than the panning * page (alt-f10 in the pattern editor, space on the info page...) */ void orderpan_recheck_muted_channels(void) { int n; for (n = 0; n < 64; n++) widgets_orderpan[n + 1].d.panbar.muted = !!(song_get_channel(n)->flags & CHN_MUTE); if (status.current_page == PAGE_ORDERLIST_PANNING) status.flags |= NEED_UPDATE; } static void order_pan_vol_song_changed_cb(void) { int n; song_channel_t *chn; for (n = 0; n < 64; n++) { chn = song_get_channel(n); widgets_orderpan[n + 1].d.panbar.value = chn->panning / 4; widgets_orderpan[n + 1].d.panbar.surround = !!(chn->flags & CHN_SURROUND); widgets_orderpan[n + 1].d.panbar.muted = !!(chn->flags & CHN_MUTE); widgets_ordervol[n + 1].d.thumbbar.value = chn->volume; } } /* --------------------------------------------------------------------- */ static void order_pan_vol_handle_key(struct key_event * k) { int n = ACTIVE_PAGE.selected_widget; if (k->state == KEY_RELEASE) return; if (!NO_MODIFIER(k->mod)) return; switch (k->sym) { case SCHISM_KEYSYM_PAGEDOWN: n += 8; break; case SCHISM_KEYSYM_PAGEUP: n -= 8; break; default: return; } n = CLAMP(n, 1, 64); if (ACTIVE_PAGE.selected_widget != n) widget_change_focus_to(n); } static int order_pre_key(struct key_event *k) { // hack to sync the active widget between pan/vol pages if (!(status.flags & CLASSIC_MODE)) { pages[PAGE_ORDERLIST_PANNING].selected_widget = pages[PAGE_ORDERLIST_VOLUMES].selected_widget = ACTIVE_PAGE.selected_widget; } if (k->sym == SCHISM_KEYSYM_F7) { if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; play_song_from_mark_orderpan(); return 1; } return 0; } static void order_pan_set_page(void) { orderpan_recheck_muted_channels(); } /* --------------------------------------------------------------------- */ void orderpan_load_page(struct page *page) { int n; page->title = "Order List and Panning (F11)"; page->draw_const = orderpan_draw_const; /* this does the work for both pages */ page->song_changed_cb = order_pan_vol_song_changed_cb; page->playback_update = order_pan_vol_playback_update; page->pre_handle_key = order_pre_key; page->handle_key = order_pan_vol_handle_key; page->set_page = order_pan_set_page; page->total_widgets = 65; page->widgets = widgets_orderpan; page->help_index = HELP_ORDERLIST_PANNING; /* 0 = order list */ widget_create_other(widgets_orderpan + 0, 1, orderlist_handle_key_on_list, orderlist_handle_text_input_on_list, orderlist_draw); widgets_orderpan[0].accept_text = 1; widgets_orderpan[0].x = 6; widgets_orderpan[0].y = 15; widgets_orderpan[0].width = 3; widgets_orderpan[0].height = 32; /* 1-64 = panbars */ widget_create_panbar(widgets_orderpan + 1, 20, 15, 1, 2, 33, orderpan_update_values_in_song, 1); for (n = 2; n <= 32; n++) { widget_create_panbar(widgets_orderpan + n, 20, 14 + n, n - 1, n + 1, n + 32, orderpan_update_values_in_song, n); widget_create_panbar(widgets_orderpan + n + 31, 54, 13 + n, n + 30, n + 32, 0, orderpan_update_values_in_song, n + 31); } widget_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 */ widget_create_other(widgets_ordervol + 0, 1, orderlist_handle_key_on_list, orderlist_handle_text_input_on_list, orderlist_draw); widgets_ordervol[0].accept_text = 1; widgets_ordervol[0].x = 6; widgets_ordervol[0].y = 15; widgets_ordervol[0].width = 3; widgets_ordervol[0].height = 32; /* 1-64 = thumbbars */ widget_create_thumbbar(widgets_ordervol + 1, 31, 15, 9, 1, 2, 33, ordervol_update_values_in_song, 0, 64); for (n = 2; n <= 32; n++) { widget_create_thumbbar(widgets_ordervol + n, 31, 14 + n, 9, n - 1, n + 1, n + 32, ordervol_update_values_in_song, 0, 64); widget_create_thumbbar(widgets_ordervol + n + 31, 65, 13 + n, 9, n + 30, n + 32, 0, ordervol_update_values_in_song, 0, 64); } widget_create_thumbbar(widgets_ordervol + 64, 65, 46, 9, 63, 64, 0, ordervol_update_values_in_song, 0, 64); } /* --------------------------------------------------------------------- */ /* this function is a lost little puppy */ #define MAX_CHANNELS 64 // blah void song_set_pan_scheme(int scheme) { int n, nc; //int half; int active = 0; song_channel_t *chn = song_get_channel(0); //mphack alert, all pan values multiplied by 4 switch (scheme) { case PANS_STEREO: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) chn[n].panning = (n & 1) ? 256 : 0; } break; case PANS_AMIGA: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) chn[n].panning = ((n + 1) & 2) ? 256 : 0; } break; case PANS_LEFT: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) chn[n].panning = 0; } break; case PANS_RIGHT: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) chn[n].panning = 256; } break; case PANS_MONO: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) chn[n].panning = 128; } break; case PANS_SLASH: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) active++; } for (n = 0, nc = 0; nc < active; n++) { if (!(chn[n].flags & CHN_MUTE)) { chn[n].panning = 256 - (256 * nc / (active - 1)); nc++; } } break; case PANS_BACKSLASH: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) active++; } for (n = 0, nc = 0; nc < active; n++) { if (!(chn[n].flags & CHN_MUTE)) { chn[n].panning = (256 * nc / (active - 1)); nc++; } } break; #if 0 case PANS_CROSS: for (n = 0; n < MAX_CHANNELS; n++) { if (!(chn[n].flags & CHN_MUTE)) active++; } half = active / 2; for (n = 0, nc = 0; nc < half; n++) { if (!(chn[n].flags & CHN_MUTE)) { if (nc & 1) { // right bias - go from 64 to 32 chn[n].panning = (64 - (32 * nc / half)) * 4; } else { // left bias - go from 0 to 32 chn[n].panning = (32 * nc / half) * 4; } nc++; } } for (; nc < active; n++) { if (!(chn[n].flags & CHN_MUTE)) { if (nc & 1) { chn[n].panning = (64 - (32 * (active - nc) / half)) * 4; } else { chn[n].panning = (32 * (active - nc) / half) * 4; } nc++; } } break; #endif default: printf("oh i am confused\n"); } // get the values on the page to correspond to the song... order_pan_vol_song_changed_cb(); } schismtracker-20250313/schism/page_palette.c000066400000000000000000000264501476471630300207230ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "config.h" #include "page.h" #include "clippy.h" #include "palettes.h" #include "widget.h" #include "vgamem.h" #include "keyboard.h" #define NUM_PALETTES 15 /* --------------------------------------------------------------------- */ static struct widget widgets_palette[51]; static int selected_palette; /* --------------------------------------------------------------------- */ /* 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", 56, 24, 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), DEFAULT_FG, n); draw_fill_chars(30, 14 + (5 * n), 34, 16 + (5 * n), DEFAULT_FG, 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(54, 25, 77, 41, BOX_THICK | BOX_INNER | BOX_INSET); draw_fill_chars(57, 14, 61, 16, DEFAULT_FG, 14); draw_fill_chars(57, 19, 61, 21, DEFAULT_FG, 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_copy_palette_to_clipboard(int which) { char palette_text[49]; palette_to_string(which, palette_text); palette_text[48] = 0; clippy_select(widgets_palette + 49, palette_text, 49); clippy_yank(); } static void palette_copy_current_to_clipboard(void) { palette_copy_palette_to_clipboard(current_palette_index); } static void palette_paste_from_clipboard(void) { clippy_paste(CLIPPY_BUFFER); } static int palette_paste_callback(SCHISM_UNUSED int cb, const void *data) { if (!data) return 0; int result = set_palette_from_string((const char*)data); if (!result) { status_text_flash("Bad character or wrong length"); return 0; } selected_palette = 0; palette_load_preset(selected_palette); palette_apply(); update_thumbbars(); status.flags |= NEED_UPDATE; status_text_flash("Palette pasted"); return 1; } /* --------------------------------------------------------------------- */ static void palette_list_draw(void) { int n, focused = (ACTIVE_PAGE.selected_widget == 48); int fg, bg; draw_fill_chars(55, 26, 76, 40, DEFAULT_FG, 0); for (n = 0; n < NUM_PALETTES; n++) { fg = 6; bg = 0; if (focused && n == selected_palette) { fg = 0; bg = 3; } else if (n == selected_palette) { bg = 14; } if(n == current_palette_index) draw_text_len("*", 1, 55, 26 + n, fg, bg); else draw_text_len(" ", 1, 55, 26 + n, fg, bg); draw_text_len(palettes[n].name, 21, 56, 26 + n, fg, bg); } } static int palette_list_handle_key_on_list(struct key_event * k) { int new_palette = selected_palette; int load_selected_palette = 0; const int focus_offsets[] = { 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, 11, 12 }; if(k->mouse == MOUSE_DBLCLICK) { if (k->state == KEY_PRESS) return 0; if (k->x < 55 || k->y < 26 || k->y > 40 || k->x > 76) return 0; new_palette = (k->y - 26); load_selected_palette = 1; } else if (k->mouse == MOUSE_CLICK) { if (k->state == KEY_PRESS) return 0; if (k->x < 55 || k->y < 26 || k->y > 40 || k->x > 76) return 0; new_palette = (k->y - 26); if(new_palette == selected_palette) load_selected_palette = 1; } else { if (k->state == KEY_RELEASE) return 0; if (k->mouse == MOUSE_SCROLL_UP) new_palette -= MOUSE_SCROLL_LINES; else if (k->mouse == MOUSE_SCROLL_DOWN) new_palette += MOUSE_SCROLL_LINES; } switch (k->sym) { case SCHISM_KEYSYM_UP: if (!NO_MODIFIER(k->mod)) return 0; if (--new_palette < 0) { widget_change_focus_to(47); return 1; } break; case SCHISM_KEYSYM_DOWN: if (!NO_MODIFIER(k->mod)) return 0; // new_palette++; if (++new_palette >= NUM_PALETTES) { widget_change_focus_to(49); return 1; } break; case SCHISM_KEYSYM_HOME: if (!NO_MODIFIER(k->mod)) return 0; new_palette = 0; break; case SCHISM_KEYSYM_PAGEUP: if (!NO_MODIFIER(k->mod)) return 0; if (new_palette == 0) { widget_change_focus_to(45); return 1; } new_palette -= 16; break; case SCHISM_KEYSYM_END: if (!NO_MODIFIER(k->mod)) return 0; new_palette = NUM_PALETTES - 1; break; case SCHISM_KEYSYM_PAGEDOWN: if (!NO_MODIFIER(k->mod)) return 0; new_palette += 16; break; case SCHISM_KEYSYM_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 SCHISM_KEYSYM_RIGHT: case SCHISM_KEYSYM_TAB: if (k->mod & SCHISM_KEYMOD_SHIFT) { widget_change_focus_to(focus_offsets[selected_palette+1] + 29); return 1; } if (!NO_MODIFIER(k->mod)) return 0; widget_change_focus_to(focus_offsets[selected_palette+1] + 8); return 1; case SCHISM_KEYSYM_LEFT: if (!NO_MODIFIER(k->mod)) return 0; widget_change_focus_to(focus_offsets[selected_palette+1] + 29); return 1; case SCHISM_KEYSYM_c: /* pasting is handled by the page */ if (k->mod & SCHISM_KEYMOD_CTRL) { palette_copy_palette_to_clipboard(selected_palette); return 1; } return 0; default: if (k->mouse == MOUSE_NONE) return 0; } new_palette = CLAMP(new_palette, 0, NUM_PALETTES - 1); if (new_palette != selected_palette || load_selected_palette) { selected_palette = new_palette; if(load_selected_palette) { palette_load_preset(selected_palette); palette_apply(); update_thumbbars(); } status.flags |= NEED_UPDATE; } return 1; } /* --------------------------------------------------------------------- */ static void palette_list_handle_key(struct key_event * k) { int n = *selected_widget; if (k->state == KEY_RELEASE) return; switch (k->sym) { case SCHISM_KEYSYM_PAGEUP: if (!NO_MODIFIER(k->mod)) n -= 3; break; case SCHISM_KEYSYM_PAGEDOWN: if (!NO_MODIFIER(k->mod)) n += 3; break; case SCHISM_KEYSYM_c: if (k->mod & SCHISM_KEYMOD_CTRL) { palette_copy_current_to_clipboard(); return; } break; case SCHISM_KEYSYM_v: if (k->mod & SCHISM_KEYMOD_CTRL) { palette_paste_from_clipboard(); return; } 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) 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 = 0; memcpy(palettes[0].colors, current_palette, sizeof(current_palette)); palette_apply(); cfg_save(); status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ void palette_load_page(struct page *page) { page->title = "Palette Configuration (Ctrl-F12)"; page->draw_const = palette_draw_const; page->handle_key = palette_list_handle_key; page->total_widgets = 51; page->selected_widget = 48; page->widgets = widgets_palette; page->help_index = HELP_PALETTES; page->clipboard_paste = palette_paste_callback; selected_palette = current_palette_index; for (int n = 0; n < 16; n++) { int tabs[3]; for (int x = 0; x < 3; x++) tabs[x] = 3 * n + (21 + x); 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; } widget_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); widget_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); widget_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(); widget_create_other(widgets_palette + 48, 0, palette_list_handle_key_on_list, NULL, palette_list_draw); widgets_palette[48].x = 56; widgets_palette[48].y = 26; widgets_palette[48].width = 20; widgets_palette[48].height = 15; for(int i = 6; i < 18; i++) { widgets_palette[i].next.backtab = 48; } widget_create_button(widgets_palette + 49, 55, 43, 20, 48, 50, 39, 18, 18, palette_copy_current_to_clipboard, "Copy To Clipboard", 3); widget_create_button(widgets_palette + 50, 55, 46, 20, 49, 0, 39, 18, 18, palette_paste_from_clipboard, "Paste From Clipboard", 1); widgets_palette[0].next.up = 50; widgets_palette[39].next.tab = 49; widgets_palette[40].next.tab = 49; widgets_palette[41].next.tab = 49; } schismtracker-20250313/schism/page_patedit.c000066400000000000000000004002421476471630300207120ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* The all-important pattern editor. The code here is a general mess, so * don't look at it directly or, uh, you'll go blind or something. */ #include "headers.h" #include #include "it.h" #include "config.h" #include "keyboard.h" #include "page.h" #include "song.h" #include "pattern-view.h" #include "config-parser.h" #include "midi.h" #include "osdefs.h" #include "fakemem.h" #include "dialog.h" #include "widget.h" #include "vgamem.h" #include "keyboard.h" #include "str.h" #include "mem.h" #include "clippy.h" #include "disko.h" /* --------------------------------------------------------------------------------------------------------- */ #define ROW_IS_MAJOR(r) (current_song->row_highlight_major != 0 && (r) % current_song->row_highlight_major == 0) #define ROW_IS_MINOR(r) (current_song->row_highlight_minor != 0 && (r) % current_song->row_highlight_minor == 0) #define ROW_IS_HIGHLIGHT(r) (ROW_IS_MINOR(r) || ROW_IS_MAJOR(r)) #define SONG_PLAYING (song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP)) /* this is actually used by pattern-view.c */ int show_default_volumes = 0; /* --------------------------------------------------------------------- */ /* The (way too many) static variables */ static int midi_start_record = 0; enum { TEMPLATE_OFF = 0, TEMPLATE_OVERWRITE, TEMPLATE_MIX_PATTERN_PRECEDENCE, TEMPLATE_MIX_CLIPBOARD_PRECEDENCE, TEMPLATE_NOTES_ONLY, TEMPLATE_MODE_MAX, }; static int template_mode = TEMPLATE_OFF; static const char *template_mode_names[] = { "", "Template, Overwrite", "Template, Mix - Pattern data precedence", "Template, Mix - Clipboard data precedence", "Template, Notes only", }; /* only one widget, but MAN is it complicated :) */ static struct widget widgets_pattern[1]; /* pattern display position */ static int top_display_channel = 1; /* one-based */ static int top_display_row = 0; /* zero-based */ /* these three tell where the cursor is in the pattern */ static int current_channel = 1, current_position = 0; static int current_row = 0; static int keyjazz_noteoff = 0; /* issue noteoffs when releasing note */ static int keyjazz_write_noteoff = 0; /* write noteoffs when releasing note */ static int keyjazz_repeat = 1; /* insert multiple notes on key repeat */ static int keyjazz_capslock = 0; /* keyjazz when capslock is on, not while it is down */ /* this is, of course, what the current pattern is */ static int current_pattern = 0; static int skip_value = 1; /* aka cursor step */ static int link_effect_column = 0; static int draw_divisions = 0; /* = vertical lines between channels */ static int shift_chord_channels = 0; /* incremented for each shift-note played */ static int centralise_cursor = 0; static int highlight_current_row = 0; int playback_tracing = 0; /* scroll lock */ int midi_playback_tracing = 0; static int panning_mode = 0; /* for the volume column */ int midi_bend_hit[64]; int midi_last_bend_hit[64]; /* blah; other forwards */ static void pated_save(const char *descr); static void pated_history_add2(int groupedf, const char *descr, int x, int y, int width, int height); static void pated_history_add(const char *descr, int x, int y, int width, int height); static void pated_history_add_grouped(const char *descr, int x, int y, int width, int height); static void pated_history_restore(int n); /* these should fix the playback tracing position discrepancy */ static int playing_row = -1; static int playing_pattern = -1; /* the current editing mask (what stuff is copied) */ static int edit_copy_mask = MASK_NOTE | MASK_INSTRUMENT | MASK_VOLUME; /* and the mask note. note that the instrument field actually isn't used */ static song_note_t mask_note = { 61, 0, 0, 0, 0, 0 }; /* C-5 */ /* playback mark (ctrl-f7) */ static int marked_pattern = -1, marked_row; /* volume stuff (alt-i, alt-j, ctrl-j) */ static int volume_percent = 100; static int vary_depth = 10; static int fast_volume_percent = 67; static int fast_volume_mode = 0; /* toggled with ctrl-j */ enum { COPY_INST_OFF = 0, /* no search (IT style) */ COPY_INST_UP = 1, /* search above the cursor for an instrument number */ COPY_INST_UP_THEN_DOWN = 2, /* search both ways, up to row 0 first, then down */ COPY_INST_SENTINEL = 3, /* non-value */ }; static int mask_copy_search_mode = COPY_INST_OFF; /* If nonzero, home/end will move to the first/last row in the current channel prior to moving to the first/last channel, i.e. operating in a 'z' pattern. This is closer to FT2's behavior for the keys. */ static int invert_home_end = 0; /* --------------------------------------------------------------------- */ /* undo and clipboard handling */ struct pattern_snap { song_note_t *data; int channels; int rows; /* used by undo/history only */ const char *snap_op; int snap_op_allocated; int x, y; int patternno; }; static struct pattern_snap fast_save = { NULL, 0, 0, "Fast Pattern Save", 0, 0, 0, -1 }; /* static int fast_save_validity = -1; */ static void snap_paste(struct pattern_snap *s, int x, int y, int xlate); static struct pattern_snap clipboard = { NULL, 0, 0, "Clipboard", 0, 0, 0, -1 }; static struct pattern_snap undo_history[10]; static int undo_history_top = 0; /* this function is stupid, it doesn't belong here */ void memused_get_pattern_saved(unsigned int *a, unsigned int *b) { int i; if (b) { for (i = 0; i < 10; i++) { if (undo_history[i].data) *b = (*b) + undo_history[i].rows; } } if (a) { if (clipboard.data) (*a) = (*a) + clipboard.rows; if (fast_save.data) (*a) = (*a) + fast_save.rows; } } /* --------------------------------------------------------------------- */ /* block selection handling */ static struct { int first_channel; int last_channel; int first_row; int last_row; } selection = { 0, 0, 0, 0 }; static struct { int in_progress; int first_channel; int first_row; } shift_selection = { 0, 0, 0 }; /* this is set to 1 on the first alt-d selection, * and shifted left on each successive press. */ static int block_double_size; /* if first_channel is zero, there's no selection, as the channel * numbers start with one. (same deal for last_channel, but i'm only * caring about one of them to be efficient.) */ #define SELECTION_EXISTS (selection.first_channel) /* CHECK_FOR_SELECTION(optional return value) will display an error dialog and cause the function to return if there is no block marked. (The spaces around the text are to make it line up the same as Impulse Tracker) */ #define CHECK_FOR_SELECTION(q) do {\ if (!SELECTION_EXISTS) {\ dialog_create(DIALOG_OK, " No block is marked ", NULL, NULL, 0, NULL);\ q;\ }\ } while(0) /* --------------------------------------------------------------------- */ /* this is for the multiple track views stuff. */ struct track_view { int width; draw_channel_header_func draw_channel_header; draw_note_func draw_note; draw_mask_func draw_mask; }; static const struct track_view track_views[] = { #define TRACK_VIEW(n) {n, draw_channel_header_##n, draw_note_##n, draw_mask_##n} TRACK_VIEW(13), /* 5 channels */ TRACK_VIEW(10), /* 6/7 channels */ TRACK_VIEW(7), /* 9/10 channels */ TRACK_VIEW(6), /* 10/12 channels */ TRACK_VIEW(3), /* 18/24 channels */ TRACK_VIEW(2), /* 24/36 channels */ TRACK_VIEW(1), /* 36/64 channels */ #undef TRACK_VIEW }; #define NUM_TRACK_VIEWS (int)ARRAY_SIZE(track_views) static uint8_t track_view_scheme[64]; static int channel_multi_enabled = 0; static int channel_multi[64]; static int visible_channels, visible_width; static void recalculate_visible_area(void); static void set_view_scheme(int scheme); static void pattern_editor_reposition(void); /* --------------------------------------------------------------------------------------------------------- */ /* options dialog */ static struct widget options_widgets[8]; static const int options_link_split[] = { 5, 6, -1 }; static int options_selected_widget = 0; static int options_last_octave = 0; static void options_close_cancel(SCHISM_UNUSED void *data) { kbd_set_current_octave(options_last_octave); } static void options_close(void *data) { int old_size, new_size; options_selected_widget = ((struct dialog *) data)->selected_widget; skip_value = options_widgets[1].d.thumbbar.value; current_song->row_highlight_minor = options_widgets[2].d.thumbbar.value; current_song->row_highlight_major = options_widgets[3].d.thumbbar.value; link_effect_column = !!(options_widgets[5].d.togglebutton.state); status.flags |= SONG_NEEDS_SAVE; old_size = song_get_pattern(current_pattern, NULL); new_size = options_widgets[4].d.thumbbar.value; if (old_size != new_size) { song_pattern_resize(current_pattern, new_size); current_row = MIN(current_row, new_size - 1); pattern_editor_reposition(); } } static void options_draw_const(void) { draw_text("Pattern Editor Options", 28, 19, 0, 2); draw_text("Base octave", 28, 23, 0, 2); draw_text("Cursor step", 28, 26, 0, 2); draw_text("Row hilight minor", 22, 29, 0, 2); draw_text("Row hilight major", 22, 32, 0, 2); draw_text("Number of rows in pattern", 14, 35, 0, 2); draw_text("Command/Value columns", 18, 38, 0, 2); draw_box(39, 22, 42, 24, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(39, 25, 43, 27, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(39, 28, 45, 30, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(39, 31, 57, 33, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(39, 34, 62, 36, BOX_THIN | BOX_INNER | BOX_INSET); } static void options_change_base_octave(void) { kbd_set_current_octave(options_widgets[0].d.thumbbar.value); } /* the base octave is changed directly when the thumbbar is changed. * anything else can wait until the dialog is closed. */ void pattern_editor_display_options(void) { struct dialog *dialog; if (options_widgets[0].width == 0) { /* haven't built it yet */ widget_create_thumbbar(options_widgets + 0, 40, 23, 2, 7, 1, 1, options_change_base_octave, 0, 8); widget_create_thumbbar(options_widgets + 1, 40, 26, 3, 0, 2, 2, NULL, 0, 16); widget_create_thumbbar(options_widgets + 2, 40, 29, 5, 1, 3, 3, NULL, 0, 32); widget_create_thumbbar(options_widgets + 3, 40, 32, 17, 2, 4, 4, NULL, 0, 128); /* Although patterns as small as 1 row can be edited properly (as of c759f7a0166c), I have discovered it's a bit annoying to hit 'home' here expecting to get 32 rows but end up with just one row instead. so I'll allow editing these patterns, but not really provide a way to set the size, at least until I decide how to present the option nonintrusively. */ widget_create_thumbbar(options_widgets + 4, 40, 35, 22, 3, 5, 5, NULL, 32, 200); widget_create_togglebutton(options_widgets + 5, 40, 38, 8, 4, 7, 6, 6, 6, NULL, "Link", 3, options_link_split); widget_create_togglebutton(options_widgets + 6, 52, 38, 9, 4, 7, 5, 5, 5, NULL, "Split", 3, options_link_split); widget_create_button(options_widgets + 7, 35, 41, 8, 5, 0, 7, 7, 7, dialog_yes_NULL, "Done", 3); } options_last_octave = kbd_get_current_octave(); options_widgets[0].d.thumbbar.value = options_last_octave; options_widgets[1].d.thumbbar.value = skip_value; options_widgets[2].d.thumbbar.value = current_song->row_highlight_minor; options_widgets[3].d.thumbbar.value = current_song->row_highlight_major; options_widgets[4].d.thumbbar.value = song_get_pattern(current_pattern, NULL); widget_togglebutton_set(options_widgets, link_effect_column ? 5 : 6, 0); dialog = dialog_create_custom(10, 18, 60, 26, options_widgets, 8, options_selected_widget, options_draw_const, NULL); dialog->action_yes = options_close; if (status.flags & CLASSIC_MODE) { dialog->action_cancel = options_close; } else { dialog->action_cancel = options_close_cancel; } dialog->data = dialog; } static struct widget template_error_widgets[1]; static void template_error_draw(void) { draw_text("Template Error", 33, 25, 0, 2); draw_text("No note in the top left position", 23, 27, 0, 2); draw_text("of the clipboard on which to", 25, 28, 0, 2); draw_text("base translations.", 31, 29, 0, 2); } /* --------------------------------------------------------------------------------------------------------- */ /* pattern length dialog */ static struct widget length_edit_widgets[4]; static void length_edit_draw_const(void) { draw_box(33,23,56,25, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(33,26,60,29, BOX_THIN | BOX_INNER | BOX_INSET); draw_text("Set Pattern Length", 31, 21, 0, 2); draw_text("Pattern Length", 19, 24, 0, 2); draw_text("Start Pattern", 20, 27, 0, 2); draw_text("End Pattern", 22, 28, 0, 2); } static void length_edit_close(SCHISM_UNUSED void *data) { int i, nl; nl = length_edit_widgets[0].d.thumbbar.value; status.flags |= SONG_NEEDS_SAVE; for (i = length_edit_widgets[1].d.thumbbar.value; i <= length_edit_widgets[2].d.thumbbar.value; i++) { if (song_get_pattern(i, NULL) != nl) { song_pattern_resize(i, nl); if (i == current_pattern) { status.flags |= NEED_UPDATE; current_row = MIN(current_row, nl - 1); pattern_editor_reposition(); } } } } static void length_edit_cancel(SCHISM_UNUSED void *data) { /* do nothing */ } void pattern_editor_length_edit(void) { struct dialog *dialog; widget_create_thumbbar(length_edit_widgets + 0, 34, 24, 22, 0, 1, 1, NULL, 32, 200); length_edit_widgets[0].d.thumbbar.value = song_get_pattern(current_pattern, NULL ); widget_create_thumbbar(length_edit_widgets + 1, 34, 27, 26, 0, 2, 2, NULL, 0, 199); widget_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; widget_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(SCHISM_UNUSED void *data) { int i, m = 0; for (i = 0; i < 64; i++) { channel_multi[i] = !!multichannel_widgets[i].d.toggle.state; if (channel_multi[i]) m = 1; } if (m) channel_multi_enabled = 1; } static int multichannel_handle_key(struct key_event *k) { if (k->sym == SCHISM_KEYSYM_n) { if ((k->mod & SCHISM_KEYMOD_ALT) && k->state == KEY_PRESS) dialog_yes(NULL); else if (NO_MODIFIER(k->mod) && k->state == KEY_RELEASE) dialog_cancel(NULL); return 1; } return 0; } static void multichannel_draw_const(void) { char sbuf[16]; int i; for (i = 0; i < 64; i++) { sprintf(sbuf, "Channel %02d", i+1); draw_text(sbuf, 9 + ((i / 16) * 16), /* X */ 22 + (i % 16), /* Y */ 0, 2); } for (i = 0; i < 64; i += 16) { draw_box( 19 + ((i / 16) * 16), /* X */ 21, 23 + ((i / 16) * 16), /* X */ 38, BOX_THIN|BOX_INNER|BOX_INSET); } draw_text("Multichannel Selection", 29, 19, 3, 2); } static void mp_advance_channel(void) { widget_change_focus_to(*selected_widget + 1); } static void pattern_editor_display_multichannel(void) { struct dialog *dialog; int i; for (i = 0; i < 64; i++) { widget_create_toggle(multichannel_widgets+i, 20 + ((i / 16) * 16), /* X */ 22 + (i % 16), /* Y */ ((i % 16) == 0) ? 64 : (i-1), ((i % 16) == 15) ? 64 : (i+1), (i < 16) ? (i+48) : (i-16), ((i + 16) % 64), ((i + 16) % 64), mp_advance_channel); multichannel_widgets[i].d.toggle.state = !!channel_multi[i]; } widget_create_button(multichannel_widgets + 64, 36, 40, 6, 15, 0, 64, 64, 64, dialog_yes_NULL, "OK", 3); dialog = dialog_create_custom(7, 18, 66, 25, multichannel_widgets, 65, 0, multichannel_draw_const, NULL); dialog->action_yes = multichannel_close; dialog->action_cancel = multichannel_close; dialog->handle_key = multichannel_handle_key; } /* This probably doesn't belong here, but whatever */ static int multichannel_get_next(int cur_channel) { int i; cur_channel--; /* make it zero-based. oh look, it's a hammer. */ i = cur_channel; if (channel_multi[cur_channel]) { /* we're in a multichan-enabled channel, so look for the next one */ do { i = (i + 1) & 63; /* no? next channel, and loop back to zero if we hit 64 */ if (channel_multi[i]) /* is this a multi-channel? */ break; /* it is! */ } while (i != cur_channel); /* at this point we've either broken the loop because the channel i is multichan, or the condition failed because we're back where we started */ } /* status_text_flash ("Newly selected channel is %d", (int) i + 1); */ return i + 1; /* make it one-based again */ } static int multichannel_get_previous(int cur_channel) { int i; cur_channel--; /* once again, .... */ i = cur_channel; if (channel_multi[cur_channel]) { do { i = i ? (i - 1) : 63; /* loop backwards this time */ if (channel_multi[i]) break; } while (i != cur_channel); } return i + 1; } /* --------------------------------------------------------------------------------------------------------- */ static void copyin_addnote(song_note_t *note, int *copyin_x, int *copyin_y) { song_note_t *pattern, *p_note; int num_rows; status.flags |= (SONG_NEEDS_SAVE|NEED_UPDATE); num_rows = song_get_pattern(current_pattern, &pattern); if ((*copyin_x + (current_channel-1)) >= 64) return; if ((*copyin_y + current_row) >= num_rows) return; p_note = pattern + 64 * (*copyin_y + current_row) + (*copyin_x + (current_channel-1)); *p_note = *note; (*copyin_x) = (*copyin_x) + 1; } static void copyin_addrow(int *copyin_x, int *copyin_y) { *copyin_x=0; (*copyin_y) = (*copyin_y) + 1; } static int pattern_selection_system_paste(SCHISM_UNUSED int cb, const void *data) { int copyin_x, copyin_y; int (*fx_map)(char f); const char *str; int x; unsigned int scantmp; if (!data) return 0; str = (const char *)data; for (x = 0; str[x] && str[x] != '\n'; x++); if (x <= 11) return 0; if (!str[x] || str[x+1] != '|') return 0; if (str[x-1] == '\r') x--; if ((str[x-3] == ' ' && str[x-2] == 'I' && str[x-1] == 'T') || (str[x-3] == 'S' && str[x-2] == '3' && str[x-1] == 'M')) { /* s3m effects */ fx_map = get_effect_number; } else if ((str[x-3] == ' ' && str[x-2] == 'X' && str[x-1] == 'M') || (str[x-3] == 'M' && str[x-2] == 'O' && str[x-1] == 'D')) { /* ptm effects */ fx_map = get_ptm_effect_number; } else { return 0; } if (str[x] == '\r') x++; str += x+2; copyin_x = copyin_y = 0; /* okay, let's start parsing */ while (*str) { song_note_t n = {0}; if (!str[0] || !str[1] || !str[2]) break; switch (*str) { case 'C': case 'c': n.note = 1; break; case 'D': case 'd': n.note = 3; break; case 'E': case 'e': n.note = 5; break; case 'F': case 'f': n.note = 6; break; case 'G': case 'g': n.note = 8; break; case 'A': case 'a': n.note = 10;break; case 'B': case 'b': n.note = 12;break; default: n.note = 0; }; /* XXX shouldn't this be note-- for flat? */ if (str[1] == '#' || str[1] == 'b') n.note++; switch (*str) { case '=': n.note = NOTE_OFF; break; case '^': n.note = NOTE_CUT; break; case '~': n.note = NOTE_FADE; break; case ' ': case '.': n.note = 0; break; default: n.note += ((str[2] - '0') * 12); break; }; str += 3; /* instrument number */ if (sscanf(str, "%02u", &scantmp) == 1) n.instrument = scantmp; else n.instrument = 0; str += 2; while (*str) { if (*str == '|' || *str == '\r' || *str == '\n') break; if (!str[0] || !str[1] || !str[2]) break; if (*str >= 'a' && *str <= 'z') { if (sscanf(str+1, "%02u", &scantmp) == 1) n.volparam = scantmp; else n.volparam = 0; switch (*str) { case 'v': n.voleffect = VOLFX_VOLUME; break; case 'p': n.voleffect = VOLFX_PANNING; break; case 'c': n.voleffect = VOLFX_VOLSLIDEUP; break; case 'd': n.voleffect = VOLFX_VOLSLIDEDOWN; break; case 'a': n.voleffect = VOLFX_FINEVOLUP; break; case 'b': n.voleffect = VOLFX_FINEVOLDOWN; break; case 'u': n.voleffect = VOLFX_VIBRATOSPEED; break; case 'h': n.voleffect = VOLFX_VIBRATODEPTH; break; case 'l': n.voleffect = VOLFX_PANSLIDELEFT; break; case 'r': n.voleffect = VOLFX_PANSLIDERIGHT; break; case 'g': n.voleffect = VOLFX_TONEPORTAMENTO; break; case 'f': n.voleffect = VOLFX_PORTAUP; break; case 'e': n.voleffect = VOLFX_PORTADOWN; break; default: n.voleffect = VOLFX_NONE; n.volparam = 0; break; }; } else { n.effect = fx_map(*str); if (sscanf(str+1, "%02X", &scantmp) == 1) n.param = scantmp; else n.param = 0; } str += 3; } copyin_addnote(&n, ©in_x, ©in_y); if (str[0] == '\r' || str[0] == '\n') { while (str[0] == '\r' || str[0] == '\n') str++; copyin_addrow(©in_x, ©in_y); } if (str[0] != '|') break; str++; } return 1; } static void pattern_selection_system_copyout(void) { char *str; int x, y, len; int total_rows; song_note_t *pattern, *cur_note; if (!(SELECTION_EXISTS)) { if (clippy_owner(CLIPPY_SELECT) == widgets_pattern) { /* unselect if we don't have a selection */ clippy_select(NULL, NULL, 0); } return; } len = 21; total_rows = song_get_pattern(current_pattern, &pattern); for (y = selection.first_row; y <= selection.last_row && y < total_rows; y++) { for (x = selection.first_channel; x <= selection.last_channel; x++) { /* must match template below */ len += 12; } len += 2; } str = mem_alloc(len+1); /* the OpenMPT/ModPlug header says: ModPlug Tracker S3M\x0d\x0a but really we can get away with: Pasted Pattern - IT\x0d\x0a because that's just how it's parser works. Add your own- just remember the file "type" is right-aligned. Please don't invent any new formats- even if you add more effects, try to base most of them on protracker (XM/MOD) or S3M/IT. */ strcpy(str, "Pasted Pattern - IT\x0d\x0a"); len = 21; for (y = selection.first_row; y <= selection.last_row && y < total_rows; y++) { cur_note = pattern + 64 * y + selection.first_channel - 1; for (x = selection.first_channel; x <= selection.last_channel; x++) { str[len] = '|'; len++; if (cur_note->note == 0) { str[len] = str[len+1] = str[len+2] = '.'; /* ... */ } else if (cur_note->note == NOTE_CUT) { str[len] = str[len+1] = str[len+2] = '^'; /* ^^^ */ } else if (cur_note->note == NOTE_OFF) { str[len] = str[len+1] = str[len+2] = '='; /* === */ } else if (cur_note->note == NOTE_FADE) { /* ModPlug won't handle this one, but it'll just drop it... */ str[len] = str[len+1] = str[len+2] = '~'; /* ~~~ */ } else { get_note_string(cur_note->note, str+len); } len += 3; if (cur_note->instrument) sprintf(str+len, "%02d", cur_note->instrument); else str[len] = str[len+1] = '.'; sprintf(str+len+3, "%02d", cur_note->volparam); switch (cur_note->voleffect) { case VOLFX_VOLUME: str[len+2] = 'v';break; case VOLFX_PANNING: str[len+2] = 'p';break; case VOLFX_VOLSLIDEUP: str[len+2] = 'c';break; case VOLFX_VOLSLIDEDOWN: str[len+2] = 'd';break; case VOLFX_FINEVOLUP: str[len+2] = 'a';break; case VOLFX_FINEVOLDOWN: str[len+2] = 'b';break; case VOLFX_VIBRATOSPEED: str[len+2] = 'u';break; case VOLFX_VIBRATODEPTH: str[len+2] = 'h';break; case VOLFX_PANSLIDELEFT: str[len+2] = 'l';break; case VOLFX_PANSLIDERIGHT: str[len+2] = 'r';break; case VOLFX_TONEPORTAMENTO: str[len+2] = 'g';break; case VOLFX_PORTAUP: str[len+2] = 'f';break; case VOLFX_PORTADOWN: str[len+2] = 'e';break; default: str[len+2] = '.'; /* override above */ str[len+3] = '.'; str[len+4] = '.'; }; len += 5; sprintf(str+len, "%c%02X", get_effect_char(cur_note->effect), cur_note->param); if (str[len] == '.' || str[len] == '?') { str[len] = '.'; if (!cur_note->param) str[len+1] = str[len+2] = '.'; } len += 3; /* Hints to implementors: If you had more columns in your channel, you should mark it here with a letter representing the channel semantic, followed by your decimal value. Add as many as you like- the next channel begins with a pipe-character (|) and the next row begins with a 0D 0A sequence. */ cur_note++; } str[len] = '\x0d'; str[len+1] = '\x0a'; len += 2; } str[len] = 0; clippy_select(widgets_pattern, str, len); } /* --------------------------------------------------------------------------------------------------------- */ /* undo dialog */ static struct widget undo_widgets[1]; static int undo_selection = 0; static void history_draw_const(void) { int i, j; int fg, bg; draw_text("Undo", 38, 22, 3, 2); draw_box(19,23,60,34, BOX_THIN | BOX_INNER | BOX_INSET); j = undo_history_top; for (i = 0; i < 10; i++) { if (i == undo_selection) { fg = 0; bg = 3; } else { fg = 2; bg = 0; } draw_char(32, 20, 24+i, fg, bg); draw_text_len(undo_history[j].snap_op, 39, 21, 24+i, fg, bg); j--; if (j < 0) j += 10; } } static void history_close(SCHISM_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 SCHISM_KEYSYM_ESCAPE: if (k->state == KEY_PRESS) return 0; dialog_cancel(NULL); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 0; undo_selection--; if (undo_selection < 0) undo_selection = 0; status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 0; undo_selection++; if (undo_selection > 9) undo_selection = 9; status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_RETURN: if (k->state == KEY_RELEASE) return 0; j = undo_history_top; for (i = 0; i < 10; i++) { if (i == undo_selection) { pated_history_restore(j); break; } j--; if (j < 0) j += 10; } dialog_cancel(NULL); status.flags |= NEED_UPDATE; return 1; default: break; }; return 0; } static void pattern_editor_display_history(void) { struct dialog *dialog; widget_create_other(undo_widgets + 0, 0, history_handle_key, NULL, 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(SCHISM_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(SCHISM_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 { widget_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; widget_create_button(volume_setup_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); widget_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(SCHISM_UNUSED void *data) { volume_percent = volume_setup_widgets[0].d.thumbbar.value; selection_amplify(volume_percent); } static int volume_amplify_jj(struct key_event *k) { if (k->state == KEY_PRESS && (k->mod & SCHISM_KEYMOD_ALT) && (k->sym == SCHISM_KEYSYM_j)) { dialog_yes(NULL); return 1; } return 0; } static void volume_amplify(void) { struct dialog *dialog; CHECK_FOR_SELECTION(return); widget_create_thumbbar(volume_setup_widgets + 0, 26, 30, 26, 0, 1, 1, NULL, 0, 200); volume_setup_widgets[0].d.thumbbar.value = volume_percent; widget_create_button(volume_setup_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); widget_create_button(volume_setup_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); dialog = dialog_create_custom(22, 25, 36, 11, volume_setup_widgets, 3, 0, volume_setup_draw_const, NULL); dialog->handle_key = volume_amplify_jj; dialog->action_yes = volume_amplify_ok; } /* --------------------------------------------------------------------------------------------------------- */ /* vary depth */ static int current_vary = -1; static void vary_setup_draw_const(void) { draw_text("Vary depth limit %", 31, 27, 0, 2); draw_box(25, 29, 52, 31, BOX_THIN | BOX_INNER | BOX_INSET); } static void vary_amplify_ok(SCHISM_UNUSED void *data) { vary_depth = volume_setup_widgets[0].d.thumbbar.value; selection_vary(0, vary_depth, current_vary); } static void vary_command(int how) { struct dialog *dialog; widget_create_thumbbar(volume_setup_widgets + 0, 26, 30, 26, 0, 1, 1, NULL, 0, 50); volume_setup_widgets[0].d.thumbbar.value = vary_depth; widget_create_button(volume_setup_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); widget_create_button(volume_setup_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); dialog = dialog_create_custom(22, 25, 36, 11, volume_setup_widgets, 3, 0, vary_setup_draw_const, NULL); dialog->action_yes = vary_amplify_ok; current_vary = how; } static int current_effect(void) { song_note_t *pattern, *cur_note; song_get_pattern(current_pattern, &pattern); cur_note = pattern + 64 * current_row + current_channel - 1; return cur_note->effect; } /* --------------------------------------------------------------------------------------------------------- */ /* settings */ #define CFG_SET_PE(v) cfg_set_number(cfg, "Pattern Editor", #v, v) void cfg_save_patedit(cfg_file_t *cfg) { int n; char s[65]; CFG_SET_PE(link_effect_column); CFG_SET_PE(draw_divisions); CFG_SET_PE(centralise_cursor); CFG_SET_PE(highlight_current_row); CFG_SET_PE(edit_copy_mask); CFG_SET_PE(volume_percent); CFG_SET_PE(fast_volume_percent); CFG_SET_PE(fast_volume_mode); CFG_SET_PE(keyjazz_noteoff); CFG_SET_PE(keyjazz_write_noteoff); CFG_SET_PE(keyjazz_repeat); CFG_SET_PE(keyjazz_capslock); CFG_SET_PE(mask_copy_search_mode); CFG_SET_PE(invert_home_end); cfg_set_number(cfg, "Pattern Editor", "crayola_mode", !!(status.flags & CRAYOLA_MODE)); for (n = 0; n < 64; n++) s[n] = track_view_scheme[n] + 'a'; s[64] = 0; cfg_set_string(cfg, "Pattern Editor", "track_view_scheme", s); for (n = 0; n < 64; n++) s[n] = (channel_multi[n]) ? 'M' : '-'; s[64] = 0; cfg_set_string(cfg, "Pattern Editor", "channel_multi", s); } #define CFG_GET_PE(v,d) v = cfg_get_number(cfg, "Pattern Editor", #v, d) void cfg_load_patedit(cfg_file_t *cfg) { int n, r = 0; char s[65]; CFG_GET_PE(link_effect_column, 0); CFG_GET_PE(draw_divisions, 1); CFG_GET_PE(centralise_cursor, 0); CFG_GET_PE(highlight_current_row, 0); CFG_GET_PE(edit_copy_mask, MASK_NOTE | MASK_INSTRUMENT | MASK_VOLUME); CFG_GET_PE(volume_percent, 100); CFG_GET_PE(fast_volume_percent, 67); CFG_GET_PE(fast_volume_mode, 0); CFG_GET_PE(keyjazz_noteoff, 0); CFG_GET_PE(keyjazz_write_noteoff, 0); CFG_GET_PE(keyjazz_repeat, 1); CFG_GET_PE(keyjazz_capslock, 0); CFG_GET_PE(mask_copy_search_mode, 0); CFG_GET_PE(invert_home_end, 0); if (cfg_get_number(cfg, "Pattern Editor", "crayola_mode", 0)) status.flags |= CRAYOLA_MODE; else status.flags &= ~CRAYOLA_MODE; cfg_get_string(cfg, "Pattern Editor", "track_view_scheme", s, 64, "a"); /* "decode" the track view scheme */ for (n = 0; n < 64; n++) { if (s[n] == '\0') { /* end of the string */ break; } else if (s[n] >= 'a' && s[n] <= 'z') { s[n] -= 'a'; } else if (s[n] >= 'A' && s[n] <= 'Z') { s[n] -= 'A'; } else { log_appendf(4, "Track view scheme corrupted; using default"); n = 64; r = 0; break; } r = s[n]; } memcpy(track_view_scheme, s, n); if (n < 64) memset(track_view_scheme + n, r, 64 - n); cfg_get_string(cfg, "Pattern Editor", "channel_multi", s, 64, ""); memset(channel_multi, 0, sizeof(channel_multi)); channel_multi_enabled = 0; for (n = 0; n < 64; n++) { if (!s[n]) break; channel_multi[n] = ((s[n] >= 'A' && s[n] <= 'Z') || (s[n] >= 'a' && s[n] <= 'z')) ? 1 : 0; if (channel_multi[n]) channel_multi_enabled = 1; } recalculate_visible_area(); pattern_editor_reposition(); if (status.current_page == PAGE_PATTERN_EDITOR) status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ /* selection handling functions */ static inline int is_in_selection(int chan, int row) { return (SELECTION_EXISTS && chan >= selection.first_channel && chan <= selection.last_channel && row >= selection.first_row && row <= selection.last_row); } static void normalise_block_selection(void) { int n; if (!SELECTION_EXISTS) return; if (selection.first_channel > selection.last_channel) { n = selection.first_channel; selection.first_channel = selection.last_channel; selection.last_channel = n; } if (selection.first_row < 0) selection.first_row = 0; if (selection.last_row < 0) selection.last_row = 0; if (selection.first_channel < 1) selection.first_channel = 1; if (selection.last_channel < 1) selection.last_channel = 1; if (selection.first_row > selection.last_row) { n = selection.first_row; selection.first_row = selection.last_row; selection.last_row = n; } } static void shift_selection_begin(void) { shift_selection.in_progress = 1; shift_selection.first_channel = current_channel; shift_selection.first_row = current_row; } static void shift_selection_update(void) { if (shift_selection.in_progress) { selection.first_channel = shift_selection.first_channel; selection.last_channel = current_channel; selection.first_row = shift_selection.first_row; selection.last_row = current_row; normalise_block_selection(); } } static void shift_selection_end(void) { shift_selection.in_progress = 0; pattern_selection_system_copyout(); } static void selection_clear(void) { selection.first_channel = 0; pattern_selection_system_copyout(); } // FIXME | this misbehaves if height is an odd number -- e.g. if an odd number // FIXME | of rows is selected and 2 * sel_rows overlaps the end of the pattern static void block_length_double(void) { song_note_t *pattern, *src, *dest; int sel_rows, total_rows; int src_end, dest_end; // = first row that is NOT affected int width, height, offset; if (!SELECTION_EXISTS) return; status.flags |= SONG_NEEDS_SAVE; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows) selection.last_row = total_rows - 1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; sel_rows = selection.last_row - selection.first_row + 1; offset = selection.first_channel - 1; width = selection.last_channel - offset; dest_end = MIN(selection.first_row + 2 * sel_rows, total_rows); height = dest_end - selection.first_row; src_end = selection.first_row + height / 2; src = pattern + 64 * (src_end - 1); dest = pattern + 64 * (dest_end - 1); pated_history_add("Undo block length double (Alt-F)", offset, selection.first_row, width, height); while (dest > src) { memset(dest + offset, 0, width * sizeof(song_note_t)); dest -= 64; memcpy(dest + offset, src + offset, width * sizeof(song_note_t)); dest -= 64; src -= 64; } pattern_selection_system_copyout(); } // FIXME: this should erase the end of the selection if 2 * sel_rows > total_rows static void block_length_halve(void) { song_note_t *pattern, *src, *dest; int sel_rows, src_end, total_rows, row; int width, height, offset; if (!SELECTION_EXISTS) return; status.flags |= SONG_NEEDS_SAVE; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows) selection.last_row = total_rows - 1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; sel_rows = selection.last_row - selection.first_row + 1; offset = selection.first_channel - 1; width = selection.last_channel - offset; src_end = MIN(selection.first_row + 2 * sel_rows, total_rows); height = src_end - selection.first_row; src = dest = pattern + 64 * selection.first_row; pated_history_add("Undo block length halve (Alt-G)", offset, selection.first_row, width, height); for (row = 0; row < height / 2; row++) { memcpy(dest + offset, src + offset, width * sizeof(song_note_t)); src += 64 * 2; dest += 64; } pattern_selection_system_copyout(); } static void selection_erase(void) { song_note_t *pattern, *note; int row; int chan_width; int total_rows; if (!SELECTION_EXISTS) return; status.flags |= SONG_NEEDS_SAVE; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; pated_history_add("Undo block cut (Alt-Z)", selection.first_channel - 1, selection.first_row, (selection.last_channel - selection.first_channel) + 1, (selection.last_row - selection.first_row) + 1); if (selection.first_channel == 1 && selection.last_channel == 64) { memset(pattern + 64 * selection.first_row, 0, (selection.last_row - selection.first_row + 1) * 64 * sizeof(song_note_t)); } else { chan_width = selection.last_channel - selection.first_channel + 1; for (row = selection.first_row; row <= selection.last_row; row++) { note = pattern + 64 * row + selection.first_channel - 1; memset(note, 0, chan_width * sizeof(song_note_t)); } } pattern_selection_system_copyout(); } static void selection_set_sample(void) { int row, chan; song_note_t *pattern, *note; int total_rows; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; status.flags |= SONG_NEEDS_SAVE; pated_history_add("Undo set sample/instrument (Alt-S)", selection.first_channel - 1, selection.first_row, (selection.last_channel - selection.first_channel) + 1, (selection.last_row - selection.first_row) + 1); if (SELECTION_EXISTS) { for (row = selection.first_row; row <= selection.last_row; row++) { note = pattern + 64 * row + selection.first_channel - 1; for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { if (note->instrument) { note->instrument = song_get_current_instrument(); } } } } else { note = pattern + 64 * current_row + current_channel - 1; if (note->instrument) { note->instrument = song_get_current_instrument(); } } pattern_selection_system_copyout(); } static void selection_swap(void) { /* s_note = selection; p_note = position */ song_note_t *pattern, *s_note, *p_note, tmp; int row, chan, sel_rows, sel_chans, total_rows; int affected_width, affected_height; CHECK_FOR_SELECTION(return); status.flags |= SONG_NEEDS_SAVE; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; sel_rows = selection.last_row - selection.first_row + 1; sel_chans = selection.last_channel - selection.first_channel + 1; affected_width = MAX(selection.last_channel, current_channel + sel_chans - 1) - MIN(selection.first_channel, current_channel) + 1; affected_height = MAX(selection.last_row, current_row + sel_rows - 1) - MIN(selection.first_row, current_row) + 1; /* The minimum combined size for the two blocks is double the number of rows in the selection by * double the number of channels. So, if the width and height don't add up, they must overlap. It's * of course possible to have the blocks adjacent but not overlapping -- there is only overlap if * *both* the width and height are less than double the size. */ if (affected_width < 2 * sel_chans && affected_height < 2 * sel_rows) { dialog_create(DIALOG_OK, " Swap blocks overlap ", NULL, NULL, 0, NULL); return; } if (current_row + sel_rows > total_rows || current_channel + sel_chans - 1 > 64) { dialog_create(DIALOG_OK, " Out of pattern range ", NULL, NULL, 0, NULL); return; } pated_history_add("Undo swap block (Alt-Y)", MIN(selection.first_channel, current_channel) - 1, MIN(selection.first_row, current_row), affected_width, affected_height); for (row = 0; row < sel_rows; row++) { s_note = pattern + 64 * (selection.first_row + row) + selection.first_channel - 1; p_note = pattern + 64 * (current_row + row) + current_channel - 1; for (chan = 0; chan < sel_chans; chan++, s_note++, p_note++) { tmp = *s_note; *s_note = *p_note; *p_note = tmp; } } pattern_selection_system_copyout(); } static void selection_set_volume(void) { int row, chan, total_rows; song_note_t *pattern, *note; CHECK_FOR_SELECTION(return); status.flags |= SONG_NEEDS_SAVE; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; pated_history_add("Undo set volume/panning (Alt-V)", selection.first_channel - 1, selection.first_row, (selection.last_channel - selection.first_channel) + 1, (selection.last_row - selection.first_row) + 1); for (row = selection.first_row; row <= selection.last_row; row++) { note = pattern + 64 * row + selection.first_channel - 1; for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { note->volparam = mask_note.volparam; note->voleffect = mask_note.voleffect; } } pattern_selection_system_copyout(); } /* The logic for this one makes my head hurt. */ static void selection_slide_volume(void) { int row, chan, total_rows; song_note_t *pattern, *note, *last_note; int first, last; /* the volumes */ int ve, lve; /* volume effect */ /* FIXME: if there's no selection, should this display a dialog, or bail silently? */ /* Impulse Tracker displays a box "No block is marked" */ CHECK_FOR_SELECTION(return); total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; /* can't slide one row */ if (selection.first_row == selection.last_row) return; status.flags |= SONG_NEEDS_SAVE; pated_history_add("Undo volume or panning slide (Alt-K)", selection.first_channel - 1, selection.first_row, (selection.last_channel - selection.first_channel) + 1, (selection.last_row - selection.first_row) + 1); /* the channel loop has to go on the outside for this one */ for (chan = selection.first_channel; chan <= selection.last_channel; chan++) { note = pattern + 64 * selection.first_row + chan - 1; last_note = pattern + 64 * selection.last_row + chan - 1; /* valid combinations: * [ volume - volume ] * [panning - panning] * [ volume - none ] \ only valid if the 'none' * [ none - volume ] / note has a sample number * in any other case, no slide occurs. */ ve = note->voleffect; lve = last_note->voleffect; first = note->volparam; last = last_note->volparam; /* Note: IT only uses the sample's default volume if there is an instrument number *AND* a note. I'm just checking the instrument number, as it's the minimal information needed to get the default volume for the instrument. Would be nice but way hard to do: if there's a note but no sample number, look back in the pattern and use the last sample number in that channel (if there is one). */ if (ve == VOLFX_NONE) { if (note->instrument == 0) continue; ve = VOLFX_VOLUME; /* Modplug hack: volume bit shift */ first = song_get_sample(note->instrument)->volume >> 2; } if (lve == VOLFX_NONE) { if (last_note->instrument == 0) continue; lve = VOLFX_VOLUME; last = song_get_sample(last_note->instrument)->volume >> 2; } if (!(ve == lve && (ve == VOLFX_VOLUME || ve == VOLFX_PANNING))) { continue; } for (row = selection.first_row; row <= selection.last_row; row++, note += 64) { note->voleffect = ve; note->volparam = (((last - first) * (row - selection.first_row) / (selection.last_row - selection.first_row) ) + first); } } pattern_selection_system_copyout(); } static void selection_wipe_volume(int reckless) { int row, chan, total_rows; song_note_t *pattern, *note; CHECK_FOR_SELECTION(return); total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; status.flags |= SONG_NEEDS_SAVE; pated_history_add((reckless ? "Recover volumes/pannings (2*Alt-K)" : "Replace extra volumes/pannings (Alt-W)"), selection.first_channel - 1, selection.first_row, (selection.last_channel - selection.first_channel) + 1, (selection.last_row - selection.first_row) + 1); for (row = selection.first_row; row <= selection.last_row; row++) { note = pattern + 64 * row + selection.first_channel - 1; for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { if (reckless || (note->instrument == 0 && !NOTE_IS_NOTE(note->note))) { note->volparam = 0; note->voleffect = VOLFX_NONE; } } } pattern_selection_system_copyout(); } static int vary_value(int ov, int limit, int depth) { int j; j = (int)((((float)limit)*rand()) / (RAND_MAX+1.0)); j = ((limit >> 1) - j); j = ov+((j * depth) / 100); if (j < 0) j = 0; if (j > limit) j = limit; return j; } static int common_variable_group(int ch) { switch (ch) { case FX_PORTAMENTODOWN: case FX_PORTAMENTOUP: case FX_TONEPORTAMENTO: return FX_TONEPORTAMENTO; case FX_VOLUMESLIDE: case FX_TONEPORTAVOL: case FX_VIBRATOVOL: return FX_VOLUMESLIDE; case FX_PANNING: case FX_PANNINGSLIDE: case FX_PANBRELLO: return FX_PANNING; default: return ch; /* err... */ }; } static void selection_vary(int fast, int depth, int how) { int row, chan, total_rows; song_note_t *pattern, *note; static char last_vary[39]; const char *vary_how; char ch; /* don't ever vary these things */ switch (how) { default: if (!FX_IS_EFFECT(how)) return; break; case FX_NONE: case FX_SPECIAL: case FX_SPEED: case FX_POSITIONJUMP: case FX_PATTERNBREAK: case FX_KEYOFF: case FX_SETENVPOSITION: case FX_VOLUME: case FX_NOTESLIDEUP: case FX_NOTESLIDEDOWN: return; } CHECK_FOR_SELECTION(return); status.flags |= SONG_NEEDS_SAVE; switch (how) { case FX_CHANNELVOLUME: case FX_CHANNELVOLSLIDE: vary_how = "Undo volume-channel vary (Ctrl-U)"; if (fast) status_text_flash("Fast volume vary"); break; case FX_PANNING: case FX_PANNINGSLIDE: case FX_PANBRELLO: vary_how = "Undo panning vary (Ctrl-Y)"; if (fast) status_text_flash("Fast panning vary"); break; default: sprintf(last_vary, "%-28s (Ctrl-K)", "Undo Xxx effect-value vary"); last_vary[5] = common_variable_group(how); if (fast) status_text_flash("Fast %-21s", last_vary+5); vary_how = last_vary; break; }; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; pated_history_add(vary_how, selection.first_channel - 1, selection.first_row, (selection.last_channel - selection.first_channel) + 1, (selection.last_row - selection.first_row) + 1); for (row = selection.first_row; row <= selection.last_row; row++) { note = pattern + 64 * row + selection.first_channel - 1; for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { if (how == FX_CHANNELVOLUME || how == FX_CHANNELVOLSLIDE) { if (note->voleffect == VOLFX_VOLUME) { note->volparam = vary_value(note->volparam, 64, depth); } } if (how == FX_PANNINGSLIDE || how == FX_PANNING || how == FX_PANBRELLO) { if (note->voleffect == VOLFX_PANNING) { note->volparam = vary_value(note->volparam, 64, depth); } } ch = note->effect; if (!FX_IS_EFFECT(ch)) continue; if (common_variable_group(ch) != common_variable_group(how)) continue; switch (ch) { /* these are .0 0. and .f f. values */ case FX_VOLUMESLIDE: case FX_CHANNELVOLSLIDE: case FX_PANNINGSLIDE: case FX_GLOBALVOLSLIDE: case FX_VIBRATOVOL: case FX_TONEPORTAVOL: if ((note->param & 15) == 15) continue; if ((note->param & 0xF0) == (0xF0))continue; if ((note->param & 15) == 0) { note->param = (1+(vary_value(note->param>>4, 15, depth))) << 4; } else { note->param = 1+(vary_value(note->param & 15, 15, depth)); } break; /* tempo has a slide */ case FX_TEMPO: if ((note->param & 15) == 15) continue; if ((note->param & 0xF0) == (0xF0))continue; /* but otherwise it's absolute */ note->param = 1 + (vary_value(note->param, 255, depth)); break; /* don't vary .E. and .F. values */ case FX_PORTAMENTODOWN: case FX_PORTAMENTOUP: if ((note->param & 15) == 15) continue; if ((note->param & 15) == 14) continue; if ((note->param & 0xF0) == (0xF0))continue; if ((note->param & 0xF0) == (0xE0))continue; note->param = 16 + (vary_value(note->param-16, 224, depth)); break; /* these are all "xx" commands */ // FIXME global/channel volume should be limited to 0-128 and 0-64, respectively case FX_TONEPORTAMENTO: case FX_CHANNELVOLUME: case FX_OFFSET: case FX_GLOBALVOLUME: case FX_PANNING: note->param = 1 + (vary_value(note->param, 255, depth)); break; /* these are all "xy" commands */ case FX_VIBRATO: case FX_TREMOR: case FX_ARPEGGIO: case FX_RETRIG: case FX_TREMOLO: case FX_PANBRELLO: case FX_FINEVIBRATO: note->param = (1 + (vary_value(note->param & 15, 15, depth))) | ((1 + (vary_value((note->param >> 4) & 15, 15, depth))) << 4); break; }; } } pattern_selection_system_copyout(); } static void selection_amplify(int percentage) { int row, chan, volume, total_rows; song_note_t *pattern, *note; if (!SELECTION_EXISTS) return; status.flags |= SONG_NEEDS_SAVE; total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; /* it says Alt-J even when Alt-I was used */ pated_history_add("Undo volume amplification (Alt-J)", selection.first_channel - 1, selection.first_row, (selection.last_channel - selection.first_channel) + 1, (selection.last_row - selection.first_row) + 1); for (row = selection.first_row; row <= selection.last_row; row++) { note = pattern + 64 * row + selection.first_channel - 1; for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { if (note->voleffect == VOLFX_NONE && note->instrument != 0) { /* Modplug hack: volume bit shift */ if (song_is_instrument_mode()) volume = 64; /* XXX */ else volume = song_get_sample(note->instrument)->volume >> 2; } else if (note->voleffect == VOLFX_VOLUME) { volume = note->volparam; } else { continue; } volume *= percentage; volume /= 100; if (volume > 64) volume = 64; else if (volume < 0) volume = 0; note->volparam = volume; note->voleffect = VOLFX_VOLUME; } } pattern_selection_system_copyout(); } static void selection_slide_effect(void) { int row, chan, total_rows; song_note_t *pattern, *note; int first, last; /* the effect values */ /* FIXME: if there's no selection, should this display a dialog, or bail silently? */ CHECK_FOR_SELECTION(return); total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; if (selection.first_row == selection.last_row) return; status.flags |= SONG_NEEDS_SAVE; pated_history_add("Undo effect data slide (Alt-X)", selection.first_channel - 1, selection.first_row, (selection.last_channel - selection.first_channel) + 1, (selection.last_row - selection.first_row) + 1); /* the channel loop has to go on the outside for this one */ for (chan = selection.first_channel; chan <= selection.last_channel; chan++) { note = pattern + chan - 1; first = note[64 * selection.first_row].param; last = note[64 * selection.last_row].param; note += 64 * selection.first_row; for (row = selection.first_row; row <= selection.last_row; row++, note += 64) { note->param = (((last - first) * (row - selection.first_row) / (selection.last_row - selection.first_row) ) + first); } } pattern_selection_system_copyout(); } static void selection_wipe_effect(void) { int row, chan, total_rows; song_note_t *pattern, *note; CHECK_FOR_SELECTION(return); total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows)selection.last_row = total_rows-1; if (selection.first_row > selection.last_row) selection.first_row = selection.last_row; status.flags |= SONG_NEEDS_SAVE; pated_history_add("Recover effects/effect data (2*Alt-X)", selection.first_channel - 1, selection.first_row, (selection.last_channel - selection.first_channel) + 1, (selection.last_row - selection.first_row) + 1); for (row = selection.first_row; row <= selection.last_row; row++) { note = pattern + 64 * row + selection.first_channel - 1; for (chan = selection.first_channel; chan <= selection.last_channel; chan++, note++) { note->effect = 0; note->param = 0; } } pattern_selection_system_copyout(); } enum roll_dir { ROLL_DOWN = -1, ROLL_UP = +1 }; static void selection_roll(enum roll_dir direction) { song_note_t *pattern, *seldata; int row, sel_rows, sel_chans, total_rows, copy_bytes, n; if (!SELECTION_EXISTS) { return; } total_rows = song_get_pattern(current_pattern, &pattern); if (selection.last_row >= total_rows) { selection.last_row = total_rows - 1; } if (selection.first_row > selection.last_row) { selection.first_row = selection.last_row; } sel_rows = selection.last_row - selection.first_row + 1; sel_chans = selection.last_channel - selection.first_channel + 1; if (sel_rows < 2) { return; } seldata = pattern + 64 * selection.first_row + selection.first_channel - 1; SCHISM_VLA_ALLOC(song_note_t, temp, sel_chans); copy_bytes = sizeof(temp); row = (direction == ROLL_DOWN ? sel_rows - 1 : 0); memcpy(temp, seldata + 64 * row, copy_bytes); for (n = 1; n < sel_rows; n++, row += direction) memcpy(seldata + 64 * row, seldata + 64 * (row + direction), copy_bytes); memcpy(seldata + 64 * row, temp, copy_bytes); SCHISM_VLA_FREE(temp); status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------------------------------------------- */ /* Row shifting operations */ /* A couple of the param names here might seem a bit confusing, so: * what_row = what row to start the insert (generally this would be current_row) * num_rows = the number of rows to insert */ static void pattern_insert_rows(int what_row, int num_rows, int first_channel, int chan_width) { song_note_t *pattern; int row, total_rows = song_get_pattern(current_pattern, &pattern); status.flags |= SONG_NEEDS_SAVE; if (first_channel < 1) first_channel = 1; if (chan_width + first_channel - 1 > 64) chan_width = 64 - first_channel + 1; if (num_rows + what_row > total_rows) num_rows = total_rows - what_row; if (first_channel == 1 && chan_width == 64) { memmove(pattern + 64 * (what_row + num_rows), pattern + 64 * what_row, 64 * sizeof(song_note_t) * (total_rows - what_row - num_rows)); memset(pattern + 64 * what_row, 0, num_rows * 64 * sizeof(song_note_t)); } else { /* shift the area down */ for (row = total_rows - num_rows - 1; row >= what_row; row--) { memmove(pattern + 64 * (row + num_rows) + first_channel - 1, pattern + 64 * row + first_channel - 1, chan_width * sizeof(song_note_t)); } /* clear the inserted rows */ for (row = what_row; row < what_row + num_rows; row++) { memset(pattern + 64 * row + first_channel - 1, 0, chan_width * sizeof(song_note_t)); } } pattern_selection_system_copyout(); } /* Same as above, but with a couple subtle differences. */ static void pattern_delete_rows(int what_row, int num_rows, int first_channel, int chan_width) { song_note_t *pattern; int row, total_rows = song_get_pattern(current_pattern, &pattern); status.flags |= SONG_NEEDS_SAVE; if (first_channel < 1) first_channel = 1; if (chan_width + first_channel - 1 > 64) chan_width = 64 - first_channel + 1; if (num_rows + what_row > total_rows) num_rows = total_rows - what_row; if (first_channel == 1 && chan_width == 64) { memmove(pattern + 64 * what_row, pattern + 64 * (what_row + num_rows), 64 * sizeof(song_note_t) * (total_rows - what_row - num_rows)); memset(pattern + 64 * (total_rows - num_rows), 0, num_rows * 64 * sizeof(song_note_t)); } else { /* shift the area up */ for (row = what_row; row <= total_rows - num_rows - 1; row++) { memmove(pattern + 64 * row + first_channel - 1, pattern + 64 * (row + num_rows) + first_channel - 1, chan_width * sizeof(song_note_t)); } /* clear the last rows */ for (row = total_rows - num_rows; row < total_rows; row++) { memset(pattern + 64 * row + first_channel - 1, 0, chan_width * sizeof(song_note_t)); } } pattern_selection_system_copyout(); } /* --------------------------------------------------------------------------------------------------------- */ /* history/undo */ static void pated_history_clear(void) { // clear undo history int i; for (i = 0; i < 10; i++) { if (undo_history[i].snap_op_allocated) free((void *) undo_history[i].snap_op); free(undo_history[i].data); memset(&undo_history[i],0,sizeof(struct pattern_snap)); undo_history[i].snap_op = "Empty"; undo_history[i].snap_op_allocated = 0; } } static void set_note_note(song_note_t *n, int a, int b) { if (a > 0 && a < 250) { a += b; if (a <= 0 || a >= 250) a = 0; } n->note = a; } static void snap_paste(struct pattern_snap *s, int x, int y, int xlate) { song_note_t *pattern, *p_note; int row, num_rows, chan_width; int chan; status.flags |= SONG_NEEDS_SAVE; if (x < 0) x = s->x; if (y < 0) y = s->y; num_rows = song_get_pattern(current_pattern, &pattern); num_rows -= y; if (s->rows < num_rows) num_rows = s->rows; if (num_rows <= 0) return; chan_width = s->channels; if (chan_width + x >= 64) chan_width = 64 - x; for (row = 0; row < num_rows; row++) { p_note = pattern + 64 * (y + row) + x; memcpy(pattern + 64 * (y + row) + x, s->data + s->channels * row, chan_width * sizeof(song_note_t)); if (!xlate) continue; for (chan = 0; chan < chan_width; chan++) { if (chan + x > 64) break; /* defensive */ set_note_note(p_note+chan, p_note[chan].note, xlate); } } pattern_selection_system_copyout(); } static void snap_copy(struct pattern_snap *s, int x, int y, int width, int height) { song_note_t *pattern; int row, total_rows, len; memused_songchanged(); s->channels = width; s->rows = height; total_rows = song_get_pattern(current_pattern, &pattern); s->data = mem_alloc(len = (sizeof(song_note_t) * s->channels * s->rows)); if (s->rows > total_rows) { memset(s->data, 0, len); } s->x = x; s->y = y; if (x == 0 && width == 64) { if (height >total_rows) height = total_rows; memcpy(s->data, pattern + 64 * y, (width*height*sizeof(song_note_t))); } else { for (row = 0; row < s->rows && row < total_rows; row++) { memcpy(s->data + s->channels * row, pattern + 64 * (row + s->y) + s->x, s->channels * sizeof(song_note_t)); } } } static int snap_honor_mute(struct pattern_snap *s, int base_channel) { int i,j; song_note_t *n; int mute[64]; int did_any; for (i = 0; i < s->channels; i++) { mute[i] = (song_get_channel(i+base_channel)->flags & CHN_MUTE); } n = s->data; did_any = 0; for (j = 0; j < s->rows; j++) { for (i = 0; i < s->channels; i++) { if (mute[i]) { memset(n, 0, sizeof(song_note_t)); did_any = 1; } n++; } } return did_any; } static void pated_history_restore(int n) { if (n < 0 || n > 9) return; snap_paste(&undo_history[n], -1, -1, 0); } static void pated_save(const char *descr) { int total_rows; total_rows = song_get_pattern(current_pattern, NULL); pated_history_add(descr,0,0,64,total_rows); } static void pated_history_add(const char *descr, int x, int y, int width, int height) { pated_history_add2(0, descr, x, y, width, height); } static void pated_history_add_grouped(const char *descr, int x, int y, int width, int height) { pated_history_add2(1, descr, x, y, width, height); } static void pated_history_add2(int groupedf, const char *descr, int x, int y, int width, int height) { int j; j = undo_history_top; if (groupedf && undo_history[j].patternno == current_pattern && undo_history[j].x == x && undo_history[j].y == y && undo_history[j].channels == width && undo_history[j].rows == height && undo_history[j].snap_op && strcmp(undo_history[j].snap_op, descr) == 0) { /* do nothing; use the previous bit of history */ } else { j = (undo_history_top + 1) % 10; free(undo_history[j].data); snap_copy(&undo_history[j], x, y, width, height); undo_history[j].snap_op = str_dup(descr); undo_history[j].snap_op_allocated = 1; undo_history[j].patternno = current_pattern; undo_history_top = j; } } static void fast_save_update(void) { int total_rows; free(fast_save.data); fast_save.data = NULL; total_rows = song_get_pattern(current_pattern, NULL); snap_copy(&fast_save, 0, 0, 64, total_rows); } /* clipboard */ static void clipboard_free(void) { free(clipboard.data); clipboard.data = NULL; } /* clipboard_copy is fundementally the same as selection_erase * except it uses memcpy instead of memset :) */ static void clipboard_copy(int honor_mute) { int flag; CHECK_FOR_SELECTION(return); clipboard_free(); snap_copy(&clipboard, selection.first_channel - 1, selection.first_row, (selection.last_channel - selection.first_channel) + 1, (selection.last_row - selection.first_row) + 1); flag = 0; if (honor_mute) { flag = snap_honor_mute(&clipboard, selection.first_channel-1); } /* transfer to system where appropriate */ clippy_yank(); if (flag) { status_text_flash("Selection honors current mute settings"); } } static void clipboard_paste_overwrite(int suppress, int grow) { song_note_t *pattern; int num_rows, chan_width; if (clipboard.data == NULL) { dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); return; } num_rows = song_get_pattern(current_pattern, &pattern); num_rows -= current_row; if (clipboard.rows < num_rows) num_rows = clipboard.rows; if (clipboard.rows > num_rows && grow) { if (current_row+clipboard.rows > 200) { status_text_flash("Resized pattern %d, but clipped to 200 rows", current_pattern); song_pattern_resize(current_pattern, 200); } else { status_text_flash("Resized pattern %d to %d rows", current_pattern, current_row + clipboard.rows); song_pattern_resize(current_pattern, current_row+clipboard.rows); } } chan_width = clipboard.channels; if (chan_width + current_channel > 64) chan_width = 64 - current_channel + 1; if (!suppress) { pated_history_add_grouped("Replace overwritten data (Alt-O)", current_channel-1, current_row, chan_width, num_rows); } snap_paste(&clipboard, current_channel-1, current_row, 0); } static void clipboard_paste_insert(void) { int num_rows, total_rows, chan_width; song_note_t *pattern; if (clipboard.data == NULL) { dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); return; } total_rows = song_get_pattern(current_pattern, &pattern); pated_save("Undo paste data (Alt-P)"); num_rows = total_rows - current_row; if (clipboard.rows < num_rows) num_rows = clipboard.rows; chan_width = clipboard.channels; if (chan_width + current_channel > 64) chan_width = 64 - current_channel + 1; pattern_insert_rows(current_row, clipboard.rows, current_channel, chan_width); clipboard_paste_overwrite(1, 0); pattern_selection_system_copyout(); } static void clipboard_paste_mix_notes(int clip, int xlate) { int row, chan, num_rows, chan_width; song_note_t *pattern, *p_note, *c_note; if (clipboard.data == NULL) { dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); return; } status.flags |= SONG_NEEDS_SAVE; num_rows = song_get_pattern(current_pattern, &pattern); num_rows -= current_row; if (clipboard.rows < num_rows) num_rows = clipboard.rows; chan_width = clipboard.channels; if (chan_width + current_channel > 64) chan_width = 64 - current_channel + 1; /* note that IT doesn't do this for "fields" either... */ pated_history_add_grouped("Replace mixed data (Alt-M)", current_channel-1, current_row, chan_width, num_rows); p_note = pattern + 64 * current_row + current_channel - 1; c_note = clipboard.data; for (row = 0; row < num_rows; row++) { for (chan = 0; chan < chan_width; chan++) { if (memcmp(p_note + chan, blank_note, sizeof(song_note_t)) == 0) { p_note[chan] = c_note[chan]; set_note_note(p_note+chan, c_note[chan].note, xlate); if (clip) { p_note[chan].instrument = song_get_current_instrument(); if (edit_copy_mask & MASK_VOLUME) { p_note[chan].voleffect = mask_note.voleffect; p_note[chan].volparam = mask_note.volparam; } else { p_note[chan].voleffect = 0; p_note[chan].volparam = 0; } if (edit_copy_mask & MASK_EFFECT) { p_note[chan].effect = mask_note.effect; p_note[chan].param = mask_note.param; } } } } p_note += 64; c_note += clipboard.channels; } } /* Same code as above. Maybe I should generalize it. */ static void clipboard_paste_mix_fields(int prec, int xlate) { int row, chan, num_rows, chan_width; song_note_t *pattern, *p_note, *c_note; if (clipboard.data == NULL) { dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); return; } status.flags |= SONG_NEEDS_SAVE; num_rows = song_get_pattern(current_pattern, &pattern); num_rows -= current_row; if (clipboard.rows < num_rows) num_rows = clipboard.rows; chan_width = clipboard.channels; if (chan_width + current_channel > 64) chan_width = 64 - current_channel + 1; p_note = pattern + 64 * current_row + current_channel - 1; c_note = clipboard.data; for (row = 0; row < num_rows; row++) { for (chan = 0; chan < chan_width; chan++) { /* Ick. There ought to be a "conditional move" operator. */ if (prec) { /* clipboard precedence */ if (c_note[chan].note != 0) { set_note_note(p_note+chan, c_note[chan].note, xlate); } if (c_note[chan].instrument != 0) p_note[chan].instrument = c_note[chan].instrument; if (c_note[chan].voleffect != VOLFX_NONE) { p_note[chan].voleffect = c_note[chan].voleffect; p_note[chan].volparam = c_note[chan].volparam; } if (c_note[chan].effect != 0) { p_note[chan].effect = c_note[chan].effect; } if (c_note[chan].param != 0) p_note[chan].param = c_note[chan].param; } else { if (p_note[chan].note == 0) { set_note_note(p_note+chan, c_note[chan].note, xlate); } if (p_note[chan].instrument == 0) p_note[chan].instrument = c_note[chan].instrument; if (p_note[chan].voleffect == VOLFX_NONE) { p_note[chan].voleffect = c_note[chan].voleffect; p_note[chan].volparam = c_note[chan].volparam; } if (p_note[chan].effect == 0) { p_note[chan].effect = c_note[chan].effect; } if (p_note[chan].param == 0) p_note[chan].param = c_note[chan].param; } } p_note += 64; c_note += clipboard.channels; } } /* --------------------------------------------------------------------- */ static void pattern_editor_reposition(void) { int total_rows = song_get_rows_in_pattern(current_pattern); if (current_channel < top_display_channel) top_display_channel = current_channel; else if (current_channel >= top_display_channel + visible_channels) top_display_channel = current_channel - visible_channels + 1; if (centralise_cursor) { if (current_row <= 16) top_display_row = 0; else if (current_row + 15 > total_rows) top_display_row = total_rows - 31; else top_display_row = current_row - 16; } else { /* This could be written better. */ if (current_row < top_display_row) top_display_row = current_row; else if (current_row > top_display_row + 31) top_display_row = current_row - 31; if (top_display_row + 31 > total_rows) top_display_row = total_rows - 31; } if (top_display_row < 0) top_display_row = 0; } static void advance_cursor(int next_row, int multichannel) { int total_rows; if (next_row && !(SONG_PLAYING && playback_tracing)) { total_rows = song_get_rows_in_pattern(current_pattern); if (skip_value) { if (current_row + skip_value <= total_rows) { current_row += skip_value; pattern_editor_reposition(); } } else { if (current_channel < 64) { current_channel++; } else { current_channel = 1; if (current_row < total_rows) current_row++; } pattern_editor_reposition(); } } if (multichannel) { current_channel = multichannel_get_next(current_channel); } } /* --------------------------------------------------------------------- */ void update_current_row(void) { char buf[4]; draw_text(str_from_num(3, current_row, buf), 12, 7, 5, 0); draw_text(str_from_num(3, song_get_rows_in_pattern(current_pattern), buf), 16, 7, 5, 0); } int get_current_channel(void) { return current_channel; } void set_current_channel(int channel) { current_channel = CLAMP(channel, 0, 64); } int get_current_row(void) { return current_row; } void set_current_row(int row) { int total_rows = song_get_rows_in_pattern(current_pattern); current_row = CLAMP(row, 0, total_rows); pattern_editor_reposition(); status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ void update_current_pattern(void) { char buf[4]; draw_text(str_from_num(3, current_pattern, buf), 12, 6, 5, 0); draw_text(str_from_num(3, csf_get_num_patterns(current_song) - 1, buf), 16, 6, 5, 0); } int get_current_pattern(void) { return current_pattern; } static void _pattern_update_magic(void) { song_sample_t *s; int i; for (i = 1; i <= 99; i++) { s = song_get_sample(i); if (!s) continue; if (((unsigned char)s->name[23]) != 0xFF) continue; if (((unsigned char)s->name[24]) != current_pattern) continue; disko_writeout_sample(i,current_pattern,1); break; } } void set_current_pattern(int n) { int total_rows; char undostr[64]; if (!playback_tracing || !SONG_PLAYING) { _pattern_update_magic(); } current_pattern = CLAMP(n, 0, 199); total_rows = song_get_rows_in_pattern(current_pattern); if (current_row > total_rows) current_row = total_rows; if (SELECTION_EXISTS) { if (selection.first_row > total_rows) { selection.first_row = selection.last_row = total_rows; } else if (selection.last_row > total_rows) { selection.last_row = total_rows; } } /* save pattern */ sprintf(undostr, "Pattern %d", current_pattern); pated_save(undostr); fast_save_update(); pattern_editor_reposition(); pattern_selection_system_copyout(); status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ static void set_playback_mark(void) { if (marked_pattern == current_pattern && marked_row == current_row) { marked_pattern = -1; } else { marked_pattern = current_pattern; marked_row = current_row; } } void play_song_from_mark_orderpan(void) { if (marked_pattern == -1) { song_start_at_order(get_current_order(), current_row); } else { song_start_at_pattern(marked_pattern, marked_row); } } void play_song_from_mark(void) { int new_order; if (marked_pattern != -1) { song_start_at_pattern(marked_pattern, marked_row); return; } new_order = get_current_order(); while (new_order < 255) { if (current_song->orderlist[new_order] == current_pattern) { set_current_order(new_order); song_start_at_order(new_order, current_row); return; } new_order++; } new_order = 0; while (new_order < 255) { if (current_song->orderlist[new_order] == current_pattern) { set_current_order(new_order); song_start_at_order(new_order, current_row); return; } new_order++; } song_start_at_pattern(current_pattern, current_row); } /* --------------------------------------------------------------------- */ static void recalculate_visible_area(void) { int n, last = 0, new_width; visible_width = 0; for (n = 0; n < 64; n++) { if (track_view_scheme[n] >= NUM_TRACK_VIEWS) { /* shouldn't happen, but might (e.g. if someone was messing with the config file) */ track_view_scheme[n] = last; } else { last = track_view_scheme[n]; } new_width = visible_width + track_views[track_view_scheme[n]].width; if (new_width > 72) break; visible_width = new_width; if (draw_divisions) visible_width++; } if (draw_divisions) { /* a division after the last channel would look pretty dopey :) */ visible_width--; } visible_channels = n; /* don't allow anything past channel 64 */ if (top_display_channel > 64 - visible_channels + 1) top_display_channel = 64 - visible_channels + 1; } static void set_view_scheme(int scheme) { if (scheme >= NUM_TRACK_VIEWS) { /* shouldn't happen */ log_appendf(4, "View scheme %d out of range -- using default scheme", scheme); scheme = 0; } memset(track_view_scheme, scheme, 64); recalculate_visible_area(); } /* --------------------------------------------------------------------- */ static void pattern_editor_redraw(void) { int chan, chan_pos, chan_drawpos = 5; int row, row_pos; char buf[4]; song_note_t *pattern, *note; const struct track_view *track_view; int total_rows; int fg, bg; int mc = (status.flags & INVERTED_PALETTE) ? 1 : 3; /* mask color */ int pattern_is_playing = ((song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP)) != 0 && current_pattern == playing_pattern); if (template_mode) { draw_text_len(template_mode_names[template_mode], 60, 2, 12, 3, 2); } /* draw the outer box around the whole thing */ draw_box(4, 14, 5 + visible_width, 47, BOX_THICK | BOX_INNER | BOX_INSET); /* how many rows are there? */ total_rows = song_get_pattern(current_pattern, &pattern); for (chan = top_display_channel, chan_pos = 0; chan_pos < visible_channels; chan++, chan_pos++) { track_view = track_views + track_view_scheme[chan_pos]; /* maybe i'm retarded but the pattern editor should be dealing with the same concept of "channel" as the rest of the interface. the mixing channels really could be any arbitrary number -- modplug just happens to reserve the first 64 for "real" channels. i'd rather pm not replicate this cruft and more or less hide the mixer from the interface... */ track_view->draw_channel_header(chan, chan_drawpos, 14, ((song_get_channel(chan - 1)->flags & CHN_MUTE) ? 0 : 3)); note = pattern + 64 * top_display_row + chan - 1; for (row = top_display_row, row_pos = 0; row_pos < 32 && row < total_rows; row++, row_pos++) { if (chan_pos == 0) { fg = pattern_is_playing && row == playing_row ? 3 : 0; bg = (current_pattern == marked_pattern && row == marked_row) ? 11 : 2; draw_text(str_from_num(3, row, buf), 1, 15 + row_pos, fg, bg); } if (is_in_selection(chan, row)) { fg = 3; bg = (ROW_IS_HIGHLIGHT(row) ? 9 : 8); } else { fg = ((status.flags & (CRAYOLA_MODE | CLASSIC_MODE)) == CRAYOLA_MODE) ? ((note->instrument + 3) % 4 + 3) : 6; if (highlight_current_row && row == current_row) bg = 1; else if (ROW_IS_MAJOR(row)) bg = 14; else if (ROW_IS_MINOR(row)) bg = 15; else bg = 0; } /* draw the cursor if on the current row, and: drawing the current channel, regardless of position OR: when the template is enabled, and the channel fits within the template size, AND shift is not being held down. (oh god it's lisp) */ int cpos; if ((row == current_row) && ((current_position > 0 || template_mode == TEMPLATE_OFF || (status.keymod & SCHISM_KEYMOD_SHIFT)) ? (chan == current_channel) : (chan >= current_channel && chan < (current_channel + (clipboard.data ? clipboard.channels : 1))))) { // yes! do write the cursor cpos = current_position; if (cpos == 6 && link_effect_column && !(status.flags & CLASSIC_MODE)) cpos = 9; // highlight full effect and value } else { cpos = -1; } track_view->draw_note(chan_drawpos, 15 + row_pos, note, cpos, fg, bg); if (draw_divisions && chan_pos < visible_channels - 1) { if (is_in_selection(chan, row)) bg = 0; draw_char(168, chan_drawpos + track_view->width, 15 + row_pos, 2, bg); } /* next row, same channel */ note += 64; } // hmm...? for (; row_pos < 32; row++, row_pos++) { if (ROW_IS_MAJOR(row)) bg = 14; else if (ROW_IS_MINOR(row)) bg = 15; else bg = 0; track_view->draw_note(chan_drawpos, 15 + row_pos, blank_note, -1, 6, bg); if (draw_divisions && chan_pos < visible_channels - 1) { draw_char(168, chan_drawpos + track_view->width, 15 + row_pos, 2, bg); } } if (chan == current_channel) { track_view->draw_mask(chan_drawpos, 47, edit_copy_mask, current_position, mc, 2); } /* blah */ if (channel_multi[chan - 1]) { if (track_view_scheme[chan_pos] == 0) { draw_char(172, chan_drawpos + 3, 47, mc, 2); } else if (track_view_scheme[chan_pos] < 3) { draw_char(172, chan_drawpos + 2, 47, mc, 2); } else if (track_view_scheme[chan_pos] == 3) { draw_char(172, chan_drawpos + 1, 47, mc, 2); } else if (current_position < 2) { draw_char(172, chan_drawpos, 47, mc, 2); } } chan_drawpos += track_view->width + !!draw_divisions; } status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ /* kill all humans */ static void transpose_notes(int amount) { int row, chan; song_note_t *pattern, *note; status.flags |= SONG_NEEDS_SAVE; song_get_pattern(current_pattern, &pattern); pated_history_add_grouped(((amount > 0) ? "Undo transposition up (Alt-Q)" : "Undo transposition down (Alt-A)" ), selection.first_channel - 1, selection.first_row, (selection.last_channel - selection.first_channel) + 1, (selection.last_row - selection.first_row) + 1); if (SELECTION_EXISTS) { for (row = selection.first_row; row <= selection.last_row; row++) { note = pattern + 64 * row + selection.first_channel - 1; for (chan = selection.first_channel; chan <= selection.last_channel; chan++) { if (note->note > 0 && note->note < 121) note->note = CLAMP(note->note + amount, 1, 120); note++; } } } else { note = pattern + 64 * current_row + current_channel - 1; if (note->note > 0 && note->note < 121) note->note = CLAMP(note->note + amount, 1, 120); } pattern_selection_system_copyout(); } /* --------------------------------------------------------------------- */ static void copy_note_to_mask(void) { int row = current_row, num_rows; song_note_t *pattern, *note; num_rows = song_get_pattern(current_pattern, &pattern); note = pattern + 64 * current_row + current_channel - 1; mask_note = *note; if (mask_copy_search_mode != COPY_INST_OFF) { while (!note->instrument && row > 0) { note -= 64; row--; } if (mask_copy_search_mode == COPY_INST_UP_THEN_DOWN && !note->instrument) { note = pattern + 64 * current_row + current_channel - 1; // Reset while (!note->instrument && row < num_rows) { note += 64; row++; } } } if (note->instrument) { if (song_is_instrument_mode()) instrument_set(note->instrument); else sample_set(note->instrument); } } /* --------------------------------------------------------------------- */ /* pos is either 0 or 1 (0 being the left digit, 1 being the right) * return: 1 (move cursor) or 0 (don't) * this is highly modplug specific :P */ static int handle_volume(song_note_t * note, struct key_event *k, int pos) { int vol = note->volparam; int fx = note->voleffect; int vp = panning_mode ? VOLFX_PANNING : VOLFX_VOLUME; int q; if (pos == 0) { q = kbd_char_to_hex(k); if (q >= 0 && q <= 9) { vol = q * 10 + vol % 10; fx = vp; } else if (k->sym == SCHISM_KEYSYM_a) { fx = VOLFX_FINEVOLUP; vol %= 10; } else if (k->sym == SCHISM_KEYSYM_b) { fx = VOLFX_FINEVOLDOWN; vol %= 10; } else if (k->sym == SCHISM_KEYSYM_c) { fx = VOLFX_VOLSLIDEUP; vol %= 10; } else if (k->sym == SCHISM_KEYSYM_d) { fx = VOLFX_VOLSLIDEDOWN; vol %= 10; } else if (k->sym == SCHISM_KEYSYM_e) { fx = VOLFX_PORTADOWN; vol %= 10; } else if (k->sym == SCHISM_KEYSYM_f) { fx = VOLFX_PORTAUP; vol %= 10; } else if (k->sym == SCHISM_KEYSYM_g) { fx = VOLFX_TONEPORTAMENTO; vol %= 10; } else if (k->sym == SCHISM_KEYSYM_h) { fx = VOLFX_VIBRATODEPTH; vol %= 10; } else if (status.flags & CLASSIC_MODE) { return 0; } else if (k->sym == SCHISM_KEYSYM_DOLLAR) { fx = VOLFX_VIBRATOSPEED; vol %= 10; } else if (k->sym == SCHISM_KEYSYM_LESS) { fx = VOLFX_PANSLIDELEFT; vol %= 10; } else if (k->sym == SCHISM_KEYSYM_GREATER) { fx = VOLFX_PANSLIDERIGHT; vol %= 10; } else { return 0; } } else { q = kbd_char_to_hex(k); if (q >= 0 && q <= 9) { vol = (vol / 10) * 10 + q; switch (fx) { case VOLFX_NONE: case VOLFX_VOLUME: case VOLFX_PANNING: fx = vp; } } else { return 0; } } note->voleffect = fx; if (fx == VOLFX_VOLUME || fx == VOLFX_PANNING) note->volparam = CLAMP(vol, 0, 64); else note->volparam = CLAMP(vol, 0, 9); return 1; } // return zero iff there is no value in the current cell at the current column static int seek_done(void) { song_note_t *pattern, *note; song_get_pattern(current_pattern, &pattern); note = pattern + 64 * current_row + current_channel - 1; switch (current_position) { case 0: case 1: return note->note != 0; case 2: case 3: return note->instrument != 0; case 4: case 5: return note->voleffect || note->volparam; case 6: case 7: case 8: // effect param columns intentionally check effect column instead return note->effect != 0; } return 1; // please stop seeking because something is probably wrong } #if 0 static int note_is_empty(song_note_t *p) { if (!p->note && p->voleffect == VOLFX_NONE && !p->effect && !p->param) return 1; return 0; } #endif // FIXME: why the 'row' param? should it be removed, or should the references to current_row be replaced? // fwiw, every call to this uses current_row. // return: zero if there was a template error, nonzero otherwise static int patedit_record_note(song_note_t *cur_note, int channel, SCHISM_UNUSED int row, int note, int force) { song_note_t *q; int i, r = 1, channels; status.flags |= SONG_NEEDS_SAVE; if (NOTE_IS_NOTE(note)) { if (template_mode) { q = clipboard.data; if (clipboard.channels < 1 || clipboard.rows < 1 || !clipboard.data) { dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); r = 0; } else if (!q->note) { widget_create_button(template_error_widgets+0,36,32,6,0,0,0,0,0, dialog_yes_NULL,"OK",3); dialog_create_custom(20, 23, 40, 12, template_error_widgets, 1, 0, template_error_draw, NULL); r = 0; } else { i = note - q->note; switch (template_mode) { case TEMPLATE_OVERWRITE: snap_paste(&clipboard, current_channel-1, current_row, i); break; case TEMPLATE_MIX_PATTERN_PRECEDENCE: clipboard_paste_mix_fields(0, i); break; case TEMPLATE_MIX_CLIPBOARD_PRECEDENCE: clipboard_paste_mix_fields(1, i); break; case TEMPLATE_NOTES_ONLY: clipboard_paste_mix_notes(1, i); break; }; } } else { cur_note->note = note; } } else { /* Note cut, etc. -- need to clear all masked fields. This will never cause a template error. Also, for one-row templates, replicate control notes across the width of the template. */ channels = (template_mode && clipboard.data != NULL && clipboard.rows == 1) ? clipboard.channels : 1; for (i = 0; i < channels && i + channel <= 64; i++) { /* I don't know what this whole 'force' thing is about, but okay */ if (!force && cur_note->note) continue; cur_note->note = note; if (edit_copy_mask & MASK_INSTRUMENT) { cur_note->instrument = 0; } if (edit_copy_mask & MASK_VOLUME) { cur_note->voleffect = 0; cur_note->volparam = 0; } if (edit_copy_mask & MASK_EFFECT) { cur_note->effect = 0; cur_note->param = 0; } cur_note++; } } pattern_selection_system_copyout(); return r; } static int pattern_editor_insert_midi(struct key_event *k) { song_note_t *pattern, *cur_note = NULL; int n, v = 0, pd, speed, tick, offset = 0; int r = current_row, c = current_channel, p = current_pattern; int quantize_next_row = 0; int ins = KEYJAZZ_NOINST, smp = KEYJAZZ_NOINST; int song_was_playing = SONG_PLAYING; if (song_is_instrument_mode()) { ins = instrument_get_current(); } else { smp = sample_get_current(); } status.flags |= SONG_NEEDS_SAVE; speed = song_get_current_speed(); tick = song_get_current_tick(); if (midi_start_record && !SONG_PLAYING) { switch (midi_start_record) { case 1: /* pattern loop */ song_loop_pattern(p, r); midi_playback_tracing = playback_tracing; playback_tracing = 1; break; case 2: /* song play */ song_start_at_pattern(p, r); midi_playback_tracing = playback_tracing; playback_tracing = 1; break; }; } // this is a long one if (midi_flags & MIDI_TICK_QUANTIZE // if quantize is on && song_was_playing // and the song was playing && playback_tracing // and we are following the song && tick > 0 && tick <= speed / 2 + 1) { // and the note is too late /* correct late notes to the next row */ /* tick + 1 because processing the keydown itself takes another tick */ offset++; quantize_next_row = 1; } song_get_pattern_offset(&p, &pattern, &r, offset); if (k->midi_note == -1) { /* nada */ } else if (k->state == KEY_RELEASE) { c = song_keyup(KEYJAZZ_NOINST, KEYJAZZ_NOINST, k->midi_note); if (c <= 0) { /* song_keyup didn't find find note off channel, abort */ return 0; } /* don't record noteoffs for no good reason... */ if (!((midi_flags & MIDI_RECORD_NOTEOFF) && (song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP)) && playback_tracing)) { return 0; } cur_note = pattern + 64 * r + (c-1); /* never "overwrite" a note off */ patedit_record_note(cur_note, c, r, NOTE_OFF, 0); } else { if (k->midi_volume > -1) { v = k->midi_volume / 2; } else { v = 0; } if (!((song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP)) && playback_tracing)) { tick = 0; } n = k->midi_note; if (!quantize_next_row) { c = song_keydown(smp, ins, n, v, c); } cur_note = pattern + 64 * r + (c-1); patedit_record_note(cur_note, c, r, n, 0); if (!template_mode) { cur_note->instrument = song_get_current_instrument(); if (midi_flags & MIDI_RECORD_VELOCITY) { cur_note->voleffect = VOLFX_VOLUME; cur_note->volparam = v; } tick %= speed; if (!(midi_flags & MIDI_TICK_QUANTIZE) && !cur_note->effect && tick != 0) { cur_note->effect = FX_SPECIAL; cur_note->param = 0xD0 | MIN(tick, 15); } } } if (!(midi_flags & MIDI_PITCHBEND) || midi_pitch_depth == 0 || k->midi_bend == 0) { if (k->state == KEY_RELEASE && k->midi_note > -1 && cur_note->instrument > 0) { song_keyrecord(cur_note->instrument, cur_note->instrument, cur_note->note, v, c+1, cur_note->effect, cur_note->param); pattern_selection_system_copyout(); } return -1; } /* pitch bend */ for (c = 0; c < 64; c++) { if ((channel_multi[c] & 1) && (channel_multi[c] & (~1))) { cur_note = pattern + 64 * r + c; if (cur_note->effect) { if (cur_note->effect != FX_PORTAMENTOUP && cur_note->effect != FX_PORTAMENTODOWN) { /* don't overwrite old effects */ continue; } pd = midi_last_bend_hit[c]; } else { pd = midi_last_bend_hit[c]; midi_last_bend_hit[c] = k->midi_bend; } pd = (((k->midi_bend - pd) * midi_pitch_depth / 8192) * speed) / 2; if (pd < -0x7F) pd = -0x7F; else if (pd > 0x7F) pd = 0x7F; if (pd < 0) { cur_note->effect = FX_PORTAMENTODOWN; /* Exx */ cur_note->param = -pd; } else if (pd > 0) { cur_note->effect = FX_PORTAMENTOUP; /* Fxx */ cur_note->param = pd; } if (k->midi_note == -1 || k->state == KEY_RELEASE) continue; if (cur_note->instrument < 1) continue; if (cur_note->voleffect == VOLFX_VOLUME) v = cur_note->volparam; else v = -1; song_keyrecord(cur_note->instrument, cur_note->instrument, cur_note->note, v, c+1, cur_note->effect, cur_note->param); } } pattern_selection_system_copyout(); return -1; } /* return 1 => handled key, 0 => no way */ static int pattern_editor_insert(struct key_event *k) { int ins, smp, j, n, vol; song_note_t *pattern, *cur_note; song_get_pattern(current_pattern, &pattern); /* keydown events are handled here for multichannel */ if (k->state == KEY_RELEASE && current_position) return 0; cur_note = pattern + 64 * current_row + current_channel - 1; switch (current_position) { case 0: /* note */ // FIXME: this is actually quite wrong; instrument numbers should be independent for each // channel and take effect when the instrument is played (e.g. with 4/8 or keyjazz input) // also, this is fully idiotic smp = ins = cur_note->instrument; if (song_is_instrument_mode()) { smp = KEYJAZZ_NOINST; } else { ins = KEYJAZZ_NOINST; } if (k->sym == SCHISM_KEYSYM_4) { if (k->state == KEY_RELEASE) return 0; if (cur_note->voleffect == VOLFX_VOLUME) { vol = cur_note->volparam; } else { vol = KEYJAZZ_DEFAULTVOL; } song_keyrecord(smp, ins, cur_note->note, vol, current_channel, cur_note->effect, cur_note->param); advance_cursor(!(k->mod & SCHISM_KEYMOD_SHIFT), 1); return 1; } else if (k->sym == SCHISM_KEYSYM_8) { /* note: Impulse Tracker doesn't skip multichannels when pressing "8" -delt. */ if (k->state == KEY_RELEASE) return 0; song_single_step(current_pattern, current_row); advance_cursor(!(k->mod & SCHISM_KEYMOD_SHIFT), 0); return 1; } if (song_is_instrument_mode()) { if (edit_copy_mask & MASK_INSTRUMENT) ins = instrument_get_current(); } else { if (edit_copy_mask & MASK_INSTRUMENT) smp = sample_get_current(); } if (k->sym == SCHISM_KEYSYM_SPACE) { /* copy mask to note */ n = mask_note.note; vol = ((edit_copy_mask & MASK_VOLUME) && cur_note->voleffect == VOLFX_VOLUME) ? mask_note.volparam : KEYJAZZ_DEFAULTVOL; } else { n = kbd_get_note(k); if (n < 0) return 0; if ((edit_copy_mask & MASK_VOLUME) && mask_note.voleffect == VOLFX_VOLUME) { vol = mask_note.volparam; } else if (cur_note->voleffect == VOLFX_VOLUME) { vol = cur_note->volparam; } else { vol = KEYJAZZ_DEFAULTVOL; } } if (k->state == KEY_RELEASE) { if (keyjazz_noteoff && NOTE_IS_NOTE(n)) { /* coda mode */ song_keyup(smp, ins, n); } /* it would be weird to have this enabled and keyjazz_noteoff * disabled, but it's possible, so handle it separately. */ if (keyjazz_write_noteoff && playback_tracing && NOTE_IS_NOTE(n)) { /* go to the next row if a note off would overwrite a note * you (likely) just entered */ if (cur_note->note) { if (++current_row > song_get_rows_in_pattern(current_pattern)) { return 1; } cur_note += 64; /* give up if the next row has a note too */ if (cur_note->note) { return 1; } } n = NOTE_OFF; } else { return 1; } } if (k->is_repeat && !keyjazz_repeat) return 1; int writenote = (keyjazz_capslock) ? !(k->mod & SCHISM_KEYMOD_CAPS) : !(k->mod & SCHISM_KEYMOD_CAPS_PRESSED); if (writenote && !patedit_record_note(cur_note, current_channel, current_row, n, 1)) { // there was a template error, don't advance the cursor and so on writenote = 0; n = NOTE_NONE; } /* Be quiet when pasting templates. It'd be nice to "play" a template when pasting it (maybe only for ones that are one row high) so as to hear the chords being inserted etc., but that's a little complicated to do. */ if (NOTE_IS_NOTE(n) && !(template_mode && writenote)) song_keydown(smp, ins, n, vol, current_channel); if (!writenote) break; /* Never copy the instrument etc. from the mask when inserting control notes or when erasing a note -- but DO write it when inserting a blank note with the space key. */ if (!(NOTE_IS_CONTROL(n) || (k->sym != SCHISM_KEYSYM_SPACE && n == NOTE_NONE)) && !template_mode) { if (edit_copy_mask & MASK_INSTRUMENT) { if (song_is_instrument_mode()) cur_note->instrument = instrument_get_current(); else cur_note->instrument = sample_get_current(); } if (edit_copy_mask & MASK_VOLUME) { cur_note->voleffect = mask_note.voleffect; cur_note->volparam = mask_note.volparam; } if (edit_copy_mask & MASK_EFFECT) { cur_note->effect = mask_note.effect; cur_note->param = mask_note.param; } } /* try again, now that we have the effect (this is a dumb way to do this...) */ if (NOTE_IS_NOTE(n) && !template_mode) song_keyrecord(smp, ins, n, vol, current_channel, cur_note->effect, cur_note->param); /* copy the note back to the mask */ mask_note.note = n; pattern_selection_system_copyout(); n = cur_note->note; if (NOTE_IS_NOTE(n) && cur_note->voleffect == VOLFX_VOLUME) vol = cur_note->volparam; if (k->mod & SCHISM_KEYMOD_SHIFT) { // advance horizontally, stopping at channel 64 // (I have no idea how IT does this, it might wrap) if (current_channel < 64) { shift_chord_channels++; current_channel++; pattern_editor_reposition(); } } else { advance_cursor(1, 1); } break; case 1: /* octave */ j = kbd_char_to_hex(k); if (j < 0 || j > 9) return 0; n = cur_note->note; if (n > 0 && n <= 120) { /* Hehe... this was originally 7 lines :) */ n = ((n - 1) % 12) + (12 * j) + 1; cur_note->note = n; } advance_cursor(1, 0); status.flags |= SONG_NEEDS_SAVE; pattern_selection_system_copyout(); break; case 2: /* instrument, first digit */ case 3: /* instrument, second digit */ if (k->sym == SCHISM_KEYSYM_SPACE) { if (song_is_instrument_mode()) n = instrument_get_current(); else n = sample_get_current(); if (n && !(status.flags & CLASSIC_MODE)) current_song->voices[current_channel - 1].last_instrument = n; cur_note->instrument = n; advance_cursor(1, 0); status.flags |= SONG_NEEDS_SAVE; break; } if (kbd_get_note(k) == 0) { cur_note->instrument = 0; if (song_is_instrument_mode()) instrument_set(0); else sample_set(0); advance_cursor(1, 0); status.flags |= SONG_NEEDS_SAVE; break; } if (current_position == 2) { j = kbd_char_to_99(k); if (j < 0) return 0; n = (j * 10) + (cur_note->instrument % 10); current_position++; } else { j = kbd_char_to_hex(k); if (j < 0 || j > 9) return 0; n = ((cur_note->instrument / 10) * 10) + j; current_position--; advance_cursor(1, 0); } /* this is kind of ugly... */ if (song_is_instrument_mode()) { j = instrument_get_current(); instrument_set(n); if (n != instrument_get_current()) { n = j; } instrument_set(j); } else { j = sample_get_current(); sample_set(n); if (n != sample_get_current()) { n = j; } sample_set(j); } if (n && !(status.flags & CLASSIC_MODE)) current_song->voices[current_channel - 1].last_instrument = n; cur_note->instrument = n; if (song_is_instrument_mode()) instrument_set(n); else sample_set(n); status.flags |= SONG_NEEDS_SAVE; pattern_selection_system_copyout(); break; case 4: case 5: /* volume */ if (k->sym == SCHISM_KEYSYM_SPACE) { cur_note->volparam = mask_note.volparam; cur_note->voleffect = mask_note.voleffect; advance_cursor(1, 0); status.flags |= SONG_NEEDS_SAVE; break; } if (kbd_get_note(k) == 0) { cur_note->volparam = mask_note.volparam = 0; cur_note->voleffect = mask_note.voleffect = VOLFX_NONE; advance_cursor(1, 0); status.flags |= SONG_NEEDS_SAVE; break; } if (k->scancode == SCHISM_SCANCODE_GRAVE) { panning_mode = !panning_mode; status_text_flash("%s control set", (panning_mode ? "Panning" : "Volume")); return 0; } if (!handle_volume(cur_note, k, current_position - 4)) return 0; mask_note.volparam = cur_note->volparam; mask_note.voleffect = cur_note->voleffect; if (current_position == 4) { current_position++; } else { current_position = 4; advance_cursor(1, 0); } status.flags |= SONG_NEEDS_SAVE; pattern_selection_system_copyout(); break; case 6: /* effect */ if (k->sym == SCHISM_KEYSYM_SPACE) { cur_note->effect = mask_note.effect; } else { n = kbd_get_effect_number(k); if (n < 0) return 0; cur_note->effect = mask_note.effect = n; } status.flags |= SONG_NEEDS_SAVE; if (link_effect_column) current_position++; else advance_cursor(1, 0); pattern_selection_system_copyout(); break; case 7: /* param, high nibble */ case 8: /* param, low nibble */ if (k->sym == SCHISM_KEYSYM_SPACE) { cur_note->param = mask_note.param; current_position = link_effect_column ? 6 : 7; advance_cursor(1, 0); status.flags |= SONG_NEEDS_SAVE; pattern_selection_system_copyout(); break; } else if (kbd_get_note(k) == 0) { cur_note->param = mask_note.param = 0; current_position = link_effect_column ? 6 : 7; advance_cursor(1, 0); status.flags |= SONG_NEEDS_SAVE; pattern_selection_system_copyout(); break; } /* FIXME: honey roasted peanuts */ n = kbd_char_to_hex(k); if (n < 0) return 0; if (current_position == 7) { cur_note->param = (n << 4) | (cur_note->param & 0xf); current_position++; } else /* current_position == 8 */ { cur_note->param = (cur_note->param & 0xf0) | n; current_position = link_effect_column ? 6 : 7; advance_cursor(1, 0); } status.flags |= SONG_NEEDS_SAVE; mask_note.param = cur_note->param; pattern_selection_system_copyout(); break; } return 1; } /* --------------------------------------------------------------------- */ /* return values: * 1 = handled key completely. don't do anything else * -1 = partly done, but need to recalculate cursor stuff * (for keys that move the cursor) * 0 = didn't handle the key. */ static int pattern_editor_handle_alt_key(struct key_event * k) { int n; int total_rows = song_get_rows_in_pattern(current_pattern); /* hack to render this useful :) */ if (k->sym == SCHISM_KEYSYM_KP_9) { k->sym = SCHISM_KEYSYM_F9; } else if (k->sym == SCHISM_KEYSYM_KP_0) { k->sym = SCHISM_KEYSYM_F10; } n = numeric_key_event(k, 0); if (n > -1 && n <= 9) { if (k->state == KEY_RELEASE) return 1; skip_value = (n == 9) ? 16 : n; status_text_flash("Cursor step set to %d", skip_value); return 1; } switch (k->sym) { case SCHISM_KEYSYM_RETURN: if (k->state == KEY_PRESS) return 1; fast_save_update(); return 1; case SCHISM_KEYSYM_BACKSPACE: if (k->state == KEY_PRESS) return 1; pated_save("Undo revert pattern data (Alt-BkSpace)"); snap_paste(&fast_save, 0, 0, 0); return 1; case SCHISM_KEYSYM_b: if (k->state == KEY_RELEASE) return 1; if (!SELECTION_EXISTS) { selection.last_channel = current_channel; selection.last_row = current_row; } selection.first_channel = current_channel; selection.first_row = current_row; normalise_block_selection(); break; case SCHISM_KEYSYM_e: if (k->state == KEY_RELEASE) return 1; if (!SELECTION_EXISTS) { selection.first_channel = current_channel; selection.first_row = current_row; } selection.last_channel = current_channel; selection.last_row = current_row; normalise_block_selection(); break; case SCHISM_KEYSYM_d: if (k->state == KEY_RELEASE) return 1; if (status.last_keysym == SCHISM_KEYSYM_d) { if (total_rows - (current_row - 1) > block_double_size) block_double_size <<= 1; } else { // emulate some weird impulse tracker behavior here: // with row highlight set to zero, alt-d selects the whole channel // if the cursor is at the top, and clears the selection otherwise block_double_size = current_song->row_highlight_major ? current_song->row_highlight_major : (current_row ? 0 : 65536); selection.first_channel = selection.last_channel = current_channel; selection.first_row = current_row; } n = block_double_size + current_row - 1; selection.last_row = MIN(n, total_rows); break; case SCHISM_KEYSYM_l: if (k->state == KEY_RELEASE) return 1; if (status.last_keysym == SCHISM_KEYSYM_l) { /* 3x alt-l re-selects the current channel */ if (selection.first_channel == selection.last_channel) { selection.first_channel = 1; selection.last_channel = 64; } else { selection.first_channel = selection.last_channel = current_channel; } } else { selection.first_channel = selection.last_channel = current_channel; selection.first_row = 0; selection.last_row = total_rows; } pattern_selection_system_copyout(); break; case SCHISM_KEYSYM_r: if (k->state == KEY_RELEASE) return 1; draw_divisions = 1; set_view_scheme(0); break; case SCHISM_KEYSYM_s: if (k->state == KEY_RELEASE) return 1; selection_set_sample(); break; case SCHISM_KEYSYM_u: if (k->state == KEY_RELEASE) return 1; if (SELECTION_EXISTS) { selection_clear(); } else if (clipboard.data) { clipboard_free(); clippy_select(NULL, NULL, 0); clippy_yank(); } else { dialog_create(DIALOG_OK, "No data in clipboard", NULL, NULL, 0, NULL); } break; case SCHISM_KEYSYM_c: if (k->state == KEY_RELEASE) return 1; clipboard_copy(0); break; case SCHISM_KEYSYM_o: if (k->state == KEY_RELEASE) return 1; if (status.last_keysym == SCHISM_KEYSYM_o) { clipboard_paste_overwrite(0, 1); } else { clipboard_paste_overwrite(0, 0); } break; case SCHISM_KEYSYM_p: if (k->state == KEY_RELEASE) return 1; clipboard_paste_insert(); break; case SCHISM_KEYSYM_m: if (k->state == KEY_RELEASE) return 1; if (status.last_keysym == SCHISM_KEYSYM_m) { clipboard_paste_mix_fields(0, 0); } else { clipboard_paste_mix_notes(0, 0); } break; case SCHISM_KEYSYM_f: if (k->state == KEY_RELEASE) return 1; block_length_double(); break; case SCHISM_KEYSYM_g: if (k->state == KEY_RELEASE) return 1; block_length_halve(); break; case SCHISM_KEYSYM_n: if (k->state == KEY_RELEASE) return 1; channel_multi[current_channel - 1] ^= 1; if (channel_multi[current_channel - 1]) { channel_multi_enabled = 1; } else { channel_multi_enabled = 0; for (n = 0; n < 64; n++) { if (channel_multi[n]) { channel_multi_enabled = 1; break; } } } if (status.last_keysym == SCHISM_KEYSYM_n) { pattern_editor_display_multichannel(); } break; case SCHISM_KEYSYM_z: if (k->state == KEY_RELEASE) return 1; clipboard_copy(0); selection_erase(); break; case SCHISM_KEYSYM_y: if (k->state == KEY_RELEASE) return 1; selection_swap(); break; case SCHISM_KEYSYM_v: if (k->state == KEY_RELEASE) return 1; selection_set_volume(); break; case SCHISM_KEYSYM_w: if (k->state == KEY_RELEASE) return 1; selection_wipe_volume(0); break; case SCHISM_KEYSYM_k: if (k->state == KEY_RELEASE) return 1; if (status.last_keysym == SCHISM_KEYSYM_k) { selection_wipe_volume(1); } else { selection_slide_volume(); } break; case SCHISM_KEYSYM_x: if (k->state == KEY_RELEASE) return 1; if (status.last_keysym == SCHISM_KEYSYM_x) { selection_wipe_effect(); } else { selection_slide_effect(); } break; case SCHISM_KEYSYM_h: if (k->state == KEY_RELEASE) return 1; draw_divisions = !draw_divisions; recalculate_visible_area(); pattern_editor_reposition(); break; case SCHISM_KEYSYM_q: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_SHIFT) transpose_notes(12); else transpose_notes(1); break; case SCHISM_KEYSYM_a: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_SHIFT) transpose_notes(-12); else transpose_notes(-1); break; case SCHISM_KEYSYM_i: if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_SHIFT) template_mode = TEMPLATE_OFF; else if (fast_volume_mode) fast_volume_amplify(); else template_mode = (template_mode + 1) % TEMPLATE_MODE_MAX; /* cycle */ break; case SCHISM_KEYSYM_j: if (k->state == KEY_RELEASE) return 1; if (fast_volume_mode) fast_volume_attenuate(); else volume_amplify(); break; case SCHISM_KEYSYM_t: if (k->state == KEY_RELEASE) return 1; n = current_channel - top_display_channel; track_view_scheme[n] = ((track_view_scheme[n] + 1) % NUM_TRACK_VIEWS); recalculate_visible_area(); pattern_editor_reposition(); break; case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 1; if (top_display_row > 0) { top_display_row--; if (current_row > top_display_row + 31) current_row = top_display_row + 31; return -1; } return 1; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 1; if (top_display_row + 31 < total_rows) { top_display_row++; if (current_row < top_display_row) current_row = top_display_row; return -1; } return 1; case SCHISM_KEYSYM_LEFT: if (k->state == KEY_RELEASE) return 1; current_channel--; return -1; case SCHISM_KEYSYM_RIGHT: if (k->state == KEY_RELEASE) return 1; current_channel++; return -1; case SCHISM_KEYSYM_INSERT: if (k->state == KEY_RELEASE) return 1; pated_save("Remove inserted row(s) (Alt-Insert)"); pattern_insert_rows(current_row, 1, 1, 64); break; case SCHISM_KEYSYM_DELETE: if (k->state == KEY_RELEASE) return 1; pated_save("Replace deleted row(s) (Alt-Delete)"); pattern_delete_rows(current_row, 1, 1, 64); break; case SCHISM_KEYSYM_F9: if (k->state == KEY_RELEASE) return 1; song_toggle_channel_mute(current_channel - 1); break; case SCHISM_KEYSYM_F10: if (k->state == KEY_RELEASE) return 1; song_handle_channel_solo(current_channel - 1); break; default: return 0; } status.flags |= NEED_UPDATE; return 1; } /* Two atoms are walking down the street, and one of them stops abruptly * and says, "Oh my God, I just lost an electron!" * The other one says, "Are you sure?" * The first one says, "Yes, I'm positive!" */ static int pattern_editor_handle_ctrl_key(struct key_event * k) { int n; int total_rows = song_get_rows_in_pattern(current_pattern); n = numeric_key_event(k, 0); if (n > -1) { if (n < 0 || n >= NUM_TRACK_VIEWS) return 0; if (k->state == KEY_RELEASE) return 1; if (k->mod & SCHISM_KEYMOD_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 SCHISM_KEYSYM_LEFT: if (k->state == KEY_RELEASE) return 1; if (current_channel > top_display_channel) current_channel--; return -1; case SCHISM_KEYSYM_RIGHT: if (k->state == KEY_RELEASE) return 1; if (current_channel < top_display_channel + visible_channels - 1) current_channel++; return -1; case SCHISM_KEYSYM_F6: if (k->state == KEY_RELEASE) return 1; song_loop_pattern(current_pattern, current_row); return 1; case SCHISM_KEYSYM_F7: if (k->state == KEY_RELEASE) return 1; set_playback_mark(); return -1; case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 1; set_previous_instrument(); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 1; set_next_instrument(); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return 1; current_row = 0; return -1; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return 1; current_row = total_rows; return -1; case SCHISM_KEYSYM_HOME: if (k->state == KEY_RELEASE) return 1; current_row--; return -1; case SCHISM_KEYSYM_END: if (k->state == KEY_RELEASE) return 1; current_row++; return -1; case SCHISM_KEYSYM_INSERT: if (k->state == KEY_RELEASE) return 1; selection_roll(ROLL_DOWN); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_DELETE: if (k->state == KEY_RELEASE) return 1; selection_roll(ROLL_UP); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_MINUS: if (k->state == KEY_RELEASE) return 1; if (song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP) && playback_tracing) return 1; prev_order_pattern(); return 1; case SCHISM_KEYSYM_EQUALS: if (!(k->mod & SCHISM_KEYMOD_SHIFT)) return 0; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_PLUS: if (k->state == KEY_RELEASE) return 1; if (song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP) && playback_tracing) return 1; next_order_pattern(); return 1; case SCHISM_KEYSYM_c: if (k->state == KEY_RELEASE) return 1; centralise_cursor = !centralise_cursor; status_text_flash("Centralise cursor %s", (centralise_cursor ? "enabled" : "disabled")); return -1; case SCHISM_KEYSYM_h: if (k->state == KEY_RELEASE) return 1; highlight_current_row = !highlight_current_row; status_text_flash("Row hilight %s", (highlight_current_row ? "enabled" : "disabled")); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_j: if (k->state == KEY_RELEASE) return 1; fast_volume_toggle(); return 1; case SCHISM_KEYSYM_u: if (k->state == KEY_RELEASE) return 1; if (fast_volume_mode) selection_vary(1, 100-fast_volume_percent, FX_CHANNELVOLUME); else vary_command(FX_CHANNELVOLUME); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_y: if (k->state == KEY_RELEASE) return 1; if (fast_volume_mode) selection_vary(1, 100-fast_volume_percent, FX_PANBRELLO); else vary_command(FX_PANBRELLO); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_k: if (k->state == KEY_RELEASE) return 1; if (fast_volume_mode) selection_vary(1, 100-fast_volume_percent, current_effect()); else vary_command(current_effect()); status.flags |= NEED_UPDATE; return 1; case SCHISM_KEYSYM_b: if (k->mod & SCHISM_KEYMOD_SHIFT) return 0; /* fall through */ case SCHISM_KEYSYM_o: if (k->state == KEY_RELEASE) return 1; song_pattern_to_sample(current_pattern, !!(k->mod & SCHISM_KEYMOD_SHIFT), !!(k->sym == SCHISM_KEYSYM_b)); return 1; case SCHISM_KEYSYM_v: if (k->state == KEY_RELEASE) return 1; show_default_volumes = !show_default_volumes; status_text_flash("Default volumes %s", (show_default_volumes ? "enabled" : "disabled")); return 1; case SCHISM_KEYSYM_x: case SCHISM_KEYSYM_z: if (k->state == KEY_RELEASE) return 1; midi_start_record++; if (midi_start_record > 2) midi_start_record = 0; switch (midi_start_record) { case 0: status_text_flash("No MIDI Trigger"); break; case 1: status_text_flash("Pattern MIDI Trigger"); break; case 2: status_text_flash("Song MIDI Trigger"); break; }; return 1; case SCHISM_KEYSYM_BACKSPACE: if (k->state == KEY_RELEASE) return 1; pattern_editor_display_history(); return 1; default: return 0; } return 0; } static int mute_toggle_hack[64]; /* mrsbrisby: please explain this one, i don't get why it's necessary... */ static int pattern_editor_handle_key_default(struct key_event * k) { /* bleah */ if (k->sym == SCHISM_KEYSYM_LESS || k->sym == SCHISM_KEYSYM_COLON || k->sym == SCHISM_KEYSYM_SEMICOLON) { if (k->state == KEY_RELEASE) return 0; if ((status.flags & CLASSIC_MODE) || current_position != 4) { set_previous_instrument(); status.flags |= NEED_UPDATE; return 1; } } else if (k->sym == SCHISM_KEYSYM_GREATER || k->sym == SCHISM_KEYSYM_QUOTE || k->sym == SCHISM_KEYSYM_QUOTEDBL) { if (k->state == KEY_RELEASE) return 0; if ((status.flags & CLASSIC_MODE) || current_position != 4) { set_next_instrument(); status.flags |= NEED_UPDATE; return 1; } } else if (k->sym == SCHISM_KEYSYM_COMMA) { if (k->state == KEY_RELEASE) return 0; switch (current_position) { case 2: case 3: edit_copy_mask ^= MASK_INSTRUMENT; break; case 4: case 5: edit_copy_mask ^= MASK_VOLUME; break; case 6: case 7: case 8: edit_copy_mask ^= MASK_EFFECT; break; } status.flags |= NEED_UPDATE; return 1; } if (song_get_mode() & (MODE_PLAYING|MODE_PATTERN_LOOP) && playback_tracing && k->is_repeat) return 0; if (!pattern_editor_insert(k)) return 0; return -1; } static int pattern_editor_handle_key(struct key_event * k) { int n, nx, v; int total_rows = song_get_rows_in_pattern(current_pattern); const struct track_view *track_view; int np, nr, nc; unsigned int basex; if (k->mouse != MOUSE_NONE) { if (k->state == KEY_RELEASE) { /* mouseup */ memset(mute_toggle_hack, 0, sizeof(mute_toggle_hack)); } if ((k->mouse == MOUSE_CLICK || k->mouse == MOUSE_DBLCLICK) && k->state == KEY_RELEASE) { shift_selection_end(); } if (k->y < 13 && !shift_selection.in_progress) return 0; if (k->y >= 15 && k->mouse != MOUSE_CLICK && k->mouse != MOUSE_DBLCLICK) { if (k->state == KEY_RELEASE) return 0; if (k->mouse == MOUSE_SCROLL_UP) { if (top_display_row > 0) { top_display_row = MAX(top_display_row - MOUSE_SCROLL_LINES, 0); if (current_row > top_display_row + 31) current_row = top_display_row + 31; if (current_row < 0) current_row = 0; return -1; } } else if (k->mouse == MOUSE_SCROLL_DOWN) { if (top_display_row + 31 < total_rows) { top_display_row = MIN(top_display_row + MOUSE_SCROLL_LINES, total_rows); if (current_row < top_display_row) current_row = top_display_row; return -1; } } return 1; } if (k->mouse != MOUSE_CLICK && k->mouse != MOUSE_DBLCLICK) return 1; basex = 5; if (current_row < 0) current_row = 0; if (current_row >= total_rows) current_row = total_rows; np = current_position; nc = current_channel; nr = current_row; for (n = top_display_channel, nx = 0; nx <= visible_channels; n++, nx++) { track_view = track_views+track_view_scheme[nx]; if (((n == top_display_channel && shift_selection.in_progress) || k->x >= basex) && ((n == visible_channels && shift_selection.in_progress) || k->x < basex + track_view->width)) { if (!shift_selection.in_progress && (k->y == 14 || k->y == 13)) { if (k->state == KEY_PRESS) { if (!mute_toggle_hack[n-1]) { song_toggle_channel_mute(n-1); status.flags |= NEED_UPDATE; mute_toggle_hack[n-1]=1; } } break; } nc = n; nr = (k->y - 15) + top_display_row; if (k->y < 15 && top_display_row > 0) { top_display_row--; } if (shift_selection.in_progress) break; v = k->x - basex; switch (track_view_scheme[nx]) { case 0: /* 5 channel view */ switch (v) { case 0: np = 0; break; case 2: np = 1; break; case 4: np = 2; break; case 5: np = 3; break; case 7: np = 4; break; case 8: np = 5; break; case 10: np = 6; break; case 11: np = 7; break; case 12: np = 8; break; }; break; case 1: /* 6/7 channels */ switch (v) { case 0: np = 0; break; case 2: np = 1; break; case 3: np = 2; break; case 4: np = 3; break; case 5: np = 4; break; case 6: np = 5; break; case 7: np = 6; break; case 8: np = 7; break; case 9: np = 8; break; }; break; case 2: /* 9/10 channels */ switch (v) { case 0: np = 0; break; case 2: np = 1; break; case 3: np = 2 + k->hx; break; case 4: np = 4 + k->hx; break; case 5: np = 6; break; case 6: np = 7 + k->hx; break; }; break; case 3: /* 18/24 channels */ switch (v) { case 0: np = 0; break; case 1: np = 1; break; case 2: np = 2 + k->hx; break; case 3: np = 4 + k->hx; break; case 4: np = 6; break; case 5: np = 7 + k->hx; break; }; break; case 4: /* now things get weird: 24/36 channels */ case 5: /* now things get weird: 36/64 channels */ case 6: /* no point doing anything here; reset */ np = 0; break; }; break; } basex += track_view->width; if (draw_divisions) basex++; } if (np == current_position && nc == current_channel && nr == current_row) { return 1; } if (nr >= total_rows) nr = total_rows; if (nr < 0) nr = 0; current_position = np; current_channel = nc; current_row = nr; if (k->state == KEY_PRESS && k->sy > 14) { if (!shift_selection.in_progress) { shift_selection_begin(); } else { shift_selection_update(); } } return -1; } if (k->midi_note > -1 || k->midi_bend != 0) { return pattern_editor_insert_midi(k); } switch (k->sym) { case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 0; if (skip_value) { if (current_row - skip_value >= 0) current_row -= skip_value; } else { current_row--; } return -1; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 0; if (skip_value) { if (current_row + skip_value <= total_rows) current_row += skip_value; } else { current_row++; } return -1; case SCHISM_KEYSYM_LEFT: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_SHIFT) { current_channel--; } else if (link_effect_column && current_position == 0 && current_channel > 1) { current_channel--; current_position = current_effect() ? 8 : 6; } else { current_position--; } return -1; case SCHISM_KEYSYM_RIGHT: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_SHIFT) { current_channel++; } else if (link_effect_column && current_position == 6 && current_channel < 64) { current_position = current_effect() ? 7 : 10; } else { current_position++; } return -1; case SCHISM_KEYSYM_TAB: if (k->state == KEY_RELEASE) return 0; if ((k->mod & SCHISM_KEYMOD_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 &= ~SCHISM_KEYMOD_SHIFT; shift_selection_end(); return -1; case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return 0; { int rh = current_song->row_highlight_major ? current_song->row_highlight_major : 16; if (current_row == total_rows) current_row -= (current_row % rh) ? (current_row % rh) : rh; else current_row -= rh; } return -1; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; current_row += current_song->row_highlight_major ? current_song->row_highlight_major : 16; return -1; case SCHISM_KEYSYM_HOME: if (k->state == KEY_RELEASE) return 0; if (current_position == 0) { if (invert_home_end ? (current_row != 0) : (current_channel == 1)) { current_row = 0; } else { current_channel = 1; } } else { current_position = 0; } return -1; case SCHISM_KEYSYM_END: if (k->state == KEY_RELEASE) return 0; n = song_find_last_channel(); if (current_position == 8) { if (invert_home_end ? (current_row != total_rows) : (current_channel == n)) { current_row = total_rows; } else { current_channel = n; } } else { current_position = 8; } return -1; case SCHISM_KEYSYM_INSERT: if (k->state == KEY_RELEASE) return 0; if (template_mode && clipboard.rows == 1) { n = clipboard.channels; if (n + current_channel > 64) { n = 64 - current_channel; } pattern_insert_rows(current_row, 1, current_channel, n); } else { pattern_insert_rows(current_row, 1, current_channel, 1); } break; case SCHISM_KEYSYM_DELETE: if (k->state == KEY_RELEASE) return 0; if (template_mode && clipboard.rows == 1) { n = clipboard.channels; if (n + current_channel > 64) { n = 64 - current_channel; } pattern_delete_rows(current_row, 1, current_channel, n); } else { pattern_delete_rows(current_row, 1, current_channel, 1); } break; case SCHISM_KEYSYM_MINUS: if (k->state == KEY_RELEASE) return 0; if (playback_tracing) { switch (song_get_mode()) { case MODE_PATTERN_LOOP: return 1; case MODE_PLAYING: song_set_current_order(song_get_current_order() - 1); return 1; default: break; }; } if (k->mod & SCHISM_KEYMOD_SHIFT) set_current_pattern(current_pattern - 4); else set_current_pattern(current_pattern - 1); return 1; case SCHISM_KEYSYM_EQUALS: if (!(k->mod & SCHISM_KEYMOD_SHIFT)) return 0; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_PLUS: if (k->state == KEY_RELEASE) return 0; if (playback_tracing) { switch (song_get_mode()) { case MODE_PATTERN_LOOP: return 1; case MODE_PLAYING: song_set_current_order(song_get_current_order() + 1); return 1; default: break; }; } if ((k->mod & SCHISM_KEYMOD_SHIFT) && k->sym == SCHISM_KEYSYM_KP_PLUS) set_current_pattern(current_pattern + 4); else set_current_pattern(current_pattern + 1); return 1; case SCHISM_KEYSYM_BACKSPACE: if (k->state == KEY_RELEASE) return 0; current_channel = multichannel_get_previous (current_channel); if (skip_value) current_row -= skip_value; else current_row--; return -1; case SCHISM_KEYSYM_RETURN: if (k->state == KEY_RELEASE) return 0; copy_note_to_mask(); if (template_mode != TEMPLATE_NOTES_ONLY) template_mode = TEMPLATE_OFF; return 1; case SCHISM_KEYSYM_l: if (k->mod & SCHISM_KEYMOD_SHIFT) { if (status.flags & CLASSIC_MODE) return 0; if (k->state == KEY_RELEASE) return 1; clipboard_copy(1); break; } return pattern_editor_handle_key_default(k); case SCHISM_KEYSYM_a: if (k->mod & SCHISM_KEYMOD_SHIFT && !(status.flags & CLASSIC_MODE)) { if (k->state == KEY_RELEASE) { return 0; } if (current_row == 0) { return 1; } do { current_row--; } while (!seek_done() && current_row != 0); return -1; } return pattern_editor_handle_key_default(k); case SCHISM_KEYSYM_f: if (k->mod & SCHISM_KEYMOD_SHIFT && !(status.flags & CLASSIC_MODE)) { if (k->state == KEY_RELEASE) { return 0; } if (current_row == total_rows) { return 1; } do { current_row++; } while (!seek_done() && current_row != total_rows); return -1; } return pattern_editor_handle_key_default(k); case SCHISM_KEYSYM_LSHIFT: case SCHISM_KEYSYM_RSHIFT: if (k->state == KEY_PRESS) { if (shift_selection.in_progress) shift_selection_end(); } else if (shift_chord_channels) { current_channel -= shift_chord_channels; while (current_channel < 1) current_channel += 64; advance_cursor(1, 1); shift_chord_channels = 0; } return 1; default: return pattern_editor_handle_key_default(k); } status.flags |= NEED_UPDATE; return 1; } /* --------------------------------------------------------------------- */ /* this function name's a bit confusing, but this is just what gets * called from the main key handler. * pattern_editor_handle_*_key above do the actual work. */ static int pattern_editor_handle_key_cb(struct key_event * k) { int ret; int total_rows = song_get_rows_in_pattern(current_pattern); if (k->mod & SCHISM_KEYMOD_SHIFT) { switch (k->sym) { case SCHISM_KEYSYM_LEFT: case SCHISM_KEYSYM_RIGHT: case SCHISM_KEYSYM_UP: case SCHISM_KEYSYM_DOWN: case SCHISM_KEYSYM_HOME: case SCHISM_KEYSYM_END: case SCHISM_KEYSYM_PAGEUP: case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; if (!shift_selection.in_progress) shift_selection_begin(); default: break; }; } if (k->mod & SCHISM_KEYMOD_ALT) ret = pattern_editor_handle_alt_key(k); else if (k->mod & SCHISM_KEYMOD_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 & SCHISM_KEYMOD_SHIFT) shift_selection_update(); status.flags |= NEED_UPDATE; return 1; } /* --------------------------------------------------------------------- */ static void pattern_editor_playback_update(void) { static int prev_row = -1; static int prev_pattern = -1; playing_row = song_get_current_row(); playing_pattern = song_get_playing_pattern(); if ((song_get_mode() & (MODE_PLAYING | MODE_PATTERN_LOOP)) != 0 && (playing_row != prev_row || playing_pattern != prev_pattern)) { prev_row = playing_row; prev_pattern = playing_pattern; if (playback_tracing) { set_current_order(song_get_current_order()); set_current_pattern(playing_pattern); current_row = playing_row; pattern_editor_reposition(); status.flags |= NEED_UPDATE; } else if (current_pattern == playing_pattern) { status.flags |= NEED_UPDATE; } } } static void pated_song_changed(void) { pated_history_clear(); // reset ctrl-f7 marked_pattern = -1; marked_row = 0; } /* --------------------------------------------------------------------- */ static int _fix_f7(struct key_event *k) { if (k->sym == SCHISM_KEYSYM_F7) { if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; play_song_from_mark(); return 1; } return 0; } void pattern_editor_load_page(struct page *page) { int i; for (i = 0; i < 10; i++) { memset(&undo_history[i],0,sizeof(struct pattern_snap)); undo_history[i].snap_op = "Empty"; undo_history[i].snap_op_allocated = 0; } page->title = "Pattern Editor (F2)"; page->playback_update = pattern_editor_playback_update; page->song_changed_cb = pated_song_changed; page->pre_handle_key = _fix_f7; page->total_widgets = 1; page->clipboard_paste = pattern_selection_system_paste; page->widgets = widgets_pattern; page->help_index = HELP_PATTERN_EDITOR; widget_create_other(widgets_pattern + 0, 0, pattern_editor_handle_key_cb, NULL, pattern_editor_redraw); } schismtracker-20250313/schism/page_preferences.c000066400000000000000000000425661476471630300215740ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "backend/audio.h" #include "it.h" #include "config.h" #include "charset.h" #include "song.h" #include "page.h" #include "osdefs.h" #include "widget.h" #include "vgamem.h" #include "mem.h" #include "disko.h" #define VOLUME_SCALE 31 /* this page will be the first against the wall when the revolution comes */ /* --------------------------------------------------------------------- */ /* statics */ static struct widget widgets_preferences[48]; static const char *interpolation_modes[] = { "Non-Interpolated", "Linear", "Cubic Spline", "8-Tap FIR Filter", NULL }; static const int interp_group[] = { 2,3,4,5,-1, }; static int ramp_group[] = { /* not const because it is modified */ -1,-1,-1, }; static int selected_audio_device = 0; static int top_audio_device = 0; static int selected_audio_driver = 0; static int top_audio_driver = 0; #define AUDIO_DEVICE_BOX_X 37 #define AUDIO_DEVICE_BOX_Y 16 #define AUDIO_DEVICE_BOX_WIDTH 41 #define AUDIO_DEVICE_BOX_HEIGHT 6 #define AUDIO_DEVICE_BOX_END_X (AUDIO_DEVICE_BOX_X+AUDIO_DEVICE_BOX_WIDTH-1) #define AUDIO_DEVICE_BOX_END_Y (AUDIO_DEVICE_BOX_Y+AUDIO_DEVICE_BOX_HEIGHT-1) #define AUDIO_DRIVER_BOX_X 37 #define AUDIO_DRIVER_BOX_Y 25 #define AUDIO_DRIVER_BOX_WIDTH 41 #define AUDIO_DRIVER_BOX_HEIGHT 6 #define AUDIO_DRIVER_BOX_END_X (AUDIO_DRIVER_BOX_X+AUDIO_DRIVER_BOX_WIDTH-1) #define AUDIO_DRIVER_BOX_END_Y (AUDIO_DRIVER_BOX_Y+AUDIO_DRIVER_BOX_HEIGHT-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); draw_text("Mixing Mode", 2, 18, 0, 2); draw_text("Available Audio Devices", AUDIO_DEVICE_BOX_X, AUDIO_DEVICE_BOX_Y - 2, 0, 2); draw_box(AUDIO_DEVICE_BOX_X - 1, AUDIO_DEVICE_BOX_Y - 1, AUDIO_DEVICE_BOX_END_X + 1, AUDIO_DEVICE_BOX_END_Y + 1, BOX_THICK | BOX_INNER | BOX_INSET); draw_text("Available Audio Drivers", AUDIO_DRIVER_BOX_X, AUDIO_DRIVER_BOX_Y - 2, 0, 2); draw_box(AUDIO_DRIVER_BOX_X - 1, AUDIO_DRIVER_BOX_Y - 1, AUDIO_DRIVER_BOX_END_X + 1, AUDIO_DRIVER_BOX_END_Y + 1, BOX_THICK | BOX_INNER | BOX_INSET); for (i = 0; interpolation_modes[i]; i++); draw_text("Output Equalizer", 2, 21+i*3, 0, 2); draw_text( "Low Frequency Band", 7, 23+i*3, 0, 2); draw_text( "Med Low Frequency Band", 3, 24+i*3, 0, 2); draw_text("Med High Frequency Band", 2, 25+i*3, 0, 2); draw_text( "High Frequency Band", 6, 26+i*3, 0, 2); draw_text("Ramp volume at start of sample",2,29+i*3,0,2); draw_box(25, 22+i*3, 47, 27+i*3, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(52, 22+i*3, 74, 27+i*3, BOX_THIN | BOX_INNER | BOX_INSET); snprintf(buf, 80, "Playback Frequency: %dHz", audio_settings.sample_rate); draw_text(buf, 2, 48, 0, 2); #define CORNER_BOTTOM "https://schismtracker.org/" draw_text(CORNER_BOTTOM, 78 - sizeof(CORNER_BOTTOM) + 1, 48, 1, 2); } /* --------------------------------------------------------------------- */ static void preferences_set_page(void) { int i, j; widgets_preferences[0].d.thumbbar.value = audio_settings.master.left; widgets_preferences[1].d.thumbbar.value = audio_settings.master.right; for (i = j = 0; interpolation_modes[i]; i++) { if (i == audio_settings.interpolation_mode) { widgets_preferences[i + 2].d.togglebutton.state=1; j = 1; } else { widgets_preferences[i + 2].d.togglebutton.state=0; } } if (!j) { audio_settings.interpolation_mode = 0; widgets_preferences[2].d.togglebutton.state=1; } for (j = 0; j < 4; j++) { widgets_preferences[i+2+(j*2)].d.thumbbar.value = audio_settings.eq_freq[j]; widgets_preferences[i+3+(j*2)].d.thumbbar.value = audio_settings.eq_gain[j]; } widgets_preferences[i+10].d.togglebutton.state = audio_settings.no_ramping?0:1; widgets_preferences[i+11].d.togglebutton.state = audio_settings.no_ramping?1:0; } /* --------------------------------------------------------------------- */ static void change_volume(void) { audio_settings.master.left = widgets_preferences[0].d.thumbbar.value; audio_settings.master.right = widgets_preferences[1].d.thumbbar.value; } #define SAVED_AT_EXIT "Audio configuration will be saved at exit" static void change_eq(void) { int i,j; for (i = 0; interpolation_modes[i]; i++); for (j = 0; j < 4; j++) { audio_settings.eq_freq[j] = widgets_preferences[i+2+(j*2)].d.thumbbar.value; audio_settings.eq_gain[j] = widgets_preferences[i+3+(j*2)].d.thumbbar.value; } song_init_eq(1, current_song->mix_frequency); } static void change_mixer(void) { int i; for (i = 0; interpolation_modes[i]; i++) { if (widgets_preferences[2+i].d.togglebutton.state) { audio_settings.interpolation_mode = i; } } audio_settings.no_ramping = widgets_preferences[i+11].d.togglebutton.state; song_init_modplug(); status_text_flash(SAVED_AT_EXIT); } /* --------------------------------------------------------------------- */ static void audio_device_list_draw(void) { int interp_modes; for (interp_modes = 0; interpolation_modes[interp_modes]; interp_modes++); int n, o = 0, focused = (ACTIVE_PAGE.selected_widget == 13 + interp_modes); int fg, bg; draw_fill_chars(AUDIO_DEVICE_BOX_X, AUDIO_DEVICE_BOX_Y, AUDIO_DEVICE_BOX_END_X, AUDIO_DEVICE_BOX_END_Y, DEFAULT_FG, 0); /* this macro expects the device name to be in UTF-8 */ #define DRAW_DEVICE(id, name) \ do { \ if ((o + top_audio_device) == selected_audio_device) { \ if (focused) { \ fg = 0; \ bg = 3; \ } else { \ fg = 6; \ bg = 14; \ } \ } else { \ fg = 6; \ bg = 0; \ }\ \ draw_text_utf8_len((id == song_audio_device_id()) ? "*" : " ", 1, AUDIO_DEVICE_BOX_X, AUDIO_DEVICE_BOX_Y + o, fg, bg); \ draw_text_utf8_len(name, AUDIO_DEVICE_BOX_WIDTH - 1, AUDIO_DEVICE_BOX_X + 1, AUDIO_DEVICE_BOX_Y + o, fg, bg); \ o++; \ } while (0) if (top_audio_device < 1) DRAW_DEVICE(AUDIO_BACKEND_DEFAULT, "default"); for (n = MAX(0, top_audio_device - 1); (size_t)n < audio_device_list_size && o < AUDIO_DEVICE_BOX_HEIGHT; n++) DRAW_DEVICE(audio_device_list[n].id, audio_device_list[n].name); #undef DRAW_DEVICE } static int audio_device_list_handle_key_on_list(struct key_event * k) { int new_device = selected_audio_device; int load_selected_device = 0; static const int focus_offsets[] = {1, 1, 2, 2, 2, 3}; switch (k->mouse) { case MOUSE_DBLCLICK: case MOUSE_CLICK: if (k->state == KEY_PRESS) return 0; if (k->x < AUDIO_DEVICE_BOX_X || k->y < AUDIO_DEVICE_BOX_Y || k->y > AUDIO_DEVICE_BOX_END_Y || k->x > AUDIO_DEVICE_BOX_END_X) return 0; new_device = top_audio_device + (k->y - AUDIO_DEVICE_BOX_Y); if (k->mouse == MOUSE_DBLCLICK || new_device == selected_audio_device) load_selected_device = 1; break; case MOUSE_SCROLL_UP: new_device -= MOUSE_SCROLL_LINES; break; case MOUSE_SCROLL_DOWN: new_device += MOUSE_SCROLL_LINES; break; default: if (k->state == KEY_RELEASE) return 0; } switch (k->sym) { case SCHISM_KEYSYM_UP: if (!NO_MODIFIER(k->mod)) return 0; if (--new_device < 0) { widget_change_focus_to(47); return 1; } break; case SCHISM_KEYSYM_DOWN: if (!NO_MODIFIER(k->mod)) return 0; if (++new_device >= (int)audio_device_list_size + 1) { //widget_change_focus_to(49); return 1; } break; case SCHISM_KEYSYM_HOME: if (!NO_MODIFIER(k->mod)) return 0; new_device = 0; break; case SCHISM_KEYSYM_PAGEUP: if (!NO_MODIFIER(k->mod)) return 0; if (new_device == 0) return 1; new_device -= 16; break; case SCHISM_KEYSYM_END: if (!NO_MODIFIER(k->mod)) return 0; new_device = audio_device_list_size; break; case SCHISM_KEYSYM_PAGEDOWN: if (!NO_MODIFIER(k->mod)) return 0; new_device += 16; break; case SCHISM_KEYSYM_RETURN: if (!NO_MODIFIER(k->mod)) return 0; load_selected_device = 1; break; case SCHISM_KEYSYM_TAB: if (!(k->mod & SCHISM_KEYMOD_SHIFT || NO_MODIFIER(k->mod))) return 0; widget_change_focus_to(focus_offsets[selected_audio_device]); return 1; case SCHISM_KEYSYM_LEFT: case SCHISM_KEYSYM_RIGHT: if (!NO_MODIFIER(k->mod)) return 0; widget_change_focus_to(focus_offsets[selected_audio_device]); return 1; default: if (k->mouse == MOUSE_NONE) return 0; } new_device = CLAMP(new_device, 0, (int)audio_device_list_size); if (new_device != selected_audio_device) { selected_audio_device = new_device; status.flags |= NEED_UPDATE; /* these HAVE to be done separately (and not as a CLAMP) because they aren't * really guaranteed to be ranges */ top_audio_device = MIN(top_audio_device, selected_audio_device); top_audio_device = MAX(top_audio_device, selected_audio_device - AUDIO_DEVICE_BOX_HEIGHT + 1); top_audio_device = MIN(top_audio_device, audio_device_list_size - AUDIO_DEVICE_BOX_HEIGHT + 1); top_audio_device = MAX(top_audio_device, 0); } if (load_selected_device) { uint32_t id = !selected_audio_device ? AUDIO_BACKEND_DEFAULT : audio_device_list[selected_audio_device - 1].id; audio_reinit(&id); } return 1; } /* --------------------------------------------------------------------- */ static void audio_driver_list_draw(void) { int interp_modes; for (interp_modes = 0; interpolation_modes[interp_modes]; interp_modes++); const int num_drivers = audio_driver_count(); int n, o = 0, focused = (ACTIVE_PAGE.selected_widget == 14 + interp_modes); int fg, bg; const char* current_audio_driver = song_audio_driver(); draw_fill_chars(AUDIO_DRIVER_BOX_X, AUDIO_DRIVER_BOX_Y, AUDIO_DRIVER_BOX_END_X, AUDIO_DRIVER_BOX_END_Y, DEFAULT_FG, 0); for (n = top_audio_driver; n < num_drivers && o < AUDIO_DRIVER_BOX_HEIGHT; n++) { const char* name = audio_driver_name(n); if ((o + top_audio_driver) == selected_audio_driver) { if (focused) { fg = 0; bg = 3; } else { fg = 6; bg = 14; } } else { fg = 6; bg = 0; } draw_text_utf8_len(!strcmp(current_audio_driver, name) ? "*" : " ", 1, AUDIO_DRIVER_BOX_X, AUDIO_DRIVER_BOX_Y + o, fg, bg); draw_text_utf8_len(name, AUDIO_DRIVER_BOX_WIDTH - 1, AUDIO_DRIVER_BOX_X + 1, AUDIO_DRIVER_BOX_Y + o, fg, bg); o++; } } static int audio_driver_list_handle_key_on_list(struct key_event * k) { int new_driver = selected_audio_driver; int load_selected_driver = 0; static const int focus_offsets[] = {4, 4, 4, 5, 5, 5}; const int num_drivers = audio_driver_count(); switch (k->mouse) { case MOUSE_DBLCLICK: case MOUSE_CLICK: if (k->state == KEY_PRESS) return 0; if (k->x < AUDIO_DRIVER_BOX_X || k->y < AUDIO_DRIVER_BOX_Y || k->y > AUDIO_DRIVER_BOX_END_Y || k->x > AUDIO_DRIVER_BOX_END_X) return 0; new_driver = top_audio_driver + (k->y - AUDIO_DRIVER_BOX_Y); if (k->mouse == MOUSE_DBLCLICK || new_driver == selected_audio_driver) load_selected_driver = 1; break; case MOUSE_SCROLL_UP: new_driver -= MOUSE_SCROLL_LINES; break; case MOUSE_SCROLL_DOWN: new_driver += MOUSE_SCROLL_LINES; break; default: if (k->state == KEY_RELEASE) return 0; } switch (k->sym) { case SCHISM_KEYSYM_UP: if (!NO_MODIFIER(k->mod)) return 0; if (--new_driver < 0) { widget_change_focus_to(47); return 1; } break; case SCHISM_KEYSYM_DOWN: if (!NO_MODIFIER(k->mod)) return 0; if (++new_driver >= num_drivers + 1) { //widget_change_focus_to(49); return 1; } break; case SCHISM_KEYSYM_HOME: if (!NO_MODIFIER(k->mod)) return 0; new_driver = 0; break; case SCHISM_KEYSYM_PAGEUP: if (!NO_MODIFIER(k->mod)) return 0; if (new_driver == 0) return 1; new_driver -= 16; break; case SCHISM_KEYSYM_END: if (!NO_MODIFIER(k->mod)) return 0; new_driver = num_drivers; break; case SCHISM_KEYSYM_PAGEDOWN: if (!NO_MODIFIER(k->mod)) return 0; new_driver += 16; break; case SCHISM_KEYSYM_RETURN: if (!NO_MODIFIER(k->mod)) return 0; load_selected_driver = 1; break; case SCHISM_KEYSYM_TAB: if (!(k->mod & SCHISM_KEYMOD_SHIFT || NO_MODIFIER(k->mod))) return 0; widget_change_focus_to(focus_offsets[selected_audio_driver]); return 1; case SCHISM_KEYSYM_LEFT: case SCHISM_KEYSYM_RIGHT: if (!NO_MODIFIER(k->mod)) return 0; widget_change_focus_to(focus_offsets[selected_audio_driver]); return 1; default: if (k->mouse == MOUSE_NONE) return 0; } new_driver = CLAMP(new_driver, 0, num_drivers - 1); if (new_driver != selected_audio_driver) { selected_audio_driver = new_driver; status.flags |= NEED_UPDATE; /* these HAVE to be done separately (and not as a CLAMP) because they aren't * really guaranteed to be ranges */ top_audio_driver = MIN(top_audio_driver, selected_audio_driver); top_audio_driver = MAX(top_audio_driver, selected_audio_driver - AUDIO_DRIVER_BOX_HEIGHT + 1); top_audio_driver = MIN(top_audio_driver, num_drivers - AUDIO_DRIVER_BOX_HEIGHT + 1); top_audio_driver = MAX(top_audio_driver, 0); } if (load_selected_driver) { audio_flash_reinitialized_text(audio_init(audio_driver_name(selected_audio_driver), NULL)); status.flags |= NEED_UPDATE; } return 1; } /* --------------------------------------------------------------------- */ static void save_config_now(SCHISM_UNUSED void *ign) { /* TODO */ cfg_midipage_save(); /* what is this doing here? */ cfg_atexit_save(); cfg_save_output(); status_text_flash("Configuration saved"); } void preferences_load_page(struct page *page) { char buf[64]; char *ptr; int i, j; int interp_modes; for (interp_modes = 0; interpolation_modes[interp_modes]; interp_modes++); page->title = "Preferences (Shift-F5)"; page->draw_const = preferences_draw_const; page->set_page = preferences_set_page; page->total_widgets = 15 + interp_modes; page->widgets = widgets_preferences; page->help_index = HELP_GLOBAL; widget_create_thumbbar(widgets_preferences + 0, 22, 14, 5, 0, 1, 1, change_volume, 0, VOLUME_SCALE); widget_create_thumbbar(widgets_preferences + 1, 22, 15, 5, 0, 2, 2, change_volume, 0, VOLUME_SCALE); widgets_preferences[0].next.left = widgets_preferences[0].next.right = widgets_preferences[0].next.tab = widgets_preferences[0].next.backtab = widgets_preferences[1].next.left = widgets_preferences[1].next.right = widgets_preferences[1].next.tab = widgets_preferences[1].next.backtab = interp_modes + 13; for (i = 0; interpolation_modes[i]; i++) { sprintf(buf, "%d Bit, %s", audio_settings.bits, interpolation_modes[i]); ptr = str_dup(buf); widget_create_togglebutton(widgets_preferences+i+2, 6, 20 + (i * 3), 26, i+1, i+3, i+2, interp_modes+11, i+3, change_mixer, ptr, 2, interp_group); if (i < 1) widgets_preferences[i+2].next.left = widgets_preferences[i+2].next.right = interp_modes + 13; else if (i < 4) widgets_preferences[i+2].next.left = widgets_preferences[i+2].next.right = interp_modes + 14; } for (j = 0; j < 4; j++) { int n = i+(j*2); if (j == 0) n = i+1; widget_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--; widget_create_thumbbar(widgets_preferences+i+3+(j*2), 53, 23+(i*3)+j, 21, i+(j*2)+1, n, i+(j*2)+4, change_eq, 0, 127); } /* default EQ setting */ widgets_preferences[i+2].d.thumbbar.value = 0; widgets_preferences[i+4].d.thumbbar.value = 16; widgets_preferences[i+6].d.thumbbar.value = 96; widgets_preferences[i+8].d.thumbbar.value = 127; ramp_group[0] = i+10; ramp_group[1] = i+11; widget_create_togglebutton(widgets_preferences+i+10, 33,29+i*3,9, i+9,i+12,i+10,i+11,i+11, change_mixer, "Enabled",2, ramp_group); widget_create_togglebutton(widgets_preferences+i+11, 46,29+i*3,9, i+9,i+12,i+10,i+13,i+13, change_mixer, "Disabled",1, ramp_group); widget_create_button(widgets_preferences+i+12, 2, 44, 27, i+10, i+12, i+12, i+13, i+13, (void (*)(void)) save_config_now, "Save Output Configuration", 2); widget_create_other(widgets_preferences+i+13, 0, audio_device_list_handle_key_on_list, NULL, audio_device_list_draw); widgets_preferences[i+13].x = AUDIO_DEVICE_BOX_X; widgets_preferences[i+13].y = AUDIO_DEVICE_BOX_Y; widgets_preferences[i+13].width = AUDIO_DEVICE_BOX_WIDTH; widgets_preferences[i+13].height = AUDIO_DEVICE_BOX_HEIGHT; widget_create_other(widgets_preferences+i+14, 0, audio_driver_list_handle_key_on_list, NULL, audio_driver_list_draw); widgets_preferences[i+14].x = AUDIO_DRIVER_BOX_X; widgets_preferences[i+14].y = AUDIO_DRIVER_BOX_Y; widgets_preferences[i+14].width = AUDIO_DRIVER_BOX_WIDTH; widgets_preferences[i+14].height = AUDIO_DRIVER_BOX_HEIGHT; } schismtracker-20250313/schism/page_samples.c000066400000000000000000001646351476471630300207410ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "config.h" #include "dialog.h" #include "dmoz.h" #include "fmt.h" #include "it.h" #include "keyboard.h" #include "page.h" #include "sample-edit.h" #include "song.h" #include "vgamem.h" #include "widget.h" #include "osdefs.h" #include "mem.h" #include "str.h" /* --------------------------------------------------------------------- */ /* static in my attic */ static struct vgamem_overlay sample_image = { 55,26,76,29, NULL, 0, 0, 0, }; static int dialog_f1_hack = 0; static struct widget widgets_samplelist[20]; static const int vibrato_waveforms[] = { 15, 16, 17, 18, -1 }; static int top_sample = 1; static int current_sample = 1; static int _altswap_lastvis = 99; // for alt-down sample-swapping static int sample_list_cursor_pos = 25; /* the "play" text */ static void sample_adlibconfig_dialog(SCHISM_UNUSED void *ign); /* shared by all the numentry widgets */ static int sample_numentry_cursor_pos = 0; /* for the loops */ static const char *const loop_states[] = { "Off", "On Forwards", "On Ping Pong", NULL }; /* playback */ static int last_note = NOTE_MIDC; static int num_save_formats = 0; /* --------------------------------------------------------------------- */ /* woo */ static int _is_magic_sample(int no) { song_sample_t *sample; int pn; sample = song_get_sample(no); if (sample && ((unsigned char) sample->name[23]) == 0xFF) { pn = (sample->name[24]); if (pn < 200) return 1; } return 0; } static void _fix_accept_text(void) { if (_is_magic_sample(current_sample)) { widgets_samplelist[0].accept_text = (sample_list_cursor_pos == 23 ? 0 : 1); } else { widgets_samplelist[0].accept_text = (sample_list_cursor_pos == 25 ? 0 : 1); } } static int _last_vis_sample(void) { int i, j, n; n = 99; j = 0; /* 65 is first visible sample on last page */ for (i = 65; i < MAX_SAMPLES; i++) { if (!csf_sample_is_empty(current_song->samples + i)) { j = i; } } while ((j + 34) > n) n += 34; if (n >= MAX_SAMPLES) n = MAX_SAMPLES - 1; return n; } /* --------------------------------------------------------------------- */ static void sample_list_reposition(void) { if (current_sample < top_sample) { top_sample = current_sample; if (top_sample < 1) top_sample = 1; } else if (current_sample > top_sample + 34) { top_sample = current_sample - 34; } if (dialog_f1_hack && status.current_page == PAGE_SAMPLE_LIST && status.previous_page == PAGE_HELP) { sample_adlibconfig_dialog(NULL); } dialog_f1_hack = 0; } /* --------------------------------------------------------------------- */ int sample_get_current(void) { return current_sample; } void sample_set(int n) { int new_sample = n; if (status.current_page == PAGE_SAMPLE_LIST) new_sample = CLAMP(n, 1, _last_vis_sample()); else new_sample = CLAMP(n, 0, _last_vis_sample()); if (current_sample == new_sample) return; current_sample = new_sample; sample_list_reposition(); /* update_current_instrument(); */ if (status.current_page == PAGE_SAMPLE_LIST) status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ /* draw the actual list */ static void sample_list_draw_list(void) { int pos, n, nl, pn; song_sample_t *sample; int has_data, is_selected; char buf[64]; int ss, cl = 0, cr = 0; int is_playing[MAX_SAMPLES]; ss = -1; song_get_playing_samples(is_playing); /* list */ for (pos = 0, n = top_sample; pos < 35; pos++, n++) { sample = song_get_sample(n); is_selected = (n == current_sample); has_data = (sample->data != NULL); if (sample->played) draw_char(is_playing[n] > 1 ? 183 : 173, 1, 13 + pos, is_playing[n] ? 3 : 1, 2); draw_text(str_from_num99(n, buf), 2, 13 + pos, (sample->flags & CHN_MUTE) ? 1 : 0, 2); // wow, this is entirely horrible pn = ((unsigned char)sample->name[24]); if (((unsigned char)sample->name[23]) == 0xFF && pn < 200) { nl = 23; draw_text(str_from_num(3, (int)pn, buf), 32, 13 + pos, 0, 2); draw_char('P', 28, 13+pos, 3, 2); draw_char('a', 29, 13+pos, 3, 2); draw_char('t', 30, 13+pos, 3, 2); draw_char('.', 31, 13+pos, 3, 2); } else { nl = 25; draw_char(168, 30, 13 + pos, 2, (is_selected ? 14 : 0)); draw_text("Play", 31, 13 + pos, (has_data ? 6 : 7), (is_selected ? 14 : 0)); } draw_text_len(sample->name, nl, 5, 13 + pos, 6, (is_selected ? 14 : 0)); if (ss == n) { draw_text_len(sample->name + cl, (cr-cl)+1, 5 + cl, 13 + pos, 3, 8); } } /* cursor */ if (ACTIVE_PAGE.selected_widget == 0) { pos = current_sample - top_sample; sample = song_get_sample(current_sample); has_data = (sample->data != NULL); if (pos < 0 || pos > 34) { /* err... */ } else if (sample_list_cursor_pos == 25) { draw_text("Play", 31, 13 + pos, 0, (has_data ? 3 : 6)); } else { draw_char(((sample_list_cursor_pos > (signed) strlen(sample->name)) ? 0 : sample->name[sample_list_cursor_pos]), sample_list_cursor_pos + 5, 13 + pos, 0, 3); } } status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ static void sample_list_predraw_hook(void) { char buf[16]; song_sample_t *sample; int has_data; sample = song_get_sample(current_sample); has_data = (sample->data != NULL); /* set all the values to the current sample */ /* default volume modplug hack here: sample volume has 4x the resolution... can't deal with this in song.cc (easily) without changing the actual volume of the sample. */ widgets_samplelist[1].d.thumbbar.value = sample->volume / 4; /* global volume */ widgets_samplelist[2].d.thumbbar.value = sample->global_volume; widgets_samplelist[2].d.thumbbar.text_at_min = (sample->flags & CHN_MUTE) ? " Muted " : NULL; /* default pan (another modplug hack) */ widgets_samplelist[3].d.toggle.state = (sample->flags & CHN_PANNING); widgets_samplelist[4].d.thumbbar.value = sample->panning / 4; widgets_samplelist[5].d.thumbbar.value = sample->vib_speed; widgets_samplelist[6].d.thumbbar.value = sample->vib_depth; widgets_samplelist[7].d.textentry.text = sample->filename; widgets_samplelist[8].d.numentry.value = sample->c5speed; widgets_samplelist[9].d.menutoggle.state = (sample->flags & CHN_LOOP ? (sample->flags & CHN_PINGPONGLOOP ? 2 : 1) : 0); widgets_samplelist[10].d.numentry.value = sample->loop_start; widgets_samplelist[11].d.numentry.value = sample->loop_end; widgets_samplelist[12].d.menutoggle.state = (sample->flags & CHN_SUSTAINLOOP ? (sample->flags & CHN_PINGPONGSUSTAIN ? 2 : 1) : 0); widgets_samplelist[13].d.numentry.value = sample->sustain_start; widgets_samplelist[14].d.numentry.value = sample->sustain_end; switch (sample->vib_type) { case VIB_SINE: widget_togglebutton_set(widgets_samplelist, 15, 0); break; case VIB_RAMP_DOWN: widget_togglebutton_set(widgets_samplelist, 16, 0); break; case VIB_SQUARE: widget_togglebutton_set(widgets_samplelist, 17, 0); break; case VIB_RANDOM: widget_togglebutton_set(widgets_samplelist, 18, 0); break; } widgets_samplelist[19].d.thumbbar.value = sample->vib_rate; if (has_data) { sprintf(buf, "%d bit%s", (sample->flags & CHN_16BIT) ? 16 : 8, (sample->flags & CHN_STEREO) ? " Stereo" : ""); } else { strcpy(buf, "No sample"); } draw_text_len(buf, 13, 64, 22, 2, 0); draw_text_len(str_from_num(0, sample->length, buf), 13, 64, 23, 2, 0); draw_sample_data(&sample_image, sample); } /* --------------------------------------------------------------------- */ static int sample_list_add_char(uint8_t c) { song_sample_t *smp; if (c < 32) return 0; smp = song_get_sample(current_sample); text_add_char(smp->name, c, &sample_list_cursor_pos, _is_magic_sample(current_sample) ? 22 : 25); _fix_accept_text(); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; return 1; } static void sample_list_delete_char(void) { song_sample_t *smp = song_get_sample(current_sample); text_delete_char(smp->name, &sample_list_cursor_pos, _is_magic_sample(current_sample) ? 23 : 25); _fix_accept_text(); status.flags |= SONG_NEEDS_SAVE; status.flags |= NEED_UPDATE; } static void sample_list_delete_next_char(void) { song_sample_t *smp = song_get_sample(current_sample); text_delete_next_char(smp->name, &sample_list_cursor_pos, _is_magic_sample(current_sample) ? 23 : 25); _fix_accept_text(); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; } static void clear_sample_text(void) { song_sample_t *smp = song_get_sample(current_sample); memset(smp->filename, 0, 14); if (_is_magic_sample(current_sample)) { memset(smp->name, 0, 24); } else { memset(smp->name, 0, 26); } sample_list_cursor_pos = 0; _fix_accept_text(); status.flags |= NEED_UPDATE; status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ static void do_swap_sample(int n) { if (n >= 1 && n <= _last_vis_sample()) { song_swap_samples(current_sample, n); } } static void do_exchange_sample(int n) { if (n >= 1 && n <= _last_vis_sample()) { song_exchange_samples(current_sample, n); } } static void do_copy_sample(int n) { if (n >= 1 && n <= _last_vis_sample()) { song_copy_sample(current_sample, song_get_sample(n)); sample_host_dialog(-1); } status.flags |= SONG_NEEDS_SAVE; } static void do_replace_sample(int n) { if (n >= 1 && n <= _last_vis_sample()) { song_replace_sample(current_sample, n); } status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ static int sample_list_handle_text_input_on_list(const char *text) { int success = 0; for (; *text; text++) if (sample_list_cursor_pos < 25 && sample_list_add_char(*text)) success = 1; return success; } static int sample_list_handle_key_on_list(struct key_event * k) { int new_sample = current_sample; int new_cursor_pos = sample_list_cursor_pos; if (k->mouse == MOUSE_CLICK && k->mouse_button == MOUSE_BUTTON_MIDDLE) { if (k->state == KEY_RELEASE) status.flags |= CLIPPY_PASTE_SELECTION; return 1; } else if (k->state == KEY_PRESS && k->mouse != MOUSE_NONE && k->x >= 5 && k->y >= 13 && k->y <= 47 && k->x <= 34) { if (k->mouse == MOUSE_SCROLL_UP) { top_sample -= MOUSE_SCROLL_LINES; if (top_sample < 1) top_sample = 1; status.flags |= NEED_UPDATE; return 1; } else if (k->mouse == MOUSE_SCROLL_DOWN) { top_sample += MOUSE_SCROLL_LINES; if (top_sample > (_last_vis_sample()-34)) top_sample = (_last_vis_sample()-34); status.flags |= NEED_UPDATE; return 1; } else { new_sample = (k->y - 13) + top_sample; new_cursor_pos = k->x - 5; if (k->x <= 29) { /* and button1 */ if (k->mouse == MOUSE_DBLCLICK) { /* this doesn't appear to work */ set_page(PAGE_LOAD_SAMPLE); status.flags |= NEED_UPDATE; return 1; } else { } #if 0 /* buggy and annoying, could be implemented properly but I don't care enough */ } else if (k->state == KEY_RELEASE || k->x == k->sx) { if (k->mouse == MOUSE_DBLCLICK || (new_sample == current_sample && sample_list_cursor_pos == 25)) { song_keydown(current_sample, KEYJAZZ_NOINST, last_note, 64, KEYJAZZ_CHAN_CURRENT); } new_cursor_pos = 25; #endif } } } else { switch (k->sym) { case SCHISM_KEYSYM_LEFT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_cursor_pos--; break; case SCHISM_KEYSYM_RIGHT: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_cursor_pos++; break; case SCHISM_KEYSYM_HOME: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_cursor_pos = 0; break; case SCHISM_KEYSYM_END: if (k->state == KEY_RELEASE) return 0; if (!NO_MODIFIER(k->mod)) return 0; new_cursor_pos = 25; break; case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_ALT) { if (current_sample > 1) { new_sample = current_sample - 1; song_swap_samples(current_sample, new_sample); } } else if (!NO_MODIFIER(k->mod)) { return 0; } else { new_sample--; } break; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_ALT) { // restrict position to the "old" value of _last_vis_sample() // (this is entirely for aesthetic reasons) if (status.last_keysym != SCHISM_KEYSYM_DOWN && !k->is_repeat) _altswap_lastvis = _last_vis_sample(); if (current_sample < _altswap_lastvis) { new_sample = current_sample + 1; song_swap_samples(current_sample, new_sample); } } else if (!NO_MODIFIER(k->mod)) { return 0; } else { new_sample++; } break; case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { new_sample = 1; } else { new_sample -= 16; } break; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return 0; if (k->mod & SCHISM_KEYMOD_CTRL) { new_sample = _last_vis_sample(); } else { new_sample += 16; } break; case SCHISM_KEYSYM_RETURN: if (k->state == KEY_PRESS) return 0; set_page(PAGE_LOAD_SAMPLE); break; case SCHISM_KEYSYM_BACKSPACE: if (k->state == KEY_RELEASE) return 0; if ((k->mod & (SCHISM_KEYMOD_CTRL | SCHISM_KEYMOD_ALT)) == 0) { if (sample_list_cursor_pos < 25) { sample_list_delete_char(); } return 1; } else if (k->mod & SCHISM_KEYMOD_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 SCHISM_KEYSYM_DELETE: if (k->state == KEY_RELEASE) return 0; if ((k->mod & (SCHISM_KEYMOD_CTRL | SCHISM_KEYMOD_ALT)) == 0) { if (sample_list_cursor_pos < 25) { sample_list_delete_next_char(); } return 1; } return 0; case SCHISM_KEYSYM_ESCAPE: if (k->mod & SCHISM_KEYMOD_SHIFT) { if (k->state == KEY_RELEASE) return 1; new_cursor_pos = 25; break; } return 0; default: if (k->mod & SCHISM_KEYMOD_ALT) { if (k->sym == SCHISM_KEYSYM_c) { clear_sample_text(); return 1; } return 0; } else if ((k->mod & SCHISM_KEYMOD_CTRL) == 0 && sample_list_cursor_pos < 25) { if (k->state == KEY_RELEASE) return 1; if (k->text) return sample_list_handle_text_input_on_list(k->text); /* ...uhhhhhh */ return 0; } return 0; } } new_sample = CLAMP(new_sample, 1, _last_vis_sample()); new_cursor_pos = CLAMP(new_cursor_pos, 0, 25); if (new_sample != current_sample) { sample_set(new_sample); sample_list_reposition(); } if (new_cursor_pos != sample_list_cursor_pos) { sample_list_cursor_pos = new_cursor_pos; _fix_accept_text(); } status.flags |= NEED_UPDATE; return 1; } /* --------------------------------------------------------------------- */ /* alt key dialog callbacks. * these don't need to do any actual redrawing, because the screen gets * redrawn anyway when the dialog is cleared. */ static void do_sign_convert(SCHISM_UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); sample_sign_convert(sample); } static void do_quality_convert(SCHISM_UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); sample_toggle_quality(sample, 1); } static void do_quality_toggle(SCHISM_UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); if (sample->flags & CHN_STEREO) status_text_flash("Can't toggle quality for stereo samples"); else sample_toggle_quality(sample, 0); } static void do_delete_sample(SCHISM_UNUSED void *data) { song_clear_sample(current_sample); status.flags |= SONG_NEEDS_SAVE; } static void do_downmix(SCHISM_UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); sample_downmix(sample); } static void do_post_loop_cut(SCHISM_UNUSED void *bweh) /* I'm already using 'data'. */ { song_sample_t *sample = song_get_sample(current_sample); unsigned long pos = ((sample->flags & CHN_SUSTAINLOOP) ? MAX(sample->loop_end, sample->sustain_end) : sample->loop_end); if (pos == 0 || pos >= sample->length) return; status.flags |= SONG_NEEDS_SAVE; song_lock_audio(); csf_stop_sample(current_song, sample); if (sample->loop_end > pos) sample->loop_end = pos; if (sample->sustain_end > pos) sample->sustain_end = pos; sample->length = pos; csf_adjust_sample_loop(sample); song_unlock_audio(); } static void do_pre_loop_cut(SCHISM_UNUSED void *bweh) { song_sample_t *sample = song_get_sample(current_sample); uint32_t pos = ((sample->flags & CHN_SUSTAINLOOP) ? MIN(sample->loop_start, sample->sustain_start) : sample->loop_start); uint32_t start_byte = pos * ((sample->flags & CHN_16BIT) ? 2 : 1) * ((sample->flags & CHN_STEREO) ? 2 : 1); uint32_t bytes = (sample->length - pos) * ((sample->flags & CHN_16BIT) ? 2 : 1) * ((sample->flags & CHN_STEREO) ? 2 : 1); if (pos == 0 || pos > sample->length) return; status.flags |= SONG_NEEDS_SAVE; song_lock_audio(); csf_stop_sample(current_song, sample); memmove(sample->data, sample->data + start_byte, bytes); sample->length -= pos; if (sample->loop_start > pos) sample->loop_start -= pos; else sample->loop_start = 0; if (sample->sustain_start > pos) sample->sustain_start -= pos; else sample->sustain_start = 0; if (sample->loop_end > pos) sample->loop_end -= pos; else sample->loop_end = 0; if (sample->sustain_end > pos) sample->sustain_end -= pos; else sample->sustain_end = 0; csf_adjust_sample_loop(sample); song_unlock_audio(); } static void do_centralise(SCHISM_UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); sample_centralise(sample); } /* --------------------------------------------------------------------- */ static struct widget sample_amplify_widgets[3]; static void do_amplify(SCHISM_UNUSED void *data) { sample_amplify(song_get_sample(current_sample), sample_amplify_widgets[0].d.thumbbar.value); } static void sample_amplify_draw_const(void) { draw_text("Sample Amplification %", 29, 27, 0, 2); draw_box(12, 29, 64, 31, BOX_THIN | BOX_INNER | BOX_INSET); } static void sample_amplify_dialog(void) { struct dialog *dialog; int percent = sample_get_amplify_amount(song_get_sample(current_sample)); percent = MIN(percent, 400); widget_create_thumbbar(sample_amplify_widgets + 0, 13, 30, 51, 0, 1, 1, NULL, 0, 400); sample_amplify_widgets[0].d.thumbbar.value = percent; widget_create_button(sample_amplify_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); widget_create_button(sample_amplify_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); dialog = dialog_create_custom(9, 25, 61, 11, sample_amplify_widgets, 3, 0, sample_amplify_draw_const, NULL); dialog->action_yes = do_amplify; } /* --------------------------------------------------------------------- */ static struct widget txtsynth_widgets[3]; static char txtsynth_entry[65536]; static void do_txtsynth(SCHISM_UNUSED void *data) { int len = strlen(txtsynth_entry); if (!len) return; song_sample_t *sample = song_get_sample(current_sample); if (sample->data) csf_free_sample(sample->data); sample->data = csf_allocate_sample(len); memcpy(sample->data, txtsynth_entry, len); sample->length = len; sample->loop_start = 0; sample->loop_end = len; sample->sustain_start = sample->sustain_end = 0; sample->flags |= CHN_LOOP; sample->flags &= ~(CHN_PINGPONGLOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN | CHN_16BIT | CHN_STEREO | CHN_ADLIB); csf_adjust_sample_loop(sample); sample_host_dialog(-1); status.flags |= SONG_NEEDS_SAVE; } static void txtsynth_draw_const(void) { draw_text("Enter a text string (e.g. ABCDCB for a triangle-wave)", 13, 27, 0, 2); draw_box(12, 29, 66, 31, BOX_THIN | BOX_INNER | BOX_INSET); } static void txtsynth_dialog(void) { struct dialog *dialog; // TODO copy the current sample into the entry? txtsynth_entry[0] = 0; widget_create_textentry(txtsynth_widgets + 0, 13, 30, 53, 0, 1, 1, NULL, txtsynth_entry, 65535); widget_create_button(txtsynth_widgets + 1, 31, 33, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); widget_create_button(txtsynth_widgets + 2, 41, 33, 6, 0, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); dialog = dialog_create_custom(9, 25, 61, 11, txtsynth_widgets, 3, 0, txtsynth_draw_const, NULL); dialog->action_yes = do_txtsynth; } /* --------------------------------------------------------------------- */ static struct widget sample_adlibconfig_widgets[28]; static int adlib_xpos[] = {26, 30, 58, 62, 39}; static int adlib_cursorpos[] = {0, 0, 0, 0, 0}; static const char *yn_toggle[3] = {"n", "y", NULL}; /* N - number, B - boolean (toggle) */ typedef enum adlibconfig_wtypes {N, B} adlibconfig_wtypes; static const struct { int xref, y; adlibconfig_wtypes type; int byteno, firstbit, nbits; } adlibconfig_widgets[] = { {4, 3, B,10, 0, 1 }, // add. synth {4, 4, N,10, 1, 3 }, // mod. feedback {0, 7, N, 5, 4, 4 }, // carrier attack {0, 8, N, 5, 0, 4 }, // carrier decay {0, 9, N, 7, 4,-4 }, // carrier sustain (0=maximum, 15=minimum) {0, 10, N, 7, 0, 4 }, // carrier release {0, 11, B, 1, 5, 1 }, // carrier sustain flag {0, 12, N, 3, 0,-6 }, // carrier volume (0=maximum, 63=minimum) {1, 7, N, 4, 4, 4 }, // modulator attack {1, 8, N, 4, 0, 4 }, // modulator decay {1, 9, N, 6, 4,-4 }, // modulator sustain (0=maximum, 15=minimum) {1, 10, N, 6, 0, 4 }, // modulator release {1, 11, B, 0, 5, 1 }, // modulator sustain flag {1, 12, N, 2, 0,-6 }, // modulator volume (0=maximum, 63=minimum) {2, 7, B, 1, 4, 1 }, // carrier scale envelope flag {2, 8, N, 3, 6, 2 }, // carrier level scaling (This is actually reversed bits...) {2, 9, N, 1, 0, 4 }, // carrier frequency multiplier {2, 10, N, 9, 0, 3 }, // carrier wave select {2, 11, B, 1, 6, 1 }, // carrier pitch vibrato {2, 12, B, 1, 7, 1 }, // carrier volume vibrato {3, 7, B, 0, 4, 1 }, // modulator scale envelope flag {3, 8, N, 2, 6, 2 }, // modulator level scaling (This is actually reversed bits...) {3, 9, N, 0, 0, 4 }, // modulator frequency multiplier {3, 10, N, 8, 0, 3 }, // modulator wave select {3, 11, B, 0, 6, 1 }, // modulator pitch vibrato {3, 12, B, 0, 7, 1 }, // modulator volume vibrato }; static void do_adlibconfig(SCHISM_UNUSED void *data) { //page->help_index = HELP_SAMPLE_LIST; song_sample_t *sample = song_get_sample(current_sample); if (sample->data) csf_free_sample(sample->data); // dumb hackaround that ought to some day be fixed: sample->data = csf_allocate_sample(1); sample->length = 1; if (!(sample->flags & CHN_ADLIB)) { sample->flags |= CHN_ADLIB; status_text_flash("Created adlib sample"); } sample->flags &= ~(CHN_16BIT | CHN_STEREO | CHN_LOOP | CHN_PINGPONGLOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN); sample->loop_start = sample->loop_end = 0; sample->sustain_start = sample->sustain_end = 0; if (!sample->c5speed) { sample->c5speed = 8363; sample->volume = 64 * 4; sample->global_volume = 64; } sample_host_dialog(-1); status.flags |= SONG_NEEDS_SAVE; } static void adlibconfig_refresh(void) { size_t a; song_sample_t *sample = song_get_sample(current_sample); draw_sample_data(&sample_image, sample); for (a = 0; a < ARRAY_SIZE(adlibconfig_widgets); a++) { unsigned int srcvalue = 0; unsigned int maskvalue = 0xFFFF; unsigned int nbits_real = (adlibconfig_widgets[a].nbits < 0 ? -adlibconfig_widgets[a].nbits : adlibconfig_widgets[a].nbits); unsigned int maxvalue = (1 << nbits_real) - 1; switch (adlibconfig_widgets[a].type) { case B: srcvalue = sample_adlibconfig_widgets[a].d.toggle.state; break; case N: srcvalue = sample_adlibconfig_widgets[a].d.numentry.value; break; } if(adlibconfig_widgets[a].nbits < 0) srcvalue = maxvalue - srcvalue; // reverse the semantics srcvalue &= maxvalue; srcvalue <<= adlibconfig_widgets[a].firstbit; maskvalue &= maxvalue; maskvalue <<= adlibconfig_widgets[a].firstbit; sample->adlib_bytes[adlibconfig_widgets[a].byteno] = (sample->adlib_bytes[adlibconfig_widgets[a].byteno] &~ maskvalue) | srcvalue; } } static void sample_adlibconfig_draw_const(void) { struct { int x, y; const char *label; } labels[] = { {19, 1, "Adlib Melodic Instrument Parameters"}, {19, 3, "Additive Synthesis:"}, {18, 4, "Modulation Feedback:"}, {26, 6, "Car Mod"}, {19, 7, "Attack"}, {20, 8, "Decay"}, {18, 9, "Sustain"}, {18, 10, "Release"}, {12, 11, "Sustain Sound"}, {19, 12, "Volume"}, {58, 6, "Car Mod"}, {43, 7, "Scale Envelope"}, {44, 8, "Level Scaling"}, {37, 9, "Frequency Multiplier"}, {46, 10, "Wave Select"}, {44, 11, "Pitch Vibrato"}, {43, 12, "Volume Vibrato"}, }; size_t a; // 39 33 draw_box(38, 2 + 30, 40, 5 + 30, BOX_THIN | BOX_INNER | BOX_INSET); draw_fill_chars(25, 6 + 30, 32,13 + 30, DEFAULT_FG, 0); draw_box(25, 6 + 30, 28, 13 + 30, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(29, 6 + 30, 32, 13 + 30, BOX_THIN | BOX_INNER | BOX_INSET); draw_fill_chars(57, 6 + 30, 64,13 + 30, DEFAULT_FG, 0); draw_box(57, 6 + 30, 60, 13 + 30, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(61, 6 + 30, 64, 13 + 30, BOX_THIN | BOX_INNER | BOX_INSET); for (a = 0; a < ARRAY_SIZE(labels); a++) draw_text(labels[a].label, labels[a].x, labels[a].y + 30, a ? 0 : 3, 2); } static int do_adlib_handlekey(struct key_event *kk) { if (kk->sym == SCHISM_KEYSYM_F1) { if (kk->state == KEY_PRESS) return 1; status.current_help_index = HELP_ADLIB_SAMPLE; dialog_f1_hack = 1; dialog_destroy_all(); set_page(PAGE_HELP); return 1; } return 0; } static void sample_adlibconfig_dialog(SCHISM_UNUSED void *ign) { struct dialog *dialog; song_sample_t *sample = song_get_sample(current_sample); size_t a; //page->help_index = HELP_ADLIB_SAMPLES; // Eh, what page? Where am I supposed to get a reference to page? // How do I make this work? -Bisqwit for (a = 0; a < ARRAY_SIZE(adlibconfig_widgets); a++) { unsigned int srcvalue = sample->adlib_bytes[adlibconfig_widgets[a].byteno]; unsigned int nbits_real = adlibconfig_widgets[a].nbits < 0 ? -adlibconfig_widgets[a].nbits : adlibconfig_widgets[a].nbits; unsigned int minvalue = 0, maxvalue = (1 << nbits_real) - 1; srcvalue >>= adlibconfig_widgets[a].firstbit; srcvalue &= maxvalue; if (adlibconfig_widgets[a].nbits < 0) srcvalue = maxvalue - srcvalue; // reverse the semantics switch (adlibconfig_widgets[a].type) { case B: widget_create_menutoggle(sample_adlibconfig_widgets + a, adlib_xpos[adlibconfig_widgets[a].xref], adlibconfig_widgets[a].y + 30, a > 0 ? a - 1 : 0, a + 1 < ARRAY_SIZE(adlibconfig_widgets) ? a + 1 : a, a, a, (a > 1 ? ((a + 4) % (ARRAY_SIZE(adlibconfig_widgets) - 2)) + 2 : 2), adlibconfig_refresh, yn_toggle); sample_adlibconfig_widgets[a].d.menutoggle.state = srcvalue; sample_adlibconfig_widgets[a].d.menutoggle.activation_keys = "ny"; break; case N: widget_create_numentry(sample_adlibconfig_widgets + a, adlib_xpos[adlibconfig_widgets[a].xref], adlibconfig_widgets[a].y + 30, nbits_real < 4 ? 1 : 2, a > 0 ? a - 1 : 0, a + 1 < ARRAY_SIZE(adlibconfig_widgets) ? a + 1 : a, (a > 1 ? ((a + 4) % (ARRAY_SIZE(adlibconfig_widgets) - 2)) + 2 : 2), adlibconfig_refresh, minvalue, maxvalue, adlib_cursorpos + adlibconfig_widgets[a].xref); sample_adlibconfig_widgets[a].d.numentry.value = srcvalue; break; } } dialog = dialog_create_custom(9, 30, 61, 15, sample_adlibconfig_widgets, ARRAY_SIZE(adlibconfig_widgets), 0, sample_adlibconfig_draw_const, NULL); dialog->action_yes = do_adlibconfig; dialog->handle_key = do_adlib_handlekey; } static void sample_adlibpatch_finish(int n) { song_sample_t *sample; if (n <= 0 || n > 128) return; sample = song_get_sample(current_sample); adlib_patch_apply((song_sample_t *) sample, n - 1); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; // redraw the sample sample_host_dialog(-1); } static void sample_adlibpatch_dialog(SCHISM_UNUSED void *ign) { numprompt_create("Enter Patch (1-128)", sample_adlibpatch_finish, 0); } /* --------------------------------------------------------------------- */ /* filename can be NULL, in which case the sample filename is used (quick save) */ struct sample_save_data { char *path; /* char *options? */ const char *format; }; static void save_sample_free_data(void *ptr) { struct sample_save_data *data = (struct sample_save_data *) ptr; if (data->path) free(data->path); free(data); } static void do_save_sample(void *ptr) { struct sample_save_data *data = (struct sample_save_data *) ptr; // I guess this function doesn't need to care about the return value, // since song_save_sample is handling all the visual feedback... song_save_sample(data->path, data->format, song_get_sample(current_sample), current_sample); save_sample_free_data(ptr); } static void sample_save(const char *filename, const char *format) { song_sample_t *sample = song_get_sample(current_sample); char *ptr, *q; struct sample_save_data *data; struct stat buf; int tmp; if (!*(filename ? filename : sample->filename)) { status_text_flash("Sample NOT saved! (No Filename?)"); return; } if (os_stat(cfg_dir_samples, &buf) == -1) { status_text_flash("Sample directory \"%s\" unreachable", filename); return; } tmp=0; data = mem_alloc(sizeof(struct sample_save_data)); if (!S_ISDIR(buf.st_mode)) { /* directory browsing */ q = strrchr(cfg_dir_samples, DIR_SEPARATOR); if (q) { tmp = q[1]; q[1] = '\0'; } } else { q = NULL; } ptr = dmoz_path_concat(cfg_dir_samples, filename ? filename : sample->filename); if (q) q[1] = tmp; data->path = ptr; data->format = format; if (filename && *filename && os_stat(ptr, &buf) == 0) { if (S_ISREG(buf.st_mode)) { dialog_create(DIALOG_OK_CANCEL, "Overwrite file?", do_save_sample, save_sample_free_data, 1, data); /* callback will free it */ } else if (S_ISDIR(buf.st_mode)) { status_text_flash("%s is a directory", filename); save_sample_free_data(data); } else { status_text_flash("%s is not a regular file", filename); save_sample_free_data(data); } } else { do_save_sample(data); } } /* export sample dialog */ static struct widget export_sample_widgets[4]; static char export_sample_filename[SCHISM_NAME_MAX + 1] = ""; static int export_sample_format = 0; static void do_export_sample(SCHISM_UNUSED void *data) { int exp = export_sample_format; int i; for (i = 0; sample_save_formats[i].label; i++) if (sample_save_formats[i].enabled && !sample_save_formats[i].enabled()) exp++; sample_save(export_sample_filename, sample_save_formats[exp].label); } static void export_sample_list_draw(void) { int n, focused = (*selected_widget == 3), c; draw_fill_chars(53, 24, 56, 31, DEFAULT_FG, 0); for (c = 0, n = 0; sample_save_formats[n].label; n++) { if (sample_save_formats[n].enabled && !sample_save_formats[n].enabled()) continue; int fg = 6, bg = 0; if (focused && c == export_sample_format) { fg = 0; bg = 3; } else if (c == export_sample_format) { bg = 14; } draw_text_len(sample_save_formats[n].label, 4, 53, 24 + c, fg, bg); c++; } } static int export_sample_list_handle_key(struct key_event * k) { int new_format = export_sample_format; if (k->state == KEY_RELEASE) return 0; switch (k->sym) { case SCHISM_KEYSYM_UP: if (!NO_MODIFIER(k->mod)) return 0; new_format--; break; case SCHISM_KEYSYM_DOWN: if (!NO_MODIFIER(k->mod)) return 0; new_format++; break; case SCHISM_KEYSYM_PAGEUP: case SCHISM_KEYSYM_HOME: if (!NO_MODIFIER(k->mod)) return 0; new_format = 0; break; case SCHISM_KEYSYM_PAGEDOWN: case SCHISM_KEYSYM_END: if (!NO_MODIFIER(k->mod)) return 0; new_format = num_save_formats - 1; break; case SCHISM_KEYSYM_TAB: if (k->mod & SCHISM_KEYMOD_SHIFT) { widget_change_focus_to(0); return 1; } /* fall through */ case SCHISM_KEYSYM_LEFT: case SCHISM_KEYSYM_RIGHT: if (!NO_MODIFIER(k->mod)) return 0; widget_change_focus_to(0); /* should focus 0/1/2 depending on what's closest */ return 1; default: return 0; } new_format = CLAMP(new_format, 0, num_save_formats - 1); if (new_format != export_sample_format) { /* update the option string */ export_sample_format = new_format; status.flags |= NEED_UPDATE; } return 1; } static void export_sample_draw_const(void) { draw_text("Export Sample", 34, 21, 0, 2); draw_text("Filename", 24, 24, 0, 2); draw_box(32, 23, 51, 25, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(52, 23, 57, 32, BOX_THICK | BOX_INNER | BOX_INSET); } static void export_sample_dialog(void) { song_sample_t *sample = song_get_sample(current_sample); struct dialog *dialog; widget_create_textentry(export_sample_widgets + 0, 33, 24, 18, 0, 1, 3, NULL, export_sample_filename, ARRAY_SIZE(export_sample_filename) - 1); widget_create_button(export_sample_widgets + 1, 31, 35, 6, 0, 1, 2, 2, 2, dialog_yes_NULL, "OK", 3); widget_create_button(export_sample_widgets + 2, 42, 35, 6, 3, 2, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); widget_create_other(export_sample_widgets + 3, 0, export_sample_list_handle_key, NULL, export_sample_list_draw); strncpy(export_sample_filename, sample->filename, ARRAY_SIZE(export_sample_filename) - 1); export_sample_filename[ARRAY_SIZE(export_sample_filename) - 1] = 0; dialog = dialog_create_custom(21, 20, 39, 18, export_sample_widgets, 4, 0, export_sample_draw_const, NULL); dialog->action_yes = do_export_sample; } /* resize sample dialog */ static struct widget resize_sample_widgets[2]; static int resize_sample_cursor; static void do_resize_sample_aa(SCHISM_UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); uint32_t newlen = resize_sample_widgets[0].d.numentry.value; sample_resize(sample, newlen, 1); } static void do_resize_sample(SCHISM_UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); uint32_t newlen = resize_sample_widgets[0].d.numentry.value; sample_resize(sample, newlen, 0); } static void resize_sample_draw_const(void) { draw_text("Resize Sample", 34, 24, 3, 2); draw_text("New Length", 31, 27, 0, 2); draw_box(41, 26, 49, 28, BOX_THICK | BOX_INNER | BOX_INSET); } static void resize_sample_dialog(int aa) { song_sample_t *sample = song_get_sample(current_sample); struct dialog *dialog; resize_sample_cursor = 0; widget_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; widget_create_button(resize_sample_widgets + 1, 36, 30, 6, 0, 1, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); dialog = dialog_create_custom(26, 22, 29, 11, resize_sample_widgets, 2, 0, resize_sample_draw_const, NULL); dialog->action_yes = aa ? do_resize_sample_aa : do_resize_sample; } /* resample sample dialog, mostly the same as above */ static struct widget resample_sample_widgets[2]; static int resample_sample_cursor; static void do_resample_sample_aa(SCHISM_UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); uint32_t newlen = ((double)sample->length * (double)resample_sample_widgets[0].d.numentry.value / (double)sample->c5speed); sample_resize(sample, newlen, 1); } static void do_resample_sample(SCHISM_UNUSED void *data) { song_sample_t *sample = song_get_sample(current_sample); uint32_t newlen = ((double)sample->length * (double)resample_sample_widgets[0].d.numentry.value / (double)sample->c5speed); sample_resize(sample, newlen, 0); } static void resample_sample_draw_const(void) { draw_text("Resample Sample", 33, 24, 3, 2); draw_text("New Sample Rate", 28, 27, 0, 2); draw_box(43, 26, 51, 28, BOX_THICK | BOX_INNER | BOX_INSET); } static void resample_sample_dialog(int aa) { song_sample_t *sample = song_get_sample(current_sample); struct dialog *dialog; resample_sample_cursor = 0; widget_create_numentry(resample_sample_widgets + 0, 44, 27, 7, 0, 1, 1, NULL, 0, 9999999, &resample_sample_cursor); resample_sample_widgets[0].d.numentry.value = sample->c5speed; widget_create_button(resample_sample_widgets + 1, 37, 30, 6, 0, 1, 1, 1, 1, dialog_cancel_NULL, "Cancel", 1); dialog = dialog_create_custom(26, 22, 28, 11, resample_sample_widgets, 2, 0, resample_sample_draw_const, NULL); dialog->action_yes = aa ? do_resample_sample_aa : do_resample_sample; } /* --------------------------------------------------------------------- */ // TODO openmpt has post loop fade, and we support it // internally, but it's never actually used or exposed // to the user. static struct widget crossfade_sample_widgets[6]; static int crossfade_sample_length_cursor; static const int crossfade_sample_loop_group[] = { 0, 1, -1 }; static void do_crossfade_sample(SCHISM_UNUSED void *data) { song_sample_t *smp = song_get_sample(current_sample); sample_crossfade(smp, crossfade_sample_widgets[2].d.numentry.value, crossfade_sample_widgets[3].d.thumbbar.value + 50, 0, crossfade_sample_widgets[1].d.togglebutton.state); } static void crossfade_sample_draw_const(void) { draw_text("Crossfade Sample", 32, 22, 3, 2); draw_text("Samples To Fade", 28, 27, 0, 2); draw_text("Volume", 28, 29, 0, 2); draw_text("Power", 47, 29, 0, 2); draw_box(27, 30, 48, 32, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(44, 26, 52, 28, BOX_THICK | BOX_INNER | BOX_INSET); } // regenerate the sample loop widget based on loop/susloop data static void crossfade_sample_loop_changed(void) { song_sample_t *smp = song_get_sample(current_sample); const int sustain = crossfade_sample_widgets[1].d.togglebutton.state; const uint32_t loop_start = (sustain) ? smp->sustain_start : smp->loop_start; const uint32_t loop_end = (sustain) ? smp->sustain_end : smp->loop_end; const uint32_t max = MIN(loop_end - loop_start, loop_start); widget_create_numentry(crossfade_sample_widgets + 2, 45, 27, 7, 0, 3, 3, NULL, 0, max, &crossfade_sample_length_cursor); crossfade_sample_widgets[2].d.numentry.value = max; } static void crossfade_sample_dialog(void) { song_sample_t *smp = song_get_sample(current_sample); struct dialog *dialog; // Sample Loop/Sustain Loop // FIXME the buttons for loop/sustain ought to be disabled when their respective loops are not valid widget_create_togglebutton(crossfade_sample_widgets + 0, 31, 24, 6, 0, 2, 1, 1, 1, crossfade_sample_loop_changed, "Loop", 2, crossfade_sample_loop_group); widget_create_togglebutton(crossfade_sample_widgets + 1, 41, 24, 7, 2, 2, 0, 0, 2, crossfade_sample_loop_changed, "Sustain", 1, crossfade_sample_loop_group); // Default to sustain loop if there is a sustain loop but no regular loop, or the regular loop is not valid crossfade_sample_widgets[((smp->flags & CHN_SUSTAINLOOP) && !((smp->flags & CHN_LOOP) && smp->loop_start && smp->loop_end)) ? 1 : 0].d.togglebutton.state = 1; // Samples To Fade; handled in other function to account for differences between // sample loop and sustain loop crossfade_sample_loop_changed(); // Priority widget_create_thumbbar(crossfade_sample_widgets + 3, 28, 31, 20, 2, 4, 4, NULL, -50, 50); crossfade_sample_widgets[3].d.thumbbar.value = 0; // Cancel/OK widget_create_button(crossfade_sample_widgets + 4, 31, 34, 6, 3, 4, 5, 5, 5, dialog_cancel_NULL, "Cancel", 1); widget_create_button(crossfade_sample_widgets + 5, 41, 34, 6, 3, 5, 4, 4, 0, dialog_yes_NULL, "OK", 3); dialog = dialog_create_custom(26, 20, 28, 17, crossfade_sample_widgets, 6, 0, crossfade_sample_draw_const, NULL); dialog->action_yes = do_crossfade_sample; } /* --------------------------------------------------------------------- */ static void sample_set_mute(int n, int mute) { song_sample_t *smp = song_get_sample(n); if (mute) { if (smp->flags & CHN_MUTE) return; smp->globalvol_saved = smp->global_volume; smp->global_volume = 0; smp->flags |= CHN_MUTE; } else { if (!(smp->flags & CHN_MUTE)) return; smp->global_volume = smp->globalvol_saved; smp->flags &= ~CHN_MUTE; } } static void sample_toggle_mute(int n) { song_sample_t *smp = song_get_sample(n); sample_set_mute(n, !(smp->flags & CHN_MUTE)); } static void sample_toggle_solo(int n) { int i, solo = 0; if (song_get_sample(n)->flags & CHN_MUTE) { solo = 1; } else { for (i = 1; i < MAX_SAMPLES; i++) { if (i != n && !(song_get_sample(i)->flags & CHN_MUTE)) { solo = 1; break; } } } for (i = 1; i < MAX_SAMPLES; i++) sample_set_mute(i, solo && i != n); } /* --------------------------------------------------------------------- */ static void dialog_noop(void *x) { (void)x; } static void sample_list_handle_alt_key(struct key_event * k) { song_sample_t *sample = song_get_sample(current_sample); int canmod = (sample->data != NULL && !(sample->flags & CHN_ADLIB)); if (k->state == KEY_RELEASE) return; switch (k->sym) { case SCHISM_KEYSYM_a: if (canmod) dialog_create(DIALOG_OK_CANCEL, "Convert sample?", do_sign_convert, NULL, 0, NULL); return; case SCHISM_KEYSYM_b: if (canmod && (sample->loop_start > 0 || ((sample->flags & CHN_SUSTAINLOOP) && sample->sustain_start > 0))) { dialog_create(DIALOG_OK_CANCEL, "Cut sample?", do_pre_loop_cut, NULL, 1, NULL); } return; case SCHISM_KEYSYM_d: if ((k->mod & SCHISM_KEYMOD_SHIFT) && !(status.flags & CLASSIC_MODE)) { if (canmod && sample->flags & CHN_STEREO) { dialog_create(DIALOG_OK_CANCEL, "Downmix sample to mono?", do_downmix, NULL, 0, NULL); } } else { dialog_create(DIALOG_OK_CANCEL, "Delete sample?", do_delete_sample, NULL, 1, NULL); } return; case SCHISM_KEYSYM_e: if (canmod) { if ((k->mod & SCHISM_KEYMOD_SHIFT) && !(status.flags & CLASSIC_MODE)) resample_sample_dialog(1); else resize_sample_dialog(1); } break; case SCHISM_KEYSYM_f: if (canmod) { if ((k->mod & SCHISM_KEYMOD_SHIFT) && !(status.flags & CLASSIC_MODE)) resample_sample_dialog(0); else resize_sample_dialog(0); } break; case SCHISM_KEYSYM_g: if (canmod) sample_reverse(sample); break; case SCHISM_KEYSYM_h: if (canmod) dialog_create(DIALOG_YES_NO, "Centralise sample?", do_centralise, NULL, 0, NULL); return; case SCHISM_KEYSYM_i: if (canmod) sample_invert(sample); break; case SCHISM_KEYSYM_l: if (canmod && (sample->loop_end > 0 || ((sample->flags & CHN_SUSTAINLOOP) && sample->sustain_end > 0))) { dialog_create(DIALOG_OK_CANCEL, "Cut sample?", do_post_loop_cut, NULL, 1, NULL); } return; case SCHISM_KEYSYM_m: if (canmod) sample_amplify_dialog(); return; case SCHISM_KEYSYM_n: song_toggle_multichannel_mode(); return; case SCHISM_KEYSYM_o: sample_save(NULL, "ITS"); return; case SCHISM_KEYSYM_p: smpprompt_create("Copy sample:", "Sample", do_copy_sample); return; case SCHISM_KEYSYM_q: if (canmod) { dialog_create(DIALOG_YES_NO, "Convert sample?", do_quality_convert, do_quality_toggle, 0, NULL); } return; case SCHISM_KEYSYM_r: smpprompt_create("Replace sample with:", "Sample", do_replace_sample); return; case SCHISM_KEYSYM_s: smpprompt_create("Swap sample with:", "Sample", do_swap_sample); return; case SCHISM_KEYSYM_t: export_sample_dialog(); return; case SCHISM_KEYSYM_v: if (!canmod || (status.flags & CLASSIC_MODE)) return; if (!(sample->flags & (CHN_LOOP|CHN_SUSTAINLOOP))) { dialog_create(DIALOG_OK, "Crossfade requires a sample loop to work.", dialog_noop, NULL, 0, NULL); return; } if (!sample->loop_start && !sample->sustain_start) { dialog_create(DIALOG_OK, "Crossfade requires data before the sample loop.", dialog_noop, NULL, 0, NULL); return; } crossfade_sample_dialog(); return; case SCHISM_KEYSYM_w: sample_save(NULL, "RAW"); return; case SCHISM_KEYSYM_x: smpprompt_create("Exchange sample with:", "Sample", do_exchange_sample); return; case SCHISM_KEYSYM_y: /* hi virt */ txtsynth_dialog(); return; case SCHISM_KEYSYM_z: { // uguu~ void (*dlg)(void *) = (k->mod & SCHISM_KEYMOD_SHIFT) ? sample_adlibpatch_dialog : sample_adlibconfig_dialog; if (canmod) { dialog_create(DIALOG_OK_CANCEL, "This will replace the current sample.", dlg, NULL, 1, NULL); } else { dlg(NULL); } } return; case SCHISM_KEYSYM_INSERT: song_insert_sample_slot(current_sample); break; case SCHISM_KEYSYM_DELETE: song_remove_sample_slot(current_sample); break; case SCHISM_KEYSYM_F9: sample_toggle_mute(current_sample); break; case SCHISM_KEYSYM_F10: sample_toggle_solo(current_sample); break; default: return; } status.flags |= NEED_UPDATE; } static void sample_list_handle_key(struct key_event * k) { int new_sample = current_sample; song_sample_t *sample = song_get_sample(current_sample); switch (k->sym) { case SCHISM_KEYSYM_SPACE: if (k->state == KEY_RELEASE) return; if (selected_widget && *selected_widget == 0) { status.flags |= NEED_UPDATE; } return; case SCHISM_KEYSYM_EQUALS: if (!(k->mod & SCHISM_KEYMOD_SHIFT)) return; SCHISM_FALLTHROUGH; case SCHISM_KEYSYM_PLUS: if (k->state == KEY_RELEASE) return; if (k->mod & SCHISM_KEYMOD_ALT) { sample->c5speed *= 2; status.flags |= SONG_NEEDS_SAVE; } else if (k->mod & SCHISM_KEYMOD_CTRL) { sample->c5speed = calc_halftone(sample->c5speed, 1); status.flags |= SONG_NEEDS_SAVE; } status.flags |= NEED_UPDATE; return; case SCHISM_KEYSYM_MINUS: if (k->state == KEY_RELEASE) return; if (k->mod & SCHISM_KEYMOD_ALT) { sample->c5speed /= 2; status.flags |= SONG_NEEDS_SAVE; } else if (k->mod & SCHISM_KEYMOD_CTRL) { sample->c5speed = calc_halftone(sample->c5speed, -1); status.flags |= SONG_NEEDS_SAVE; } status.flags |= NEED_UPDATE; return; case SCHISM_KEYSYM_COMMA: case SCHISM_KEYSYM_LESS: if (k->state == KEY_RELEASE) return; song_change_current_play_channel(-1, 0); return; case SCHISM_KEYSYM_PERIOD: case SCHISM_KEYSYM_GREATER: if (k->state == KEY_RELEASE) return; song_change_current_play_channel(1, 0); return; case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return; new_sample--; break; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return; new_sample++; break; case SCHISM_KEYSYM_ESCAPE: if (k->mod & SCHISM_KEYMOD_SHIFT) { if (k->state == KEY_RELEASE) return; sample_list_cursor_pos = 25; _fix_accept_text(); widget_change_focus_to(0); status.flags |= NEED_UPDATE; return; } return; default: if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_RELEASE) return; sample_list_handle_alt_key(k); } else if (!k->is_repeat) { int n, v; if (k->midi_note > -1) { n = k->midi_note; if (k->midi_volume > -1) { v = k->midi_volume / 2; } else { v = KEYJAZZ_DEFAULTVOL; } } else { n = (k->sym == SCHISM_KEYSYM_SPACE) ? last_note : kbd_get_note(k); if (n <= 0 || n > 120) return; v = KEYJAZZ_DEFAULTVOL; } if (k->state == KEY_RELEASE) { song_keyup(current_sample, KEYJAZZ_NOINST, n); } else { song_keydown(current_sample, KEYJAZZ_NOINST, n, v, KEYJAZZ_CHAN_CURRENT); last_note = n; } } return; } new_sample = CLAMP(new_sample, 1, _last_vis_sample()); if (new_sample != current_sample) { sample_set(new_sample); sample_list_reposition(); status.flags |= NEED_UPDATE; } } /* --------------------------------------------------------------------- */ /* wheesh */ static void sample_list_draw_const(void) { int n; draw_box(4, 12, 35, 48, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(63, 12, 77, 24, BOX_THICK | BOX_INNER | BOX_INSET); draw_box(36, 12, 53, 18, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(37, 15, 47, 17, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(36, 19, 53, 25, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(37, 22, 47, 24, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(36, 26, 53, 33, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(37, 29, 47, 32, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(36, 35, 53, 41, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(37, 38, 47, 40, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(36, 42, 53, 48, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(37, 45, 47, 47, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(54, 25, 77, 30, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(54, 31, 77, 41, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(54, 42, 77, 48, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(55, 45, 72, 47, BOX_THIN | BOX_INNER | BOX_INSET); draw_fill_chars(41, 30, 46, 30, DEFAULT_FG, 0); draw_fill_chars(64, 13, 76, 23, DEFAULT_FG, 0); draw_text("Default Volume", 38, 14, 0, 2); draw_text("Global Volume", 38, 21, 0, 2); draw_text("Default Pan", 39, 28, 0, 2); draw_text("Vibrato Speed", 38, 37, 0, 2); draw_text("Vibrato Depth", 38, 44, 0, 2); draw_text("Filename", 55, 13, 0, 2); draw_text("Speed", 58, 14, 0, 2); draw_text("Loop", 59, 15, 0, 2); draw_text("LoopBeg", 56, 16, 0, 2); draw_text("LoopEnd", 56, 17, 0, 2); draw_text("SusLoop", 56, 18, 0, 2); draw_text("SusLBeg", 56, 19, 0, 2); draw_text("SusLEnd", 56, 20, 0, 2); draw_text("Quality", 56, 22, 0, 2); draw_text("Length", 57, 23, 0, 2); draw_text("Vibrato Waveform", 58, 33, 0, 2); draw_text("Vibrato Rate", 60, 44, 0, 2); for (n = 0; n < 13; n++) draw_char(154, 64 + n, 21, 3, 0); } /* --------------------------------------------------------------------- */ /* wow. this got ugly. */ /* callback for the loop menu toggles */ static void update_sample_loop_flags(void) { song_sample_t *sample = song_get_sample(current_sample); /* these switch statements fall through */ sample->flags &= ~(CHN_LOOP | CHN_PINGPONGLOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN); switch (widgets_samplelist[9].d.menutoggle.state) { case 2: sample->flags |= CHN_PINGPONGLOOP; SCHISM_FALLTHROUGH; case 1: sample->flags |= CHN_LOOP; } switch (widgets_samplelist[12].d.menutoggle.state) { case 2: sample->flags |= CHN_PINGPONGSUSTAIN; SCHISM_FALLTHROUGH; case 1: sample->flags |= CHN_SUSTAINLOOP; } if (sample->flags & CHN_LOOP) { if (sample->loop_start == sample->length) sample->loop_start = 0; if (sample->loop_end <= sample->loop_start) sample->loop_end = sample->length; } if (sample->flags & CHN_SUSTAINLOOP) { if (sample->sustain_start == sample->length) sample->sustain_start = 0; if (sample->sustain_end <= sample->sustain_start) sample->sustain_end = sample->length; } csf_adjust_sample_loop(sample); /* update any samples currently playing */ song_update_playing_sample(current_sample); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } /* callback for the loop numentries */ static void update_sample_loop_points(void) { song_sample_t *sample = song_get_sample(current_sample); int flags_changed = 0; /* 9 = loop toggle, 10 = loop start, 11 = loop end */ if ((unsigned long) widgets_samplelist[10].d.numentry.value > sample->length - 1) widgets_samplelist[10].d.numentry.value = sample->length - 1; if (widgets_samplelist[11].d.numentry.value <= widgets_samplelist[10].d.numentry.value) { widgets_samplelist[9].d.menutoggle.state = 0; flags_changed = 1; } else if ((unsigned long) widgets_samplelist[11].d.numentry.value > sample->length) { widgets_samplelist[11].d.numentry.value = sample->length; } if (sample->loop_start != (unsigned long) widgets_samplelist[10].d.numentry.value || sample->loop_end != (unsigned long) widgets_samplelist[11].d.numentry.value) { flags_changed = 1; } sample->loop_start = widgets_samplelist[10].d.numentry.value; sample->loop_end = widgets_samplelist[11].d.numentry.value; /* 12 = sus toggle, 13 = sus start, 14 = sus end */ if ((unsigned long) widgets_samplelist[13].d.numentry.value > sample->length - 1) widgets_samplelist[13].d.numentry.value = sample->length - 1; if (widgets_samplelist[14].d.numentry.value <= widgets_samplelist[13].d.numentry.value) { widgets_samplelist[12].d.menutoggle.state = 0; flags_changed = 1; } else if ((unsigned long) widgets_samplelist[14].d.numentry.value > sample->length) { widgets_samplelist[14].d.numentry.value = sample->length; } if (sample->sustain_start != (unsigned long) widgets_samplelist[13].d.numentry.value || sample->sustain_end != (unsigned long) widgets_samplelist[14].d.numentry.value) { flags_changed = 1; } sample->sustain_start = widgets_samplelist[13].d.numentry.value; sample->sustain_end = widgets_samplelist[14].d.numentry.value; if (flags_changed) { update_sample_loop_flags(); } csf_adjust_sample_loop(sample); status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ static void update_values_in_song(void) { song_sample_t *sample = song_get_sample(current_sample); /* a few more modplug hacks here... */ sample->volume = widgets_samplelist[1].d.thumbbar.value * 4; sample->global_volume = widgets_samplelist[2].d.thumbbar.value; if (widgets_samplelist[3].d.toggle.state) sample->flags |= CHN_PANNING; else sample->flags &= ~CHN_PANNING; sample->vib_speed = widgets_samplelist[5].d.thumbbar.value; sample->vib_depth = widgets_samplelist[6].d.thumbbar.value; if (widgets_samplelist[15].d.togglebutton.state) sample->vib_type = VIB_SINE; else if (widgets_samplelist[16].d.togglebutton.state) sample->vib_type = VIB_RAMP_DOWN; else if (widgets_samplelist[17].d.togglebutton.state) sample->vib_type = VIB_SQUARE; else sample->vib_type = VIB_RANDOM; sample->vib_rate = widgets_samplelist[19].d.thumbbar.value; status.flags |= SONG_NEEDS_SAVE; } static void update_sample_speed(void) { song_sample_t *sample = song_get_sample(current_sample); sample->c5speed = widgets_samplelist[8].d.numentry.value; status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } static void update_panning(void) { song_sample_t *sample = song_get_sample(current_sample); sample->flags |= CHN_PANNING; sample->panning = widgets_samplelist[4].d.thumbbar.value * 4; widgets_samplelist[3].d.toggle.state = 1; status.flags |= SONG_NEEDS_SAVE; } static void update_filename(void) { status.flags |= SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ int sample_is_used_by_instrument(int samp) { song_instrument_t *ins; int i, j; if (samp < 1) return 0; for (i = 1; i <= MAX_INSTRUMENTS; i++) { ins = song_get_instrument(i); if (!ins) continue; for (j = 0; j < 120; j++) { if (ins->sample_map[j] == (unsigned int)samp) return 1; } } return 0; } void sample_synchronize_to_instrument(void) { song_instrument_t *ins; int instnum = instrument_get_current(); int pos, first; ins = song_get_instrument(instnum); first = 0; for (pos = 0; pos < 120; pos++) { if (first == 0) first = ins->sample_map[pos]; if (ins->sample_map[pos] == (unsigned int)instnum) { sample_set(instnum); return; } } if (first > 0) { sample_set(first); } else { sample_set(instnum); } } void sample_list_load_page(struct page *page) { vgamem_ovl_alloc(&sample_image); page->title = "Sample List (F3)"; page->draw_const = sample_list_draw_const; page->predraw_hook = sample_list_predraw_hook; page->handle_key = sample_list_handle_key; page->set_page = sample_list_reposition; page->total_widgets = 20; page->widgets = widgets_samplelist; page->help_index = HELP_SAMPLE_LIST; /* 0 = sample list */ widget_create_other(widgets_samplelist + 0, 1, sample_list_handle_key_on_list, sample_list_handle_text_input_on_list, sample_list_draw_list); _fix_accept_text(); widgets_samplelist[0].x = 5; widgets_samplelist[0].y = 13; widgets_samplelist[0].width = 30; widgets_samplelist[0].height = 35; /* 1 -> 6 = middle column */ widget_create_thumbbar(widgets_samplelist + 1, 38, 16, 9, 1, 2, 7, update_values_in_song, 0, 64); widget_create_thumbbar(widgets_samplelist + 2, 38, 23, 9, 1, 3, 7, update_values_in_song, 0, 64); widget_create_toggle(widgets_samplelist + 3, 38, 30, 2, 4, 0, 7, 7, update_values_in_song); widget_create_thumbbar(widgets_samplelist + 4, 38, 31, 9, 3, 5, 7, update_panning, 0, 64); widget_create_thumbbar(widgets_samplelist + 5, 38, 39, 9, 4, 6, 15, update_values_in_song, 0, 64); widget_create_thumbbar(widgets_samplelist + 6, 38, 46, 9, 5, 6, 19, update_values_in_song, 0, 32); /* 7 -> 14 = top right box */ widget_create_textentry(widgets_samplelist + 7, 64, 13, 13, 7, 8, 0, update_filename, NULL, 12); widget_create_numentry(widgets_samplelist + 8, 64, 14, 7, 7, 9, 0, update_sample_speed, 0, 9999999, &sample_numentry_cursor_pos); widget_create_menutoggle(widgets_samplelist + 9, 64, 15, 8, 10, 1, 0, 0, update_sample_loop_flags, loop_states); widget_create_numentry(widgets_samplelist + 10, 64, 16, 7, 9, 11, 0, update_sample_loop_points, 0, 9999999, &sample_numentry_cursor_pos); widget_create_numentry(widgets_samplelist + 11, 64, 17, 7, 10, 12, 0, update_sample_loop_points, 0, 9999999, &sample_numentry_cursor_pos); widget_create_menutoggle(widgets_samplelist + 12, 64, 18, 11, 13, 1, 0, 0, update_sample_loop_flags, loop_states); widget_create_numentry(widgets_samplelist + 13, 64, 19, 7, 12, 14, 0, update_sample_loop_points, 0, 9999999, &sample_numentry_cursor_pos); widget_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 */ widget_create_togglebutton(widgets_samplelist + 15, 57, 36, 6, 14, 17, 5, 16, 16, update_values_in_song, "\xb9\xba", 3, vibrato_waveforms); widget_create_togglebutton(widgets_samplelist + 16, 67, 36, 6, 14, 18, 15, 0, 0, update_values_in_song, "\xbd\xbe", 3, vibrato_waveforms); widget_create_togglebutton(widgets_samplelist + 17, 57, 39, 6, 15, 19, 5, 18, 18, update_values_in_song, "\xbb\xbc", 3, vibrato_waveforms); widget_create_togglebutton(widgets_samplelist + 18, 67, 39, 6, 16, 19, 17, 0, 0, update_values_in_song, "Random", 1, vibrato_waveforms); /* 19 = vibrato rate */ widget_create_thumbbar(widgets_samplelist + 19, 56, 46, 16, 17, 19, 0, update_values_in_song, 0, 255); /* count how many formats there really are */ num_save_formats = 0; int i; for (i = 0; sample_save_formats[i].label; i++) if (!sample_save_formats[i].enabled || sample_save_formats[i].enabled()) num_save_formats++; } schismtracker-20250313/schism/page_timeinfo.c000066400000000000000000000127161476471630300210770ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "config.h" #include "page.h" #include "widget.h" #include "vgamem.h" #include "keyboard.h" #include "song.h" #include "mem.h" #include "str.h" /* --------------------------------------------------------------------- */ static struct widget widgets_timeinfo[1]; static int display_session = 0; // list static int top_line = 0; /* --------------------------------------------------------------------- */ static void timeinfo_draw_const(void) { draw_text("Module time:", 6, 13, 0, 2); draw_text("Current session:", 2, 14, 0, 2); draw_text("Total time:", 7, 16, 0, 2); } static int timeinfo_handle_key(struct key_event * k) { switch (k->sym) { case SCHISM_KEYSYM_BACKQUOTE: if (k->state != KEY_RELEASE) return 0; if ((k->mod & SCHISM_KEYMOD_RALT) && (k->mod & SCHISM_KEYMOD_RSHIFT)) { display_session = !display_session; status.flags |= NEED_UPDATE; return 1; } return 0; case SCHISM_KEYSYM_s: if (k->state != KEY_RELEASE) return 0; if (!(status.flags & CLASSIC_MODE)) { display_session = !display_session; status.flags |= NEED_UPDATE; return 1; } return 0; default: break; } if (display_session) { switch (k->sym) { case SCHISM_KEYSYM_UP: if (k->state == KEY_RELEASE) return 1; top_line--; break; case SCHISM_KEYSYM_PAGEUP: if (k->state == KEY_RELEASE) return 1; top_line -= 15; break; case SCHISM_KEYSYM_DOWN: if (k->state == KEY_RELEASE) return 1; top_line++; break; case SCHISM_KEYSYM_PAGEDOWN: if (k->state == KEY_RELEASE) return 1; top_line += 15; break; case SCHISM_KEYSYM_HOME: if (k->state == KEY_RELEASE) return 1; top_line = 0; break; case SCHISM_KEYSYM_END: if (k->state == KEY_RELEASE) return 1; top_line = current_song->histlen; break; default: if (k->state == KEY_PRESS) { if (k->mouse == MOUSE_SCROLL_UP) { top_line -= MOUSE_SCROLL_LINES; break; } else if (k->mouse == MOUSE_SCROLL_DOWN) { top_line += MOUSE_SCROLL_LINES; break; } } return 0; } top_line = MIN(top_line, (ptrdiff_t)current_song->histlen); top_line = MAX(top_line, 0); status.flags |= NEED_UPDATE; return 1; } else { return 0; } } static inline void draw_time(uint64_t secs, int x, int y) { char buf[64] = {0}; secs = MIN(secs, UINT64_C(3599999)); // 999:59:59 int amt = snprintf(buf, 64, "%4" PRIu64 ":%02" PRIu64 ":%02" PRIu64, secs / 3600, secs / 60 % 60, secs % 60); amt = CLAMP(amt, 0, 64); amt = MIN(amt, 80 - x); draw_text_len(buf, amt, x, y, 0, 2); } static void timeinfo_redraw(void) { uint64_t total_secs = 0; { // Module time uint64_t msecs = 0; for (size_t i = 0; i < current_song->histlen; i++) msecs += current_song->history[i].runtime; const uint64_t secs = msecs / 1000; draw_time(secs, 18, 13); total_secs += secs; } { // Current session const time_t now = time(NULL); const time_t start = mktime(¤t_song->editstart.time); double secs_d = difftime(now, start); draw_time(secs_d, 18, 14); total_secs += secs_d; } draw_time(total_secs, 18, 16); // draw the bar for (int x = 1; x < 79; x++) draw_char(154, x, 18, 0, 2); if (display_session) { uint64_t session_secs = 0; for (size_t i = 0; i < current_song->histlen; i++) { char buf[27]; const uint64_t runtime_secs = current_song->history[i].runtime / 1000; session_secs += runtime_secs; if ((int)i >= top_line && (int)i < top_line + 29) { if (current_song->history[i].time_valid) { str_date_from_tm(¤t_song->history[i].time, buf, cfg_str_date_format); draw_text_len(buf, 27, 4, 20 + i - top_line, 0, 2); str_time_from_tm(¤t_song->history[i].time, buf, cfg_str_time_format); draw_text_len(buf, 27, 29, 20 + i - top_line, 0, 2); } else { draw_text("", 4, 20 + i - top_line, 0, 2); } draw_time(runtime_secs, 44, 20 + i - top_line); draw_time(session_secs, 64, 20 + i - top_line); } } } } static void timeinfo_set_page(void) { // reset this display_session = 0; } /* --------------------------------------------------------------------- */ void timeinfo_load_page(struct page *page) { page->title = "Time Information"; page->draw_const = timeinfo_draw_const; page->set_page = timeinfo_set_page; page->total_widgets = 1; page->widgets = widgets_timeinfo; page->help_index = HELP_TIME_INFORMATION; widget_create_other(widgets_timeinfo + 0, 0, timeinfo_handle_key, NULL, timeinfo_redraw); } schismtracker-20250313/schism/page_vars.c000066400000000000000000000207361476471630300202410ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "config.h" #include "dialog.h" #include "it.h" #include "page.h" #include "song.h" #include "vgamem.h" #include "widget.h" /* --------------------------------------------------------------------- */ /* static variables */ static struct widget widgets_vars[18]; static const int group_control[] = { 8, 9, -1 }; static const int group_playback[] = { 10, 11, -1 }; static const int group_slides[] = { 12, 13, -1 }; static char Amiga[6] = "Amiga"; /* --------------------------------------------------------------------- */ static void update_song_title(void) { status.flags |= NEED_UPDATE | SONG_NEEDS_SAVE; } /* --------------------------------------------------------------------- */ static void song_vars_draw_const(void) { int n; draw_box(16, 15, 43, 17, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(16, 18, 50, 21, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(16, 22, 34, 28, BOX_THIN | BOX_INNER | BOX_INSET); draw_box(12, 41, 78, 45, BOX_THICK | BOX_INNER | BOX_INSET); draw_fill_chars(20, 26, 33, 27, DEFAULT_FG, 0); draw_text("Song Variables", 33, 13, 3, 2); draw_text("Song Name", 7, 16, 0, 2); draw_text("Initial Tempo", 3, 19, 0, 2); draw_text("Initial Speed", 3, 20, 0, 2); draw_text("Global Volume", 3, 23, 0, 2); draw_text("Mixing Volume", 3, 24, 0, 2); draw_text("Separation", 6, 25, 0, 2); draw_text("Old Effects", 5, 26, 0, 2); draw_text("Compatible Gxx", 2, 27, 0, 2); draw_text("Control", 9, 30, 0, 2); draw_text("Playback", 8, 33, 0, 2); draw_text("Pitch Slides", 4, 36, 0, 2); draw_text("Directories", 34, 40, 3, 2); draw_text("Module", 6, 42, 0, 2); draw_text("Sample", 6, 43, 0, 2); draw_text("Instrument", 2, 44, 0, 2); for (n = 1; n < 79; n++) draw_char(129, n, 39, 1, 2); } /* --------------------------------------------------------------------- */ void song_vars_sync_stereo(void) { // copy from the song to the page if (song_is_stereo()) widget_togglebutton_set(widgets_vars, 10, 0); else widget_togglebutton_set(widgets_vars, 11, 0); } static void update_values_in_song(void) { song_set_initial_tempo(widgets_vars[1].d.thumbbar.value); song_set_initial_speed(widgets_vars[2].d.thumbbar.value); song_set_initial_global_volume(widgets_vars[3].d.thumbbar.value); song_set_mixing_volume(widgets_vars[4].d.thumbbar.value); song_set_separation(widgets_vars[5].d.thumbbar.value); song_set_old_effects(widgets_vars[6].d.toggle.state); song_set_compatible_gxx(widgets_vars[7].d.toggle.state); song_set_instrument_mode(widgets_vars[8].d.togglebutton.state); if (widgets_vars[10].d.togglebutton.state) { if (!song_is_stereo()) { song_set_stereo(); } } else { if (song_is_stereo()) { song_set_mono(); } } song_set_linear_pitch_slides(widgets_vars[12].d.togglebutton.state); status.flags |= SONG_NEEDS_SAVE; } static void init_instruments(SCHISM_UNUSED void *data) { song_init_instruments(-1); } static void maybe_init_instruments(void) { /* XXX actually, in IT the buttons on this dialog say OK/No for whatever reason */ song_set_instrument_mode(1); dialog_create(DIALOG_YES_NO, "Initialise instruments?", init_instruments, NULL, 0, NULL); status.flags |= SONG_NEEDS_SAVE; } static void song_changed_cb(void) { char *b; int c; widgets_vars[0].d.textentry.text = current_song->title; widgets_vars[0].d.textentry.cursor_pos = strlen(widgets_vars[0].d.textentry.text); widgets_vars[1].d.thumbbar.value = current_song->initial_tempo; widgets_vars[2].d.thumbbar.value = current_song->initial_speed; widgets_vars[3].d.thumbbar.value = current_song->initial_global_volume; widgets_vars[4].d.thumbbar.value = current_song->mixing_volume; widgets_vars[5].d.thumbbar.value = current_song->pan_separation; widgets_vars[6].d.toggle.state = song_has_old_effects(); widgets_vars[7].d.toggle.state = song_has_compatible_gxx(); if (song_is_instrument_mode()) widget_togglebutton_set(widgets_vars, 8, 0); else widget_togglebutton_set(widgets_vars, 9, 0); if (song_is_stereo()) widget_togglebutton_set(widgets_vars, 10, 0); else widget_togglebutton_set(widgets_vars, 11, 0); if (song_has_linear_pitch_slides()) widget_togglebutton_set(widgets_vars, 12, 0); else widget_togglebutton_set(widgets_vars, 13, 0); /* XXX wtf is going on here */ for (b = strpbrk(song_get_basename(), "Aa"), c = 12632; c && b && b[1]; c >>= 4, b++) if ((c & 15) != b[1] - *b) return; if (!c) for (b = Amiga, c = 12632; c; c>>= 4, b++) b[1] = *b + (c & 15); } /* --------------------------------------------------------------------- */ /* bleh */ static void dir_modules_changed(void) { status.flags |= DIR_MODULES_CHANGED; } static void dir_samples_changed(void) { status.flags |= DIR_SAMPLES_CHANGED; } static void dir_instruments_changed(void) { status.flags |= DIR_INSTRUMENTS_CHANGED; } /* --------------------------------------------------------------------- */ void song_vars_load_page(struct page *page) { page->title = "Song Variables & Directory Configuration (F12)"; page->draw_const = song_vars_draw_const; page->song_changed_cb = song_changed_cb; page->total_widgets = 18; page->widgets = widgets_vars; page->help_index = HELP_GLOBAL; /* 0 = song name */ widget_create_textentry(widgets_vars, 17, 16, 26, 0, 1, 1, update_song_title, current_song->title, 25); /* 1 = tempo */ widget_create_thumbbar(widgets_vars + 1, 17, 19, 33, 0, 2, 2, update_values_in_song, 31, 255); /* 2 = speed */ widget_create_thumbbar(widgets_vars + 2, 17, 20, 33, 1, 3, 3, update_values_in_song, 1, 255); /* 3 = global volume */ widget_create_thumbbar(widgets_vars + 3, 17, 23, 17, 2, 4, 4, update_values_in_song, 0, 128); /* 4 = mixing volume */ widget_create_thumbbar(widgets_vars + 4, 17, 24, 17, 3, 5, 5, update_values_in_song, 0, 128); /* 5 = separation */ widget_create_thumbbar(widgets_vars + 5, 17, 25, 17, 4, 6, 6, update_values_in_song, 0, 128); /* 6 = old effects */ widget_create_toggle(widgets_vars + 6, 17, 26, 5, 7, 5, 7, 7, update_values_in_song); /* 7 = compatible gxx */ widget_create_toggle(widgets_vars + 7, 17, 27, 6, 8, 6, 8, 8, update_values_in_song); /* 8-13 = switches */ widget_create_togglebutton(widgets_vars + 8, 17, 30, 11, 7, 10, 9, 9, 9, maybe_init_instruments, "Instruments", 1, group_control); widgets_vars[8].next.backtab = 9; widget_create_togglebutton(widgets_vars + 9, 32, 30, 11, 7, 11, 8, 8, 8, update_values_in_song, "Samples", 1, group_control); widget_create_togglebutton(widgets_vars + 10, 17, 33, 11, 8, 12, 11, 11, 11, update_values_in_song, "Stereo", 1, group_playback); widget_create_togglebutton(widgets_vars + 11, 32, 33, 11, 9, 13, 10, 10, 10, update_values_in_song, "Mono", 1, group_playback); widget_create_togglebutton(widgets_vars + 12, 17, 36, 11, 10, 14, 13, 13, 13, update_values_in_song, "Linear", 1, group_slides); widget_create_togglebutton(widgets_vars + 13, 32, 36, 11, 11, 14, 12, 12, 12, update_values_in_song, Amiga, 1, group_slides); /* 14-16 = directories */ widget_create_textentry(widgets_vars + 14, 13, 42, 65, 12, 15, 15, dir_modules_changed, cfg_dir_modules, ARRAY_SIZE(cfg_dir_modules) - 1); widget_create_textentry(widgets_vars + 15, 13, 43, 65, 14, 16, 16, dir_samples_changed, cfg_dir_samples, ARRAY_SIZE(cfg_dir_samples) - 1); widget_create_textentry(widgets_vars + 16, 13, 44, 65, 15, 17, 17, dir_instruments_changed, cfg_dir_instruments, ARRAY_SIZE(cfg_dir_instruments) - 1); /* 17 = save all preferences */ widget_create_button(widgets_vars + 17, 28, 47, 22, 16, 17, 17, 17, 17, cfg_save, "Save all Preferences", 2); widgets_vars[17].next.backtab = 17; } schismtracker-20250313/schism/page_waterfall.c000066400000000000000000000327031476471630300212440ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "keyboard.h" #include "page.h" #include "song.h" #include "widget.h" #include "vgamem.h" #define NATIVE_SCREEN_WIDTH 640 #define NATIVE_SCREEN_HEIGHT 400 #define SCOPE_ROWS 32 /* This value is used internally to scale the power output of the FFT to decibels. */ static const float fft_inv_bufsize = 1.0f/(FFT_BUFFER_SIZE>>2); /* Scaling for FFT. Input is expected to be int16_t. */ static const float inv_s_range = 1.f/32768.f; static int16_t current_fft_data[2][FFT_OUTPUT_SIZE]; /*Table to change the scale from linear to log.*/ static uint32_t fftlog[FFT_BANDS_SIZE]; /* variables :) */ static int mono = 0; //gain, in dBs. //static int gain = 0; static int noisefloor=72; /* get the _whole_ display */ static struct vgamem_overlay ovl = { 0, 0, 79, 49, NULL, 0, 0, 0 }; /* tables */ static uint32_t bit_reverse[FFT_BUFFER_SIZE]; static float window[FFT_BUFFER_SIZE]; static float precos[FFT_OUTPUT_SIZE]; static float presin[FFT_OUTPUT_SIZE]; /* fft state */ static float state_real[FFT_BUFFER_SIZE]; static float state_imag[FFT_BUFFER_SIZE]; static inline SCHISM_ALWAYS_INLINE uint32_t _reverse_bits(uint32_t in) { uint32_t r = 0, n; for (n = 0; n < FFT_BUFFER_SIZE_LOG; n++) { r <<= 1; r |= (in & 1); in >>= 1; } return r; } void vis_init(void) { unsigned int n; for (n = 0; n < FFT_BUFFER_SIZE; n++) { bit_reverse[n] = _reverse_bits(n); #if 0 /*Rectangular/none*/ window[n] = 1; /*Cosine/sine window*/ window[n] = sin(M_PI * n/ FFT_BUFFER_SIZE -1); /*Hann Window*/ window[n] = 0.50f - 0.50f * cos(2.0*M_PI * n / (FFT_BUFFER_SIZE - 1)); /*Hamming Window*/ window[n] = 0.54f - 0.46f * cos(2.0*M_PI * n / (FFT_BUFFER_SIZE - 1)); /*Gaussian*/ window[n] = powf(M_E,-0.5f *pow((n-(FFT_BUFFER_SIZE-1)/2.f)/(0.4*(FFT_BUFFER_SIZE-1)/2.f),2.f)); /*Blackmann*/ window[n] = 0.42659 - 0.49656 * cos(2.0*M_PI * n/ (FFT_BUFFER_SIZE-1)) + 0.076849 * cos(4.0*M_PI * n /(FFT_BUFFER_SIZE-1)); /*Blackman-Harris*/ window[n] = 0.35875 - 0.48829 * cos(2.0*M_PI * n/ (FFT_BUFFER_SIZE-1)) + 0.14128 * cos(4.0*M_PI * n /(FFT_BUFFER_SIZE-1)) - 0.01168 * cos(6.0*M_PI * n /(FFT_BUFFER_SIZE-1)); #endif /*Hann Window*/ window[n] = 0.50f - 0.50f * cos(2.0*M_PI * n / (FFT_BUFFER_SIZE - 1)); } for (n = 0; n < FFT_OUTPUT_SIZE; n++) { float j = (2.0*M_PI) * n / FFT_BUFFER_SIZE; precos[n] = cos(j); presin[n] = sin(j); } #if 0 /* linear */ const double factor = (float)FFT_OUTPUT_SIZE / FFT_BANDS_SIZE; for (n = 0; n < FFT_BANDS_SIZE; n++) fftlog[n] = n * factor; #elif 1 /* exponential */ const double factor = (float)FFT_OUTPUT_SIZE / FFT_BANDS_SIZE / FFT_BANDS_SIZE; for (n = 0; n < FFT_BANDS_SIZE; n++) fftlog[n] = n * n * factor; #else /* constant note scale */ const float factor = 8.0f / (float)FFT_BANDS_SIZE; const float factor2 = (float)FFT_OUTPUT_SIZE / 256.0f; for (n = 0; n < FFT_BANDS_SIZE; n++) fftlog[n] = factor2 * (powf(2.0f, n * factor) - 1.0f); #endif } /* * Understanding In and Out: * input is the samples (so, it is amplitude). The scale is expected to be signed 16bits. * The window function calculated in "window" will automatically be applied. * output is a value between 0 and 128 representing 0 = noisefloor variable * and 128 = 0dBFS (deciBell, FullScale) for each band. */ static void _vis_data_work(int16_t output[FFT_OUTPUT_SIZE], int16_t input[FFT_BUFFER_SIZE]) { uint32_t n, k, y; uint32_t ex = 1, ff = FFT_OUTPUT_SIZE; uint32_t yp; float fr, fi; float tr, ti; float out; for (n = 0; n < FFT_BUFFER_SIZE; n++) { uint32_t nr = bit_reverse[n]; state_real[n] = (float)input[nr] * inv_s_range * window[nr]; state_imag[n] = 0; } for (n = FFT_BUFFER_SIZE_LOG; n != 0; n--) { for (k = 0; k != ex; k++) { fr = precos[k * ff]; fi = presin[k * ff]; for (y = k; y < FFT_BUFFER_SIZE; y += ex << 1) { yp = y + ex; tr = fr * state_real[yp] - fi * state_imag[yp]; ti = fr * state_imag[yp] + fi * state_real[yp]; state_real[yp] = state_real[y] - tr; state_imag[yp] = state_imag[y] - ti; state_real[y] += tr; state_imag[y] += ti; } } ex <<= 1; ff >>= 1; } /* collect fft */ /* XXX I changed the behavior here since the states were getting overflowed (originally * was 'n + 1' changed to just 'n'. Hopefully nothing breaks... */ const float fft_dbinv_bufsize = dB(fft_inv_bufsize); for (n = 0; n < FFT_OUTPUT_SIZE; n++) { /* "out" is the total power for each band. * To get amplitude from "output", use sqrt(out[N])/(sizeBuf>>2) * To get dB from "output", use powerdB(out[N])+db(1/(sizeBuf>>2)). * powerdB is = 10 * log10(in) * dB is = 20 * log10(in) */ out = ((state_real[n]) * (state_real[n])) + ((state_imag[n]) * (state_imag[n])); /* +0.0000000001f is -100dB of power. Used to prevent evaluating powerdB(0.0) */ output[n] = pdB_s(noisefloor, out + 0.0000000001f, fft_dbinv_bufsize); } } // "chan" is either zero for all, or nonzero for a specific output channel static inline SCHISM_ALWAYS_INLINE int16_t _fft_get_value(uint32_t chan, uint32_t offset) { switch (chan) { case 1: case 2: return current_fft_data[chan - 1][offset]; default: break; } const uint32_t x1 = current_fft_data[0][offset], x2 = current_fft_data[1][offset]; return (x1 >> 1) + (x2 >> 1) + (x1 & x2 & 1); } /* convert the fft bands to columns of screen * out and fft have a range of 0 to 128 * * "chan" is the channel to process, or 0 for all */ void fft_get_columns(uint32_t width, unsigned char *out, uint32_t chan) { uint32_t i, a; for (i = 0, a = 0; i < width && a < FFT_OUTPUT_SIZE; i++) { const uint32_t fftlog_i = (i * FFT_BANDS_SIZE / width); int j; uint32_t ax = fftlog[fftlog_i]; if (ax >= FFT_OUTPUT_SIZE) break; // NOW JUST WHO SAY THEY AINT GOT MANY BLOOD? /* mmm... this got ugly */ if ((fftlog_i + 1 >= FFT_BANDS_SIZE) || (ax + 1 > fftlog[fftlog_i + 1])) { a = ax; j = _fft_get_value(chan, a); } else { j = _fft_get_value(chan, a); while (a <= ax) { a++; int x = _fft_get_value(chan, a); j = MAX(j, x); } } /* FIXME if the FTT data is 16-bits, why are we cutting off the top bits */ out[i] = j; } } /* Convert the output of */ static inline SCHISM_ALWAYS_INLINE unsigned char *_dobits(unsigned char *q, unsigned char *in, int length, int y) { int i, c; for (i = 0; i < length; i++) { /* j is has range 0 to 128. Now use the upper side for drawing.*/ c = 128 + in[i]; if (c > 255) c = 255; *q = c; q += y; } return q; } /*x = screen.x, h = 0..128, c = colour */ static inline SCHISM_ALWAYS_INLINE void _drawslice(int x, int h, int c) { int y = ((h>>2) & (SCOPE_ROWS-1))+1; vgamem_ovl_drawline(&ovl, x, (NATIVE_SCREEN_HEIGHT-y), x, (NATIVE_SCREEN_HEIGHT-1), c); } static void _vis_process(void) { static const int k = NATIVE_SCREEN_WIDTH / 2; int i; unsigned char outfft[NATIVE_SCREEN_WIDTH]; unsigned char *q; /* move up previous line by one pixel */ memmove(ovl.q, ovl.q + NATIVE_SCREEN_WIDTH, (NATIVE_SCREEN_WIDTH * ((NATIVE_SCREEN_HEIGHT - 1) - SCOPE_ROWS))); q = ovl.q + (NATIVE_SCREEN_WIDTH * ((NATIVE_SCREEN_HEIGHT - 1) - SCOPE_ROWS)); if (mono) { fft_get_columns(NATIVE_SCREEN_WIDTH, outfft, 0); _dobits(q, outfft, NATIVE_SCREEN_WIDTH, 1); } else { fft_get_columns(k, outfft, 1); fft_get_columns(k, outfft + k, 2); _dobits(q + k - 1, outfft, k, -1); _dobits(q + k, outfft + k, k, 1); } /* draw the scope at the bottom */ q = ovl.q + (NATIVE_SCREEN_WIDTH * (NATIVE_SCREEN_HEIGHT - SCOPE_ROWS)); i = SCOPE_ROWS * NATIVE_SCREEN_WIDTH; memset(q, 0, i); if (mono) { for (i = 0; i < NATIVE_SCREEN_WIDTH; i++) _drawslice(i, outfft[i], 5); } else { for (i = 0; i < k; i++) _drawslice(k - i - 1, outfft[i], 5); for (i = 0; i < k; i++) _drawslice(k + i, outfft[k + i], 5); } status.flags |= NEED_UPDATE; } #define VIS_WORK_EX(SUFFIX, BITS, INLOOP) \ void vis_work_##BITS##SUFFIX(const int##BITS##_t *in, int inlen) \ { \ int16_t dl[FFT_BUFFER_SIZE], dr[FFT_BUFFER_SIZE]; \ int i, j, k; \ \ if (!inlen) { \ memset(current_fft_data[0], 0, FFT_OUTPUT_SIZE*2); \ memset(current_fft_data[1], 0, FFT_OUTPUT_SIZE*2); \ } else { \ for (i = 0; i < FFT_BUFFER_SIZE;) { \ for (k = j = 0; k < inlen && i < FFT_BUFFER_SIZE; k++, i++) { \ INLOOP \ } \ } \ \ _vis_data_work(current_fft_data[0], dl); \ _vis_data_work(current_fft_data[1], dr); \ } \ if (status.current_page == PAGE_WATERFALL) _vis_process(); \ } #define VIS_WORK(BITS) \ VIS_WORK_EX(s, BITS, { \ dl[i] = rshift_signed(lshift_signed((int32_t)in[j], 32 - BITS), 16); j++; \ dr[i] = rshift_signed(lshift_signed((int32_t)in[j], 32 - BITS), 16); j++; \ }) \ \ VIS_WORK_EX(m, BITS, { \ dl[i] = dr[i] = rshift_signed(lshift_signed((int32_t)in[j], 32 - BITS), 16); j++; \ }) VIS_WORK(32) VIS_WORK(16) VIS_WORK(8) #undef VIS_WORK #undef VIS_WORK_EX static void draw_screen(void) { /* waterfall uses a single overlay */ vgamem_ovl_apply(&ovl); } static int waterfall_handle_key(struct key_event *k) { int n, v, order, ii; if (NO_MODIFIER(k->mod)) { if (k->midi_note > -1) { n = k->midi_note; if (k->midi_volume > -1) { v = k->midi_volume / 2; } else { v = 64; } } else { v = 64; n = kbd_get_note(k); } if (n > -1) { if (song_is_instrument_mode()) { ii = instrument_get_current(); } else { ii = sample_get_current(); } if (k->state == KEY_RELEASE) { song_keyup(KEYJAZZ_NOINST, ii, n); status.last_keysym = 0; } else if (!k->is_repeat) { song_keydown(KEYJAZZ_NOINST, ii, n, v, KEYJAZZ_CHAN_CURRENT); } return 1; } } switch (k->sym) { case SCHISM_KEYSYM_s: if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_RELEASE) return 1; song_toggle_stereo(); status.flags |= NEED_UPDATE; return 1; } return 0; case SCHISM_KEYSYM_m: if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_RELEASE) return 1; mono = !mono; return 1; } return 0; case SCHISM_KEYSYM_LEFT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; noisefloor-=4; break; case SCHISM_KEYSYM_RIGHT: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; noisefloor+=4; break; case SCHISM_KEYSYM_g: if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_PRESS) return 1; order = song_get_current_order(); if (song_get_mode() == MODE_PLAYING) { n = current_song->orderlist[order]; } else { n = song_get_playing_pattern(); } if (n < 200) { set_current_order(order); set_current_pattern(n); set_current_row(song_get_current_row()); set_page(PAGE_PATTERN_EDITOR); } return 1; } return 0; case SCHISM_KEYSYM_r: if (k->mod & SCHISM_KEYMOD_ALT) { if (k->state == KEY_RELEASE) return 1; song_flip_stereo(); return 1; } return 0; case SCHISM_KEYSYM_PLUS: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; if (song_get_mode() == MODE_PLAYING) { song_set_current_order(song_get_current_order() + 1); } return 1; case SCHISM_KEYSYM_MINUS: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; if (song_get_mode() == MODE_PLAYING) { song_set_current_order(song_get_current_order() - 1); } return 1; case SCHISM_KEYSYM_SEMICOLON: case SCHISM_KEYSYM_COLON: if (k->state == KEY_RELEASE) return 1; if (song_is_instrument_mode()) { instrument_set(instrument_get_current() - 1); } else { sample_set(sample_get_current() - 1); } return 1; case SCHISM_KEYSYM_QUOTE: case SCHISM_KEYSYM_QUOTEDBL: if (k->state == KEY_RELEASE) return 1; if (song_is_instrument_mode()) { instrument_set(instrument_get_current() + 1); } else { sample_set(sample_get_current() + 1); } return 1; case SCHISM_KEYSYM_COMMA: case SCHISM_KEYSYM_LESS: if (k->state == KEY_RELEASE) return 1; song_change_current_play_channel(-1, 0); return 1; case SCHISM_KEYSYM_PERIOD: case SCHISM_KEYSYM_GREATER: if (k->state == KEY_RELEASE) return 1; song_change_current_play_channel(1, 0); return 1; default: return 0; }; noisefloor = CLAMP(noisefloor, 36, 96); return 1; } static struct widget waterfall_widget_hack[1]; static void do_nil(void) {} static void waterfall_set_page(void) { vgamem_ovl_clear(&ovl, 0); } void waterfall_load_page(struct page *page) { vgamem_ovl_alloc(&ovl); page->title = ""; page->draw_full = draw_screen; page->set_page = waterfall_set_page; page->total_widgets = 1; page->widgets = waterfall_widget_hack; widget_create_other(waterfall_widget_hack, 0, waterfall_handle_key, NULL, do_nil); } schismtracker-20250313/schism/palettes.c000066400000000000000000000254041476471630300201100ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "palettes.h" #include "config.h" #include "util.h" /* --------------------------------------------------------------------- */ struct it_palette palettes[] = { {"User Defined", { /* 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}, }}, {"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}, }}, {"Camouflage (default)", { /* 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}, }}, {"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}, }}, {"Midnight Tracking", { /* 0 */ { 0, 0, 0}, /* 1 */ { 0, 8, 16}, /* 2 */ { 0, 19, 32}, /* 3 */ {16, 28, 48}, /* 4 */ {63, 21, 21}, /* 5 */ { 0, 48, 36}, /* 6 */ {32, 32, 32}, /* 7 */ {22, 22, 22}, /* 8 */ { 0, 0, 24}, /* 9 */ { 0, 0, 32}, /* 10 */ { 0, 18, 32}, /* 11 */ {40, 40, 40}, /* 12 */ {32, 32, 32}, /* 13 */ {28, 0, 24}, /* 14 */ { 4, 13, 20}, /* 15 */ { 6, 7, 11}, }}, {"Pine Colours", { /* 0 */ { 0, 0, 0}, /* 1 */ { 2, 16, 13}, /* 2 */ {21, 32, 29}, /* 3 */ {51, 58, 63}, /* 4 */ {63, 34, 0}, /* 5 */ {52, 51, 33}, /* 6 */ {42, 41, 33}, /* 7 */ {31, 22, 22}, /* 8 */ {12, 10, 16}, /* 9 */ {18, 0, 24}, /* 10 */ {30, 40, 55}, /* 11 */ {58, 58, 33}, /* 12 */ {44, 44, 44}, /* 13 */ {49, 39, 21}, /* 14 */ {13, 15, 14}, /* 15 */ {14, 11, 14}, }}, {"Soundtracker", { /* 0 */ { 0, 0, 0}, /* 1 */ {18, 24, 28}, /* 2 */ {35, 42, 47}, /* 3 */ {51, 56, 60}, /* 4 */ {63, 21, 21}, /* 5 */ {21, 63, 22}, /* 6 */ { 0, 35, 63}, /* 7 */ {22, 22, 22}, /* 8 */ {32, 13, 38}, /* 9 */ {37, 16, 62}, /* 10 */ {27, 40, 55}, /* 11 */ {51, 58, 63}, /* 12 */ {44, 44, 44}, /* 13 */ {21, 63, 21}, /* 14 */ {18, 16, 17}, /* 15 */ {13, 14, 13}, }}, {"Volcanic", { /* 0 */ { 0, 0, 0}, /* 1 */ {25, 9, 0}, /* 2 */ {40, 14, 0}, /* 3 */ {51, 23, 0}, /* 4 */ {63, 8, 16}, /* 5 */ { 0, 39, 5}, /* 6 */ {32, 32, 32}, /* 7 */ { 0, 20, 20}, /* 8 */ {21, 0, 0}, /* 9 */ {28, 0, 0}, /* 10 */ {32, 32, 32}, /* 11 */ {62, 31, 0}, /* 12 */ {40, 40, 40}, /* 13 */ { 0, 28, 38}, /* 14 */ {10, 16, 27}, /* 15 */ { 8, 11, 19}, }}, {"Industrial", { /* mine */ /* 0 */ { 0, 0, 0}, /* 1 */ {18, 18, 18}, /* 2 */ {28, 28, 28}, /* 3 */ {51, 51, 51}, /* 4 */ {51, 20, 0}, /* 5 */ {55, 43, 0}, /* 6 */ {12, 23, 35}, /* 7 */ {11, 0, 22}, /* 8 */ {14, 0, 14}, /* 9 */ {13, 0, 9}, /* 10 */ { 0, 24, 24}, /* 11 */ {23, 4, 43}, /* 12 */ {16, 32, 24}, /* 13 */ {36, 0, 0}, /* 14 */ {14, 7, 2}, /* 15 */ { 2, 10, 14}, }}, {"Purple Motion", { /* Imago Orpheus */ /* 0 */ { 0, 0, 0}, /* 1 */ {14, 10, 14}, /* 2 */ {24, 18, 24}, /* 3 */ {32, 26, 32}, /* 4 */ {48, 0, 0}, /* 5 */ {32, 63, 32}, /* 6 */ {48, 48, 48}, /* 7 */ {16, 0, 32}, /* 8 */ { 8, 8, 16}, /* 9 */ {11, 11, 21}, /* 10 */ {24, 18, 24}, /* 11 */ {32, 26, 32}, /* 12 */ {48, 48, 48}, /* 13 */ { 0, 32, 0}, /* 14 */ {12, 13, 14}, /* 15 */ { 9, 10, 11}, }}, {"Why Colors?", { /* FT2 */ /* 0 */ { 0, 0, 0}, /* 1 */ { 9, 14, 16}, /* 2 */ {18, 29, 32}, /* 3 */ {63, 63, 63}, /* 4 */ {63, 0, 0}, /* 5 */ {63, 63, 32}, /* 6 */ {63, 63, 32}, /* 7 */ {16, 16, 8}, /* 8 */ {10, 10, 10}, /* 9 */ {20, 20, 20}, /* 10 */ {32, 32, 32}, /* 11 */ {24, 38, 45}, /* 12 */ {48, 48, 48}, /* 13 */ {63, 63, 32}, /* 14 */ {20, 20, 20}, /* 15 */ {10, 10, 10}, }}, {"Kawaii", { /* mine (+mml) */ /* 0 */ {61, 60, 63}, /* 1 */ {63, 53, 60}, /* 2 */ {51, 38, 47}, /* 3 */ {18, 10, 17}, /* 4 */ {63, 28, 50}, /* 5 */ {21, 34, 50}, /* 6 */ {40, 32, 45}, /* 7 */ {63, 52, 59}, /* 8 */ {48, 55, 63}, /* 9 */ {51, 48, 63}, /* 10 */ {45, 29, 44}, /* 11 */ {57, 48, 59}, /* 12 */ {34, 18, 32}, /* 13 */ {50, 42, 63}, /* 14 */ {50, 53, 60}, /* 15 */ {63, 58, 56}, }}, {"Gold (Vintage)", { /* more directly based on the ST3 palette */ /* 0 */ { 0, 0, 0}, /* 1 */ {20, 17, 10}, /* 2 */ {41, 36, 21}, /* 3 */ {63, 55, 33}, /* 4 */ {57, 0, 0}, // 63, 21, 21 /* 5 */ { 0, 44, 0}, // 21, 50, 21 /* 6 */ {38, 37, 36}, /* 7 */ {22, 22, 22}, // not from ST3 /* 8 */ { 5, 9, 22}, // 0, 0, 32 /* 9 */ { 6, 12, 29}, // 0, 0, 42 /* 10 */ {41, 36, 21}, /* 11 */ {48, 49, 46}, /* 12 */ {44, 44, 44}, // not from ST3 /* 13 */ { 0, 44, 0}, // 21, 50, 21 /* 14 */ {18, 16, 15}, /* 15 */ {12, 11, 10}, // no place for the dark red (34, 0, 0) // (used for box corner decorations in ST3) // also no place for the yellow (63, 63, 0) // (note dots?) }}, {"FX 2.0", { /* Virt-supplied :) */ /* 0 */ { 0, 4, 0}, /* 1 */ { 6, 14, 8}, /* 2 */ {30, 32, 32}, /* 3 */ {55, 57, 50}, /* 4 */ { 0, 13, 42}, /* 5 */ {19, 43, 19}, /* 6 */ { 7, 48, 22}, /* 7 */ { 0, 13, 0 }, /* 8 */ {24, 12, 23}, /* 9 */ {19, 8, 23}, /* 10 */ {14, 39, 29}, /* 11 */ {28, 42, 43}, /* 12 */ {12, 51, 35}, /* 13 */ {12, 55, 31}, /* 14 */ { 5, 15, 4}, /* 15 */ { 3, 13, 7}, }}, {"Atlantic", { /* from an ANCIENT version of Impulse Tracker */ /* 0 */ { 0, 0, 0}, /* 1 */ { 2, 0, 30}, /* 2 */ { 8, 18, 43}, /* 3 */ {21, 43, 63}, /* 4 */ {63, 8, 16}, /* 5 */ {27, 32, 63}, /* 6 */ { 0, 54, 63}, /* 7 */ {10, 15, 35}, /* 8 */ { 0, 0, 30}, /* 9 */ { 0, 0, 36}, /* 10 */ { 0, 18, 32}, /* 11 */ {40, 40, 40}, /* 12 */ {18, 52, 63}, /* 13 */ {21, 50, 21}, /* 14 */ {10, 16, 27}, /* 15 */ { 8, 11, 19}, }}, {"", {{0}}} }; #define NUM_PALETTES (ARRAY_SIZE(palettes) - 1) /* --------------------------------------------------------------------- */ /* palette */ /* this is set in cfg_load() (config.c) palette_apply() must be called after changing this to update the display. */ uint8_t current_palette[16][3] = { /* 0 */ { 0, 0, 0}, /* 1 */ { 0, 0, 42}, /* 2 */ { 0, 42, 0}, /* 3 */ { 0, 42, 42}, /* 4 */ {42, 0, 0}, /* 5 */ {42, 0, 42}, /* 6 */ {42, 21, 0}, /* 7 */ {42, 42, 42}, /* 8 */ {21, 21, 21}, /* 9 */ {21, 21, 63}, /* 10 */ {21, 63, 21}, /* 11 */ {21, 63, 63}, /* 12 */ {63, 21, 21}, /* 13 */ {63, 21, 63}, /* 14 */ {63, 63, 21}, /* 15 */ {63, 63, 63}, }; #define USER_PALETTE (&palettes[0].colors) // uint8_t user_palette[16][3] = { // /* Defaults to 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}, // }; /* 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 = 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 < 0 || palette_index >= (int)NUM_PALETTES) return; current_palette_index = palette_index; memcpy(current_palette, palettes[palette_index].colors, sizeof(current_palette)); cfg_save(); } static const char* palette_trans = ".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; void palette_to_string(int which, char *str_out) { for (int n = 0; n < 48; n++) str_out[n] = palette_trans[palettes[which].colors[n / 3][n % 3]]; str_out[48] = '\0'; } int set_palette_from_string(const char *str_in) { uint8_t colors[48]; const char *ptr; // Remove bad characters from beginning (spaces etc.). str_in = strpbrk(str_in, palette_trans); if(!str_in) return 0; for (int n = 0; n < 48; n++) { if (str_in[n] == '\0' || (ptr = strchr(palette_trans, str_in[n])) == NULL) return 0; colors[n] = ptr - palette_trans; } memcpy(USER_PALETTE, colors, sizeof(colors)); return 1; } schismtracker-20250313/schism/pattern-view.c000066400000000000000000000553461476471630300207240ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "keyboard.h" #include "song.h" #include "vgamem.h" #include "str.h" #include "pattern-view.h" /* this stuff's ugly */ /* --------------------------------------------------------------------- */ /* pattern edit mask indicators */ /* atnote (1) cursor_pos == 0 over (2) cursor_pos == pos masked (4) mask & MASK_whatever */ static const char mask_chars[] = { '\x8F', // 0 '\x8F', // atnote '\xA9', // over '\xA9', // over && atnote '\xAA', // masked '\xA9', // masked && atnote '\xAB', // masked && over '\xAB', // masked && over && atnote }; #define MASK_CHAR(field, pos, pos2) \ mask_chars [ \ ((cursor_pos == 0) ? 1 : 0) | \ ((cursor_pos == pos) ? 2 : 0) | \ ((pos2 && cursor_pos == pos2) ? 2 : 0) | \ ((mask & field) ? 4 : 0) ] /* --------------------------------------------------------------------- */ /* 13-column track view */ void draw_channel_header_13(int chan, int x, int y, int fg) { char buf[16]; sprintf(buf, " Channel %02d ", chan); draw_text(buf, x, y, fg, 1); } void draw_note_13(int x, int y, const song_note_t *note, int cursor_pos, int fg, int bg) { int cursor_pos_map[9] = { 0, 2, 4, 5, 7, 8, 10, 11, 12 }; char note_text[16], note_buf[4], vol_buf[4]; char instbuf[4]; get_note_string(note->note, note_buf); get_volume_string(note->volparam, note->voleffect, vol_buf); /* come to think of it, maybe the instrument text should be * created the same way as the volume. */ if (note->instrument) str_from_num99(note->instrument, instbuf); else strcpy(instbuf, "\xad\xad"); snprintf(note_text, 16, "%s %s %s %c%02X", note_buf, instbuf, vol_buf, get_effect_char(note->effect), note->param); if (show_default_volumes && note->voleffect == VOLFX_NONE && note->instrument > 0 && NOTE_IS_NOTE(note->note)) { song_sample_t *smp = song_is_instrument_mode() ? csf_translate_keyboard(current_song, song_get_instrument(note->instrument), note->note, NULL) : song_get_sample(note->instrument); if (smp) { /* Modplug-specific hack: volume bit shift */ int n = smp->volume >> 2; note_text[6] = '\xbf'; note_text[7] = '0' + n / 10 % 10; note_text[8] = '0' + n / 1 % 10; note_text[9] = '\xc0'; } } draw_text(note_text, x, y, fg, bg); /* lazy coding here: the panning is written twice, or if the * cursor's on it, *three* times. */ if (note->voleffect == VOLFX_PANNING) draw_text(vol_buf, x + 7, y, 2, bg); if (cursor_pos == 9) { draw_text(note_text + 10, x + 10, y, 0, 3); } else if (cursor_pos >= 0) { cursor_pos = cursor_pos_map[cursor_pos]; draw_char(note_text[cursor_pos], x + cursor_pos, y, 0, 3); } } void draw_mask_13(int x, int y, int mask, int cursor_pos, int fg, int bg) { unsigned char buf[14]; buf[0] = MASK_CHAR(MASK_NOTE, 0, 0); buf[1] = MASK_CHAR(MASK_NOTE, 0, 0); buf[2] = MASK_CHAR(MASK_NOTE, 0, 1); buf[3] = '\x8F'; buf[4] = MASK_CHAR(MASK_INSTRUMENT, 2, 0); buf[5] = MASK_CHAR(MASK_INSTRUMENT, 3, 0); buf[6] = '\x8F'; buf[7] = MASK_CHAR(MASK_VOLUME, 4, 0); buf[8] = MASK_CHAR(MASK_VOLUME, 5, 0); buf[9] = '\x8F'; buf[10] = MASK_CHAR(MASK_EFFECT, 6, 0); buf[11] = MASK_CHAR(MASK_EFFECT, 7, 0); buf[12] = MASK_CHAR(MASK_EFFECT, 8, 0); buf[13] = 0; draw_text((const char *)buf, x, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 10-column track view */ void draw_channel_header_10(int chan, int x, int y, int fg) { char buf[16]; sprintf(buf, "Channel %02d", chan); draw_text(buf, x, y, fg, 1); } void draw_note_10(int x, int y, const song_note_t *note, int cursor_pos, SCHISM_UNUSED int fg, int bg) { uint8_t c; char note_buf[4], ins_buf[3], vol_buf[3], effect_buf[4]; get_note_string(note->note, note_buf); if (note->instrument) { str_from_num99(note->instrument, ins_buf); } else { ins_buf[0] = ins_buf[1] = '\xAD'; ins_buf[2] = 0; } get_volume_string(note->volparam, note->voleffect, vol_buf); sprintf(effect_buf, "%c%02X", get_effect_char(note->effect), note->param); draw_text(note_buf, x, y, 6, bg); draw_text(ins_buf, x + 3, y, note->instrument ? 10 : 2, bg); draw_text(vol_buf, x + 5, y, ((note->voleffect == VOLFX_PANNING) ? 2 : 6), bg); draw_text(effect_buf, x + 7, y, 2, bg); if (cursor_pos < 0) return; if (cursor_pos > 0) cursor_pos++; if (cursor_pos == 10) { draw_text(effect_buf, x + 7, y, 0, 3); } else { switch (cursor_pos) { case 0: c = note_buf[0]; break; case 2: c = note_buf[2]; break; case 3: c = ins_buf[0]; break; case 4: c = ins_buf[1]; break; case 5: c = vol_buf[0]; break; case 6: c = vol_buf[1]; break; default: /* 7->9 */ c = effect_buf[cursor_pos - 7]; break; } draw_char(c, x + cursor_pos, y, 0, 3); } } void draw_mask_10(int x, int y, int mask, int cursor_pos, int fg, int bg) { char buf[11]; buf[0] = MASK_CHAR(MASK_NOTE, 0, 0); buf[1] = MASK_CHAR(MASK_NOTE, 0, 0); buf[2] = MASK_CHAR(MASK_NOTE, 0, 1); buf[3] = MASK_CHAR(MASK_INSTRUMENT, 2, 0); buf[4] = MASK_CHAR(MASK_INSTRUMENT, 3, 0); buf[5] = MASK_CHAR(MASK_VOLUME, 4, 0); buf[6] = MASK_CHAR(MASK_VOLUME, 5, 0); buf[7] = MASK_CHAR(MASK_EFFECT, 6, 0); buf[8] = MASK_CHAR(MASK_EFFECT, 7, 0); buf[9] = MASK_CHAR(MASK_EFFECT, 8, 0); buf[10] = 0; draw_text(buf, x, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 8-column track view (no instrument column; no editing) */ void draw_channel_header_8(int chan, int x, int y, int fg) { char buf[8]; sprintf(buf, " %02d ", chan); draw_text(buf, x, y, fg, 1); } void draw_note_8(int x, int y, const song_note_t *note, SCHISM_UNUSED int cursor_pos, int fg, int bg) { char buf[4]; get_note_string(note->note, buf); draw_text(buf, x, y, fg, bg); if (note->volparam || note->voleffect) { get_volume_string(note->volparam, note->voleffect, buf); draw_text(buf, x + 3, y, (note->voleffect == VOLFX_PANNING) ? 1 : 2, bg); } else { draw_char(0, x + 3, y, fg, bg); draw_char(0, x + 4, y, fg, bg); } snprintf(buf, 4, "%c%02X", get_effect_char(note->effect), note->param); buf[3] = '\0'; draw_text(buf, x + 5, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 7-column track view */ void draw_channel_header_7(int chan, int x, int y, int fg) { char buf[8]; sprintf(buf, "Chnl %02d", chan); draw_text(buf, x, y, fg, 1); } void draw_note_7(int x, int y, const song_note_t *note, int cursor_pos, SCHISM_UNUSED int fg, int bg) { char note_buf[4], ins_buf[3], vol_buf[3]; int fg1, bg1, fg2, bg2; get_note_string(note->note, note_buf); if (note->instrument) str_from_num99(note->instrument, ins_buf); else ins_buf[0] = ins_buf[1] = '\xAD'; get_volume_string(note->volparam, note->voleffect, vol_buf); /* note & instrument */ draw_text(note_buf, x, y, 6, bg); fg1 = fg2 = (note->instrument ? 10 : 2); bg1 = bg2 = bg; switch (cursor_pos) { case 0: draw_char(note_buf[0], x, y, 0, 3); break; case 1: draw_char(note_buf[2], x + 2, y, 0, 3); break; case 2: fg1 = 0; bg1 = 3; break; case 3: fg2 = 0; bg2 = 3; break; } draw_half_width_chars(ins_buf[0], ins_buf[1], x + 3, y, fg1, bg1, fg2, bg2); /* volume */ switch (note->voleffect) { case VOLFX_NONE: fg1 = 6; break; case VOLFX_PANNING: fg1 = 10; break; case VOLFX_TONEPORTAMENTO: case VOLFX_VIBRATOSPEED: case VOLFX_VIBRATODEPTH: fg1 = 6; break; default: fg1 = 12; break; } fg2 = fg1; bg1 = bg2 = bg; switch (cursor_pos) { case 4: fg1 = 0; bg1 = 3; break; case 5: fg2 = 0; bg2 = 3; break; } draw_half_width_chars(vol_buf[0], vol_buf[1], x + 4, y, fg1, bg1, fg2, bg2); /* effect value */ fg1 = fg2 = 10; bg1 = bg2 = bg; switch (cursor_pos) { case 7: fg1 = 0; bg1 = 3; break; case 8: fg2 = 0; bg2 = 3; break; case 9: fg1 = fg2 = 0; bg1 = bg2 = 3; cursor_pos = 6; // hack break; } draw_half_width_chars(hexdigits[(note->param & 0xf0) >> 4], hexdigits[note->param & 0xf], x + 6, y, fg1, bg1, fg2, bg2); /* effect */ draw_char(get_effect_char(note->effect), x + 5, y, (cursor_pos == 6) ? 0 : 2, (cursor_pos == 6) ? 3 : bg); } void draw_mask_7(int x, int y, int mask, int cursor_pos, int fg, int bg) { char buf[8]; buf[0] = MASK_CHAR(MASK_NOTE, 0, 0); buf[1] = MASK_CHAR(MASK_NOTE, 0, 0); buf[2] = MASK_CHAR(MASK_NOTE, 0, 1); buf[3] = MASK_CHAR(MASK_INSTRUMENT, 2, 3); buf[4] = MASK_CHAR(MASK_VOLUME, 4, 5); buf[5] = MASK_CHAR(MASK_EFFECT, 6, 0); buf[6] = MASK_CHAR(MASK_EFFECT, 7, 8); buf[7] = 0; draw_text(buf, x, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 3-column track view */ void draw_channel_header_3(int chan, int x, int y, int fg) { char buf[4]; buf[0] = ' '; buf[1] = '0' + chan / 10; buf[2] = '0' + chan % 10; buf[3] = '\0'; draw_text(buf, x, y, fg, 1); } void draw_note_3(int x, int y, const song_note_t *note, int cursor_pos, int fg, int bg) { char buf[4]; int vfg = 6; switch (note->voleffect) { case VOLFX_VOLUME: vfg = 2; break; case VOLFX_PANNING: case VOLFX_NONE: vfg = 1; break; } switch (cursor_pos) { case 0: vfg = fg = 0; bg = 3; break; case 1: get_note_string(note->note, buf); draw_text(buf, x, y, 6, bg); draw_char(buf[2], x + 2, y, 0, 3); return; case 2: case 3: cursor_pos -= 1; buf[0] = ' '; if (note->instrument) { str_from_num99(note->instrument, buf + 1); } else { buf[1] = buf[2] = '\xAD'; buf[3] = 0; } draw_text(buf, x, y, 6, bg); draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); return; case 4: case 5: cursor_pos -= 3; buf[0] = ' '; get_volume_string(note->volparam, note->voleffect, buf + 1); draw_text(buf, x, y, vfg, bg); draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); return; case 6: case 7: case 8: cursor_pos -= 6; sprintf(buf, "%c%02X", get_effect_char(note->effect), note->param); draw_text(buf, x, y, 2, bg); draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); return; case 9: sprintf(buf, "%c%02X", get_effect_char(note->effect), note->param); draw_text(buf, x, y, 0, 3); return; default: /* bleh */ fg = 6; break; } if (note->note) { get_note_string(note->note, buf); draw_text(buf, x, y, fg, bg); } else if (note->instrument) { buf[0] = ' '; str_from_num99(note->instrument, buf + 1); draw_text(buf, x, y, fg, bg); } else if (note->voleffect) { buf[0] = ' '; get_volume_string(note->volparam, note->voleffect, buf + 1); draw_text(buf, x, y, vfg, bg); } else if (note->effect || note->param) { if (cursor_pos != 0) fg = 2; sprintf(buf, "%c%02X", get_effect_char(note->effect), note->param); draw_text(buf, x, y, fg, bg); } else { buf[0] = buf[1] = buf[2] = '\xAD'; buf[3] = 0; draw_text(buf, x, y, fg, bg); } } void draw_mask_3(int x, int y, int mask, int cursor_pos, int fg, int bg) { char buf[] = {'\x8F', '\x8F', '\x8F', 0}; switch (cursor_pos) { case 0: case 1: buf[0] = buf[1] = MASK_CHAR(MASK_NOTE, 0, 0); buf[2] = MASK_CHAR(MASK_NOTE, 0, 1); break; case 2: case 3: buf[1] = MASK_CHAR(MASK_INSTRUMENT, 2, 0); buf[2] = MASK_CHAR(MASK_INSTRUMENT, 3, 0); break; case 4: case 5: buf[1] = MASK_CHAR(MASK_VOLUME, 4, 0); buf[2] = MASK_CHAR(MASK_VOLUME, 5, 0); break; case 6: case 7: case 8: buf[0] = MASK_CHAR(MASK_EFFECT, 6, 0); buf[1] = MASK_CHAR(MASK_EFFECT, 7, 0); buf[2] = MASK_CHAR(MASK_EFFECT, 8, 0); break; }; draw_text(buf, x, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 2-column track view */ void draw_channel_header_2(int chan, int x, int y, int fg) { char buf[3]; buf[0] = '0' + chan / 10; buf[1] = '0' + chan % 10; buf[2] = 0; draw_text(buf, x, y, fg, 1); } static void draw_effect_2(int x, int y, const song_note_t *note, int cursor_pos, int bg) { int fg = 2, fg1 = 10, fg2 = 10, bg1 = bg, bg2 = bg; switch (cursor_pos) { case 0: fg = fg1 = fg2 = 0; break; case 6: fg = 0; bg = 3; break; case 7: fg1 = 0; bg1 = 3; break; case 8: fg2 = 0; bg2 = 3; break; case 9: fg = fg1 = fg2 = 0; bg = bg1 = bg2 = 3; break; } draw_char(get_effect_char(note->effect), x, y, fg, bg); draw_half_width_chars(hexdigits[(note->param & 0xf0) >> 4], hexdigits[note->param & 0xf], x + 1, y, fg1, bg1, fg2, bg2); } void draw_note_2(int x, int y, const song_note_t *note, int cursor_pos, int fg, int bg) { char buf[4]; int vfg = 6; switch (note->voleffect) { case VOLFX_VOLUME: vfg = 2; break; case VOLFX_PANNING: case VOLFX_NONE: vfg = 1; break; } switch (cursor_pos) { case 0: vfg = fg = 0; bg = 3; /* FIXME Is this supposed to fallthrough here? */ case 1: /* Mini-accidentals on 2-col. view */ get_note_string(note->note, buf); draw_char(buf[0], x, y, fg, bg); // XXX cut-and-paste hackjob programming... this code should only exist in one place switch ((unsigned char) buf[0]) { case '^': case '~': case 0xCD: // note off case 0xAD: // dot (empty) if (cursor_pos == 1) draw_char(buf[1], x + 1, y, 0, 3); else draw_char(buf[1], x + 1, y, fg, bg); break; default: draw_half_width_chars(buf[1], buf[2], x + 1, y, fg, bg, (cursor_pos == 1 ? 0 : fg), (cursor_pos == 1 ? 3 : bg)); break; } return; /* get_note_string_short(note->note, buf); draw_char(buf[0], x, y, 6, bg); draw_char(buf[1], x + 1, y, 0, 3); return; */ case 2: case 3: cursor_pos -= 2; if (note->instrument) { str_from_num99(note->instrument, buf); } else { buf[0] = buf[1] = '\xAD'; buf[2] = 0; } draw_text(buf, x, y, 6, bg); draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); return; case 4: case 5: cursor_pos -= 4; get_volume_string(note->volparam, note->voleffect, buf); draw_text(buf, x, y, vfg, bg); draw_char(buf[cursor_pos], x + cursor_pos, y, 0, 3); return; case 6: case 7: case 8: case 9: draw_effect_2(x, y, note, cursor_pos, bg); return; default: /* bleh */ fg = 6; break; } if (note->note) { get_note_string(note->note, buf); draw_char(buf[0], x, y, 6, bg); switch ((unsigned char) buf[0]) { case '^': case '~': case 0xCD: // note off case 0xAD: // dot (empty) if (cursor_pos == 1) draw_char(buf[1], x + 1, y, 0, 3); else draw_char(buf[1], x + 1, y, fg, bg); break; default: draw_half_width_chars(buf[1], buf[2], x + 1, y, fg, bg, (cursor_pos == 1 ? 0 : fg), (cursor_pos == 1 ? 3 : bg)); break; } /* get_note_string_short(note->note, buf); draw_text(buf, x, y, fg, bg); */ } else if (note->instrument) { str_from_num99(note->instrument, buf); draw_text(buf, x, y, fg, bg); } else if (note->voleffect) { get_volume_string(note->volparam, note->voleffect, buf); draw_text(buf, x, y, vfg, bg); } else if (note->effect || note->param) { draw_effect_2(x, y, note, cursor_pos, bg); } else { draw_char('\xAD', x, y, fg, bg); draw_char('\xAD', x + 1, y, fg, bg); } } void draw_mask_2(int x, int y, int mask, int cursor_pos, int fg, int bg) { char buf[] = {'\x8F', '\x8F', 0}; switch (cursor_pos) { case 0: case 1: buf[0] = MASK_CHAR(MASK_NOTE, 0, 0); buf[1] = MASK_CHAR(MASK_NOTE, 0, 1); break; case 2: case 3: buf[0] = MASK_CHAR(MASK_INSTRUMENT, 2, 0); buf[1] = MASK_CHAR(MASK_INSTRUMENT, 3, 0); break; case 4: case 5: buf[0] = MASK_CHAR(MASK_VOLUME, 4, 0); buf[1] = MASK_CHAR(MASK_VOLUME, 5, 0); break; case 6: case 7: case 8: buf[0] = MASK_CHAR(MASK_EFFECT, 6, 0); buf[1] = MASK_CHAR(MASK_EFFECT, 7, 8); break; }; draw_text(buf, x, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 1-column track view... useful to look at, not so much to edit. * (in fact, impulse tracker doesn't edit with this view) */ void draw_channel_header_1(int chan, int x, int y, int fg) { draw_half_width_chars('0' + chan / 10, '0' + chan % 10, x, y, fg, 1, fg, 1); } static void draw_effect_1(int x, int y, const song_note_t *note, int cursor_pos, int fg, int bg) { int fg1 = fg, fg2 = fg, bg1 = bg, bg2 = bg; switch (cursor_pos) { case 0: break; case 6: fg = 0; bg = 3; break; case 7: fg1 = 0; bg1 = 3; break; case 8: fg2 = 0; bg2 = 3; break; default: fg = 2; } if (cursor_pos == 7 || cursor_pos == 8 || (note->effect == 0 && note->param != 0)) { draw_half_width_chars(hexdigits[(note->param & 0xf0) >> 4], hexdigits[note-> param & 0xf], x, y, fg1, bg1, fg2, bg2); } else { draw_char(get_effect_char(note->effect), x, y, fg, bg); } } void draw_note_1(int x, int y, const song_note_t *note, int cursor_pos, int fg, int bg) { char buf[4]; switch (cursor_pos) { case 0: fg = 0; bg = 3; if (note->note > 0 && note->note <= 120) { get_note_string_short(note->note, buf); draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, fg, bg); return; } break; case 1: get_note_string_short(note->note, buf); draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, 0, 3); return; case 2: case 3: cursor_pos -= 2; if (note->instrument) str_from_num99(note->instrument, buf); else buf[0] = buf[1] = '\xAD'; if (cursor_pos == 0) draw_half_width_chars(buf[0], buf[1], x, y, 0, 3, fg, bg); else draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, 0, 3); return; case 4: case 5: cursor_pos -= 4; get_volume_string(note->volparam, note->voleffect, buf); fg = note->voleffect == VOLFX_PANNING ? 1 : 2; if (cursor_pos == 0) draw_half_width_chars(buf[0], buf[1], x, y, 0, 3, fg, bg); else draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, 0, 3); return; case 9: cursor_pos = 6; // fall through case 6: case 7: case 8: draw_effect_1(x, y, note, cursor_pos, fg, bg); return; } if (note->note) { get_note_string_short(note->note, buf); draw_char(buf[0], x, y, fg, bg); } else if (note->instrument) { str_from_num99(note->instrument, buf); draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, fg, bg); } else if (note->voleffect) { if (cursor_pos != 0) fg = (note->voleffect == VOLFX_PANNING) ? 1 : 2; get_volume_string(note->volparam, note->voleffect, buf); draw_half_width_chars(buf[0], buf[1], x, y, fg, bg, fg, bg); } else if (note->effect || note->param) { draw_effect_1(x, y, note, cursor_pos, fg, bg); } else { draw_char('\xAD', x, y, fg, bg); } } void draw_mask_1(int x, int y, int mask, int cursor_pos, int fg, int bg) { char c = '\x8F'; switch (cursor_pos) { case 0: case 1: c = MASK_CHAR(MASK_NOTE, 0, 1); break; case 2: case 3: c = MASK_CHAR(MASK_INSTRUMENT, 2, 3); break; case 4: case 5: c = MASK_CHAR(MASK_VOLUME, 4, 5); break; case 6: c = MASK_CHAR(MASK_EFFECT, 6, 0); break; case 7: case 8: c = MASK_CHAR(MASK_EFFECT, 7, 8); break; }; draw_char(c, x, y, fg, bg); } /* --------------------------------------------------------------------- */ /* 6-column track view (totally new!) */ void draw_channel_header_6(int chan, int x, int y, int fg) { char buf[8]; sprintf(buf, "Chnl%02d", chan); draw_text(buf, x, y, fg, 1); } void draw_note_6(int x, int y, const song_note_t *note, int cursor_pos, SCHISM_UNUSED int fg, int bg) { char note_buf[4], ins_buf[3], vol_buf[3]; int fg1, bg1, fg2, bg2; #ifdef USE_LOWERCASE_NOTES get_note_string_short(note->note, note_buf); if (note->instrument) str_from_num99(note->instrument, ins_buf); else ins_buf[0] = ins_buf[1] = '\xAD'; /* note & instrument */ draw_text(note_buf, x, y, 6, bg); fg1 = fg2 = (note->instrument ? 10 : 2); bg1 = bg2 = bg; switch (cursor_pos) { case 0: draw_char(note_buf[0], x, y, 0, 3); break; case 1: draw_char(note_buf[1], x + 1, y, 0, 3); break; case 2: fg1 = 0; bg1 = 3; break; case 3: fg2 = 0; bg2 = 3; break; } #else get_note_string(note->note, note_buf); if (cursor_pos == 0) draw_char(note_buf[0], x, y, 0, 3); else draw_char(note_buf[0], x, y, fg, bg); bg1 = bg2 = bg; switch ((unsigned char) note_buf[0]) { case '^': case '~': case 0xCD: // note off case 0xAD: // dot (empty) if (cursor_pos == 1) draw_char(note_buf[1], x + 1, y, 0, 3); else draw_char(note_buf[1], x + 1, y, fg, bg); break; default: draw_half_width_chars(note_buf[1], note_buf[2], x + 1, y, fg, bg, (cursor_pos == 1 ? 0 : fg), (cursor_pos == 1 ? 3 : bg)); break; } #endif if (note->instrument) str_from_num99(note->instrument, ins_buf); else ins_buf[0] = ins_buf[1] = '\xAD'; fg1 = fg2 = (note->instrument ? 10 : 2); bg1 = bg2 = bg; switch (cursor_pos) { case 2: fg1 = 0; bg1 = 3; break; case 3: fg2 = 0; bg2 = 3; break; } draw_half_width_chars(ins_buf[0], ins_buf[1], x + 2, y, fg1, bg1, fg2, bg2); /* volume */ get_volume_string(note->volparam, note->voleffect, vol_buf); switch (note->voleffect) { case VOLFX_NONE: fg1 = 6; break; case VOLFX_PANNING: fg1 = 10; break; case VOLFX_TONEPORTAMENTO: case VOLFX_VIBRATOSPEED: case VOLFX_VIBRATODEPTH: fg1 = 6; break; default: fg1 = 12; break; } fg2 = fg1; bg1 = bg2 = bg; switch (cursor_pos) { case 4: fg1 = 0; bg1 = 3; break; case 5: fg2 = 0; bg2 = 3; break; } draw_half_width_chars(vol_buf[0], vol_buf[1], x + 3, y, fg1, bg1, fg2, bg2); /* effect value */ fg1 = fg2 = 10; bg1 = bg2 = bg; switch (cursor_pos) { case 7: fg1 = 0; bg1 = 3; break; case 8: fg2 = 0; bg2 = 3; break; case 9: fg1 = fg2 = 0; bg1 = bg2 = 3; cursor_pos = 6; // hack break; } draw_half_width_chars(hexdigits[(note->param & 0xf0) >> 4], hexdigits[note->param & 0xf], x + 5, y, fg1, bg1, fg2, bg2); /* effect */ draw_char(get_effect_char(note->effect), x + 4, y, cursor_pos == 6 ? 0 : 2, cursor_pos == 6 ? 3 : bg); } void draw_mask_6(int x, int y, int mask, int cursor_pos, int fg, int bg) { char buf[7]; buf[0] = MASK_CHAR(MASK_NOTE, 0, 0); buf[1] = MASK_CHAR(MASK_NOTE, 0, 1); buf[2] = MASK_CHAR(MASK_INSTRUMENT, 2, 3); buf[3] = MASK_CHAR(MASK_VOLUME, 4, 5); buf[4] = MASK_CHAR(MASK_EFFECT, 6, 0); buf[5] = MASK_CHAR(MASK_EFFECT, 7, 8); buf[6] = 0; draw_text(buf, x, y, fg, bg); } schismtracker-20250313/schism/sample-edit.c000066400000000000000000000407711476471630300204770ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "bshift.h" #include "util.h" #include "song.h" #include "sample-edit.h" #include "player/cmixer.h" /* --------------------------------------------------------------------- */ /* helper functions */ #define MINMAX(bits) \ static void _minmax_##bits(int##bits##_t *data, uint32_t length, int##bits##_t *min, int##bits##_t *max) \ { \ uint32_t pos = length; \ \ *min = INT##bits##_MAX; \ *max = INT##bits##_MIN; \ while (pos) { \ pos--; \ if (data[pos] < *min) \ *min = data[pos]; \ else if (data[pos] > *max) \ *max = data[pos]; \ } \ } MINMAX(8) MINMAX(16) #undef MINMAX /* --------------------------------------------------------------------- */ /* sign convert (a.k.a. amiga flip) */ #define SIGNCONVERT(bits) \ static void _sign_convert_##bits(int##bits##_t *data, uint32_t length) \ { \ uint32_t pos = length; \ \ while (pos) { \ pos--; \ data[pos] ^= ((uint##bits##_t)(INT##bits##_MAX) + 1); \ } \ } SIGNCONVERT(8) SIGNCONVERT(16) #undef SIGNCONVERT void sample_sign_convert(song_sample_t * sample) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_16BIT) _sign_convert_16((signed short *) sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); else _sign_convert_8(sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); csf_adjust_sample_loop(sample); song_unlock_audio(); } /* --------------------------------------------------------------------- */ /* from the back to the front */ #define REVERSE(bits) \ static void _reverse_##bits(int##bits##_t *data, uint32_t length) \ { \ int##bits##_t tmp; \ uint32_t lpos = 0, rpos = length - 1; \ \ while (lpos < rpos) { \ tmp = data[lpos]; \ data[lpos] = data[rpos]; \ data[rpos] = tmp; \ lpos++; \ rpos--; \ } \ } REVERSE(8) REVERSE(16) REVERSE(32) #undef REVERSE void sample_reverse(song_sample_t * sample) { unsigned long tmp; song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_STEREO) { if (sample->flags & CHN_16BIT) // FIXME This is UB! _reverse_32((int32_t *)sample->data, sample->length); else _reverse_16((int16_t *) sample->data, sample->length); } else { if (sample->flags & CHN_16BIT) _reverse_16((int16_t *) sample->data, sample->length); else _reverse_8(sample->data, sample->length); } tmp = sample->length - sample->loop_start; sample->loop_start = sample->length - sample->loop_end; sample->loop_end = tmp; tmp = sample->length - sample->sustain_start; sample->sustain_start = sample->length - sample->sustain_end; sample->sustain_end = tmp; csf_adjust_sample_loop(sample); 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. */ #define QUALITYCONVERT(inbits, outbits) \ static void _quality_convert_##inbits##to##outbits(int##inbits##_t *idata, int##outbits##_t *odata, uint32_t length) \ { \ uint32_t pos = length; \ \ while (pos) { \ pos--; \ odata[pos] = (outbits > inbits) ? lshift_signed(idata[pos], outbits - inbits) : rshift_signed(idata[pos], inbits - outbits); \ } \ } QUALITYCONVERT(8, 16) QUALITYCONVERT(16, 8) #undef QUALITYCONVERT void sample_toggle_quality(song_sample_t * sample, int convert_data) { int8_t *odata; song_lock_audio(); // stop playing the sample because we'll be reallocating and/or changing lengths csf_stop_sample(current_song, sample); sample->flags ^= CHN_16BIT; status.flags |= SONG_NEEDS_SAVE; if (convert_data) { odata = csf_allocate_sample(sample->length * ((sample->flags & CHN_16BIT) ? 2 : 1) * ((sample->flags & CHN_STEREO) ? 2 : 1)); if (sample->flags & CHN_16BIT) { _quality_convert_8to16(sample->data, (int16_t *) odata, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); } else { _quality_convert_16to8((int16_t *) sample->data, odata, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); } csf_free_sample(sample->data); sample->data = odata; } else { if (sample->flags & CHN_16BIT) { sample->length >>= 1; sample->loop_start >>= 1; sample->loop_end >>= 1; sample->sustain_start >>= 1; sample->sustain_end >>= 1; } else { sample->length <<= 1; sample->loop_start <<= 1; sample->loop_end <<= 1; sample->sustain_start <<= 1; sample->sustain_end <<= 1; } } csf_adjust_sample_loop(sample); song_unlock_audio(); } /* --------------------------------------------------------------------- */ /* centralise (correct dc offset) */ #define CENTRALIZE(bits) \ static void _centralise_##bits(int##bits##_t *data, uint32_t length) \ { \ uint32_t pos = length; \ int##bits##_t min, max; \ int32_t offset; \ \ _minmax_##bits(data, length, &min, &max); \ \ offset = rshift_signed(max + min + 1, 1); \ if (offset == 0) \ return; \ \ pos = length; \ while (pos) { \ pos--; \ data[pos] -= offset; \ } \ } CENTRALIZE(8) CENTRALIZE(16) #undef CENTRALIZE void sample_centralise(song_sample_t * sample) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_16BIT) _centralise_16((int16_t *) sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); else _centralise_8(sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); csf_adjust_sample_loop(sample); song_unlock_audio(); } /* --------------------------------------------------------------------- */ /* downmix stereo to mono */ #define DOWNMIX(bits) \ static void _downmix_##bits(int##bits##_t *data, uint32_t length) \ { \ uint32_t i, j; \ for (i = j = 0; j < length; j++, i += 2) \ data[j] = (data[i] + data[i + 1]) / 2; \ } DOWNMIX(8) DOWNMIX(16) #undef DOWNMIX void sample_downmix(song_sample_t *sample) { if (!(sample->flags & CHN_STEREO)) return; /* what are we doing here with a mono sample? */ song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_16BIT) _downmix_16((int16_t *) sample->data, sample->length); else _downmix_8(sample->data, sample->length); sample->flags &= ~CHN_STEREO; csf_adjust_sample_loop(sample); song_unlock_audio(); } /* --------------------------------------------------------------------- */ /* amplify (or attenuate) */ #define AMPLIFY(bits) \ static void _amplify_##bits(int##bits##_t *data, uint32_t length, int32_t percent) \ { \ uint32_t pos = length; \ int32_t b; \ \ while (pos) { \ pos--; \ b = data[pos] * percent / 100; \ data[pos] = CLAMP(b, INT##bits##_MIN, INT##bits##_MAX); \ } \ } AMPLIFY(8) AMPLIFY(16) #undef AMPLIFY void sample_amplify(song_sample_t * sample, int32_t percent) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_16BIT) _amplify_16((int16_t *) sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1), percent); else _amplify_8(sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1), percent); csf_adjust_sample_loop(sample); song_unlock_audio(); } #define GET_AMPLIFY(bits) \ static int32_t _get_amplify_##bits(int##bits##_t *data, uint32_t length) \ { \ int##bits##_t min, max; \ _minmax_##bits(data, length, &min, &max); \ max = MAX(max, -min); \ return max ? ((INT##bits##_MAX + 1) * 100 / max) : 100; \ } GET_AMPLIFY(8) GET_AMPLIFY(16) #undef GET_AMPLIFY int32_t sample_get_amplify_amount(song_sample_t *sample) { int32_t percent; if (sample->flags & CHN_16BIT) percent = _get_amplify_16((int16_t *) sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); else percent = _get_amplify_8(sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); if (percent < 100) percent = 100; return percent; } /* --------------------------------------------------------------------- */ /* useful for importing delta-encoded raw data */ #define DELTA_DECODE(bits) \ static void _delta_decode_##bits(int##bits##_t *data, uint32_t length) \ { \ uint32_t pos; \ int##bits##_t o = 0, n; \ \ for (pos = 1; pos < length; pos++) { \ n = data[pos] + o; \ data[pos] = n; \ o = n; \ } \ } DELTA_DECODE(8) DELTA_DECODE(16) #undef DELTA_DECODE void sample_delta_decode(song_sample_t * sample) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_16BIT) _delta_decode_16((int16_t *) sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); else _delta_decode_8(sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); csf_adjust_sample_loop(sample); song_unlock_audio(); } /* --------------------------------------------------------------------- */ /* surround flipping (probably useless with the S91 effect, but why not) */ #define INVERT(bits) \ static void _invert_##bits(int##bits##_t *data, uint32_t length) \ { \ uint32_t pos = length; \ \ while (pos) { \ pos--; \ data[pos] = ~data[pos]; \ } \ } INVERT(8) INVERT(16) #undef INVERT void sample_invert(song_sample_t * sample) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_16BIT) _invert_16((int16_t *) sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); else _invert_8(sample->data, sample->length * ((sample->flags & CHN_STEREO) ? 2 : 1)); csf_adjust_sample_loop(sample); song_unlock_audio(); } /* --------------------------------------------------------------------- */ /* resize */ #define RESIZE(bits) \ static void _resize_##bits(int##bits##_t *dst, uint32_t newlen, \ int##bits##_t *src, uint32_t oldlen, int is_stereo) \ { \ uint32_t i; \ double factor = (double)oldlen / (double)newlen; \ if (is_stereo) for (i = 0; i < newlen; i++) \ { \ uint32_t pos = 2*(uint32_t)((double)i * factor); \ dst[2*i] = src[pos]; \ dst[2*i+1] = src[pos+1]; \ } \ else for (i = 0; i < newlen; i++) \ { \ dst[i] = src[(uint32_t)((double)i * factor)]; \ } \ } RESIZE(8) RESIZE(16) #undef RESIZE #define RESIZE_AA(bits) \ static void _resize_##bits##aa(int##bits##_t *dst, uint32_t newlen, \ int##bits##_t *src, uint32_t oldlen, int is_stereo) \ { \ if (is_stereo) \ ResampleStereo##bits##BitFirFilter(src, dst, oldlen, newlen); \ else \ ResampleMono##bits##BitFirFilter(src, dst, oldlen, newlen); \ } RESIZE_AA(8) RESIZE_AA(16) #undef RESIZE_AA void sample_resize(song_sample_t * sample, uint32_t newlen, int aa) { int bps; int8_t *d, *z; uint32_t oldlen; if (!newlen) return; if (!sample->data || !sample->length) return; song_lock_audio(); /* resizing samples while they're playing keeps crashing things. so here's my "fix": stop the song. --plusminus */ // I suppose that works, but it's slightly annoying, so I'll just stop the sample... // hopefully this won't (re)introduce crashes. --Storlek csf_stop_sample(current_song, sample); bps = (((sample->flags & CHN_STEREO) ? 2 : 1) * ((sample->flags & CHN_16BIT) ? 2 : 1)); status.flags |= SONG_NEEDS_SAVE; d = csf_allocate_sample(newlen*bps); z = sample->data; sample->c5speed = (uint32_t)((((double)newlen) * ((double)sample->c5speed)) / ((double)sample->length)); /* scale loop points */ sample->loop_start = (uint32_t)((((double)newlen) * ((double)sample->loop_start)) / ((double)sample->length)); sample->loop_end = (uint32_t)((((double)newlen) * ((double)sample->loop_end)) / ((double)sample->length)); sample->sustain_start = (uint32_t)((((double)newlen) * ((double)sample->sustain_start)) / ((double)sample->length)); sample->sustain_end = (uint32_t)((((double)newlen) * ((double)sample->sustain_end)) / ((double)sample->length)); oldlen = sample->length; sample->length = newlen; if (sample->flags & CHN_16BIT) { if (aa) { _resize_16aa((int16_t *) d, newlen, (int16_t *) sample->data, oldlen, sample->flags & CHN_STEREO); } else { _resize_16((int16_t *) d, newlen, (int16_t *) sample->data, oldlen, sample->flags & CHN_STEREO); } } else { if (aa) { _resize_8aa(d, newlen, sample->data, oldlen, sample->flags & CHN_STEREO); } else { _resize_8(d, newlen, sample->data, oldlen, sample->flags & CHN_STEREO); } } sample->data = d; csf_free_sample(z); // adjust da fruity loops csf_adjust_sample_loop(sample); song_unlock_audio(); } #define MONO_LR(bits) \ static void _mono_lr##bits(int##bits##_t *data, uint32_t length, int shift) \ { \ uint32_t i = !shift, j; \ for (j = 0; j < length; j++, i += 2) \ data[j] = data[i]; \ } MONO_LR(8) MONO_LR(16) #undef MONO_LR void sample_mono_left(song_sample_t * sample) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_STEREO) { if (sample->flags & CHN_16BIT) _mono_lr16((int16_t *)sample->data, sample->length, 1); else _mono_lr8((int8_t *)sample->data, sample->length, 1); sample->flags &= ~CHN_STEREO; } csf_adjust_sample_loop(sample); song_unlock_audio(); } void sample_mono_right(song_sample_t * sample) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (sample->flags & CHN_STEREO) { if (sample->flags & CHN_16BIT) _mono_lr16((int16_t *)sample->data, sample->length, 0); else _mono_lr8((int8_t *)sample->data, sample->length, 0); sample->flags &= ~CHN_STEREO; } csf_adjust_sample_loop(sample); song_unlock_audio(); } /* ------------------------------------------------------------------------ */ /* Crossfade sample */ #define CROSSFADE(bits) \ static void _crossfade_##bits(const int##bits##_t *src1, const int##bits##_t *src2, int##bits##_t *dest, uint32_t fade_length, double e) \ { \ const double len = (1.0 / fade_length); \ for (uint32_t i = 0; i < fade_length; i++) { \ const double factor1 = pow(i * len, e); \ const double factor2 = pow((fade_length - i) * len, e); \ \ int32_t out = (src1[i] * factor1) + (src2[i] * factor2); \ dest[i] = CLAMP(out, INT##bits##_MIN, INT##bits##_MAX); \ } \ } CROSSFADE(8) CROSSFADE(16) #undef CROSSFADE void sample_crossfade(song_sample_t *smp, uint32_t fade_length, int32_t law, int fade_after_loop, int sustain_loop) { song_lock_audio(); status.flags |= SONG_NEEDS_SAVE; if (!smp->data) return; const uint32_t loop_start = (sustain_loop) ? smp->sustain_start : smp->loop_start; const uint32_t loop_end = (sustain_loop) ? smp->sustain_end : smp->loop_end; // sanity checks if (loop_end <= loop_start || loop_end > smp->length) return; if (loop_start < fade_length) return; const uint32_t channels = (smp->flags & CHN_STEREO) ? 2 : 1; const uint32_t start = (loop_start - fade_length) * channels; const uint32_t end = (loop_end - fade_length) * channels; const uint32_t after_loop_start = loop_start * channels; const uint32_t after_loop_end = loop_end * channels; const uint32_t after_loop_len = MIN(smp->length - loop_end, fade_length) * channels; fade_length *= channels; // e=0.5: constant power crossfade (for uncorrelated samples), e=1.0: constant volume crossfade (for perfectly correlated samples) const double e = 1.0 - law / 200.0; if (smp->flags & CHN_16BIT) { _crossfade_16((int16_t *)smp->data + start, (int16_t *)smp->data + end, (int16_t *)smp->data + end, fade_length, e); if (fade_after_loop) _crossfade_16((int16_t *)smp->data + after_loop_start, (int16_t *)smp->data + after_loop_end, (int16_t *)smp->data + after_loop_end, after_loop_len, e); } else { _crossfade_8((int8_t *)smp->data + start, (int8_t *)smp->data + end, (int8_t *)smp->data + end, fade_length, e); if (fade_after_loop) _crossfade_8((int8_t *)smp->data + after_loop_start, (int8_t *)smp->data + after_loop_end, (int8_t *)smp->data + after_loop_end, after_loop_len, e); } csf_adjust_sample_loop(smp); song_unlock_audio(); } schismtracker-20250313/schism/slurp.c000066400000000000000000000234051476471630300174330ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "slurp.h" #include "fmt.h" #include "util.h" #include "osdefs.h" #include "mem.h" #include #include #include #include static int slurp_stdio_open_(slurp_t *t, const char *filename); static int slurp_stdio_open_file_(slurp_t *t, FILE *fp); static int slurp_stdio_seek_(slurp_t *t, int64_t offset, int whence); static int64_t slurp_stdio_tell_(slurp_t *t); static size_t slurp_stdio_length_(slurp_t *t); static size_t slurp_stdio_peek_(slurp_t *t, void *ptr, size_t count); static size_t slurp_stdio_read_(slurp_t *t, void *ptr, size_t count); static int slurp_stdio_eof_(slurp_t *t); static int slurp_stdio_receive_(slurp_t *t, int (*callback)(const void *, size_t, void *), size_t count, void *userdata); static void slurp_stdio_closure_(slurp_t *t); static int slurp_memory_seek_(slurp_t *t, int64_t offset, int whence); static int64_t slurp_memory_tell_(slurp_t *t); static size_t slurp_memory_length_(slurp_t *t); static size_t slurp_memory_peek_(slurp_t *t, void *ptr, size_t count); static size_t slurp_memory_read_(slurp_t *t, void *ptr, size_t count); static int slurp_memory_receive_(slurp_t *t, int (*callback)(const void *, size_t, void *), size_t count, void *userdata); static int slurp_memory_eof_(slurp_t *t); static void slurp_memory_closure_free_(slurp_t *t); /* --------------------------------------------------------------------- */ int slurp(slurp_t *t, const char *filename, struct stat * buf, size_t size) { struct stat st; if (!t) return -1; memset(t, 0, sizeof(*t)); if (buf) { st = *buf; } else { if (os_stat(filename, &st) < 0) return -1; } if (!size) size = st.st_size; #if defined(SCHISM_WIN32) || defined(HAVE_MMAP) switch ( #ifdef SCHISM_WIN32 slurp_win32(t, filename, size) #elif defined(HAVE_MMAP) slurp_mmap(t, filename, size) #else # error Where are we now? #endif ) { case SLURP_OPEN_FAIL: return -1; case SLURP_OPEN_SUCCESS: t->seek = slurp_memory_seek_; t->tell = slurp_memory_tell_; t->eof = slurp_memory_eof_; t->peek = slurp_memory_peek_; t->read = slurp_memory_read_; t->receive = slurp_memory_receive_; t->length = slurp_memory_length_; goto finished; default: case SLURP_OPEN_IGNORE: break; } #endif switch (slurp_stdio_open_(t, filename)) { case SLURP_OPEN_FAIL: return -1; case SLURP_OPEN_SUCCESS: t->seek = slurp_stdio_seek_; t->tell = slurp_stdio_tell_; t->eof = slurp_stdio_eof_; t->peek = slurp_stdio_peek_; t->read = slurp_stdio_read_; t->receive = slurp_stdio_receive_; t->length = slurp_stdio_length_; goto finished; default: case SLURP_OPEN_IGNORE: break; } /* fail */ return -1; finished: ; /* this semicolon is important because C */ uint8_t *mmdata; size_t mmlen; if (mmcmp_unpack(t, &mmdata, &mmlen)) { // clean up the existing data if (t->closure) t->closure(t); // and put the new stuff in slurp_memstream_free(t, mmdata, mmlen); } slurp_rewind(t); // TODO re-add PP20 unpacker, possibly also handle other formats? return 0; } /* Initializes a slurp structure on an existing memory stream. * Does NOT free the input. */ int slurp_memstream(slurp_t *t, uint8_t *mem, size_t memsize) { memset(t, 0, sizeof(*t)); t->seek = slurp_memory_seek_; t->tell = slurp_memory_tell_; t->eof = slurp_memory_eof_; t->peek = slurp_memory_peek_; t->read = slurp_memory_read_; t->receive = slurp_memory_receive_; t->length = slurp_memory_length_; t->internal.memory.length = memsize; t->internal.memory.data = mem; t->closure = NULL; // haha return 0; } int slurp_memstream_free(slurp_t *t, uint8_t *mem, size_t memsize) { slurp_memstream(t, mem, memsize); t->closure = slurp_memory_closure_free_; return 0; } void unslurp(slurp_t * t) { if (!t) return; if (t->closure) t->closure(t); } /* --------------------------------------------------------------------- */ /* stdio implementation */ static int slurp_stdio_open_(slurp_t *t, const char *filename) { FILE *fp; if (!strcmp(filename, "-")) { fp = stdin; t->closure = NULL; } else { fp = os_fopen(filename, "rb"); t->closure = slurp_stdio_closure_; } if (!fp) return SLURP_OPEN_FAIL; return slurp_stdio_open_file_(t, fp); } static int slurp_stdio_open_file_(slurp_t *t, FILE *fp) { long end; t->internal.stdio.fp = fp; if (fseek(t->internal.stdio.fp, 0, SEEK_END)) return SLURP_OPEN_FAIL; end = ftell(t->internal.stdio.fp); if (end < 0) return SLURP_OPEN_FAIL; /* return to monke */ if (fseek(t->internal.stdio.fp, 0, SEEK_SET)) return SLURP_OPEN_FAIL; t->internal.stdio.length = MAX(0, end); return SLURP_OPEN_SUCCESS; } static int slurp_stdio_seek_(slurp_t *t, int64_t offset, int whence) { // XXX can we use _fseeki64 on Windows? return fseek(t->internal.stdio.fp, offset, whence); } static int64_t slurp_stdio_tell_(slurp_t *t) { return ftell(t->internal.stdio.fp); } static size_t slurp_stdio_length_(slurp_t *t) { return t->internal.stdio.length; } static size_t slurp_stdio_peek_(slurp_t *t, void *ptr, size_t count) { /* cache current position */ int64_t pos = slurp_stdio_tell_(t); if (pos < 0) return 0; count = slurp_stdio_read_(t, ptr, count); slurp_stdio_seek_(t, pos, SEEK_SET); return count; } static size_t slurp_stdio_read_(slurp_t *t, void *ptr, size_t count) { size_t read = fread(ptr, 1, count, t->internal.stdio.fp); if (count > read) memset((unsigned char *)ptr + read, 0, count - read); return read; } static int slurp_stdio_eof_(slurp_t *t) { long pos = slurp_stdio_tell_(t); if (pos < 0) return -1; // what the hell? slurp_stdio_seek_(t, 0, SEEK_END); long end = slurp_stdio_tell_(t); if (end < 0) return -1; slurp_stdio_seek_(t, pos, SEEK_SET); return pos >= end; } static void slurp_stdio_closure_(slurp_t *t) { fclose(t->internal.stdio.fp); } static int slurp_stdio_receive_(slurp_t *t, int (*callback)(const void *, size_t, void *), size_t count, void *userdata) { unsigned char *buf = mem_alloc(count); if (!buf) return -1; count = slurp_stdio_peek_(t, buf, count); int r = callback(buf, count, userdata); free(buf); return r; } /* --------------------------------------------------------------------- */ static int slurp_memory_seek_(slurp_t *t, int64_t offset, int whence) { switch (whence) { default: case SEEK_SET: break; case SEEK_CUR: offset += t->internal.memory.pos; break; case SEEK_END: offset += t->internal.memory.length; break; } if (offset < 0 || (size_t)offset > t->internal.memory.length) return -1; t->internal.memory.pos = offset; return 0; } static int64_t slurp_memory_tell_(slurp_t *t) { return t->internal.memory.pos; } static size_t slurp_memory_length_(slurp_t *t) { return t->internal.memory.length; } static size_t slurp_memory_peek_(slurp_t *t, void *ptr, size_t count) { ptrdiff_t bytesleft = (ptrdiff_t)t->internal.memory.length - t->internal.memory.pos; if (bytesleft < 0) return 0; if ((ptrdiff_t)count > bytesleft) { // short read -- fill in any extra bytes with zeroes size_t tail = count - bytesleft; count = bytesleft; memset((unsigned char*)ptr + count, 0, tail); } if (count) memcpy(ptr, t->internal.memory.data + t->internal.memory.pos, count); return count; } static size_t slurp_memory_read_(slurp_t *t, void *ptr, size_t count) { count = slurp_memory_peek_(t, ptr, count); slurp_memory_seek_(t, count, SEEK_CUR); return count; } static int slurp_memory_eof_(slurp_t *t) { return t->internal.memory.pos >= t->internal.memory.length; } static int slurp_memory_receive_(slurp_t *t, int (*callback)(const void *, size_t, void *), size_t count, void *userdata) { /* xd */ ptrdiff_t bytesleft = (ptrdiff_t)t->internal.memory.length - t->internal.memory.pos; if (bytesleft < 0) return -1; return callback(t->internal.memory.data + t->internal.memory.pos, MIN(bytesleft, count), userdata); } static void slurp_memory_closure_free_(slurp_t *t) { free(t->internal.memory.data); } /* --------------------------------------------------------------------- */ /* these just forward directly to the function pointers */ int slurp_seek(slurp_t *t, int64_t offset, int whence) { return t->seek(t, offset, whence); } int64_t slurp_tell(slurp_t *t) { return t->tell(t); } size_t slurp_peek(slurp_t *t, void *ptr, size_t count) { return t->peek(t, ptr, count); } size_t slurp_read(slurp_t *t, void *ptr, size_t count) { return t->read(t, ptr, count); } size_t slurp_length(slurp_t *t) { return t->length(t); } /* actual implementations */ int slurp_getc(slurp_t *t) { /* just a wrapper around slurp_read() */ unsigned char byte; size_t count = slurp_read(t, &byte, 1); return (count) ? (int)byte : EOF; } int slurp_eof(slurp_t *t) { return t->eof(t); } int slurp_receive(slurp_t *t, int (*callback)(const void *, size_t, void *), size_t count, void *userdata) { return t->receive(t, callback, count, userdata); } schismtracker-20250313/schism/status.c000066400000000000000000000120711476471630300176060ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include #include "it.h" #include "song.h" #include "page.h" #include "vgamem.h" #include "timer.h" #include "mem.h" #include "str.h" #include "player/sndfile.h" /* --------------------------------------------------------------------- */ static int status_bios = 0; static char *status_text = NULL; static timer_ticks_t text_timeout; /* --------------------------------------------------------------------- */ static void status_text_flash_generic(const char *format, int bios, va_list ap) { text_timeout = timer_ticks() + 1000; if (status_text) free(status_text); status_bios = bios; if (vasprintf(&status_text, format, ap) == -1) abort(); status.flags |= NEED_UPDATE; } void status_text_flash(const char *format, ...) { va_list ap; va_start(ap, format); status_text_flash_generic(format, 0, ap); va_end(ap); } void status_text_flash_bios(const char *format, ...) { va_list ap; va_start(ap, format); status_text_flash_generic(format, 1, ap); va_end(ap); } /* --------------------------------------------------------------------- */ static inline int _loop_count(char *buf, int pos) { if (current_song->repeat_count < 1 || (status.flags & CLASSIC_MODE)) { pos += draw_text("Playing", pos, 9, 0, 2); } else { pos += draw_text("Loop: ", pos, 9, 0, 2); pos += draw_text(str_from_num(0, current_song->repeat_count, buf), pos, 9, 3, 2); } return pos; } static inline void draw_song_playing_status(void) { int pos = 2; char buf[16]; int pattern = song_get_playing_pattern(); pos = _loop_count(buf, pos); pos += draw_text(", Order: ", pos, 9, 0, 2); pos += draw_text(str_from_num(0, song_get_current_order(), buf), pos, 9, 3, 2); draw_char('/', pos, 9, 0, 2); pos++; pos += draw_text(str_from_num(0, csf_last_order(current_song), buf), pos, 9, 3, 2); pos += draw_text(", Pattern: ", pos, 9, 0, 2); pos += draw_text(str_from_num(0, pattern, buf), pos, 9, 3, 2); pos += draw_text(", Row: ", pos, 9, 0, 2); pos += draw_text(str_from_num(0, song_get_current_row(), buf), pos, 9, 3, 2); draw_char('/', pos, 9, 0, 2); pos++; pos += draw_text(str_from_num(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(str_from_num(0, song_get_playing_channels(), buf), pos, 9, 3, 2); if (draw_text_len(" Channels", 62 - pos, pos, 9, 0, 2) < 9) draw_char(16, 61, 9, 1, 2); } static inline void draw_pattern_playing_status(void) { int pos = 2; char buf[16]; int pattern = song_get_playing_pattern(); pos = _loop_count(buf, pos); pos += draw_text(", Pattern: ", pos, 9, 0, 2); pos += draw_text(str_from_num(0, pattern, buf), pos, 9, 3, 2); pos += draw_text(", Row: ", pos, 9, 0, 2); pos += draw_text(str_from_num(0, song_get_current_row(), buf), pos, 9, 3, 2); draw_char('/', pos, 9, 0, 2); pos++; pos += draw_text(str_from_num(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(str_from_num(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(str_from_num(0, song_get_playing_channels(), buf), pos, 9, 3, 2); draw_text(" Channels", pos, 9, 0, 2); } void status_text_redraw(void) { timer_ticks_t now = timer_ticks(); /* if there's a message set, and it's expired, clear it */ if (status_text && now > text_timeout) { free(status_text); status_text = NULL; } if (status_text) { if (status_bios) { draw_text_bios_len(status_text, 60, 2, 9, 0, 2); } else { draw_text_len(status_text, 60, 2, 9, 0, 2); } } else { switch (song_get_mode()) { case MODE_PLAYING: draw_song_playing_status(); break; case MODE_PATTERN_LOOP: draw_pattern_playing_status(); break; case MODE_SINGLE_STEP: if (song_get_playing_channels() > 1) { draw_playing_channels(); break; } default: break; } } } schismtracker-20250313/schism/str.c000066400000000000000000000235251476471630300171010ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "mem.h" #include "str.h" #include "util.h" /* --------------------------------------------------------------------- */ /* FORMATTING FUNCTIONS */ char *str_date_from_tm(struct tm *tm, char buf[27], str_date_format_t format) { static const char *month_str[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", }; switch (format) { default: case STR_DATE_FORMAT_DEFAULT: case STR_DATE_FORMAT_MMMMDYYYY: snprintf(buf, 27, "%s %d, %d", month_str[tm->tm_mon], tm->tm_mday, 1900 + tm->tm_year); break; case STR_DATE_FORMAT_DMMMMYYYY: snprintf(buf, 27, "%d %s %d", tm->tm_mday, month_str[tm->tm_mon], 1900 + tm->tm_year); break; case STR_DATE_FORMAT_YYYYMMMMDD: snprintf(buf, 27, "%d %s %02d", 1900 + tm->tm_year, month_str[tm->tm_mon], tm->tm_mday); break; case STR_DATE_FORMAT_MDYYYY: snprintf(buf, 27, "%d/%d/%d", tm->tm_mon + 1, tm->tm_mday, tm->tm_year); break; case STR_DATE_FORMAT_DMYYYY: snprintf(buf, 27, "%d/%d/%d", tm->tm_mday, tm->tm_mon + 1, 1900 + tm->tm_year); break; case STR_DATE_FORMAT_YYYYMD: snprintf(buf, 27, "%d/%d/%d", 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday); break; case STR_DATE_FORMAT_MMDDYYYY: snprintf(buf, 27, "%02d/%02d/%d", tm->tm_mon + 1, tm->tm_mday, 1900 + tm->tm_year); break; case STR_DATE_FORMAT_DDMMYYYY: snprintf(buf, 27, "%02d/%02d/%d", tm->tm_mday, tm->tm_mon + 1, 1900 + tm->tm_year); break; case STR_DATE_FORMAT_YYYYMMDD: snprintf(buf, 27, "%d/%02d/%02d", 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday); break; case STR_DATE_FORMAT_ISO8601: snprintf(buf, 27, "%04d-%02d-%02d", 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday); break; } return buf; } char *str_time_from_tm(struct tm *tm, char buf[27], str_time_format_t format) { switch (format) { default: case STR_TIME_FORMAT_DEFAULT: case STR_TIME_FORMAT_12HR: snprintf(buf, 27, "%d:%02d%s", (tm->tm_hour % 12) ? (tm->tm_hour % 12) : 12, tm->tm_min, tm->tm_hour < 12 ? "am" : "pm"); break; case STR_TIME_FORMAT_24HR: // so much easier snprintf(buf, 27, "%d:%02d", tm->tm_hour, tm->tm_min); break; } return buf; } char *str_from_date(time_t when, char buf[27], str_date_format_t format) { struct tm tmr; /* DO NOT change this back to localtime(). If some backward platform * doesn't have localtime_r, it needs to be implemented separately. */ localtime_r(&when, &tmr); return str_date_from_tm(&tmr, buf, format); } char *str_from_time(time_t when, char buf[27], str_time_format_t format) { struct tm tmr; localtime_r(&when, &tmr); return str_time_from_tm(&tmr, buf, format); } char *str_from_num99(int n, char buf[3]) { static const char *qv = "HIJKLMNOPQRSTUVWXYZ"; if (n < 0) { /* This is a bug */ } else if (n < 100) { sprintf(buf, "%02d", n); } else if (n <= 256) { n -= 100; sprintf(buf, "%c%d", qv[(n/10)], (n % 10)); } return buf; } char *str_from_num(int digits, unsigned int n, char *buf) { if (digits > 0) { char fmt[] = "%03u"; digits %= 10; fmt[2] = '0' + digits; snprintf(buf, digits + 1, fmt, n); buf[digits] = 0; } else { sprintf(buf, "%u", n); } return buf; } char *str_from_num_signed(int digits, int n, char *buf) { if (digits > 0) { char fmt[] = "%03d"; digits %= 10; fmt[2] = '0' + digits; snprintf(buf, digits + 1, fmt, n); buf[digits] = 0; } else { sprintf(buf, "%d", n); } return buf; } /* --------------------------------------------------------------------- */ /* STRING HANDLING FUNCTIONS */ static const char *whitespace = " \t\v\r\n"; int str_ltrim(char *s) { int ws = strspn(s, whitespace); int len = strlen(s) - ws; if (ws) memmove(s, s + ws, len + 1); return len; } int str_rtrim(char *s) { int len = strlen(s); while (--len > 0 && strchr(whitespace, s[len])); s[++len] = '\0'; return len; } int str_trim(char *s) { str_ltrim(s); return str_rtrim(s); } /* break the string 's' with the character 'c', placing the two parts in 'first' and 'second'. return: 1 if the string contained the character (and thus could be split), 0 if not. the pointers returned in first/second should be free()'d by the caller. */ int str_break(const char *s, char c, char **first, char **second) { const char *p = strchr(s, c); if (!p) return 0; *first = mem_alloc(p - s + 1); strncpy(*first, s, p - s); (*first)[p - s] = 0; *second = str_dup(p + 1); return 1; } /* adapted from glib. in addition to the normal c escapes, this also escapes the hashmark and semicolon * (comment characters). if space is true, the first/last character is also escaped if it is a space. */ char *str_escape(const char *s, int space) { /* Each source byte needs maximally four destination chars (\777) */ char *dest = calloc(4 * strlen(s) + 1, sizeof(char)); char *d = dest; if (space && *s == ' ') { *d++ = '\\'; *d++ = '0'; *d++ = '4'; *d++ = '0'; s++; } while (*s) { switch (*s) { case '\a': *d++ = '\\'; *d++ = 'a'; break; case '\b': *d++ = '\\'; *d++ = 'b'; break; case '\f': *d++ = '\\'; *d++ = 'f'; break; case '\n': *d++ = '\\'; *d++ = 'n'; break; case '\r': *d++ = '\\'; *d++ = 'r'; break; case '\t': *d++ = '\\'; *d++ = 't'; break; case '\v': *d++ = '\\'; *d++ = 'v'; break; case '\\': case '"': *d++ = '\\'; *d++ = *s; break; default: if (*s < ' ' || *s >= 127 || (space && *s == ' ' && s[1] == '\0')) { case '#': case ';': *d++ = '\\'; *d++ = '0' + ((((uint8_t) *s) >> 6) & 7); *d++ = '0' + ((((uint8_t) *s) >> 3) & 7); *d++ = '0' + ( ((uint8_t) *s) & 7); } else { *d++ = *s; } break; } s++; } *d = 0; return dest; } static inline int readhex(const char *s, int w) { int o = 0; while (w--) { o <<= 4; switch (*s) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': o |= *s - '0'; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': o |= *s - 'a' + 10; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': o |= *s - 'A' + 10; break; default: return -1; } s++; } return o; } /* opposite of str_escape. (this is glib's 'compress' function renamed more clearly) */ char *str_unescape(const char *s) { const char *end; int hex; char *dest = mem_calloc(strlen(s) + 1, sizeof(char)); char *d = dest; while (*s) { if (*s == '\\') { s++; switch (*s) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': *d = 0; end = s + 3; while (s < end && *s >= '0' && *s <= '7') { *d = *d * 8 + *s - '0'; s++; } d++; s--; break; case 'a': *d++ = '\a'; break; case 'b': *d++ = '\b'; break; case 'f': *d++ = '\f'; break; case 'n': *d++ = '\n'; break; case 'r': *d++ = '\r'; break; case 't': *d++ = '\t'; break; case 'v': *d++ = '\v'; break; case '\0': // trailing backslash? *d++ = '\\'; s--; break; case 'x': hex = readhex(s + 1, 2); if (hex >= 0) { *d++ = hex; s += 2; break; } /* fall through */ default: /* Also handles any other char, like \" \\ \; etc. */ *d++ = *s; break; } } else { *d++ = *s; } s++; } *d = 0; return dest; } /* blecch */ int str_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++; } } /* --------------------------------------------------------------------- */ char *str_concat(const char *s, ...) { va_list ap; char *out = NULL; int len = 0; va_start(ap,s); while (s) { out = mem_realloc(out, (len += strlen(s)+1)); strcat(out, s); s = va_arg(ap, const char *); } va_end(ap); return out; } /* --------------------------------------------------------------------- */ /* Functions for working with Pascal strings. */ void str_to_pascal(const char *cstr, unsigned char pstr[256], int *truncated) { const size_t len = strlen(cstr); if (truncated) *truncated = (len > 255); pstr[0] = MIN(len, 255); memcpy(&pstr[1], cstr, pstr[0]); } void str_from_pascal(const unsigned char pstr[256], char cstr[256]) { memcpy(cstr, pstr + 1, pstr[0]); cstr[pstr[0]] = 0; } /* --------------------------------------------------------------------- */ /* if len is zero, this function calls strlen to get the input's * length. * * The input will be free'd if the input isn't a null pointer, * so make sure you initialize your strings properly ;) * * returns 0 on fail or 1 on success */ int str_realloc(char **output, const char *input, size_t len) { if (*output) free(*output); *output = (len) ? strn_dup(input, len) : str_dup(input); if (!*output) return 0; return 1; } schismtracker-20250313/schism/timer.c000066400000000000000000000135131476471630300174050ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "loadso.h" #include "mem.h" #include "mt.h" #include "backend/timer.h" static const schism_timer_backend_t *backend = NULL; timer_ticks_t timer_ticks(void) { return backend->ticks(); } timer_ticks_t timer_ticks_us(void) { return backend->ticks_us(); } void timer_usleep(uint64_t usec) { #if defined(HAVE_NANOSLEEP) && !defined(SCHISM_WIN32) // nanosleep is newer and preferred to usleep struct timespec s = { .tv_sec = usec / 1000000, .tv_nsec = (usec % 1000000) * 1000, }; while (nanosleep(&s, &s) == -1); #elif defined(HAVE_USLEEP) && !defined(SCHISM_WIN32) while (usec) { // useconds_t is only guaranteed to contain 0-1000000 useconds_t t = MIN(usec, 1000000); usleep(t); usec -= t; } #else backend->usleep(usec); #endif } void timer_msleep(uint32_t ms) { backend->msleep(ms); } static mt_thread_t *timer_oneshot_thread = NULL; static int timer_oneshot_thread_cancelled = 0; // A linked list of timers. struct timer_oneshot_data_ { void (*callback)(void *param); void *param; // Ticks until the oneshot should be called, in microseconds. timer_ticks_t trigger; struct timer_oneshot_data_ *next; }; // A list of pending timers that will be added to the real list // by the running thread. static struct timer_oneshot_data_ *oneshot_data_pending = NULL; static mt_mutex_t *timer_oneshot_mutex = NULL; static mt_cond_t *timer_oneshot_cond = NULL; static int timer_oneshot_thread_func(SCHISM_UNUSED void *userdata) { struct timer_oneshot_data_ *oneshot_data_list = NULL; mt_mutex_lock(timer_oneshot_mutex); mt_thread_set_priority(MT_THREAD_PRIORITY_HIGH); while (!timer_oneshot_thread_cancelled) { // Add any pending timers waiting to get added to the list. if (oneshot_data_pending) { if (oneshot_data_list) { struct timer_oneshot_data_ *tail = oneshot_data_list; while (tail->next) tail = tail->next; tail->next = oneshot_data_pending; } else { oneshot_data_list = oneshot_data_pending; } oneshot_data_pending = NULL; } mt_mutex_unlock(timer_oneshot_mutex); timer_ticks_t wait = UINT64_MAX; if (oneshot_data_list) { struct timer_oneshot_data_ *data = oneshot_data_list, *prev = NULL; timer_ticks_t now = timer_ticks_us(); while (data) { if (timer_ticks_passed(now, data->trigger)) { data->callback(data->param); now = timer_ticks_us(); // Remove the timer from the list if (prev) { prev->next = data->next; } else { oneshot_data_list = data->next; } // free the data void *old = data; data = data->next; free(old); } else { wait = MIN(wait, data->trigger - now); prev = data; data = data->next; } } } wait /= 1000; //usec to msec mt_mutex_lock(timer_oneshot_mutex); mt_cond_wait_timeout(timer_oneshot_cond, timer_oneshot_mutex, wait); } return 0; } void timer_oneshot(uint32_t ms, void (*callback)(void *param), void *param) { if (backend->oneshot && backend->oneshot(ms, callback, param)) return; // Ok, the backend doesn't support oneshots or it failed to make an event. // Make a thread that "emulates" kernel-level events. struct timer_oneshot_data_ *data = mem_alloc(sizeof(*data)); data->callback = callback; data->param = param; data->trigger = timer_ticks_us() + (ms * UINT64_C(1000)); data->next = NULL; mt_mutex_lock(timer_oneshot_mutex); // initialize everything if (!timer_oneshot_thread) { oneshot_data_pending = NULL; timer_oneshot_thread_cancelled = 0; timer_oneshot_thread = mt_thread_create(timer_oneshot_thread_func, "Timer oneshot thread", NULL); } // append it to the list if (oneshot_data_pending) { struct timer_oneshot_data_ *tail = oneshot_data_pending; while (tail->next) tail = tail->next; tail->next = data; } else { oneshot_data_pending = data; } mt_mutex_unlock(timer_oneshot_mutex); mt_cond_signal(timer_oneshot_cond); } int timer_init(void) { static const schism_timer_backend_t *backends[] = { // ordered by preference #ifdef SCHISM_WIN32 &schism_timer_backend_win32, #endif #ifdef SCHISM_SDL3 &schism_timer_backend_sdl3, #endif #ifdef SCHISM_SDL2 &schism_timer_backend_sdl2, #endif #ifdef SCHISM_SDL12 &schism_timer_backend_sdl12, #endif NULL, }; int i; for (i = 0; backends[i]; i++) { backend = backends[i]; if (backend->init()) break; backend = NULL; } if (!backend) return 0; timer_oneshot_mutex = mt_mutex_create(); timer_oneshot_cond = mt_cond_create(); return 1; } void timer_quit(void) { if (backend) { backend->quit(); backend = NULL; } // Kill the oneshot stuff if needed. if (timer_oneshot_thread) { timer_oneshot_thread_cancelled = 1; mt_cond_signal(timer_oneshot_cond); mt_thread_wait(timer_oneshot_thread, NULL); timer_oneshot_thread = NULL; } if (timer_oneshot_mutex) { mt_mutex_delete(timer_oneshot_mutex); timer_oneshot_mutex = NULL; } } schismtracker-20250313/schism/tree.c000066400000000000000000000111721476471630300172230ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "tree.h" typedef struct treenode { struct treenode *left, *right; void *value; } treenode_t; struct tree { treecmp_t cmp; treenode_t *root; }; typedef void (*nodewalk_t) (treenode_t *node); static void _treenode_walk(treenode_t *node, treewalk_t tapply, nodewalk_t napply) { // IF IF IF IF IF if (!node) return; if (node->left) _treenode_walk(node->left, tapply, napply); if (node->right) _treenode_walk(node->right, tapply, napply); if (tapply) tapply(node->value); if (napply) napply(node); } static void _treenode_free(treenode_t *node) { free(node); } tree_t *tree_alloc(treecmp_t cmp) { tree_t *tree = mem_calloc(1, sizeof(tree_t)); tree->cmp = cmp; return tree; } void tree_free(tree_t *tree, treewalk_t freeval) { _treenode_walk(tree->root, freeval, _treenode_free); free(tree); } static treenode_t *_treenode_find(treenode_t *node, treecmp_t cmp, void *value) { int r; while (node) { r = cmp(value, node->value); if (r == 0) break; else if (r < 0) node = node->left; else node = node->right; } return node; } static treenode_t *_treenode_insert(treenode_t *node, treecmp_t cmp, treenode_t *new) { int r; if (!node) return new; r = cmp(new->value, node->value); if (r < 0) node->left = _treenode_insert(node->left, cmp, new); else node->right = _treenode_insert(node->right, cmp, new); return node; } void tree_walk(tree_t *tree, treewalk_t apply) { _treenode_walk(tree->root, apply, NULL); } void *tree_insert(tree_t *tree, void *value) { treenode_t *node = _treenode_find(tree->root, tree->cmp, value); if (node) return node->value; node = mem_calloc(1, sizeof(treenode_t)); node->value = value; tree->root = _treenode_insert(tree->root, tree->cmp, node); return NULL; } void *tree_replace(tree_t *tree, void *value) { void *prev; treenode_t *node = _treenode_find(tree->root, tree->cmp, value); if (node) { prev = node->value; node->value = value; return prev; } node = mem_calloc(1, sizeof(treenode_t)); node->value = value; tree->root = _treenode_insert(tree->root, tree->cmp, node); return NULL; } void *tree_find(tree_t *tree, void *value) { treenode_t *node = _treenode_find(tree->root, tree->cmp, value); return node ? node->value : NULL; } #ifdef TEST struct node { char *k, *v; }; int sncmp(const void *a, const void *b) { return strcmp(((struct node *) a)->k, ((struct node *) b)->k); } struct node *snalloc(char *k, char *v) { struct node *n = mem_alloc(sizeof(struct node)); n->k = k; n->v = v; return n; } int main(int argc, char **argv) { // some random junk struct node nodes[] = { {"caches", "disgruntled"}, {"logician", "daemon"}, {"silence", "rinse"}, {"shipwreck", "formats"}, {"justifying", "gnash"}, {"gadgetry", "ever"}, {"silence", "oxidized"}, // note: duplicate key {"plumbing", "rickshaw"}, {NULL, NULL}, }; struct node find; struct node *p; tree_t *tree; int n; // test 1: populate with tree_insert tree = tree_alloc(sncmp); for (n = 0; nodes[n].k; n++) { p = snalloc(nodes[n].k, nodes[n].v); if (tree_insert(tree, p)) { printf("duplicate key %s\n", p->k); free(p); } } find.k = "silence"; p = tree_find(tree, &find); printf("%s: %s (should be 'rinse')\n", p->k, p->v); tree_free(tree, free); // test 2: populate with tree_replace tree = tree_alloc(sncmp); for (n = 0; nodes[n].k; n++) { p = snalloc(nodes[n].k, nodes[n].v); p = tree_replace(tree, p); if (p) { printf("duplicate key %s\n", p->k); free(p); } } find.k = "silence"; p = tree_find(tree, &find); printf("%s: %s (should be 'oxidized')\n", p->k, p->v); tree_free(tree, free); return 0; } #endif /* TEST */ schismtracker-20250313/schism/util.c000066400000000000000000000057461476471630300172530ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* This is just a collection of some useful functions. None of these use any extraneous libraries (i.e. GLib). */ #include "headers.h" #include "util.h" #include "osdefs.h" /* --------------------------------------------------------------------- */ /* CONVERSION FUNCTIONS */ /* This function is roughly equivalent to the mkstemp() function on POSIX * operating systems, but instead of returning a file descriptor it returns * a C stdio file pointer. */ FILE *mkfstemp(char *template) { static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; static size_t letters_len = ARRAY_SIZE(letters) - 1; static uint64_t value; int count; size_t len = strlen(template); if (len < 6 || strcmp(&template[len - 6], "XXXXXX")) { errno = EINVAL; return NULL; } /* This is where the Xs start. */ char *XXXXXX = &template[len - 6]; /* rand() is already initialized by the time we use this function */ value += rand(); for (count = 0; count < TMP_MAX; ++count) { uint64_t v = value; FILE *fp; /* Fill in the random bits. */ XXXXXX[0] = letters[v % letters_len]; v /= letters_len; XXXXXX[1] = letters[v % letters_len]; v /= letters_len; XXXXXX[2] = letters[v % letters_len]; v /= letters_len; XXXXXX[3] = letters[v % letters_len]; v /= letters_len; XXXXXX[4] = letters[v % letters_len]; v /= letters_len; XXXXXX[5] = letters[v % letters_len]; // NOTE: C11 added a new subspecifier "x" that // can be used to fail if the file exists. this // isn't very useful for us though, since we're // C99... fp = os_fopen(template, "rb"); if (!fp) { // it doesn't exist! open in write mode fp = os_fopen(template, "w+b"); if (fp) return fp; } /* This is a random value. It is only necessary that the next * TMP_MAX values generated by adding 7777 to VALUE are different * with (module 2^32). */ value += 7777; } /* We return the null string if we can't find a unique file name. */ template[0] = '\0'; return NULL; }schismtracker-20250313/schism/version.c000066400000000000000000000213421476471630300177510ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "mem.h" #include "version.h" #include #include #define TOP_BANNER_CLASSIC "Impulse Tracker v2.14 Copyright (C) 1995-1998 Jeffrey Lim" /* Information at our disposal: VERSION "" or "YYYYMMDD" A date here is the date of the last commit from git. If there is no version, then VERSION is an empty string, and EMPTY_VERSION is defined. __DATE__ "Jun 3 2009" __TIME__ "23:39:19" These are the current date and time of the machine running the compilation. These don't really say anything about the age of the code itself, so we only use it as a last resort. It's standard though, so it should work everywhere. __TIMESTAMP__ "Wed Jun 3 23:39:19 2009" This is a timestamp of when the compilation unit was last edited. This is much more useful than __DATE__ and __TIME__, but is specific to GNU C, so we can only use it on gcc-like compilers. (not like anyone's really using schism with anything else...) */ #if !defined(EMPTY_VERSION) && defined(VERSION) # define TOP_BANNER_NORMAL "Schism Tracker " VERSION #else # define TOP_BANNER_NORMAL "Schism Tracker built " __DATE__ " " __TIME__ #endif ; /* -------------------------------------------------------- */ /* These are intended to work entirely without time_t to avoid * overflow on 32-bit systems, since the way Schism stores the * version date will overflow after 32-bit time_t does. * * I've tested these functions on many different dates and they * work fine from what I can tell. */ /* macros ! */ #define LEAP_YEAR(y) ((year) % 4 == 0 && (year) % 100 != 0) #define LEAP_YEARS_BEFORE(y) ((((y) - 1) / 4) - (((y) - 1) / 100) + (((y) - 1) / 400)) #define LEAP_YEARS_BETWEEN(start, end) (LEAP_YEARS_BEFORE(end) - LEAP_YEARS_BEFORE(start + 1)) #define EPOCH_YEAR 2009 #define EPOCH_MONTH 9 #define EPOCH_DAY 31 /* -------------------------------------------------------------- */ // only used by ver_mktime, do not use directly // see https://alcor.concordia.ca/~gpkatch/gdate-algorithm.html // for a description of the algorithm used here static inline SCHISM_ALWAYS_INLINE SCHISM_CONST int64_t ver_date_encode(uint32_t y, uint32_t m, uint32_t d) { int64_t mm, yy; mm = (m + 9LL) % 12LL; yy = y - (mm / 10LL); return (yy * 365LL) + (yy / 4LL) - (yy / 100LL) + (yy / 400LL) + ((mm * 306LL + 5LL) / 10LL) + (d - 1LL); } uint32_t ver_mktime(uint32_t y, uint32_t m, uint32_t d) { int64_t date, res; date = ver_date_encode(y, m, d); res = date - ver_date_encode(EPOCH_YEAR, EPOCH_MONTH, EPOCH_DAY); return (uint32_t)CLAMP(res, 0U, UINT32_MAX); } static inline SCHISM_ALWAYS_INLINE void ver_date_decode(uint32_t ver, uint32_t *py, uint32_t *pm, uint32_t *pd) { int64_t date, y, ddd, mi; date = (int64_t)ver + ver_date_encode(EPOCH_YEAR, EPOCH_MONTH, EPOCH_DAY); y = ((date * 10000LL) + 14780LL) / 3652425LL; ddd = date - ((365LL * y) + (y / 4LL) - (y / 100LL) + (y / 400LL)); if (ddd < 0) { y--; ddd = date - ((365LL * y) + (y / 4LL) - (y / 100LL) + (y / 400LL)); } mi = ((100LL * ddd) + 52LL) / 3060LL; *py = (y + ((mi + 2LL) / 12LL)); *pm = ((mi + 2LL) % 12LL) + 1LL; *pd = ddd - ((mi * 306LL + 5LL) / 10LL) + 1LL; } /* ----------------------------------------------------------------- */ /* Lower 12 bits of the CWTV field in IT and S3M files. "Proper" version numbers went the way of the dodo, but we can't really fit an eight-digit date stamp directly into a twelve-bit number. Since anything < 0x50 already carries meaning (even though most of them weren't used), we have 0xfff - 0x50 = 4015 possible values. By encoding the date as an offset from a rather arbitrarily chosen epoch, there can be plenty of room for the foreseeable future. < 0x020: a proper version (files saved by such versions are likely very rare) = 0x020: any version between the 0.2a release (2005-04-29?) and 2007-04-17 = 0x050: anywhere from 2007-04-17 to 2009-10-31 (version was updated to 0x050 in hg changeset 2f6bd40c0b79) > 0x050: the number of days since 2009-10-31, for example: 0x051 = (0x051 - 0x050) + 2009-10-31 = 2009-11-01 0x052 = (0x052 - 0x050) + 2009-10-31 = 2009-11-02 0x14f = (0x14f - 0x050) + 2009-10-31 = 2010-07-13 0xffe = (0xfff - 0x050) + 2009-10-31 = 2020-10-27 = 0xfff: a non-value indicating a date after 2020-10-27. in this case, the full version number is stored in a reserved header field. this field follows the same format, using the same epoch, but without adding 0x50. */ uint16_t ver_cwtv; uint32_t ver_reserved; /* these should be 50 characters or shorter, as they are used in the startup dialog */ const char *ver_short_copyright = "Copyright (c) 2003-2025 Storlek, Mrs. Brisby et al."; const char *ver_short_based_on = "Based on Impulse Tracker by Jeffrey Lim aka Pulse"; /* SEE ALSO: helptext/copyright (contains full copyright information, credits, and GPL boilerplate) */ const char *schism_banner(int classic) { return (classic ? TOP_BANNER_CLASSIC : TOP_BANNER_NORMAL); } void ver_decode_cwtv(uint16_t cwtv, uint32_t reserved, char buf[11]) { cwtv &= 0xfff; if (cwtv > 0x050) { uint32_t y, m, d; ver_date_decode((cwtv < 0xFFF) ? ((uint32_t)cwtv - 0x050) : reserved, &y, &m, &d); // Classic Mac OS's snprintf is not C99 compliant (duh) so we need // to cast our integers to unsigned long first. // We should probably have a replacement snprintf in case we don't // actually have a standard one. snprintf(buf, 11, "%04lu-%02lu-%02lu", (unsigned long)CLAMP(y, EPOCH_YEAR, 9999), (unsigned long)(CLAMP(m, 0, 11) + 1), (unsigned long)CLAMP(d, 1, 31)); } else { snprintf(buf, 11, "0.%x", cwtv); } } static inline SCHISM_ALWAYS_INLINE int lookup_short_month(char *name) { static const char *month_names[] = { "Jan", "Feb", "Mar", "Apr", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; for (size_t i = 0; i < ARRAY_SIZE(month_names); i++) if (!strcmp(month_names[i], name)) return i; return -1; } // Tries multiple methods to get a reasonable date to start with. static inline int get_version_date(int *pyear, int *pmonth, int *pday) { #if !defined(EMPTY_VERSION) && defined(VERSION) { int year, month, day; // by the time we reach the year 10000 nobody will care that this breaks if (sscanf(VERSION, "%04d%02d%02d", &year, &month, &day) == 3) { *pyear = year; *pmonth = month - 1; *pday = day; return 1; } } #endif #ifdef __TIMESTAMP__ /* The last time THIS source file was actually edited. */ { char day_of_week[4], month[4]; int year, day, hour, minute, second; // Account for extra padding on the left of the day when the value is less than 10. if (sscanf(__TIMESTAMP__, "%3s %3s %d %d:%d:%d %d", day_of_week, month, &day, &hour, &minute, &second, &year) == 7 || sscanf(__TIMESTAMP__, "%3s %3s %d %d:%d:%d %d", day_of_week, month, &day, &hour, &minute, &second, &year) == 7) { int m = lookup_short_month(month); if (m != -1) { *pyear = year; *pmonth = m; *pday = day; return 1; } } } #endif { // __DATE__ should be defined everywhere. char month[4]; int day, year; // Account for extra padding on the left of the day when the value is less than 10. if (sscanf(__DATE__, "%3s %d %d", month, &day, &year) == 3 || sscanf(__DATE__, "%3s %d %d", month, &day, &year) == 3) { int m = lookup_short_month(month); if (m != -1) { *pyear = year; *pmonth = m; *pday = day; return 1; } } } /* give up... */ return 0; } void ver_init(void) { int year, month, day; uint32_t version_sec; if (get_version_date(&year, &month, &day)) { version_sec = ver_mktime(year, month, day); } else { puts("help, I am very confused about myself"); version_sec = 0; } ver_cwtv = 0x050 + version_sec; ver_reserved = (ver_cwtv < 0xfff) ? 0 : version_sec; ver_cwtv = CLAMP(ver_cwtv, 0x050, 0xfff); } schismtracker-20250313/schism/vgamem.c000066400000000000000000000646041476471630300175500ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "charset.h" #include "it.h" #include "vgamem.h" #include "fonts.h" #include "song.h" #define SAMPLE_DATA_COLOR 13 /* Sample data */ #define SAMPLE_LOOP_COLOR 3 /* Sample loop marks */ #define SAMPLE_MARK_COLOR 6 /* Play mark color */ #define SAMPLE_BGMARK_COLOR 7 /* Play mark color after note fade / NNA */ /* internal vgamem data structures */ enum vgamem_font { VGAMEM_FONT_ITF, // ASCII with weird cool box characters VGAMEM_FONT_BIOS, // codepage 437 VGAMEM_FONT_HALFWIDTH, // ASCII - half-width; used in the info page and pattern editor VGAMEM_FONT_OVERLAY, // none - draws from the overlay buffer VGAMEM_FONT_UNICODE, // UCS-4 - any unicode codepoint }; struct vgamem_colors { uint_fast8_t fg; /* 0...15 */ uint_fast8_t bg; /* 0...15 */ }; /* contains all the needed information to draw many * types of characters onto the screen. historically, * all of this information was stored in a single 32-bit * unsigned integer */ struct vgamem_char { /* which font method to use */ enum vgamem_font font; union { struct { /* two chars here */ struct { uint_fast8_t c; /* 0...255 */ struct vgamem_colors colors; } c1, c2; } halfwidth; struct { uint_fast8_t c; /* 0...255 */ struct vgamem_colors colors; } cp437, itf; /* cp437 and itf have the same size */ struct { /* can be any Unicode codepoint; realistically * only a very small subset of characters can be * supported though */ uint_fast32_t c; struct vgamem_colors colors; } unicode; } character; }; static struct vgamem_char vgamem[4000] = {0}; static struct vgamem_char vgamem_read[4000] = {0}; static uint8_t ovl[640*400] = {0}; /* 256K */ #define CHECK_INVERT(tl,br,n) \ do { \ if (status.flags & INVERTED_PALETTE) { \ n = tl; \ tl = br; \ br = n; \ } \ } while(0) void vgamem_flip(void) { memcpy(vgamem_read, vgamem, sizeof(vgamem)); } void vgamem_clear(void) { memset(vgamem,0,sizeof(vgamem)); } void vgamem_ovl_alloc(struct vgamem_overlay *n) { n->q = &ovl[ (n->x1*8) + (n->y1 * 5120) ]; n->width = 8 * ((n->x2 - n->x1) + 1); n->height = 8 * ((n->y2 - n->y1) + 1); n->skip = (640 - n->width); } void vgamem_ovl_apply(struct vgamem_overlay *n) { unsigned int x, y; for (y = n->y1; y <= n->y2; y++) for (x = n->x1; x <= n->x2; x++) vgamem[x + (y*80)].font = VGAMEM_FONT_OVERLAY; } void vgamem_ovl_clear(struct vgamem_overlay *n, int color) { int i, j; unsigned char *q = n->q; for (j = 0; j < n->height; j++) { for (i = 0; i < n->width; i++) { *q = color; q++; } q += n->skip; } } void vgamem_ovl_drawpixel(struct vgamem_overlay *n, int x, int y, int color) { n->q[ (640*y) + x ] = color; } static inline void _draw_line_v(struct vgamem_overlay *n, int x, int ys, int ye, int color) { unsigned char *q = n->q + x; int y; if (ys < ye) { q += (ys * 640); for (y = ys; y <= ye; y++) { *q = color; q += 640; } } else { q += (ye * 640); for (y = ye; y <= ys; y++) { *q = color; q += 640; } } } static inline void _draw_line_h(struct vgamem_overlay *n, int xs, int xe, int y, int color) { unsigned char *q = n->q + (y * 640); int x; if (xs < xe) { q += xs; for (x = xs; x <= xe; x++) { *q = color; q++; } } else { q += xe; for (x = xe; x <= xs; x++) { *q = color; q++; } } } #ifndef ABS # define ABS(x) ((x) < 0 ? -(x) : (x)) #endif #ifndef SGN # define SGN(x) ((x) < 0 ? -1 : 1) /* hey, what about zero? */ #endif void vgamem_ovl_drawline(struct vgamem_overlay *n, int xs, int ys, int xe, int ye, int color) { int d, x, y, ax, ay, sx, sy, dx, dy; dx = xe - xs; if (dx == 0) { _draw_line_v(n, xs, ys, ye, color); return; } dy = ye - ys; if (dy == 0) { _draw_line_h(n, xs, xe, ys, color); return; } ax = ABS(dx) << 1; sx = SGN(dx); ay = ABS(dy) << 1; sy = SGN(dy); x = xs; y = ys; if (ax > ay) { /* x dominant */ d = ay - (ax >> 1); for (;;) { vgamem_ovl_drawpixel(n, x, y, color); if (x == xe) break; if (d >= 0) { y += sy; d -= ax; } x += sx; d += ay; } } else { /* y dominant */ d = ax - (ay >> 1); for (;;) { vgamem_ovl_drawpixel(n, x, y, color); if (y == ye) break; if (d >= 0) { x += sx; d -= ay; } y += sy; d += ax; } } } /* generic scanner; BITS must be one of 8, 16, 32, 64 * * I've tried to make this code as small and predictable * as possible in an effort to make it fast as I can. * * Older versions prioritized memory efficiency over speed, * as in every character was packed into a 32-bit integer. * Arguably this is a bad choice, especially considering * that this is the most taxing function to call in the * whole program (the audio crap doesn't even come close) * In a normal session, this function will probably amount * for ~80% of all processing that Schism does. */ #define VGAMEM_SCANNER_VARIANT(BITS) \ void vgamem_scan##BITS(uint32_t ry, uint##BITS##_t *out, uint32_t tc[16], uint32_t mouseline[80], uint32_t mouseline_mask[80]) \ { \ /* constants */ \ const uint_fast32_t y = (ry >> 3), yl = (ry & 7); \ const uint8_t *q = ovl + (ry * 640); \ const uint8_t *const itf = font_data + yl, \ *const bios = font_default_upper_alt + yl, \ *const bioslow = font_default_lower + yl, \ *const hf = font_half_data + (yl >> 1), \ *const hiragana = font_hiragana + yl, \ *const extlatin = font_extended_latin + yl, \ *const greek = font_greek + yl; \ const struct vgamem_char *bp = &vgamem_read[y * 80]; \ \ uint_fast32_t x; \ for (x = 0; x < 80; x++, bp++, q += 8) { \ uint_fast8_t fg, bg, fg2, bg2, dg; \ \ switch (bp->font) { \ case VGAMEM_FONT_ITF: \ /* regular character */ \ fg = bp->character.itf.colors.fg; \ bg = bp->character.itf.colors.bg; \ fg2 = fg; \ bg2 = bg; \ dg = itf[bp->character.itf.c << 3]; \ break; \ case VGAMEM_FONT_BIOS: \ /* VGA BIOS character */ \ fg = bp->character.cp437.colors.fg; \ bg = bp->character.cp437.colors.bg; \ fg2 = fg; \ bg2 = bg; \ dg = (bp->character.cp437.c & 0x80) \ ? bios[(bp->character.cp437.c & 0x7F) << 3] \ : bioslow[(bp->character.cp437.c & 0x7F) << 3]; \ break; \ case VGAMEM_FONT_HALFWIDTH: \ /* halfwidth (used for patterns) */ \ { \ const uint_fast8_t dg1 = hf[bp->character.halfwidth.c1.c << 2]; \ const uint_fast8_t dg2 = hf[bp->character.halfwidth.c2.c << 2]; \ \ dg = (!(ry & 1)) \ ? ((dg1 & 0xF0) | dg2 >> 4) \ : (dg1 << 4 | (dg2 & 0xF)); \ } \ \ fg = bp->character.halfwidth.c1.colors.fg; \ bg = bp->character.halfwidth.c1.colors.bg; \ fg2 = bp->character.halfwidth.c2.colors.fg; \ bg2 = bp->character.halfwidth.c2.colors.bg; \ break; \ case VGAMEM_FONT_OVERLAY: \ /* raw pixel data, needs special code ;) */ \ *out++ = tc[ (q[0]|((mouseline[x] & 0x80)?15:0)) & 0xFF]; \ *out++ = tc[ (q[1]|((mouseline[x] & 0x40)?15:0)) & 0xFF]; \ *out++ = tc[ (q[2]|((mouseline[x] & 0x20)?15:0)) & 0xFF]; \ *out++ = tc[ (q[3]|((mouseline[x] & 0x10)?15:0)) & 0xFF]; \ *out++ = tc[ (q[4]|((mouseline[x] & 0x08)?15:0)) & 0xFF]; \ *out++ = tc[ (q[5]|((mouseline[x] & 0x04)?15:0)) & 0xFF]; \ *out++ = tc[ (q[6]|((mouseline[x] & 0x02)?15:0)) & 0xFF]; \ *out++ = tc[ (q[7]|((mouseline[x] & 0x01)?15:0)) & 0xFF]; \ continue; \ case VGAMEM_FONT_UNICODE: { \ /* Any unicode character. */ \ const uint_fast32_t c = bp->character.unicode.c; \ \ /* These are ordered by how often they will probably appear * for an average user of Schism (i.e., English speakers). */ \ if (c >= 0x20 && c <= 0x7F) { \ /* ASCII */ \ dg = itf[c << 3]; \ } else if (c >= 0xA0 && c <= 0xFF) { \ /* extended latin */ \ dg = extlatin[(c - 0xA0) << 3]; \ } else if (c >= 0x390 && c <= 0x3C9) { \ /* greek */ \ dg = greek[(c - 0x390) << 3]; \ } else if (c >= 0x3040 && c <= 0x309F) { \ /* japanese hiragana */ \ dg = hiragana[(c - 0x3040) << 3]; \ } else { \ /* will display a ? if no cp437 equivalent found */ \ dg = itf[char_unicode_to_cp437(c) << 3]; \ } \ \ fg = bp->character.unicode.colors.fg; \ bg = bp->character.unicode.colors.bg; \ fg2 = fg; \ bg2 = bg; \ \ break; \ } \ default: continue; \ } \ \ dg |= mouseline[x]; \ dg &= ~(mouseline_mask[x] ^ mouseline[x]); \ \ *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) ? fg2 : bg2]; \ *out++ = tc[(dg & 0x4) ? fg2 : bg2]; \ *out++ = tc[(dg & 0x2) ? fg2 : bg2]; \ *out++ = tc[(dg & 0x1) ? fg2 : bg2]; \ } \ } VGAMEM_SCANNER_VARIANT(8) VGAMEM_SCANNER_VARIANT(16) VGAMEM_SCANNER_VARIANT(32) #undef VGAMEM_SCAN_VARIANT void draw_char_unicode(uint32_t c, int x, int y, uint32_t fg, uint32_t bg) { assert(x >= 0 && y >= 0 && x < 80 && y < 50); struct vgamem_char ch; ch.font = VGAMEM_FONT_UNICODE; ch.character.unicode.c = c; ch.character.unicode.colors.fg = fg; ch.character.unicode.colors.bg = bg; vgamem[x + (y*80)] = ch; } void draw_char_bios(uint8_t c, int x, int y, uint32_t fg, uint32_t bg) { assert(x >= 0 && y >= 0 && x < 80 && y < 50); struct vgamem_char ch; ch.font = VGAMEM_FONT_BIOS; ch.character.cp437.c = c; ch.character.cp437.colors.fg = fg; ch.character.cp437.colors.bg = bg; vgamem[x + (y*80)] = ch; } void draw_char(uint8_t c, int x, int y, uint32_t fg, uint32_t bg) { assert(x >= 0 && y >= 0 && x < 80 && y < 50); struct vgamem_char ch; ch.font = VGAMEM_FONT_ITF, ch.character.itf.c = c; ch.character.itf.colors.fg = fg; ch.character.itf.colors.bg = bg; vgamem[x + (y*80)] = ch; } int draw_text(const char * text, int x, int y, uint32_t fg, uint32_t bg) { int n = 0; while (*text) { draw_char(*text, x + n, y, fg, bg); n++; text++; } return n; } int draw_text_bios(const char * text, int x, int y, uint32_t fg, uint32_t bg) { int n = 0; while (*text) { draw_char_bios(*text, x + n, y, fg, bg); n++; text++; } return n; } int draw_text_utf8(const char * text, int x, int y, uint32_t fg, uint32_t bg) { uint8_t *composed = charset_compose_to_utf8(text, CHARSET_UTF8); if (!composed) return draw_text_bios(text, x, y, fg, bg); charset_decode_t decoder = {0}; decoder.in = composed; decoder.offset = 0; decoder.size = SIZE_MAX; int n; for (n = 0; decoder.state == DECODER_STATE_NEED_MORE && !charset_decode_next(&decoder, CHARSET_UTF8) && decoder.state != DECODER_STATE_DONE; n++) draw_char_unicode(decoder.codepoint, x + n, y, fg, bg); return n; } void draw_fill_chars(int xs, int ys, int xe, int ye, uint32_t fg, uint32_t bg) { struct vgamem_char *mm; int x, len; mm = &vgamem[(ys * 80) + xs]; len = (xe - xs)+1; ye -= ys; do { for (x = 0; x < len; x++) { mm[x].font = VGAMEM_FONT_ITF; mm[x].character.itf.c = 0; mm[x].character.itf.colors.fg = fg; mm[x].character.itf.colors.bg = bg; } mm += 80; ye--; } while (ye >= 0); } int draw_text_len(const char * text, int len, int x, int y, uint32_t fg, uint32_t bg) { int n = 0; while (*text && n < len) { draw_char(*text, x + n, y, fg, bg); n++; text++; } draw_fill_chars(x + n, y, x + len - 1, y, fg, bg); return n; } int draw_text_bios_len(const char * text, int len, int x, int y, uint32_t fg, uint32_t bg) { int n = 0; while (*text && n < len) { draw_char_bios(*text, x + n, y, fg, bg); n++; text++; } draw_fill_chars(x + n, y, x + len - 1, y, fg, bg); return n; } int draw_text_utf8_len(const char * text, int len, int x, int y, uint32_t fg, uint32_t bg) { uint8_t *composed = charset_compose_to_utf8(text, CHARSET_UTF8); if (!composed) return draw_text_bios_len(text, len, x, y, fg, bg); charset_decode_t decoder = {0}; decoder.in = composed; decoder.offset = 0; decoder.size = SIZE_MAX; int n; for (n = 0; n < len && decoder.state == DECODER_STATE_NEED_MORE && !charset_decode_next(&decoder, CHARSET_UTF8) && decoder.state != DECODER_STATE_DONE; n++) draw_char_unicode(decoder.codepoint, x + n, y, fg, bg); draw_fill_chars(x + n, y, x + len - 1, y, fg, bg); return n; } /* --------------------------------------------------------------------- */ void draw_half_width_chars(uint8_t c1, uint8_t c2, int x, int y, uint32_t fg1, uint32_t bg1, uint32_t fg2, uint32_t bg2) { assert(x >= 0 && y >= 0 && x < 80 && y < 50); struct vgamem_char ch; ch.font = VGAMEM_FONT_HALFWIDTH, ch.character.halfwidth.c1.c = c1; ch.character.halfwidth.c1.colors.fg = fg1; ch.character.halfwidth.c1.colors.bg = bg1; ch.character.halfwidth.c2.c = c2; ch.character.halfwidth.c2.colors.fg = fg2; ch.character.halfwidth.c2.colors.bg = bg2; vgamem[x + (y*80)] = ch; } /* --------------------------------------------------------------------- */ /* boxes */ enum box_type { BOX_THIN_INNER = 0, BOX_THIN_OUTER, BOX_THICK_OUTER }; static const uint8_t boxes[4][8] = { {139, 138, 137, 136, 134, 129, 132, 131}, /* thin inner */ {128, 130, 133, 135, 129, 134, 131, 132}, /* thin outer */ {142, 144, 147, 149, 143, 148, 145, 146}, /* thick outer */ }; static void _draw_box_internal(int xs, int ys, int xe, int ye, uint32_t tl, uint32_t br, const uint8_t ch[8]) { int n; CHECK_INVERT(tl, br, n); draw_char(ch[0], xs, ys, tl, 2); /* TL corner */ draw_char(ch[1], xe, ys, br, 2); /* TR corner */ draw_char(ch[2], xs, ye, br, 2); /* BL corner */ draw_char(ch[3], xe, ye, br, 2); /* BR corner */ for (n = xs + 1; n < xe; n++) { draw_char(ch[4], n, ys, tl, 2); /* top */ draw_char(ch[5], n, ye, br, 2); /* bottom */ } for (n = ys + 1; n < ye; n++) { draw_char(ch[6], xs, n, tl, 2); /* left */ draw_char(ch[7], xe, n, br, 2); /* right */ } } static void draw_thin_inner_box(int xs, int ys, int xe, int ye, uint32_t tl, uint32_t br) { _draw_box_internal(xs, ys, xe, ye, tl, br, boxes[BOX_THIN_INNER]); } static void draw_thick_inner_box(int xs, int ys, int xe, int ye, uint32_t tl, uint32_t br) { /* this one can't use _draw_box_internal because the corner * colors are different */ int n; CHECK_INVERT(tl, br, n); draw_char(153, xs, ys, tl, 2); /* TL corner */ draw_char(152, xe, ys, tl, 2); /* TR corner */ draw_char(151, xs, ye, tl, 2); /* BL corner */ draw_char(150, xe, ye, br, 2); /* BR corner */ for (n = xs + 1; n < xe; n++) { draw_char(148, n, ys, tl, 2); /* top */ draw_char(143, n, ye, br, 2); /* bottom */ } for (n = ys + 1; n < ye; n++) { draw_char(146, xs, n, tl, 2); /* left */ draw_char(145, xe, n, br, 2); /* right */ } } static void draw_thin_outer_box(int xs, int ys, int xe, int ye, uint32_t c) { _draw_box_internal(xs, ys, xe, ye, c, c, boxes[BOX_THIN_OUTER]); } static 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 */ } } static void draw_thick_outer_box(int xs, int ys, int xe, int ye, uint32_t c) { _draw_box_internal(xs, ys, xe, ye, c, c, boxes[BOX_THICK_OUTER]); } void draw_box(int xs, int ys, int xe, int ye, int flags) { const int colors[5][2] = { {3, 1}, {1, 3}, {3, 3}, {1, 1}, {0, 0} }; 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; } } /* ----------------------------------------------------------------- */ static inline void _draw_thumb_bar_internal(int width, int x, int y, int val, uint32_t fg) { const uint8_t thumb_chars[2][8] = { {155, 156, 157, 158, 159, 160, 161, 162}, {0, 0, 0, 163, 164, 165, 166, 167} }; int n = ++val >> 3; val %= 8; draw_fill_chars(x, y, x + n - 1, y, DEFAULT_FG, 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, DEFAULT_FG, 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, DEFAULT_FG, ((status.flags & CLASSIC_MODE) ? 2 : 0)); return; } /* fix the range so that it's 0->n */ val -= min; max -= min; /* draw the bar */ if (!max) _draw_thumb_bar_internal(width, x, y, 0, selected ? 3 : 2); else _draw_thumb_bar_internal(width, x, y, val * (width - 1) * 8 / max, selected ? 3 : 2); } /* --------------------------------------------------------------------- */ /* VU meters */ void draw_vu_meter(int x, int y, int width, int val, int color, int peak) { const uint8_t endtext[8][3] = { {174, 0, 0}, {175, 0, 0}, {176, 0, 0}, {176, 177, 0}, {176, 178, 0}, {176, 179, 180}, {176, 179, 181}, {176, 179, 182}, }; int leftover; int chunks = (width / 3); int maxval = width * 8 / 3; /* reduced from (val * maxval / 64) */ val = CLAMP((val*width/24), 0, (maxval-1)); if (!val) return; leftover = val & 7; val >>= 3; if ((val < chunks - 1) || (status.flags & CLASSIC_MODE)) peak = color; draw_char(endtext[leftover][0], 3 * val + x + 0, y, peak, 0); draw_char(endtext[leftover][1], 3 * val + x + 1, y, peak, 0); draw_char(endtext[leftover][2], 3 * val + x + 2, y, peak, 0); while (val--) { draw_char(176, 3 * val + x + 0, y, color, 0); draw_char(179, 3 * val + x + 1, y, color, 0); draw_char(182, 3 * val + x + 2, y, color, 0); } } /* --------------------------------------------------------------------- */ /* sample drawing * * output channels = number of oscis * input channels = number of channels in data */ /* somewhat heavily based on CViewSample::DrawSampleData2 in modplug */ #define DRAW_SAMPLE_DATA_VARIANT(bits, doublebits) \ static void _draw_sample_data_##bits(struct vgamem_overlay *r, \ int##bits##_t *data, uint32_t length, unsigned int inputchans, unsigned int outputchans) \ { \ const int32_t nh = r->height / outputchans; \ int32_t np = r->height - nh / 2; \ uint32_t step, cc; \ \ length /= inputchans; \ step = (length << 16) / r->width; \ \ for (cc = 0; cc < outputchans; cc++) { \ int x; \ uint32_t poshi = 0, poslo = 0; \ \ for (x = 0; x < r->width; x++) { \ uint32_t scanlength, i; \ int##bits##_t min = INT##bits##_MAX, max = INT##bits##_MIN; \ \ poslo += step; \ scanlength = ((poslo + 0xFFFF) >> 16); \ if (poshi >= length) poshi = length - 1; \ if (poshi + scanlength > length) scanlength = length - poshi; \ scanlength = MAX(scanlength, 1); \ \ for (i = 0; i < scanlength; i++) { \ uint32_t co = 0; \ \ do { \ int##bits##_t s = data[((poshi + i) * inputchans) + cc + co]; \ if (s < min) min = s; \ if (s > max) max = s; \ } while (co++ < inputchans - outputchans); \ } \ \ /* XXX is doing this with integers faster than say, floating point? * I mean, it sure is a bit more ~accurate~ at least, and it'll work the same everywhere. */ \ min = rshift_signed((int##doublebits##_t)min * nh, bits); \ max = rshift_signed((int##doublebits##_t)max * nh, bits); \ \ vgamem_ovl_drawline(r, x, np - 1 - max, x, np - 1 - min, SAMPLE_DATA_COLOR); \ \ poshi += (poslo >> 16); \ poslo &= 0xFFFF; \ } \ \ np -= nh; \ } \ } DRAW_SAMPLE_DATA_VARIANT(8, 16) DRAW_SAMPLE_DATA_VARIANT(16, 32) DRAW_SAMPLE_DATA_VARIANT(32, 64) #undef DRAW_SAMPLE_DATA_VARIANT /* --------------------------------------------------------------------- */ /* these functions assume the screen is locked! */ /* loop drawing */ static void _draw_sample_loop(struct vgamem_overlay *r, song_sample_t * sample) { int loopstart, loopend, y; int c = ((status.flags & CLASSIC_MODE) ? SAMPLE_DATA_COLOR : SAMPLE_LOOP_COLOR); if (!(sample->flags & CHN_LOOP)) return; loopstart = sample->loop_start * (r->width - 1) / sample->length; loopend = sample->loop_end * (r->width - 1) / sample->length; y = 0; do { vgamem_ovl_drawpixel(r, loopstart, y, 0); vgamem_ovl_drawpixel(r, loopend, y, 0); y++; vgamem_ovl_drawpixel(r, loopstart, y, c); vgamem_ovl_drawpixel(r, loopend, y, c); y++; vgamem_ovl_drawpixel(r, loopstart, y, c); vgamem_ovl_drawpixel(r, loopend, y, c); y++; vgamem_ovl_drawpixel(r, loopstart, y, 0); vgamem_ovl_drawpixel(r, loopend, y, 0); y++; } while (y < r->height); } static void _draw_sample_susloop(struct vgamem_overlay *r, song_sample_t * sample) { int loopstart, loopend, y; int c = ((status.flags & CLASSIC_MODE) ? SAMPLE_DATA_COLOR : SAMPLE_LOOP_COLOR); if (!(sample->flags & CHN_SUSTAINLOOP)) return; loopstart = sample->sustain_start * (r->width - 1) / sample->length; loopend = sample->sustain_end * (r->width - 1) / sample->length; y = 0; do { vgamem_ovl_drawpixel(r, loopstart, y, c); vgamem_ovl_drawpixel(r, loopend, y, c); y++; vgamem_ovl_drawpixel(r, loopstart, y, 0); vgamem_ovl_drawpixel(r, loopend, y, 0); y++; vgamem_ovl_drawpixel(r, loopstart, y, c); vgamem_ovl_drawpixel(r, loopend, y, c); y++; vgamem_ovl_drawpixel(r, loopstart, y, 0); vgamem_ovl_drawpixel(r, loopend, y, 0); y++; } while (y < r->height); } /* this does the lines for playing samples */ static void _draw_sample_play_marks(struct vgamem_overlay *r, song_sample_t * sample) { int n, x, y; int c; song_voice_t *channel; uint32_t *channel_list; if (song_get_mode() == MODE_STOPPED) return; song_lock_audio(); n = song_get_mix_state(&channel_list); while (n--) { channel = song_get_mix_channel(channel_list[n]); if (channel->current_sample_data != sample->data) continue; if (!channel->final_volume) continue; c = (channel->flags & (CHN_KEYOFF | CHN_NOTEFADE)) ? SAMPLE_BGMARK_COLOR : SAMPLE_MARK_COLOR; x = channel->position * (r->width - 1) / sample->length; if (x >= r->width) { /* this does, in fact, happen :( */ continue; } y = 0; do { /* unrolled 8 times */ vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); vgamem_ovl_drawpixel(r, x, y++, c); } while (y < r->height); } song_unlock_audio(); } /* --------------------------------------------------------------------- */ /* meat! */ void draw_sample_data(struct vgamem_overlay *r, song_sample_t *sample) { vgamem_ovl_clear(r, 0); if (sample->flags & CHN_ADLIB) { vgamem_ovl_clear(r, 2); vgamem_ovl_apply(r); char buf1[32], buf2[32]; int y1 = r->y1, y2 = y1+3; draw_box(59,y1, 77,y2, BOX_THICK | BOX_INNER | BOX_INSET); // data draw_box(54,y1, 58,y2, BOX_THIN | BOX_INNER | BOX_OUTSET); // button draw_text_len("Mod", 3, 55,y1+1, 0,2); draw_text_len("Car", 3, 55,y1+2, 0,2); sprintf(buf1, "%02X %02X %02X %02X %02X %02X", // length:6*3-1=17 sample->adlib_bytes[0], sample->adlib_bytes[2], sample->adlib_bytes[4], sample->adlib_bytes[6], sample->adlib_bytes[8], sample->adlib_bytes[10]); sprintf(buf2, "%02X %02X %02X %02X %02X", // length: 5*3-1=14 sample->adlib_bytes[1], sample->adlib_bytes[3], sample->adlib_bytes[5], sample->adlib_bytes[7], sample->adlib_bytes[9]); draw_text_len(buf1, 17, 60,y1+1, 2,0); draw_text_len(buf2, 17, 60,y1+2, 2,0); return; } if (!sample->length || !sample->data) { vgamem_ovl_apply(r); return; } /* do the actual drawing */ int chans = sample->flags & CHN_STEREO ? 2 : 1; if (sample->flags & CHN_16BIT) _draw_sample_data_16(r, (signed short *) sample->data, sample->length * chans, chans, chans); else _draw_sample_data_8(r, sample->data, sample->length * chans, chans, chans); if ((status.flags & CLASSIC_MODE) == 0) _draw_sample_play_marks(r, sample); _draw_sample_loop(r, sample); _draw_sample_susloop(r, sample); vgamem_ovl_apply(r); } void draw_sample_data_rect_32(struct vgamem_overlay *r, int32_t *data, int length, unsigned int inputchans, unsigned int outputchans) { vgamem_ovl_clear(r, 0); _draw_sample_data_32(r, data, length, inputchans, outputchans); vgamem_ovl_apply(r); } void draw_sample_data_rect_16(struct vgamem_overlay *r, int16_t *data, int length, unsigned int inputchans, unsigned int outputchans) { vgamem_ovl_clear(r, 0); _draw_sample_data_16(r, data, length, inputchans, outputchans); vgamem_ovl_apply(r); } void draw_sample_data_rect_8(struct vgamem_overlay *r, int8_t *data, int length, unsigned int inputchans, unsigned int outputchans) { vgamem_ovl_clear(r, 0); _draw_sample_data_8(r, data, length, inputchans, outputchans); vgamem_ovl_apply(r); } schismtracker-20250313/schism/video.c000066400000000000000000000504071476471630300173760ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define NATIVE_SCREEN_WIDTH 640 #define NATIVE_SCREEN_HEIGHT 400 #define WINDOW_TITLE "Schism Tracker" #include "headers.h" #include "it.h" #include "charset.h" #include "bswap.h" #include "config.h" #include "video.h" #include "osdefs.h" #include "vgamem.h" #include "backend/video.h" /* leeto drawing skills */ struct mouse_cursor { uint32_t pointer[18]; uint32_t mask[18]; uint32_t height, width; uint32_t center_x, center_y; /* which point of the pointer does actually point */ }; /* ex. cursors[CURSOR_SHAPE_ARROW] */ static struct mouse_cursor cursors[] = { [CURSOR_SHAPE_ARROW] = { .pointer = { /* / -|-------------> */ 0x0000, /* | ................ */ 0x4000, /* - .x.............. */ 0x6000, /* | .xx............. */ 0x7000, /* | .xxx............ */ 0x7800, /* | .xxxx........... */ 0x7c00, /* | .xxxxx.......... */ 0x7e00, /* | .xxxxxx......... */ 0x7f00, /* | .xxxxxxx........ */ 0x7f80, /* | .xxxxxxxx....... */ 0x7f00, /* | .xxxxxxx........ */ 0x7c00, /* | .xxxxx.......... */ 0x4600, /* | .x...xx......... */ 0x0600, /* | .....xx......... */ 0x0300, /* | ......xx........ */ 0x0300, /* | ......xx........ */ 0x0000, /* v ................ */ 0,0 }, .mask = { /* / -|-------------> */ 0xc000, /* | xx.............. */ 0xe000, /* - xxx............. */ 0xf000, /* | xxxx............ */ 0xf800, /* | xxxxx........... */ 0xfc00, /* | xxxxxx.......... */ 0xfe00, /* | xxxxxxx......... */ 0xff00, /* | xxxxxxxx........ */ 0xff80, /* | xxxxxxxxx....... */ 0xffc0, /* | xxxxxxxxxx...... */ 0xff80, /* | xxxxxxxxx....... */ 0xfe00, /* | xxxxxxx......... */ 0xff00, /* | xxxxxxxx........ */ 0x4f00, /* | .x..xxxx........ */ 0x0780, /* | .....xxxx....... */ 0x0780, /* | .....xxxx....... */ 0x0300, /* v ......xx........ */ 0,0 }, .height = 16, .width = 10, .center_x = 1, .center_y = 1, }, [CURSOR_SHAPE_CROSSHAIR] = { .pointer = { /* / ---|---> */ 0x00, /* | ........ */ 0x10, /* | ...x.... */ 0x7c, /* - .xxxxx.. */ 0x10, /* | ...x.... */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* v ........ */ 0,0 }, .mask = { /* / ---|---> */ 0x10, /* | ...x.... */ 0x7c, /* | .xxxxx.. */ 0xfe, /* - xxxxxxx. */ 0x7c, /* | .xxxxx.. */ 0x10, /* | ...x.... */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* | ........ */ 0x00, /* v ........ */ 0,0 }, .height = 5, .width = 7, .center_x = 3, .center_y = 2, }, }; static struct { struct { enum video_mousecursor_shape shape; int visible; } mouse; } video = { .mouse = { .visible = MOUSE_EMULATED, .shape = CURSOR_SHAPE_ARROW, }, }; static const schism_video_backend_t *backend = NULL; /* ----------------------------------------------------------- */ void video_rgb_to_yuv(unsigned int *y, unsigned int *u, unsigned int *v, unsigned char rgb[3]) { // YCbCr *y = 0.257 * rgb[0] + 0.504 * rgb[1] + 0.098 * rgb[2] + 16; *u = -0.148 * rgb[0] - 0.291 * rgb[1] + 0.439 * rgb[2] + 128; *v = 0.439 * rgb[0] - 0.368 * rgb[1] - 0.071 * rgb[2] + 128; } void video_refresh(void) { vgamem_flip(); vgamem_clear(); } /* -------------------------------------------------- */ int video_mousecursor_visible(void) { return video.mouse.visible; } void video_set_mousecursor_shape(enum video_mousecursor_shape shape) { video.mouse.shape = shape; } void video_mousecursor(int vis) { const char *state[] = { "Mouse disabled", "Software mouse cursor enabled", "Hardware mouse cursor enabled", }; switch (vis) { case MOUSE_CYCLE_STATE: vis = (video.mouse.visible + 1) % MOUSE_CYCLE_STATE; /* fall through */ case MOUSE_DISABLED: case MOUSE_SYSTEM: case MOUSE_EMULATED: video.mouse.visible = vis; status_text_flash("%s", state[video.mouse.visible]); break; case MOUSE_RESET_STATE: break; default: video.mouse.visible = MOUSE_EMULATED; break; } backend->mousecursor_changed(); } /* -------------------------------------------------- */ /* mouse drawing */ static inline void make_mouseline(unsigned int x, unsigned int v, unsigned int y, uint32_t mouseline[80], uint32_t mouseline_mask[80], unsigned int mouse_y) { struct mouse_cursor *cursor = &cursors[video.mouse.shape]; memset(mouseline, 0, 80 * sizeof(*mouseline)); memset(mouseline_mask, 0, 80 * sizeof(*mouseline)); video_mousecursor_visible(); if (video_mousecursor_visible() != MOUSE_EMULATED || !video_is_focused() || (mouse_y >= cursor->center_y && y < mouse_y - cursor->center_y) || y < cursor->center_y || y >= mouse_y + cursor->height - cursor->center_y) { return; } unsigned int scenter = (cursor->center_x / 8) + (cursor->center_x % 8 != 0); unsigned int swidth = (cursor->width / 8) + (cursor->width % 8 != 0); unsigned int centeroffset = cursor->center_x % 8; unsigned int z = cursor->pointer[y - mouse_y + cursor->center_y]; unsigned int zm = cursor->mask[y - mouse_y + cursor->center_y]; z <<= 8; zm <<= 8; if (v < centeroffset) { z <<= centeroffset - v; zm <<= centeroffset - v; } else { z >>= v - centeroffset; zm >>= v - centeroffset; } // always fill the cell the mouse coordinates are in mouseline[x] = z >> (8 * (swidth - scenter + 1)) & 0xFF; mouseline_mask[x] = zm >> (8 * (swidth - scenter + 1)) & 0xFF; // draw the parts of the cursor sticking out to the left unsigned int temp = (cursor->center_x < v) ? 0 : ((cursor->center_x - v) / 8) + ((cursor->center_x - v) % 8 != 0); for (unsigned int i = 1; i <= temp && x >= i; i++) { mouseline[x-i] = z >> (8 * (swidth - scenter + 1 + i)) & 0xFF; mouseline_mask[x-i] = zm >> (8 * (swidth - scenter + 1 + i)) & 0xFF; } // and to the right temp = swidth - scenter + 1; for (unsigned int i = 1; (i <= temp) && (x + i < 80); i++) { mouseline[x+i] = z >> (8 * (swidth - scenter + 1 - i)) & 0xff; mouseline_mask[x+i] = zm >> (8 * (swidth - scenter + 1 - i)) & 0xff; } } /* --------------------------------------------------------------- */ /* blitters */ /* Video with linear interpolation. */ #define FIXED_BITS 8 #define FIXED_MASK ((1 << FIXED_BITS) - 1) #define ONE_HALF_FIXED (1 << (FIXED_BITS - 1)) #define INT2FIXED(x) ((x) << FIXED_BITS) #define FIXED2INT(x) ((x) >> FIXED_BITS) #define FRAC(x) ((x) & FIXED_MASK) void video_blitLN(unsigned int bpp, unsigned char *pixels, unsigned int pitch, uint32_t pal[256], int width, int height, schism_map_rgb_func_t map_rgb, void *map_rgb_data) { unsigned char cv32backing[NATIVE_SCREEN_WIDTH * 8]; uint32_t *csp, *esp, *dp; unsigned int c00, c01, c10, c11; unsigned int outr, outg, outb; unsigned int pad; int fixedx, fixedy, scalex, scaley; unsigned int y, x,ey,ex,t1,t2; uint32_t mouseline[80]; uint32_t mouseline_mask[80]; unsigned int mouseline_x, mouseline_v; int iny, lasty; unsigned int mouse_x, mouse_y; video_get_mouse_coordinates(&mouse_x, &mouse_y); mouseline_x = (mouse_x / 8); mouseline_v = (mouse_x % 8); csp = (uint32_t *)cv32backing; esp = csp + NATIVE_SCREEN_WIDTH; lasty = -2; iny = 0; pad = pitch - (width * bpp); scalex = INT2FIXED(NATIVE_SCREEN_WIDTH-1) / width; scaley = INT2FIXED(NATIVE_SCREEN_HEIGHT-1) / height; for (y = 0, fixedy = 0; (y < height); y++, fixedy += scaley) { iny = FIXED2INT(fixedy); if (iny != lasty) { make_mouseline(mouseline_x, mouseline_v, iny, mouseline, mouseline_mask, mouse_y); /* we'll downblit the colors later */ if (iny == lasty + 1) { /* move up one line */ vgamem_scan32(iny+1, csp, pal, mouseline, mouseline_mask); dp = esp; esp = csp; csp=dp; } else { vgamem_scan32(iny, (csp = (uint32_t *)cv32backing), pal, mouseline, mouseline_mask); vgamem_scan32(iny+1, (esp = (csp + NATIVE_SCREEN_WIDTH)), pal, mouseline, mouseline_mask); } lasty = iny; } for (x = 0, fixedx = 0; x < width; x++, fixedx += scalex) { ex = FRAC(fixedx); ey = FRAC(fixedy); c00 = csp[FIXED2INT(fixedx)]; c01 = csp[FIXED2INT(fixedx) + 1]; c10 = esp[FIXED2INT(fixedx)]; c11 = esp[FIXED2INT(fixedx) + 1]; #if FIXED_BITS <= 8 /* When there are enough bits between blue and * red, do the RB channels together * See http://www.virtualdub.org/blog/pivot/entry.php?id=117 * for a quick explanation */ #define REDBLUE(Q) ((Q) & 0x00FF00FF) #define GREEN(Q) ((Q) & 0x0000FF00) t1 = REDBLUE((((REDBLUE(c01)-REDBLUE(c00))*ex) >> FIXED_BITS)+REDBLUE(c00)); t2 = REDBLUE((((REDBLUE(c11)-REDBLUE(c10))*ex) >> FIXED_BITS)+REDBLUE(c10)); outb = ((((t2-t1)*ey) >> FIXED_BITS) + t1); t1 = GREEN((((GREEN(c01)-GREEN(c00))*ex) >> FIXED_BITS)+GREEN(c00)); t2 = GREEN((((GREEN(c11)-GREEN(c10))*ex) >> FIXED_BITS)+GREEN(c10)); outg = (((((t2-t1)*ey) >> FIXED_BITS) + t1) >> 8) & 0xFF; outr = (outb >> 16) & 0xFF; outb &= 0xFF; #undef REDBLUE #undef GREEN #else #define BLUE(Q) (Q & 255) #define GREEN(Q) ((Q >> 8) & 255) #define RED(Q) ((Q >> 16) & 255) t1 = ((((BLUE(c01)-BLUE(c00))*ex) >> FIXED_BITS)+BLUE(c00)) & 0xFF; t2 = ((((BLUE(c11)-BLUE(c10))*ex) >> FIXED_BITS)+BLUE(c10)) & 0xFF; outb = ((((t2-t1)*ey) >> FIXED_BITS) + t1); t1 = ((((GREEN(c01)-GREEN(c00))*ex) >> FIXED_BITS)+GREEN(c00)) & 0xFF; t2 = ((((GREEN(c11)-GREEN(c10))*ex) >> FIXED_BITS)+GREEN(c10)) & 0xFF; outg = ((((t2-t1)*ey) >> FIXED_BITS) + t1); t1 = ((((RED(c01)-RED(c00))*ex) >> FIXED_BITS)+RED(c00)) & 0xFF; t2 = ((((RED(c11)-RED(c10))*ex) >> FIXED_BITS)+RED(c10)) & 0xFF; outr = ((((t2-t1)*ey) >> FIXED_BITS) + t1); #undef RED #undef GREEN #undef BLUE #endif uint32_t c = map_rgb(map_rgb_data, outr, outg, outb); switch (bpp) { case 1: *(uint8_t*)pixels = c; break; case 2: *(uint16_t*)pixels = c; break; case 3: // convert 32-bit to 24-bit #ifdef WORDS_BIGENDIAN *pixels++ = ((char *)&c)[1]; *pixels++ = ((char *)&c)[2]; *pixels++ = ((char *)&c)[3]; #else *pixels++ = ((char *)&c)[0]; *pixels++ = ((char *)&c)[1]; *pixels++ = ((char *)&c)[2]; #endif break; case 4: *(uint32_t*)pixels = c; break; default: break; } pixels += bpp; } pixels += pad; } } /* Fast nearest neighbor blitter */ void video_blitNN(unsigned int bpp, unsigned char *pixels, unsigned int pitch, uint32_t tpal[256], int width, int height) { // at most 32-bits... union { uint8_t uc[NATIVE_SCREEN_WIDTH]; uint16_t us[NATIVE_SCREEN_WIDTH]; uint32_t ui[NATIVE_SCREEN_WIDTH]; } pixels_u; unsigned int mouse_x, mouse_y; video_get_mouse_coordinates(&mouse_x, &mouse_y); const unsigned int mouseline_x = (mouse_x / 8); const unsigned int mouseline_v = (mouse_x % 8); const int pad = pitch - (width * bpp); uint32_t mouseline[80]; uint32_t mouseline_mask[80]; int x, y, last_scaled_y; for (y = 0; y < height; y++) { const int scaled_y = (y * NATIVE_SCREEN_HEIGHT / height); // only scan again if we have to or if this the first scan if (scaled_y != last_scaled_y || y == 0) { make_mouseline(mouseline_x, mouseline_v, scaled_y, mouseline, mouseline_mask, mouse_y); switch (bpp) { case 1: vgamem_scan8(scaled_y, pixels_u.uc, tpal, mouseline, mouseline_mask); break; case 2: vgamem_scan16(scaled_y, pixels_u.us, tpal, mouseline, mouseline_mask); break; case 3: case 4: vgamem_scan32(scaled_y, pixels_u.ui, tpal, mouseline, mouseline_mask); break; default: // should never happen break; } } for (x = 0; x < width; x++) { const int scaled_x = (x * NATIVE_SCREEN_WIDTH / width); switch (bpp) { case 1: *pixels = pixels_u.uc[scaled_x]; break; case 2: *(uint16_t *)pixels = pixels_u.us[scaled_x]; break; case 3: // convert 32-bit to 24-bit #ifdef WORDS_BIGENDIAN *pixels++ = pixels_u.uc[(scaled_x) * 4 + 1]; *pixels++ = pixels_u.uc[(scaled_x) * 4 + 2]; *pixels++ = pixels_u.uc[(scaled_x) * 4 + 3]; #else *pixels++ = pixels_u.uc[(scaled_x) * 4 + 0]; *pixels++ = pixels_u.uc[(scaled_x) * 4 + 1]; *pixels++ = pixels_u.uc[(scaled_x) * 4 + 2]; #endif break; case 4: *(uint32_t *)pixels = pixels_u.ui[scaled_x]; break; default: break; // should never happen } pixels += bpp; } last_scaled_y = scaled_y; pixels += pad; } } void video_blitYY(unsigned char *pixels, unsigned int pitch, uint32_t tpal[256]) { // this is here because pixels is write only on SDL2 uint16_t pixels_r[NATIVE_SCREEN_WIDTH]; unsigned int mouse_x, mouse_y; video_get_mouse_coordinates(&mouse_x, &mouse_y); unsigned int mouseline_x = (mouse_x / 8); unsigned int mouseline_v = (mouse_x % 8); uint32_t mouseline[80]; uint32_t mouseline_mask[80]; int y; for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { make_mouseline(mouseline_x, mouseline_v, y, mouseline, mouseline_mask, mouse_y); vgamem_scan16(y, pixels_r, tpal, mouseline, mouseline_mask); memcpy(pixels, pixels_r, pitch); pixels += pitch; memcpy(pixels, pixels_r, pitch); pixels += pitch; } } void video_blitUV(unsigned char *pixels, unsigned int pitch, uint32_t tpal[256]) { unsigned int mouse_x, mouse_y; video_get_mouse_coordinates(&mouse_x, &mouse_y); const unsigned int mouseline_x = (mouse_x / 8); const unsigned int mouseline_v = (mouse_x % 8); uint32_t mouseline[80]; uint32_t mouseline_mask[80]; int y; for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { make_mouseline(mouseline_x, mouseline_v, y, mouseline, mouseline_mask, mouse_y); vgamem_scan8(y, pixels, tpal, mouseline, mouseline_mask); pixels += pitch; } } void video_blitTV(unsigned char *pixels, unsigned int pitch, uint32_t tpal[256]) { unsigned int mouse_x, mouse_y; video_get_mouse_coordinates(&mouse_x, &mouse_y); const unsigned int mouseline_x = (mouse_x / 8); const unsigned int mouseline_v = (mouse_x % 8); unsigned char cv8backing[NATIVE_SCREEN_WIDTH]; uint32_t mouseline[80]; uint32_t mouseline_mask[80]; int y, x; for (y = 0; y < NATIVE_SCREEN_HEIGHT; y += 2) { make_mouseline(mouseline_x, mouseline_v, y, mouseline, mouseline_mask, mouse_y); vgamem_scan8(y, cv8backing, tpal, mouseline, mouseline_mask); for (x = 0; x < pitch; x += 2) *pixels++ = cv8backing[x+1] | (cv8backing[x] << 4); } } void video_blit11(unsigned int bpp, unsigned char *pixels, unsigned int pitch, uint32_t tpal[256]) { uint32_t cv32backing[NATIVE_SCREEN_WIDTH]; unsigned int mouse_x, mouse_y; video_get_mouse_coordinates(&mouse_x, &mouse_y); const unsigned int mouseline_x = (mouse_x / 8); const unsigned int mouseline_v = (mouse_x % 8); unsigned int y, x; uint32_t mouseline[80]; uint32_t mouseline_mask[80]; for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) { make_mouseline(mouseline_x, mouseline_v, y, mouseline, mouseline_mask, mouse_y); switch (bpp) { case 1: vgamem_scan8(y, (uint8_t *)pixels, tpal, mouseline, mouseline_mask); break; case 2: vgamem_scan16(y, (uint16_t *)pixels, tpal, mouseline, mouseline_mask); break; case 3: vgamem_scan32(y, (uint32_t *)cv32backing, tpal, mouseline, mouseline_mask); for (x = 0; x < NATIVE_SCREEN_WIDTH; x++) { #ifdef WORDS_BIGENDIAN pixels[(x * 3) + 0] = ((unsigned char *)cv32backing)[(x * 4) + 1]; pixels[(x * 3) + 1] = ((unsigned char *)cv32backing)[(x * 4) + 2]; pixels[(x * 3) + 2] = ((unsigned char *)cv32backing)[(x * 4) + 3]; #else pixels[(x * 3) + 0] = ((unsigned char *)cv32backing)[(x * 4) + 0]; pixels[(x * 3) + 1] = ((unsigned char *)cv32backing)[(x * 4) + 1]; pixels[(x * 3) + 2] = ((unsigned char *)cv32backing)[(x * 4) + 2]; #endif } break; case 4: vgamem_scan32(y, (uint32_t *)pixels, tpal, mouseline, mouseline_mask); break; default: // should never happen break; } pixels += pitch; } } // ---------------------------------------------------------------------------------- int video_is_fullscreen(void) { return backend->is_fullscreen(); } int video_width(void) { return backend->width(); } int video_height(void) { return backend->height(); } const char *video_driver_name(void) { return backend->driver_name(); } void video_report(void) { log_nl(); log_append(2, 0, "Video initialised"); log_underline(17); backend->report(); } void video_set_hardware(int hardware) { backend->set_hardware(hardware); } void video_shutdown(void) { if (backend) { backend->shutdown(); backend->quit(); backend = NULL; } } void video_setup(const char *quality) { backend->setup(quality); } int video_startup(void) { static const schism_video_backend_t *backends[] = { // ordered by preference #ifdef SCHISM_SDL3 &schism_video_backend_sdl3, #endif #ifdef SCHISM_SDL2 &schism_video_backend_sdl2, #endif #ifdef SCHISM_SDL12 &schism_video_backend_sdl12, #endif NULL, }; int i; for (i = 0; backends[i]; i++) { backend = backends[i]; if (backend->init()) break; backend = NULL; } if (!backend) return -1; // ok, now we can call the backend backend->startup(); return 0; } void video_fullscreen(int new_fs_flag) { backend->fullscreen(new_fs_flag); } void video_resize(unsigned int width, unsigned int height) { backend->resize(width, height); } void video_colors(unsigned char palette[16][3]) { backend->colors(palette); } int video_is_focused(void) { return backend->is_focused(); } int video_is_visible(void) { return backend->is_visible(); } int video_is_wm_available(void) { return backend->is_wm_available(); } int video_is_hardware(void) { return backend->is_hardware(); } /* -------------------------------------------------------- */ int video_is_screensaver_enabled(void) { return backend->is_screensaver_enabled(); } void video_toggle_screensaver(int enabled) { backend->toggle_screensaver(enabled); } /* ---------------------------------------------------------- */ /* coordinate translation */ void video_translate(unsigned int vx, unsigned int vy, unsigned int *x, unsigned int *y) { backend->translate(vx, vy, x, y); } void video_get_logical_coordinates(int x, int y, int *trans_x, int *trans_y) { backend->get_logical_coordinates(x, y, trans_x, trans_y); } /* -------------------------------------------------- */ /* input grab */ int video_is_input_grabbed(void) { return backend->is_input_grabbed(); } void video_set_input_grabbed(int enabled) { backend->set_input_grabbed(enabled); } /* -------------------------------------------------- */ /* warp mouse position */ void video_warp_mouse(unsigned int x, unsigned int y) { backend->warp_mouse(x, y); } void video_get_mouse_coordinates(unsigned int *x, unsigned int *y) { backend->get_mouse_coordinates(x, y); } /* -------------------------------------------------- */ /* menu toggling */ int video_have_menu(void) { return backend->have_menu(); } void video_toggle_menu(int on) { backend->toggle_menu(on); } /* ------------------------------------------------------------ */ void video_blit(void) { backend->blit(); } /* ------------------------------------------------------------ */ int video_get_wm_data(video_wm_data_t *wm_data) { return backend->get_wm_data(wm_data); } /* ------------------------------------------------------------ */ void video_show_cursor(int enabled) { backend->show_cursor(enabled); } schismtracker-20250313/schism/widget-keyhandler.c000066400000000000000000000573521476471630300217050ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "page.h" #include "song.h" #include "keyboard.h" #include "widget.h" /* --------------------------------------------------------------------- */ /* n => the delta-value */ static void numentry_move_cursor(struct widget *widget, int n) { if (widget->d.numentry.reverse) return; n += *(widget->d.numentry.cursor_pos); n = CLAMP(n, 0, widget->width - 1); if (*(widget->d.numentry.cursor_pos) == n) return; *(widget->d.numentry.cursor_pos) = n; status.flags |= NEED_UPDATE; } static void textentry_move_cursor(struct widget *widget, int n) { n += widget->d.textentry.cursor_pos; n = CLAMP(n, 0, widget->d.textentry.max_length); if (widget->d.textentry.cursor_pos == n) return; widget->d.textentry.cursor_pos = n; status.flags |= NEED_UPDATE; } static void bitset_move_cursor(struct widget *widget, int n) { n += *widget->d.bitset.cursor_pos; n = CLAMP(n, 0, widget->d.bitset.nbits-1); if (*widget->d.bitset.cursor_pos == n) return; *widget->d.bitset.cursor_pos = n; status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ /* thumbbar value prompt */ static void thumbbar_prompt_finish(int n) { if (n >= ACTIVE_WIDGET.d.thumbbar.min && n <= ACTIVE_WIDGET.d.thumbbar.max) { ACTIVE_WIDGET.d.thumbbar.value = n; if (ACTIVE_WIDGET.changed) ACTIVE_WIDGET.changed(); } status.flags |= NEED_UPDATE; } static int thumbbar_prompt_value(struct widget *widget, struct key_event *k) { int c; if (!NO_MODIFIER(k->mod)) { /* annoying */ return 0; } if (k->sym == SCHISM_KEYSYM_MINUS) { if (widget->d.thumbbar.min >= 0) return 0; c = '-'; } else { c = numeric_key_event(k, 0); if (c < 0) return 0; c += '0'; } numprompt_create("Enter Value", thumbbar_prompt_finish, c); return 1; } /* --------------------------------------------------------------------- */ /* Find backtabs. */ static inline int find_tab_to(int target) { for (int i = 0; i < *total_widgets; i++) { if (widgets[i].next.tab == target && i != target) { return i; } } return -1; } static inline int find_down_to(int target) { for (int i = 0; i < *total_widgets; i++) { if (widgets[i].next.down == target && i != target) { return i; } } return -1; } static inline int find_right_to(int target) { for (int i = 0; i < *total_widgets; i++) { if (widgets[i].next.right == target && i != target) { return i; } } return -1; } static inline int find_right_or_down_to(int target, int checkNotEqual) { if (status.flags & CLASSIC_MODE) { int right_to = find_right_to(target); if(right_to > -1 && right_to != checkNotEqual) return right_to; int down_to = find_down_to(target); if(down_to > -1 && down_to != checkNotEqual) return down_to; } else { int down_to = find_down_to(target); if(down_to > -1 && down_to != checkNotEqual) return down_to; int right_to = find_right_to(target); if(right_to > -1 && right_to != checkNotEqual) return right_to; } return -1; } static inline int find_tab_to_recursive(int target) { int current = target; for(int i = 0; i < *total_widgets; i++) { int widget_backtab = widgets[current].next.backtab; if(widget_backtab > -1) return widget_backtab; int tab_to = find_tab_to(current); if(tab_to > -1) return tab_to; int right_or_down_to = find_right_or_down_to(current, target); if(right_or_down_to > -1) { current = right_or_down_to; continue; } return -1; } return -1; } static void _backtab(void) { /* hunt for a widget that leads back to this one */ if (!total_widgets || !selected_widget) return; int selected = *selected_widget; int backtab = find_tab_to_recursive(selected); if(backtab > -1) { widget_change_focus_to(backtab); return; } int right_or_down_to = find_right_or_down_to(selected, selected); if(right_or_down_to > -1) widget_change_focus_to(right_or_down_to); } /* return: 1 = handled text, 0 = didn't */ int widget_handle_text_input(const char* text_input) { struct widget* widget = &ACTIVE_WIDGET; if (!widget) return 0; switch (widget->type) { case WIDGET_OTHER: if (widget->accept_text && widget->d.other.handle_text_input && ACTIVE_WIDGET.d.other.handle_text_input(text_input)) return 1; break; case WIDGET_NUMENTRY: if (widget_numentry_handle_text(widget, text_input)) return 1; break; case WIDGET_TEXTENTRY: if (widget_textentry_add_text(widget, text_input)) return 1; break; default: break; } return 0; } static int widget_menutoggle_handle_key(struct widget *w, struct key_event *k) { if( ((k->mod & (SCHISM_KEYMOD_CTRL | SCHISM_KEYMOD_ALT | SCHISM_KEYMOD_GUI)) == 0) && w->d.menutoggle.activation_keys) { const char* m = w->d.menutoggle.activation_keys; const char* p = strchr(m, (char)k->sym); if (p && *p) { w->d.menutoggle.state = p - m; if(w->changed) w->changed(); status.flags |= NEED_UPDATE; return 1; } } return 0; } static int widget_bitset_handle_key(struct widget *w, struct key_event *k) { if( ((k->mod & (SCHISM_KEYMOD_CTRL | SCHISM_KEYMOD_ALT | SCHISM_KEYMOD_GUI)) == 0) && w->d.bitset.activation_keys) { const char* m = w->d.bitset.activation_keys; const char* p = strchr(m, (char)k->sym); if (p && *p) { int bit_index = p-m; w->d.bitset.value ^= (1 << bit_index); if(w->changed) w->changed(); status.flags |= NEED_UPDATE; return 1; } } return 0; } /* return: 1 = handled key, 0 = didn't */ int widget_handle_key(struct key_event * k) { struct widget *widget = &ACTIVE_WIDGET; if (!widget) return 0; int n, onw, wx, fmin, fmax, pad; void (*changed)(void); enum widget_type current_type = widget->type; if (!(status.flags & DISKWRITER_ACTIVE) && (current_type == WIDGET_OTHER) && widget->d.other.handle_key(k)) return 1; if (!(status.flags & DISKWRITER_ACTIVE) && k->mouse && (status.flags & CLASSIC_MODE)) { switch(current_type) { case WIDGET_NUMENTRY: if (k->mouse_button == MOUSE_BUTTON_LEFT) { k->sym = SCHISM_KEYSYM_MINUS; k->mouse = MOUSE_NONE; } else if (k->mouse_button == MOUSE_BUTTON_RIGHT) { k->sym = SCHISM_KEYSYM_PLUS; k->mouse = MOUSE_NONE; } break; default: break; }; } if (k->mouse == MOUSE_CLICK) { if (status.flags & DISKWRITER_ACTIVE) return 0; switch (current_type) { case WIDGET_TOGGLE: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; widget->d.toggle.state = !widget->d.toggle.state; if (widget->changed) widget->changed(); status.flags |= NEED_UPDATE; return 1; case WIDGET_MENUTOGGLE: if (!NO_MODIFIER(k->mod)) return 0; if (k->state == KEY_RELEASE) return 1; widget->d.menutoggle.state = (widget->d.menutoggle.state + 1) % widget->d.menutoggle.num_choices; if (widget->changed) widget->changed(); status.flags |= NEED_UPDATE; return 1; default: break; } } else if (k->mouse == MOUSE_DBLCLICK) { if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR) { if (!NO_MODIFIER(k->mod)) return 0; widget->d.panbar.muted = !widget->d.panbar.muted; changed = widget->changed; if (changed) changed(); return 1; } } if (k->mouse == MOUSE_CLICK || (k->mouse == MOUSE_NONE && k->sym == SCHISM_KEYSYM_RETURN)) { #if 0 if (k->mouse && k->mouse_button == MOUSE_BUTTON_MIDDLE) { if (status.flags & DISKWRITER_ACTIVE) return 0; if (k->state == KEY_PRESS) return 1; status.flags |= CLIPPY_PASTE_SELECTION; return 1; } #endif if (k->mouse && (current_type == WIDGET_THUMBBAR || current_type == WIDGET_PANBAR)) { if (status.flags & DISKWRITER_ACTIVE) return 0; /* swallow it */ if (!k->on_target) return 0; fmin = widget->d.thumbbar.min; fmax = widget->d.thumbbar.max; if (current_type == WIDGET_PANBAR) { n = k->fx - ((widget->x + 11) * k->rx); wx = (widget->width - 16) * k->rx; } else { n = k->fx - (widget->x * k->rx); wx = (widget->width-1) * k->rx; } if (n < 0) n = 0; else if (n >= wx) n = wx; n = fmin + ((n * (fmax - fmin)) / wx); if (n < fmin) n = fmin; else if (n > fmax) n = fmax; if (current_type == WIDGET_PANBAR) { widget->d.panbar.muted = 0; widget->d.panbar.surround = 0; if (k->x - widget->x < 11) return 1; if (k->x - widget->x > 19) return 1; } widget_numentry_change_value(widget, n); return 1; } if (k->mouse) { switch (widget->type) { case WIDGET_BUTTON: pad = widget->d.button.padding+1; break; case WIDGET_TOGGLEBUTTON: pad = widget->d.togglebutton.padding+1; break; default: pad = 0; }; onw = ((signed) k->x < widget->x || (signed) k->x >= widget->x + widget->width + pad || (signed) k->y != widget->y) ? 0 : 1; n = (k->state == KEY_PRESS && onw) ? 1 : 0; if (widget->depressed != n) status.flags |= NEED_UPDATE; else if (k->state == KEY_RELEASE) return 1; // swallor widget->depressed = n; if (current_type != WIDGET_TEXTENTRY && current_type != WIDGET_NUMENTRY) { if (k->state == KEY_PRESS || !onw) return 1; } else if (!onw) { return 1; } } else { n = (k->state == KEY_PRESS) ? 1 : 0; if (widget->depressed != n) status.flags |= NEED_UPDATE; else if (k->state == KEY_RELEASE) return 1; // swallor widget->depressed = n; if (k->state == KEY_PRESS) return 1; } if (k->mouse) { switch(current_type) { case WIDGET_MENUTOGGLE: case WIDGET_BUTTON: case WIDGET_TOGGLEBUTTON: if (k->on_target && widget->activate) widget->activate(); default: break; }; } else if (current_type != WIDGET_OTHER) { if (widget->activate) widget->activate(); } switch (current_type) { case WIDGET_OTHER: break; case WIDGET_TEXTENTRY: if (status.flags & DISKWRITER_ACTIVE) return 0; /* LOL WOW THIS SUCKS */ if (k->mouse == MOUSE_CLICK && k->on_target) { /* position cursor */ n = k->x - widget->x; n = CLAMP(n, 0, widget->width - 1); wx = k->sx - widget->x; wx = CLAMP(wx, 0, widget->width - 1); widget->d.textentry.cursor_pos = n+widget->d.textentry.firstchar; wx = wx+widget->d.textentry.firstchar; if (widget->d.textentry.cursor_pos >= (signed) strlen(widget->d.textentry.text)) widget->d.textentry.cursor_pos = strlen(widget->d.textentry.text); if (wx >= (signed) strlen(widget->d.textentry.text)) wx = strlen(widget->d.textentry.text); status.flags |= NEED_UPDATE; } /* for a text entry, the only thing enter does is run the activate callback. thus, if no activate callback is defined, the key wasn't handled */ return (widget->activate != NULL); case WIDGET_NUMENTRY: if (status.flags & DISKWRITER_ACTIVE) return 0; if (k->mouse == MOUSE_CLICK && k->on_target) { /* position cursor */ n = k->x - widget->x; n = CLAMP(n, 0, widget->width - 1); wx = k->sx - widget->x; wx = CLAMP(wx, 0, widget->width - 1); if (n >= widget->width) n = widget->width-1; *widget->d.numentry.cursor_pos = n; status.flags |= NEED_UPDATE; } break; case WIDGET_TOGGLEBUTTON: if (status.flags & DISKWRITER_ACTIVE) return 0; if (widget->d.togglebutton.group) { /* this also runs the changed callback and redraws the button(s) */ widget_togglebutton_set(widgets, *selected_widget, 1); return 1; } /* else... */ widget->d.togglebutton.state = !widget->d.togglebutton.state; SCHISM_FALLTHROUGH; case WIDGET_BUTTON: /* maybe buttons should ignore the changed callback, and use activate instead... (but still call the changed callback for togglebuttons if they *actually* changed) */ if (widget->changed) widget->changed(); status.flags |= NEED_UPDATE; return 1; default: break; } return 0; } /* a WIDGET_OTHER that *didn't* handle the key itself needs to get run through the switch statement to account for stuff like the tab key */ if (k->state == KEY_RELEASE) return 0; if (k->mouse == MOUSE_SCROLL_UP && current_type == WIDGET_NUMENTRY) { k->sym = SCHISM_KEYSYM_MINUS; } else if (k->mouse == MOUSE_SCROLL_DOWN && current_type == WIDGET_NUMENTRY) { k->sym = SCHISM_KEYSYM_PLUS; } switch (k->sym) { case SCHISM_KEYSYM_ESCAPE: /* this is to keep the text entries from taking the key hostage and inserting '<-' characters instead of showing the menu */ return 0; case SCHISM_KEYSYM_UP: if (status.flags & DISKWRITER_ACTIVE) return 0; if (!NO_MODIFIER(k->mod)) return 0; widget_change_focus_to(widget->next.up); return 1; case SCHISM_KEYSYM_DOWN: if (status.flags & DISKWRITER_ACTIVE) return 0; if (!NO_MODIFIER(k->mod)) return 0; widget_change_focus_to(widget->next.down); return 1; case SCHISM_KEYSYM_TAB: if (status.flags & DISKWRITER_ACTIVE) return 0; if (k->mod & SCHISM_KEYMOD_SHIFT) { _backtab(); return 1; } if (!NO_MODIFIER(k->mod)) return 0; widget_change_focus_to(widget->next.tab); return 1; case SCHISM_KEYSYM_LEFT: if (status.flags & DISKWRITER_ACTIVE) return 0; switch (current_type) { case WIDGET_BITSET: if (NO_MODIFIER(k->mod)) bitset_move_cursor(widget, -1); break; case WIDGET_NUMENTRY: if (!NO_MODIFIER(k->mod)) { return 0; } numentry_move_cursor(widget, -1); return 1; case WIDGET_TEXTENTRY: if (!NO_MODIFIER(k->mod)) { return 0; } textentry_move_cursor(widget, -1); return 1; case WIDGET_PANBAR: widget->d.panbar.muted = 0; widget->d.panbar.surround = 0; /* fall through */ case WIDGET_THUMBBAR: /* I'm handling the key modifiers differently than Impulse Tracker, but only because I think this is much more useful. :) */ n = 1; if (k->mod & (SCHISM_KEYMOD_ALT | SCHISM_KEYMOD_GUI)) n *= 8; if (k->mod & SCHISM_KEYMOD_SHIFT) n *= 4; if (k->mod & SCHISM_KEYMOD_CTRL) n *= 2; n = widget->d.numentry.value - n; widget_numentry_change_value(widget, n); return 1; default: if (!NO_MODIFIER(k->mod)) return 0; widget_change_focus_to(widget->next.left); return 1; } break; case SCHISM_KEYSYM_RIGHT: if (status.flags & DISKWRITER_ACTIVE) return 0; /* pretty much the same as left, but with a few small * changes here and there... */ switch (current_type) { case WIDGET_BITSET: if (NO_MODIFIER(k->mod)) bitset_move_cursor(widget, 1); break; case WIDGET_NUMENTRY: if (!NO_MODIFIER(k->mod)) { return 0; } numentry_move_cursor(widget, 1); return 1; case WIDGET_TEXTENTRY: if (!NO_MODIFIER(k->mod)) { return 0; } textentry_move_cursor(widget, 1); return 1; case WIDGET_PANBAR: widget->d.panbar.muted = 0; widget->d.panbar.surround = 0; /* fall through */ case WIDGET_THUMBBAR: n = 1; if (k->mod & (SCHISM_KEYMOD_ALT | SCHISM_KEYMOD_GUI)) n *= 8; if (k->mod & SCHISM_KEYMOD_SHIFT) n *= 4; if (k->mod & SCHISM_KEYMOD_CTRL) n *= 2; n = widget->d.numentry.value + n; widget_numentry_change_value(widget, n); return 1; default: if (!NO_MODIFIER(k->mod)) return 0; widget_change_focus_to(widget->next.right); return 1; } break; case SCHISM_KEYSYM_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; widget_numentry_change_value(widget, n); return 1; default: break; } break; case SCHISM_KEYSYM_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; widget_numentry_change_value(widget, n); return 1; default: break; } break; case SCHISM_KEYSYM_SPACE: if (status.flags & DISKWRITER_ACTIVE) return 0; switch (current_type) { case WIDGET_BITSET: if (!NO_MODIFIER(k->mod)) return 0; widget->d.bitset.value ^= (1 << *widget->d.bitset.cursor_pos); if (widget->changed) widget->changed(); status.flags |= NEED_UPDATE; return 1; case WIDGET_TOGGLE: if (!NO_MODIFIER(k->mod)) return 0; widget->d.toggle.state = !widget->d.toggle.state; if (widget->changed) widget->changed(); status.flags |= NEED_UPDATE; return 1; case WIDGET_MENUTOGGLE: if (!NO_MODIFIER(k->mod)) return 0; widget->d.menutoggle.state = (widget->d.menutoggle.state + 1) % widget->d.menutoggle.num_choices; if (widget->changed) widget->changed(); status.flags |= NEED_UPDATE; return 1; case WIDGET_PANBAR: if (!NO_MODIFIER(k->mod)) return 0; widget->d.panbar.muted = !widget->d.panbar.muted; changed = widget->changed; widget_change_focus_to(widget->next.down); if (changed) changed(); return 1; default: break; } break; case SCHISM_KEYSYM_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 & SCHISM_KEYMOD_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 SCHISM_KEYSYM_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 SCHISM_KEYSYM_PLUS: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_NUMENTRY) { widget_numentry_change_value(widget, widget->d.numentry.value + 1); return 1; } break; case SCHISM_KEYSYM_MINUS: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_NUMENTRY && NO_MODIFIER(k->mod)) { widget_numentry_change_value(widget, widget->d.numentry.value - 1); return 1; } break; case SCHISM_KEYSYM_l: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR) { if (k->mod & SCHISM_KEYMOD_ALT) { song_set_pan_scheme(PANS_LEFT); return 1; } else if (NO_MODIFIER(k->mod)) { widget->d.panbar.muted = 0; widget->d.panbar.surround = 0; widget_numentry_change_value(widget, 0); return 1; } } break; case SCHISM_KEYSYM_m: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR) { if (k->mod & SCHISM_KEYMOD_ALT) { song_set_pan_scheme(PANS_MONO); return 1; } else if (NO_MODIFIER(k->mod)) { widget->d.panbar.muted = 0; widget->d.panbar.surround = 0; widget_numentry_change_value(widget, 32); return 1; } } break; case SCHISM_KEYSYM_r: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR) { if (k->mod & SCHISM_KEYMOD_ALT) { song_set_pan_scheme(PANS_RIGHT); return 1; } else if (NO_MODIFIER(k->mod)) { widget->d.panbar.muted = 0; widget->d.panbar.surround = 0; widget_numentry_change_value(widget, 64); return 1; } } break; case SCHISM_KEYSYM_s: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR) { if (k->mod & SCHISM_KEYMOD_ALT) { song_set_pan_scheme(PANS_STEREO); return 1; } else if(NO_MODIFIER(k->mod)) { widget->d.panbar.muted = 0; widget->d.panbar.surround = 1; if (widget->changed) widget->changed(); status.flags |= NEED_UPDATE; return 1; } } break; case SCHISM_KEYSYM_a: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR && (k->mod & SCHISM_KEYMOD_ALT)) { song_set_pan_scheme(PANS_AMIGA); return 1; } break; #if 0 case SCHISM_KEYSYM_x: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR && (k->mod & SCHISM_KEYMOD_ALT)) { song_set_pan_scheme(PANS_CROSS); return 1; } break; #endif case SCHISM_KEYSYM_SLASH: case SCHISM_KEYSYM_KP_DIVIDE: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR && (k->mod & SCHISM_KEYMOD_ALT)) { song_set_pan_scheme(PANS_SLASH); return 1; } break; case SCHISM_KEYSYM_BACKSLASH: if (status.flags & DISKWRITER_ACTIVE) return 0; if (current_type == WIDGET_PANBAR && (k->mod & SCHISM_KEYMOD_ALT)) { song_set_pan_scheme(PANS_BACKSLASH); return 1; } break; default: /* this avoids a warning about all the values of an enum not being handled. (sheesh, it's already hundreds of lines long as it is!) */ break; } if (status.flags & DISKWRITER_ACTIVE) return 0; /* if we're here, that mess didn't completely handle the key (gosh...) so now here's another mess. */ switch (current_type) { case WIDGET_MENUTOGGLE: if (widget_menutoggle_handle_key(widget, k)) return 1; break; case WIDGET_BITSET: if (widget_bitset_handle_key(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 & (SCHISM_KEYMOD_CTRL | SCHISM_KEYMOD_ALT | SCHISM_KEYMOD_GUI)) == 0 && k->text && widget_textentry_add_text(widget, k->text)) return 1; break; case WIDGET_NUMENTRY: if ((k->mod & (SCHISM_KEYMOD_CTRL | SCHISM_KEYMOD_ALT | SCHISM_KEYMOD_GUI)) == 0 && k->text && widget_numentry_handle_text(widget, k->text)) return 1; break; default: break; } /* if we got down here the key wasn't handled */ return 0; } schismtracker-20250313/schism/widget.c000066400000000000000000000434011476471630300175470ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "charset.h" #include "it.h" #include "page.h" #include "widget.h" #include "vgamem.h" #include "str.h" /* --------------------------------------------------------------------- */ /* create_* functions (the constructors, if you will) */ void widget_create_toggle(struct widget *w, int x, int y, int next_up, int next_down, int next_left, int next_right, int next_tab, void (*changed) (void)) { w->type = WIDGET_TOGGLE; w->accept_text = 0; w->x = x; w->y = y; w->width = 3; /* "Off" */ w->next.up = next_up; w->next.left = next_left; w->next.down = next_down; w->next.right = next_right; w->next.tab = next_tab; w->next.backtab = -1; w->changed = changed; w->activate = NULL; w->depressed = 0; w->height = 1; } void widget_create_menutoggle(struct widget *w, int x, int y, int next_up, int next_down, int next_left, int next_right, int next_tab, void (*changed) (void), const char *const *choices) { int n, width = 0, len; for (n = 0; choices[n]; n++) { len = strlen(choices[n]); if (width < len) width = len; } w->type = WIDGET_MENUTOGGLE; w->accept_text = 0; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.left = next_left; w->next.down = next_down; w->next.right = next_right; w->next.tab = next_tab; w->next.backtab = -1; w->changed = changed; w->d.menutoggle.choices = choices; w->d.menutoggle.num_choices = n; w->activate = NULL; w->d.menutoggle.activation_keys = NULL; } void widget_create_button(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_left, int next_right, int next_tab, void (*changed) (void), const char *text, int padding) { w->type = WIDGET_BUTTON; w->accept_text = 0; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.left = next_left; w->next.down = next_down; w->next.right = next_right; w->next.tab = next_tab; w->next.backtab = -1; w->changed = changed; w->d.button.text = text; w->d.button.padding = padding; w->activate = NULL; } void widget_create_togglebutton(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_left, int next_right, int next_tab, void (*changed) (void), const char *text, int padding, const int *group) { w->type = WIDGET_TOGGLEBUTTON; w->accept_text = 0; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.left = next_left; w->next.down = next_down; w->next.right = next_right; w->next.tab = next_tab; w->next.backtab = -1; w->changed = changed; w->d.togglebutton.text = text; w->d.togglebutton.padding = padding; w->d.togglebutton.group = group; w->activate = NULL; } void widget_create_textentry(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_tab, void (*changed) (void), char *text, int max_length) { w->type = WIDGET_TEXTENTRY; w->accept_text = 1; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.down = next_down; w->next.right = -1; w->next.tab = next_tab; w->next.backtab = -1; 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 widget_create_numentry(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_tab, void (*changed) (void), int min, int max, int *cursor_pos) { w->type = WIDGET_NUMENTRY; w->accept_text = 1; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.down = next_down; w->next.right = -1; w->next.tab = next_tab; w->next.backtab = -1; 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 widget_create_thumbbar(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_tab, void (*changed) (void), int min, int max) { w->type = WIDGET_THUMBBAR; w->accept_text = 0; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.down = next_down; w->next.right = -1; w->next.tab = next_tab; w->next.backtab = -1; w->changed = changed; w->d.thumbbar.min = min; w->d.thumbbar.max = max; w->d.thumbbar.text_at_min = NULL; w->d.thumbbar.text_at_max = NULL; w->activate = NULL; } void widget_create_bitset(struct widget *w, int x, int y, int width, int next_up, int next_down, int next_tab, void (*changed) (void), int nbits, const char* bits_on, const char* bits_off, int *cursor_pos) { w->type = WIDGET_BITSET; w->accept_text = 0; w->x = x; w->y = y; w->width = width; w->depressed = 0; w->height = 1; w->next.up = next_up; w->next.down = next_down; w->next.right = -1; w->next.tab = next_tab; w->next.backtab = -1; w->changed = changed; w->d.numentry.reverse = 0; w->d.bitset.nbits = nbits; w->d.bitset.bits_on = bits_on; w->d.bitset.bits_off = bits_off; w->d.bitset.cursor_pos = cursor_pos; w->activate = NULL; } void widget_create_panbar(struct widget *w, int x, int y, int next_up, int next_down, int next_tab, void (*changed) (void), int channel) { w->type = WIDGET_PANBAR; w->accept_text = 0; w->x = x; w->y = y; w->width = 24; w->height = 1; w->next.up = next_up; w->next.down = next_down; w->next.right = -1; w->next.tab = next_tab; w->next.backtab = -1; 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 widget_create_other(struct widget *w, int next_tab, int (*i_handle_key) (struct key_event *k), int (*i_handle_text_input) (const char* text), void (*i_redraw) (void)) { w->type = WIDGET_OTHER; w->accept_text = 0; w->next.up = w->next.down = w->next.left = w->next.right = 0; w->next.tab = next_tab; w->next.backtab = -1; /* 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.handle_text_input = i_handle_text_input; w->d.other.redraw = i_redraw; } /* --------------------------------------------------------------------- */ /* generic text stuff */ void text_add_char(char *text, uint8_t c, int *cursor_pos, int max_length) { int len; text[max_length] = 0; len = strlen(text); if (*cursor_pos >= max_length) *cursor_pos = max_length - 1; /* FIXME: this causes some weirdness with the end key. maybe hitting end should trim spaces? */ while (len < *cursor_pos) text[len++] = ' '; memmove(text + *cursor_pos + 1, text + *cursor_pos, max_length - *cursor_pos - 1); text[*cursor_pos] = c; (*cursor_pos)++; } void text_delete_char(char *text, int *cursor_pos, int max_length) { if (*cursor_pos == 0) return; (*cursor_pos)--; memmove(text + *cursor_pos, text + *cursor_pos + 1, max_length - *cursor_pos); } void text_delete_next_char(char *text, int *cursor_pos, int max_length) { memmove(text + *cursor_pos, text + *cursor_pos + 1, max_length - *cursor_pos); } /* --------------------------------------------------------------------- */ /* text entries */ static void textentry_reposition(struct widget *w) { int len; w->d.textentry.text[w->d.textentry.max_length] = 0; len = strlen(w->d.textentry.text); if (w->d.textentry.cursor_pos < w->d.textentry.firstchar) { w->d.textentry.firstchar = w->d.textentry.cursor_pos; } else if (w->d.textentry.cursor_pos > len) { w->d.textentry.cursor_pos = len; } else if (w->d.textentry.cursor_pos > (w->d.textentry.firstchar + w->width - 1)) { w->d.textentry.firstchar = w->d.textentry.cursor_pos - w->width + 1; if (w->d.textentry.firstchar < 0) w->d.textentry.firstchar = 0; } } int widget_textentry_add_char(struct widget *w, unsigned char c) { text_add_char(w->d.textentry.text, c, &(w->d.textentry.cursor_pos), w->d.textentry.max_length); if (w->changed) w->changed(); status.flags |= NEED_UPDATE; return 1; } int widget_textentry_add_text(struct widget *w, const char* text) { if (!text) return 0; for (; *text; text++) if (!widget_textentry_add_char(w, *(unsigned char *)text)) return 0; return 1; } /* --------------------------------------------------------------------- */ /* numeric entries */ void widget_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; } static inline int fast_pow10(int n) { static const int tens[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 }; /* use our cache if we can to avoid buffer overrun */ return (n < (int)ARRAY_SIZE(tens)) ? tens[n] : pow(10, n); } int widget_numentry_handle_text(struct widget *w, const char* text_input) { if (text_input == NULL) return 0; char valid_digits[] = "0123456789"; int len = strspn((const char*)text_input, valid_digits); if (len < 1) return 1; int value = w->d.numentry.value; if (w->d.numentry.reverse) { for (int i = 0; i < len; i++) { value *= 10; value += text_input[0] - '0'; } } else { int pos = *(w->d.numentry.cursor_pos), n = 0; for (; n < len && pos < w->width; n++, pos++) { int pow10_of_pos = fast_pow10(w->width - 1 - pos); /* isolate our digit and subtract it */ value -= value % (pow10_of_pos * 10) / pow10_of_pos * pow10_of_pos; /* add our digit in its place */ value += (text_input[n] - '0') * pow10_of_pos; } *(w->d.numentry.cursor_pos) = CLAMP(pos, 0, w->width - 1); } /* notify that our value changed */ widget_numentry_change_value(w, value); return 1; } /* --------------------------------------------------------------------- */ /* toggle buttons */ void widget_togglebutton_set(struct widget *p_widgets, int widget, int do_callback) { const int *group = p_widgets[widget].d.togglebutton.group; int i; if (!group) return; /* assert */ for (i = 0; group[i] >= 0; i++) p_widgets[group[i]].d.togglebutton.state = 0; p_widgets[widget].d.togglebutton.state = 1; if (do_callback && p_widgets[widget].changed) p_widgets[widget].changed(); status.flags |= NEED_UPDATE; } /* --------------------------------------------------------------------- */ /* /me takes a deep breath */ void widget_draw_widget(struct widget *w, int selected) { char buf[16] = "Channel 42"; const char *ptr, *endptr; /* for the menutoggle */ char *str; int n; const int tfg = selected ? 0 : 2; const 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, DEFAULT_FG, 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, DEFAULT_FG, 0); ptr = w->d.menutoggle.choices[w->d.menutoggle.state]; endptr = strchr(ptr, ' '); if (endptr) { n = endptr - ptr; draw_text_len(ptr, n, w->x, w->y, tfg, tbg); draw_text(endptr + 1, w->x + n + 1, w->y, 2, 0); } else { draw_text(ptr, w->x, w->y, tfg, tbg); } break; case WIDGET_BUTTON: draw_box(w->x - 1, w->y - 1, w->x + w->width + 2, w->y + 1, BOX_THIN | BOX_INNER | ( w->depressed ? BOX_INSET : BOX_OUTSET)); draw_text(w->d.button.text, w->x + w->d.button.padding, w->y, selected ? 3 : 0, 2); break; case WIDGET_TOGGLEBUTTON: draw_box(w->x - 1, w->y - 1, w->x + w->width + 2, w->y + 1, BOX_THIN | BOX_INNER |( (w->d.togglebutton.state || w->depressed) ? BOX_INSET : BOX_OUTSET)); draw_text(w->d.togglebutton.text, w->x + w->d.togglebutton.padding, w->y, selected ? 3 : 0, 2); break; case WIDGET_TEXTENTRY: textentry_reposition(w); draw_text_len(w->d.textentry.text + w->d.textentry.firstchar, w->width, w->x, w->y, 2, 0); if (selected && !drew_cursor) { n = w->d.textentry.cursor_pos - w->d.textentry.firstchar; draw_char(((n < (signed) strlen(w->d.textentry.text)) ? (w->d.textentry.text[w->d.textentry.cursor_pos]) : ' '), w->x + n, w->y, 0, 3); } break; case WIDGET_NUMENTRY: if (w->d.numentry.reverse) { str = str_from_num(w->width, w->d.numentry.value, buf); while (*str == '0') str++; draw_text_len("", w->width, w->x, w->y, 2, 0); if (*str) { draw_text(str, (w->x+w->width) - strlen(str), w->y, 2, 0); } if (selected && !drew_cursor) { while (str[0] && str[1]) str++; if (!str[0]) str[0] = ' '; draw_char(str[0], w->x + (w->width-1), w->y, 0, 3); } } else { if (w->d.numentry.min < 0 || w->d.numentry.max < 0) { str_from_num_signed(w->width, w->d.numentry.value, buf); } else { str_from_num(w->width, w->d.numentry.value, buf); } draw_text_len(buf, w->width, w->x, w->y, 2, 0); if (selected && !drew_cursor) { n = *(w->d.numentry.cursor_pos); draw_char(buf[n], w->x + n, w->y, 0, 3); } } break; case WIDGET_BITSET: for(n = 0; n < w->d.bitset.nbits; ++n) { int set = !!(w->d.bitset.value & (1 << n)); char label_c1 = set ? w->d.bitset.bits_on[n*2+0] : w->d.bitset.bits_off[n*2+0]; char label_c2 = set ? w->d.bitset.bits_on[n*2+1] : w->d.bitset.bits_off[n*2+1]; int is_focused = selected && n == *w->d.bitset.cursor_pos; /* In textentries, cursor=0,3; normal=2,0 */ static const char fg_selection[4] = { 2, /* not cursor, not set */ 3, /* not cursor, is set */ 0, /* has cursor, not set */ 0 /* has cursor, is set */ }; static const char bg_selection[4] = { 0, /* not cursor, not set */ 0, /* not cursor, is set */ 2, /* has cursor, not set */ 3 /* has cursor, is set */ }; fg = fg_selection[set + is_focused*2]; bg = bg_selection[set + is_focused*2]; if(label_c2) draw_half_width_chars(label_c1, label_c2, w->x + n, w->y, fg, bg, fg, bg); else draw_char(label_c1, w->x + n, w->y, fg, bg); } break; case WIDGET_THUMBBAR: if (w->d.thumbbar.text_at_min && w->d.thumbbar.min == w->d.thumbbar.value) { draw_text_len(w->d.thumbbar.text_at_min, w->width, w->x, w->y, selected ? 3 : 2, 0); } else if (w->d.thumbbar.text_at_max && w->d.thumbbar.max == w->d.thumbbar.value) { /* this will probably do Bad Things if the text is too long */ int len = strlen(w->d.thumbbar.text_at_max); int pos = w->x + w->width - len; draw_fill_chars(w->x, w->y, pos - 1, w->y, DEFAULT_FG, 0); draw_text_len(w->d.thumbbar.text_at_max, len, pos, w->y, selected ? 3 : 2, 0); } else { draw_thumb_bar(w->x, w->y, w->width, w->d.thumbbar.min, w->d.thumbbar.max, w->d.thumbbar.value, selected); } if (w->d.thumbbar.min < 0 || w->d.thumbbar.max < 0) { str_from_num_signed(3, w->d.thumbbar.value, buf); } else { str_from_num(3, w->d.thumbbar.value, buf); } draw_text(buf, w->x + w->width + 1, w->y, 1, 2); break; case WIDGET_PANBAR: str_from_num(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(str_from_num(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 widget_change_focus_to(int new_widget_index) { if(new_widget_index == *selected_widget || new_widget_index < 0 || new_widget_index >= *total_widgets) { return; } if (ACTIVE_WIDGET.depressed) ACTIVE_WIDGET.depressed = 0; *selected_widget = new_widget_index; ACTIVE_WIDGET.depressed = 0; if (ACTIVE_WIDGET.type == WIDGET_TEXTENTRY) ACTIVE_WIDGET.d.textentry.cursor_pos = strlen(ACTIVE_WIDGET.d.textentry.text); status.flags |= NEED_UPDATE; } static int _find_widget_xy(int x, int y) { struct widget *w; int i, pad; if (!total_widgets) return -1; for (i = 0; i < *total_widgets; i++) { w = widgets + i; switch (w->type) { case WIDGET_BUTTON: pad = w->d.button.padding + 1; break; case WIDGET_TOGGLEBUTTON: pad = w->d.togglebutton.padding + 1; break; default: pad = 0; } if (x >= w->x && x < w->x + w->width + pad && y >= w->y && y < w->y + w->height) { return i; } } return -1; } int widget_change_focus_to_xy(int x, int y) { int n = _find_widget_xy(x, y); if (n >= 0) { widget_change_focus_to(n); return 1; } return 0; } schismtracker-20250313/schism/xpmdata.c000066400000000000000000000175711476471630300177330ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "video.h" /* for declaration of xpmdata */ #include "util.h" #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; } /* * convert colour spec to RGB (in 0xrrggbb format). * return 1 if successful. */ static int color_to_rgb(const char *spec, int speclen, uint32_t *rgb) { /* poor man's rgb.txt */ static struct { const char *name; uint32_t rgb; } known[] = { {"none", 0xffffffff}, {"black", 0x00000000}, {"white", 0x00ffffff}, {"red", 0x00ff0000}, {"green", 0x0000ff00}, {"blue", 0x000000ff}, {"gray27",0x00454545}, {"gray4", 0x000a0a0a}, }; if(spec[0] == '#') { char buf[7]; switch(speclen) { case 4: buf[0] = buf[1] = spec[1]; buf[2] = buf[3] = spec[2]; buf[4] = buf[5] = spec[3]; break; case 7: memcpy(buf, spec + 1, 6); break; case 13: buf[0] = spec[1]; buf[1] = spec[2]; buf[2] = spec[5]; buf[3] = spec[6]; buf[4] = spec[9]; buf[5] = spec[10]; break; } buf[6] = '\0'; *rgb = strtol(buf, NULL, 16); return 1; } else { size_t i; for(i = 0; i < ARRAY_SIZE(known); i++) if(string_equal(known[i].name, spec, speclen)) { *rgb = known[i].rgb; return 1; } return 0; } } #define STARTING_HASH_SIZE 256 struct hash_entry { char *key; uint32_t color; struct hash_entry *next; }; struct color_hash { struct hash_entry **table; struct hash_entry *entries; /* array of all entries */ struct hash_entry *next_free; int size; int maxnum; }; static int hash_key(const char *key, int cpp, int size) { int hash; hash = 0; while ( cpp-- > 0 ) { hash = hash * 33 + *key++; } return hash & (size - 1); } static struct color_hash *create_colorhash(int maxnum) { int bytes, s; struct color_hash *hash; /* we know how many entries we need, so we can allocate everything here */ hash = malloc(sizeof *hash); if(!hash) return NULL; /* use power-of-2 sized hash table for decoding speed */ for(s = STARTING_HASH_SIZE; s < maxnum; s <<= 1) ; hash->size = s; hash->maxnum = maxnum; bytes = hash->size * sizeof(struct hash_entry **); hash->entries = NULL; /* in case malloc fails */ hash->table = malloc(bytes); if(!hash->table) return NULL; memset(hash->table, 0, bytes); hash->entries = malloc(maxnum * sizeof(struct hash_entry)); if(!hash->entries) return NULL; hash->next_free = hash->entries; return hash; } static int add_colorhash(struct color_hash *hash, char *key, int cpp, uint32_t color) { int h = hash_key(key, cpp, hash->size); struct hash_entry *e = hash->next_free++; e->color = color; e->key = key; e->next = hash->table[h]; hash->table[h] = e; return 1; } /* fast lookup that works if cpp == 1 */ #define QUICK_COLORHASH(hash, key) ((hash)->table[*(uint8_t *)(key)]->color) static uint32_t get_colorhash(struct color_hash *hash, const char *key, int cpp) { struct hash_entry *entry = hash->table[hash_key(key, cpp, hash->size)]; while(entry) { if(memcmp(key, entry->key, cpp) == 0) return entry->color; entry = entry->next; } return 0; /* garbage in - garbage out */ } static void free_colorhash(struct color_hash *hash) { if(hash && hash->table) { free(hash->table); free(hash->entries); free(hash); } } int xpmdata(const char *data[], uint32_t **pixels, int *w, int *h) { int n; int x, y; int ncolors, cpp; uint32_t *dst; struct color_hash *colors = NULL; char *keystrings = NULL, *nextkey; const char *line; const char ***xpmlines = NULL; #define get_next_line(q) *(*q)++ int error; error = 0; xpmlines = (const char ***) &data; line = get_next_line(xpmlines); if(!line) goto done; /* * The header string of an XPMv3 image has the format * * [ ] * * where the hotspot coords are intended for mouse cursors. * Right now we don't use the hotspots but it should be handled * one day. */ if(sscanf(line, "%d %d %d %d", w, h, &ncolors, &cpp) != 4 || *w <= 0 || *h <= 0 || ncolors <= 0 || cpp <= 0) { error = 1; goto done; } keystrings = malloc(ncolors * cpp); if(!keystrings) { error = 2; goto done; } nextkey = keystrings; *pixels = malloc(*w * *h * sizeof(*pixels)); if (!*pixels) { error = 2; goto done; } /* Read the colors */ colors = create_colorhash(ncolors); if (!colors) { error = 2; goto done; } for(n = 0; n < ncolors; ++n) { const char *p; line = get_next_line(xpmlines); if(!line) goto done; p = line + cpp + 1; /* parse a colour definition */ for(;;) { char nametype; const char *colname; uint32_t rgb, pixel; 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); /* UINT32_MAX is transparent */ pixel = (rgb == UINT32_C(0xFFFFFFFF) ? 0 : (rgb | UINT32_C(0xFF000000))); add_colorhash(colors, nextkey, cpp, pixel); nextkey += cpp; break; } } /* Read the pixels */ dst = *pixels; for(y = 0; y < *h; y++) { line = get_next_line(xpmlines); for (x = 0; x < *w; x++) dst[x] = get_colorhash(colors, line + x * cpp, cpp); dst += *w; } done: if (error) { free(*pixels); *pixels = NULL; } free(keystrings); free_colorhash(colors); return error; } schismtracker-20250313/scripts/000077500000000000000000000000001476471630300163175ustar00rootroot00000000000000schismtracker-20250313/scripts/bin2h.sh000077500000000000000000000016311476471630300176610ustar00rootroot00000000000000#!/bin/sh # # this is a bin2h-like program that is useful even when cross compiling. # # it requires: # unix-like "od" tool # unix-like "sed" tool # unix-like "fgrep" tool # # it's designed to be as portable as possible and not necessarily depend on # gnu-style versions of these tools name="" type="unsigned const char" if [ "X$1" = "X-n" ]; then name="$2" shift shift fi if [ "X$1" = "X-t" ]; then type="$2" shift shift fi if [ "X$name" = "X" ]; then echo "Usage: $0 -n name [-t type] files... > output.h" fi if [ "X$OD" = "X" ]; then OD="od" fi if [ "X$SED" = "X" ]; then SED="sed" fi case "$type" in *static*) ;; *) echo "extern $type $name""[];" ;; esac echo "$type $name""[] = {" $OD -b -v "$@" \ | $SED -e 's/^[^ ][^ ]*[ ]*//' \ | $SED -e "s/\([0-9]\{3\}\)/'\\\\\1',/g" \ | $SED -e '$ s/,$//' echo "};" schismtracker-20250313/scripts/build-font.sh000077500000000000000000000005141476471630300207210ustar00rootroot00000000000000#!/bin/sh # make default (builtin) font if [ "$#" -lt 2 ]; then echo >&2 "usage: $0 srcdir fonts > default-font.c" exit 1 fi srcdir="$1" shift for file in "$@"; do ident=font_`basename "$srcdir"/"$file" .fnt | tr /- __` sh "$srcdir"/scripts/bin2h.sh -n "$ident" "$srcdir"/"$file" || exit 1 done schismtracker-20250313/scripts/genhelp.pl000066400000000000000000000040641476471630300203020ustar00rootroot00000000000000#!/usr/bin/perl use 5.8.0; use strict; use warnings; use Encode; use utf8; use open ':encoding(utf-8)'; my $usage = sprintf("Usage: %s srcdir helptexts > helptext.c\n", $0); if ($#ARGV < 1) { print STDERR $usage; exit 1; } foreach my $i (@ARGV) { if ($i eq "--help") { print STDOUT $usage; exit 0; } } my $srcdir = shift(@ARGV); my @helptexts = @ARGV; my $typechars = "|+:;!\%#="; my @arrnames = (); sub die_at { my ($filename, $line, $message) = @_; die(sprintf("%s:%d: %s\n", $filename, $line + 1, $message)); } sub str_begins_with_spn { my ($str1, $str2, @others) = @_; foreach my $char (split('', $str2)) { if (rindex($str1, $char, 0) == 0) { return 1; } } return 0; } print "extern const unsigned char *help_text[];\n\n"; for my $i (0 .. $#helptexts) { my $file; open($file, $srcdir . "/" . $helptexts[$i]) or die(sprintf("could not open file %s", $srcdir . "/" . $helptexts[$i])); my $arrname = sprintf("help_text_%d", $i); printf("static const unsigned char %s[] = {\n", $arrname); my $blank = 1; while (my $line = <$file>) { $line =~ s/[\r\n]+//; # strip newline chars $blank = 0; if (length($line) <= 0) { # ignore empty lines next; } elsif (length($line) > 76) { die_at($helptexts[$i], $., "line is longer than 76 characters"); } elsif ($line =~ / $/) { die_at($helptexts[$i], $., "trailing whitespace"); } elsif (!str_begins_with_spn($line, $typechars)) { die_at($helptexts[$i], $., sprintf("line-type character %s is not one of %s", substr($line, 0, 1), $typechars)) } foreach my $codepoint (split('', $line)) { $codepoint = ord($codepoint); if ($codepoint == 0x00B6) { $codepoint = 0x14; } elsif ($codepoint == 0x00A7) { $codepoint = 0x15; } printf("0x%02x, ", ord(encode("cp437", chr($codepoint)))); } print("0x0A, \n"); } if ($blank) { die_at($helptexts[$i], 0, "file is empty"); } print("0x00};\n\n"); $arrnames[$i] = $arrname; } print("const unsigned char* help_text[] = {\n"); foreach my $s (@arrnames) { printf("\t%s,\n", $s); } print("};\n"); schismtracker-20250313/scripts/half2itf.py000077500000000000000000000005651476471630300204010ustar00rootroot00000000000000#!/usr/bin/env python import sys, struct if sys.version_info[0] > 2: read = sys.stdin.buffer.read write = sys.stdout.buffer.write else: read = sys.stdin.read write = sys.stdout.write write(struct.pack('<1024H', *[ (d & 0xf0) | ((d & 0xf) << 12) for d in struct.unpack('1024B', read()) ])) write(chr(0x12)) write(chr(0x02)) schismtracker-20250313/scripts/itcfg.py000077500000000000000000000504221476471630300177730ustar00rootroot00000000000000#!/usr/bin/env python # coding: utf-8 import sys, struct, time, os """ Here is the general layout of Impulse Tracker's configuration file (IT.CFG). It's quite a bit messier than the .IT format, probably because it wasn't at all intended for people to be tinkering with. Impulse Tracker has practically no error checking when loading the configuration, so it's very easy to cause a crash or hang just by changing values around in a hex editor. See also CONFIG.TXT from the Impulse Tracker source: https://bitbucket.org/jthlim/impulsetracker/src/tip/InternalDocumentation/ 0 1 2 3 4 5 6 7 8 9 A B C D E F ┌───────────────────────────────────────────────────────────────┐ 0000: │ Directories: module, sample, instrument (70 chars each) │ ├───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┤ ├───┴───┼───┼───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┤ 00D0: │.......│Kbd│ Palette settings (48 bytes, 3 per color) │ ├───┬───┼───┼───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┤ ├───┴───┴───┼───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┤ 0100: │...........│ Info page view setup (6 * 8 bytes, see below) │ ├───┬───┬───┼───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┤ ├───┴───┴───┼───┴───┼───┴───┼───┴───┼───┼───┼───┼───┼───┴───┼───┤ 0130: │...........│NumView│Key Sig│NormTrk│Min│Maj│Msk│Div│TrkCols│TVS│ ├───────────┴───────┴───────┴───────┴───┴───┴───┴───┴───────┴───┤ 0140: │ Track view scheme (2 * 100 bytes) │ ├───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┤ ├───┴───┴───┴───┴───┴───┴───┼───┼───┼───┼───┼───┼───┴───┼───┼───┘ 0200: │...........................│VCT│Lnk│PF1│Amp│C-5│FastVol│PF2│ └───────────────────────────┴───┴───┴───┴───┴───┴───────┴───┘ Each directory name is 70 characters although only 64 are used. The first \0 byte terminates the name (unlike some other parts of the interface). The palette is stored in the obvious fashion, three bytes per color going down the columns, for the red/green/blue component of each of the 16 colors. Each component value is in the range 0-63. Kbd: Keyboard type (ignored by IT 2.04+) 0 = United States 1 = United Kingdom 2 = Sweden/Finland 3 = Spain 4 = Portugal 5 = Netherlands 6 = Italy 7 = Germany 8 = France NumView: Number of views active on info page. Even though the config file supports six views, IT only allows creating five. (However, adding a sixth with a hex editor doesn't cause any evident problems) Key Sig: "MUST be 0 (key signatures not defined)" (from IT src) NormTrk: Number of tracks in "normal" (13-column, ST3-style) view in the pattern editor. Min/Maj: Pattern row hilight information. Mask: Pattern edit copy mask: Bit 0: Instrument Bit 1: Volume Bit 2: Effect Div: Whether to draw the divisions between channels. Values other than 0 or 1 will make the track divisions "stuck", i.e. Alt-H will not disable them. TrkCols: How many columns to draw the box around the track view. This also adjusts the positioning of the "normal" channels. If zero, no track view is drawn. This should not have a value of 1. VCT: View-channel cursor tracking (Ctrl-T) Values other than 0 or 1 will make the cursor tracking "stuck", i.e. Ctrl-T will not disable it. Link: Link/Split effect column (zero means split) PF1: Bit 0. On = Centralise cursor in pattern editor Bit 1. On = Highlight current row Bit 2. On = Fast volume mode (Ctrl-J) enabled Bit 3. On = MIDI: Tick Quantize Bit 4. On = Base Program 1 Bit 5. On = Record Note Off Bit 6. On = Record Velocity Bit 7. On = Record Aftertouch Amp: MIDI volume amplification (percentage). C-5: MIDI C-5 note. Default value is 60, i.e. C-5. FastVol: Fast volume percentage. Although this is a 16-bit value, the fast volume dialog only changes the low byte, so if the high byte is nonzero, the fast volume settings will be "stuck". PF2: Bit 0. On = Show default volumes in normal pattern view. Bit 1. On = MIDI: Cut Note Off Track view scheme. The track view settings are stored as 100 (!) two-byte pairs of channel and view type. Channel number is first, numbered from 0; 0FFh is the end mark. Values for the view type: 0 = 13-column, nnn ii vv eee 1 = 10-column, nnniivveee 2 = 7-column, nnnivee (half-width inst / vol / effect value) 3 = 3-column, nnn/_ii/_vv/eee 4 = 2-column, nn/ii/vv/ee (half-width effect values) Info Page settings (starting at 0103h): 3 4 5 6 7 8 9 A B C D E F 0 1 2 ┌───────┬───┬───┬───────┬───────╥───────┬───┬───┬───────┬───────┐ xxxx: │ Type │TCh│Row│Height │MemOff ║ Type │TCh│Row│Height │MemOff │ └───────┴───┴───┴───────┴───────╨───────┴───┴───┴───────┴───────┘ Type: Window "type" of respective info page window 0 = Sample names 1 = 5-channel pattern view 2 = 8-channel pattern view 3 = 10-channel pattern view 4 = 24-channel pattern view 5 = 36-channel pattern view 6 = 64-channel pattern view 7 = Global volume / active channel count 8 = Note dots 9 = Technical details TCh: "Top channel" (from IT src) Row: Row number on screen to draw the view. Numbering starts at 0, and counts downward from the top of the screen. The first info page view should be on row 12. Height: How many rows the view uses. IT disallows sizing windows smaller than 4 rows, but the hard minimum is 3. (Setting most views smaller than that *will* cause a crash.) MemOff: VGA memory offset of beginning of first row. Should be equal to 2 * 80 * row. File format changes: IT <1.06 - original (?) data size is 521 bytes. (Note: only tested with 1.05; information on other versions would be appreciated!) IT 1.06 - 522 bytes First flag byte (PF1) added. IT 2.04 - 522 bytes Keyboard type byte no longer used due to the introduction of KEYBOARD.CFG. IT 2.05 - 522 bytes 24-channel track view added, resulting in renumbering of the 36-channel view, global volume, and technical data. IT 2.06 - 526 bytes Amp, C-5, and FastVol bytes added. IT 2.11 - 526 bytes Info page settings adjusted slightly to use an extra row at the bottom of the screen (just increased the 'height' value for the last visible window by one). IT 2.12 - 527 bytes Second flag byte (PF2) added. Also, 64-channel and note dots views introduced on the info page, thus causing the global volume and technical data to be renumbered again. IT 2.14p5 - 1337 bytes No apparent changes to the file format aside from the size. I can't see a purpose for the extra bytes, aside from leet... at least, changing those values seems to have no effect on the operation of the tracker. Note that no automatic configuration adjustments are made in any IT version when loading a configuration file written by an older version of the tracker (as there's no real version information in the file). And there you have it. If you have any questions, comments, anomalous IT.CFG files, or a decidedly ancient version of Impulse Tracker, send an e-mail to . -- Storlek - http://rigelseven.com/ - http://schismtracker.org/ """ def warning(s): sys.stderr.write(s + "\n") def getnth(n): if n > 3 and n < 21: return "%dth" % n return {1: "%dst", 2: "%dnd", 3: "%drd"}.get(n % 10, "%dth") % n def pluralize(n, s, pl=None): return "%g %s" % (n, [pl or s + "s", s][n == 1]) def asciiz(s): try: s = s.decode("cp437", "replace") except: pass return s.split("\0")[0] if len(sys.argv) != 2 or sys.argv[1] in ["--help", "-h"]: print("usage: %s /path/to/it.cfg > ~/.schism/config" % sys.argv[0]) sys.exit(0) itcfg = open(sys.argv[1], "rb") try: itcfg.seek(0, 2) # EOF itcfg_size = itcfg.tell() if itcfg_size < 527: warning("file is too small -- resave with IT 2.12+") sys.exit(1) itcfg.seek(0) # back to start except IOError: warning("%s: failed to seek, what kind of non-file is this?" % sys.argv[1]) sys.exit(1) dir_modules, dir_samples, dir_instruments = map(asciiz, struct.unpack("70s 70s 70s", itcfg.read(210))) # Not used by IT 2.04+ it203_keyboard = min(struct.unpack("B", itcfg.read(1))[0], 9) palette = "".join([".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"[b & 0x3f] for b in struct.unpack("48B", itcfg.read(48))]) # Info page nastiness. info_window_types = [ "samples", "track5", "track8", "track10", "track12", "track18", "track24", # added in 2.05 "track36", "track64", # added in 2.12 "global", "dots", # added in 2.12 "tech", ] infopage_data = [itcfg.read(8) for n in range(6)] num_views, unknown = struct.unpack(" 6: warning("info page: too many views, will probably crash IT") num_views = 1 nextrow = 12 # row that the next view should start on infopage_layout = [] for n, data in enumerate(infopage_data[:num_views]): nth = getnth(n + 1) # XXX what's this unknown byte? always seems to be 0, but changing it seems not to affect anything. wtype, unknown, firstrow, height, offset = struct.unpack(" nextrow: warning("info page: %s view followed by %s" % (nth, pluralize(firstrow - nextrow, "empty row"))) if height < 3: warning("info page: %s view is %s too short (will cause crash/hang)" % (nth, pluralize(3 - height, "row"))) elif firstrow + height > 50: warning("info page: %s view is %s too tall (might cause crash)" % (nth, pluralize(firstrow + height - 50, "row"))) if wtype > 11: warning("info page: %s view has unknown window type %d (will cause crash)" % (nth, wtype)) wtype = 0 # something sane nextrow = firstrow + height # Work around a Schism Tracker oddity: the saved height of the first view is off by one. # I'm sure there was a good reason for this, but I have no idea what it might have been. if n == 0: height -= 1 infopage_layout.append("%s %d" % (info_window_types[wtype], height)) # IT 2.11 added an extra row to the info page if nextrow == 49: warning("info page: extra row at bottom of screen (old IT version?)") elif nextrow < 50: warning("info page: %d extra rows at bottom of screen (corrupt config?)" % 50 - nextrow) elif nextrow > 50: warning("info page: data extends %s beyond end of screen (corrupt config?)" % pluralize(nextrow - 50, "row")) if num_views == 6: # as far as I can tell, this requires a hex editor, but it works fine once enabled # (handled here rather than above to allow error-checking the rest of the settings) warning("info page: six views visible, omghax") infopage_layout = " ".join(infopage_layout[:num_views]) # Pattern editor settings normal_view, rhmin, rhmaj, edit_copy_mask, draw_divisions, track_cols = struct.unpack(" 78: warning("pattern editor: track setup is too wide, display will look trashed") track_view_scheme = [] end_reached = False prevchannel = -1 for n, (channel, scheme) in enumerate(struct.unpack("BB", itcfg.read(2)) for n in range(100)): nth = getnth(n + 1) if channel == 0xff: # End marker. break elif scheme > 4: warning("pattern editor: %s view uses out-of-range scheme %d, will crash IT" % (nth, scheme)) break if scheme > 3: # adjust up for Schism's added 6 column / 12 channel view scheme += 1 if channel > 63: warning("pattern editor: %s track view shows channel %d (weird but harmless)" % (nth, channel + 1)) elif prevchannel + 1 != channel: warning("pattern editor: tracks not in sequential order -- Schism Tracker can't do this") track_view_scheme.append(scheme) prevchannel = channel # might be nice to check each track's width and compare with the width of the box if track_view_visible and not track_view_scheme: warning("pattern editor: track view setup was blank... strange!") track_view_visible = False if normal_view and track_view_visible: # Split view -- channel data is displayed on both left and right of row numbers warning("pattern editor: split track view unimplemented in Schism Tracker") elif not track_view_visible: # Normal (5-channel) view, not affected by alt-h etc. # Handle this by building a 5-channel track view instead (blah) draw_divisions = True track_view_scheme = [] track_view_scheme = "".join(["abcdefghijklmnopqrstuvwxyz"[v] for v in track_view_scheme]) (view_tracking, link_effect_column, pflag1, midi_amplification, midi_c5note, fast_volume_percent, pflag2 ) = struct.unpack("<5BHB", itcfg.read(8)) if view_tracking not in [0, 1]: warning("pattern editor: weird view tracking value %d; Ctrl-T won't work right" % view_tracking) if view_tracking and normal_view > 0: warning("pattern editor: view tracking unimplemented in Schism Tracker") # link/split is not quite as weird -- neither button will appear to be "pressed", # but it still operates fine (acts like 'link') and selecting either mode fixes it if fast_volume_percent > 255: warning("pattern editor: fast volume percent has high byte set, Alt-J will be broken") view_tracking = bool(view_tracking) link_effect_column = bool(link_effect_column) centralise_cursor = bool(pflag1 & 1) highlight_current_row = bool(pflag1 & 2) fast_volume_mode = bool(pflag1 & 4) midi_tick_quantize = bool(pflag1 & 8) midi_base_program_1 = bool(pflag1 & 16) midi_record_note_off = bool(pflag1 & 32) midi_record_velocity = bool(pflag1 & 64) midi_record_aftertouch = bool(pflag1 & 128) show_default_volumes = bool(pflag2 & 1) midi_cut_note_off = bool(pflag2 & 2) # I hate how Schism Tracker saves MIDI settings... midi_flags = 0 if midi_tick_quantize: midi_flags |= 1 if midi_base_program_1: midi_flags |= 2 if midi_record_note_off: midi_flags |= 4 if midi_record_velocity: midi_flags |= 8 if midi_record_aftertouch: midi_flags |= 16 if midi_cut_note_off: midi_flags |= 32 # finally! dump the config print("# Configuration imported from Impulse Tracker on %s" % time.ctime()) if it203_keyboard != 0: print("# Note: keyboard set to %s (IT <=2.03)" % [ "United States", "United Kingdom", "Sweden/Finland", "Spain", "Portugal", "Netherlands", "Italy", "Germany", "France", "unknown" ][it203_keyboard]) print("") print("[Directories]") print("modules=%s" % dir_modules.replace("\\", "\\\\")) print("samples=%s" % dir_samples.replace("\\", "\\\\")) print("instruments=%s" % dir_instruments.replace("\\", "\\\\")) print("sort_with=strcasecmp") print("") print("[General]") print("classic_mode=1") # ;) print("palette_cur=%s" % palette) print("") print("[Pattern Editor]") print("link_effect_column=%d" % link_effect_column) print("draw_divisions=%d" % draw_divisions) print("centralise_cursor=%d" % centralise_cursor) print("highlight_current_row=%d" % highlight_current_row) print("show_default_volumes=%d" % show_default_volumes) print("edit_copy_mask=%d" % edit_copy_mask) print("fast_volume_percent=%d" % fast_volume_percent) print("fast_volume_mode=%d" % fast_volume_mode) print("track_view_scheme=%s" % track_view_scheme) print("highlight_major=%d" % rhmaj) print("highlight_minor=%d" % rhmin) print("") print("[MIDI]") print("flags=%d" % midi_flags) print("amplification=%d" % midi_amplification) print("c5note=%d" % midi_c5note) print("pitch_depth=0") schismtracker-20250313/scripts/itf2half.py000077500000000000000000000005321476471630300203730ustar00rootroot00000000000000#!/usr/bin/env python import sys, struct if sys.version_info[0] > 2: read = sys.stdin.buffer.read write = sys.stdout.buffer.write else: read = sys.stdin.read write = sys.stdout.write write(struct.pack('1024B', *[ (d & 0xf0) | ((d & 0xf000) >> 12) for d in struct.unpack('<1024H', read(2048)) ])) schismtracker-20250313/scripts/itmidicfg.py000077500000000000000000000040251476471630300206340ustar00rootroot00000000000000#!/usr/bin/env python # coding: utf-8 import sys, struct, time, os """ No surprises with this format, it's stored exactly the same way as the embedded MIDI data within IT files, namely as an array of 153 entries of 32 bytes apiece, each terminated with \0, in the same order as presented in Impulse Tracker's MIDI Output Configuration screen: MIDI Start MIDI Stop MIDI Tick Note On Note Off Change Volume Change Pan Bank Select Program Change Macro Setup SF0 -> SFF Z80 -> ZFF Every line should be present, for a total expected file size of (9 + 16 + 128) * 32 = 4896 bytes. """ if len(sys.argv) != 2 or sys.argv[1] in ["--help", "-h"]: print("usage: %s /path/to/itmidi.cfg >> ~/.schism/config" % sys.argv[0]) sys.exit(0) def warning(s): sys.stderr.write("%s: %s\n" % (sys.argv[1], s)) def fatal(s): warning(s) sys.exit(5) itmidi = open(sys.argv[1], "rb") def readvalue(k): try: e = itmidi.read(32) if len(e) != 32: fatal("file is truncated, should be 4896 bytes") e = e.decode("ascii", "replace").split("\0")[0] e.encode("ascii") # test except IOError: fatal("read error") except UnicodeError: warning("garbage data encountered for %s" % k.replace("_", " ")) e = "" return e midiconfig = [(k, readvalue(k)) for k in ["start", "stop", "tick", "note_on", "note_off", "set_volume", "set_panning", "set_bank", "set_program"] + ["SF%X" % n for n in range(16)] + ["Z%02X" % n for n in range(128, 256)]] try: if itmidi.read(1): warning("warning: file has trailing data") except IOError: # Don't care anymore. pass print("# MIDI configuration imported from Impulse Tracker on %s" % time.ctime()) print("") print("[MIDI]") for (k, v) in midiconfig: print("%s=%s" % (k, v)) schismtracker-20250313/scripts/lutgen.c000066400000000000000000000203271476471630300177650ustar00rootroot00000000000000#define compile \ exec gcc -Wall -pedantic -std=gnu99 -lm lutgen.c -o lutgen || exit 255 #include #include #include #include #include /* * cubic spline interpolation doc, * (derived from "digital image warping", g. wolberg) * * interpolation polynomial: f(x) = A3*(x-floor(x))**3 + A2*(x-floor(x))**2 + A1*(x-floor(x)) + A0 * * with Y = equispaced data points (dist=1), YD = first derivates of data points and IP = floor(x) * the A[0..3] can be found by solving * A0 = Y[IP] * A1 = YD[IP] * A2 = 3*(Y[IP+1]-Y[IP])-2.0*YD[IP]-YD[IP+1] * A3 = -2.0 * (Y[IP+1]-Y[IP]) + YD[IP] - YD[IP+1] * * with the first derivates as * YD[IP] = 0.5 * (Y[IP+1] - Y[IP-1]); * YD[IP+1] = 0.5 * (Y[IP+2] - Y[IP]) * * the coefs becomes * A0 = Y[IP] * A1 = YD[IP] * = 0.5 * (Y[IP+1] - Y[IP-1]); * A2 = 3.0 * (Y[IP+1]-Y[IP])-2.0*YD[IP]-YD[IP+1] * = 3.0 * (Y[IP+1] - Y[IP]) - 0.5 * 2.0 * (Y[IP+1] - Y[IP-1]) - 0.5 * (Y[IP+2] - Y[IP]) * = 3.0 * Y[IP+1] - 3.0 * Y[IP] - Y[IP+1] + Y[IP-1] - 0.5 * Y[IP+2] + 0.5 * Y[IP] * = -0.5 * Y[IP+2] + 2.0 * Y[IP+1] - 2.5 * Y[IP] + Y[IP-1] * = Y[IP-1] + 2 * Y[IP+1] - 0.5 * (5.0 * Y[IP] + Y[IP+2]) * A3 = -2.0 * (Y[IP+1]-Y[IP]) + YD[IP] + YD[IP+1] * = -2.0 * Y[IP+1] + 2.0 * Y[IP] + 0.5 * (Y[IP+1] - Y[IP-1]) + 0.5 * (Y[IP+2] - Y[IP]) * = -2.0 * Y[IP+1] + 2.0 * Y[IP] + 0.5 * Y[IP+1] - 0.5 * Y[IP-1] + 0.5 * Y[IP+2] - 0.5 * Y[IP] * = 0.5 * Y[IP+2] - 1.5 * Y[IP+1] + 1.5 * Y[IP] - 0.5 * Y[IP-1] * = 0.5 * (3.0 * (Y[IP] - Y[IP+1]) - Y[IP-1] + YP[IP+2]) * * then interpolated data value is (horner rule) * out = (((A3*x)+A2)*x+A1)*x+A0 * * this gives parts of data points Y[IP-1] to Y[IP+2] of * part x**3 x**2 x**1 x**0 * Y[IP-1] -0.5 1 -0.5 0 * Y[IP] 1.5 -2.5 0 1 * Y[IP+1] -1.5 2 0.5 0 * Y[IP+2] 0.5 -0.5 0 0 */ // number of bits used to scale spline coefs #define SPLINE_QUANTBITS 14 #define SPLINE_QUANTSCALE (1L << SPLINE_QUANTBITS) #define SPLINE_8SHIFT (SPLINE_QUANTBITS - 8) #define SPLINE_16SHIFT (SPLINE_QUANTBITS) // forces coefsset to unity gain #define SPLINE_CLAMPFORUNITY // log2(number) of precalculated splines (range is [4..14]) #define SPLINE_FRACBITS 10 #define SPLINE_LUTLEN (1L << SPLINE_FRACBITS) int16_t cubic_spline_lut[4 * SPLINE_LUTLEN]; void cubic_spline_init(void) { int i; int len = SPLINE_LUTLEN; float flen = 1.0f / (float) SPLINE_LUTLEN; float scale = (float) SPLINE_QUANTSCALE; for (i = 0; i < len; i++) { float LCm1, LC0, LC1, LC2; float LX = ((float) i) * flen; int indx = i << 2; #ifdef SPLINE_CLAMPFORUNITY int sum; #endif LCm1 = (float) floor(0.5 + scale * (-0.5 * LX * LX * LX + 1.0 * LX * LX - 0.5 * LX )); LC0 = (float) floor(0.5 + scale * ( 1.5 * LX * LX * LX - 2.5 * LX * LX + 1.0)); LC1 = (float) floor(0.5 + scale * (-1.5 * LX * LX * LX + 2.0 * LX * LX + 0.5 * LX )); LC2 = (float) floor(0.5 + scale * ( 0.5 * LX * LX * LX - 0.5 * LX * LX )); cubic_spline_lut[indx + 0] = (int16_t) ((LCm1 < -scale) ? -scale : ((LCm1 > scale) ? scale : LCm1)); cubic_spline_lut[indx + 1] = (int16_t) ((LC0 < -scale) ? -scale : ((LC0 > scale) ? scale : LC0 )); cubic_spline_lut[indx + 2] = (int16_t) ((LC1 < -scale) ? -scale : ((LC1 > scale) ? scale : LC1 )); cubic_spline_lut[indx + 3] = (int16_t) ((LC2 < -scale) ? -scale : ((LC2 > scale) ? scale : LC2 )); #ifdef SPLINE_CLAMPFORUNITY sum = cubic_spline_lut[indx + 0] + cubic_spline_lut[indx + 1] + cubic_spline_lut[indx + 2] + cubic_spline_lut[indx + 3]; if (sum != SPLINE_QUANTSCALE) { int max = indx; if (cubic_spline_lut[indx + 1] > cubic_spline_lut[max]) max = indx + 1; if (cubic_spline_lut[indx + 2] > cubic_spline_lut[max]) max = indx + 2; if (cubic_spline_lut[indx + 3] > cubic_spline_lut[max]) max = indx + 3; cubic_spline_lut[max] += (SPLINE_QUANTSCALE - sum); } #endif } } /* fir interpolation doc, * (derived from "an engineer's guide to fir digital filters", n.j. loy) * * calculate coefficients for ideal lowpass filter (with cutoff = fc in 0..1 (mapped to 0..nyquist)) * c[-N..N] = (i==0) ? fc : sin(fc*pi*i)/(pi*i) * * then apply selected window to coefficients * c[-N..N] *= w(0..N) * with n in 2*N and w(n) being a window function (see loy) * * then calculate gain and scale filter coefs to have unity gain. */ // quantizer scale of window coefs #define WFIR_QUANTBITS 15 #define WFIR_QUANTSCALE (1L << WFIR_QUANTBITS) #define WFIR_8SHIFT (WFIR_QUANTBITS - 8) #define WFIR_16BITSHIFT (WFIR_QUANTBITS) // log2(number)-1 of precalculated taps range is [4..12] #define WFIR_FRACBITS 10 #define WFIR_LUTLEN ((1L << (WFIR_FRACBITS + 1)) + 1) // number of samples in window #define WFIR_LOG2WIDTH 3 #define WFIR_WIDTH (1L << WFIR_LOG2WIDTH) #define WFIR_SMPSPERWING ((WFIR_WIDTH - 1) >> 1) // cutoff (1.0 == pi/2) #define WFIR_CUTOFF 0.90f // wfir type #define WFIR_HANN 0 #define WFIR_HAMMING 1 #define WFIR_BLACKMANEXACT 2 #define WFIR_BLACKMAN3T61 3 #define WFIR_BLACKMAN3T67 4 #define WFIR_BLACKMAN4T92 5 #define WFIR_BLACKMAN4T74 6 #define WFIR_KAISER4T 7 #define WFIR_TYPE WFIR_BLACKMANEXACT // wfir help #ifndef M_zPI #define M_zPI 3.1415926535897932384626433832795 #endif #define M_zEPS 1e-8 #define M_zBESSELEPS 1e-21 float coef(int pc_nr, float p_ofs, float p_cut, int p_width, int p_type) { double width_m1 = p_width - 1; double width_m1_half = 0.5 * width_m1; double pos_u = (double) pc_nr - p_ofs; double pos = pos_u - width_m1_half; double idl = 2.0 * M_zPI / width_m1; double wc, si; if (fabs(pos) < M_zEPS) { wc = 1.0; si = p_cut; return p_cut; } switch (p_type) { case WFIR_HANN: wc = 0.50 - 0.50 * cos(idl * pos_u); break; case WFIR_HAMMING: wc = 0.54 - 0.46 * cos(idl * pos_u); break; case WFIR_BLACKMANEXACT: wc = 0.42 - 0.50 * cos(idl * pos_u) + 0.08 * cos(2.0 * idl * pos_u); break; case WFIR_BLACKMAN3T61: wc = 0.44959 - 0.49364 * cos(idl * pos_u) + 0.05677 * cos(2.0 * idl * pos_u); break; case WFIR_BLACKMAN3T67: wc = 0.42323 - 0.49755 * cos(idl * pos_u) + 0.07922 * cos(2.0 * idl * pos_u); break; case WFIR_BLACKMAN4T92: wc = 0.35875 - 0.48829 * cos(idl * pos_u) + 0.14128 * cos(2.0 * idl * pos_u) - 0.01168 * cos(3.0 * idl * pos_u); break; case WFIR_BLACKMAN4T74: wc = 0.40217 - 0.49703 * cos(idl * pos_u) + 0.09392 * cos(2.0 * idl * pos_u) - 0.00183 * cos(3.0*idl*pos_u); break; case WFIR_KAISER4T: wc = 0.40243 - 0.49804 * cos(idl * pos_u) + 0.09831 * cos(2.0 * idl * pos_u) - 0.00122 * cos(3.0 * idl * pos_u); break; default: wc = 1.0; break; } pos *= M_zPI; si = sin(p_cut * pos) / pos; return (float)(wc * si); } int16_t windowed_fir_lut[WFIR_LUTLEN*WFIR_WIDTH]; void windowed_fir_init(void) { int pcl; // number of precalculated lines for 0..1 (-1..0) float pcllen = (float)(1L << WFIR_FRACBITS); float norm = 1.0f / (float)(2.0f * pcllen); float cut = WFIR_CUTOFF; float scale = (float) WFIR_QUANTSCALE; for (pcl = 0; pcl < WFIR_LUTLEN; pcl++) { float gain,coefs[WFIR_WIDTH]; float ofs = ((float) pcl - pcllen) * norm; int cc, indx = pcl << WFIR_LOG2WIDTH; for (cc = 0, gain = 0.0f; cc < WFIR_WIDTH; cc++) { coefs[cc] = coef(cc, ofs, cut, WFIR_WIDTH, WFIR_TYPE); gain += coefs[cc]; } gain = 1.0f / gain; for (cc = 0; cc < WFIR_WIDTH; cc++) { float coef = (float)floor( 0.5 + scale * coefs[cc] * gain); windowed_fir_lut[indx + cc] = (int16_t)((coef < -scale) ? - scale : ((coef > scale) ? scale : coef)); } } } #define LOOP(x, y) \ printf("static int16_t %s[%" PRId32 "] = {\n", #x, (int32_t)y); \ \ for (int i = 0; i < y; i++) { \ if (i && !(i % 64)) \ printf("\n"); \ printf(" %d,", x[i]); \ } \ \ printf("\n};\n\n"); int main(int argc, char **argv) { cubic_spline_init(); windowed_fir_init(); LOOP(cubic_spline_lut, (4 * SPLINE_LUTLEN)); LOOP(windowed_fir_lut, (WFIR_LUTLEN * WFIR_WIDTH)); return 0; } schismtracker-20250313/scripts/palette.py000077500000000000000000000010431476471630300203300ustar00rootroot00000000000000#!/usr/bin/env python # This converts a palette string from the packed ASCII format used # in the runtime config into a C structure suitable for inclusion # in palettes.h. import sys table = '.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' if len(sys.argv) != 2: print("usage: %s " % sys.argv[0]) sys.exit(1) for n in range(16): print("/* %2d */ {%s}," % (n, ', '.join([ "%2d" % table.index(c) for c in sys.argv[1][3 * n : 3 * n + 3] ]))) schismtracker-20250313/scripts/timestamp.py000077500000000000000000000063061476471630300207040ustar00rootroot00000000000000#!/usr/bin/env python import struct, sys """ Each timestamp field is an eight-byte value, first storing the time the file was loaded into the editor in DOS date/time format (see http://msdn.microsoft.com/en-us/library/ms724247(VS.85).aspx for details of this format), and then the length of time the file was loaded in the editor, in units of 1/18.2 second. (apparently this is fairly standard in DOS apps - I don't know) A new timestamp is written whenever the file is reloaded, but it is overwritten if the file is re-saved multiple times in the same editing session. Thanks to Saga Musix for finally figuring this crazy format out. """ for filename in sys.argv[1:]: f = open(filename, 'rb') if f.read(4) != 'IMPM'.encode('ascii'): print("%s: not an IT file" % filename) continue f.seek(0x20) ordnum, insnum, smpnum, patnum = struct.unpack('<4H', f.read(8)) f.seek(0x2e) special, = struct.unpack('> 5) & 15 year = (fatdate >> 9) + 1980 second = (fattime & 31) * 2 minute = (fattime >> 5) & 63 hour = fattime >> 11 print('\t%04d-%02d-%02d %02d:%02d:%02d %s' % (year, month, day, hour, minute, second, ticks2hms(ticks))) totalticks += ticks print("\t%13d ticks = %s" % (totalticks, ticks2hms(totalticks))) schismtracker-20250313/sys/000077500000000000000000000000001476471630300154465ustar00rootroot00000000000000schismtracker-20250313/sys/alsa/000077500000000000000000000000001476471630300163665ustar00rootroot00000000000000schismtracker-20250313/sys/alsa/midi-alsa.c000066400000000000000000000366131476471630300204030ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "midi.h" #include "util.h" #include "mem.h" #ifdef USE_ALSA #include #include #include #define PORT_NAME "Schism Tracker" static snd_seq_t *seq; static int local_port = -1; #define MIDI_BUFSIZE 65536 static unsigned char big_midi_buf[MIDI_BUFSIZE]; static int alsa_queue; struct alsa_midi { int c, p; const char *client; const char *port; snd_midi_event_t *dev; int mark; }; static size_t (*ALSA_snd_seq_port_info_sizeof)(void); static size_t (*ALSA_snd_seq_client_info_sizeof)(void); static size_t (*ALSA_snd_seq_queue_tempo_sizeof)(void); static int (*ALSA_snd_seq_control_queue)(snd_seq_t*s,int q,int type, int value, snd_seq_event_t *ev); static void (*ALSA_snd_seq_queue_tempo_set_tempo)(snd_seq_queue_tempo_t *info, unsigned int tempo); static void (*ALSA_snd_seq_queue_tempo_set_ppq)(snd_seq_queue_tempo_t *info, int ppq); static int (*ALSA_snd_seq_set_queue_tempo)(snd_seq_t *handle, int q, snd_seq_queue_tempo_t *tempo); static long (*ALSA_snd_midi_event_encode)(snd_midi_event_t *dev,const unsigned char *buf,long count,snd_seq_event_t *ev); static int (*ALSA_snd_seq_event_output)(snd_seq_t *handle, snd_seq_event_t *ev); static int (*ALSA_snd_seq_alloc_queue)(snd_seq_t*h); static int (*ALSA_snd_seq_free_event)(snd_seq_event_t *ev); static int (*ALSA_snd_seq_connect_from)(snd_seq_t*seeq,int my_port,int src_client, int src_port); static int (*ALSA_snd_seq_connect_to)(snd_seq_t*seeq,int my_port,int dest_client,int dest_port); static int (*ALSA_snd_seq_disconnect_from)(snd_seq_t*seeq,int my_port,int src_client, int src_port); static int (*ALSA_snd_seq_disconnect_to)(snd_seq_t*seeq,int my_port,int dest_client,int dest_port); static const char * (*ALSA_snd_strerror)(int errnum); static int (*ALSA_snd_seq_poll_descriptors_count)(snd_seq_t*h,short e); static int (*ALSA_snd_seq_poll_descriptors)(snd_seq_t*h,struct pollfd*pfds,unsigned int space, short e); static int (*ALSA_snd_seq_event_input)(snd_seq_t*h,snd_seq_event_t**ev); static int (*ALSA_snd_seq_event_input_pending)(snd_seq_t*h,int fs); static int (*ALSA_snd_midi_event_new)(size_t s,snd_midi_event_t **rd); static long (*ALSA_snd_midi_event_decode)(snd_midi_event_t *dev,unsigned char *buf,long count, const snd_seq_event_t*ev); static void (*ALSA_snd_midi_event_reset_decode)(snd_midi_event_t*d); static int (*ALSA_snd_seq_create_simple_port)(snd_seq_t*h,const char *name,unsigned int caps,unsigned int type); static int (*ALSA_snd_seq_drain_output)(snd_seq_t*h); static int (*ALSA_snd_seq_query_next_client)(snd_seq_t*h,snd_seq_client_info_t*info); static int (*ALSA_snd_seq_client_info_get_client)(const snd_seq_client_info_t *info); static void (*ALSA_snd_seq_client_info_set_client)(snd_seq_client_info_t*inf,int cl); static void (*ALSA_snd_seq_port_info_set_client)(snd_seq_port_info_t*inf,int cl); static void (*ALSA_snd_seq_port_info_set_port)(snd_seq_port_info_t*inf,int pl); static int (*ALSA_snd_seq_query_next_port)(snd_seq_t*h,snd_seq_port_info_t*inf); static unsigned int (*ALSA_snd_seq_port_info_get_capability)(const snd_seq_port_info_t *inf); static int (*ALSA_snd_seq_port_info_get_client)(const snd_seq_port_info_t*inf); static int (*ALSA_snd_seq_port_info_get_port)(const snd_seq_port_info_t*inf); static const char * (*ALSA_snd_seq_client_info_get_name)(snd_seq_client_info_t*inf); static const char * (*ALSA_snd_seq_port_info_get_name)(const snd_seq_port_info_t*inf); static int (*ALSA_snd_seq_open)(snd_seq_t**h,const char *name,int str, int mode); static int (*ALSA_snd_seq_set_client_name)(snd_seq_t*seeq,const char *name); /* these are inline functions; prefix them anyway */ #define ALSA_snd_seq_ev_clear snd_seq_ev_clear #define ALSA_snd_seq_ev_set_source snd_seq_ev_set_source #define ALSA_snd_seq_ev_set_subs snd_seq_ev_set_subs #define ALSA_snd_seq_ev_set_direct snd_seq_ev_set_direct #define ALSA_snd_seq_ev_schedule_tick snd_seq_ev_schedule_tick #define ALSA_snd_seq_client_info_alloca snd_seq_client_info_alloca #define ALSA_snd_seq_port_info_alloca snd_seq_port_info_alloca #define ALSA_snd_seq_queue_tempo_alloca snd_seq_queue_tempo_alloca #define ALSA_snd_seq_start_queue snd_seq_start_queue static int load_alsa_syms(void); #ifdef ALSA_DYNAMIC_LOAD #include "loadso.h" /* said inline functions call these... */ #define snd_seq_client_info_sizeof ALSA_snd_seq_client_info_sizeof #define snd_seq_port_info_sizeof ALSA_snd_seq_port_info_sizeof #define snd_seq_queue_tempo_sizeof ALSA_snd_seq_queue_tempo_sizeof #define snd_seq_control_queue ALSA_snd_seq_control_queue void *alsa_dltrick_handle_; static void alsa_dlend(void) { if (alsa_dltrick_handle_) { loadso_object_unload(alsa_dltrick_handle_); alsa_dltrick_handle_ = NULL; } } static int alsa_dlinit(void) { if (alsa_dltrick_handle_) return 0; // libasound.so.2 alsa_dltrick_handle_ = library_load("asound", 2, 0); if (!alsa_dltrick_handle_) return -1; int retval = load_alsa_syms(); if (retval < 0) alsa_dlend(); return retval; } SCHISM_STATIC_ASSERT(sizeof(void (*)) == sizeof(void *), "dynamic loading code assumes function pointer and void pointer are of equivalent size"); static int load_alsa_sym(const char *fn, void *addr) { void *func = loadso_function_load(alsa_dltrick_handle_, fn); if (!func) return 0; memcpy(addr, &func, sizeof(void *)); return 1; } #define SCHISM_ALSA_SYM(x) \ if (!load_alsa_sym(#x, &ALSA_##x)) return -1 #else #define SCHISM_ALSA_SYM(x) ALSA_##x = x static int alsa_dlinit(void) { return load_alsa_syms(); } #endif static int load_alsa_syms(void) { SCHISM_ALSA_SYM(snd_seq_port_info_sizeof); SCHISM_ALSA_SYM(snd_seq_client_info_sizeof); SCHISM_ALSA_SYM(snd_seq_control_queue); SCHISM_ALSA_SYM(snd_seq_queue_tempo_sizeof); SCHISM_ALSA_SYM(snd_seq_queue_tempo_set_tempo); SCHISM_ALSA_SYM(snd_seq_queue_tempo_set_ppq); SCHISM_ALSA_SYM(snd_seq_set_queue_tempo); SCHISM_ALSA_SYM(snd_midi_event_encode); SCHISM_ALSA_SYM(snd_seq_event_output); SCHISM_ALSA_SYM(snd_seq_alloc_queue); SCHISM_ALSA_SYM(snd_seq_free_event); SCHISM_ALSA_SYM(snd_seq_connect_from); SCHISM_ALSA_SYM(snd_seq_connect_to); SCHISM_ALSA_SYM(snd_seq_disconnect_from); SCHISM_ALSA_SYM(snd_seq_disconnect_to); SCHISM_ALSA_SYM(snd_strerror); SCHISM_ALSA_SYM(snd_seq_poll_descriptors_count); SCHISM_ALSA_SYM(snd_seq_poll_descriptors); SCHISM_ALSA_SYM(snd_seq_event_input); SCHISM_ALSA_SYM(snd_seq_event_input_pending); SCHISM_ALSA_SYM(snd_midi_event_new); SCHISM_ALSA_SYM(snd_midi_event_decode); SCHISM_ALSA_SYM(snd_midi_event_reset_decode); SCHISM_ALSA_SYM(snd_seq_create_simple_port); SCHISM_ALSA_SYM(snd_seq_drain_output); SCHISM_ALSA_SYM(snd_seq_query_next_client); SCHISM_ALSA_SYM(snd_seq_client_info_get_client); SCHISM_ALSA_SYM(snd_seq_client_info_set_client); SCHISM_ALSA_SYM(snd_seq_port_info_set_client); SCHISM_ALSA_SYM(snd_seq_port_info_set_port); SCHISM_ALSA_SYM(snd_seq_query_next_port); SCHISM_ALSA_SYM(snd_seq_port_info_get_capability); SCHISM_ALSA_SYM(snd_seq_port_info_get_client); SCHISM_ALSA_SYM(snd_seq_port_info_get_port); SCHISM_ALSA_SYM(snd_seq_client_info_get_name); SCHISM_ALSA_SYM(snd_seq_port_info_get_name); SCHISM_ALSA_SYM(snd_seq_open); SCHISM_ALSA_SYM(snd_seq_set_client_name); return 0; } /* see mixer-alsa.c */ #undef assert #define assert(x) static void _alsa_drain(struct midi_port *p SCHISM_UNUSED) { /* not port specific */ ALSA_snd_seq_drain_output(seq); } static void _alsa_send(struct midi_port *p, const unsigned char *data, uint32_t len, uint32_t delay) { struct alsa_midi *ex; snd_seq_event_t ev; long rr; ex = (struct alsa_midi *)p->userdata; while (len > 0) { ALSA_snd_seq_ev_clear(&ev); ALSA_snd_seq_ev_set_source(&ev, local_port); ALSA_snd_seq_ev_set_subs(&ev); if (!delay) { ALSA_snd_seq_ev_set_direct(&ev); } else { ALSA_snd_seq_ev_schedule_tick(&ev, alsa_queue, 1, delay); } /* we handle our own */ ev.dest.port = ex->p; ev.dest.client = ex->c; rr = ALSA_snd_midi_event_encode(ex->dev, data, len, &ev); if (rr < 1) break; ALSA_snd_seq_event_output(seq, &ev); ALSA_snd_seq_free_event(&ev); data += rr; len -= rr; } } static int _alsa_start(struct midi_port *p) { struct alsa_midi *data; int err; err = 0; data = (struct alsa_midi *)p->userdata; if (p->io & MIDI_INPUT) err = ALSA_snd_seq_connect_from(seq, 0, data->c, data->p); if (p->io & MIDI_OUTPUT) err = ALSA_snd_seq_connect_to(seq, 0, data->c, data->p); if (err < 0) { log_appendf(4, "ALSA: %s", ALSA_snd_strerror(err)); return 0; } return 1; } static int _alsa_stop(struct midi_port *p) { struct alsa_midi *data; int err; err = 0; data = (struct alsa_midi *)p->userdata; if (p->io & MIDI_OUTPUT) err = ALSA_snd_seq_disconnect_to(seq, 0, data->c, data->p); if (p->io & MIDI_INPUT) err = ALSA_snd_seq_disconnect_from(seq, 0, data->c, data->p); if (err < 0) { log_appendf(4, "ALSA: %s", ALSA_snd_strerror(err)); return 0; } return 1; } static int _alsa_thread(struct midi_provider *p) { int npfd; struct pollfd *pfd; struct midi_port *ptr, *src; struct alsa_midi *data; static snd_midi_event_t *dev = NULL; snd_seq_event_t *ev; long s; npfd = ALSA_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; while (!p->cancelled) { if (ALSA_snd_seq_poll_descriptors(seq, pfd, npfd, POLLIN) != npfd) { free(pfd); return 0; } // wait 10 msec for a midi event if (!poll(pfd, npfd, 10)) continue; do { if (ALSA_snd_seq_event_input(seq, &ev) < 0) break; if (!ev) continue; ptr = src = NULL; while (midi_port_foreach(p, &ptr)) { data = (struct alsa_midi *)ptr->userdata; if (ev->source.client == data->c && ev->source.port == data->p && (ptr->io & MIDI_INPUT)) { src = ptr; } } if (!src || !ev) { ALSA_snd_seq_free_event(ev); continue; } if (!dev && ALSA_snd_midi_event_new(sizeof(big_midi_buf), &dev) < 0) { /* err... */ break; } s = ALSA_snd_midi_event_decode(dev, big_midi_buf, sizeof(big_midi_buf), ev); if (s > 0) midi_received_cb(src, big_midi_buf, s); ALSA_snd_midi_event_reset_decode(dev); ALSA_snd_seq_free_event(ev); } while (ALSA_snd_seq_event_input_pending(seq, 0) > 0); } 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; ptr = NULL; while (midi_port_foreach(_alsa_provider, &ptr)) { data = (struct alsa_midi *)ptr->userdata; data->mark = 0; } /* believe it or not this is the RIGHT way to do this */ ALSA_snd_seq_client_info_alloca(&cinfo); ALSA_snd_seq_port_info_alloca(&pinfo); ALSA_snd_seq_client_info_set_client(cinfo, -1); while (ALSA_snd_seq_query_next_client(seq, cinfo) >= 0) { int cn = ALSA_snd_seq_client_info_get_client(cinfo); ALSA_snd_seq_port_info_set_client(pinfo, cn); ALSA_snd_seq_port_info_set_port(pinfo, -1); while (ALSA_snd_seq_query_next_port(seq, pinfo) >= 0) { io = 0; if ((ALSA_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 ((ALSA_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; /* Now get clients and ports */ c = ALSA_snd_seq_port_info_get_client(pinfo); if (c == SND_SEQ_CLIENT_SYSTEM) continue; p = ALSA_snd_seq_port_info_get_port(pinfo); ptr = NULL; ok = 0; while (midi_port_foreach(_alsa_provider, &ptr)) { data = (struct alsa_midi *)ptr->userdata; if (data->c == c && data->p == p) { data->mark = 1; ok = 1; } } if (ok) continue; ctext = ALSA_snd_seq_client_info_get_name(cinfo); ptext = ALSA_snd_seq_port_info_get_name(pinfo); if (strcmp(ctext, PORT_NAME) == 0 && strcmp(ptext, PORT_NAME) == 0) { continue; } data = mem_alloc(sizeof(struct alsa_midi)); data->c = c; data->p = p; data->client = ctext; data->mark = 1; data->port = ptext; buffer = NULL; if (ALSA_snd_midi_event_new(MIDI_BUFSIZE, &data->dev) < 0) { /* err... */ free(data); continue; } if (asprintf(&buffer, "%3d:%-3d %-20.20s %s", c, p, ctext, ptext) == -1) { free(data); continue; } midi_port_register(_alsa_provider, io, buffer, data, 1); free(buffer); } } /* remove "disappeared" midi ports */ ptr = NULL; while (midi_port_foreach(_alsa_provider, &ptr)) { data = (struct alsa_midi *)ptr->userdata; if (data->mark) continue; midi_port_unregister(ptr->num); } } int alsa_midi_setup(void) { static const struct midi_driver alsa_driver = { .poll = _alsa_poll, .thread = _alsa_thread, .enable = _alsa_start, .disable = _alsa_stop, .send = _alsa_send, .flags = MIDI_PORT_CAN_SCHEDULE, .drain = _alsa_drain, }; snd_seq_queue_tempo_t *tempo; /* only bother if alsa midi actually exists, otherwise this will * produce useless and annoying error messages on systems where alsa * libs are installed but which aren't actually running it */ struct stat sbuf; if (stat("/dev/snd/seq", &sbuf) != 0) return 0; #ifdef ALSA_DYNAMIC_LOAD if (!alsa_dltrick_handle_) #endif if (alsa_dlinit()) return 0; if (ALSA_snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0 || ALSA_snd_seq_set_client_name(seq, PORT_NAME) < 0) return 0; alsa_queue = ALSA_snd_seq_alloc_queue(seq); ALSA_snd_seq_queue_tempo_alloca(&tempo); ALSA_snd_seq_queue_tempo_set_tempo(tempo, 480000); ALSA_snd_seq_queue_tempo_set_ppq(tempo, 480); ALSA_snd_seq_set_queue_tempo(seq, alsa_queue, tempo); ALSA_snd_seq_start_queue(seq, alsa_queue, NULL); ALSA_snd_seq_drain_output(seq); local_port = ALSA_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 ); if (local_port < 0) { log_appendf(4, "ALSA: %s", ALSA_snd_strerror(local_port)); return 0; } if (!midi_provider_register("ALSA", &alsa_driver)) return 0; return 1; } #endif schismtracker-20250313/sys/fd.org/000077500000000000000000000000001476471630300166255ustar00rootroot00000000000000schismtracker-20250313/sys/fd.org/autopackage.apspec000066400000000000000000000031501476471630300223050ustar00rootroot00000000000000# -*-shell-script-*- [Meta] RootName: @schismtracker.org:1.1 DisplayName: Schism Tracker ShortName: schismtracker Maintainer: Mrs. Brisby Packager: Mrs. Brisby Summary: Schism Tracker is a music editor that matches the look and feel of Impulse Tracker as closely as possible. URL: http://schismtracker.org/ License: GNU General Public License, Version 2 SoftwareVersion: 1.1 AutopackageTarget: 1.0 [Description] Schism Tracker is a music editor in the spirit of Impulse Tracker. Nearly every feature of Impulse Tracker is available in exactly the same manner. Improvements have been extremely careful to avoid disturbing any muscle memory that the user might have developed with Impulse Tracker. [BuildPrepare] mkdir -p linux-x86-build && cd linux-x86-build && prepareBuild --src .. $EXTRA_ARGS [BuildUnprepare] unprepareBuild [Imports] import </dev/null 2>&1 installDesktop "AudioVideo" schism.desktop installDesktop "AudioVideo" itf.desktop installData NEWS installData README installData COPYING installData ChangeLog [Uninstall] # Usually just the following line is enough to uninstall everything uninstallFromLog schismtracker-20250313/sys/fd.org/schism.desktop000066400000000000000000000013201476471630300215020ustar00rootroot00000000000000[Desktop Entry] Actions=Play;FontEditor;RenderWAV Version=1.0 Name=Schism Tracker Comment=Impulse Tracker clone Comment[fr]=Clone d'Impulse Tracker Terminal=false TryExec=schismtracker Exec=schismtracker %f Type=Application Icon=schism-icon-128 Categories=AudioVideo;Audio;Midi;Sequencer;Player;Music; MimeType=audio/x-it;audio/x-s3m;audio/x-xm;audio/x-mod; Keywords=impulse;module;midi;music; [Desktop Action Play] Name=Schism Tracker (play song) Exec=schismtracker -p %f [Desktop Action FontEditor] Name=Schism Tracker (font editor) Exec=schismtracker --font-editor [Desktop Action RenderWAV] Name=Schism Tracker (render song) Exec=env SCHISM_FILE=%f schismtracker --diskwrite="\\$SCHISM_FILE.wav" "\\$SCHISM_FILE" schismtracker-20250313/sys/jack/000077500000000000000000000000001476471630300163565ustar00rootroot00000000000000schismtracker-20250313/sys/jack/midi-jack.c000066400000000000000000000330551476471630300203600ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "midi.h" #include "util.h" #include "timer.h" #include "mem.h" #include #include #include #include #define PORT_NAME "Schism Tracker" /* shamelessly ripped off SDL_jackaudio.c here */ static jack_client_t *(*JACK_jack_client_open)(const char *, jack_options_t, jack_status_t *, ...); static int (*JACK_jack_client_close)(jack_client_t *); static int (*JACK_jack_activate)(jack_client_t *); static void *(*JACK_jack_port_get_buffer)(jack_port_t *, jack_nframes_t); static int (*JACK_jack_port_unregister)(jack_client_t *, jack_port_t *); static jack_port_t *(*JACK_jack_port_register)(jack_client_t *, const char *, const char *, unsigned long, unsigned long); static int (*JACK_jack_connect)(jack_client_t *, const char *, const char *); static int (*JACK_jack_set_process_callback)(jack_client_t *, JackProcessCallback, void *); static jack_nframes_t (*JACK_jack_midi_get_event_count)(void *); static int (*JACK_jack_midi_event_get)(jack_midi_event_t *, void *, uint32_t); static jack_port_t* (*JACK_jack_port_by_name)(jack_client_t *, const char*); static jack_ringbuffer_t* (*JACK_jack_ringbuffer_create)(size_t); static size_t (*JACK_jack_ringbuffer_write_space)(const jack_ringbuffer_t *); static size_t (*JACK_jack_ringbuffer_write)(jack_ringbuffer_t *, const char *, size_t); static size_t (*JACK_jack_ringbuffer_peek)(jack_ringbuffer_t *, char *, size_t); static size_t (*JACK_jack_ringbuffer_read_space)(const jack_ringbuffer_t *); static void (*JACK_jack_ringbuffer_read_advance)(jack_ringbuffer_t *, size_t); static size_t (*JACK_jack_ringbuffer_read)(jack_ringbuffer_t *, char *, size_t); static jack_midi_data_t * (*JACK_jack_midi_event_reserve)(void *, jack_nframes_t, size_t); static void (*JACK_jack_ringbuffer_free)(jack_ringbuffer_t *); static const char * (*JACK_jack_port_name)(const jack_port_t *); static int (*JACK_jack_connect)(jack_client_t *, const char *, const char *); static int (*JACK_jack_disconnect)(jack_client_t *, const char *, const char *); static const char ** (*JACK_jack_get_ports)(jack_client_t *, const char *, const char *, unsigned long); static int (*JACK_jack_port_is_mine)(const jack_client_t *, const jack_port_t *); static void (*JACK_jack_midi_clear_buffer)(void *); static jack_time_t (*JACK_jack_frames_to_time)(const jack_client_t *client, jack_nframes_t); static jack_nframes_t (*JACK_jack_time_to_frames)(const jack_client_t *, jack_time_t); static jack_time_t (*JACK_jack_get_time)(void); static void (*JACK_jack_free)(void *ptr); static int load_jack_syms(void); #ifdef JACK_DYNAMIC_LOAD #include "loadso.h" void *jack_dltrick_handle_ = NULL; static void jack_dlend(void) { if (jack_dltrick_handle_) { loadso_object_unload(jack_dltrick_handle_); jack_dltrick_handle_ = NULL; } } static int jack_dlinit(void) { if (jack_dltrick_handle_) return 0; // libjack.so.0 jack_dltrick_handle_ = library_load("jack", 0, 0); if (!jack_dltrick_handle_) return -1; int retval = load_jack_syms(); if (retval < 0) jack_dlend(); return retval; } // FIXME this is repeated in many places SCHISM_STATIC_ASSERT(sizeof(void (*)) == sizeof(void *), "dynamic loading code assumes function pointer and void pointer are of equivalent size"); static int load_jack_sym(const char *fn, void *addr) { void *func = loadso_function_load(jack_dltrick_handle_, fn); if (!func) return 0; memcpy(addr, &func, sizeof(void *)); return 1; } #define SCHISM_JACK_SYM(x) \ if (!load_jack_sym(#x, &JACK_##x)) return -1 #else #define SCHISM_JACK_SYM(x) JACK_##x = x static int jack_dlinit(void) { load_jack_syms(); return 0; } #endif static int load_jack_syms(void) { SCHISM_JACK_SYM(jack_client_open); SCHISM_JACK_SYM(jack_client_close); SCHISM_JACK_SYM(jack_activate); SCHISM_JACK_SYM(jack_port_get_buffer); SCHISM_JACK_SYM(jack_port_register); SCHISM_JACK_SYM(jack_connect); SCHISM_JACK_SYM(jack_set_process_callback); SCHISM_JACK_SYM(jack_midi_event_get); SCHISM_JACK_SYM(jack_midi_get_event_count); SCHISM_JACK_SYM(jack_port_by_name); SCHISM_JACK_SYM(jack_ringbuffer_create); SCHISM_JACK_SYM(jack_ringbuffer_free); SCHISM_JACK_SYM(jack_ringbuffer_write_space); SCHISM_JACK_SYM(jack_ringbuffer_write); SCHISM_JACK_SYM(jack_ringbuffer_peek); SCHISM_JACK_SYM(jack_ringbuffer_read_space); SCHISM_JACK_SYM(jack_ringbuffer_read_advance); SCHISM_JACK_SYM(jack_ringbuffer_read); SCHISM_JACK_SYM(jack_midi_event_reserve); SCHISM_JACK_SYM(jack_port_name); SCHISM_JACK_SYM(jack_connect); SCHISM_JACK_SYM(jack_disconnect); SCHISM_JACK_SYM(jack_get_ports); SCHISM_JACK_SYM(jack_port_is_mine); SCHISM_JACK_SYM(jack_midi_clear_buffer); SCHISM_JACK_SYM(jack_get_time); SCHISM_JACK_SYM(jack_time_to_frames); SCHISM_JACK_SYM(jack_frames_to_time); SCHISM_JACK_SYM(jack_free); return 0; } /* ------------------------------------------------------ */ /* this is based off of code from RtMidi */ #define JACK_RINGBUFFER_SIZE 16384 static jack_client_t *client = NULL; static jack_port_t *midi_in_port = NULL; static jack_port_t *midi_out_port = NULL; static jack_ringbuffer_t *ringbuffer_in = NULL; static jack_ringbuffer_t *ringbuffer_out = NULL; static size_t ringbuffer_in_max_write = 0; static size_t ringbuffer_out_max_write = 0; struct jack_midi { jack_port_t* port; int mark; }; static void _jack_send(SCHISM_UNUSED struct midi_port *p, const unsigned char *data, uint32_t len, uint32_t delay) { while (len > 0) { uint32_t real_len = midi_event_length(*data); if (real_len > len) return; if (real_len + sizeof(real_len) > ringbuffer_out_max_write) return; // yield for other threads while the process thread is busy while (JACK_jack_ringbuffer_write_space(ringbuffer_out) < real_len + sizeof(real_len)) sched_yield(); JACK_jack_ringbuffer_write(ringbuffer_out, (const char*)&real_len, sizeof(real_len)); JACK_jack_ringbuffer_write(ringbuffer_out, (const char*)data, real_len); data += real_len; len -= real_len; } (void)delay; } static int _jack_start(struct midi_port *p) { struct jack_midi* m = (struct jack_midi *)p->userdata; if (p->io & MIDI_INPUT) return !JACK_jack_connect(client, JACK_jack_port_name(m->port), JACK_jack_port_name(midi_in_port)); else if (p->io & MIDI_OUTPUT) return !JACK_jack_connect(client, JACK_jack_port_name(midi_out_port), JACK_jack_port_name(m->port)); return 1; } static int _jack_stop(struct midi_port *p) { struct jack_midi* m = (struct jack_midi *)p->userdata; if (p->io & MIDI_INPUT) return !JACK_jack_disconnect(client, JACK_jack_port_name(m->port), JACK_jack_port_name(midi_in_port)); else if (p->io & MIDI_OUTPUT) return !JACK_jack_disconnect(client, JACK_jack_port_name(midi_out_port), JACK_jack_port_name(m->port)); return 1; } /* gets called by JACK in a separate thread */ static int _jack_process(jack_nframes_t nframes, void *user_data) { struct midi_provider *p = (struct midi_provider *)user_data; if (p->cancelled) return 1; /* try to exit safely */ /* handle midi in */ void *midi_in_buffer = JACK_jack_port_get_buffer(midi_in_port, nframes); const jack_nframes_t count = JACK_jack_midi_get_event_count(midi_in_buffer); for (jack_nframes_t i = 0; i < count; i++) { jack_midi_event_t event = {0}; if (JACK_jack_midi_event_get(&event, midi_in_buffer, i)) break; const uint32_t len = event.size; if (len + sizeof(len) > ringbuffer_out_max_write) break; if (JACK_jack_ringbuffer_write_space(ringbuffer_in) < len + sizeof(len)) break; /* give up */ JACK_jack_ringbuffer_write(ringbuffer_in, (const char*)&len, sizeof(len)); JACK_jack_ringbuffer_write(ringbuffer_in, (const char*)event.buffer, len); } /* handle midi out */ void *midi_out_buffer = JACK_jack_port_get_buffer(midi_out_port, nframes); JACK_jack_midi_clear_buffer(midi_out_buffer); uint32_t size = 0; while (JACK_jack_ringbuffer_peek(ringbuffer_out, (char*)&size, sizeof(size)) == sizeof(size) && JACK_jack_ringbuffer_read_space(ringbuffer_out) >= sizeof(size) + size) { JACK_jack_ringbuffer_read_advance(ringbuffer_out, sizeof(size)); jack_midi_data_t* data = JACK_jack_midi_event_reserve(midi_out_buffer, 0, size); if (data) JACK_jack_ringbuffer_read(ringbuffer_out, (char*)data, size); else JACK_jack_ringbuffer_read_advance(ringbuffer_out, size); } return 0; } static void _jack_disconnect(void) { if (midi_in_port) { JACK_jack_port_unregister(client, midi_in_port); midi_out_port = NULL; } if (midi_out_port) { JACK_jack_port_unregister(client, midi_out_port); midi_out_port = NULL; } if (client) { JACK_jack_client_close(client); client = NULL; } } static int _jack_thread(struct midi_provider *p) { /* processes midi in events... */ while (!p->cancelled) { uint32_t size = 0; while (JACK_jack_ringbuffer_peek(ringbuffer_in, (char*)&size, sizeof(size)) == sizeof(size) && JACK_jack_ringbuffer_read_space(ringbuffer_in) >= sizeof(size) + size) { JACK_jack_ringbuffer_read_advance(ringbuffer_in, sizeof(size)); unsigned char *event = malloc(size); if (event) { JACK_jack_ringbuffer_read(ringbuffer_in, (char *)event, size); midi_received_cb(NULL, event, size); free(event); } else { JACK_jack_ringbuffer_read_advance(ringbuffer_in, size); } } timer_msleep(10); } // if we're cancelled, disconnect _jack_disconnect(); return 0; } /* inout for these functions should be EITHER MIDI_INPUT or MIDI_OUTPUT, never both. * jack has no concept of duplex ports */ static void _jack_enumerate_ports(const char **port_names, struct midi_provider *p, int inout) { struct midi_port *ptr; struct jack_midi *m; int ok; /* search for new ports to insert */ const char **port_name; for (port_name = port_names; *port_name; port_name++) { jack_port_t* port = JACK_jack_port_by_name(client, *port_name); if (JACK_jack_port_is_mine(client, port)) continue; ptr = NULL; ok = 0; while (midi_port_foreach(p, &ptr)) { m = (struct jack_midi *)ptr->userdata; if (ptr->iocap == inout && m->port == port) { m->mark = 1; ok = 1; } } if (ok) continue; /* create the UI name... */ char name[55]; snprintf(name, 55, " %-*.*s (JACK)", 55 - 9, 55 - 9, *port_name); m = mem_alloc(sizeof(*m)); m->port = port; m->mark = 1; midi_port_register(p, inout, name, m, 1); } } static int _jack_attempt_connect(struct midi_provider* jack_provider_) { /* already connected? */ if (client) return 1; /* create our client */ client = JACK_jack_client_open(PORT_NAME, 0, NULL); if (!client) goto fail; midi_in_port = JACK_jack_port_register(client, "MIDI In", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); if (!midi_in_port) goto fail; midi_out_port = JACK_jack_port_register(client, "MIDI Out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); if (!midi_out_port) goto fail; /* hand this over to JACK */ if (JACK_jack_set_process_callback(client, _jack_process, (void*)jack_provider_)) goto fail; if (JACK_jack_activate(client)) goto fail; return 1; fail: _jack_disconnect(); return 0; } static void _jack_poll(struct midi_provider* jack_provider_) { struct midi_port* ptr; struct jack_midi* m; if (!_jack_attempt_connect(jack_provider_)) return; ptr = NULL; while (midi_port_foreach(jack_provider_, &ptr)) { m = (struct jack_midi*)ptr->userdata; m->mark = 0; } const char** ports; ports = JACK_jack_get_ports(client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput); _jack_enumerate_ports(ports, jack_provider_, MIDI_INPUT); JACK_jack_free(ports); ports = JACK_jack_get_ports(client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput); _jack_enumerate_ports(ports, jack_provider_, MIDI_OUTPUT); JACK_jack_free(ports); while (midi_port_foreach(jack_provider_, &ptr)) { m = (struct jack_midi*)ptr->userdata; if (!m->mark) midi_port_unregister(ptr->num); } } int jack_midi_setup(void) { static const struct midi_driver jack_driver = { .poll = _jack_poll, .flags = 0, // jack is realtime .thread = _jack_thread, .enable = _jack_start, .disable = _jack_stop, .send = _jack_send, }; #ifdef JACK_DYNAMIC_LOAD if (!jack_dltrick_handle_) #endif if (jack_dlinit()) return 0; ringbuffer_in = JACK_jack_ringbuffer_create(JACK_RINGBUFFER_SIZE); if (!ringbuffer_in) goto fail; ringbuffer_out = JACK_jack_ringbuffer_create(JACK_RINGBUFFER_SIZE); if (!ringbuffer_out) goto fail; ringbuffer_in_max_write = JACK_jack_ringbuffer_write_space(ringbuffer_in); ringbuffer_out_max_write = JACK_jack_ringbuffer_write_space(ringbuffer_out); struct midi_provider* p = midi_provider_register("JACK-MIDI", &jack_driver); if (!p) /* how? can this check be removed? */ goto fail; return 1; fail: _jack_disconnect(); return 0; } schismtracker-20250313/sys/macos/000077500000000000000000000000001476471630300165505ustar00rootroot00000000000000schismtracker-20250313/sys/macos/dirent/000077500000000000000000000000001476471630300200355ustar00rootroot00000000000000schismtracker-20250313/sys/macos/dirent/macos-dirent.c000066400000000000000000000067631476471630300226020ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Based off code by Guido van Rossum in Python. */ #include "headers.h" #include "str.h" #include "dmoz.h" #include "osdefs.h" #include "charset.h" #include "util.h" #include "macos-dirent.h" #include #include /* values for DIR */ struct dir_ { long dirid; int nextfile; struct dirent dir; }; /* Open a directory. This means calling PBOpenWD. */ DIR *opendir(const char *path) { /* Schism never opens more than one directory at a time, * so we can just one static directory structure. */ static DIR dir; if (dir.nextfile) { errno = ENFILE; return NULL; } OSErr err = noErr; WDPBRec pb; unsigned char ppath[256]; FSSpec spec; { // We can just pass the full path to PBOpenWD int truncated; str_to_pascal(path, ppath, &truncated); if (truncated) { errno = ENAMETOOLONG; return NULL; } // Append a separator on the end if one isn't there already; I don't // know if this is strictly necessary, but every macos path I've seen // that goes to a folder has an explicit path separator on the end. if (ppath[ppath[0]] != ':') { if (ppath[0] >= 255) { errno = ENAMETOOLONG; return NULL; } ppath[++ppath[0]] = ':'; } pb.ioNamePtr = ppath; pb.ioVRefNum = 0; pb.ioWDProcID = 0; pb.ioWDDirID = 0; } err = PBOpenWD(&pb, 0); switch (err) { case noErr: break; case nsvErr: case fnfErr: errno = ENOENT; return NULL; case tmwdoErr: errno = ENFILE; return NULL; case afpAccessDenied: errno = EACCES; return NULL; default: return NULL; } dir.dirid = pb.ioVRefNum; dir.nextfile = 1; return &dir; } /* Close an open directory. */ void closedir(DIR *dirp) { WDPBRec pb; pb.ioVRefNum = dirp->dirid; (void) PBCloseWD(&pb, 0); dirp->dirid = 0; dirp->nextfile = 0; } /* Read the next directory entry. */ struct dirent *readdir(DIR *dp) { union { DirInfo d; FileParam f; HFileInfo hf; } pb; unsigned char pname[256]; pname[0] = '\0'; pb.d.ioNamePtr = pname; pb.d.ioVRefNum = dp->dirid; pb.d.ioDrDirID = 0; pb.d.ioFDirIndex = dp->nextfile++; short err = PBGetCatInfo((CInfoPBPtr)&pb, 0); switch (err) { case noErr: break; case nsvErr: case fnfErr: errno = ENOENT; return NULL; case ioErr: errno = EIO; return NULL; case bdNamErr: errno = EILSEQ; return NULL; case paramErr: case dirNFErr: case afpObjectTypeErr: errno = ENOTDIR; return NULL; case afpAccessDenied: errno = EACCES; return NULL; default: return NULL; } str_from_pascal(pname, dp->dir.d_name); return &dp->dir; }schismtracker-20250313/sys/macos/dirent/macos-dirent.h000066400000000000000000000024341476471630300225760ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_SYS_MACOS_DIRENT_H_ #define SCHISM_SYS_MACOS_DIRENT_H_ typedef struct dir_ DIR; struct dirent { char d_name[256]; }; extern DIR *opendir(const char *path); extern struct dirent *readdir(DIR *dirp); extern void closedir(DIR *dirp); #endif /* SCHISM_SYS_MACOS_DIRENT_H_ */ schismtracker-20250313/sys/macos/mt.c000066400000000000000000000127111476471630300173360ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* SDL 1.2 doesn't provide threads on Mac OS, so we need to implement * them ourselves. */ #include "headers.h" #include "mem.h" #include "log.h" #include "backend/mt.h" #include /* -------------------------------------------------------------- */ struct mt_mutex { MPCriticalRegionID mutex; }; static mt_mutex_t *macos_mutex_create(void) { mt_mutex_t *mutex = mem_alloc(sizeof(*mutex)); OSStatus err = MPCreateCriticalRegion(&mutex->mutex); if (err != noErr) { free(mutex); return NULL; } return mutex; } static void macos_mutex_delete(mt_mutex_t *mutex) { MPDeleteCriticalRegion(mutex->mutex); } static void macos_mutex_lock(mt_mutex_t *mutex) { MPEnterCriticalRegion(mutex->mutex, kDurationForever); } static void macos_mutex_unlock(mt_mutex_t *mutex) { MPExitCriticalRegion(mutex->mutex); } /* -------------------------------------------------------------- */ struct mt_cond { MPEventID event; }; static mt_cond_t *macos_cond_create(void) { mt_cond_t *cond = mem_alloc(sizeof(*cond)); OSStatus err = MPCreateEvent(&cond->event); if (err != noErr) { free(cond); return NULL; } return cond; } static void macos_cond_delete(mt_cond_t *cond) { MPDeleteEvent(cond->event); } static void macos_cond_signal(mt_cond_t *cond) { MPSetEvent(cond->event, 1); } static void macos_cond_wait(mt_cond_t *cond, mt_mutex_t *mutex) { MPWaitForEvent(cond->event, NULL, kDurationForever); } static void macos_cond_wait_timeout(mt_cond_t *cond, mt_mutex_t *mutex, uint32_t timeout) { MPWaitForEvent(cond->event, NULL, timeout); } /* -------------------------------------------------------------- */ // what? static MPQueueID notification_queue = NULL; struct mt_thread { MPTaskID task; MPEventID event; // for macos_thread_wait int status; void *userdata; int (*func)(void *); }; static OSStatus macos_dummy_thread_func(void *userdata) { mt_thread_t *thread = userdata; thread->status = thread->func(thread->userdata); // Notify any waiting thread MPSetEvent(thread->event, 1); return 0; } static mt_thread_t *macos_thread_create(schism_thread_function_t func, SCHISM_UNUSED const char *name, void *userdata) { OSStatus err = noErr; mt_thread_t *thread = mem_alloc(sizeof(*thread)); thread->func = func; thread->userdata = userdata; err = MPCreateEvent(&thread->event); if (err != noErr) { free(thread); log_appendf(4, "MPCreateEvent: %" PRId32, err); return NULL; } // use a 512 KiB stack size, which should be plenty for us err = MPCreateTask(macos_dummy_thread_func, thread, UINT32_C(524288), notification_queue, NULL, NULL, 0, &thread->task); if (err != noErr) { MPDeleteEvent(thread->event); free(thread); log_appendf(4, "MPCreateTask: %" PRId32, err); return NULL; } return thread; } static void macos_thread_wait(mt_thread_t *thread, int *status) { // Wait until the dummy function calls us back MPWaitForEvent(thread->event, NULL, kDurationForever); MPDeleteEvent(thread->event); if (status) *status = thread->status; free(thread); } static void macos_thread_set_priority(int priority) { MPTaskWeight weight; switch (priority) { case MT_THREAD_PRIORITY_LOW: weight = 10; break; case MT_THREAD_PRIORITY_NORMAL: weight = 100; break; case MT_THREAD_PRIORITY_HIGH: weight = 1000; break; case MT_THREAD_PRIORITY_TIME_CRITICAL: weight = 10000; break; default: return; } MPSetTaskWeight(MPCurrentTaskID(), weight); } static mt_thread_id_t macos_thread_id(void) { return (mt_thread_id_t)MPCurrentTaskID(); } ////////////////////////////////////////////////////////////////////////////// static int macos_threads_init(void) { if (!MPLibraryIsLoaded()) return 0; if (MPCreateQueue(¬ification_queue) != noErr) return 0; return 1; } static void macos_threads_quit(void) { MPDeleteQueue(notification_queue); } ////////////////////////////////////////////////////////////////////////////// const schism_mt_backend_t schism_mt_backend_macos = { .init = macos_threads_init, .quit = macos_threads_quit, .thread_create = macos_thread_create, .thread_wait = macos_thread_wait, .thread_set_priority = macos_thread_set_priority, .thread_id = macos_thread_id, .mutex_create = macos_mutex_create, .mutex_delete = macos_mutex_delete, .mutex_lock = macos_mutex_lock, .mutex_unlock = macos_mutex_unlock, .cond_create = macos_cond_create, .cond_delete = macos_cond_delete, .cond_signal = macos_cond_signal, .cond_wait = macos_cond_wait, .cond_wait_timeout = macos_cond_wait_timeout, }; schismtracker-20250313/sys/macos/osdefs.c000066400000000000000000000454121476471630300202050ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "osdefs.h" #include "events.h" #include "song.h" #include "page.h" #include "widget.h" #include "dmoz.h" #include "charset.h" #include "str.h" #include #include #include #include /* ------------------------------------------------------------------------ */ static inline SCHISM_ALWAYS_INLINE time_t time_get_macintosh_epoch(void) { // This assumes the epoch is in UTC. This is only always true for Mac OS // Extended partitions; I'm not sure about the time functions since I // don't use those... struct tm macintosh_epoch_tm = { .tm_year = 4, // 1904 .tm_mon = 0, // January .tm_mday = 1, // 1st }; return mktime(&macintosh_epoch_tm); } static inline SCHISM_ALWAYS_INLINE time_t time_convert_from_macintosh(uint32_t x) { return ((time_t)x) + time_get_macintosh_epoch(); } static inline SCHISM_ALWAYS_INLINE uint32_t time_convert_to_macintosh(time_t x) { return (uint32_t)(x - time_get_macintosh_epoch()); } /* ------------------------------------------------------------------------ */ void macos_get_modkey(schism_keymod_t *mk) { // SDL 1.2 treats Command as Control, so switch the values. schism_keymod_t mk_ = (*mk) & ~(SCHISM_KEYMOD_CTRL|SCHISM_KEYMOD_GUI); if (*mk & SCHISM_KEYMOD_LCTRL) mk_ |= SCHISM_KEYMOD_LGUI; if (*mk & SCHISM_KEYMOD_RCTRL) mk_ |= SCHISM_KEYMOD_RGUI; if (*mk & SCHISM_KEYMOD_LGUI) mk_ |= SCHISM_KEYMOD_LCTRL; if (*mk & SCHISM_KEYMOD_RGUI) mk_ |= SCHISM_KEYMOD_RCTRL; *mk = mk_; } /* ------------------------------------------------------------------------ */ void macos_show_message_box(const char *title, const char *text) { // This converts the message to the HFS character set, which // isn't necessarily the system character set, but whatever unsigned char err[256], explanation[256]; int16_t hit; // These message boxes should really only be taking in ASCII anyway str_to_pascal(title, err, NULL); str_to_pascal(text, explanation, NULL); StandardAlert(kAlertDefaultOKText, err, explanation, NULL, &hit); } /* ------------------------------------------------------------------------ */ int macos_mkdir(const char *path, SCHISM_UNUSED mode_t mode) { HParamBlockRec pb = {0}; unsigned char mpath[256]; struct stat st; { int truncated = 0; // Fix the path, then convert it to a pascal string char *normal = dmoz_path_normal(path); str_to_pascal(normal, mpath, &truncated); free(normal); if (truncated) { errno = ENAMETOOLONG; return -1; } } // Append a separator on the end if one isn't there already; I don't // know if this is strictly necessary, but every macos path I've seen // that goes to a folder has an explicit path separator on the end. if (mpath[mpath[0]] != ':') { if (mpath[0] >= 255) { errno = ENAMETOOLONG; return -1; } mpath[++mpath[0]] = ':'; } pb.fileParam.ioNamePtr = mpath; OSErr err = PBDirCreateSync(&pb); switch (err) { case noErr: return 0; case nsvErr: case fnfErr: case dirNFErr: errno = ENOTDIR; return -1; case dirFulErr: case dskFulErr: errno = ENOSPC; return -1; case bdNamErr: errno = EILSEQ; return -1; case ioErr: case wrgVolTypErr: //FIXME find a more appropriate errno value for this errno = EIO; return -1; case wPrErr: case vLckdErr: errno = EROFS; return -1; case afpAccessDenied: errno = EACCES; return -1; default: return -1; } } int macos_stat(const char *file, struct stat *st) { CInfoPBRec pb = {0}; unsigned char ppath[256]; { int truncated; char *normal = dmoz_path_normal(file); str_to_pascal(normal, ppath, &truncated); free(normal); if (truncated) { errno = ENAMETOOLONG; return -1; } } // If our path is just a volume name, PBGetCatInfoSync will // fail, so append a path separator on the end in this case. if (!memchr(ppath + 1, ':', ppath[0])) { if (ppath[0] >= 255) { errno = ENAMETOOLONG; return -1; } ppath[++ppath[0]] = ':'; } if (!strcmp(file, ".")) { *st = (struct stat){ .st_mode = S_IFDIR, .st_ino = -1, }; } else { pb.hFileInfo.ioNamePtr = ppath; OSErr err = PBGetCatInfoSync(&pb); switch (err) { case noErr: *st = (struct stat){ .st_mode = (pb.hFileInfo.ioFlAttrib & ioDirMask) ? S_IFDIR : S_IFREG, .st_ino = pb.hFileInfo.ioFlStBlk, .st_dev = pb.hFileInfo.ioVRefNum, .st_nlink = 1, .st_size = pb.hFileInfo.ioFlLgLen, .st_atime = time_convert_from_macintosh(pb.hFileInfo.ioFlMdDat), .st_mtime = time_convert_from_macintosh(pb.hFileInfo.ioFlMdDat), .st_ctime = time_convert_from_macintosh(pb.hFileInfo.ioFlCrDat), }; return 0; case nsvErr: case fnfErr: errno = ENOENT; return -1; case bdNamErr: case paramErr: errno = EILSEQ; return -1; case ioErr: errno = EIO; return -1; case afpAccessDenied: errno = EACCES; return -1; case dirNFErr: case afpObjectTypeErr: errno = ENOTDIR; return -1; default: return -1; } } return -1; } /* ------------------------------------------------------------------------ */ /* MacOS initialization code stolen from SDL and removed Mac OS X code */ /* SDL - Simple DirectMedia Layer Copyright (C) 1997-2006 Sam Lantinga This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Sam Lantinga slouken@libsdl.org */ /* This file takes care of command line argument parsing, and stdio redirection in the MacOS environment. (stdio/stderr is *not* directed for Mach-O builds) */ #include #include #include #include #include /* The standard output files */ #define STDOUT_FILE "stdout.txt" #define STDERR_FILE "stderr.txt" /* Structure for keeping prefs in 1 variable */ typedef struct { Str255 command_line; Str255 video_driver_name; int output_to_file; } PrefsRecord; /* See if the command key is held down at startup */ static int CommandKeyIsDown(void) { KeyMap theKeyMap; GetKeys(theKeyMap); // if (((unsigned char *)theKeyMap)[6] & 0x80) return 1; return 0; } /* Parse a command line buffer into arguments */ static int ParseCommandLine(char *cmdline, char **argv) { char *bufp; int argc; argc = 0; for ( bufp = cmdline; *bufp; ) { /* Skip leading whitespace */ while (isspace(*bufp)) ++bufp; /* Skip over argument */ if ( *bufp == '"' ) { ++bufp; if ( *bufp ) { if ( argv ) { argv[argc] = bufp; } ++argc; } /* Skip over word */ while ( *bufp && (*bufp != '"') ) { ++bufp; } } else { if ( *bufp ) { if ( argv ) { argv[argc] = bufp; } ++argc; } /* Skip over word */ while ( *bufp && !isspace(*bufp) ) { ++bufp; } } if ( *bufp ) { if ( argv ) { *bufp = '\0'; } ++bufp; } } if ( argv ) { argv[argc] = NULL; } return(argc); } /* Remove the output files if there was no output written */ static void cleanup_output(void) { FILE *file; int empty; /* Flush the output in case anything is queued */ fclose(stdout); fclose(stderr); /* See if the files have any output in them */ file = fopen(STDOUT_FILE, "rb"); if ( file ) { empty = (fgetc(file) == EOF) ? 1 : 0; fclose(file); if ( empty ) { remove(STDOUT_FILE); } } file = fopen(STDERR_FILE, "rb"); if ( file ) { empty = (fgetc(file) == EOF) ? 1 : 0; fclose(file); if ( empty ) { remove(STDERR_FILE); } } } // XXX this should be adapted for a dmoz.c static int getCurrentAppName (StrFileName name) { ProcessSerialNumber process; ProcessInfoRec process_info; FSSpec process_fsp; process.highLongOfPSN = 0; process.lowLongOfPSN = kCurrentProcess; process_info.processInfoLength = sizeof (process_info); process_info.processName = NULL; process_info.processAppSpec = &process_fsp; if ( noErr != GetProcessInformation (&process, &process_info) ) return 0; memcpy(name, process_fsp.name, process_fsp.name[0] + 1); return 1; } static int getPrefsFile (FSSpec *prefs_fsp, int create) { /* The prefs file name is the application name, possibly truncated, */ /* plus " Preferences */ #define SUFFIX " Preferences" #define MAX_NAME 19 /* 31 - strlen (SUFFIX) */ short volume_ref_number; long directory_id; StrFileName prefs_name; StrFileName app_name; /* Get Preferences folder - works with Multiple Users */ if ( noErr != FindFolder ( kOnSystemDisk, kPreferencesFolderType, kDontCreateFolder, &volume_ref_number, &directory_id) ) exit (-1); if ( ! getCurrentAppName (app_name) ) exit (-1); /* Truncate if name is too long */ if (app_name[0] > MAX_NAME ) app_name[0] = MAX_NAME; memcpy(prefs_name + 1, app_name + 1, app_name[0]); memcpy(prefs_name + app_name[0] + 1, SUFFIX, strlen (SUFFIX)); prefs_name[0] = app_name[0] + strlen (SUFFIX); /* Make the file spec for prefs file */ if ( noErr != FSMakeFSSpec (volume_ref_number, directory_id, prefs_name, prefs_fsp) ) { if ( !create ) return 0; else { /* Create the prefs file */ memcpy(prefs_fsp->name, prefs_name, prefs_name[0] + 1); prefs_fsp->parID = directory_id; prefs_fsp->vRefNum = volume_ref_number; FSpCreateResFile (prefs_fsp, 0x3f3f3f3f, 'pref', 0); // '????' parsed as trigraph if ( noErr != ResError () ) return 0; } } return 1; } static int readPrefsResource (PrefsRecord *prefs) { Handle prefs_handle; prefs_handle = Get1Resource( 'CLne', 128 ); if (prefs_handle != NULL) { int offset = 0; // int j = 0; HLock(prefs_handle); /* Get command line string */ memcpy(prefs->command_line, *prefs_handle, (*prefs_handle)[0]+1); /* Get video driver name */ offset += (*prefs_handle)[0] + 1; memcpy(prefs->video_driver_name, *prefs_handle + offset, (*prefs_handle)[offset] + 1); /* Get save-to-file option (1 or 0) */ offset += (*prefs_handle)[offset] + 1; prefs->output_to_file = (*prefs_handle)[offset]; ReleaseResource( prefs_handle ); return ResError() == noErr; } return 0; } static int writePrefsResource (PrefsRecord *prefs, short resource_file) { Handle prefs_handle; UseResFile (resource_file); prefs_handle = Get1Resource ( 'CLne', 128 ); if (prefs_handle != NULL) RemoveResource (prefs_handle); prefs_handle = NewHandle ( prefs->command_line[0] + prefs->video_driver_name[0] + 4 ); if (prefs_handle != NULL) { int offset; HLock (prefs_handle); /* Command line text */ offset = 0; memcpy(*prefs_handle, prefs->command_line, prefs->command_line[0] + 1); /* Video driver name */ offset += prefs->command_line[0] + 1; memcpy(*prefs_handle + offset, prefs->video_driver_name, prefs->video_driver_name[0] + 1); /* Output-to-file option */ offset += prefs->video_driver_name[0] + 1; *( *((char**)prefs_handle) + offset) = (char)prefs->output_to_file; *( *((char**)prefs_handle) + offset + 1) = 0; AddResource (prefs_handle, 'CLne', 128, "\pCommand Line"); WriteResource (prefs_handle); UpdateResFile (resource_file); DisposeHandle (prefs_handle); return ResError() == noErr; } return 0; } static int readPreferences (PrefsRecord *prefs) { int no_error = 1; FSSpec prefs_fsp; /* Check for prefs file first */ if ( getPrefsFile (&prefs_fsp, 0) ) { short prefs_resource; prefs_resource = FSpOpenResFile (&prefs_fsp, fsRdPerm); if ( prefs_resource == -1 ) /* this shouldn't happen, but... */ return 0; UseResFile (prefs_resource); no_error = readPrefsResource (prefs); CloseResFile (prefs_resource); } /* Fall back to application's resource fork (reading only, so this is safe) */ else { no_error = readPrefsResource (prefs); } return no_error; } static int writePreferences (PrefsRecord *prefs) { int no_error = 1; FSSpec prefs_fsp; /* Get prefs file, create if it doesn't exist */ if ( getPrefsFile (&prefs_fsp, 1) ) { short prefs_resource; prefs_resource = FSpOpenResFile (&prefs_fsp, fsRdWrPerm); if (prefs_resource == -1) return 0; no_error = writePrefsResource (prefs, prefs_resource); CloseResFile (prefs_resource); } return no_error; } static char **args = NULL; static char *commandLine = NULL; /* called by main, fixes ~everything~ basically */ void macos_sysinit(int *pargc, char ***pargv) { #define DEFAULT_ARGS {'\0'} /* pascal string for default args */ #define DEFAULT_VIDEO_DRIVER {'\7', 't', 'o', 'o', 'l', 'b', 'o', 'x'} /* pascal string for default video driver name */ #define DEFAULT_OUTPUT_TO_FILE 1 /* 1 == output to file, 0 == no output */ #define VIDEO_ID_DRAWSPROCKET 1 /* these correspond to popup menu choices */ #define VIDEO_ID_TOOLBOX 2 PrefsRecord prefs = { DEFAULT_ARGS, DEFAULT_VIDEO_DRIVER, DEFAULT_OUTPUT_TO_FILE }; int nargs; StrFileName appNameText; int videodriver = VIDEO_ID_TOOLBOX; int settingsChanged = 0; long i; /* Kyle's SDL command-line dialog code ... */ InitGraf (&qd.thePort); InitFonts (); InitWindows (); InitMenus (); InitDialogs (nil); InitCursor (); InitContextualMenus(); FlushEvents(everyEvent,0); MaxApplZone (); MoreMasters (); MoreMasters (); if ( readPreferences (&prefs) ) { if (memcmp(prefs.video_driver_name+1, "DSp", 3) == 0) videodriver = 1; else if (memcmp(prefs.video_driver_name+1, "toolbox", 7) == 0) videodriver = 2; } if ( CommandKeyIsDown() ) { #define kCL_OK 1 #define kCL_Cancel 2 #define kCL_Text 3 #define kCL_File 4 #define kCL_Video 6 DialogPtr commandDialog; short dummyType; Rect dummyRect; Handle dummyHandle; short itemHit; /* Assume that they will change settings, rather than do exhaustive check */ settingsChanged = 1; /* Create dialog and display it */ commandDialog = GetNewDialog (1000, nil, (WindowPtr)-1); SetPort (commandDialog); /* Setup controls */ GetDialogItem (commandDialog, kCL_File, &dummyType, &dummyHandle, &dummyRect); /* MJS */ SetControlValue ((ControlHandle)dummyHandle, prefs.output_to_file ); GetDialogItem (commandDialog, kCL_Text, &dummyType, &dummyHandle, &dummyRect); SetDialogItemText (dummyHandle, prefs.command_line); GetDialogItem (commandDialog, kCL_Video, &dummyType, &dummyHandle, &dummyRect); SetControlValue ((ControlRef)dummyHandle, videodriver); SetDialogDefaultItem (commandDialog, kCL_OK); SetDialogCancelItem (commandDialog, kCL_Cancel); do { ModalDialog(nil, &itemHit); /* wait for user response */ /* Toggle command-line output checkbox */ if ( itemHit == kCL_File ) { GetDialogItem(commandDialog, kCL_File, &dummyType, &dummyHandle, &dummyRect); /* MJS */ SetControlValue((ControlHandle)dummyHandle, !GetControlValue((ControlHandle)dummyHandle) ); } } while (itemHit != kCL_OK && itemHit != kCL_Cancel); /* Get control values, even if they did not change */ GetDialogItem (commandDialog, kCL_Text, &dummyType, &dummyHandle, &dummyRect); /* MJS */ GetDialogItemText (dummyHandle, prefs.command_line); GetDialogItem (commandDialog, kCL_File, &dummyType, &dummyHandle, &dummyRect); /* MJS */ prefs.output_to_file = GetControlValue ((ControlHandle)dummyHandle); GetDialogItem (commandDialog, kCL_Video, &dummyType, &dummyHandle, &dummyRect); videodriver = GetControlValue ((ControlRef)dummyHandle); DisposeDialog (commandDialog); if (itemHit == kCL_Cancel ) { exit (0); } } /* Set pseudo-environment variables for video driver, update prefs */ switch ( videodriver ) { case VIDEO_ID_DRAWSPROCKET: setenv("SDL_VIDEODRIVER", "DSp", 1); memcpy(prefs.video_driver_name, "\pDSp", 4); break; case VIDEO_ID_TOOLBOX: setenv("SDL_VIDEODRIVER", "toolbox", 1); memcpy(prefs.video_driver_name, "\ptoolbox", 8); break; } /* Redirect standard I/O to files */ if ( prefs.output_to_file ) { freopen (STDOUT_FILE, "w", stdout); freopen (STDERR_FILE, "w", stderr); } else { fclose (stdout); fclose (stderr); } if (settingsChanged) { /* Save the prefs, even if they might not have changed (but probably did) */ if ( ! writePreferences (&prefs) ) fprintf (stderr, "WARNING: Could not save preferences!\n"); } appNameText[0] = 0; getCurrentAppName (appNameText); /* check for error here ? */ commandLine = (char*) malloc (appNameText[0] + prefs.command_line[0] + 2); if ( commandLine == NULL ) { exit(-1); } /* Rather than rewrite ParseCommandLine method, let's replace */ /* any spaces in application name with underscores, */ /* so that the app name is only 1 argument */ for (i = 1; i < 1+appNameText[0]; i++) if ( appNameText[i] == ' ' ) appNameText[i] = '_'; /* Copy app name & full command text to command-line C-string */ memcpy(commandLine, appNameText + 1, appNameText[0]); commandLine[appNameText[0]] = ' '; memcpy(commandLine + appNameText[0] + 1, prefs.command_line + 1, prefs.command_line[0]); commandLine[ appNameText[0] + 1 + prefs.command_line[0] ] = '\0'; /* Parse C-string into argv and argc */ nargs = ParseCommandLine (commandLine, NULL); args = (char **)malloc((nargs+1)*(sizeof *args)); if ( args == NULL ) { exit(-1); } ParseCommandLine (commandLine, args); // FIXME argc, argv should be in UTF-8 *pargc = nargs; *pargv = args; } void macos_sysexit(void) { free(args); free(commandLine); cleanup_output(); } schismtracker-20250313/sys/macos/resource.r000066400000000000000000000122331476471630300205630ustar00rootroot00000000000000#include "Processes.r" #include "CodeFragments.r" #include "Types.r" resource 'cfrg' (0) { { kPowerPCCFragArch, kIsCompleteCFrag, kNoVersionNum, kNoVersionNum, kDefaultStackSize, kNoAppSubFolder, kApplicationCFrag, kDataForkCFragLocator, kZeroOffset, kCFragGoesToEOF, "Schism Tracker" } }; resource 'SIZE' (-1) { reserved, acceptSuspendResumeEvents, reserved, canBackground, doesActivateOnFGSwitch, backgroundAndForeground, getFrontClicks, ignoreChildDiedEvents, is32BitCompatible, isHighLevelEventAware, onlyLocalHLEvents, notStationeryAware, useTextEditServices, reserved, reserved, reserved, 65536 * 1024, // Recommended RAM 8192 * 1024 // Minimum RAM }; /* ------------------------------------------------------------------------ */ /* version info */ resource 'vers' (1) { VERSION_YEAR, (VERSION_MONTH << 4) | VERSION_DAY, release, 0, verUS, VERSION, VERSION }; resource 'vers' (2) { VERSION_YEAR, (VERSION_MONTH << 4) | VERSION_DAY, release, 0, verUS, VERSION, "An oldschool sample-based music composition tool." }; /* ------------------------------------------------------------------------ */ /* menu bar ? */ data 'DLOG' (1000) { $"0072 0040 00EA 01B3 0001 0100 0000 0000 0000 03E8 0C43 6F6D 6D61 6E64 204C 696E" /* .r.@.ê.³...........è.Command Lin */ $"6500 280A" /* e.( */ }; data 'DLOG' (1001) { $"0072 0040 00DB 01AC 0001 0100 0000 0000 0000 03E9 0C45 7272 6F72 2057 696E 646F" /* .r.@.Û.¬...........é.Error Windo */ $"7700 280A" /* w.( */ }; data 'DLOG' (1002) { $"00B8 00BE 0147 01D8 0005 0100 0000 0000 0000 03EA 1643 6F6E 6669 726D 2044 6973" /* .¸.¾.G.Ø...........ê.Confirm Dis */ $"706C 6179 2043 6861 6E67 6510 280A" /* play Change.( */ }; data 'DITL' (1000) { $"0005 0000 0000 0052 0113 0066 0158 0402 4F4B 0000 0000 0052 00C2 0066 0107 0406" /* .......R...f.X..OK.....R.Â.f.... */ $"4361 6E63 656C 0000 0000 000F 0084 001F 0155 1000 0000 0000 0054 0019 0066 007D" /* Cancel.......„...U.......T...f.} */ $"050E 4F75 7470 7574 2074 6F20 6669 6C65 0000 0000 000F 0018 001F 007F 080D 436F" /* ..Output to file..............Co */ $"6D6D 616E 6420 4C69 6E65 3A00 0000 0000 0030 0018 0040 0158 0702 0080" /* mmand Line:......0...@.X...€ */ }; data 'DITL' (1001) { $"0001 0000 0000 0046 0120 005A 015A 0402 4F4B 0000 0000 0010 000A 0038 0160 0800" /* .......F. .Z.Z..OK.......Â.8.`.. */ }; data 'DITL' (1002) { $"0002 0000 0000 006F 001E 0083 0058 0406 4361 6E63 656C 0000 0000 006E 00C0 0082" /* .......o...ƒ.X..Cancel.....n.À.‚ */ $"00FA 0402 4F4B 0000 0000 000E 000F 005F 010C 88B3 5468 6520 7365 7474 696E 6720" /* .ú..OK........._..ˆ³The setting */ $"666F 7220 796F 7572 206D 6F6E 6974 6F72 2068 6173 2062 6565 6E20 6368 616E 6765" /* for your monitor has been change */ $"642C 2061 6E64 2069 7420 6D61 7920 6E6F 7420 6265 2064 6973 706C 6179 6564 2063" /* d, and it may not be displayed c */ $"6F72 7265 6374 6C79 2E20 546F 2063 6F6E 6669 726D 2074 6865 2064 6973 706C 6179" /* orrectly. To confirm the display */ $"2069 7320 636F 7272 6563 742C 2063 6C69 636B 204F 4B2E 2054 6F20 7265 7475 726E" /* is correct, click OK. To return */ $"2074 6F20 7468 6520 6F72 6967 696E 616C 2073 6574 7469 6E67 2C20 636C 6963 6B20" /* to the original setting, click */ $"4361 6E63 656C 2E00" /* Cancel.. */ }; data 'MENU' (128, preload) { $"0080 0000 0000 0000 0000 FFFF FFFB 0114 0C41 626F 7574 2053 444C 2E2E 2E00 0000" /* .€........ÿÿÿû...About SDL...... */ $"0001 2D00 0000 0000" /* ..-..... */ }; data 'MENU' (129) { $"0081 0000 0000 0000 0000 FFFF FFFF 0C56 6964 656F 2044 7269 7665 7219 4472 6177" /* .........ÿÿÿÿ.Video Driver.Draw */ $"5370 726F 636B 6574 2028 4675 6C6C 7363 7265 656E 2900 0000 001E 546F 6F6C 426F" /* Sprocket (Fullscreen).....ToolBo */ $"7820 2028 4675 6C6C 7363 7265 656E 2F57 696E 646F 7765 6429 0000 0000 00" /* x (Fullscreen/Windowed)..... */ }; data 'CNTL' (128) { $"0000 0000 0010 0140 0000 0100 0064 0081 03F0 0000 0000 0D56 6964 656F 2044 7269" /* .......@.....d..ð.....Video Dri */ $"7665 723A" /* ver: */ }; data 'TMPL' (128, "CLne") { $"0C43 6F6D 6D61 6E64 204C 696E 6550 5354 520C 5669 6465 6F20 4472 6976 6572 5053" /* .Command LinePSTR.Video DriverPS */ $"5452 0C53 6176 6520 546F 2046 696C 6542 4F4F 4C" /* TR.Save To FileBOOL */ }; schismtracker-20250313/sys/macosx/000077500000000000000000000000001476471630300167405ustar00rootroot00000000000000schismtracker-20250313/sys/macosx/Schism_Tracker.app/000077500000000000000000000000001476471630300224205ustar00rootroot00000000000000schismtracker-20250313/sys/macosx/Schism_Tracker.app/Contents/000077500000000000000000000000001476471630300242155ustar00rootroot00000000000000schismtracker-20250313/sys/macosx/Schism_Tracker.app/Contents/Info.plist000066400000000000000000000060211476471630300261640ustar00rootroot00000000000000 CFBundleDevelopmentRegion English CFBundleExecutable schismtracker CFBundleDocumentTypes CFBundleTypeExtensions it IT CFBundleTypeIconFile moduleIcon.icns CFBundleMIMETypes audio/mod audio/x-mod CFBundleTypeName Impulse Tracker Module CFBundleTypeRole Editor LSIsAppleDefaultForType CFBundleTypeExtensions 669 amf AMF ams AMS dbm DBM dmf DMF far FAR mdl MDL med MED mod MOD mt2 MT2 mtm MTM okt OKT psm PSM ptm PTM s3m S3M stm STM ult ULT umx UMX xm XM CFBundleTypeIconFile moduleIcon.icns CFBundleMIMETypes audio/mod audio/x-mod CFBundleTypeName Audio Module CFBundleTypeRole Viewer LSIsAppleDefaultForType CFBundleGetInfoString Copyright NSHumanReadableCopyright Copyright CFBundleIconFile appIcon.icns CFBundleIdentifier org.schismtracker.SchismTracker CFBundleInfoDictionaryVersion 6.0 CFBundleName Schism Tracker CFBundlePackageType APPL CFBundleShortVersionString CFBundleShortVersionString CFBundleSignature Schm CFBundleVersion CFBundleVersion NSMainNibFile MainMenu NSPrincipalClass NSApplication CGDisableCoalescedUpdates NSHighResolutionCapable schismtracker-20250313/sys/macosx/Schism_Tracker.app/Contents/PkgInfo000066400000000000000000000000101476471630300254640ustar00rootroot00000000000000APPLSchmschismtracker-20250313/sys/macosx/Schism_Tracker.app/Contents/Resources/000077500000000000000000000000001476471630300261675ustar00rootroot00000000000000schismtracker-20250313/sys/macosx/Schism_Tracker.app/Contents/Resources/AppSettings.plist000066400000000000000000000007261476471630300315120ustar00rootroot00000000000000 EncryptAndChecksum IsDroppable OutputType None RemainRunningAfterCompletion RequiresAdminPrivileges ScriptInterpreter /bin/sh schismtracker-20250313/sys/macosx/Schism_Tracker.app/Contents/Resources/appIcon.icns000066400000000000000000001465061476471630300304520ustar00rootroot00000000000000icnsFics#Hx?x?ics8]W]]]WW]]WW]]W2V]WVW]WWis32Au Z{*Ub}E aspuYi|twuO( \w\F%(-38TH/8.%4_gVL=E576'9i _OOBF>6: 2 ;*`TQGC=.;)$ &VaUNE>?&9(@emwo:aTID>X]{}a%ciontȘt` Opke*GVqpq}<VfbfnM}Vjky_L%_sj}OJ"00>.t{LM-A,(:&L]PS;M3=5#Agm WLU?K:8=>F#WNUCI>0B5' /CXPSDCA'B#>[XaZ.YOO DAWQnrtjT%Y`iu}nf_Q/=QUV*AIOi]_h5N[YYu}^?bI _oa^wtiU>"E_a{nI>$((+l)ioIC/7*$-c?VPJB834./jQNLA@9-6( l7QOKA<:'5$4IJPJ'QNH A\D4+1O+#@3 )^rHBGeHD:T?20+K*%'#59x#]tNONc>;9RB*,.L6,H  VU]xLEFe@73LM2-%A<%'!A/Ua:]zLDAbC>8GL*./?E2ie@\zJGEdA66EV/'/SQzwV8 \{JA@cJ;26Q5SymQ9)\zNG@\A;TzlYH7[wCCXsnf eu?QnWYurw_$$ 8Ezczv)& 3AQjz]LQw;!GMX\Sf]rW"*MWUwa|s!GV]gZF]nmLc4M`]lndj_9LQ:Yhz`V~xqNX%6vpUgx{}j=&Q6)f.if}qQMI!%BDRN mwR/)=W#! 8S>xno|U:VM.,2[+%&Z% .l' ofeS76RT-00[-$$ T, VI awG>\[75M\-,+Z9 QEI{j UfF@X^:6D`1.%OE**:N -m"WhGBWa89Ab/00LO! 2Z&]E ViEATd65>i:.'=Z'!Z- I~iViF?Ni?<8e;0-2[%#$!Q7 ;pUkHGRj::9d>(*0`1EM _DTmHBMn<51_J0,$W8#%1S'Eh|M:UoJAGm?<5YK(-+UD3iioR5UoHDIp=42VX-&@TDfydJ2TpG?CpE:0FV/Ift_K9-SpKEBm=7KpsfYLM?SmA@R}wuw [g6H[GUc^cQ&$ 8;[aQec)& 3?BJYXLADc6!>GGOanOIWN_rJ" GNNYRhzSi|t`!GNU]RBP^r}{s[>Grn.MXVbqbY^n~|qQ3-iuE'V_`WQp~ruohtbCB$ Yu]Nngj]lzxqlk\9&8/Frl(_|cfdndK>=#%!.62ksA bvueL0+3D%#"*=#]sZeoP;EE0-.E+%!?$Lrk# ee|`VM98CI/0-C,$%8(6mq= YoHANR97AM/-,B4 !79)aqW NaGBLS;7;O3/':<*) (:OpiObGDKU:O112;A!'>3#$I#9":Time>NhJCBZ@<7FC*--=:*OYjsn\E-NhHEE[=54EL.'0D>XnxrgUA/LiHAA[D;39H0C[q}zsfWH<0'MhKF@W=8Hb{|tjbYQM?LfCBOp|~zxw| S\|z6l8mk}@6է VIyڈ9 YL |; [O*}RTWoțe2ѰU( ٻnD ʥX5໐fB%٫zN,՜d6it32w$+];Hz>'+*(&%$E[`Q@@Wze-*(%$$&,Pb^K?FeU8SmE?!,*(&##)=XeZFAQroI:<>63Fn=%*(&$",I_eUCD^]B;@?:>A>;HmlF44>!*(&$"1 @\i^IEYzfG>AB=>WY<49S|=#(&$! (LciXGJeWB@CA>HgvL66Dfd)&$"$*1WjfQHSsN@DE@@V}3;S|(&$#  B`nbMI`ǃHCAIkf<"&$! !Ogm[KQls/{¸ RVmb&$! $4ZnjUK[{l:+\?4o%$!  FdrdPOgsQjK;B^: $! &Rkr_NWuhPNKMs b$! $7]rnYPb^OPRRMrmZ k #! HgwhTSntWPSTSRROc]GBQwi^L=7l :! (UovcR[yhTRTUTTSRRSqRDH]mfZJ>=F=E a# $8awr[TheSURTSTTSRRP\JFPntkbSFIRG52)2 " "JjzlWYwuD}SR[zjRTSRQML`~ni\MHQQ?$,+${ 9  )YszfVc_48kdifQTPMUrsmeWJOSI0+,&V`!$=c{v_XpL0H|yXQPPczpl`PNSP;!$,)7 %Loo[_~q@3ZhZfVNOXu~cZOSTE+ 03/8'+\v~iZj]8+V{PUPSfg {kqzfOWN7  *96(c`$=@gzc^wL3.-2üM[x[HFtocODEK( +1,(+'<TPrs`fjA100@kenQHTtyri]RSJL;yK(30% ,)+7V/_zm_rT9/6Rso\Rc_JKdtodWVZT?@Jx"(  &2<=5'262~:%074) *)!c S|hjjk eXeAznd`e_J3'#7BA{+ !$/:@:,!,7/s_#/76, &)&; 4Rrho{jk e|tjaecU<+%&'%5B,9A>2%(63Vw48/# %351]*Qggjjb cfdg]G2( ''$1B>t[@8)!$'>CJ,!+/71#pPqgfrjfukcR:-)* )(&&*8KHga!  "-89A=50 ",2.")($E2 PĺĹ zoa\[c0**,,+*)(+3?E?F@Sq! ",8>:.!06/xV#-30% ")'/ \$ OM~skhk]UJvi(-,,+*+2>GE:-(=@Iz#! #,8?=2%+70]n#.53'  )( z PÁzoiliZB4GFm(,+,11+)(''&$1B>s]A<1%5;:|[*! $6>4  OÁ{gpm`I945543211CTYF4-+;**)('&&*8LJhj#  &0=D8ik -ADFD0b 1 OÁÿxlebj=655665327AKPUN=*,+**('+5?FAGASt &'2;;0072I /DTVM10*#:[  OÀ ù}tptjaVa3876557?JQL@?LEK),*),3?GF?H}# ) '3<=4(!538&2EVWM8" (%( 0NwqrthQCQPf4767>JRPE:2/:LFT)++-4?HH@3)%29A=0!)4=?7+ 450}@4HXXM8" (&p0  N|trvo\F=:=QQ~j4=HQTK>62"07JFz])3>HKC7,'&'&&$4A;z@ *5?A9-" 15,qe !6KZYL8! '&"DZ  *?N¾|rwufOA==><=PQzvLVOD:54433214HFuuBMG;/))((''&&$2A:r[=;>UZ|E=75665433106NQp:2,+*)(''%2HGhs-& ';BKVL7! !04*w.  Mjgm@A ?>>DNT]Yv2776K5325=ILTP`*+,,+**)'&',7ACJESu (>GPPD= !*-74)RZ *Llkm?CB@@DMWZRFPQm4776546=GOPF9FIU/+,,+)(-8BGA6->>F# +@S_]E>8,x]$.2/&('#4 Llkm>ADLV\WMA<:LRe67=HPRI=50/BKN4+>/9CIC9-&$$:@<3 !-BV`^J5!-4-Xr)130& '&!.   LlknFT\[RF?= ;JT_9KG@)1:DJE;/(%%&%$5@;zA#/EXa]J6"*51@#-440% '&bY ,Llkr[WKB@??>>=;HT^RUQF;64433210=KCVCKG=1+'(&&%#3@9sU2H[b^J5$#422/'076/$ &  KlkoCBAK@?>=<>N^eM=7556543320/=QOb=4-*)*)(''&&%#-?@FPWW\\?576854225=GMVM|_&+,+**)(''%$#)6OWbF5%7=;ht(" ),%nX XLlks?CA@AFQYYQEGUUE6766547=IPND=ICvv&,,+**)'%%+9Nb]VOL#)39=B:I %660C iMlku>CGPY\TI@<:EVRK5658?IQOF;3/2HEi(,,+)('-;Qch^J5=>=;DVOT6@KRRI>521102HG[++*)0>Seh_J5(""5@:yC  &1:?=5* 22-~J&/20) %%z W MlkxVQFA@@?>>=;CVMgPTL@843A2101FHT.1@Vgj^J5)$$%$#3@8s\#*4>A>4*22*mi#+330' &%P Mlkv?BA8@?>=<?DNUW\Sd376+54331//6J][bI5+())(''&&%$"*ADU~A>2( (2091,675,"   &$ U Llkz?CA@AEOWZRGASOj47653119H]k_^NM'++**)(''%%'-7=L- %7::S0) &#` SMlk{>CGPX[TJA<;>RQn376544;J_mm^IAJDV(,+**('&*0;CGE:BA97&/5BA6xf )--:*QMlk|MZ]VMC>=>=<>RP|w255>Maon^J80-:JEyc',*))-5>GIE:0'"6?9yF!+3<=8-74-Sy !'352)U2Llk}SOFA@@?>>=<=QOy;Pdqo^I92001/8ICu|%,09BJJE:/($5"3@7tc &19?=7,#*20=  '..,"$(%n kLlk}?BBAA@?>>=;;PTzkp]J:3223211/4GDg9FMKD9.(&%&%%$"1@8]v$-6?@=4)'2105 '/1/)$$A),Llk?CBAA@?=<;M}'3;CA<1'!11+|] %,32/%  #$"+TLlk?CBA@>>GXjuk`Wj656+542214NQa:6$5448?IQRLAJJK8*+**)(''&%$&+4:KLDB".11D3761&   %#Ol'Klk?M]nwp^K@;::NS]=557?B9zI2>@@=<:LTZA=HQVTK@720.>JBO(+)*-6?FHE<2)#3?6tj&/799?90S  &4^(KlkZLB?@@?>>=<9IV[ZVTK@8428110.;K #+4:L@B?6,#'20,I  ":gIFW,*)(''&&%$#$(.CJF~^.) #20(re 7^BqJ/  KlkVa[RHA>=>=<:EVKc257=EMSRMC92.0FHR1*)('&%%(-7?FDCG>=<:DVKk=JRVSJ@820#/0FHK;)**('(-5=DHE>3*2=7Yy1NusN4 JlkAABAA@?>>=;9DWSzTRI?7324110//EIDG'*-2=DJHD90'#!->;K!,FrsP7 JlkBAA@?==>DLTa\{9754332110/.@IAT7CJKH@6-'$$#!'<<>0)BjuQ9 !%#!JlkCBB@@?BISY[WOWP~1605433211/.-?NLjIF;2+'%%&%%$##!%;=6>&?bvT<%"$(,+)&$" JlkBABGNW]\VLB<=RO{36,54321028@GNWO}r++)((''&&%%$##!"9=4uM<;;>QOx46(4337>HNSOGDLCt&**)(''&&%%$#".8CbxWA,+18<;8541/-*)' IlkX\RIB?>=<;=QOt5'8=FMSRND;3-7IDc)**)(''&&$# #5MryXC0/8@DC?=<:64405=IlkFBA@A@?>>=<;=QPk?CMSVRI@720//-4GFV,)*)('%"!$2Kn{YE45@GKKGECA?=987IlkEBBAA@?>>=;:==>CIQ^`eH:6544332110/.1FHI:$/E_|]J;>PW^[WSSQPLIHL0JlkGBB@@?CIRX\YQVVZA46654332110/--BD?d}^K?CVahfa\[YXVUSU-IlkGACIPX]]XNF>;NSWI466543321/,*.>Zx}_MBH_looifeba^ZXZ*IlkOW]_\TKB><;;:MTUR366542/-/=>=<;:JTQX35301:QkbQGNk}}xwurqknf"Ill}JBA@A@?>>=<;:HTOZ/9NfcQJRr~{xtpmHll|JBBAA@?>>=<:8EPIrcRJU{}{uGll|KABAA@?>=98;H`dTKU}Hll{KABA@=; F[r¶fSJTHkjvOVo¿øgTIUƷ Fp|ĸhTGOݿEŹgSAFBƁƺgP;:ȿCǻgO4(rɹCɾiL,*CiM% ж$L(ڿ$L11[X%$@gw-,*('Ow8%0,+*(4]T#0-+*(CkZ$1v"0-++(#*Q{uG R6*-+*(&6Ld42cS!-+*(&$#@OK=}xu5Ftu!/+*(%#!2GRI:5135/+9Zv3&*(&$",=QWJ;YzUv 1#&$! DY_PCH]x|f,{ùuGIk~[oP&$! $-N`]KCPixyl;*y rM7.\s'$!  Zh\KJ`z|dNGIJJIHEVzoP?mJJQj\HJJIGDCTo{dTMD96;;/(WWMi 0 &Ndl[OYtU25^Y\yZGJHEKboZQJ@8;=6% RWSXN!$!5XliVQdrE/BmiNHGHVraUPG=;>;-CXVJq $DapdTVod<1Q}[PZMGGNefID<>?5# 3\aVl.'(Qhq_T_{}T5*NkHLHJZvzZ|xbP]hU=B<+ 2fcU^ M$Q8[qmZVkpG2-,1twEQimO@?plZSJKZ\u1/0<_}Zot_H?IcuaWPG@?]u`i?'/-" ;XULu -V*TlucXf}S:/5LrfboSJWqTCDWtjZTLCBEA.@bY_X$.0(  %WVLe M$L6bvq^\qеtY|^RRQMOgdKBLfs`YQHBFE:))_`Ug#.2, PUSO pOGs]bzqYRURQ[tvWFGYv}h]UMFEGA0!![aOt' +30% AZ]O| -L&W̢fVUVSTehNFOiVSJFIF8(Lb[jI1*  >ggSfL;9gʌRWVT[rz[JI]z|i\ykHIJ@/"MpgfX #--0\XQUpI-xg[jʐRVdmRIRlrc\TYfhZ5( '25EgbXb$-1*FVTI+Lxc^a^Jx͑nBqKL`}|i`XPKMwxg\ %197-']aPv #.3." 8WUImKJpb_bba^RtίBplrd]TMMNF7`k]e! $/9;3&VaUk1"-41&  "UTL]oKpacb a]Wa6{iaYQNPL>/%"Pm[j(#.8=7* HbWeO!-54* MTUI+Jiagpb a]6rd]UPQPF5)$%&"Jlbl7*7>;/$=a^\b15-! CadRr K*Jt|^_a`Z}TSQSL=.''& !AlfjQ=6( 2io[v( 'Ae^Nb oIg`u|_iqgYdiZQD4+( '&%%(?sshT  +5=jiUn*  ).+!SSRM * I yjcZUp{xV-*:)('&)2HNI7) ATRGr m eFwneabbZHCrw{[3556rxy^3=<;9=tzsC<64543211/.9rzqv91*)('& $#"?qphb,% =ejZyA9* >_aSc&  F~cad?@?=<Hppba"/Fstbm3 &A@>>AKTXPDkyp}3665425;EMMD9esir-*)' ,7@D?30cjZl"$2?GE>dbThM #+.,#"QSQFk Ewedd=@CISYUK@:8dzm56;ENOG<3.-Zudz2*).7@EA7+%!$ZkXs.'5AIF9+M_Y[]%./-# ?TRFk%   EwedeFSZYPD=;8_{m}7;ENQK@6100/-Sua;)/8BGC9-'$5#!Nkal9(7DIG:,C`^Ou!)01-" -TQI[F  hEwed~jYUIA>==<<;8Z|p~OSND9422110/,Puc~N@IE;0)%%&&%$# HlahI*9FLH:,5_]Op)%-43,! RQPF k  EwedgB@? >=<;:;_zI<6432110.,NzvwY;2+('('&%$#=ifceCLH;-!%[]UjN-53+"  DRPEq $ -Ewedh>A@??=<>DNThs<4655432003;DY~usT')*)(('&$#"%:r|li8,! "^hge`'! 6VYO` E =Fwedi>A?>@EOVWOAW}mA4655325:7T~kF4547=HNME91-7lnjr(**)'&&*4BNQK<1_iUt0!+3993&F`]Nv# ',,-VYTDy # kFwec}jKW[ULA=;<;8S~jM5?IPOG;30//.5kqhr*)((,6DPRK=/%! Pj`k;%/6<:2(-]]Sj>  $,.-' =RQDfD Fwec|nUOD?>>=<<;8O}j_NQI?6210/.2irgs-.8FQTL>/'#" IkaiN!(2;=:1'!"[]SdV !(//-$ )SPLPi Fwec}l?@@??>=<;::Qva>84P32110/..asd}KSUL>0'$%&%$##Ajb_b(6>?<1'Q^[Th&-20,! OPPD" -Ewec}n>A@??=<=BKR]u}Z265543211/-.2_nS>1*&''&%$#"4jpcj=;0%$E^\Ly,)242)  BRQCjC -Ewec}o>A?>@DMTWODFutz^3655431/05?MVjj}E&))(('&$#%+4Bsx`v*RsjtL'*)((&%&).9AEB;gkUt1#+?mm`jT  VY]G!=Ewec{pLW[TKA=;<;9@rwxi145:DRZZN@4/)JskrW&*(''+3=EFC9/%!Si_k= *18942_^ZYa  *Zb`IpB>Dwec{rRND?>==<<;9?qwvu8GT][OA50/./,Bqkpj$*/7@GGB8.&""!Kj`iS$.6;:4*!K\\My $,,)DWQE_ h kCvec|q>@@??>=<<;8A@??>=;::@L~z?944332110/.,8oymvGA7-(%%&&%$##"7gg[h'08@>9.%2]\RjL #*//+" NOPBx BDvec{w>A@?><=CN[b\z}n56554320 39CLxmu0)('('&-%$##!-fo]{A>7-$$Z[V\\!(01/(  ?QPCd gDvec{z>@??FP^c_RDFNOJ@ksb4()(('& %#"$*2=pwau; T\_Rw/43.$   .QOJNCvecz{>HS_e`TE=9i{l:4459BKPOI?6/,^s^?')(('% (.8?EB<_m_k?Qkm\t6    QPN=},ADvecy~]f`TF?<;<;:8d{m>50.,Tsa{G')',4=DGC:0' Ki_hY$,35QlfXlE    ;ABU}gCvecy~QG@>=<<;97^~rVTQH>620//.+PsisM&+0:AHFB7/&"! Fjb\d",2984* 0\[SbX   #B_u>Cvecz=@@??>=;;:>CfxQ=74332110//.+Lrjq`:FJG?5-'$#"!:hgZk!)18;82(Y[[Rp   :]s_FBvecy>A@?>==BIQWUgmL255432110/.-,LyvswB=4+'%%&%$##"!.dhT})(18=;7-%O][Lx!   5Yn>~qY,Cvecx?@?@FMUZYQG;96U}iU3553226=FMOLABmpdr*('&%$#"!"&/eulnQ-'7][RfS -Mh?sY;&  BvecxU^YPF@<;<;:7S}iY246;CKPOJ@70+4jqev.('&&%$#&,5=<<;:7R}k}_=HOTPH>60. ,2hr`7'('&%',2;BFB<2&IiaZe/Viuu\@+ Bvecw??@??>=<<;96O~w|nRPF>6202//.-/er]@&(,1:AGEB7.&" >hfYo';^tv\A. 2BvecwA@@??><;;=BH^}m864332110//.-,[r`yL5@GHE=4+%"!!1egS*%9Yrw_B0 "%#!KBuecvA@@>>=AGPVXSV|xys155432110//-,+Uwqu_FD91)&%$$##"!!*bgRr6"6Rpx`E3$"%),+)&$" ,Bvecv@?AFMUZZSJA9Cuwv~35543210/.16>Dbxud*)'&%$##"!!%^h\gB5LmyaF6'&-1430-+*(&##-BvecvHRY]ZRI@<:98Aswt45542126?>=<<;:9>qxn=AKQSOG>60.--*=<<;98=r}nURND<400/.-+6kqcy/''$"$-@Xw}gN?6:IPUSPLJIFDB?<5AvedtD@@??><;;Ss~hPA9>QY^[WSSQPLIHL@vedsE@@>&AGPUYVPv~k?45432110//.-+/gnVsYniPD?>=<<;:97^}iS/8H\zmWIHRu~{xtpm?vedqG@@??>=<<;:95YxaxftoXJIU~}{u@vedpH@@??>=<;869Cb|qXJIW@vedoH@@?><9;CUkqYLIWAvedoH>==CTgr[LJV@vdblKQes[LGUƷ ?thqu\KDOݿ=w\JAF<x\H;>ȿ;x\G4,ɹ;z]F,?={]E% ж E%ڿ?)*Kj{I%$6Vrwutc-,*(!Bcwwtsr/%0,+*(,OmzvtuwpirvF$0-+*(9ZuzvuvwkK!*rta 1-++(#$Egzzwwxvb= Ersq.+-+*(&.Bq~zwyylT.,SnvrrvE"-+*(&$6DB7j|{weK9FA406ew\C7522AYdT>78BYpyyt_gwnbruD!-*(%$$& 9GG:26KbbO<7Uf]F;8AWn{{yiN5,,-(&2Klbqrp,(*(&$",6FLB67G_fVA9ZeW8(]suC+(&$"$.$=LK>6:BSm||nU;/.1/,4KecK2&'-Zss_,(&$"10EPI;8E[i^F=BSihN+8ALHBU}~}}||}x>A[qkN^zwwvvxwtsr qqptC&$! $)DUSE?I]hy\FB5&#FWp~~}}|{qpaD1*Ozwwvvuutsr qqpr^'$!  5LYPDBQfiXBcaKOGHgz~~}}|~f9/2D_txwwvvuutsr qqpqo*"$! ?SYMBG[kfQB@?acGLr{~~~}}| ~f:Rly|zxwwvvuvtsutr qqpptB$! $-IXWHCOdnbKBBDC?Xf?]lRF{~~}}|{yu}|yxwwvvwvkXC``gsrrqqppr]%! ! 9P]TFEWlm[GCDEDDC@MybqaG:7?y~~}}|{{|zzyxwyxsaN>6.12Ltrrqqppqn)!! #DW^QFK_ymSEDFFEEDDCCqoWA8:G^u~~~}}|{zzyyz{wjVD;4.+8A;7qrrqqpsA# $-K]\MGTlzPEFDEDDCAH=;ASl}~~}}|{zz{zs_K@92.381/4-/hsrqqpq]# !Kax~~}}|{{|}yhTE>81288,//)Uurqqpn(  !GZbUJQh{rO24UQSkQCEBADWo~~}}|}|q^KB<538;4$-/+@srqqp osA!$"/Ob`QL[r|gC0?b}xy]HCBCNd{~~}nRE@;57;9,&/-0nrqqp oq] %>Xf]OPdzw\;3LptTJRFBBHZr~}ptX<97<<2"  361Xuqqp opn'%J_fXNXo~pO6-I|`CFBERg~jOk#yfSCQXJ9>9*';8-Htqqp oos@$=4SfdUQbx|eE40/2ijAJ]vx`H;:b~o\LD>9=>c8" %+%*.-3qp ooq\SB\k`SWk{uY=322:=BIC?/.;5LN#,-& ..(Otqpor>$W3YkiZWh{zi]_dZKUuoWLMLHJ\urYE?F[rvcRJD?=CB8(!9:>Z!+/) ,.+6qqpop\JAhX]quc]ce`]j}|fRMOMLShiOBBQh}lZNHB?BE?0"8:5c%*1.$  $141frpom&\$Ou|p`afge`qt]POPNO]tv^HAI^vzJFBBFC7(1;8YB/)*;;.Rtponr?86^~`chhefcgyMRPOTglSDDTk~kZQi]CFG>.#4F@SM!*+%30*>rpo nq[*lv`VycigKRhcgyMQ\rzbKDL`yubTNHFIUS5)! &/45@:AU"*-('.,.mqppo nol%Gn^YZY}dheAMhdf~d~dFGWnl[RKHGFQONS"" %/74+ 9:6e "+/+  !/-'Xsppo nnr<Eh\Z]\[X~ehgCLe\iBdb{tcUPJGJLD5AEEZ#""  $.670%5:4[.!*1.% .-'Grppo nnpZEi[]\]]\[X~figCLj}C~l\SMJJNJ=/'$:GA](!!#,5:4) /;4TF *10' )-,1opponk#Dc[`h]]\[W~e`breVPLJNND5+&&'%7GA\5*5;9/$*:7FV/2*  '680]rpo nnmq<*DlxrZZ[ZV|rz ~o}KLNQJ=ZE' $5:1WroonnmnY A sd\Vi[ZQB85 43310DWUm@3-+=**)('&&)7NMUQ$  $,=FFNXMs7*,+**('*2;A>IFIW %.44,2:6>Z ".893%0-*0onnmlp; Are^[[^Z_XmS5876557=FKG=@PHpA+,*)+2;BA8-)ADB]" +%/670%#884e #0:93' #,+(]qonnm lnY 0Aykc\\__WHATUkV6767=FLKB82/;PKgG*++-2;CC<1)%b+,!'0792(783Y1&2<;4' ,+%Lronnm lmj! @tf_]^b]PD=;>TVjY6=><=TVgbIPKA954433215LK`^?GB8/*))(''&&$4E?XH8<6,#-84AV(6??5) &++*cpnnm llnX @mxi\PE@$?>=;>W^jjC<75665433106PT_j82,++**)(''%$3JJUW+% *=A@h=6* #36.Prnnm llmj  @t][v^ABBAA@>>CKP^]fk47765324;DHUTUg++,,+**)'&'+5=?KIJW "/8II?]. $*86-;pnnmlo9  V@m^]v^ACB@@BJRUNDQV_r6776556;DJJC8INPg.+,,+*))-5>A<3,ACB_" $1>DB5>:1WD (+(!++(,konmlmW  S@n^]u_@BCJRWSJA<:NW[t7667ED8*/73FP#+-*! ",*$Wqnmlkj  @n^]u`FRXWOE?=>=;KXYr9;DLOI@72H10@PHt9*07@EA8-(&%&%$7D?]6!(5AFD8+,868e&,-*! ,*%Epnmlko9  -?n^]tdXSIB@??>>=;JXYsLPLC:54433210?PGnI?FB;0+('&&%#5E>XB*7CHE9+!&762_%#+0.(  +*(.nnml kmV  ?n^]taDBA5@?>=<=Nb`uH<7566543320/?TRhR;2,*)*)(''&&%#0DAPZAIF9,!662YD*20)  %+)&]pml kki ?n^]tbACBAA?>?EMRW`Yt=676"54224:CKXQcN(+,+**)(''&%$'2LRR_7," 9@?PS% ,/)Kqml kkn8 X?n^]tcACB@AENTTMCIYUw@6766546;DJI@IHPMEb" &.3>` #7734nml kkmV k@n^]td@BFNTWPG?<:GZS|E6657=FLJC:3/3LKXf*,,+*)'+3AKNH;/?C=>=;FZR}J7>GMME<521103KMRf+A*-6CMOI[7 $-486/'!662Y6")+)# *)!Qqmml kkjn7 @n^]sgSNFA@@?>>=;DYPzYLOG?7432101JNOg-/8DOQJ=/(%+#5E>XF"(08:7/'561PJ&--*" *)$:omml kkjlV C?n^]seABBAA@?>=<>CKQT_WoU47654332003IZTwN=2+)(''&&%$"+EHK^:8/&+764f((/0.&  #*("Vpml kkjjn6 ?n^]sgACB@ADLRUNEBVTkY576;53226>LTT[MmB)++**)(''%%&+4:NOFg) (;=;[?,% *("Boml kkjjlU X@n^]rh@CFNTWPH@<;>UVj[5765548AMVUL>AOIdH),+**('&*/7>B?7EE:e/#+2DC:VH ,/1/lmlkkjgX?n^]qiLUYSKB>=>=<>UVhb366:DPWWM@50.n^]qjPMEA@@?>>=<=TVfk9FRYXNA621/9MI_`(,/6>ED@6-'$"6D=XK%,3761( .756g "((%&,( JplkkjkT>n^]qj@BBAA@?>>=;LW#*1896/'*761]) !(*($ ('$0nlkkjig->n^]ql@CBAA@?=<CMY_Y]Z]v776'5432149BFVXSi1+*)*)(''&&%%#!(BFDk=;4,#661GO &,-,&  !('PollkkjilT>n^]qp@BAAGP[`\QE=RWYt:6%5447=EKLG>LNIr4*+**)(''&%$&*17LNDe7"266:e,0.*"  (&"9nllkkjiig>n^]qq@IS]c]RF?;NPXWs;557;AHMLF>61.ENEv=*+**)'&&(.6=B?8AE=[:4@Bn^]ot[d^SG@>=>=<:NYWs>620.ANEkC)+)*,2;BC@80'"5C>=<9KZXwRQOG>74232110.>NHdH(-18?ED@6.'$#"!3C=JX"*0762) #65/MK 6M_lomllkkji!f>m^]px?BBAA@?>=5,(%#$#!-BAC^"(/5750'553;_ 2L_oprommlkkj#ijko8>m^]o{ACBA@??BHPUTV_TI46M5433210//>SQbjA;4,('&'&&%%$#"(@B=m(!'/5:85+#/643e -J[nrsqonnmlk%lmnf[G#=m^]o}ABABFLSWVNG>HYQN56,5432138@FKWRXi+*)*)(''&&%%$#!%?B;d?9;81)!*64/Z5  *FYmrurpponnm=lmnnog\H. =m^]o~CHPWZWNG@<;:GZP}Q5665447=EJMI@n^]oS[WOGA>=>=<:GZOwT457;BHNLH?72.1JLLj/*)('&%%',3:@=BI@XS3/.9KUguwvtsrqqpopqqrj^J3"  >n^]oJGB@?>>=<:FZPpY=GMQNG>620/0JLHu6*('(,2:?C?:1'4B>=;9DZWmePME>7425110//HMDv=(*-19?DC?6.'#".B@Bb %5QcuwzxvuutsrsttumaM7( BIRc^md9754332110/.CMCjG6>DFC;3,'$##!(@A;n)#3Mat{||xwwvvuutuvvocN8)!"%#!;n^]nCBB@@?BGOTVRMZVii2605433211/.-AQOeWDA91*'&%&%%$##!%@A7b3"2H`q|~}{yxwwvvuvvwxocO;-!"&),+)&$" UUgr46054331027=CMYSdZ+*)((''&&%%$##!#=A:X<1D^n}|{{zzyxwxxyyqdP=/&'.2430-+*(&##;m^]mHRWZXPHA><;;>UUdx56(4336=<;>UUby6557Whx~~}}|{{||}}uhT?4-/;CEC?=<:64405<:m^]lEBA@A@?>>=<;=TU]y>=;:AGM_b]wC96544332110/.1ILGx5&-;LfuzkXE;9@S[`[WSSQPLIHL1;m^]kFBB@@?BGNSVTMXYUx>56654332110/..GI@fQbt{m\H=:DZejfa\[YXVUSU.;m_]jFACGMTWWSJD>;QXT}C5665433210.,.9Pbq|}n[I@?Ianqoifeba^ZXZH<;;:OXSI4665421/07H[r~o]J@BMjx{sokjhgcf_';m_]jTVNGB?>=>=<;:MYQM454227GXp~p^KBCPp}xwurqknf":m_]iGBA@A@?>>=<;:KYPN18EUm|s`LCFRw~{xtpm9m_^iHBBAA@?>>=<;9HULm]i|tbNEGU}{u9m_^hHBBAA@?>=;9:CVjyucOEIW:m_^hIBBA@><=CRczwdPEHW9l_^gH@??DQaxxfRFGV9l^\eKP_vzgREGUƷ 8lbiy{gSEBRݿ6~jSD?J 6kTB8>ȿ7lTB1,ɹ6nU?)U6pU@" ж@%ڿt8mk@#+E$"fc=2,QjHɿƤhչ2/뻾kKŽȥkֻ21mMǿʧn׽22n Oͨpؿ35n Rϩr37o Uѫu39o WӬ!w3;p Yծ#z35%ҩoP/()-48:;7. ϥiH& %,/22.'͡cA#&)*'! ʝ]9 ! ǙV1   ǗQ*  Q* schismtracker-20250313/sys/macosx/Schism_Tracker.app/Contents/Resources/moduleIcon.icns000066400000000000000000001613751476471630300311600ustar00rootroot00000000000000icnsics#H??????????????????????????is32 Ƽ żѱ о˶ ׽ҥp{٣ğΛ˧ٺԶҥͻТȦ򵮫򩨭ɧ ļ ůҲ ˷ ӧ۸ԴѶвŸÿڵݹѾﻹɼ¹ɪ Ƽ Žұ о˶ ׽ҥq|٤ŠϜ˧ٻշҦμѣȨ򶯫󪩭ɧs8mkghhhhi@KfLfLeL>LiLgLgLgLhLhLhLgNj7K ICN#il32 ɾBǝۢܕ0ѭ}˄uǼĩѶʷ<ͻ|w9ˮz_`̸h`{?ɰ3ᰣ⿑7ڞ}֨nh;߿ɫ:ӿʤ鰢Ψ:z▀d?ͻѵ>µ>ĎƘ╆죓w>µ³ʮ¢<ŪֵƗ=ÿᓅrmC˷ٸAܳC tpDӫ@٤@b]@ۯEĴ5UUUɾBѧݱܧο0Ѻ˒º¶Ūей<Ҿy9ˮ̽?ͷ3ͮ7⾩;οӺ:ү鿭е:ý噊㤒x?Ͼӹ>Ŀȹ>ݴӺ⺭¶>¶ij<ɾҳ=߷ʣCŸƱAƶC ѥD@௡@wr@߷Eƀó5UUUɾBǝۢܖ0Ѯ}˄vǽĩѶ˷<ͼ|w9ˮ{aa̸jb{?ɰ3ⲥ7ڠթpj;ʫ:ˤ鱢Ω:z◁f?ͻѶ>ö>Őƚ◇줔y>¶´ʯ£<ūֶǘ=ᕇupC˸ٹAܴC vrDԭ@٥@c^@۰EĴ5UUUl8mk*<<<<<<<<<<<;2 (778998999;:9:987777+ 36777667777677666796' ich#Hih32ǃU^UROGADHƾ؀ƼkփڝhvWiԁe~ƺx˟dlƶƌǁüʾλ ļƱ!ɵā!ƺɅ"߼ʵӢ#tkgmuyƲe`i]`%÷ia\^w%ڶΩqshaU ր̿%%Ƌ{Љxl3¥xyugo3%ꐟtϲȇ}m3%θԾ3%ꬒȔŤ͛v3%LjoݼktZɪ3%ܱßcЂ|mi^ɪ3%ҞæĦ3%ʾȫ3%⦯轋ᝲӰ3%rÿ朁ʙ{sڱ3%‹uߒrҭ3%ɽٽƮҾɺ˫3%ӴɯĬ߮ά3§ܡ} ӈsq׳3±y ykuԱ3%ɑ۫ʬ3%Źɬ3ӳ3%݋u|sܶ3%{nu׳3%ì̭3˻˭3%n~cֱ3  qn\hٱ3%ه~hа3  ͸Բ3̀ ´U3UUUU^UROGADHځր ƾƼۘۮ}m{Փzǻ˦u}ŽʖǁĽʾμ ļƱ!ɵā!ʺɂ"׸#ukmoxyƲ`÷%λU%IJ%ʼ%ֳװ3%ԧ3%뷼Ҭ3%Ͻô3%뻢եͳҬ3%òѕ訄ȔnȪ3%˩ȯyՓ}ztɪ3%ӥéĦ3̀ȫ3%ҳ±ƳҰ3%Ѯ澮ܻҨױ3%ղΰ߿淮åѭ3%ʸƽ˫3%˼»ά3%аܿݰճ3߹ ⨪ұ3%Գĵɬ3%÷ɬ3ʿӳ3%趥ڶ3%窭Գ3%ӿ˭3˭3%윁vֱ3 冂p}ر3%ޔyа3  ͹Բ3̀ ´U3UUUU^UROGADHƾƼlքڝiwXjԂf~ƺy˟dlƶƍǁļʾλ ļƱ!ɵā!ƺɅ"߽ʵԣ#tkgmvyƲgbj_`%÷kd^`x%۷ΪsujcU%%%Ǎ}ыzn3æz|wjp3%꒡vϳɉ~o3%ιԾ3%ꭓȕƥΛw3%ȉpݽlt[ɪ3%ܲàdЃ}nj_ɪ3%ҟĦĦ3%ʾȫ3%⨰辍៳Ӱ3%t枃˛}vٱ3%Œxtҭ3%ɽپǯҾɺ˫3%ӴʰĬ߯ά3ĩܢ ԉus׳3ò| {mwԱ3%ɒ۬ʬ3%Źɬ3ӳ3%ݍw}uܶ3%}pw׳3%ĭ̭3˻˭3%pdֱ3 rp\iر3%ڇ~iа3  ͸Բ3̀ ´U3UUUh8mk    TD mU uRwSwSwRwRwSwRwRwRw5w www w w w!w!w!w w!w!w!w!w!w"w#w#w!w!w!w!w!w!w w w w wwx aߞ #@RUVWWVWWVVVVWXWVVVWWWVVUUVY[ZVK/ it32o߂݀ ր߂ڀ¿ހہ ҀÿZ߀݀ځ¿%ں܀ڀ ȫԾр¾-}߀ ޭ\7Bځ Ѣ>?ѧww$׬))˭7ڵ؀ >8@̀'¥/ځ0>հk[ymĚ@¾ȿ$qhTrپֹjfkAp%Ot.lNȺA»ʾ$?ۡ67vlmyeGxϯK{KuJµ@ÿʾ$@Ǧtm ϐp'ϲN˼Y=HdZ?ɻʾA~F:}ʚhhʀ 1x?ʣ˞ʽEDӂCa/P4GC@H5+5q9964ʽFǪȣλɯʽ Ł7þſʽĀ΀ʽǀĀȀ*ÿʽJüʽЁ;ĽʽԂӀ ʂ"žʽ؁׀Հʽۃۀۂ"ûʽ߂ قĽʽׁ ۀƽʽ䲠Ⱡ 㲠௝܀ޮרŃʽ$ZZYYYY YYXXހ݀WWUTƿ~uk> ʾ$ρހ#ֿzrcaeccdZ\losɽނ%~wrmkiijkmpruy~ނĻ~yvsr twz|~߀&Ľ}{ujjw|}wnp~,߁%ǿmiDDjmiFGn#ýv9zl4ws9wj4٥(bZ~i3__Wxf3`+½WmDgUTfCbS&XuY{qUTlSogRäހڀڂ-ĹcQYja]OU{a\赢絢-浡ᰝˡŃ2Jv1|Hw[[ZZZZZZZZ%YYXXUT\YV\USQUсЁ&Ǥ ہĽ ߀ƿ݀܁»õа݀å|[U٦[ZـѠXWUT حߠäS`GӓWlȯ"DIcZʤ!ŃXށߋ 6E涠뷠"I| 3Ȥ+\L{dށMIEOϽ:/9}@@@@*X&HDAIȤށݳӁӄܯȤހހɤ ߀ʤ ƀ ͤ߿ӳζű׳ŵϤwWaځު]\ԧV^wommZYҤ JK"ԵWDZD;]W_jtğ?C:Ԥ5ҶP?Ǣ]漅y7Ψ`ԫ=ݠ?ͨ|s6֤oÀ_mPlœ`Ґgѱݠ?gO|hդ3WV΋zSܶx7ʦ_湁u7{}r6פ2ٔhƾaݵkPl_kOhhN{hؤ3oΌޫ|޻gih_gheceeפ7S⦠̲Ir]gvϿ_s]g}rlZbvpդ2Dǐo2S]꺨ʀʁ3R2PԤ2Ksezfc`gVM;V]]]]\\fb_f`][bҤἺ仸ѤФ½ҤſӤ صκ֤  ـ 殫^\˚ZYפ  δD;D;٤ Ćy7~s7٤ nPijO}iؤhkgdff֤  t^htn[cxrҤïπ­­̂ ̂3S2QФ__^^^^^^^^ ^^^^]]gd`gb^\bͤ߁翹žˤ ýʤ ľʤڶ̸ˤ 篣DDǗBBͤ ϱ##Ϥ wepp`zoϤX3vUTw2oSϤSTQP|PxPϤ cDPna]AMh_ΤŪéɀ©Ɂê¨ȁêǁ 7 5̤DDCCCC CCCC CCCCBBOJGPKGDLʤށށށ݁鹵ſɤſʤˤ¿Ϥ߂݀ ր߂܀Հ¿ހہ ҀÿZ߀݀ځ¿%܀ڀ р¾-̘߀ iEPځ ơLRď$96Eں؀͹-"NIǸ'պ?)ځ0ϦO}jōyŧN¾ȿ$΁yi~ͅxyS%`=\ßO»ʾ$?FE͇}~}#Y^YX¾Nÿʾ$@ϙ̌4Ɩ\qKVuhM½ʾAώTKʰ%uuʙ?R«˽ʽERӍQp=^BVRNVC9!C|HGDBʽFǯȨξɲʽ Ł7þſʽĀ΀ʽǀĀȀ*ÿʽJüʽЁ;ĽʽԂӀ ʂ"žʽ؁׀Հʽۃۀۂ"ûʽ߂ قĽʽ݁ ۀƽʽ́ Ȁ܀ƹʽ$䓓ᒒ ᑑޑހ݀ܐԎƿ~uow ʾ$؁ހ#zrlieccdjllosɽ ނ%~wrmkiijkmpruy~ނĻ~yvsr twz|~߀&Ľ}{|},߁%ǿ}}#ýywyw٥?ww+½&äрсЁ-˿Ūxw锔哓㓓 哓㓓%㒒ޑύƐ ف؁&Ǵ ہĽ %ƿ݀܁»݀þۨʾـĶ ~ȃǾ}ʀÂ}{(Ԃʪǯ{ͧë{y)ךϛNJ͚ʼn)١Κϙ *ѪҠարր*lj׳xȿܷyx 디ꔔ암锔{$ϕԐٖ߁ ݂,ɷ)Ŀ )¿» ߁ ܀ ہJSonpo ҮRXК.zHQuhgeKQäanG֞fy/Ƽ#SMU^i)."Ƥ)%DՆqј׵Tրպ'*ƺzgvǤ]ڀISfAdҩTטZ*_>]ʤUe܏Ήud`_ϨTޓdJ*jqs^ZXɤ8C⦞5yP]oʦTCZ*ýpMWthʤ0טfށߤCXԼּ1Z*AȤ9kZrށ\XS]ϿH<#GNNNN8g3)WSOWȤށݸցքݵȤހހɤ ߀ ʤ ƀ ͤ2Ϥ׻եށĕլãҤ ؊"Ґ΄}̓ѝĀ}Ԥ5ҍĖְ϶{п̀ŀ¦{֤۴ׯԠԌη⺛ƀդ3Ւ̾ڲثԙ՝̶綠繐ŀ—Ԥzx޾ڱӺڥ˨ɴҗĀĞӤ8߼ƀʩ޻x̍؀̗ÀƮxҤ2纖٦ܙƎ}镕͈рФ3Ф2Ф3ФӀ¿ҤۯȀ!ƹźԤ֠ԸȄ~Ǔƃ¹}}֤2Ӏҍϭ̴{̻ҬDZ{{פ2齝ԖϞыʵԞ̋ؤ3ڳβҜʴٝפď·ހ٥ʨȴ㧕ȧդ2ٸx̍ڀځyʌxԤ2҉岧ᛗō}ꕔ񕕂蛗’Ҥ!ѤФ½ҤſӤ ĺ֤  ـ ŕϾפ  ф~Ŀ}٤ ݲж{{٤ ܢՌؤߟ֤*訕̩Ҥ߀܂ ݂y΍xФ 훘ƕͤ žˤ ýʤ ľʤ ¸ˤ ӮSX̻QUͤ 0Ĺ#0#Ϥ ߉sЗn|ϤiBie@aϤfbdc^^Ϥ |R^rvO[ymΤπρ΁́EB̤RQQQQQ QQQQ QQQQPP_YU^ZVRZʤ鿻ſɤſʤˤ¿Ϥ߂݀ ր߁ ׀Ԁހہ ҀÿZ߀݀ځ¿%ڻ܀ڀ ȭԾр¾-~߀ ޮ\7Bځ ѣ>@ѩxw$ح*)̯7ڶ؀ ?9À'æ0ځ0?ձl\zmĚA¾ȿ$riUrٿֺlglBq%Pv/nOȺB»ʾ$?ۣ77wmnzfHxϰL|LwK¶Aÿʾ$@ȧun ϐq(ϳO˽Z=Ie[@ɻʾAG:~ʛhiʁ 2z@ʥˠʽEEӂCb0Q5HCAI6,6q::75ʽFǪȣλɯʽ Ł7þſʽĀ΀ʽǀĀȀ*ÿʽJüʽЁ;ĽʽԂӀ ʂ"žʽ؁׀Հʽۃۀۂ"ûʽ߂ قĽʽׁ ۀƽʽ䳢ⳡ 㳡౟܀ޯתƃʽ$\\[[[[ [[Z[ހ݀ZZXVƿ~ukA ʾ$ρހ#zrdbeccdZ\losɽ ނ%~wrmkiijkmpruy~ ނĻ~yvsr twz|~ހ$Ľ}{ullw|}xoq~,߁%ǿokGHjokIIo#ýw⯣DE巳ղn;CfYYW=?äTaGԔXmɰ#E=HФP\"Ƥڬ%7udw֩GրǡۡơiZsjǤUׇOFV3wWʐGǂMͰ٠Oq0jOɤUXvvޭ}iQSRǏG{W<]dgLwM~sKɤ86❕̯(aCOobG6JòY?Id[ʤ"ƄYށߍ 6F渡빡#J~ 4Ȥ,]M|eށNIFOϽ;/:~AAAA+Y'IEBIȤށݳӁӄܯȤހހɤ ߀ʤ ƀ ͤ2ԵηƲ״ŶϤzZcځެ__ԩZ`yoop\\Ҥ NN#նZȳG=_YalvġBE=Ԥ5ӷSBȣ_澇{:ΪaխAݢBͪ~t:֤qĂapSnŖaғiҲݢBiQjդ3YZύ}?fimkÓa݋qYBu{|eggԤڸ94ަՆLmu`jxaVdBɹn\dztӤ8Ùg\۬@ޖ5U_麩ɀFdBƊ5TҤ2gd|RpWgdbhXO=X____NqIBd_^dФ3׼ܹڄ߹Ф2Ф3Фͷ΀˯˴ŧŶҤ6Ĉ[aԴssqqӦ__ϥգ^]][ԤnyϝoH=_Ya¬F=F=֤2ݳBVܷz:ʧa溄x:}~t:פ2ږkǿcݶnSn“anRjjQ}jؤ3rϏޭ}޼ilj’aikgeggפU⨡ͳLހu`ixau`iuo\exrդ2FȒr5V`껪ʀʁ6U5TԤ2Oug|iebiXO>X````__idbhc_]dҤύ伹ѤФ½ҤſӤ طκ֤  ـ 毬`_˜\\פ  ϶G=F=٤ ň{:u:٤ pSklQkؤkmhghh֤ v`jvp]fztҤŰïπïï͂ ï®͂­6V5TФaa`a`a`a`a `a`a``jebid`^eͤ翺žˤ ýʤ ľʤڷ̸ˤ 豣EEǙCCͤ г$$Ϥ xfqp`{pϤY4wVUx2pTϤTURQ}PyPϤ dDQob^BNi`ΤƬĪɀêɁūêɁūéȁé 8 6̤DDCCCC CCCC CCCCCBPKHPLHEMʤ߁߁ށ݁鹶ſɤſʤˤ¿Ϥt8mk@    E   9  ,n  %6|  *>Y .EZ 1I[ 3KZ 3LZ 3LZ 3M[ 3M[ 3M[ 3M\ 3M\ 3M\ 3M\ 3M\ 3M\ 3M\ 3M[ 3M[ 3M[ 3M[ 3MZ 3MZ 3M[ 3M[ 3M[ 3MK 3M-3M% 3MF 3M) 3M23M:% 3MC+ 3MH. 3MM23MO43MP53MP6 3MQ53MP53MP5 3MP5 3MQ5 3MQ5 3MR5 3MS6!3MS6!3MS6!3MS6!3MS6!3MS6!3MS6!3MR5 3MR5 3MR5 3MR5 3MR5 3MS6!3MS6!3MS6!3MS6!3MS6!3MR6!3MR5 3M[5 3MZ5 3MZ5 3MZ5 3M[5 3M\5 3M]5 3M^6!3M_6!3M_6!3M_6!3M^6!3M^63M\43M\43M\43M]43M^43M_43M`43Ma43Ma43M`43M^43M]43M\43M[43MZ43MZ43MQ43MR43MS43MS43MS43MS43MS43MS43MS43MR43MR43MR43MR33MN33MN33LM33LM33KL31IJ1.EF. *>B*  %6I[itz~xl^J6%  ,;IU]beffghhhiijjgggggggggggggggghhhhiijjjjiihhhhgghhhhiijjjgggggggggfgggggjklmmnnjjhe`WJ<,   ,6>EIKLLMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMNNNNNNMMLJF?7,  %*.133333344444444444444443333333344444444443333333344444444443333333333333444444442/*%    schismtracker-20250313/sys/macosx/audio.c000066400000000000000000000335641476471630300202200ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // Mac OS X CoreAudio driver #include "headers.h" #include "charset.h" #include "mt.h" #include "mem.h" #include "str.h" #include "backend/audio.h" #include #include #include #if MAC_OS_X_VERSION_MAX_ALLOWED <= 1050 # include #endif // AudioObject APIs were added in 10.4 #if (MAC_OS_X_VERSION_MIN_REQUIRED < 1040) || \ (!defined(AUDIO_UNIT_VERSION) || ((AUDIO_UNIT_VERSION + 0) < 1040)) # define USE_AUDIODEVICE_APIS 1 #endif // Audio Component APIs were refactored out of Component // Manager in 10.6; for older versions we can simply // #define the new symbols to the old ones #if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060) || \ (!defined(AUDIO_UNIT_VERSION) || ((AUDIO_UNIT_VERSION + 0) < 1060)) # define USE_COMPONENT_MANAGER_APIS 1 #endif #ifdef USE_COMPONENT_MANAGER_APIS typedef struct ComponentDescription AudioComponentDescription; typedef Component AudioComponent; typedef AudioUnit AudioComponentInstance; # define AudioComponentInstanceNew OpenAComponent # define AudioComponentInstanceDispose CloseComponent # define AudioComponentFindNext FindNextComponent #endif struct schism_audio_device { // The callback and the protecting mutex void (*callback)(uint8_t *stream, int len); mt_mutex_t *mutex; int paused; // what to pass to memset() to generate silence. // this is 0x80 for 8-bit audio, and 0 for everything else uint8_t silence; // audio unit AudioComponentInstance au; // The buffer that the callback fills void *buffer; uint32_t buffer_offset; uint32_t buffer_size; }; /* ---------------------------------------------------------- */ /* drivers */ static const char *drivers[] = { "coreaudio", }; static int macosx_audio_driver_count() { return ARRAY_SIZE(drivers); } static const char *macosx_audio_driver_name(int i) { if (i >= ARRAY_SIZE(drivers) || i < 0) return NULL; return drivers[i]; } /* ------------------------------------------------------------------------ */ // Our local "cache" for audio devices; stores the ID as well as a UTF-8 name. static struct { AudioDeviceID id; char *name; } *devices = NULL; static uint32_t devices_size = 0; static void _macosx_audio_free_devices(void) { if (devices) { for (uint32_t i = 0; i < devices_size; i++) free(devices[i].name); free(devices); devices = NULL; devices_size = 0; } } static char *_macosx_cfstring_to_utf8(CFStringRef cfstr) { size_t len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), kCFStringEncodingUTF8); char *buf = mem_alloc(len + 1); if (!CFStringGetCString(cfstr, buf, len + 1, kCFStringEncodingUTF8)) { free(buf); return NULL; } // nul terminate buf[len] = '\0'; return buf; } static uint32_t macosx_audio_device_count(void) { OSStatus result; UInt32 size; #ifndef USE_AUDIODEVICE_APIS AudioObjectPropertyAddress addr = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, // FIXME this was renamed to kAudioObjectPropertyElementMain in 12.0 kAudioObjectPropertyElementMaster }; #endif _macosx_audio_free_devices(); #ifdef USE_AUDIODEVICE_APIS result = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &size, NULL); #else result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, NULL, &size); #endif if (result != kAudioHardwareNoError) return 0; devices_size = size / sizeof(AudioDeviceID); if (devices_size <= 0) return 0; AudioDeviceID device_ids[devices_size]; #ifdef USE_AUDIODEVICE_APIS // I bought a property in Egypt and what they do for you is they give you the property result = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &size, device_ids); #else result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, NULL, &size, device_ids); #endif if (result != kAudioHardwareNoError) return 0; devices = mem_calloc(devices_size, sizeof(*devices)); // final count of all of the devices uint32_t c = 0; for (uint32_t i = 0; i < devices_size; i++) { // XXX why is this so damn paranoid? char *ptr = NULL; int usable = 0; CFIndex len = 0; { #ifdef USE_AUDIODEVICE_APIS result = AudioDeviceGetPropertyInfo(device_ids[i], 0, 0, kAudioDevicePropertyStreamConfiguration, &size, NULL); #else addr.mScope = kAudioDevicePropertyScopeOutput; addr.mSelector = kAudioDevicePropertyStreamConfiguration; result = AudioObjectGetPropertyDataSize(device_ids[i], &addr, 0, NULL, &size); #endif if (result != noErr) continue; AudioBufferList *buflist = (AudioBufferList *)mem_alloc(size); #ifdef USE_AUDIODEVICE_APIS result = AudioDeviceGetProperty(device_ids[i], 0, 0, kAudioDevicePropertyStreamConfiguration, &size, buflist); #else result = AudioObjectGetPropertyData(device_ids[i], &addr, 0, NULL, &size, buflist); #endif if (result == noErr) { UInt32 j; for (j = 0; j < buflist->mNumberBuffers; j++) { if (buflist->mBuffers[j].mNumberChannels > 0) { usable = 1; break; } } } free(buflist); } if (!usable) continue; { // Prioritize CFString so we know we're getting UTF-8 CFStringRef cfstr; size = sizeof(cfstr); #ifdef USE_AUDIODEVICE_APIS result = AudioDeviceGetProperty(device_ids[i], 0, 0, kAudioDevicePropertyDeviceNameCFString, &size, &cfstr); #else addr.mSelector = kAudioObjectPropertyName; result = AudioObjectGetPropertyData(device_ids[i], &addr, 0, NULL, &size, &cfstr); #endif if (result == kAudioHardwareNoError) { ptr = _macosx_cfstring_to_utf8(cfstr); CFRelease(cfstr); } #ifdef USE_AUDIODEVICE_APIS else { // Fallback to just receiving it as a C string // XXX: what encoding is this in? result = AudioDeviceGetPropertyInfo(device_ids[i], 0, 0, kAudioDevicePropertyDeviceName, &size, NULL); if (result != kAudioHardwareNoError) continue; ptr = mem_alloc(size + 1); result = AudioDeviceGetProperty(device_ids[i], 0, 0, kAudioDevicePropertyDeviceName, &size, ptr); if (result != kAudioHardwareNoError) { free(ptr); continue; } ptr[size] = '\0'; } #endif } if (usable) { // Trim any whitespace off the end of the name str_rtrim(ptr); usable = (strlen(ptr) > 0); } if (usable) { devices[c].id = device_ids[i]; devices[c].name = ptr; c++; } else { free(ptr); } } // keep the real size, don't care if we allocated more. devices_size = c; return devices_size; } static const char *macosx_audio_device_name(uint32_t i) { if (i < 0 || i >= devices_size) return NULL; return devices[i].name; } /* ---------------------------------------------------------- */ static int macosx_audio_init_driver(const char *driver) { int fnd = 0; for (int i = 0; i < ARRAY_SIZE(drivers); i++) { if (!strcmp(drivers[i], driver)) { fnd = 1; break; } } if (!fnd) return -1; (void)macosx_audio_device_count(); return 0; } static void macosx_audio_quit_driver(void) { _macosx_audio_free_devices(); } /* -------------------------------------------------------- */ static OSStatus macosx_audio_callback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { schism_audio_device_t *dev = (schism_audio_device_t *)inRefCon; void *ptr; uint32_t i; if (dev->paused) { for (i = 0; i < ioData->mNumberBuffers; i++) memset(ioData->mBuffers[i].mData, dev->silence, ioData->mBuffers[i].mDataByteSize); return 0; } for (i = 0; i < ioData->mNumberBuffers; i++) { uint32_t remaining = ioData->mBuffers[i].mDataByteSize; void *ptr = ioData->mBuffers[i].mData; while (remaining > 0) { if (dev->buffer_offset >= dev->buffer_size) { memset(dev->buffer, dev->silence, dev->buffer_size); mt_mutex_lock(dev->mutex); dev->callback(dev->buffer, dev->buffer_size); mt_mutex_unlock(dev->mutex); dev->buffer_offset = 0; } uint32_t len = dev->buffer_size - dev->buffer_offset; if (len > remaining) len = remaining; memcpy(ptr, (char *)dev->buffer + dev->buffer_offset, len); ptr = (char *)ptr + len; remaining -= len; dev->buffer_offset += len; } } return 0; } // nonzero on success static schism_audio_device_t *macosx_audio_open_device(uint32_t id, const schism_audio_spec_t *desired, schism_audio_spec_t *obtained) { schism_audio_device_t *dev = mem_calloc(1, sizeof(schism_audio_device_t)); dev->mutex = mt_mutex_create(); if (!dev->mutex) { free(dev); return NULL; } OSStatus result = noErr; // build our audio stream AudioStreamBasicDescription desired_ca = { .mFormatID = kAudioFormatLinearPCM, .mFormatFlags = #ifdef WORDS_BIGENDIAN // data is native endian kLinearPCMFormatFlagIsBigEndian | #endif ((desired->bits != 8) ? kLinearPCMFormatFlagIsSignedInteger : 0) | kLinearPCMFormatFlagIsPacked, .mChannelsPerFrame = desired->channels, .mSampleRate = desired->freq, .mBitsPerChannel = desired->bits, .mFramesPerPacket = 1, }; desired_ca.mBytesPerFrame = desired_ca.mBitsPerChannel * desired_ca.mChannelsPerFrame / 8; desired_ca.mBytesPerPacket = desired_ca.mBytesPerFrame * desired_ca.mFramesPerPacket; AudioComponentDescription desc = { .componentType = kAudioUnitType_Output, .componentSubType = kAudioUnitSubType_DefaultOutput, .componentManufacturer = kAudioUnitManufacturer_Apple, }; AudioComponent comp = AudioComponentFindNext(NULL, &desc); if (!comp) { mt_mutex_delete(dev->mutex); free(dev); return NULL; } result = AudioComponentInstanceNew(comp, &dev->au); if (result != noErr) { mt_mutex_delete(dev->mutex); free(dev); return NULL; } result = AudioUnitInitialize(dev->au); if (result != noErr) { mt_mutex_delete(dev->mutex); free(dev); return NULL; } // If a device is provided, try to find it in the list and set the current device // If we can't find it, punt if (id != AUDIO_BACKEND_DEFAULT) { result = AudioUnitSetProperty(dev->au, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &devices[id].id, sizeof(devices[id].id)); if (result != noErr) { mt_mutex_delete(dev->mutex); free(dev); return NULL; } } // Set the input format of the audio unit. result = AudioUnitSetProperty(dev->au, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &desired_ca, sizeof(desired_ca)); if (result != noErr) { mt_mutex_delete(dev->mutex); free(dev); return NULL; } struct AURenderCallbackStruct callback = { .inputProc = macosx_audio_callback, .inputProcRefCon = dev, }; result = AudioUnitSetProperty(dev->au, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callback, sizeof(callback)); if (result != noErr) { mt_mutex_delete(dev->mutex); free(dev); return NULL; } dev->buffer_offset = dev->buffer_size = desired->samples * desired->channels * (desired->bits / 8); dev->buffer = mem_alloc(dev->buffer_size); dev->callback = desired->callback; dev->silence = (desired->bits == 8) ? 0x80 : 0; result = AudioOutputUnitStart(dev->au); if (result != noErr) { mt_mutex_delete(dev->mutex); free(dev->buffer); free(dev); return NULL; } memcpy(obtained, desired, sizeof(schism_audio_spec_t)); return dev; } static void macosx_audio_close_device(schism_audio_device_t *dev) { OSStatus result = noErr; if (!dev) return; result = AudioOutputUnitStop(dev->au); if (result != noErr) return; // Remove the callback struct AURenderCallbackStruct callback = {0}; result = AudioUnitSetProperty(dev->au, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callback, sizeof(callback)); if (result != noErr) return; result = AudioComponentInstanceDispose(dev->au); if (result != noErr) return; free(dev->buffer); mt_mutex_delete(dev->mutex); free(dev); } static void macosx_audio_lock_device(schism_audio_device_t *dev) { if (!dev) return; mt_mutex_lock(dev->mutex); } static void macosx_audio_unlock_device(schism_audio_device_t *dev) { if (!dev) return; mt_mutex_unlock(dev->mutex); } static void macosx_audio_pause_device(schism_audio_device_t *dev, int paused) { if (!dev) return; mt_mutex_lock(dev->mutex); dev->paused = !!paused; mt_mutex_unlock(dev->mutex); } ////////////////////////////////////////////////////////////////////////////// // init functions (stubs) static int macosx_audio_init(void) { return 1; } static void macosx_audio_quit(void) { // dont do anything } ////////////////////////////////////////////////////////////////////////////// const schism_audio_backend_t schism_audio_backend_macosx = { .init = macosx_audio_init, .quit = macosx_audio_quit, .driver_count = macosx_audio_driver_count, .driver_name = macosx_audio_driver_name, .device_count = macosx_audio_device_count, .device_name = macosx_audio_device_name, .init_driver = macosx_audio_init_driver, .quit_driver = macosx_audio_quit_driver, .open_device = macosx_audio_open_device, .close_device = macosx_audio_close_device, .lock_device = macosx_audio_lock_device, .unlock_device = macosx_audio_unlock_device, .pause_device = macosx_audio_pause_device, }; schismtracker-20250313/sys/macosx/clippy.m000066400000000000000000000054521476471630300204240ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "backend/clippy.h" #include "loadso.h" #include "charset.h" #include "mem.h" #include "util.h" #import static char *macosx_clippy_get_clipboard(void); static int macosx_clippy_have_selection(void) { return 0; } static int macosx_clippy_have_clipboard(void) { char *text = macosx_clippy_get_clipboard(); int res = (text && text[0]); free(text); return res; } static void macosx_clippy_set_selection(const char *text) { // nonexistent } static void macosx_clippy_set_clipboard(const char *text) { NSString *contents = [NSString stringWithUTF8String: text]; NSPasteboard *pb = [NSPasteboard generalPasteboard]; [pb declareTypes:[NSArray arrayWithObject: NSStringPboardType] owner:nil]; [pb setString:contents forType:NSStringPboardType]; } static char *macosx_clippy_get_selection(void) { // doesn't exist, ever return str_dup(""); } static char *macosx_clippy_get_clipboard(void) { NSPasteboard *pb = [NSPasteboard generalPasteboard]; NSString *type = [pb availableTypeFromArray:[NSArray arrayWithObject: NSStringPboardType]]; if (type != nil) { NSString *contents = [pb stringForType: type]; if (contents != nil) { const char *po = [contents UTF8String]; if (po) return str_dup(po); } } return str_dup(""); } static int macosx_clippy_init(void) { // nothing to do return 1; } static void macosx_clippy_quit(void) { // nothing to do } const schism_clippy_backend_t schism_clippy_backend_macosx = { .init = macosx_clippy_init, .quit = macosx_clippy_quit, .have_selection = macosx_clippy_have_selection, .get_selection = macosx_clippy_get_selection, .set_selection = macosx_clippy_set_selection, .have_clipboard = macosx_clippy_have_clipboard, .get_clipboard = macosx_clippy_get_clipboard, .set_clipboard = macosx_clippy_set_clipboard, }; schismtracker-20250313/sys/macosx/dmoz.m000066400000000000000000000035361476471630300200760ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "backend/dmoz.h" #include "loadso.h" #include "charset.h" #include "mem.h" #include "util.h" #import #import // FIXME we need to also get the Application Support directory through // NSSearchPathForDirectoriesInDomains() static char *macosx_dmoz_get_exe_path(void) { NSBundle *bundle = [NSBundle mainBundle]; /* this returns the exedir for non-bundled and the resourceDir for bundled */ const char *base = [[bundle resourcePath] fileSystemRepresentation]; if (base) return str_dup(base); return NULL; } static int macosx_dmoz_init(void) { // do nothing return 1; } static void macosx_dmoz_quit(void) { // do nothing } const schism_dmoz_backend_t schism_dmoz_backend_macosx = { .init = macosx_dmoz_init, .quit = macosx_dmoz_quit, .get_exe_path = macosx_dmoz_get_exe_path, }; schismtracker-20250313/sys/macosx/ibook-support.c000066400000000000000000000051351476471630300217250ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "util.h" #include #include #include #include #include #define kMyDriversKeyboardClassName "AppleADBKeyboard" #define kfnSwitchError 200 #define kfnAppleMode 0 #define kfntheOtherMode 1 #ifndef kIOHIDFKeyModeKey #define kIOHIDFKeyModeKey "HIDFKeyMode" #endif int macosx_ibook_fnswitch(int setting) { kern_return_t kr; mach_port_t mp; io_service_t so; /*io_name_t sn;*/ io_connect_t dp; io_iterator_t it; CFDictionaryRef classToMatch; /*CFNumberRef fnMode;*/ unsigned int res, dummy; kr = IOMasterPort(bootstrap_port, &mp); if (kr != KERN_SUCCESS) return -1; classToMatch = IOServiceMatching(kIOHIDSystemClass); if (classToMatch == NULL) { return -1; } kr = IOServiceGetMatchingServices(mp, classToMatch, &it); if (kr != KERN_SUCCESS) return -1; so = IOIteratorNext(it); IOObjectRelease(it); if (!so) return -1; kr = IOServiceOpen(so, mach_task_self(), kIOHIDParamConnectType, &dp); if (kr != KERN_SUCCESS) return -1; kr = IOHIDGetParameter(dp, CFSTR(kIOHIDFKeyModeKey), sizeof(res), &res, (IOByteCount *) &dummy); if (kr != KERN_SUCCESS) { IOServiceClose(dp); return -1; } if (setting == kfnAppleMode || setting == kfntheOtherMode) { dummy = setting; kr = IOHIDSetParameter(dp, CFSTR(kIOHIDFKeyModeKey), &dummy, sizeof(dummy)); if (kr != KERN_SUCCESS) { IOServiceClose(dp); return -1; } } IOServiceClose(dp); /* old setting... */ return res; } schismtracker-20250313/sys/macosx/macosx-sdlmain.m000066400000000000000000000435051476471630300220440ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* wee.... this is used to do some schism-on-macosx customization and get access to cocoa stuff pruned up some here :) -mrsb */ /* SDLMain.m - main entry point for our Cocoa-ized SDL app Initial Version: Darrell Walisser Non-NIB-Code & other changes: Max Horn Feel free to customize this file to suit your needs */ #include "headers.h" #include "it.h" #include "events.h" #include "osdefs.h" #include "mem.h" #include #import /* for MAXPATHLEN */ #import #import // main.c extern char *initial_song; // These constants were renamed in the Sierra SDK #if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 # define NSEventModifierFlagOption NSAlternateKeyMask # define NSEventModifierFlagControl NSControlKeyMask # define NSEventModifierFlagCommand NSCommandKeyMask # define NSEventModifierFlagShift NSShiftKeyMask # define NSEventModifierFlagFunction NSFunctionKeyMask # define NSEventTypeKeyDown NSKeyDown #endif /* Portions of CPS.h --------------------------------------------------- */ typedef struct CPSProcessSerNum { UInt32 lo; UInt32 hi; } CPSProcessSerNum; extern OSErr CPSGetCurrentProcess(CPSProcessSerNum *psn); extern OSErr CPSEnableForegroundOperation(CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); extern OSErr CPSSetProcessName(CPSProcessSerNum *psn, char *processname); extern OSErr CPSSetFrontProcess(CPSProcessSerNum *psn); /* --------------------------------------------------------------------- */ static int macosx_did_finderlaunch = 0; static int macosx_launched = 0; // FIXME this sucks #define KEQ_FN(n) [NSString stringWithFormat:@"%C", (unichar)(NSF##n##FunctionKey)] @interface NSApplication(OtherMacOSXExtensions) -(void)setAppleMenu:(NSMenu*)m; @end @interface SchismTrackerDelegate : NSObject - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename; @end @interface SchismTracker : NSApplication @end @implementation SchismTracker /* Invoked from the Quit menu item */ - (void)terminate:(id)sender { /* Post a QUIT event */ schism_event_t event; event.type = SCHISM_QUIT; events_push_event(&event); } - (void)_menu_callback:(id)sender { schism_event_t e; NSString *px; const char *po; /* little hack to ignore keydown events here */ NSEvent* event = [NSApp currentEvent]; if (!event) return; if ([event type] == NSEventTypeKeyDown) return; px = [sender representedObject]; po = [px UTF8String]; if (po) { e.type = SCHISM_EVENT_NATIVE_SCRIPT; e.script.which = str_dup(po); events_push_event(&e); } } @end /* @implementation SchismTracker */ /* The main class of the application, the application's delegate */ @implementation SchismTrackerDelegate - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { if (!filename) return NO; const char *po = [filename UTF8String]; if (!po) return NO; // If we already launched, add the event. if (macosx_launched) { schism_event_t e; e.type = SCHISM_EVENT_NATIVE_OPEN; e.open.file = str_dup(po); events_push_event(&e); } else { initial_song = str_dup(po); } return YES; } /* other interesting ones: - (BOOL)application:(NSApplication *)theApplication printFile:(NSString *)filename - (BOOL)applicationOpenUntitledFile:(NSApplication *)theApplication */ /* Set the working directory to the .app's parent directory */ - (void) setupWorkingDirectory:(BOOL)shouldChdir { if (shouldChdir) { char parentdir[MAXPATHLEN]; CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); if (CFURLGetFileSystemRepresentation(url2, true, (unsigned char *) parentdir, MAXPATHLEN)) { assert ( chdir (parentdir) == 0 ); /* chdir to the binary app's parent */ } CFRelease(url); CFRelease(url2); } } /* Called when the internal event loop has just started running */ - (void) applicationDidFinishLaunching: (NSNotification *) note { /* Set the working directory to the .app's parent directory */ [self setupWorkingDirectory: (BOOL)macosx_did_finderlaunch]; macosx_launched = 1; /* If we launched from the Finder we have extra arguments that we * don't care about that will trip up the regular main function. */ exit(schism_main(macosx_did_finderlaunch ? 1 : *_NSGetArgc(), *_NSGetArgv())); } @end /* @implementation SchismTrackerDelegate */ /* ------------------------------------------------------- */ static void setApplicationMenu(void) { /* this really needs to be cleaned up --paper */ NSMenu *appleMenu; NSMenu *otherMenu; NSMenuItem *menuItem; appleMenu = [[NSMenu alloc] initWithTitle:@""]; /* Add menu items */ menuItem = (NSMenuItem*)[appleMenu addItemWithTitle:@"Help" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(1)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"help"]; [appleMenu addItem:[NSMenuItem separatorItem]]; menuItem = (NSMenuItem*)[appleMenu addItemWithTitle:@"View Patterns" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(2)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"pattern"]; menuItem = (NSMenuItem*)[appleMenu addItemWithTitle:@"Orders/Panning" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(11)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"orders"]; menuItem = (NSMenuItem*)[appleMenu addItemWithTitle:@"Variables" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(12)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"variables"]; menuItem = (NSMenuItem*)[appleMenu addItemWithTitle:@"Message Editor" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(9)]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagShift]; [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:(NSEventModifierFlagOption|NSEventModifierFlagCommand)]; [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; [appleMenu addItem:[NSMenuItem separatorItem]]; [appleMenu addItemWithTitle:@"Quit Schism Tracker" action:@selector(terminate:) keyEquivalent:@"q"]; /* Put menu into the menubar */ menuItem = (NSMenuItem*)[[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [menuItem setSubmenu:appleMenu]; [[NSApp mainMenu] addItem:menuItem]; /* File menu */ otherMenu = [[NSMenu alloc] initWithTitle:@"File"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"New..." action:@selector(_menu_callback:) keyEquivalent:@"n"]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagControl]; [menuItem setRepresentedObject: @"new"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Load..." action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(9)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"load"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Save Current" action:@selector(_menu_callback:) keyEquivalent:@"s"]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagControl]; [menuItem setRepresentedObject: @"save"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Save As..." action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(10)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"save_as"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Export..." action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(10)]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagShift]; [menuItem setRepresentedObject: @"export_song"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Message Log" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(11)]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagFunction|NSEventModifierFlagControl]; [menuItem setRepresentedObject: @"logviewer"]; menuItem = (NSMenuItem*)[[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [menuItem setSubmenu:otherMenu]; [[NSApp mainMenu] addItem:menuItem]; /* Playback menu */ otherMenu = [[NSMenu alloc] initWithTitle:@"Playback"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Show Infopage" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(5)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"info"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Play Song" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(5)]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagControl]; [menuItem setRepresentedObject: @"play"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Play Pattern" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(6)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"play_pattern"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Play from Order" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(6)]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagShift]; [menuItem setRepresentedObject: @"play_order"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Play from Mark/Cursor" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(7)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"play_mark"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Stop" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(8)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"stop"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Calculate Length" action:@selector(_menu_callback:) keyEquivalent:@"p"]; [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagFunction|NSEventModifierFlagControl)]; [menuItem setRepresentedObject: @"calc_length"]; menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [menuItem setSubmenu:otherMenu]; [[NSApp mainMenu] addItem:menuItem]; /* Sample menu */ otherMenu = [[NSMenu alloc] initWithTitle:@"Samples"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Sample List" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(3)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"sample_page"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Sample Library" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(3)]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagShift]; [menuItem setRepresentedObject: @"sample_library"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Reload Soundcard" action:@selector(_menu_callback:) keyEquivalent:@"g"]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagControl]; [menuItem setRepresentedObject: @"init_sound"]; menuItem = (NSMenuItem*)[[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [menuItem setSubmenu:otherMenu]; [[NSApp mainMenu] addItem:menuItem]; /* Instrument menu */ otherMenu = [[NSMenu alloc] initWithTitle:@"Instruments"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Instrument List" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(4)]; [menuItem setKeyEquivalentModifierMask:0]; [menuItem setRepresentedObject: @"inst_page"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Instrument Library" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(4)]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagShift]; [menuItem setRepresentedObject: @"inst_library"]; menuItem = (NSMenuItem*)[[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [menuItem setSubmenu:otherMenu]; [[NSApp mainMenu] addItem:menuItem]; /* Settings menu */ otherMenu = [[NSMenu alloc] initWithTitle:@"Settings"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Preferences" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(5)]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagShift]; [menuItem setRepresentedObject: @"preferences"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"MIDI Configuration" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(1)]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagShift]; [menuItem setRepresentedObject: @"midi_config"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Palette Editor" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(12)]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagControl]; [menuItem setRepresentedObject: @"palette_page"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Font Editor" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(12)]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagShift]; [menuItem setRepresentedObject: @"font_editor"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"System Configuration" action:@selector(_menu_callback:) keyEquivalent:KEQ_FN(1)]; [menuItem setKeyEquivalentModifierMask:NSEventModifierFlagControl]; [menuItem setRepresentedObject: @"system_config"]; menuItem = (NSMenuItem*)[otherMenu addItemWithTitle:@"Toggle Fullscreen" action:@selector(_menu_callback:) keyEquivalent:@"\r"]; [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagControl|NSEventModifierFlagOption)]; [menuItem setRepresentedObject: @"fullscreen"]; menuItem = (NSMenuItem*)[[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [menuItem setSubmenu:otherMenu]; [[NSApp mainMenu] addItem:menuItem]; /* Tell the application object that this is now the application menu */ [NSApp setAppleMenu:appleMenu]; /* Finally give up our references to the objects */ [appleMenu release]; [menuItem release]; } /* Create a window menu */ static void setupWindowMenu(void) { NSMenu *windowMenu; NSMenuItem *windowMenuItem; windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; /* "Minimize" item */ { NSMenuItem *menuItem; menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; [windowMenu addItem:menuItem]; /* DON'T WORRY! I'M GONNA CUSHION OUR FALL WITH THIS WATER BUCKET! */ [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]; } #ifdef main # undef main #endif /* Main entry point to executable - should *not* be SDL_main! */ int main (int argc, char **argv) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; /* Copy the arguments into a global variable -- this * is passed if we are launched by double-clicking */ macosx_did_finderlaunch = (argc >= 2 && strncmp (argv[1], "-psn", 4) == 0); { CPSProcessSerNum psn; /* Tell the dock about us */ if (!CPSGetCurrentProcess(&psn)) { if (!macosx_did_finderlaunch) CPSSetProcessName(&psn, "Schism Tracker"); if (!CPSEnableForegroundOperation(&psn, 0x03, 0x3C, 0x2C, 0x1103)) if (!CPSSetFrontProcess(&psn)) [SchismTracker sharedApplication]; } } /* Ensure the application object is initialised */ [SchismTracker sharedApplication]; /* Set up the menubar */ [NSApp setMainMenu:[[NSMenu alloc] init]]; setApplicationMenu(); setupWindowMenu(); /* Create the app delegate */ SchismTrackerDelegate *delegate = [[SchismTrackerDelegate alloc] init]; [NSApp setDelegate: (id)delegate]; /* stupid cast to silence compiler */ /* Start the main event loop */ [NSApp run]; [delegate release]; [pool release]; return 0; } /* ---------------------------------------------------------------- */ int macosx_get_key_repeat(int *pdelay, int *prate) { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; if (!defaults) return 0; int delay = [defaults integerForKey:@"InitialKeyRepeat"]; int rate = [defaults integerForKey:@"KeyRepeat"]; // apparently these will never be zero. if (!delay || delay < 0 || !rate || rate < 0) return 0; // According to this Apple Discussions thread, these // values are milliseconds divided by 15: // // https://discussions.apple.com/thread/1316947 *pdelay = delay * 15; *prate = rate * 15; return 1; } char *macosx_get_application_support_dir(void) { NSArray* strings = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, true); if ([strings count] < 1) return NULL; NSString *path = [strings objectAtIndex: 0]; if (!path) return NULL; return str_dup([path UTF8String]); } schismtracker-20250313/sys/macosx/midi-macosx.c000066400000000000000000000137531476471630300213270ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "midi.h" #include "mem.h" #include "util.h" #include #include #include static MIDIClientRef client = 0; static MIDIPortRef portIn = 0; static MIDIPortRef portOut = 0; struct macosx_midi { MIDIEndpointRef ep; unsigned char packet[1024]; MIDIPacketList *pl; MIDIPacket *x; int mark; /* used in polling */ }; static void readProc(const MIDIPacketList *np, SCHISM_UNUSED void *rc, void *crc) { struct midi_port *p; struct macosx_midi *m; MIDIPacket *x; uint32_t i; p = (struct midi_port *)crc; m = (struct macosx_midi *)p->userdata; x = (MIDIPacket*)&np->packet[0]; for (i = 0; i < np->numPackets; i++) { midi_received_cb(p, x->data, x->length); x = MIDIPacketNext(x); } } static void _macosx_send(struct midi_port *p, const unsigned char *data, uint32_t len, uint32_t delay) { struct macosx_midi *m = (struct macosx_midi *)p->userdata; if (!m->x) m->x = MIDIPacketListInit(m->pl); /* msec to nsec? */ m->x = MIDIPacketListAdd(m->pl, sizeof(m->packet), m->x, (MIDITimeStamp)AudioConvertNanosToHostTime( AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()) + (1000000 * delay)), len, data); } static void _macosx_drain(struct midi_port *p) { struct macosx_midi *m; m = (struct macosx_midi *)p->userdata; if (m->x) { MIDISend(portOut, m->ep, m->pl); m->x = NULL; } } /* lifted from portmidi */ static void get_ep_name(MIDIEndpointRef ep, char* buf, size_t buf_len) { MIDIEntityRef entity; MIDIDeviceRef device; CFStringRef endpointName = NULL, deviceName = NULL, fullName = NULL; /* get the entity and device info */ MIDIEndpointGetEntity(ep, &entity); MIDIEntityGetDevice(entity, &device); /* create the nicely formatted 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 as UTF-8 */ CFStringGetCString(fullName, buf, buf_len, kCFStringEncodingUTF8); /* clean up */ if (fullName && !deviceName) CFRelease(fullName); } 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 struct midi_port* _macosx_find_port(struct midi_provider* p, MIDIEndpointRef ep, int inout) { struct macosx_midi* m; struct midi_port* ptr = NULL; while (midi_port_foreach(p, &ptr)) { m = (struct macosx_midi*)ptr->userdata; if (m->ep == ep && ptr->iocap == inout) return ptr; } return NULL; } static void _macosx_add_port(struct midi_provider *p, MIDIEndpointRef ep, int inout) { struct macosx_midi* m; struct midi_port* ptr; ptr = NULL; while (midi_port_foreach(p, &ptr)) { m = (struct macosx_midi*)ptr->userdata; if (m->ep == ep && ptr->iocap == inout) { m->mark = 1; return; } } m = mem_alloc(sizeof(struct macosx_midi)); m->ep = ep; m->mark = 1; /* 55 is the maximum size for the MIDI page */ char name[55]; get_ep_name(m->ep, name, 55); name[54] = '\0'; midi_port_register(p, inout, name, m, 1); } static void _macosx_poll(struct midi_provider *p) { struct midi_port* ptr; struct macosx_midi* m; MIDIEndpointRef ep; ItemCount i; ItemCount num_out, num_in; num_out = MIDIGetNumberOfDestinations(); num_in = MIDIGetNumberOfSources(); if (!num_out || !num_in) return; ptr = NULL; while (midi_port_foreach(p, &ptr)) { m = (struct macosx_midi*)ptr->userdata; m->mark = 0; } for (i = 0; i < num_out; i++) { ep = MIDIGetDestination(i); if (!ep) continue; _macosx_add_port(p, ep, MIDI_OUTPUT); } for (i = 0; i < num_in; i++) { ep = MIDIGetSource(i); if (!ep) continue; _macosx_add_port(p, ep, MIDI_INPUT); } ptr = NULL; while (midi_port_foreach(p, &ptr)) { m = (struct macosx_midi*)ptr->userdata; if (!m->mark) midi_port_unregister(ptr->num); } } int macosx_midi_setup(void) { static const struct midi_driver driver = { .flags = MIDI_PORT_CAN_SCHEDULE, .poll = _macosx_poll, .thread = NULL, .enable = _macosx_start, .disable = _macosx_stop, .send = _macosx_send, .drain = _macosx_drain, }; if (MIDIClientCreate(CFSTR("Schism Tracker"), NULL, NULL, &client) != noErr) return 0; if (MIDIInputPortCreate(client, CFSTR("Input port"), readProc, NULL, &portIn) != noErr) return 0; if (MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut) != noErr) return 0; if (!midi_provider_register("Mac OS X", &driver)) return 0; return 1; } schismtracker-20250313/sys/macosx/osdefs.c000066400000000000000000000272341476471630300203770ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "it.h" #include "osdefs.h" #include "events.h" #include "song.h" #include "page.h" #include "widget.h" #include "mem.h" #include #include #include #include /* --------------------------------------------------------- */ /* Handle Caps Lock and other oddities; Cocoa doesn't send very * specific key events so we have to do this manually through the * HID library. Annoying. */ // #define SCHISM_MACOSXHID_DEBUG struct hid_item_data { IOHIDDeviceInterface **interface; // hm struct { IOHIDElementCookie caps_lock; } cookies; }; struct hid_item_node { struct hid_item_data data; struct hid_item_node *next; }; static struct hid_item_node *hid_item_list = NULL; static void hid_item_insert(struct hid_item_data *data) { struct hid_item_node *node = mem_alloc(sizeof(*node)); memcpy(&node->data, data, sizeof(node->data)); node->next = hid_item_list; hid_item_list = node; } static void hid_item_free(void) { struct hid_item_node* temp; while (hid_item_list) { temp = hid_item_list; hid_item_list = hid_item_list->next; if (temp->data.interface) { (*temp->data.interface)->close(temp->data.interface); (*temp->data.interface)->Release(temp->data.interface); } free(temp); } } static CFDictionaryRef create_hid_device(uint32_t page, uint32_t usage) { CFMutableDictionaryRef dict = IOServiceMatching(kIOHIDDeviceKey); if (dict) { CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); if (number) { CFDictionarySetValue(dict, CFSTR(kIOHIDPrimaryUsagePageKey), number); CFRelease(number); number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); if (number) { CFDictionarySetValue(dict, CFSTR(kIOHIDPrimaryUsageKey), number); CFRelease(number); CFRetain(dict); return dict; } } CFRelease(dict); } return NULL; } static void quit_hid_callback(void) { hid_item_free(); } static void process_hid_element(const void *value, struct hid_item_data *data) { if (CFGetTypeID(value) != CFDictionaryGetTypeID()) { #ifdef SCHISM_MACOSXHID_DEBUG printf("MACOSX HID HID element isn't a dictionary?\n"); #endif return; // err } CFDictionaryRef dict = (CFDictionaryRef)value; uint32_t element_type = 0, usage_page = 0, usage = 0, cookie = 0; { CFTypeRef type_ref; // element type type_ref = CFDictionaryGetValue(dict, CFSTR(kIOHIDElementTypeKey)); if (type_ref) CFNumberGetValue(type_ref, kCFNumberIntType, &element_type); // usage page type_ref = CFDictionaryGetValue(dict, CFSTR(kIOHIDElementUsagePageKey)); if (type_ref) CFNumberGetValue(type_ref, kCFNumberIntType, &usage_page); // usage page type_ref = CFDictionaryGetValue(dict, CFSTR(kIOHIDElementUsageKey)); if (type_ref) CFNumberGetValue(type_ref, kCFNumberIntType, &usage); // cookie! (mmm delicious...) type_ref = CFDictionaryGetValue(dict, CFSTR(kIOHIDElementCookieKey)); if (type_ref) CFNumberGetValue(type_ref, kCFNumberIntType, &cookie); } #ifdef SCHISM_MACOSXHID_DEBUG printf("got element with type %x, usage page %x, usage %x, and cookie %x\n", element_type, usage_page, usage, cookie); #endif switch (element_type) { // Mac OS X defines these as buttons, which makes sense // considering a keyboard is in essense just a giant // basket case of buttons... case kIOHIDElementTypeInput_Button: switch (usage_page) { case kHIDUsage_GD_Keyboard: case kHIDUsage_GD_Keypad: switch (usage) { case kHIDUsage_KeyboardCapsLock: #ifdef SCHISM_MACOSXHID_DEBUG puts("MACOSX HID: found caps lock key"); #endif data->cookies.caps_lock = (IOHIDElementCookie)cookie; break; default: // don't care about other cookies for now break; } break; default: // should never ever happen break; } break; case kIOHIDElementTypeCollection: { // ugh CFTypeRef element = CFDictionaryGetValue(dict, CFSTR(kIOHIDElementKey)); if (!element || CFGetTypeID(element) != CFArrayGetTypeID()) break; // what? for (int i = 0; i < CFArrayGetCount(element); i++) process_hid_element(CFArrayGetValueAtIndex(element, i), data); break; } default: break; } } static void add_hid_device(io_object_t hid_device) { struct hid_item_data item = {0}; CFMutableDictionaryRef hid_properties = NULL; kern_return_t result = IORegistryEntryCreateCFProperties(hid_device, &hid_properties, kCFAllocatorDefault, kNilOptions); if (result != KERN_SUCCESS) goto fail; // fill in the interface { SInt32 score = 0; // dunno what this is for // receive the interface (this is like IUnknown * on windows or something) IOCFPlugInInterface **plugin_interface = NULL; result = IOCreatePlugInInterfaceForService(hid_device, kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin_interface, &score); if (result != KERN_SUCCESS) { #ifdef SCHISM_MACOSXHID_DEBUG printf("MACOSX HID IOCreatePlugInInterfaceForService failed: %d\n", result); #endif goto fail; } // yapfest to get the real interface we want if ((*plugin_interface)->QueryInterface(plugin_interface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (void *)&(item.interface)) == S_OK) { // and NOW open it, the option bits are unused I think ? result = (*item.interface)->open(item.interface, 0); if (result != kIOReturnSuccess) { #ifdef SCHISM_MACOSXHID_DEBUG printf("MACOSX HID opening interface failed failed: %d\n", result); #endif IODestroyPlugInInterface((IOCFPlugInInterface **)item.interface); goto fail; } } else { IODestroyPlugInInterface(plugin_interface); goto fail; } } // now fill in the cookies { // search for the caps lock element... CFTypeRef element = CFDictionaryGetValue(hid_properties, CFSTR(kIOHIDElementKey)); if (!element || CFGetTypeID(element) != CFArrayGetTypeID()) { #ifdef SCHISM_MACOSXHID_DEBUG printf("MACOSX HID no elements?\n"); #endif goto fail; // whoops } for (int i = 0; i < CFArrayGetCount(element); i++) process_hid_element(CFArrayGetValueAtIndex(element, i), &item); } hid_item_insert(&item); fail: if (hid_properties) CFRelease(hid_properties); } static void add_hid_devices(mach_port_t master_port, uint32_t page, uint32_t usage) { IOReturn res; io_iterator_t hid_iterator = 0; CFDictionaryRef device = create_hid_device(page, usage); if (!device) goto end; res = IOServiceGetMatchingServices(master_port, device, &hid_iterator); if (res != kIOReturnSuccess) { #ifdef SCHISM_MACOSXHID_DEBUG printf("MACOSX HID IOServiceGetMatchingServices: %d\n", res); #endif goto end; } if (!hid_iterator) // paranoia at its finest goto end; { // iterate over each object and add it to our list of HID devices io_object_t hid_object = 0; while ((hid_object = IOIteratorNext(hid_iterator))) { #ifdef SCHISM_MACOSXHID_DEBUG printf("MACOSX HID device found...\n"); #endif add_hid_device(hid_object); IOObjectRelease(hid_object); } } end: if (device) CFRelease(device); if (hid_iterator) IOObjectRelease(hid_iterator); } static void init_hid_callback(void) { kern_return_t res; mach_port_t master_port = MACH_PORT_NULL; res = IOMasterPort(MACH_PORT_NULL, &master_port); if (res != kIOReturnSuccess) { #ifdef SCHISM_MACOSXHID_DEBUG printf("MACOSX HID failed to get master port: %d\n", res); #endif goto fail; } add_hid_devices(master_port, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard); add_hid_devices(master_port, kHIDPage_GenericDesktop, kHIDUsage_GD_Keypad); fail: return; } /* --------------------------------------------------------- */ /* this gets stored at startup as the initial value of fnswitch before * we tamper with it, so we can restore it on shutdown */ static int ibook_helper = -1; int macosx_event(schism_event_t *event) { switch (event->type) { case SCHISM_WINDOWEVENT_FOCUS_GAINED: macosx_ibook_fnswitch(1); return 1; case SCHISM_WINDOWEVENT_FOCUS_LOST: macosx_ibook_fnswitch(ibook_helper); return 1; case SCHISM_KEYDOWN: case SCHISM_KEYUP: switch (status.fix_numlock_setting) { case NUMLOCK_GUESS: /* why is this checking for ibook_helper? */ if (ibook_helper != -1) { if (ACTIVE_PAGE.selected_widget > -1 && ACTIVE_PAGE.selected_widget < ACTIVE_PAGE.total_widgets && ACTIVE_PAGE_WIDGET.accept_text) { /* text is more likely? */ event->key.mod |= SCHISM_KEYMOD_NUM; } else { event->key.mod &= ~SCHISM_KEYMOD_NUM; } } /* otherwise honor it */ break; default: /* other cases are handled in schism/main.c */ break; } switch (event->key.scancode) { case SCHISM_SCANCODE_KP_ENTER: /* On portables, the regular Insert key * isn't available. This is equivalent to * pressing Fn-Return, which just so happens * to be a "de facto" Insert in mac land. * However, on external keyboards this causes * a real keypad enter to get eaten by this * function as well. IMO it's more important * for now that portable users can actually * have an Insert key. * * - paper */ event->key.sym = SCHISM_KEYSYM_INSERT; break; default: break; }; return 1; default: break; } return 1; } void macosx_sysexit(void) { /* return back to default */ if (ibook_helper != -1) macosx_ibook_fnswitch(ibook_helper); quit_hid_callback(); } void macosx_sysinit(SCHISM_UNUSED int *pargc, SCHISM_UNUSED char ***pargv) { /* macosx_ibook_fnswitch only sets the value if it's one of (0, 1) */ ibook_helper = macosx_ibook_fnswitch(-1); init_hid_callback(); } void macosx_get_modkey(schism_keymod_t *mk) { int caps_pressed = 0; struct hid_item_node *node; for (node = hid_item_list; node; node = node->next) { IOHIDEventStruct event; #ifdef SCHISM_MACOSXHID_DEBUG printf("MACOSX HID: checking for mods on HID item\n"); #endif IOReturn res = (*node->data.interface)->getElementValue(node->data.interface, node->data.cookies.caps_lock, &event); if (res == kIOReturnSuccess) caps_pressed |= !!(event.value); #ifdef SCHISM_MACOSXHID_DEBUG printf("MACOSX HID getElementValue: %d\n", res); #endif } #ifdef SCHISM_MACOSXHID_DEBUG printf("was caps pressed?: %s\n", caps_pressed ? "yes" : "no"); #endif (*mk) &= ~SCHISM_KEYMOD_CAPS_PRESSED; if (caps_pressed) (*mk) |= SCHISM_KEYMOD_CAPS_PRESSED; } void macosx_show_message_box(const char *title, const char *text) { CFStringRef cfs_title, cfs_text; CFOptionFlags flags; cfs_title = CFStringCreateWithCString(kCFAllocatorDefault, title, kCFStringEncodingUTF8); if (cfs_title) { cfs_text = CFStringCreateWithCString(kCFAllocatorDefault, text, kCFStringEncodingUTF8); if (cfs_text) { CFUserNotificationDisplayAlert(0, kCFUserNotificationNoteAlertLevel, NULL, NULL, NULL, cfs_title, cfs_text, NULL, NULL, NULL, &flags); CFRelease(cfs_text); } CFRelease(cfs_title); } } schismtracker-20250313/sys/os2/000077500000000000000000000000001476471630300161515ustar00rootroot00000000000000schismtracker-20250313/sys/os2/osdefs.c000066400000000000000000000111101476471630300175720ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "osdefs.h" #include "mem.h" #include "log.h" #define INCL_DOS #define INCL_PM #define INCL_WINDIALOGS #include int os2_mkdir(const char *path, SCHISM_UNUSED mode_t mode) { USHORT rc; char *sys; sys = charset_iconv_easy(path, CHARSET_UTF8, CHARSET_DOSCP); if (!sys) return -1; rc = DosMkDir(sys, 0UL); free(sys); return rc ? -1 : 0; } FILE *os2_fopen(const char *path, const char *rw) { FILE *fp; char *sys; sys = charset_iconv_easy(path, CHARSET_UTF8, CHARSET_DOSCP); if (!sys) return NULL; fp = fopen(sys, rw); free(sys); return fp; } int os2_stat(const char* path, struct stat* st) { int rc; char *sys; sys = charset_iconv_easy(path, CHARSET_UTF8, CHARSET_DOSCP); if (!sys) return -1; rc = stat(path, st); free(sys); return rc; } /* ------------------------------------------------------------------------ */ // helper function to retrieve profile data (must free result) // // For context: OS/2 INI files aren't actually only text, and can contain some // stupid arbitrary data like structures etc. // We don't use those, but prepare for them anyway... static inline void *os2_get_profile_data(PSZ pszAppName, PSZ pszKeyWord, ULONG *psz) { void *res; BOOL brc; brc = PrfQueryProfileSize(HINI_PROFILE, pszAppName, pszKeyWord, psz); if (!brc) return NULL; res = mem_alloc(*psz); brc = PrfQueryProfileData(HINI_PROFILE, pszAppName, pszKeyWord, res, psz); if (!brc) return NULL; return res; } static inline int convert_str_to_long(char *str, size_t num, LONG *result) { size_t i; /* Fail on empty string */ if (!str || !*str) return 0; *result = 0; for (i = 0; str[i] && i < num; i++) { if (str[i] < '0' || str[i] > '9') return 0; /* put the fries in the bag */ *result *= 10; *result += str[i] - '0'; } return 1; } static inline int os2_get_profile_integer(PSZ pszAppName, PSZ pszKeyWord, LONG *pint) { ULONG sz; char *ptr; ptr = os2_get_profile_data(pszAppName, pszKeyWord, &sz); if (!ptr) return 0; int suckies = convert_str_to_long(ptr, sz, pint); free(ptr); return !!suckies; } int os2_get_key_repeat(int *pdelay, int *prate) { // This is just an estimate; there's no official documentation on what these // values actually *are*, and changing them in the control panel doesn't seem to // actually do anything. can someone who knows what they're doing clue me in // onto what the translations are really supposed to be? if (pdelay) { LONG delay; if (!os2_get_profile_integer("PM_ControlPanel", "KeyRepeatDelay", &delay)) return 0; // docs say range 0..890 if (delay < 0 || delay > 890) return 0; // at value 89, we get a delay of ~500ms. *pdelay = (delay * 500L / 89L); } if (prate) { LONG rate; if (!os2_get_profile_integer("PM_ControlPanel", "KeyRepeatRate", &rate)) return 0; // docs say range 1..20 if (rate < 1 || rate > 20) return 0; // At a value of 18 we get an average of 50ms per repeat; // apparently 1 is the slowest while 20 is the fastest, // so I inverted the rate here. Hopefully this one is // actually linear instead of inverse (AHEM WINDOWS) *prate = (20L - rate) * 25L; } return 1; } void os2_show_message_box(const char *title, const char *text) { char *sys_title, *sys_text; sys_title = charset_iconv_easy(title, CHARSET_UTF8, CHARSET_DOSCP); if (!sys_title) return; sys_text = charset_iconv_easy(text, CHARSET_UTF8, CHARSET_DOSCP); if (!sys_text) { free(sys_title); return; } // untested: WinMessageBox(HWND_DESKTOP, HWND_DESKTOP, sys_text, sys_title, 0L, MB_OK | MB_INFORMATION); free(sys_title); free(sys_text); }schismtracker-20250313/sys/oss/000077500000000000000000000000001476471630300162525ustar00rootroot00000000000000schismtracker-20250313/sys/oss/midi-oss.c000066400000000000000000000103761476471630300201510ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include "headers.h" #include "midi.h" #include "util.h" #include #include #include #include #ifdef HAVE_POLL_H # include #elif HAVE_SYS_POLL_H # include #endif #include /* this is stupid; oss doesn't have a concept of ports... */ #define MAX_OSS_MIDI 64 #define MAX_MIDI_PORTS (MAX_OSS_MIDI + 2) static int opened[MAX_MIDI_PORTS]; static void _oss_send(struct midi_port *p, const unsigned char *data, uint32_t len, SCHISM_UNUSED uint32_t delay) { int fd, r, n; fd = opened[ n = INT_SHAPED_PTR(p->userdata) ]; if (fd < 0) return; while (len > 0) { r = write(fd, data, len); if (r < -1 && errno == EINTR) continue; if (r < 1) { /* err, can't happen? */ (void)close(opened[n]); opened[n] = -1; p->userdata = PTR_SHAPED_INT(-1); p->io = 0; /* failure! */ return; } data += r; len -= r; } } static int _oss_start_stop(SCHISM_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; while (!p->cancelled) { ptr = NULL; j = 0; while (midi_port_foreach(p, &ptr)) { i = INT_SHAPED_PTR(ptr->userdata); if (i == -1) continue; /* err... */ if (!(ptr->io & MIDI_INPUT)) continue; pfd[j].fd = i; pfd[j].events = POLLIN; pfd[j].revents = 0; /* RH 5 bug */ j++; } if (!j || poll(pfd, j, -1) < 1) { sleep(1); continue; } for (i = 0; i < j; i++) { if (!(pfd[i].revents & POLLIN)) continue; do { r = read(pfd[i].fd, midi_buf, sizeof(midi_buf)); } while (r == -1 && errno == EINTR); if (r > 0) { ptr = src = NULL; while (midi_port_foreach(p, &ptr)) { if (INT_SHAPED_PTR(ptr->userdata) == pfd[i].fd) { src = ptr; } } midi_received_cb(src, midi_buf, r); } } } /* stupid gcc */ return 0; } static int _tryopen(int n, const char *name, struct midi_provider *_oss_provider) { int io; char *ptr; if (opened[n] != -1) return 1; if ((opened[n] = open(name, O_RDWR|O_NOCTTY|O_NONBLOCK)) != -1) { io = MIDI_INPUT | MIDI_OUTPUT; } else if ((opened[n] = open(name, O_RDONLY|O_NOCTTY|O_NONBLOCK)) != -1) { io = MIDI_INPUT; } else if ((opened[n] = open(name, O_WRONLY|O_NOCTTY|O_NONBLOCK)) != -1) { io = MIDI_OUTPUT; } else { return 2; } ptr = NULL; if (asprintf(&ptr, " %-16s (OSS)", name) == -1) return 3; midi_port_register(_oss_provider, io, ptr, PTR_SHAPED_INT((long)n), 0); free(ptr); return 0; } static void _oss_poll(struct midi_provider *_oss_provider) { char sbuf[64]; int i; _tryopen(0, "/dev/midi", _oss_provider); for (i = 0; i < MAX_OSS_MIDI; i++) { sprintf(sbuf, "/dev/midi%d", i); if (!_tryopen(i + 1, sbuf, _oss_provider)) continue; sprintf(sbuf, "/dev/midi%02d", i); if (!_tryopen(i + 1, sbuf, _oss_provider)) continue; } } int oss_midi_setup(void) { static const struct midi_driver driver = { .flags = 0, .poll = _oss_poll, .thread = _oss_thread, .enable = _oss_start_stop, .disable = _oss_start_stop, .send = _oss_send, }; int i; for (i = 0; i < MAX_MIDI_PORTS; i++) opened[i] = -1; if (!midi_provider_register("OSS", &driver)) return 0; return 1; } schismtracker-20250313/sys/posix/000077500000000000000000000000001476471630300166105ustar00rootroot00000000000000schismtracker-20250313/sys/posix/osdefs.c000066400000000000000000000030651476471630300202430ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "osdefs.h" /* ugh */ #if defined(HAVE_EXECL) && defined(HAVE_FORK) && !defined(SCHISM_WIN32) #include int posix_run_hook(const char *dir, const char *name, const char *maybe_arg) { char *tmp; int st; switch (fork()) { case -1: return 0; case 0: if (chdir(dir) == -1) _exit(255); if (asprintf(&tmp, "./%s", name) < 0) _exit(255); execl(tmp, tmp, maybe_arg, (char *)NULL); free(tmp); _exit(255); }; while (wait(&st) == -1); if (WIFEXITED(st) && WEXITSTATUS(st) == 0) return 1; return 0; } #endif schismtracker-20250313/sys/posix/schismtracker.1000066400000000000000000000241451476471630300215420ustar00rootroot00000000000000.TH SCHISMTRACKER 1 "Jan 2, 2025" .\" Disable hyphenation, it's awful .nh .SH NAME schismtracker \- tracked music editor based on Impulse Tracker .SH SYNOPSIS \fBschismtracker\fP [\fIoptions\fP] [\fIdirectory\fP] [\fIfile\fP] .SH DESCRIPTION \fBschismtracker\fP is a tracked music module editor that aims to match the look and feel of Impulse Tracker as closely as possible. It can load most common tracker formats, supports saving as IT and S3M, and can also export to WAV and AIFF. .SH OPTIONS .P .TP \fB\-a\fP, \fB\-\-audio\-driver\fP=\fIDRIVER\fP[:\fIDEVICE\fP] Audio device configuration. \fIdriver\fP is the SDL driver to use, e.g. \fIalsa\fP (ALSA), \fIdma\fP or \fIdsp\fP (OSS); \fIdevice\fP is the name of the device itself, for example \fIhw:2\fP or \fI/dev/dsp1\fP. .TP \fB\-v\fP, \fB\-\-video\-driver\fP=\fIDRIVER\fP SDL video driver, such as \fIx11\fP, \fIdga\fP, or \fIfbcon\fP. Note that this is different from the video driver setting within the program, and is unlikely to be useful. .TP \fB\-\-network\fP, \fB\-\-no\-network\fP Enable/disable networking (on by default). Used for MIDI over IP. .TP \fB\-\-classic\fP, \fB\-\-no\-classic\fP Start Schism Tracker in "classic" mode, or don't. This is mostly cosmetic, although it does change the program's behavior slightly in a few places. .TP \fB\-f\fP, \fB\-F\fP, \fB\-\-fullscreen\fP, \fB\-\-no\-fullscreen\fP Enable/disable fullscreen mode at startup. .TP \fB\-p\fP, \fB\-P\fP, \fB\-\-play\fP, \fB\-\-no\-play\fP Start playing after loading song on command line. .TP \fB\-\-diskwrite\fP=\fIFILENAME\fP Render output to a file, and then exit. WAV or AIFF writer is auto-selected based on file extension. Include \fI%c\fP somewhere in the name to write each channel separately. This is meaningless if no initial filename is given. .TP \fB\-\-headless\fP Run in non-interactive mode for automated rendering. Requires both \fB\-\-diskwrite\fP and an input song file to be specified. Useful for batch conversion of songs to audio files. .TP \fB\-\-font\-editor\fP, \fB\-\-no\-font\-editor\fP Run the font editor (itf). This can also be accessed by pressing Shift-F12. .TP \fB\-\-hooks\fP, \fB\-\-no\-hooks\fP Run hooks. Enabled by default. .TP \fB\-\-version\fP Display version information and build date. .TP \fB\-h\fP, \fB\-\-help\fP Print a summary of available options. .P A filename supplied on the command line will be loaded at startup. Additionally, if either a file or directory name is given, the default module, sample, and instrument paths will be set accordingly. .SH USAGE A detailed discussion of how to use Schism Tracker is far beyond the scope of this document, but here is a very brief rundown of the basics. Context-sensitive help can be accessed at any time while running the program by pressing \fBF1\fP. .P The \fBF3\fP key will bring you to the \fIsample list\fP. Press enter here to open a file browser, navigate in the list using the up/down arrow keys, and hit enter again to load a sample. You will likely want to get some samples to work with. You can also "rip" from existing modules; see for example \fIhttp://www.modarchive.org/\fP for a very large selection of modules. (Keep in mind, however, that some authors don't appreciate having their samples ripped!) .P Now that you've loaded a sample, press \fBF2\fP to get to the \fIpattern editor\fP. This is where the majority of the composition takes place. In short, the song is laid out vertically, with each row representing 1/16 note; to play multiple notes simultaneously, they are placed in different channels. The four sub-columns of each channel are the note, sample number, volume, and effect. A list of effects is available in the pattern editor help, but you can safely ignore that column for now. Assuming a US keymap, notes are entered with the keyboard as follows: (Note) C# D# F# G# A# C# D# F# G# A# C# D# | | || | | | || || | | | || | | | || || | | | || | | | | || | | | || || | | | || | | | || || | | | || | | (What you | |S||D| | |G||H||J| | |2||3| | |5||6||7| | |9||0| | type) | '-''-' | '-''-''-' | '-''-' | '-''-''-' | '-''-' | | Z| X| C| V| B| N| M| Q| W| E| R| T| Y| U| I| O| P| '--'--'--'--'--'--'--'--'--'--'--'--'--'--'--'--'--' (Note) C D E F G A B C D E F G A B C D E (Octave 0) (Octave 1) (Octave 2) .\" this .P is for elvis, which gets very confused by the preceding diagram .P The "/" and "*" keys on the numeric keypad change octaves, and the current octave is displayed near the top of the screen. Try typing "qwerty" into the pattern - it will enter an ascending note sequence, and you'll hear the notes as they're entered. (of course, assuming you loaded a sample!) Press \fBF6\fP to play your pattern, and \fBF8\fP to stop. .P Other important keys for the pattern editor include Ins/Del to shift notes up and down within a channel, Shift-Arrows to mark a block, Alt-C/Alt-P to copy and paste, and Alt-U to clear the mark. There are well over a hundred key bindings for the pattern editor; it is well worth the effort to learn them all eventually. .P Now that you have something in your pattern, you'll need to set up an \fIorderlist\fP. Press \fBF11\fP to switch to the orderlist page, and type 0 to add the pattern you created. Now press \fBF5\fP to start playing. The song will begin at the first order, look up the pattern number and play that pattern, then advance to the next order, and so forth. .P Of course, having only one pattern isn't all that interesting, so go back to the pattern editor and press the + key to change to the next pattern. Now you can write another four bars of music and add the new pattern to the orderlist, and the next time you play the song, your two patterns will play in sequence. .P You may wish to give your song a title; press \fBF12\fP and type a name in the box at the top. You can also adjust the tempo and a number of other settings on this page, but for now, most of them are fine at their default values. .P To save your new song, press \fBF10\fP, type a filename, and hit enter. You can load it again later by pressing \fBF9\fP. .P This tutorial has deliberately omitted the \fIinstrument editor\fP (on \fBF4\fP), for the purposes of brevity and simplicity. You may want to experiment with it once you have a feel for how the program works. (Select "instruments" on F12 to enable instrument mode.) .SH HISTORY Storlek began studying Impulse Tracker's design in 2002, noting subtle details of the design and implementation. Posts on the Modplug forums about rewriting Impulse Tracker were met with ridicule and mockery. "It can't be done," they said. .P Schism Tracker v0.031a was released in July 2003, though very little worked at that point, and it was more of a player with primitive editing capabilities. File saving was hard-coded to write to "test.it" in the current directory, and there was no way to load a sample. .P The first version that was more or less usable was 0.15a, from December 2004. .P From 2005 through 2009, Mrs. Brisby did most of the development, and implemented tons of features, including MIDI support, mouse support, and disk writing. .P Storlek "took over" development again in 2009, and incrementally rewrote much of the code through 2015. .P In 2016, Schism Tracker was moved to GitHub under shared maintainership. Since then, many people have contributed improvements and bug fixes to the codebase. .SH FILES .TP ~/.schism/config Program settings, stored in an INI-style format. Most options are accessible from within Schism Tracker's interface, but there are a few "hidden" options. .TP ~/.schism/startup\-hook, ~/.schism/exit\-hook, ~/.schism/diskwriter\-hook Optional files to execute upon certain events. (Must be executable) .TP ~/.schism/fonts/ \fIfont.cfg\fP, and any \fI.itf\fP files found in this directory, are displayed in the file browser of the font editor. .SS Supported file formats .TP MOD Amiga modules (with some obscure variants such as FLT8) .TP 669 Composer 669 / Unis669 .TP MTM MultiTracker .TP S3M Scream Tracker 3 (including Adlib support) .TP XM Fast Tracker 2 .TP IT Impulse Tracker (including old instrument format) .TP MDL Digitrakker 3 .TP IMF Imago Orpheus .TP OKT Amiga Oktalyzer .TP SFX Sound FX .TP MUS Doom engine (percussion missing) .TP FAR Farandole Composer .TP STM Scream Tracker 2 (partial functionality) .TP ULT UltraTracker (partial functionality) .TP S3I Scream Tracker 3 sample .TP WAV Microsoft WAV audio .TP FLAC Xiph.Org Free Lossless Audio Codec audio .TP AIFF Audio IFF (Apple) .TP 8SVX Amiga 8SVX sample .TP ITS Impulse Tracker sample .TP AU Sun/NeXT Audio .TP RAW Headerless sample data .TP PAT Gravis UltraSound patch .TP XI Fast Tracker 2 instrument .TP ITI Impulse Tracker instrument .P Schism Tracker is able to save modules in IT and S3M format, sample data as ITS, S3I, AIFF, AU, WAV, and RAW, and instruments as ITI. Additionally, it can render to WAV and AIFF (optionally writing each channel to a separate file), and can export MID files. .SH AUTHORS Schism Tracker was written by Storlek and Mrs. Brisby, with player code from Modplug by Olivier Lapicque. Based on Impulse Tracker by Jeffrey Lim. .P Additional code and data have been contributed by many others; refer to the file \fIAUTHORS\fP in the source distribution for a more complete list. .P The keyboard diagram in this manual page was adapted from the one used in the documentation for Impulse Tracker, which in turn borrowed it from Scream Tracker 3. .SH COPYRIGHT Copyright \(co 2003-2025 Storlek, Mrs. Brisby et al. Licensed under the GNU GPL <\fIhttp://gnu.org/licenses/gpl.html\fP>. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. .SH BUGS They almost certainly exist. Post on \fIhttps://github.com/schismtracker/schismtracker/issues\fP if you find one. Agitha shares her happiness with benefactors of the insect kingdom. .SH INTERNETS \fIhttp://schismtracker.org/\fP - main website .br \fI#schismtracker\fP on EsperNet - IRC channel .SH SEE ALSO .\" No favoritism: this list is alphabetical, trackers then players .BR chibitracker (1), .BR milkytracker (1), .BR protracker (1), .BR renoise (1), .BR ocp (1), .BR xmp (1) schismtracker-20250313/sys/posix/slurp-mmap.c000066400000000000000000000035301476471630300210520ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include #include #include "slurp.h" static void munmap_slurp_(slurp_t *fp) { (void)munmap((void*)fp->internal.memory.data, fp->internal.memory.length); (void)close(fp->internal.memory.interfaces.mmap.fd); } int slurp_mmap(slurp_t *fp, const char *filename, size_t st) { int fd = open(filename, O_RDONLY); if (fd == -1) return 0; void *addr = mmap(NULL, st, PROT_READ, MAP_SHARED #if defined(MAP_POPULATE) && defined(MAP_NONBLOCK) | MAP_POPULATE | MAP_NONBLOCK #endif #if defined(MAP_NORESERVE) | MAP_NORESERVE #endif , fd, 0); if (addr == MAP_FAILED) { (void)close(fd); return (errno == ENOMEM) ? SLURP_OPEN_FAIL : SLURP_OPEN_IGNORE; } fp->closure = munmap_slurp_; fp->internal.memory.length = st; fp->internal.memory.data = addr; fp->internal.memory.interfaces.mmap.fd = fd; return 1; } schismtracker-20250313/sys/sdl12/000077500000000000000000000000001476471630300163735ustar00rootroot00000000000000schismtracker-20250313/sys/sdl12/audio.c000066400000000000000000000224371476471630300176500ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "mem.h" #include "util.h" #include "backend/audio.h" #include "loadso.h" #include "init.h" struct schism_audio_device { void (*callback)(uint8_t *stream, int len); }; static int (SDLCALL *sdl12_InitSubSystem)(uint32_t flags); static void (SDLCALL *sdl12_QuitSubSystem)(uint32_t flags); static int (SDLCALL *sdl12_OpenAudio)(SDL_AudioSpec *desired, SDL_AudioSpec *obtained); static void (SDLCALL *sdl12_CloseAudio)(void); static void (SDLCALL *sdl12_LockAudio)(void); static void (SDLCALL *sdl12_UnlockAudio)(void); static void (SDLCALL *sdl12_PauseAudio)(int); static void (SDLCALL *sdl12_SetError)(const char *fmt, ...); static char * (SDLCALL *sdl12_GetError)(void); static void (SDLCALL *sdl12_ClearError)(void); static int (SDLCALL *sdl12_AudioInit)(const char *driver); static void (SDLCALL *sdl12_AudioQuit)(void); /* ---------------------------------------------------------- */ /* drivers */ static int sdl12_audio_init_driver(const char *name) { char *orig_drv; { // Calling getenv() with a subsequent setenv() causes // the original pointer to get lost, so we have to // duplicate it. const char *orig_drv_unsafe = getenv("SDL_AUDIODRIVER"); orig_drv = (orig_drv_unsafe) ? str_dup(orig_drv_unsafe) : NULL; } if (name) setenv("SDL_AUDIODRIVER", name, 1); int ret = sdl12_InitSubSystem(SDL_INIT_AUDIO); /* clean up our dirty work, or unset the var */ if (name) { if (orig_drv) { setenv("SDL_AUDIODRIVER", orig_drv, 1); } else { unsetenv("SDL_AUDIODRIVER"); } } if (orig_drv) free(orig_drv); /* forward any error, if any */ return ret; } static void sdl12_audio_quit_driver(void) { sdl12_QuitSubSystem(SDL_INIT_AUDIO); } /* ---------------------------------------------------------- */ /* This method will silently fail for drivers that don't work on startup, * but this can be remedied by simply restarting Schism. */ static struct sdl12_audio_driver_info { const char *driver; int exists; } drivers[] = { /* SDL 1.2 doesn't provide an API for this, so we have to * build this when initializing the audio subsystem. * These in the same order as the bootstrap array in * src/audio/SDL_audio.c to hopefully mimic SDL's original * behavior. */ {"pulse", 0}, // PulseAudio {"alsa", 0}, // ALSA {"sndio", 0}, // OpenBSD sndio {"netbsd", 0}, // NetBSD audio {"openbsd", 0}, // OpenBSD audio (outdated) {"dsp", 0}, // /dev/dsp (OSS) {"dma", 0}, // /dev/dsp DMA audio (OSS) {"qsa-nto", 0}, // QNX NTO audio {"audio", 0}, // Sun Microsystems audio {"AL", 0}, // IRIX DMedia audio {"arts", 0}, // artsc audio (?) {"esd", 0}, // Enlightened Sound Daemon {"nas", 0}, // Network Audio System {"dsound", 0}, // Win32 DirectSound #ifdef SCHISM_WIN32 {"waveout", 0}, // Win32 WaveOut #endif {"paud", 0}, // AIX Paudio {"baudio", 0}, // BeOS {"coreaudio", 0}, // Mac OS X CoreAudio {"sndmgr", 0}, // Mac OS SoundManager 3.0 {"AHI", 0}, // AmigaOS {"dcaudio", 0}, // Dreamcast AICA audio {"nds", 0}, // Nintendo DS Audio #ifndef SCHISM_WIN32 {"waveout", 0}, // Tru64 MME WaveOut #endif {"dart", 0}, // OS/2 Direct Audio RouTines {"epoc", 0}, // EPOC streaming audio {"ums", 0}, // AIX UMS audio // These two are pretty much guaranteed to exist {"disk", 1}, {"dummy", 1}, }; static int sdl12_audio_driver_info_init(void) { int atleast_one_loaded = 0; /* save the last error before we screw with our crap */ const char *cached_err = sdl12_GetError(); for (size_t i = 0; i < ARRAY_SIZE(drivers); i++) { // Clear any error before starting so we // can check for one later sdl12_ClearError(); if (sdl12_AudioInit(drivers[i].driver)) continue; // Make sure there was no error initializing the // driver. (SDL sets the error here to "No available // audio device", but we'll use a more broad test // instead) const char *audio_init_err = sdl12_GetError(); if (audio_init_err && *audio_init_err) continue; // Ok, the driver was bootstrapped successfully, now // punt and save that info. sdl12_AudioQuit(); atleast_one_loaded = drivers[i].exists = 1; } /* restore the last error */ sdl12_SetError(cached_err); return atleast_one_loaded; } static void sdl12_audio_driver_info_quit(void) { // reset for (size_t i = 0; i < ARRAY_SIZE(drivers); i++) drivers[i].exists = 0; } static int sdl12_audio_driver_count(void) { int c = 0; for (size_t i = 0; i < ARRAY_SIZE(drivers); i++) if (drivers[i].exists) c++; return c; } static const char *sdl12_audio_driver_name(int x) { size_t i = 0; for (int c = 0; i < ARRAY_SIZE(drivers); i++) { if (!drivers[i].exists) continue; if (c == x) break; c++; } return (i < ARRAY_SIZE(drivers)) ? drivers[i].driver : NULL; } /* --------------------------------------------------------------- */ /* SDL 1.2 doesn't have a concept of audio devices */ static uint32_t sdl12_audio_device_count(void) { return 0; } static const char *sdl12_audio_device_name(SCHISM_UNUSED uint32_t i) { return NULL; } /* -------------------------------------------------------- */ static void SDLCALL sdl12_dummy_callback(void *userdata, uint8_t *stream, int len) { // call our own callback schism_audio_device_t *dev = userdata; dev->callback(stream, len); } static int sdl12_audio_opened = 0; static schism_audio_device_t *sdl12_audio_open_device(SCHISM_UNUSED uint32_t id, const schism_audio_spec_t *desired, schism_audio_spec_t *obtained) { /* SDL 1.2 only supports opening one device at a time! */ if (sdl12_audio_opened) return NULL; schism_audio_device_t *dev = mem_calloc(1, sizeof(*dev)); dev->callback = desired->callback; //dev->userdata = desired->userdata; SDL_AudioSpec sdl_desired = { .freq = desired->freq, // SDL 1.2 has no support for 32-bit audio at all .format = (desired->bits == 8) ? (AUDIO_U8) : (AUDIO_S16SYS), .channels = desired->channels, .samples = desired->samples, .callback = sdl12_dummy_callback, .userdata = dev, }; SDL_AudioSpec sdl_obtained; if (sdl12_OpenAudio(&sdl_desired, &sdl_obtained)) { free(dev); return NULL; } *obtained = (schism_audio_spec_t){ .freq = sdl_obtained.freq, .bits = sdl_obtained.format & 0xFF, .channels = sdl_obtained.channels, .samples = sdl_obtained.samples, }; sdl12_audio_opened = 1; return dev; } static void sdl12_audio_close_device(schism_audio_device_t *dev) { if (!dev) return; sdl12_CloseAudio(); free(dev); sdl12_audio_opened = 0; } /* lock/unlock/pause */ static void sdl12_audio_lock_device(schism_audio_device_t *dev) { if (!dev) return; sdl12_LockAudio(); } static void sdl12_audio_unlock_device(schism_audio_device_t *dev) { if (!dev) return; sdl12_UnlockAudio(); } static void sdl12_audio_pause_device(schism_audio_device_t *dev, int paused) { if (!dev) return; sdl12_PauseAudio(paused); } ////////////////////////////////////////////////////////////////////////////// // dynamic loading static int sdl12_audio_load_syms(void) { SCHISM_SDL12_SYM(InitSubSystem); SCHISM_SDL12_SYM(QuitSubSystem); SCHISM_SDL12_SYM(OpenAudio); SCHISM_SDL12_SYM(CloseAudio); SCHISM_SDL12_SYM(LockAudio); SCHISM_SDL12_SYM(UnlockAudio); SCHISM_SDL12_SYM(PauseAudio); SCHISM_SDL12_SYM(SetError); SCHISM_SDL12_SYM(GetError); SCHISM_SDL12_SYM(ClearError); SCHISM_SDL12_SYM(AudioInit); SCHISM_SDL12_SYM(AudioQuit); return 0; } static int sdl12_audio_init(void) { if (!sdl12_init()) return 0; if (sdl12_audio_load_syms()) return 0; if (!sdl12_audio_driver_info_init()) return 0; return 1; } static void sdl12_audio_quit(void) { // the subsystem quitting is handled by the quit driver function sdl12_audio_driver_info_quit(); sdl12_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_audio_backend_t schism_audio_backend_sdl12 = { .init = sdl12_audio_init, .quit = sdl12_audio_quit, .driver_count = sdl12_audio_driver_count, .driver_name = sdl12_audio_driver_name, .device_count = sdl12_audio_device_count, .device_name = sdl12_audio_device_name, .init_driver = sdl12_audio_init_driver, .quit_driver = sdl12_audio_quit_driver, .open_device = sdl12_audio_open_device, .close_device = sdl12_audio_close_device, .lock_device = sdl12_audio_lock_device, .unlock_device = sdl12_audio_unlock_device, .pause_device = sdl12_audio_pause_device, }; schismtracker-20250313/sys/sdl12/events.c000066400000000000000000000720741476471630300200550ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "events.h" #include "backend/events.h" #include "util.h" #include "video.h" #include "init.h" #include static schism_keymod_t sdl12_modkey_trans(uint16_t mod) { schism_keymod_t res = 0; if (mod & KMOD_LSHIFT) res |= SCHISM_KEYMOD_LSHIFT; if (mod & KMOD_RSHIFT) res |= SCHISM_KEYMOD_RSHIFT; if (mod & KMOD_LCTRL) res |= SCHISM_KEYMOD_LCTRL; if (mod & KMOD_RCTRL) res |= SCHISM_KEYMOD_RCTRL; if (mod & KMOD_LALT) res |= SCHISM_KEYMOD_LALT; if (mod & KMOD_RALT) res |= SCHISM_KEYMOD_RALT; if (mod & KMOD_LMETA) res |= SCHISM_KEYMOD_LGUI; if (mod & KMOD_RMETA) res |= SCHISM_KEYMOD_RGUI; if (mod & KMOD_NUM) res |= SCHISM_KEYMOD_NUM; if (mod & KMOD_CAPS) res |= SCHISM_KEYMOD_CAPS; if (mod & KMOD_MODE) res |= SCHISM_KEYMOD_MODE; return res; } static schism_keysym_t sdl12_keycode_trans(SDLKey key) { if ((int)key <= 255) { switch (key) { case SDLK_PAUSE: return SCHISM_KEYSYM_PAUSE; case SDLK_CLEAR: return SCHISM_KEYSYM_CLEAR; default: break; } return (schism_keysym_t)key; } switch (key) { #define CASEKEYSYM12TOSCHISM(schism, k12) case SDLK_##k12: return SCHISM_KEYSYM_##schism CASEKEYSYM12TOSCHISM(KP_0, KP0); CASEKEYSYM12TOSCHISM(KP_1, KP1); CASEKEYSYM12TOSCHISM(KP_2, KP2); CASEKEYSYM12TOSCHISM(KP_3, KP3); CASEKEYSYM12TOSCHISM(KP_4, KP4); CASEKEYSYM12TOSCHISM(KP_5, KP5); CASEKEYSYM12TOSCHISM(KP_6, KP6); CASEKEYSYM12TOSCHISM(KP_7, KP7); CASEKEYSYM12TOSCHISM(KP_8, KP8); CASEKEYSYM12TOSCHISM(KP_9, KP9); CASEKEYSYM12TOSCHISM(NUMLOCKCLEAR, NUMLOCK); CASEKEYSYM12TOSCHISM(SCROLLLOCK, SCROLLOCK); CASEKEYSYM12TOSCHISM(RGUI, RMETA); CASEKEYSYM12TOSCHISM(LGUI, LMETA); CASEKEYSYM12TOSCHISM(PRINTSCREEN, PRINT); #undef CASEKEYSYM12TOSCHISM #define CASEKEYSYM12TOSCHISM(k) case SDLK_##k: return SCHISM_KEYSYM_##k CASEKEYSYM12TOSCHISM(CLEAR); CASEKEYSYM12TOSCHISM(PAUSE); CASEKEYSYM12TOSCHISM(KP_PERIOD); CASEKEYSYM12TOSCHISM(KP_DIVIDE); CASEKEYSYM12TOSCHISM(KP_MULTIPLY); CASEKEYSYM12TOSCHISM(KP_MINUS); CASEKEYSYM12TOSCHISM(KP_PLUS); CASEKEYSYM12TOSCHISM(KP_ENTER); CASEKEYSYM12TOSCHISM(KP_EQUALS); CASEKEYSYM12TOSCHISM(UP); CASEKEYSYM12TOSCHISM(DOWN); CASEKEYSYM12TOSCHISM(RIGHT); CASEKEYSYM12TOSCHISM(LEFT); CASEKEYSYM12TOSCHISM(INSERT); CASEKEYSYM12TOSCHISM(HOME); CASEKEYSYM12TOSCHISM(END); CASEKEYSYM12TOSCHISM(PAGEUP); CASEKEYSYM12TOSCHISM(PAGEDOWN); CASEKEYSYM12TOSCHISM(F1); CASEKEYSYM12TOSCHISM(F2); CASEKEYSYM12TOSCHISM(F3); CASEKEYSYM12TOSCHISM(F4); CASEKEYSYM12TOSCHISM(F5); CASEKEYSYM12TOSCHISM(F6); CASEKEYSYM12TOSCHISM(F7); CASEKEYSYM12TOSCHISM(F8); CASEKEYSYM12TOSCHISM(F9); CASEKEYSYM12TOSCHISM(F10); CASEKEYSYM12TOSCHISM(F11); CASEKEYSYM12TOSCHISM(F12); CASEKEYSYM12TOSCHISM(F13); CASEKEYSYM12TOSCHISM(F14); CASEKEYSYM12TOSCHISM(F15); CASEKEYSYM12TOSCHISM(CAPSLOCK); CASEKEYSYM12TOSCHISM(RSHIFT); CASEKEYSYM12TOSCHISM(LSHIFT); CASEKEYSYM12TOSCHISM(RCTRL); CASEKEYSYM12TOSCHISM(LCTRL); CASEKEYSYM12TOSCHISM(RALT); CASEKEYSYM12TOSCHISM(LALT); //CASEKEYSYM12TOSCHISM(MODE); CASEKEYSYM12TOSCHISM(HELP); CASEKEYSYM12TOSCHISM(SYSREQ); CASEKEYSYM12TOSCHISM(MENU); CASEKEYSYM12TOSCHISM(POWER); CASEKEYSYM12TOSCHISM(UNDO); #undef CASEKEYSYM12TOSCHISM default: break; } return SCHISM_KEYSYM_UNKNOWN; } static schism_scancode_t sdl12_scancode_trans(uint8_t sc) { switch (sc) { #ifdef SCHISM_WIN32 case (0x13 - 8): return SCHISM_SCANCODE_0; case (0x0A - 8): return SCHISM_SCANCODE_1; case (0x0B - 8): return SCHISM_SCANCODE_2; case (0x0C - 8): return SCHISM_SCANCODE_3; case (0x0D - 8): return SCHISM_SCANCODE_4; case (0x0E - 8): return SCHISM_SCANCODE_5; case (0x0F - 8): return SCHISM_SCANCODE_6; case (0x10 - 8): return SCHISM_SCANCODE_7; case (0x11 - 8): return SCHISM_SCANCODE_8; case (0x12 - 8): return SCHISM_SCANCODE_9; case (0x26 - 8): return SCHISM_SCANCODE_A; case (0x30 - 8): return SCHISM_SCANCODE_APOSTROPHE; case (0x38 - 8): return SCHISM_SCANCODE_B; case (0x33 - 8): return SCHISM_SCANCODE_BACKSLASH; case (0x16 - 8): return SCHISM_SCANCODE_BACKSPACE; case (0x36 - 8): return SCHISM_SCANCODE_C; case (0x42 - 8): return SCHISM_SCANCODE_CAPSLOCK; case (0x3B - 8): return SCHISM_SCANCODE_COMMA; case (0x28 - 8): return SCHISM_SCANCODE_D; case (0x1A - 8): return SCHISM_SCANCODE_E; case (0x15 - 8): return SCHISM_SCANCODE_EQUALS; case (0x09 - 8): return SCHISM_SCANCODE_ESCAPE; case (0x29 - 8): return SCHISM_SCANCODE_F; case (0x43 - 8): return SCHISM_SCANCODE_F1; case (0x4C - 8): return SCHISM_SCANCODE_F10; case (0x5F - 8): return SCHISM_SCANCODE_F11; case (0x60 - 8): return SCHISM_SCANCODE_F12; case (0x44 - 8): return SCHISM_SCANCODE_F2; case (0x45 - 8): return SCHISM_SCANCODE_F3; case (0x46 - 8): return SCHISM_SCANCODE_F4; case (0x47 - 8): return SCHISM_SCANCODE_F5; case (0x48 - 8): return SCHISM_SCANCODE_F6; case (0x49 - 8): return SCHISM_SCANCODE_F7; case (0x4A - 8): return SCHISM_SCANCODE_F8; case (0x4B - 8): return SCHISM_SCANCODE_F9; case (0x2A - 8): return SCHISM_SCANCODE_G; case (0x31 - 8): return SCHISM_SCANCODE_GRAVE; case (0x2B - 8): return SCHISM_SCANCODE_H; case (0x1F - 8): return SCHISM_SCANCODE_I; case (0x2C - 8): return SCHISM_SCANCODE_J; case (0x2D - 8): return SCHISM_SCANCODE_K; case (0x5A - 8): return SCHISM_SCANCODE_KP_0; case (0x57 - 8): return SCHISM_SCANCODE_KP_1; case (0x58 - 8): return SCHISM_SCANCODE_KP_2; case (0x59 - 8): return SCHISM_SCANCODE_KP_3; case (0x53 - 8): return SCHISM_SCANCODE_KP_4; case (0x54 - 8): return SCHISM_SCANCODE_KP_5; case (0x55 - 8): return SCHISM_SCANCODE_KP_6; case (0x4F - 8): return SCHISM_SCANCODE_KP_7; case (0x50 - 8): return SCHISM_SCANCODE_KP_8; case (0x51 - 8): return SCHISM_SCANCODE_KP_9; case (0x52 - 8): return SCHISM_SCANCODE_KP_MINUS; case (0x3F - 8): return SCHISM_SCANCODE_KP_MULTIPLY; case (0x5B - 8): return SCHISM_SCANCODE_KP_PERIOD; case (0x56 - 8): return SCHISM_SCANCODE_KP_PLUS; case (0x2E - 8): return SCHISM_SCANCODE_L; case (0x40 - 8): return SCHISM_SCANCODE_LALT; case (0x25 - 8): return SCHISM_SCANCODE_LCTRL; case (0x22 - 8): return SCHISM_SCANCODE_LEFTBRACKET; case (0x85 - 8): return SCHISM_SCANCODE_LGUI; case (0x32 - 8): return SCHISM_SCANCODE_LSHIFT; case (0x3A - 8): return SCHISM_SCANCODE_M; case (0x14 - 8): return SCHISM_SCANCODE_MINUS; case (0x39 - 8): return SCHISM_SCANCODE_N; case (0x5E - 8): return SCHISM_SCANCODE_NONUSBACKSLASH; case (0x4D - 8): return SCHISM_SCANCODE_NUMLOCKCLEAR; case (0x20 - 8): return SCHISM_SCANCODE_O; case (0x21 - 8): return SCHISM_SCANCODE_P; case (0x3C - 8): return SCHISM_SCANCODE_PERIOD; case (0x6B - 8): return SCHISM_SCANCODE_PRINTSCREEN; case (0x18 - 8): return SCHISM_SCANCODE_Q; case (0x1B - 8): return SCHISM_SCANCODE_R; case (0x24 - 8): return SCHISM_SCANCODE_RETURN; case (0x86 - 8): return SCHISM_SCANCODE_RGUI; case (0x23 - 8): return SCHISM_SCANCODE_RIGHTBRACKET; case (0x3E - 8): return SCHISM_SCANCODE_RSHIFT; case (0x27 - 8): return SCHISM_SCANCODE_S; case (0x4E - 8): return SCHISM_SCANCODE_SCROLLLOCK; case (0x2F - 8): return SCHISM_SCANCODE_SEMICOLON; case (0x3D - 8): return SCHISM_SCANCODE_SLASH; case (0x41 - 8): return SCHISM_SCANCODE_SPACE; case (0x1C - 8): return SCHISM_SCANCODE_T; case (0x17 - 8): return SCHISM_SCANCODE_TAB; case (0x1E - 8): return SCHISM_SCANCODE_U; case (0x37 - 8): return SCHISM_SCANCODE_V; case (0x19 - 8): return SCHISM_SCANCODE_W; case (0x35 - 8): return SCHISM_SCANCODE_X; case (0x1D - 8): return SCHISM_SCANCODE_Y; case (0x34 - 8): return SCHISM_SCANCODE_Z; #elif defined(SCHISM_MACOSX) /* Mac OS X */ case 0x1D: return SCHISM_SCANCODE_0; case 0x12: return SCHISM_SCANCODE_1; case 0x13: return SCHISM_SCANCODE_2; case 0x14: return SCHISM_SCANCODE_3; case 0x15: return SCHISM_SCANCODE_4; case 0x17: return SCHISM_SCANCODE_5; case 0x16: return SCHISM_SCANCODE_6; case 0x1A: return SCHISM_SCANCODE_7; case 0x1C: return SCHISM_SCANCODE_8; case 0x19: return SCHISM_SCANCODE_9; //case 0x00: return SCHISM_SCANCODE_A; case 0x27: return SCHISM_SCANCODE_APOSTROPHE; case 0x0B: return SCHISM_SCANCODE_B; case 0x2A: return SCHISM_SCANCODE_BACKSLASH; case 0x33: return SCHISM_SCANCODE_BACKSPACE; case 0x08: return SCHISM_SCANCODE_C; //case 0x00: return SCHISM_SCANCODE_CAPSLOCK; case 0x2B: return SCHISM_SCANCODE_COMMA; case 0x02: return SCHISM_SCANCODE_D; case 0x75: return SCHISM_SCANCODE_DELETE; case 0x7D: return SCHISM_SCANCODE_DOWN; case 0x0E: return SCHISM_SCANCODE_E; case 0x77: return SCHISM_SCANCODE_END; case 0x18: return SCHISM_SCANCODE_EQUALS; case 0x35: return SCHISM_SCANCODE_ESCAPE; case 0x03: return SCHISM_SCANCODE_F; case 0x7A: return SCHISM_SCANCODE_F1; case 0x6E: return SCHISM_SCANCODE_F10; case 0x67: return SCHISM_SCANCODE_F11; case 0x6F: return SCHISM_SCANCODE_F12; case 0x78: return SCHISM_SCANCODE_F2; case 0x63: return SCHISM_SCANCODE_F3; case 0x76: return SCHISM_SCANCODE_F4; case 0x60: return SCHISM_SCANCODE_F5; case 0x61: return SCHISM_SCANCODE_F6; case 0x62: return SCHISM_SCANCODE_F7; case 0x64: return SCHISM_SCANCODE_F8; case 0x65: return SCHISM_SCANCODE_F9; case 0x05: return SCHISM_SCANCODE_G; case 0x32: return SCHISM_SCANCODE_GRAVE; case 0x04: return SCHISM_SCANCODE_H; case 0x73: return SCHISM_SCANCODE_HOME; case 0x22: return SCHISM_SCANCODE_I; case 0x72: return SCHISM_SCANCODE_INSERT; case 0x26: return SCHISM_SCANCODE_J; case 0x28: return SCHISM_SCANCODE_K; case 0x52: return SCHISM_SCANCODE_KP_0; case 0x53: return SCHISM_SCANCODE_KP_1; case 0x54: return SCHISM_SCANCODE_KP_2; case 0x55: return SCHISM_SCANCODE_KP_3; case 0x56: return SCHISM_SCANCODE_KP_4; case 0x57: return SCHISM_SCANCODE_KP_5; case 0x58: return SCHISM_SCANCODE_KP_6; case 0x59: return SCHISM_SCANCODE_KP_7; case 0x5B: return SCHISM_SCANCODE_KP_8; case 0x5C: return SCHISM_SCANCODE_KP_9; case 0x4B: return SCHISM_SCANCODE_KP_DIVIDE; case 0x4C: return SCHISM_SCANCODE_KP_ENTER; case 0x51: return SCHISM_SCANCODE_KP_EQUALS; case 0x4E: return SCHISM_SCANCODE_KP_MINUS; case 0x43: return SCHISM_SCANCODE_KP_MULTIPLY; case 0x41: return SCHISM_SCANCODE_KP_PERIOD; case 0x45: return SCHISM_SCANCODE_KP_PLUS; case 0x25: return SCHISM_SCANCODE_L; case 0x7B: return SCHISM_SCANCODE_LEFT; case 0x21: return SCHISM_SCANCODE_LEFTBRACKET; case 0x2E: return SCHISM_SCANCODE_M; case 0x1B: return SCHISM_SCANCODE_MINUS; case 0x2D: return SCHISM_SCANCODE_N; case 0x0A: return SCHISM_SCANCODE_NONUSBACKSLASH; case 0x47: return SCHISM_SCANCODE_NUMLOCKCLEAR; case 0x1F: return SCHISM_SCANCODE_O; case 0x23: return SCHISM_SCANCODE_P; case 0x79: return SCHISM_SCANCODE_PAGEDOWN; case 0x74: return SCHISM_SCANCODE_PAGEUP; case 0x2F: return SCHISM_SCANCODE_PERIOD; case 0x6B: return SCHISM_SCANCODE_PRINTSCREEN; case 0x0C: return SCHISM_SCANCODE_Q; case 0x0F: return SCHISM_SCANCODE_R; case 0x24: return SCHISM_SCANCODE_RETURN; case 0x7C: return SCHISM_SCANCODE_RIGHT; case 0x1E: return SCHISM_SCANCODE_RIGHTBRACKET; case 0x01: return SCHISM_SCANCODE_S; case 0x71: return SCHISM_SCANCODE_SCROLLLOCK; case 0x29: return SCHISM_SCANCODE_SEMICOLON; case 0x2C: return SCHISM_SCANCODE_SLASH; case 0x31: return SCHISM_SCANCODE_SPACE; case 0x11: return SCHISM_SCANCODE_T; case 0x30: return SCHISM_SCANCODE_TAB; case 0x20: return SCHISM_SCANCODE_U; case 0x7E: return SCHISM_SCANCODE_UP; case 0x09: return SCHISM_SCANCODE_V; case 0x0D: return SCHISM_SCANCODE_W; case 0x07: return SCHISM_SCANCODE_X; case 0x10: return SCHISM_SCANCODE_Y; case 0x06: return SCHISM_SCANCODE_Z; #elif defined(SCHISM_MACOS) // Taken from Inside Macintosh case 0x35: return SCHISM_SCANCODE_ESCAPE; case 0x7A: return SCHISM_SCANCODE_F1; case 0x78: return SCHISM_SCANCODE_F2; case 0x63: return SCHISM_SCANCODE_F3; case 0x76: return SCHISM_SCANCODE_F4; case 0x60: return SCHISM_SCANCODE_F5; case 0x61: return SCHISM_SCANCODE_F6; case 0x62: return SCHISM_SCANCODE_F7; case 0x64: return SCHISM_SCANCODE_F8; case 0x65: return SCHISM_SCANCODE_F9; case 0x6D: return SCHISM_SCANCODE_F10; case 0x67: return SCHISM_SCANCODE_F11; case 0x6F: return SCHISM_SCANCODE_F12; case 0x69: return SCHISM_SCANCODE_PRINTSCREEN; case 0x6B: return SCHISM_SCANCODE_SCROLLLOCK; case 0x71: return SCHISM_SCANCODE_PAUSE; case 0x7F: return SCHISM_SCANCODE_POWER; case 0x32: return SCHISM_SCANCODE_GRAVE; case 0x12: return SCHISM_SCANCODE_1; case 0x13: return SCHISM_SCANCODE_2; case 0x14: return SCHISM_SCANCODE_3; case 0x15: return SCHISM_SCANCODE_4; case 0x17: return SCHISM_SCANCODE_5; case 0x16: return SCHISM_SCANCODE_6; case 0x1A: return SCHISM_SCANCODE_7; case 0x1C: return SCHISM_SCANCODE_8; case 0x19: return SCHISM_SCANCODE_9; case 0x1D: return SCHISM_SCANCODE_0; case 0x1B: return SCHISM_SCANCODE_MINUS; case 0x18: return SCHISM_SCANCODE_EQUALS; case 0x33: return SCHISM_SCANCODE_BACKSPACE; case 0x72: return SCHISM_SCANCODE_INSERT; case 0x73: return SCHISM_SCANCODE_HOME; case 0x74: return SCHISM_SCANCODE_PAGEUP; case 0x47: return SCHISM_SCANCODE_NUMLOCKCLEAR; case 0x51: return SCHISM_SCANCODE_KP_EQUALS; case 0x4B: return SCHISM_SCANCODE_KP_DIVIDE; case 0x43: return SCHISM_SCANCODE_KP_MULTIPLY; case 0x30: return SCHISM_SCANCODE_TAB; case 0x0C: return SCHISM_SCANCODE_Q; case 0x0D: return SCHISM_SCANCODE_W; case 0x0E: return SCHISM_SCANCODE_E; case 0x0F: return SCHISM_SCANCODE_R; case 0x11: return SCHISM_SCANCODE_T; case 0x10: return SCHISM_SCANCODE_Y; case 0x20: return SCHISM_SCANCODE_U; case 0x22: return SCHISM_SCANCODE_I; case 0x1F: return SCHISM_SCANCODE_O; case 0x23: return SCHISM_SCANCODE_P; case 0x21: return SCHISM_SCANCODE_LEFTBRACKET; case 0x1E: return SCHISM_SCANCODE_RIGHTBRACKET; case 0x2A: return SCHISM_SCANCODE_BACKSLASH; case 0x75: return SCHISM_SCANCODE_DELETE; case 0x77: return SCHISM_SCANCODE_END; case 0x79: return SCHISM_SCANCODE_PAGEDOWN; case 0x59: return SCHISM_SCANCODE_KP_7; case 0x5B: return SCHISM_SCANCODE_KP_8; case 0x5C: return SCHISM_SCANCODE_KP_9; case 0x4E: return SCHISM_SCANCODE_KP_MINUS; case 0x39: return SCHISM_SCANCODE_CAPSLOCK; case 0x00: return SCHISM_SCANCODE_A; case 0x01: return SCHISM_SCANCODE_S; case 0x02: return SCHISM_SCANCODE_D; case 0x03: return SCHISM_SCANCODE_F; case 0x05: return SCHISM_SCANCODE_G; case 0x04: return SCHISM_SCANCODE_H; case 0x26: return SCHISM_SCANCODE_J; case 0x28: return SCHISM_SCANCODE_K; case 0x25: return SCHISM_SCANCODE_L; case 0x29: return SCHISM_SCANCODE_SEMICOLON; case 0x27: return SCHISM_SCANCODE_APOSTROPHE; case 0x24: return SCHISM_SCANCODE_RETURN; case 0x56: return SCHISM_SCANCODE_KP_4; case 0x57: return SCHISM_SCANCODE_KP_5; case 0x58: return SCHISM_SCANCODE_KP_6; case 0x45: return SCHISM_SCANCODE_KP_PLUS; case 0x38: return SCHISM_SCANCODE_LSHIFT; case 0x06: return SCHISM_SCANCODE_Z; case 0x07: return SCHISM_SCANCODE_X; case 0x08: return SCHISM_SCANCODE_C; case 0x09: return SCHISM_SCANCODE_V; case 0x0B: return SCHISM_SCANCODE_B; case 0x2D: return SCHISM_SCANCODE_N; case 0x2E: return SCHISM_SCANCODE_M; case 0x2B: return SCHISM_SCANCODE_COMMA; case 0x2F: return SCHISM_SCANCODE_PERIOD; case 0x2C: return SCHISM_SCANCODE_SLASH; case 0x7E: return SCHISM_SCANCODE_UP; case 0x53: return SCHISM_SCANCODE_KP_1; case 0x54: return SCHISM_SCANCODE_KP_2; case 0x55: return SCHISM_SCANCODE_KP_3; case 0x4C: return SCHISM_SCANCODE_KP_ENTER; // The Right Ctrl, Alt, Super, and Shift keys // are treated identically by classic Mac OS, // and hence they cannot be used here... case 0x3B: return SCHISM_SCANCODE_LCTRL; case 0x3A: return SCHISM_SCANCODE_LALT; case 0x37: return SCHISM_SCANCODE_LGUI; case 0x31: return SCHISM_SCANCODE_SPACE; case 0x7B: return SCHISM_SCANCODE_LEFT; case 0x7D: return SCHISM_SCANCODE_DOWN; case 0x7C: return SCHISM_SCANCODE_RIGHT; case 0x52: return SCHISM_SCANCODE_KP_0; case 0x41: return SCHISM_SCANCODE_KP_PERIOD; #else /* Linux and friends ? */ case 0x13: return SCHISM_SCANCODE_0; case 0x0A: return SCHISM_SCANCODE_1; case 0x0B: return SCHISM_SCANCODE_2; case 0x0C: return SCHISM_SCANCODE_3; case 0x0D: return SCHISM_SCANCODE_4; case 0x0E: return SCHISM_SCANCODE_5; case 0x0F: return SCHISM_SCANCODE_6; case 0x10: return SCHISM_SCANCODE_7; case 0x11: return SCHISM_SCANCODE_8; case 0x12: return SCHISM_SCANCODE_9; case 0x26: return SCHISM_SCANCODE_A; case 0x30: return SCHISM_SCANCODE_APOSTROPHE; case 0x38: return SCHISM_SCANCODE_B; case 0x33: return SCHISM_SCANCODE_BACKSLASH; case 0x16: return SCHISM_SCANCODE_BACKSPACE; case 0x36: return SCHISM_SCANCODE_C; case 0x42: return SCHISM_SCANCODE_CAPSLOCK; case 0x3B: return SCHISM_SCANCODE_COMMA; case 0x28: return SCHISM_SCANCODE_D; //case 0x00: return SCHISM_SCANCODE_DELETE; //case 0x00: return SCHISM_SCANCODE_DOWN; case 0x1A: return SCHISM_SCANCODE_E; //case 0x00: return SCHISM_SCANCODE_END; case 0x15: return SCHISM_SCANCODE_EQUALS; case 0x09: return SCHISM_SCANCODE_ESCAPE; case 0x29: return SCHISM_SCANCODE_F; case 0x43: return SCHISM_SCANCODE_F1; case 0x4C: return SCHISM_SCANCODE_F10; case 0x5F: return SCHISM_SCANCODE_F11; case 0x60: return SCHISM_SCANCODE_F12; case 0x44: return SCHISM_SCANCODE_F2; case 0x45: return SCHISM_SCANCODE_F3; case 0x46: return SCHISM_SCANCODE_F4; case 0x47: return SCHISM_SCANCODE_F5; case 0x48: return SCHISM_SCANCODE_F6; case 0x49: return SCHISM_SCANCODE_F7; case 0x4A: return SCHISM_SCANCODE_F8; case 0x4B: return SCHISM_SCANCODE_F9; case 0x2A: return SCHISM_SCANCODE_G; case 0x31: return SCHISM_SCANCODE_GRAVE; case 0x2B: return SCHISM_SCANCODE_H; //case 0x00: return SCHISM_SCANCODE_HOME; case 0x1F: return SCHISM_SCANCODE_I; //case 0x00: return SCHISM_SCANCODE_INSERT; case 0x2C: return SCHISM_SCANCODE_J; case 0x2D: return SCHISM_SCANCODE_K; case 0x5A: return SCHISM_SCANCODE_KP_0; case 0x57: return SCHISM_SCANCODE_KP_1; case 0x58: return SCHISM_SCANCODE_KP_2; case 0x59: return SCHISM_SCANCODE_KP_3; case 0x53: return SCHISM_SCANCODE_KP_4; case 0x54: return SCHISM_SCANCODE_KP_5; case 0x55: return SCHISM_SCANCODE_KP_6; case 0x4F: return SCHISM_SCANCODE_KP_7; case 0x50: return SCHISM_SCANCODE_KP_8; case 0x51: return SCHISM_SCANCODE_KP_9; //case 0x00: return SCHISM_SCANCODE_KP_DIVIDE; //case 0x00: return SCHISM_SCANCODE_KP_ENTER; //case 0x00: return SCHISM_SCANCODE_KP_EQUALS; case 0x52: return SCHISM_SCANCODE_KP_MINUS; case 0x3F: return SCHISM_SCANCODE_KP_MULTIPLY; case 0x5B: return SCHISM_SCANCODE_KP_PERIOD; case 0x56: return SCHISM_SCANCODE_KP_PLUS; case 0x2E: return SCHISM_SCANCODE_L; case 0x40: return SCHISM_SCANCODE_LALT; case 0x25: return SCHISM_SCANCODE_LCTRL; //case 0x00: return SCHISM_SCANCODE_LEFT; case 0x22: return SCHISM_SCANCODE_LEFTBRACKET; case 0x85: return SCHISM_SCANCODE_LGUI; case 0x32: return SCHISM_SCANCODE_LSHIFT; case 0x3A: return SCHISM_SCANCODE_M; case 0x14: return SCHISM_SCANCODE_MINUS; case 0x39: return SCHISM_SCANCODE_N; case 0x5E: return SCHISM_SCANCODE_NONUSBACKSLASH; case 0x4D: return SCHISM_SCANCODE_NUMLOCKCLEAR; case 0x20: return SCHISM_SCANCODE_O; case 0x21: return SCHISM_SCANCODE_P; //case 0x00: return SCHISM_SCANCODE_PAGEDOWN; //case 0x00: return SCHISM_SCANCODE_PAGEUP; case 0x3C: return SCHISM_SCANCODE_PERIOD; case 0x6B: return SCHISM_SCANCODE_PRINTSCREEN; case 0x18: return SCHISM_SCANCODE_Q; case 0x1B: return SCHISM_SCANCODE_R; case 0x24: return SCHISM_SCANCODE_RETURN; case 0x86: return SCHISM_SCANCODE_RGUI; //case 0x00: return SCHISM_SCANCODE_RIGHT; case 0x23: return SCHISM_SCANCODE_RIGHTBRACKET; case 0x3E: return SCHISM_SCANCODE_RSHIFT; case 0x27: return SCHISM_SCANCODE_S; case 0x4E: return SCHISM_SCANCODE_SCROLLLOCK; case 0x2F: return SCHISM_SCANCODE_SEMICOLON; case 0x3D: return SCHISM_SCANCODE_SLASH; case 0x41: return SCHISM_SCANCODE_SPACE; case 0x1C: return SCHISM_SCANCODE_T; case 0x17: return SCHISM_SCANCODE_TAB; case 0x1E: return SCHISM_SCANCODE_U; //case 0x00: return SCHISM_SCANCODE_UP; case 0x37: return SCHISM_SCANCODE_V; case 0x19: return SCHISM_SCANCODE_W; case 0x35: return SCHISM_SCANCODE_X; case 0x1D: return SCHISM_SCANCODE_Y; case 0x34: return SCHISM_SCANCODE_Z; #endif default: return SCHISM_SCANCODE_UNKNOWN; } } ////////////////////////////////////////////////////////////////////////////// static SDLMod (SDLCALL *sdl12_GetModState)(void); static int (SDLCALL *sdl12_PollEvent)(SDL_Event *event); static Uint8 (SDLCALL *sdl12_EventState)(Uint8 type, int state); static Uint8 (SDLCALL *sdl12_GetAppState)(void); static schism_keymod_t sdl12_event_mod_state(void) { return sdl12_modkey_trans(sdl12_GetModState()); } static Uint8 app_state = 0; static void sdl12_pump_events(void) { // SDL does send events for this, but they're broken on Windows, // so we have to manually check for changes. { Uint8 app_state_new = sdl12_GetAppState(); static const struct { Uint8 mask; uint32_t gain; uint32_t lost; } conv[] = { {SDL_APPINPUTFOCUS, SCHISM_WINDOWEVENT_FOCUS_GAINED, SCHISM_WINDOWEVENT_FOCUS_LOST}, {SDL_APPACTIVE, SCHISM_WINDOWEVENT_SHOWN, SCHISM_WINDOWEVENT_HIDDEN}, }; for (size_t i = 0; i < ARRAY_SIZE(conv); i++) { if ((app_state & conv[i].mask) == (app_state_new & conv[i].mask)) continue; schism_event_t e; e.type = (app_state_new & conv[i].mask) ? conv[i].gain : conv[i].lost; events_push_event(&e); } app_state = app_state_new; } /* Convert our events to Schism's internal representation */ SDL_Event e; while (sdl12_PollEvent(&e)) { schism_event_t schism_event = {0}; switch (e.type) { case SDL_QUIT: schism_event.type = SCHISM_QUIT; events_push_event(&schism_event); break; case SDL_VIDEORESIZE: schism_event.type = SCHISM_WINDOWEVENT_RESIZED; schism_event.window.data.resized.width = e.resize.w; schism_event.window.data.resized.height = e.resize.h; events_push_event(&schism_event); break; case SDL_VIDEOEXPOSE: schism_event.type = SCHISM_WINDOWEVENT_EXPOSED; events_push_event(&schism_event); break; #if 0 // This is broken on Windows. case SDL_ACTIVEEVENT: switch (e.active.state) { case SDL_APPINPUTFOCUS: schism_event.type = (e.active.gain) ? SCHISM_WINDOWEVENT_FOCUS_GAINED : SCHISM_WINDOWEVENT_FOCUS_LOST; printf("%d\n", (int)e.active.gain); events_push_event(&schism_event); break; case SDL_APPACTIVE: schism_event.type = (e.active.gain) ? SCHISM_WINDOWEVENT_SHOWN : SCHISM_WINDOWEVENT_HIDDEN; events_push_event(&schism_event); break; default: break; } break; #endif case SDL_KEYDOWN: schism_event.type = SCHISM_KEYDOWN; schism_event.key.state = KEY_PRESS; schism_event.key.sym = sdl12_keycode_trans(e.key.keysym.sym); schism_event.key.scancode = sdl12_scancode_trans(e.key.keysym.scancode); schism_event.key.mod = sdl12_modkey_trans(e.key.keysym.mod); /* Only convert the Unicode if it's actually useful; * this tripped a bug under mac os x where the left & right * arrow keys are translated to some odd unicode char. */ if (!(schism_event.key.sym & SCHISM_KEYSYM_SCANCODE_MASK) && !(schism_event.key.mod & SCHISM_KEYMOD_CTRL) && schism_event.key.sym != SCHISM_KEYSYM_ESCAPE) { /* convert UCS-2 to UTF-8 */ if (e.key.keysym.unicode < 0x80) { schism_event.key.text[0] = e.key.keysym.unicode; } else if (e.key.keysym.unicode < 0x800) { schism_event.key.text[0] = 0xC0 | (e.key.keysym.unicode >> 6); schism_event.key.text[1] = 0x80 | (e.key.keysym.unicode & 0x3F); } else { schism_event.key.text[0] = 0xE0 | (e.key.keysym.unicode >> 12); schism_event.key.text[1] = 0x80 | ((e.key.keysym.unicode >> 6) & 0x3F); schism_event.key.text[2] = 0x80 | (e.key.keysym.unicode & 0x3F); } } events_push_event(&schism_event); break; case SDL_KEYUP: schism_event.type = SCHISM_KEYUP; schism_event.key.state = KEY_RELEASE; schism_event.key.sym = sdl12_keycode_trans(e.key.keysym.sym); schism_event.key.scancode = sdl12_scancode_trans(e.key.keysym.scancode); schism_event.key.mod = sdl12_modkey_trans(e.key.keysym.mod); events_push_event(&schism_event); break; case SDL_MOUSEMOTION: schism_event.type = SCHISM_MOUSEMOTION; schism_event.motion.x = e.motion.x; schism_event.motion.y = e.motion.y; events_push_event(&schism_event); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: schism_event.type = (e.type == SDL_MOUSEBUTTONDOWN) ? SCHISM_MOUSEBUTTONDOWN : SCHISM_MOUSEBUTTONUP; switch (e.button.button) { case SDL_BUTTON_LEFT: schism_event.button.button = MOUSE_BUTTON_LEFT; break; case SDL_BUTTON_MIDDLE: schism_event.button.button = MOUSE_BUTTON_MIDDLE; break; case SDL_BUTTON_RIGHT: schism_event.button.button = MOUSE_BUTTON_RIGHT; break; default: break; } switch (e.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_MIDDLE: case SDL_BUTTON_RIGHT: schism_event.button.state = !!e.button.state; //schism_event.button.clicks = e.button.clicks; schism_event.button.x = e.button.x; schism_event.button.y = e.button.y; events_push_event(&schism_event); break; case SDL_BUTTON_WHEELDOWN: case SDL_BUTTON_WHEELUP: { schism_event.type = SCHISM_MOUSEWHEEL; schism_event.wheel.x = 0; schism_event.wheel.y = (e.button.button == SDL_BUTTON_WHEELDOWN) ? -1 : 1; unsigned int mx, my; video_get_mouse_coordinates(&mx, &my); schism_event.wheel.mouse_x = mx; schism_event.wheel.mouse_y = my; events_push_event(&schism_event); break; } default: break; } break; case SDL_SYSWMEVENT: schism_event.type = SCHISM_EVENT_WM_MSG; schism_event.wm_msg.backend = SCHISM_WM_MSG_BACKEND_SDL12; #ifdef SCHISM_WIN32 schism_event.wm_msg.subsystem = SCHISM_WM_MSG_SUBSYSTEM_WINDOWS; schism_event.wm_msg.msg.win.hwnd = e.syswm.msg->hwnd; schism_event.wm_msg.msg.win.msg = e.syswm.msg->msg; schism_event.wm_msg.msg.win.wparam = e.syswm.msg->wParam; schism_event.wm_msg.msg.win.lparam = e.syswm.msg->lParam; #elif defined(SDL_VIDEO_DRIVER_X11) && defined(SCHISM_USE_X11) if (e.syswm.msg->subsystem == SDL_SYSWM_X11) { schism_event.wm_msg.subsystem = SCHISM_WM_MSG_SUBSYSTEM_X11; schism_event.wm_msg.msg.x11.event.type = e.syswm.msg->event.xevent.type; if (e.syswm.msg->event.xevent.type == SelectionRequest) { schism_event.wm_msg.msg.x11.event.selection_request.serial = e.syswm.msg->event.xevent.xselectionrequest.serial; schism_event.wm_msg.msg.x11.event.selection_request.send_event = e.syswm.msg->event.xevent.xselectionrequest.send_event; // `Bool' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.display = e.syswm.msg->event.xevent.xselectionrequest.display; // `Display *' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.owner = e.syswm.msg->event.xevent.xselectionrequest.owner; // `Window' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.requestor = e.syswm.msg->event.xevent.xselectionrequest.requestor; // `Window' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.selection = e.syswm.msg->event.xevent.xselectionrequest.selection; // `Atom' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.target = e.syswm.msg->event.xevent.xselectionrequest.target; // `Atom' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.property = e.syswm.msg->event.xevent.xselectionrequest.property; // `Atom' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.time = e.syswm.msg->event.xevent.xselectionrequest.time; // `Time' in Xlib } } #endif events_push_event(&schism_event); break; default: break; } } } ////////////////////////////////////////////////////////////////////////////// static int sdl12_events_load_syms(void) { SCHISM_SDL12_SYM(GetModState); SCHISM_SDL12_SYM(PollEvent); SCHISM_SDL12_SYM(EventState); SCHISM_SDL12_SYM(GetAppState); return 0; } static int sdl12_events_init(void) { if (!sdl12_init()) return 0; if (sdl12_events_load_syms()) return 0; #if defined(SCHISM_WIN32) || defined(SCHISM_USE_X11) sdl12_EventState(SDL_SYSWMEVENT, SDL_ENABLE); #endif app_state = sdl12_GetAppState(); return 1; } static void sdl12_events_quit(void) { sdl12_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_events_backend_t schism_events_backend_sdl12 = { .init = sdl12_events_init, .quit = sdl12_events_quit, .keymod_state = sdl12_event_mod_state, .pump_events = sdl12_pump_events, }; schismtracker-20250313/sys/sdl12/init.c000066400000000000000000000061571476471630300175130ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "osdefs.h" #include "init.h" #include static int (SDLCALL *sdl12_Init)(Uint32 flags); static void (SDLCALL *sdl12_Quit)(void); static char *(SDLCALL *sdl12_GetError)(void); static int load_sdl12_syms(void); #ifdef SDL12_DYNAMIC_LOAD #include "loadso.h" static void *sdl12_dltrick_handle_ = NULL; static void sdl12_dlend(void) { if (sdl12_dltrick_handle_) { loadso_object_unload(sdl12_dltrick_handle_); sdl12_dltrick_handle_ = NULL; } } static int sdl12_dlinit(void) { // already have it? if (sdl12_dltrick_handle_) return 0; sdl12_dltrick_handle_ = library_load("SDL-1.2", 0, 0); if (!sdl12_dltrick_handle_) { sdl12_dltrick_handle_ = library_load("SDL", 0, 0); if (!sdl12_dltrick_handle_) return -1; } int retval = load_sdl12_syms(); if (retval < 0) sdl12_dlend(); return retval; } SCHISM_STATIC_ASSERT(sizeof(void (*)) == sizeof(void *), "dynamic loading code assumes function pointer and void pointer are of equivalent size"); int sdl12_load_sym(const char *fn, void *addr) { void *func = loadso_function_load(sdl12_dltrick_handle_, fn); if (!func) return 0; memcpy(addr, &func, sizeof(void *)); return 1; } #else static int sdl12_dlinit(void) { load_sdl12_syms(); return 0; } #define sdl12_dlend() // nothing #endif static int load_sdl12_syms(void) { SCHISM_SDL12_SYM(Init); SCHISM_SDL12_SYM(Quit); SCHISM_SDL12_SYM(GetError); return 0; } ////////////////////////////////////////////////////////////////////////////// // this is used so that SDL_Quit is only called // once every backend is done static int roll = 0; // returns non-zero on success or zero on error int sdl12_init(void) { if (!roll) { if (sdl12_dlinit()) return 0; // the subsystems are initialized by the actual backends int r = sdl12_Init(0); if (r < 0) { os_show_message_box("Error initializing SDL 1.2:", sdl12_get_error()); return 0; } } roll++; return roll; } void sdl12_quit(void) { if (roll > 0) roll--; if (roll == 0) { sdl12_Quit(); sdl12_dlend(); } } const char *sdl12_get_error(void) { if (sdl12_GetError) return sdl12_GetError(); return ""; } schismtracker-20250313/sys/sdl12/init.h000066400000000000000000000027571476471630300175220ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_SYS_SDL12_INIT_H_ #define SCHISM_SYS_SDL12_INIT_H_ #include "headers.h" #include int sdl12_init(void); void sdl12_quit(void); // eh? const char *sdl12_get_error(void); #ifdef SDL12_DYNAMIC_LOAD // must be called AFTER sdl12_init() int sdl12_load_sym(const char *fn, void *addr); # define SCHISM_SDL12_SYM(x) \ if (!sdl12_load_sym("SDL_" #x, &sdl12_##x)) return -1 #else # define SCHISM_SDL12_SYM(x) \ sdl12_##x = SDL_##x #endif #endif /* SCHISM_SYS_SDL12_INIT_H_ */ schismtracker-20250313/sys/sdl12/mt.c000066400000000000000000000144331476471630300171640ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "mem.h" #include "backend/mt.h" #include "init.h" #ifdef SCHISM_WIN32 # include #endif /* ------------------------------------ */ #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD static SDL_Thread *(SDLCALL *sdl12_CreateThread)(int (SDLCALL *fn)(void *), void *data, pfnSDL_CurrentBeginThread begin pfnSDL_CurrentEndThread); #else static SDL_Thread *(SDLCALL *sdl12_CreateThread)(int (SDLCALL *fn)(void *), void *data); #endif static void (SDLCALL *sdl12_WaitThread)(SDL_Thread *thread, int *status); static uint32_t (SDLCALL *sdl12_ThreadID)(void); struct mt_thread { SDL_Thread *thread; schism_thread_function_t func; void *userdata; }; static int sdl12_dummy_thread_func(void *userdata) { mt_thread_t *thread = userdata; return thread->func(thread->userdata); } static mt_thread_t *sdl12_thread_create(schism_thread_function_t func, SCHISM_UNUSED const char *name, void *userdata) { mt_thread_t *thread = mem_alloc(sizeof(*thread)); thread->func = func; thread->userdata = userdata; /* ew */ SDL_Thread *sdl_thread = sdl12_CreateThread(sdl12_dummy_thread_func, thread #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD , # ifdef __OS2__ _beginthread, _endthread # elif defined(_WIN32_WCE) NULL, NULL, # else _beginthreadex, _endthreadex # endif #endif ); if (!sdl_thread) { free(thread); return NULL; } thread->thread = sdl_thread; return thread; } static void sdl12_thread_wait(mt_thread_t *thread, int *status) { sdl12_WaitThread(thread->thread, status); free(thread); } static void sdl12_thread_set_priority(SCHISM_UNUSED int priority) { #ifdef SCHISM_WIN32 // equivalent to what SDL 2 does int npri; switch (priority) { #define PRIORITY(x, y) case MT_THREAD_PRIORITY_##x: npri = THREAD_PRIORITY_##y; break PRIORITY(LOW, LOWEST); PRIORITY(NORMAL, NORMAL); PRIORITY(HIGH, HIGHEST); PRIORITY(TIME_CRITICAL, TIME_CRITICAL); default: return; #undef PRIORITY } SetThreadPriority(GetCurrentThread(), npri); #else /* no-op */ #endif } // returns the current thread's ID static mt_thread_id_t sdl12_thread_id(void) { return sdl12_ThreadID(); } /* -------------------------------------------------------------- */ /* mutexes */ static SDL_mutex *(SDLCALL *sdl12_CreateMutex)(void); static void (SDLCALL *sdl12_DestroyMutex)(SDL_mutex *mutex); static int (SDLCALL *sdl12_mutexP)(SDL_mutex *mutex); static int (SDLCALL *sdl12_mutexV)(SDL_mutex *mutex); struct mt_mutex { SDL_mutex *mutex; }; static mt_mutex_t *sdl12_mutex_create(void) { mt_mutex_t *mutex = mem_alloc(sizeof(*mutex)); mutex->mutex = sdl12_CreateMutex(); if (!mutex->mutex) { free(mutex); return NULL; } return mutex; } static void sdl12_mutex_delete(mt_mutex_t *mutex) { sdl12_DestroyMutex(mutex->mutex); free(mutex); } static void sdl12_mutex_lock(mt_mutex_t *mutex) { sdl12_mutexP(mutex->mutex); } static void sdl12_mutex_unlock(mt_mutex_t *mutex) { sdl12_mutexV(mutex->mutex); } /* -------------------------------------------------------------- */ static SDL_cond *(SDLCALL *sdl12_CreateCond)(void); static void (SDLCALL *sdl12_DestroyCond)(SDL_cond *cond); static int (SDLCALL *sdl12_CondSignal)(SDL_cond *cond); static int (SDLCALL *sdl12_CondWait)(SDL_cond *cond, SDL_mutex *mut); static int (SDLCALL *sdl12_CondWaitTimeout)(SDL_cond *cond, SDL_mutex *mut, uint32_t timeout); struct mt_cond { SDL_cond *cond; }; static mt_cond_t *sdl12_cond_create(void) { mt_cond_t *cond = mem_alloc(sizeof(*cond)); cond->cond = sdl12_CreateCond(); if (!cond->cond) { free(cond); return NULL; } return cond; } static void sdl12_cond_delete(mt_cond_t *cond) { sdl12_DestroyCond(cond->cond); free(cond); } static void sdl12_cond_signal(mt_cond_t *cond) { sdl12_CondSignal(cond->cond); } static void sdl12_cond_wait(mt_cond_t *cond, mt_mutex_t *mutex) { sdl12_CondWait(cond->cond, mutex->mutex); } static void sdl12_cond_wait_timeout(mt_cond_t *cond, mt_mutex_t *mutex, uint32_t timeout) { sdl12_CondWaitTimeout(cond->cond, mutex->mutex, timeout); } ////////////////////////////////////////////////////////////////////////////// static int sdl12_threads_load_syms(void) { SCHISM_SDL12_SYM(CreateThread); SCHISM_SDL12_SYM(WaitThread); SCHISM_SDL12_SYM(ThreadID); SCHISM_SDL12_SYM(CreateMutex); SCHISM_SDL12_SYM(DestroyMutex); SCHISM_SDL12_SYM(mutexP); SCHISM_SDL12_SYM(mutexV); SCHISM_SDL12_SYM(CreateCond); SCHISM_SDL12_SYM(DestroyCond); SCHISM_SDL12_SYM(CondSignal); SCHISM_SDL12_SYM(CondWait); SCHISM_SDL12_SYM(CondWaitTimeout); return 0; } static int sdl12_threads_init(void) { if (!sdl12_init()) return 0; if (sdl12_threads_load_syms()) return 0; return 1; } static void sdl12_threads_quit(void) { sdl12_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_mt_backend_t schism_mt_backend_sdl12 = { .init = sdl12_threads_init, .quit = sdl12_threads_quit, .thread_create = sdl12_thread_create, .thread_wait = sdl12_thread_wait, .thread_set_priority = sdl12_thread_set_priority, .thread_id = sdl12_thread_id, .mutex_create = sdl12_mutex_create, .mutex_delete = sdl12_mutex_delete, .mutex_lock = sdl12_mutex_lock, .mutex_unlock = sdl12_mutex_unlock, .cond_create = sdl12_cond_create, .cond_delete = sdl12_cond_delete, .cond_signal = sdl12_cond_signal, .cond_wait = sdl12_cond_wait, .cond_wait_timeout = sdl12_cond_wait_timeout, }; schismtracker-20250313/sys/sdl12/timer.c000066400000000000000000000072421476471630300176640ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "mt.h" #include "mem.h" #include "backend/timer.h" #include "init.h" static int (SDLCALL *sdl12_InitSubSystem)(Uint32 flags); static void (SDLCALL *sdl12_QuitSubSystem)(Uint32 flags); static uint32_t (SDLCALL *sdl12_GetTicks)(void); static void (SDLCALL *sdl12_Delay)(uint32_t ms); static SDL_TimerID (SDLCALL *sdl12_AddTimer)(uint32_t ms, SDL_NewTimerCallback callback, void *param); static timer_ticks_t sdl12_timer_ticks(void) { return sdl12_GetTicks(); } static timer_ticks_t sdl12_timer_ticks_us(void) { // wow return sdl12_timer_ticks() * 1000LL; } static void sdl12_usleep(uint64_t us) { sdl12_Delay(us / 1000); } static void sdl12_msleep(uint32_t ms) { sdl12_Delay(ms); } ////////////////////////////////////////////////////////////////////////////// // oneshot timer #if 0 // XXX: Need to check whether oneshot timers are evaluated FIFO. // From the looks of it this seems to be dependent on the // platform. // SDL 2.0+ uses threaded timers everywhere, which means they are // always FIFO. struct _sdl12_timer_oneshot_curry { void (*callback)(void *param); void *param; }; static uint32_t SDLCALL _sdl12_timer_oneshot_callback(uint32_t interval, void *param) { // NOTE: treat this stuff as read-only to prevent race conditions // and other weird crap. struct _sdl12_timer_oneshot_curry *curry = (struct _sdl12_timer_oneshot_curry *)param; curry->callback(curry->param); free(curry); return 0; } static int sdl12_timer_oneshot(uint32_t interval, void (*callback)(void *param), void *param) { struct _sdl12_timer_oneshot_curry *curry = mem_alloc(sizeof(*curry)); curry->callback = callback; curry->param = param; SDL_TimerID id = sdl12_AddTimer(interval, _sdl12_timer_oneshot_callback, curry); if (!id) free(curry); return !!id; } #endif ////////////////////////////////////////////////////////////////////////////// static int sdl12_timer_load_syms(void) { SCHISM_SDL12_SYM(InitSubSystem); SCHISM_SDL12_SYM(QuitSubSystem); SCHISM_SDL12_SYM(GetTicks); SCHISM_SDL12_SYM(Delay); SCHISM_SDL12_SYM(AddTimer); return 0; } static int sdl12_timer_init(void) { if (!sdl12_init()) return 0; if (sdl12_timer_load_syms()) return 0; if (sdl12_InitSubSystem(SDL_INIT_TIMER) < 0) return 0; return 1; } static void sdl12_timer_quit(void) { sdl12_QuitSubSystem(SDL_INIT_TIMER); sdl12_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_timer_backend_t schism_timer_backend_sdl12 = { .init = sdl12_timer_init, .quit = sdl12_timer_quit, .ticks = sdl12_timer_ticks, .ticks_us = sdl12_timer_ticks_us, .usleep = sdl12_usleep, .msleep = sdl12_msleep, //.oneshot = sdl12_timer_oneshot, }; schismtracker-20250313/sys/sdl12/video.c000066400000000000000000000567661476471630300176710ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define NATIVE_SCREEN_WIDTH 640 #define NATIVE_SCREEN_HEIGHT 400 /* should be the native res of the display (set once and never again) * assumes the user starts schism from the desktop and that the desktop * is the native res (or at least something with square pixels) */ static int display_native_x = -1; static int display_native_y = -1; #include "headers.h" #include "it.h" #include "osdefs.h" #include "vgamem.h" #include "config.h" #include "events.h" #include "mem.h" #include "backend/video.h" #include "init.h" /* bugs * ... in sdl. not in this file :) * * - take special care to call SDL_SetVideoMode _exactly_ once * when on a console (video.desktop.fb_hacks) * */ #if HAVE_SYS_KD_H # include #endif #if HAVE_LINUX_FB_H # include # include #endif #include #include #if HAVE_SYS_IOCTL_H # include #endif #if HAVE_SIGNAL_H #include #endif #include #include #ifdef SCHISM_MACOS # include #endif #include "video.h" #ifndef SCHISM_MACOSX #ifdef SCHISM_WIN32 #include "auto/schismico.h" #else #include "auto/schismico_hires.h" #endif #endif #include "charset.h" #ifdef SCHISM_MACOS # include #endif enum { VIDEO_SURFACE = 0, // removed... //VIDEO_DDRAW = 1, //VIDEO_YUV = 2, //VIDEO_GL = 3, }; static struct video_cf { struct { unsigned int width; unsigned int height; int autoscale; } draw; struct { unsigned int width,height,bpp; int swsurface; int fb_hacks; int fullscreen; int doublebuf; int want_type; int type; } desktop; SDL_Rect clip; SDL_Surface *surface; SDL_Overlay *overlay; struct { unsigned int x; unsigned int y; } mouse; uint32_t pal[256]; uint32_t tc_bgr32[256]; } video; static int _did_init = 0; char *SDL_VideoDriverName(char *namebuf, int maxlen); static int (SDLCALL *sdl12_InitSubSystem)(Uint32 flags); static void (SDLCALL *sdl12_QuitSubSystem)(Uint32 flags); static char *(SDLCALL *sdl12_VideoDriverName)(char *namebuf, int maxlen); static const SDL_VideoInfo *(SDLCALL *sdl12_GetVideoInfo)(void); static void (SDLCALL *sdl12_WM_SetCaption)(const char *title, const char *icon); static SDL_Surface *(SDLCALL *sdl12_SetVideoMode)(int width, int height, int bpp, Uint32 flags); static SDL_Rect **(SDLCALL *sdl12_ListModes)(SDL_PixelFormat *format, Uint32 flags); static int (SDLCALL *sdl12_ShowCursor)(int toggle); static Uint32 (SDLCALL *sdl12_MapRGB)(const SDL_PixelFormat *fmt, Uint8 r, Uint8 g, Uint8 b); static int (SDLCALL *sdl12_SetColors)(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors); static int (SDLCALL *sdl12_LockSurface)(SDL_Surface *surface); static void (SDLCALL *sdl12_Delay)(Uint32 ms); static void (SDLCALL *sdl12_UnlockSurface)(SDL_Surface *surface); static int (SDLCALL *sdl12_Flip)(SDL_Surface *screen); static SDL_GrabMode (SDLCALL *sdl12_WM_GrabInput)(SDL_GrabMode mode); static Uint8 (SDLCALL *sdl12_GetAppState)(void); static void (SDLCALL *sdl12_WarpMouse)(Uint16 x, Uint16 y); static Uint8 (SDLCALL *sdl12_EventState)(Uint8 type, int state); static void (SDLCALL *sdl12_FreeSurface)(SDL_Surface *surface); static void (SDLCALL *sdl12_WM_SetIcon)(SDL_Surface *icon, Uint8 *mask); static SDL_Surface *(SDLCALL *sdl12_CreateRGBSurfaceFrom)(void *pixels, int width, int height, int depth, int pitch, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); static int (SDLCALL *sdl12_EnableUNICODE)(int enable); static int (SDLCALL *sdl12_GetWMInfo)(SDL_SysWMinfo *); #ifdef SCHISM_MACOS static void (SDLCALL *sdl12_InitQuickDraw)(struct QDGlobals *the_qd); #endif static const char *sdl12_video_driver_name(void) { char buf[256]; return str_dup(sdl12_VideoDriverName(buf, 256)); } static void sdl12_video_report(void) { char buf[256]; log_appendf(5, " Using driver '%s'", sdl12_VideoDriverName(buf, 256)); switch (video.desktop.type) { case VIDEO_SURFACE: log_appendf(5, " %s%s video surface", (video.surface->flags & SDL_HWSURFACE) ? "Hardware" : "Software", (video.surface->flags & SDL_HWACCEL) ? " accelerated" : ""); if (SDL_MUSTLOCK(video.surface)) log_append(4, 0, " Must lock surface"); log_appendf(5, " Display format: %d bits/pixel", video.surface->format->BitsPerPixel); break; }; if (video.desktop.fullscreen || video.desktop.fb_hacks) log_appendf(5, " Display dimensions: %dx%d", video.desktop.width, video.desktop.height); } // check if w and h are multiples of native res (and by the same multiplier) static int best_resolution(int w, int h) { if ((w % NATIVE_SCREEN_WIDTH == 0) && (h % NATIVE_SCREEN_HEIGHT == 0) && ((w / NATIVE_SCREEN_WIDTH) == (h / NATIVE_SCREEN_HEIGHT))) { return 1; } else { return 0; } } static int sdl12_video_is_fullscreen(void) { return video.desktop.fullscreen; } static int sdl12_video_width(void) { return video.clip.w; } static int sdl12_video_height(void) { return video.clip.h; } static void sdl12_video_shutdown(void) { if (video.desktop.fullscreen) { video.desktop.fullscreen = 0; video_resize(0,0); } } static void sdl12_video_fullscreen(int tri) { if (tri == 0 || video.desktop.fb_hacks) { video.desktop.fullscreen = 0; } else if (tri == 1) { video.desktop.fullscreen = 1; } else if (tri < 0) { video.desktop.fullscreen = !video.desktop.fullscreen; } if (_did_init) { if (video.desktop.fullscreen) { video_resize(video.desktop.width, video.desktop.height); video_toggle_menu(0); } else { video_toggle_menu(1); video_resize(0, 0); } video_report(); } } static void sdl12_video_setup(const char *interpolation) { strncpy(cfg_video_interpolation, interpolation, 7); } static void sdl12_video_startup(void) { SDL_Rect **modes; int i, x = -1, y = -1; // center the window on startup by default; this is what the SDL 2 backend does... int center_enabled = 0; if (!getenv("SDL_VIDEO_WINDOW_POS")) { setenv("SDL_VIDEO_WINDOW_POS", "center", 1); center_enabled = 1; } /* get monitor native res (assumed to be user's desktop res) * first time we start video */ if (display_native_x < 0 || display_native_y < 0) { const SDL_VideoInfo* info = sdl12_GetVideoInfo(); display_native_x = info->current_w; display_native_y = info->current_h; } sdl12_WM_SetCaption("Schism Tracker", "Schism Tracker"); #ifndef SCHISM_MACOSX /* apple/macs use a bundle; this overrides their nice pretty icon */ { const char **xpm; #ifdef SCHISM_WIN32 xpm = _schism_icon_xpm; #else xpm = _schism_icon_xpm_hires; #endif uint32_t *pixels; int width, height; if (!xpmdata(xpm, &pixels, &width, &height)) { SDL_Surface *icon = sdl12_CreateRGBSurfaceFrom(pixels, width, height, 32, width * sizeof(uint32_t), 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); if (icon) { sdl12_WM_SetIcon(icon, NULL); sdl12_FreeSurface(icon); } free(pixels); } } #endif #if HAVE_LINUX_FB_H if (!getenv("DISPLAY") && !video.desktop.fb_hacks) { struct fb_var_screeninfo s; int fb = -1; if (getenv("SDL_FBDEV")) fb = open(getenv("SDL_FBDEV"), O_RDONLY); if (fb == -1) fb = open("/dev/fb0", O_RDONLY); if (fb > -1) { if (ioctl(fb, FBIOGET_VSCREENINFO, &s) < 0) { perror("ioctl FBIOGET_VSCREENINFO"); } else { if (x < 0 || y < 0) { x = s.xres; if (x < NATIVE_SCREEN_WIDTH) x = NATIVE_SCREEN_WIDTH; y = s.yres; } setenv("SDL_VIDEODRIVER", "fbcon", 1); video.desktop.bpp = s.bits_per_pixel; video.desktop.fb_hacks = 1; video.desktop.doublebuf = 1; video.desktop.fullscreen = 0; video.desktop.swsurface = 0; video.surface = sdl12_SetVideoMode(x,y, video.desktop.bpp, SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_ASYNCBLIT); } close(fb); } } #endif if (!video.surface) { /* if we already got one... */ video.surface = sdl12_SetVideoMode(640,400,0,SDL_RESIZABLE); if (!video.surface) { // ok perror("SDL_SetVideoMode"); exit(255); } } video.desktop.bpp = video.surface->format->BitsPerPixel; if (x < 0 || y < 0) { modes = sdl12_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE); if (modes != (SDL_Rect**)0 && modes != (SDL_Rect**)-1) { for (i = 0; modes[i]; i++) { if (modes[i]->w < NATIVE_SCREEN_WIDTH) continue; if (modes[i]->h < NATIVE_SCREEN_HEIGHT)continue; if (x == -1 || y == -1 || modes[i]->w < x || modes[i]->h < y) { if (modes[i]->w != NATIVE_SCREEN_WIDTH || modes[i]->h != NATIVE_SCREEN_HEIGHT) { if (x == NATIVE_SCREEN_WIDTH || y == NATIVE_SCREEN_HEIGHT) continue; } x = modes[i]->w; y = modes[i]->h; if (best_resolution(x,y)) break; } } } } if (x < 0 || y < 0) { x = 640; y = 480; } /*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_SURFACE: /* no scaling when using the SDL surfaces directly */ video.desktop.swsurface = !cfg_video_hardware; video.desktop.want_type = VIDEO_SURFACE; break; }; /* okay, i think we're ready */ sdl12_ShowCursor(SDL_DISABLE); video.desktop.fullscreen = cfg_video_fullscreen; _did_init = 1; // This call actually creates the surface. video_fullscreen(video.desktop.fullscreen); // We have to unset this variable, because otherwise // SDL will re-center the window every time it's // resized. if (center_enabled) unsetenv("SDL_VIDEO_WINDOW_POS"); #ifdef SCHISM_WIN32 /* We want to edit the window style so it accepts drag & drop */ SDL_SysWMinfo wm_info; SDL_VERSION(&wm_info.version); if (sdl12_GetWMInfo(&wm_info)) { LONG_PTR x = GetWindowLongPtrA(wm_info.window, GWL_EXSTYLE); SetWindowLongPtrA(wm_info.window, GWL_EXSTYLE, x | WS_EX_ACCEPTFILES); } #endif } static SDL_Surface *_setup_surface(unsigned int w, unsigned int h, unsigned int sdlflags) { int want_fixed = cfg_video_want_fixed; if (video.desktop.doublebuf) sdlflags |= (SDL_DOUBLEBUF|SDL_ASYNCBLIT); if (video.desktop.fullscreen) { w = video.desktop.width; h = video.desktop.height; } else { sdlflags |= SDL_RESIZABLE; } // What? if (want_fixed == -1 && best_resolution(w,h)) { want_fixed = 0; } if (want_fixed) { double ratio_w = (double)w / (double)cfg_video_want_fixed_width; double ratio_h = (double)h / (double)cfg_video_want_fixed_height; if (ratio_w < ratio_h) { video.clip.w = w; video.clip.h = (double)cfg_video_want_fixed_height * ratio_w; } else { video.clip.h = h; video.clip.w = (double)cfg_video_want_fixed_width * ratio_h; } video.clip.x=(w-video.clip.w)/2; video.clip.y=(h-video.clip.h)/2; } else { video.clip.x = 0; video.clip.y = 0; video.clip.w = w; video.clip.h = h; } if (video.desktop.fb_hacks && video.surface) { /* the original one will be _just fine_ */ } else { if (video.desktop.fullscreen) { sdlflags &=~SDL_RESIZABLE; sdlflags |= SDL_FULLSCREEN; } else { sdlflags &=~SDL_FULLSCREEN; sdlflags |= SDL_RESIZABLE; } sdlflags |= (video.desktop.swsurface ? SDL_SWSURFACE : SDL_HWSURFACE); /* if using swsurface, get a surface the size of the whole native monitor res * to avoid issues with weirdo display modes * get proper aspect ratio and surface of correct size */ if (video.desktop.fullscreen && video.desktop.swsurface) { double ar = NATIVE_SCREEN_WIDTH / (double) NATIVE_SCREEN_HEIGHT; // ar = 4.0 / 3.0; want_fixed = 1; // uncomment for 4:3 fullscreen // get maximum size that can be this AR if ((display_native_y * ar) > display_native_x) { video.clip.h = display_native_x / ar; video.clip.w = display_native_x; } else { video.clip.h = display_native_y; video.clip.w = display_native_y * ar; } // clip to size (i.e. letterbox if necessary) video.clip.x = (display_native_x - video.clip.w) / 2; video.clip.y = (display_native_y - video.clip.h) / 2; // get a surface the size of the whole screen @ native res w = display_native_x; h = display_native_y; /* if we don't care about getting the right aspect ratio, * sod letterboxing and just get a surface the size of the entire display */ if (!want_fixed) { video.clip.w = display_native_x; video.clip.h = display_native_y; video.clip.x = 0; video.clip.y = 0; } } video.surface = sdl12_SetVideoMode(w, h, video.desktop.bpp, sdlflags); } if (!video.surface) { perror("SDL_SetVideoMode"); exit(EXIT_FAILURE); } return video.surface; } static void sdl12_video_resize(unsigned int width, unsigned int height) { if (!width) width = cfg_video_width; if (!height) height = cfg_video_height; video.draw.width = width; video.draw.height = height; video.draw.autoscale = 1; switch (video.desktop.want_type) { case VIDEO_SURFACE: video.draw.autoscale = 0; if (video.desktop.fb_hacks && (video.desktop.width != NATIVE_SCREEN_WIDTH || video.desktop.height != NATIVE_SCREEN_HEIGHT)) { video.draw.autoscale = 1; } _setup_surface(width, height, 0); video.desktop.type = VIDEO_SURFACE; break; default: break; }; status.flags |= (NEED_UPDATE); } static void _sdl_pal(int i, int rgb[3]) { video.pal[i] = sdl12_MapRGB(video.surface->format, rgb[0], rgb[1], rgb[2]); } static void _bgr32_pal(int i, int rgb[3]) { video.tc_bgr32[i] = rgb[2] | (rgb[1] << 8) | (rgb[0] << 16) | (255 << 24); } static void sdl12_video_colors(unsigned char palette[16][3]) { void (*fun)(int i,int rgb[3]); const int lastmap[] = { 0,1,2,3,5 }; int rgb[3], i, j, p; switch (video.desktop.type) { case VIDEO_SURFACE: fun = _sdl_pal; break; default: /* eh? */ return; }; /* make our "base" space */ for (i = 0; i < 16; i++) { rgb[0]=palette[i][0]; rgb[1]=palette[i][1]; rgb[2]=palette[i][2]; fun(i, rgb); _bgr32_pal(i, rgb); } /* make our "gradient" space */ for (i = 128; i < 256; i++) { j = i - 128; p = lastmap[(j>>5)]; rgb[0] = (int)palette[p][0] + (((int)(palette[p+1][0] - palette[p][0]) * (j&31)) /32); rgb[1] = (int)palette[p][1] + (((int)(palette[p+1][1] - palette[p][1]) * (j&31)) /32); rgb[2] = (int)palette[p][2] + (((int)(palette[p+1][2] - palette[p][2]) * (j&31)) /32); fun(i, rgb); _bgr32_pal(i, rgb); } } static uint32_t sdl12_map_rgb_callback(void *data, uint8_t r, uint8_t g, uint8_t b) { SDL_PixelFormat *format = (SDL_PixelFormat *)data; return sdl12_MapRGB(format, r, g, b); } SCHISM_HOT static void sdl12_video_blit(void) { unsigned char *pixels = NULL; unsigned int bpp = 0; unsigned int pitch = 0; switch (video.desktop.type) { case VIDEO_SURFACE: if (SDL_MUSTLOCK(video.surface)) { while (sdl12_LockSurface(video.surface) == -1) { sdl12_Delay(10); } } bpp = video.surface->format->BytesPerPixel; pixels = (unsigned char *)video.surface->pixels; if (cfg_video_want_fixed) { pixels += video.clip.y * video.surface->pitch; pixels += video.clip.x * bpp; } pitch = video.surface->pitch; break; }; if (video.draw.autoscale || (video.clip.w == NATIVE_SCREEN_WIDTH && video.clip.h == NATIVE_SCREEN_HEIGHT)) { /* scaling is provided by the hardware, or isn't necessary */ video_blit11(bpp, pixels, pitch, video.pal); } else { if (!charset_strcasecmp(cfg_video_interpolation, CHARSET_UTF8, "nearest", CHARSET_UTF8)) { video_blitNN(bpp, pixels, pitch, video.pal, video.clip.w, video.clip.h); } else { video_blitLN(bpp, pixels, pitch, video.tc_bgr32, video.clip.w, video.clip.h, sdl12_map_rgb_callback, video.surface->format); } } switch (video.desktop.type) { case VIDEO_SURFACE: if (SDL_MUSTLOCK(video.surface)) { sdl12_UnlockSurface(video.surface); } sdl12_Flip(video.surface); break; }; } static void sdl12_video_translate(unsigned int vx, unsigned int vy, unsigned int *x, unsigned int *y) { vx = MAX(vx, video.clip.x); vx -= video.clip.x; vy = MAX(vy, video.clip.y); vy -= video.clip.y; vx = MIN(vx, video.clip.w); vy = MIN(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_mousecursor_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; } static void sdl12_video_get_logical_coordinates(int x, int y, int *trans_x, int *trans_y) { if (trans_x) *trans_x = x; if (trans_y) *trans_y = y; } static int sdl12_video_is_hardware(void) { switch (video.desktop.type) { case VIDEO_SURFACE: return !!(video.surface->flags & SDL_HWSURFACE); }; return 0; } static int sdl12_video_is_input_grabbed(void) { return sdl12_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_ON; } static void sdl12_video_set_input_grabbed(SCHISM_UNUSED int enabled) { sdl12_WM_GrabInput(enabled ? SDL_GRAB_ON : SDL_GRAB_OFF); } static int sdl12_video_is_focused(void) { return (sdl12_GetAppState() & (SDL_APPMOUSEFOCUS | SDL_APPINPUTFOCUS)); } static int sdl12_video_is_visible(void) { return (sdl12_GetAppState() & (SDL_APPACTIVE)); } static int sdl12_video_is_screensaver_enabled(void) { // assume its enabled return 1; } static void sdl12_video_toggle_screensaver(SCHISM_UNUSED int enabled) { /* SDL 1.2 doesn't provide this */ } static int sdl12_video_is_wm_available(void) { return (sdl12_GetVideoInfo()->wm_available); } static void sdl12_video_warp_mouse(unsigned int x, unsigned int y) { sdl12_WarpMouse(x, y); } static void sdl12_video_set_hardware(int hardware) { video.desktop.swsurface = !hardware; // recreate the surface with the same size... video_resize(video.draw.width, video.draw.height); video_report(); } static void sdl12_video_get_mouse_coordinates(unsigned int *x, unsigned int *y) { *x = video.mouse.x; *y = video.mouse.y; } static int sdl12_video_have_menu(void) { #ifdef SCHISM_WIN32 return 1; #else return 0; #endif } static void sdl12_video_toggle_menu(SCHISM_UNUSED int on) { if (!video_have_menu()) return; int width, height; int cache_size = 0; #ifdef SCHISM_WIN32 /* Get the HWND */ SDL_SysWMinfo wm_info; SDL_VERSION(&wm_info.version); if (!sdl12_GetWMInfo(&wm_info)) return; WINDOWPLACEMENT placement; placement.length = sizeof(placement); GetWindowPlacement(wm_info.window, &placement); cache_size = (placement.showCmd == SW_MAXIMIZE); #endif if (cache_size) { width = video.draw.width; height = video.draw.height; } #ifdef SCHISM_WIN32 win32_toggle_menu(wm_info.window, on); #endif if (cache_size) { video_resize(width, height); } } static void sdl12_video_mousecursor_changed(void) { const int vis = video_mousecursor_visible(); sdl12_ShowCursor(vis == MOUSE_SYSTEM); // Totally turn off mouse event sending when the mouse is disabled int evstate = (vis == MOUSE_DISABLED) ? SDL_DISABLE : SDL_ENABLE; if (evstate != sdl12_EventState(SDL_MOUSEMOTION, SDL_QUERY)) { sdl12_EventState(SDL_MOUSEMOTION, evstate); sdl12_EventState(SDL_MOUSEBUTTONDOWN, evstate); sdl12_EventState(SDL_MOUSEBUTTONUP, evstate); } } ////////////////////////////////////////////////////////////////////////////// static int sdl12_video_get_wm_data(video_wm_data_t *wm_data) { SDL_SysWMinfo info; SDL_VERSION(&info.version); if (!sdl12_GetWMInfo(&info)) return 0; #ifdef SCHISM_WIN32 wm_data->subsystem = VIDEO_WM_DATA_SUBSYSTEM_WINDOWS; wm_data->data.windows.hwnd = info.window; // don't care about other values for now #else # ifdef SDL_VIDEO_DRIVER_X11 if (info.subsystem == SDL_SYSWM_X11) { wm_data->subsystem = VIDEO_WM_DATA_SUBSYSTEM_X11; wm_data->data.x11.display = info.info.x11.display; wm_data->data.x11.window = info.info.x11.window; wm_data->data.x11.lock_func = info.info.x11.lock_func; wm_data->data.x11.unlock_func = info.info.x11.unlock_func; } # endif #endif return 1; } ////////////////////////////////////////////////////////////////////////////// static void sdl12_video_show_cursor(int enabled) { sdl12_ShowCursor(enabled ? SDL_ENABLE : SDL_DISABLE); } ////////////////////////////////////////////////////////////////////////////// static int sdl12_video_load_syms(void) { SCHISM_SDL12_SYM(InitSubSystem); SCHISM_SDL12_SYM(QuitSubSystem); SCHISM_SDL12_SYM(VideoDriverName); SCHISM_SDL12_SYM(GetVideoInfo); SCHISM_SDL12_SYM(WM_SetCaption); SCHISM_SDL12_SYM(SetVideoMode); SCHISM_SDL12_SYM(ListModes); SCHISM_SDL12_SYM(ShowCursor); SCHISM_SDL12_SYM(MapRGB); SCHISM_SDL12_SYM(SetColors); SCHISM_SDL12_SYM(LockSurface); SCHISM_SDL12_SYM(UnlockSurface); SCHISM_SDL12_SYM(Delay); SCHISM_SDL12_SYM(Flip); SCHISM_SDL12_SYM(WM_GrabInput); SCHISM_SDL12_SYM(WM_SetIcon); SCHISM_SDL12_SYM(GetAppState); SCHISM_SDL12_SYM(WarpMouse); SCHISM_SDL12_SYM(EventState); SCHISM_SDL12_SYM(FreeSurface); SCHISM_SDL12_SYM(CreateRGBSurfaceFrom); SCHISM_SDL12_SYM(EnableUNICODE); SCHISM_SDL12_SYM(GetWMInfo); #ifdef SCHISM_MACOS SCHISM_SDL12_SYM(InitQuickDraw); #endif return 0; } static int sdl12_video_init(void) { if (!sdl12_init()) return 0; if (sdl12_video_load_syms()) { sdl12_quit(); return 0; } if (sdl12_InitSubSystem(SDL_INIT_VIDEO) < 0) { sdl12_quit(); return 0; } #ifdef SCHISM_MACOS sdl12_InitQuickDraw(&qd); #endif sdl12_EnableUNICODE(1); if (!events_init(&schism_events_backend_sdl12)) { sdl12_QuitSubSystem(SDL_INIT_VIDEO); sdl12_quit(); return 0; } return 1; } static void sdl12_video_quit(void) { sdl12_QuitSubSystem(SDL_INIT_VIDEO); sdl12_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_video_backend_t schism_video_backend_sdl12 = { .init = sdl12_video_init, .quit = sdl12_video_quit, .startup = sdl12_video_startup, .shutdown = sdl12_video_shutdown, .is_fullscreen = sdl12_video_is_fullscreen, .width = sdl12_video_width, .height = sdl12_video_height, .driver_name = sdl12_video_driver_name, .report = sdl12_video_report, .set_hardware = sdl12_video_set_hardware, .setup = sdl12_video_setup, .fullscreen = sdl12_video_fullscreen, .resize = sdl12_video_resize, .colors = sdl12_video_colors, .is_focused = sdl12_video_is_focused, .is_visible = sdl12_video_is_visible, .is_wm_available = sdl12_video_is_wm_available, .is_hardware = sdl12_video_is_hardware, .is_screensaver_enabled = sdl12_video_is_screensaver_enabled, .toggle_screensaver = sdl12_video_toggle_screensaver, .translate = sdl12_video_translate, .get_logical_coordinates = sdl12_video_get_logical_coordinates, .is_input_grabbed = sdl12_video_is_input_grabbed, .set_input_grabbed = sdl12_video_set_input_grabbed, .warp_mouse = sdl12_video_warp_mouse, .get_mouse_coordinates = sdl12_video_get_mouse_coordinates, .have_menu = sdl12_video_have_menu, .toggle_menu = sdl12_video_toggle_menu, .blit = sdl12_video_blit, .mousecursor_changed = sdl12_video_mousecursor_changed, .get_wm_data = sdl12_video_get_wm_data, .show_cursor = sdl12_video_show_cursor, }; schismtracker-20250313/sys/sdl2/000077500000000000000000000000001476471630300163125ustar00rootroot00000000000000schismtracker-20250313/sys/sdl2/audio.c000066400000000000000000000232621476471630300175640ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "init.h" #include "headers.h" #include "mem.h" #include "backend/audio.h" struct schism_audio_device { SDL_AudioDeviceID id; void (*callback)(uint8_t *stream, int len); }; #ifndef SDL_AUDIO_ALLOW_SAMPLES_CHANGE /* added in SDL 2.0.9 */ #define SDL_AUDIO_ALLOW_SAMPLES_CHANGE 0x00000008 #endif static int (SDLCALL *sdl2_InitSubSystem)(uint32_t flags); static void (SDLCALL *sdl2_QuitSubSystem)(uint32_t flags); static int (SDLCALL *sdl2_AudioInit)(const char *driver_name); static void (SDLCALL *sdl2_AudioQuit)(void); static int (SDLCALL *sdl2_GetNumAudioDrivers)(void) = NULL; static const char *(SDLCALL *sdl2_GetAudioDriver)(int i) = NULL; static int (SDLCALL *sdl2_GetNumAudioDevices)(int) = NULL; static const char *(SDLCALL *sdl2_GetAudioDeviceName)(int, int) = NULL; static SDL_AudioDeviceID (SDLCALL *sdl2_OpenAudioDevice)(const char *device, int iscapture, const SDL_AudioSpec *desired, SDL_AudioSpec *obtained, int allowed_changes); static void (SDLCALL *sdl2_CloseAudioDevice)(SDL_AudioDeviceID dev); static void (SDLCALL *sdl2_LockAudioDevice)(SDL_AudioDeviceID dev); static void (SDLCALL *sdl2_UnlockAudioDevice)(SDL_AudioDeviceID dev); static void (SDLCALL *sdl2_PauseAudioDevice)(SDL_AudioDeviceID dev, int pause_on); static const char * (SDLCALL *sdl2_GetError)(void); static void (SDLCALL *sdl2_ClearError)(void); static int (SDLCALL *sdl2_SetError)(const char *fmt, ...); static void (SDLCALL *sdl2_GetVersion)(SDL_version * ver); /* explanation for this: * in 2.0.18, the logic for SDL's audio initialization functions * changed, so that you can use SDL_AudioInit() directly without * any repercussions; before that, SDL would do a sanity check * calling SDL_WasInit() which surprise surprise doesn't actually * get initialized from SDL_AudioInit(). to work around this, we * have to use a separate audio driver initialization function * under SDL pre-2.0.18. */ static int SDLCALL schism_init_audio_impl(const char *name) { const char *orig_drv = getenv("SDL_AUDIODRIVER"); if (name) setenv("SDL_AUDIODRIVER", name, 1); int ret = sdl2_InitSubSystem(SDL_INIT_AUDIO); /* clean up our dirty work, or empty the var */ if (name) { if (orig_drv) { setenv("SDL_AUDIODRIVER", orig_drv, 1); } else { unsetenv("SDL_AUDIODRIVER"); } } /* forward any error, if any */ return ret; } static void SDLCALL schism_quit_audio_impl(void) { sdl2_QuitSubSystem(SDL_INIT_AUDIO); } // static int (SDLCALL *sdl2_audio_init_func)(const char *) = schism_init_audio_impl; static void (SDLCALL *sdl2_audio_quit_func)(void) = schism_quit_audio_impl; /* ---------------------------------------------------------- */ /* drivers */ static int sdl2_audio_driver_count(void) { return sdl2_GetNumAudioDrivers(); } static const char *sdl2_audio_driver_name(int i) { return sdl2_GetAudioDriver(i); } /* --------------------------------------------------------------- */ static uint32_t sdl2_audio_device_count(void) { int x = sdl2_GetNumAudioDevices(0); return MAX(x, 0); } static const char *sdl2_audio_device_name(uint32_t i) { return sdl2_GetAudioDeviceName(i, 0); } /* ---------------------------------------------------------- */ static int sdl2_audio_init_driver(const char *driver) { int x = sdl2_audio_init_func(driver); if (x < 0) return x; // force poll for audio devices (void)sdl2_GetNumAudioDevices(0); return 0; } static void sdl2_audio_quit_driver(void) { sdl2_audio_quit_func(); } /* -------------------------------------------------------- */ // This is here to prevent having to put SDLCALL into // the original audio callback static void SDLCALL sdl2_dummy_callback(void *userdata, uint8_t *stream, int len) { // call our own callback schism_audio_device_t *dev = userdata; dev->callback(stream, len); } // nonzero on success static inline int sdl2_audio_open_device_impl(schism_audio_device_t *dev, const char *name, const SDL_AudioSpec *desired, SDL_AudioSpec *obtained, int change) { // cache the current error const char *err = sdl2_GetError(); sdl2_ClearError(); SDL_AudioDeviceID id = sdl2_OpenAudioDevice(name, 0, desired, obtained, change); const char *new_err = sdl2_GetError(); int failed = (new_err && *new_err); // reset the original error sdl2_SetError("%s", err); if (!failed) { dev->id = id; return 1; } return 0; } static schism_audio_device_t *sdl2_audio_open_device(uint32_t id, const schism_audio_spec_t *desired, schism_audio_spec_t *obtained) { schism_audio_device_t *dev = mem_calloc(1, sizeof(*dev)); dev->callback = desired->callback; uint32_t format; switch (desired->bits) { case 8: format = AUDIO_U8; break; default: case 16: format = AUDIO_S16SYS; break; case 32: format = AUDIO_S32SYS; break; } SDL_AudioSpec sdl_desired = {0}; sdl_desired.freq = desired->freq; sdl_desired.format = format; sdl_desired.channels = desired->channels; sdl_desired.samples = desired->samples; sdl_desired.callback = sdl2_dummy_callback; sdl_desired.userdata = dev; SDL_AudioSpec sdl_obtained; const char *name = (id != AUDIO_BACKEND_DEFAULT) ? sdl2_GetAudioDeviceName(id, 0) : NULL; // First try opening the device without any change at all // (well, except frequencies ;)) if (sdl2_audio_open_device_impl(dev, name, &sdl_desired, &sdl_obtained, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE)) goto got_device; // Ok, try opening it until we find something that fits. int change = SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE | SDL_AUDIO_ALLOW_SAMPLES_CHANGE | SDL_AUDIO_ALLOW_FORMAT_CHANGE; // !!! FIXME: SDL_GetAudioDeviceName might change if (sdl2_audio_open_device_impl(dev, name, &sdl_desired, &sdl_obtained, change)) { int need_reopen = 0; switch (sdl_obtained.format) { case AUDIO_U8: case AUDIO_S16SYS: case AUDIO_S32SYS: break; default: change &= ~(SDL_AUDIO_ALLOW_FORMAT_CHANGE); need_reopen = 1; break; } switch (sdl_obtained.channels) { case 1: case 2: break; default: change &= ~(SDL_AUDIO_ALLOW_CHANNELS_CHANGE); need_reopen = 1; break; } if (!need_reopen) goto got_device; sdl2_CloseAudioDevice(dev->id); if (sdl2_audio_open_device_impl(dev, name, &sdl_desired, &sdl_obtained, change)) goto got_device; } free(dev); return NULL; got_device: memset(obtained, 0, sizeof(*obtained)); obtained->freq = sdl_obtained.freq; obtained->bits = SDL_AUDIO_BITSIZE(sdl_obtained.format); obtained->channels = sdl_obtained.channels; obtained->samples = sdl_obtained.samples; return dev; } static void sdl2_audio_close_device(schism_audio_device_t *dev) { if (!dev) return; sdl2_CloseAudioDevice(dev->id); free(dev); } static void sdl2_audio_lock_device(schism_audio_device_t *dev) { if (!dev) return; sdl2_LockAudioDevice(dev->id); } static void sdl2_audio_unlock_device(schism_audio_device_t *dev) { if (!dev) return; sdl2_UnlockAudioDevice(dev->id); } static void sdl2_audio_pause_device(schism_audio_device_t *dev, int paused) { if (!dev) return; sdl2_PauseAudioDevice(dev->id, paused); } ////////////////////////////////////////////////////////////////////////////// // dynamic loading static int sdl2_audio_load_syms(void) { SCHISM_SDL2_SYM(InitSubSystem); SCHISM_SDL2_SYM(QuitSubSystem); SCHISM_SDL2_SYM(AudioInit); SCHISM_SDL2_SYM(AudioQuit); SCHISM_SDL2_SYM(GetNumAudioDrivers); SCHISM_SDL2_SYM(GetAudioDriver); SCHISM_SDL2_SYM(GetNumAudioDevices); SCHISM_SDL2_SYM(GetAudioDeviceName); SCHISM_SDL2_SYM(GetVersion); SCHISM_SDL2_SYM(OpenAudioDevice); SCHISM_SDL2_SYM(CloseAudioDevice); SCHISM_SDL2_SYM(LockAudioDevice); SCHISM_SDL2_SYM(UnlockAudioDevice); SCHISM_SDL2_SYM(PauseAudioDevice); SCHISM_SDL2_SYM(GetError); SCHISM_SDL2_SYM(ClearError); SCHISM_SDL2_SYM(SetError); return 0; } static int sdl2_audio_init(void) { if (!sdl2_init()) return 0; if (sdl2_audio_load_syms()) return 0; // see if we can use the normal audio init and quit functions SDL_version ver; sdl2_GetVersion(&ver); if (SDL2_VERSION_ATLEAST(ver, 2, 0, 18)) { sdl2_audio_init_func = sdl2_AudioInit; sdl2_audio_quit_func = sdl2_AudioQuit; } return 1; } static void sdl2_audio_quit(void) { // the subsystem quitting is handled by the quit driver function sdl2_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_audio_backend_t schism_audio_backend_sdl2 = { .init = sdl2_audio_init, .quit = sdl2_audio_quit, .driver_count = sdl2_audio_driver_count, .driver_name = sdl2_audio_driver_name, .device_count = sdl2_audio_device_count, .device_name = sdl2_audio_device_name, .init_driver = sdl2_audio_init_driver, .quit_driver = sdl2_audio_quit_driver, .open_device = sdl2_audio_open_device, .close_device = sdl2_audio_close_device, .lock_device = sdl2_audio_lock_device, .unlock_device = sdl2_audio_unlock_device, .pause_device = sdl2_audio_pause_device, }; schismtracker-20250313/sys/sdl2/clippy.c000066400000000000000000000076611476471630300177700ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "init.h" #include "headers.h" /* always include this one first, kthx */ #include "backend/clippy.h" #include "mem.h" static int enable_primary_selection = 0; #if defined(SDL2_DYNAMIC_LOAD) || SDL_VERSION_ATLEAST(2, 26, 0) static SDL_bool (SDLCALL *sdl2_HasPrimarySelectionText)(void); static int (SDLCALL *sdl2_SetPrimarySelectionText)(const char *text); static char * (SDLCALL *sdl2_GetPrimarySelectionText)(void); #endif static SDL_bool (SDLCALL *sdl2_HasClipboardText)(void); static int (SDLCALL *sdl2_SetClipboardText)(const char *text); static char * (SDLCALL *sdl2_GetClipboardText)(void); static void (SDLCALL *sdl2_free)(void *); static int sdl2_clippy_have_selection(void) { #if defined(SDL2_DYNAMIC_LOAD) || SDL_VERSION_ATLEAST(2, 26, 0) if (enable_primary_selection) return sdl2_HasPrimarySelectionText(); #endif return 0; } static int sdl2_clippy_have_clipboard(void) { return sdl2_HasClipboardText(); } static void sdl2_clippy_set_selection(const char *text) { #if defined(SDL2_DYNAMIC_LOAD) || SDL_VERSION_ATLEAST(2, 26, 0) if (enable_primary_selection) sdl2_SetPrimarySelectionText(text); #endif } static void sdl2_clippy_set_clipboard(const char *text) { sdl2_SetClipboardText(text); } static char *sdl2_clippy_get_selection(void) { #if defined(SDL2_DYNAMIC_LOAD) || SDL_VERSION_ATLEAST(2, 26, 0) if (enable_primary_selection) { char *sdl = sdl2_GetPrimarySelectionText(); if (sdl) { char *us = str_dup(sdl); sdl2_free(sdl); return us; } } #endif return str_dup(""); } static char *sdl2_clippy_get_clipboard(void) { char *sdl = sdl2_GetClipboardText(); if (!sdl) return str_dup(""); char *us = str_dup(sdl); sdl2_free(sdl); return us; } ////////////////////////////////////////////////////////////////////////////// // dynamic loading static int sdl2_clippy_load_syms(void) { SCHISM_SDL2_SYM(HasClipboardText); SCHISM_SDL2_SYM(SetClipboardText); SCHISM_SDL2_SYM(GetClipboardText); SCHISM_SDL2_SYM(free); return 0; } static int sdl2_26_0_clippy_load_syms(void) { #if defined(SDL2_DYNAMIC_LOAD) || SDL_VERSION_ATLEAST(2, 26, 0) SCHISM_SDL2_SYM(HasPrimarySelectionText); SCHISM_SDL2_SYM(SetPrimarySelectionText); SCHISM_SDL2_SYM(GetPrimarySelectionText); return 0; #else return -1; #endif } static int sdl2_clippy_init(void) { if (!sdl2_init()) return 0; if (sdl2_clippy_load_syms()) return 0; if (!sdl2_26_0_clippy_load_syms()) enable_primary_selection = 1; return 1; } static void sdl2_clippy_quit(void) { sdl2_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_clippy_backend_t schism_clippy_backend_sdl2 = { .init = sdl2_clippy_init, .quit = sdl2_clippy_quit, .have_selection = sdl2_clippy_have_selection, .get_selection = sdl2_clippy_get_selection, .set_selection = sdl2_clippy_set_selection, .have_clipboard = sdl2_clippy_have_clipboard, .get_clipboard = sdl2_clippy_get_clipboard, .set_clipboard = sdl2_clippy_set_clipboard, }; schismtracker-20250313/sys/sdl2/dmoz.c000066400000000000000000000037341476471630300174360ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "init.h" #include "headers.h" /* always include this one first, kthx */ #include "backend/dmoz.h" #include "mem.h" static char * (SDLCALL *sdl2_GetBasePath)(void); static void (SDLCALL *sdl2_free)(void *); static char *sdl2_dmoz_get_exe_path(void) { char *sdl = sdl2_GetBasePath(); if (!sdl) return NULL; char *us = str_dup(sdl); sdl2_free(sdl); return us; } ////////////////////////////////////////////////////////////////////////////// // dynamic loading static int sdl2_dmoz_load_syms(void) { SCHISM_SDL2_SYM(GetBasePath); SCHISM_SDL2_SYM(free); return 0; } static int sdl2_dmoz_init(void) { if (!sdl2_init()) return 0; if (sdl2_dmoz_load_syms()) return 0; return 1; } static void sdl2_dmoz_quit(void) { sdl2_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_dmoz_backend_t schism_dmoz_backend_sdl2 = { .init = sdl2_dmoz_init, .quit = sdl2_dmoz_quit, .get_exe_path = sdl2_dmoz_get_exe_path, }; schismtracker-20250313/sys/sdl2/events.c000066400000000000000000000460541476471630300177730ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "init.h" #include "headers.h" #include "events.h" #include "backend/events.h" #include "util.h" #include "mem.h" #include "video.h" #include /* need to redefine these on SDL < 2.0.4 */ #if !SDL_VERSION_ATLEAST(2, 0, 4) #define SDL_AUDIODEVICEADDED (0x1100) #define SDL_AUDIODEVICEREMOVED (0x1101) #endif static int (SDLCALL *sdl2_InitSubSystem)(Uint32 flags) = NULL; static void (SDLCALL *sdl2_QuitSubSystem)(Uint32 flags) = NULL; static SDL_Keymod (SDLCALL *sdl2_GetModState)(void); static int (SDLCALL *sdl2_PollEvent)(SDL_Event *event) = NULL; static SDL_bool (SDLCALL *sdl2_IsTextInputActive)(void) = NULL; static void (SDLCALL *sdl2_free)(void *) = NULL; static void (SDLCALL *sdl2_GetVersion)(SDL_version * ver) = NULL; static Uint8 (SDLCALL *sdl2_EventState)(Uint32 type, int state) = NULL; // whether SDL's wheel event gives mouse coordinates or not static int wheel_have_mouse_coordinates = 0; #ifdef SCHISM_CONTROLLER // Okay, this is a bit stupid; unlike the regular events these // are actually handled and mapped in this file rather than in the // main logic. This really ought to not be the case and the events // should just be sent to the main file as-is so it can handle it. static SDL_bool (SDLCALL *sdl2_IsGameController)(int joystick_index) = NULL; static SDL_GameController* (SDLCALL *sdl2_GameControllerOpen)(int joystick_index) = NULL; static void (SDLCALL *sdl2_GameControllerClose)(SDL_GameController *gamecontroller) = NULL; static SDL_Joystick* (SDLCALL *sdl2_GameControllerGetJoystick)(SDL_GameController *gamecontroller) = NULL; static SDL_JoystickID (SDLCALL *sdl2_JoystickInstanceID)(SDL_Joystick *joystick) = NULL; static int (SDLCALL *sdl2_NumJoysticks)(void); #include "it.h" #include "song.h" #include "page.h" struct controller_node { SDL_GameController *controller; SDL_JoystickID id; struct controller_node *next; }; static struct controller_node *game_controller_list = NULL; static void game_controller_insert(SDL_GameController *controller) { struct controller_node *node = mem_alloc(sizeof(*node)); // FIXME: A and B are in different places on the Wii // and Wii U ports. node->controller = controller; node->id = sdl2_JoystickInstanceID(sdl2_GameControllerGetJoystick(controller)); node->next = game_controller_list; game_controller_list = node; } static void game_controller_remove(SDL_JoystickID id) { struct controller_node* prev; struct controller_node* temp = game_controller_list; if (!temp) return; if (temp->id == id) { game_controller_list = temp->next; free(temp); return; } while (temp && temp->id != id) { prev = temp; temp = temp->next; } if (temp) { prev->next = temp->next; sdl2_GameControllerClose(temp->controller); free(temp); } return; } static void game_controller_free(void) { struct controller_node* temp; while (game_controller_list) { temp = game_controller_list; game_controller_list = game_controller_list->next; sdl2_GameControllerClose(temp->controller); free(temp); } } static int sdl2_controller_load_syms(void) { SCHISM_SDL2_SYM(InitSubSystem); SCHISM_SDL2_SYM(QuitSubSystem); SCHISM_SDL2_SYM(IsGameController); SCHISM_SDL2_SYM(GameControllerOpen); SCHISM_SDL2_SYM(GameControllerClose); SCHISM_SDL2_SYM(GameControllerGetJoystick); SCHISM_SDL2_SYM(JoystickInstanceID); SCHISM_SDL2_SYM(NumJoysticks); return 0; } static int sdl2_controller_init(void) { if (!sdl2_init()) return 0; if (sdl2_controller_load_syms()) return 0; if (sdl2_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) return 0; for (int i = 0; i < sdl2_NumJoysticks(); i++) { if (sdl2_IsGameController(i)) { SDL_GameController *controller = sdl2_GameControllerOpen(i); if (controller) game_controller_insert(controller); } } return 1; } static int sdl2_controller_quit(void) { game_controller_free(); sdl2_QuitSubSystem(SDL_INIT_GAMECONTROLLER); sdl2_quit(); return 1; } // the minimum value for schism to handle left axis events #define CONTROLLER_LEFT_AXIS_SENSITIVITY (INT16_MAX / 2) static int sdl2_controller_sdlevent(SDL_Event *event) { SDL_Event newev = {0}; SDL_Keycode sym = SDLK_UNKNOWN; switch (event->type) { case SDL_CONTROLLERAXISMOTION: if (event->caxis.axis == SDL_CONTROLLER_AXIS_LEFTX || event->caxis.axis == SDL_CONTROLLER_AXIS_LEFTY) { // Left axis simply acts as a D-pad static SDL_Keycode lastaxissym = SDLK_UNKNOWN; switch (event->caxis.axis) { case SDL_CONTROLLER_AXIS_LEFTX: if (event->caxis.value > CONTROLLER_LEFT_AXIS_SENSITIVITY) { sym = SDLK_RIGHT; } else if (event->caxis.value < -CONTROLLER_LEFT_AXIS_SENSITIVITY) { sym = SDLK_LEFT; } break; case SDL_CONTROLLER_AXIS_LEFTY: if (event->caxis.value > CONTROLLER_LEFT_AXIS_SENSITIVITY) { sym = SDLK_DOWN; } else if (event->caxis.value < -CONTROLLER_LEFT_AXIS_SENSITIVITY) { sym = SDLK_UP; } break; } if (sym == lastaxissym) return 0; if (sym != SDLK_UNKNOWN) { newev.type = SDL_KEYDOWN; newev.key.state = SDL_PRESSED; lastaxissym = sym; } else { newev.type = SDL_KEYUP; newev.key.state = SDL_RELEASED; sym = lastaxissym; lastaxissym = SDLK_UNKNOWN; } newev.key.keysym.sym = sym; *event = newev; return 1; } else if (event->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX || event->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTY) { // TODO control the mouse here; we'd need access to main() // to do that, so i'm putting it off until this crap // gets moved into events.c/events.h } return 0; case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: switch (event->cbutton.button) { case SDL_CONTROLLER_BUTTON_DPAD_UP: sym = SDLK_UP; break; case SDL_CONTROLLER_BUTTON_DPAD_DOWN: sym = SDLK_DOWN; break; case SDL_CONTROLLER_BUTTON_DPAD_LEFT: sym = SDLK_LEFT; break; case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: sym = SDLK_RIGHT; break; case SDL_CONTROLLER_BUTTON_A: /* "Load Module" if the song is stopped, else stop the song */ sym = (song_get_mode() == MODE_STOPPED) ? SDLK_F9 : SDLK_F8; break; case SDL_CONTROLLER_BUTTON_B: if (status.current_page == PAGE_LOAD_MODULE) { // if the cursor is on a song, load then play; otherwise handle as enter // (hmm. ctrl-enter?) sym = SDLK_RETURN; } else { // F5 key sym = SDLK_F5; } break; case SDL_CONTROLLER_BUTTON_BACK: // dialog escape, or jump back a pattern if (status.dialog_type) { sym = SDLK_ESCAPE; break; } else if (event->cbutton.state == SDL_PRESSED && song_get_mode() == MODE_PLAYING) { song_set_current_order(song_get_current_order() - 1); } return 0; case SDL_CONTROLLER_BUTTON_START: // dialog enter, or jump forward a pattern if (status.dialog_type) { sym = SDLK_RETURN; break; } else if (event->cbutton.state == SDL_PRESSED && song_get_mode() == MODE_PLAYING) { song_set_current_order(song_get_current_order() + 1); } return 0; case SDL_CONTROLLER_BUTTON_GUIDE: event->type = SDL_QUIT; return 1; case SDL_CONTROLLER_BUTTON_X: // should these case SDL_CONTROLLER_BUTTON_Y: // do something? default: return 0; } newev.key.keysym.sym = sym; if (event->cbutton.state == SDL_PRESSED) { newev.type = SDL_KEYDOWN; newev.key.state = SDL_PRESSED; } else { newev.type = SDL_KEYUP; newev.key.state = SDL_RELEASED; } newev.key.type = newev.type; // no-op? *event = newev; return 1; case SDL_CONTROLLERDEVICEADDED: { SDL_GameController *controller = sdl2_GameControllerOpen(event->cdevice.which); if (controller) game_controller_insert(controller); return 0; } case SDL_CONTROLLERDEVICEREMOVED: game_controller_remove(event->cdevice.which); return 0; default: break; } return 1; } #endif static schism_keymod_t sdl2_modkey_trans(uint16_t mod) { schism_keymod_t res = 0; if (mod & KMOD_LSHIFT) res |= SCHISM_KEYMOD_LSHIFT; if (mod & KMOD_RSHIFT) res |= SCHISM_KEYMOD_RSHIFT; if (mod & KMOD_LCTRL) res |= SCHISM_KEYMOD_LCTRL; if (mod & KMOD_RCTRL) res |= SCHISM_KEYMOD_RCTRL; if (mod & KMOD_LALT) res |= SCHISM_KEYMOD_LALT; if (mod & KMOD_RALT) res |= SCHISM_KEYMOD_RALT; if (mod & KMOD_LGUI) res |= SCHISM_KEYMOD_LGUI; if (mod & KMOD_RGUI) res |= SCHISM_KEYMOD_RGUI; if (mod & KMOD_NUM) res |= SCHISM_KEYMOD_NUM; if (mod & KMOD_CAPS) res |= SCHISM_KEYMOD_CAPS; if (mod & KMOD_MODE) res |= SCHISM_KEYMOD_MODE; return res; } static schism_keymod_t sdl2_event_mod_state(void) { return sdl2_modkey_trans(sdl2_GetModState()); } /* These are here for linking text input to keyboard inputs. * If no keyboard input can be found, then the text will * be sent as a SCHISM_TEXTINPUT event. * * - paper */ static schism_event_t pending_keydown; static int have_pending_keydown = 0; static void push_pending_keydown(schism_event_t *event) { if (!have_pending_keydown) { pending_keydown = *event; have_pending_keydown = 1; } } static void pop_pending_keydown(const char *text) { /* text should always be in UTF-8 here */ if (have_pending_keydown) { if (text) { strncpy(pending_keydown.key.text, text, ARRAY_SIZE(pending_keydown.text.text)); pending_keydown.key.text[ARRAY_SIZE(pending_keydown.text.text)-1] = '\0'; } events_push_event(&pending_keydown); have_pending_keydown = 0; } } static void sdl2_pump_events(void) { SDL_Event e; while (sdl2_PollEvent(&e)) { schism_event_t schism_event = {0}; #ifdef SCHISM_CONTROLLER if (!sdl2_controller_sdlevent(&e)) continue; #endif switch (e.type) { case SDL_QUIT: schism_event.type = SCHISM_QUIT; events_push_event(&schism_event); break; case SDL_WINDOWEVENT: switch (e.window.event) { case SDL_WINDOWEVENT_SHOWN: schism_event.type = SCHISM_WINDOWEVENT_SHOWN; events_push_event(&schism_event); break; case SDL_WINDOWEVENT_EXPOSED: schism_event.type = SCHISM_WINDOWEVENT_EXPOSED; events_push_event(&schism_event); break; case SDL_WINDOWEVENT_FOCUS_LOST: schism_event.type = SCHISM_WINDOWEVENT_FOCUS_LOST; events_push_event(&schism_event); break; case SDL_WINDOWEVENT_FOCUS_GAINED: schism_event.type = SCHISM_WINDOWEVENT_FOCUS_GAINED; events_push_event(&schism_event); break; case SDL_WINDOWEVENT_RESIZED: schism_event.type = SCHISM_WINDOWEVENT_RESIZED; schism_event.window.data.resized.width = e.window.data1; schism_event.window.data.resized.height = e.window.data2; events_push_event(&schism_event); break; case SDL_WINDOWEVENT_SIZE_CHANGED: schism_event.type = SCHISM_WINDOWEVENT_SIZE_CHANGED; schism_event.window.data.resized.width = e.window.data1; schism_event.window.data.resized.height = e.window.data2; events_push_event(&schism_event); break; } break; case SDL_KEYDOWN: // pop any pending keydowns pop_pending_keydown(NULL); schism_event.type = SCHISM_KEYDOWN; schism_event.key.state = KEY_PRESS; schism_event.key.repeat = e.key.repeat; // Schism's and SDL2's representation of these are the same. schism_event.key.sym = e.key.keysym.sym; schism_event.key.scancode = e.key.keysym.scancode; schism_event.key.mod = sdl2_modkey_trans(e.key.keysym.mod); // except this one! if (!sdl2_IsTextInputActive()) { // push it NOW events_push_event(&schism_event); } else { push_pending_keydown(&schism_event); } break; case SDL_KEYUP: // pop any pending keydowns pop_pending_keydown(NULL); schism_event.type = SCHISM_KEYUP; schism_event.key.state = KEY_RELEASE; schism_event.key.sym = e.key.keysym.sym; schism_event.key.scancode = e.key.keysym.scancode; schism_event.key.mod = sdl2_modkey_trans(e.key.keysym.mod); events_push_event(&schism_event); break; case SDL_TEXTINPUT: if (have_pending_keydown) { pop_pending_keydown(e.text.text); } else { schism_event.type = SCHISM_TEXTINPUT; strncpy(schism_event.text.text, e.text.text, ARRAY_SIZE(schism_event.text.text)); schism_event.text.text[ARRAY_SIZE(schism_event.text.text)-1] = '\0'; events_push_event(&schism_event); } break; case SDL_MOUSEMOTION: schism_event.type = SCHISM_MOUSEMOTION; schism_event.motion.x = e.motion.x; schism_event.motion.y = e.motion.y; events_push_event(&schism_event); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: schism_event.type = (e.type == SDL_MOUSEBUTTONDOWN) ? SCHISM_MOUSEBUTTONDOWN : SCHISM_MOUSEBUTTONUP; switch (e.button.button) { case SDL_BUTTON_LEFT: schism_event.button.button = MOUSE_BUTTON_LEFT; break; case SDL_BUTTON_MIDDLE: schism_event.button.button = MOUSE_BUTTON_MIDDLE; break; case SDL_BUTTON_RIGHT: schism_event.button.button = MOUSE_BUTTON_RIGHT; break; } schism_event.button.state = !!e.button.state; schism_event.button.clicks = e.button.clicks; schism_event.button.x = e.button.x; schism_event.button.y = e.button.y; events_push_event(&schism_event); break; case SDL_MOUSEWHEEL: schism_event.type = SCHISM_MOUSEWHEEL; schism_event.wheel.x = e.wheel.x; schism_event.wheel.y = e.wheel.y; #if SDL_VERSION_ATLEAST(2, 26, 0) // TODO: this #if might not be necessary if (wheel_have_mouse_coordinates) { schism_event.wheel.mouse_x = e.wheel.mouseX; schism_event.wheel.mouse_y = e.wheel.mouseY; } else #endif { unsigned int x, y; video_get_mouse_coordinates(&x, &y); schism_event.wheel.mouse_x = x; schism_event.wheel.mouse_y = y; } events_push_event(&schism_event); break; case SDL_DROPFILE: schism_event.type = SCHISM_DROPFILE; schism_event.drop.file = str_dup(e.drop.file); sdl2_free(e.drop.file); events_push_event(&schism_event); break; /* these two have no structures because we don't use them */ case SDL_AUDIODEVICEADDED: schism_event.type = SCHISM_AUDIODEVICEADDED; events_push_event(&schism_event); break; case SDL_AUDIODEVICEREMOVED: schism_event.type = SCHISM_AUDIODEVICEREMOVED; events_push_event(&schism_event); break; case SDL_SYSWMEVENT: schism_event.type = SCHISM_EVENT_WM_MSG; schism_event.wm_msg.backend = SCHISM_WM_MSG_BACKEND_SDL2; switch (e.syswm.msg->subsystem) { #if defined(SDL_VIDEO_DRIVER_WINDOWS) case SDL_SYSWM_WINDOWS: schism_event.wm_msg.subsystem = SCHISM_WM_MSG_SUBSYSTEM_WINDOWS; schism_event.wm_msg.msg.win.hwnd = e.syswm.msg->msg.win.hwnd; schism_event.wm_msg.msg.win.msg = e.syswm.msg->msg.win.msg; schism_event.wm_msg.msg.win.wparam = e.syswm.msg->msg.win.wParam; schism_event.wm_msg.msg.win.lparam = e.syswm.msg->msg.win.lParam; // ignore WM_DROPFILES messages. these are already handled // by the SDL_DROPFILES event and trying to use our implementation // only results in an empty string which is undesirable if (schism_event.wm_msg.msg.win.msg != WM_DROPFILES) events_push_event(&schism_event); break; #endif #if defined(SDL_VIDEO_DRIVER_X11) case SDL_SYSWM_X11: schism_event.wm_msg.subsystem = SCHISM_WM_MSG_SUBSYSTEM_X11; schism_event.wm_msg.msg.x11.event.type = e.syswm.msg->msg.x11.event.type; if (e.syswm.msg->msg.x11.event.type == SelectionRequest) { schism_event.wm_msg.msg.x11.event.selection_request.serial = e.syswm.msg->msg.x11.event.xselectionrequest.serial; schism_event.wm_msg.msg.x11.event.selection_request.send_event = e.syswm.msg->msg.x11.event.xselectionrequest.send_event; // `Bool' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.display = e.syswm.msg->msg.x11.event.xselectionrequest.display; // `Display *' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.owner = e.syswm.msg->msg.x11.event.xselectionrequest.owner; // `Window' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.requestor = e.syswm.msg->msg.x11.event.xselectionrequest.requestor; // `Window' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.selection = e.syswm.msg->msg.x11.event.xselectionrequest.selection; // `Atom' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.target = e.syswm.msg->msg.x11.event.xselectionrequest.target; // `Atom' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.property = e.syswm.msg->msg.x11.event.xselectionrequest.property; // `Atom' in Xlib schism_event.wm_msg.msg.x11.event.selection_request.time = e.syswm.msg->msg.x11.event.xselectionrequest.time; // `Time' in Xlib } // ...? //events_push_event(&schism_event); break; #endif default: break; } } } pop_pending_keydown(NULL); } ////////////////////////////////////////////////////////////////////////////// // dynamic loading static int sdl2_events_load_syms(void) { SCHISM_SDL2_SYM(InitSubSystem); SCHISM_SDL2_SYM(QuitSubSystem); SCHISM_SDL2_SYM(GetModState); SCHISM_SDL2_SYM(IsTextInputActive); SCHISM_SDL2_SYM(PollEvent); SCHISM_SDL2_SYM(EventState); SCHISM_SDL2_SYM(free); SCHISM_SDL2_SYM(GetVersion); return 0; } static int sdl2_events_init(void) { if (!sdl2_init()) return 0; if (sdl2_events_load_syms()) return 0; if (sdl2_InitSubSystem(SDL_INIT_EVENTS) < 0) return 0; #ifdef SCHISM_CONTROLLER { int r = sdl2_controller_init(); # if defined(SCHISM_WII) || defined(SCHISM_WIIU) // only warn the user if controller initialization failed // when on an actual console. if (!r) log_appendf(4, "SDL: Failed to initialize game controllers!"); # endif } #endif SDL_version ver; sdl2_GetVersion(&ver); wheel_have_mouse_coordinates = SDL2_VERSION_ATLEAST(ver, 2, 26, 0); #if defined(SCHISM_WIN32) || defined(SCHISM_USE_X11) sdl2_EventState(SDL_SYSWMEVENT, SDL_ENABLE); #endif return 1; } static void sdl2_events_quit(void) { #ifdef SCHISM_CONTROLLER sdl2_controller_quit(); #endif sdl2_QuitSubSystem(SDL_INIT_EVENTS); sdl2_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_events_backend_t schism_events_backend_sdl2 = { .init = sdl2_events_init, .quit = sdl2_events_quit, #if !defined(SCHISM_WII) && !defined(SCHISM_WIIU) /* These ports have no key repeat... */ .flags = SCHISM_EVENTS_BACKEND_HAS_KEY_REPEAT, #else .flags = 0, #endif .keymod_state = sdl2_event_mod_state, .pump_events = sdl2_pump_events, }; schismtracker-20250313/sys/sdl2/init.c000066400000000000000000000056771476471630300174400ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "init.h" #include "headers.h" static int (SDLCALL *sdl2_Init)(Uint32 flags); static void (SDLCALL *sdl2_Quit)(void); static const char *(SDLCALL *sdl2_GetError)(void); static int load_sdl2_syms(void); #ifdef SDL2_DYNAMIC_LOAD #include "loadso.h" static void *sdl2_dltrick_handle_ = NULL; static void sdl2_dlend(void) { if (sdl2_dltrick_handle_) { loadso_object_unload(sdl2_dltrick_handle_); sdl2_dltrick_handle_ = NULL; } } static int sdl2_dlinit(void) { // already have it? if (sdl2_dltrick_handle_) return 0; sdl2_dltrick_handle_ = library_load("SDL2-2.0", 0, 0); if (!sdl2_dltrick_handle_) { sdl2_dltrick_handle_ = library_load("SDL2", 0, 0); if (!sdl2_dltrick_handle_) return -1; } int retval = load_sdl2_syms(); if (retval < 0) sdl2_dlend(); return retval; } SCHISM_STATIC_ASSERT(sizeof(void (*)) == sizeof(void *), "dynamic loading code assumes function pointer and void pointer are of equivalent size"); int sdl2_load_sym(const char *fn, void *addr) { void *func = loadso_function_load(sdl2_dltrick_handle_, fn); if (!func) return 0; memcpy(addr, &func, sizeof(void *)); return 1; } #else static int sdl2_dlinit(void) { load_sdl2_syms(); return 0; } #define sdl2_dlend() // nothing #endif static int load_sdl2_syms(void) { SCHISM_SDL2_SYM(Init); SCHISM_SDL2_SYM(Quit); SCHISM_SDL2_SYM(GetError); return 0; } ////////////////////////////////////////////////////////////////////////////// // this is used so that SDL_Quit is only called // once every backend is done static int roll = 0; // returns non-zero on success or zero on error int sdl2_init(void) { if (!roll) { if (sdl2_dlinit()) return 0; // the subsystems are initialized by the actual backends int r = sdl2_Init(0); if (r < 0) { fprintf(stderr, "SDL2: SDL_Init: %s\n", sdl2_GetError()); return 0; } } roll++; return roll; } void sdl2_quit(void) { if (roll > 0) roll--; if (roll == 0) { sdl2_Quit(); sdl2_dlend(); } } schismtracker-20250313/sys/sdl2/init.h000066400000000000000000000035601476471630300174320ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_SYS_SDL2_INIT_H_ #define SCHISM_SYS_SDL2_INIT_H_ // We have to include this **before** headers.h because // otherwise tgmath.h doesn't work quite right combined // with intrin.h under Win32. What the hell. #include #ifdef SCHISM_OS2 // Work around weird compiler bug? # undef __386__ # include # define __386__ #endif #include #include "headers.h" int sdl2_init(void); void sdl2_quit(void); #ifdef SDL2_DYNAMIC_LOAD // must be called AFTER sdl2_init() int sdl2_load_sym(const char *fn, void *addr); #define SCHISM_SDL2_SYM(x) \ if (!sdl2_load_sym("SDL_" #x, &sdl2_##x)) return -1 #else #define SCHISM_SDL2_SYM(x) \ sdl2_##x = SDL_##x #endif #define SDL2_VERSION_ATLEAST(ver, mmajor, mminor, mpatch) \ SCHISM_SEMVER_ATLEAST(mmajor, mminor, mpatch, ver.major, ver.minor, ver.patch) #endif /* SCHISM_SYS_SDL2_INIT_H_ */ schismtracker-20250313/sys/sdl2/mt.c000066400000000000000000000145051476471630300171030ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "init.h" #include "headers.h" #include "mem.h" #include "backend/mt.h" /* ------------------------------------ */ #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD static SDL_Thread *(SDLCALL *sdl2_CreateThread)(SDL_ThreadFunction fn, const char *name, void *data, pfnSDL_CurrentBeginThread begin, pfnSDL_CurrentEndThread end) = NULL; #else static SDL_Thread *(SDLCALL *sdl2_CreateThread)(SDL_ThreadFunction fn, const char *name, void *data) = NULL; #endif static void (SDLCALL *sdl2_WaitThread)(SDL_Thread * thread, int *status) = NULL; static int (SDLCALL *sdl2_SetThreadPriority)(SDL_ThreadPriority priority) = NULL; static SDL_threadID (SDLCALL *sdl2_ThreadID)(void) = NULL; struct mt_thread { SDL_Thread *thread; schism_thread_function_t func; void *userdata; }; static int SDLCALL sdl2_dummy_thread_func(void *userdata) { mt_thread_t *thread = userdata; return thread->func(thread->userdata); } static mt_thread_t *sdl2_thread_create(schism_thread_function_t func, const char *name, void *userdata) { mt_thread_t *thread = mem_alloc(sizeof(*thread)); thread->func = func; thread->userdata = userdata; /* ew */ #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD SDL_Thread *sdl_thread = sdl2_CreateThread(sdl2_dummy_thread_func, name, thread, # ifdef SCHISM_WIN32 (pfnSDL_CurrentBeginThread)_beginthreadex, (pfnSDL_CurrentEndThread)_endthreadex # elif defined(SCHISM_OS2) (pfnSDL_CurrentBeginThread)_beginthread, (pfnSDL_CurrentEndThread)_endthread # endif ); #else SDL_Thread *sdl_thread = sdl2_CreateThread(sdl2_dummy_thread_func, name, thread); #endif if (!sdl_thread) { free(thread); return NULL; } thread->thread = sdl_thread; return thread; } static void sdl2_thread_wait(mt_thread_t *thread, int *status) { sdl2_WaitThread(thread->thread, status); free(thread); } static void sdl2_thread_set_priority(int priority) { // !!! FIXME this should use a switch statement, // or this API should be removed altogether sdl2_SetThreadPriority(priority); } // returns the current thread's ID static mt_thread_id_t sdl2_thread_id(void) { return sdl2_ThreadID(); } /* -------------------------------------------------------------- */ /* mutexes */ static SDL_mutex *(SDLCALL *sdl2_CreateMutex)(void) = NULL; static void (SDLCALL *sdl2_DestroyMutex)(SDL_mutex * mutex) = NULL; static int (SDLCALL *sdl2_LockMutex)(SDL_mutex * mutex) = NULL; static int (SDLCALL *sdl2_UnlockMutex)(SDL_mutex * mutex) = NULL; struct mt_mutex { SDL_mutex *mutex; }; static mt_mutex_t *sdl2_mutex_create(void) { mt_mutex_t *mutex = mem_alloc(sizeof(*mutex)); mutex->mutex = sdl2_CreateMutex(); if (!mutex->mutex) { free(mutex); return NULL; } return mutex; } static void sdl2_mutex_delete(mt_mutex_t *mutex) { sdl2_DestroyMutex(mutex->mutex); free(mutex); } static void sdl2_mutex_lock(mt_mutex_t *mutex) { sdl2_LockMutex(mutex->mutex); } static void sdl2_mutex_unlock(mt_mutex_t *mutex) { sdl2_UnlockMutex(mutex->mutex); } /* -------------------------------------------------------------- */ static SDL_cond *(SDLCALL *sdl2_CreateCond)(void) = NULL; static void (SDLCALL *sdl2_DestroyCond)(SDL_cond *cond) = NULL; static int (SDLCALL *sdl2_CondSignal)(SDL_cond *cond) = NULL; static int (SDLCALL *sdl2_CondWait)(SDL_cond *cond, SDL_mutex *mutex) = NULL; static int (SDLCALL *sdl2_CondWaitTimeout)(SDL_cond *cond, SDL_mutex *mutex, uint32_t timeout) = NULL; struct mt_cond { SDL_cond *cond; }; static mt_cond_t *sdl2_cond_create(void) { mt_cond_t *cond = mem_alloc(sizeof(*cond)); cond->cond = sdl2_CreateCond(); if (!cond->cond) { free(cond); return NULL; } return cond; } static void sdl2_cond_delete(mt_cond_t *cond) { sdl2_DestroyCond(cond->cond); free(cond); } static void sdl2_cond_signal(mt_cond_t *cond) { sdl2_CondSignal(cond->cond); } static void sdl2_cond_wait(mt_cond_t *cond, mt_mutex_t *mutex) { sdl2_CondWait(cond->cond, mutex->mutex); } static void sdl2_cond_wait_timeout(mt_cond_t *cond, mt_mutex_t *mutex, uint32_t timeout) { sdl2_CondWaitTimeout(cond->cond, mutex->mutex, timeout); } ////////////////////////////////////////////////////////////////////////////// static int sdl2_threads_load_syms(void) { SCHISM_SDL2_SYM(CreateThread); SCHISM_SDL2_SYM(WaitThread); SCHISM_SDL2_SYM(SetThreadPriority); SCHISM_SDL2_SYM(ThreadID); SCHISM_SDL2_SYM(CreateMutex); SCHISM_SDL2_SYM(DestroyMutex); SCHISM_SDL2_SYM(LockMutex); SCHISM_SDL2_SYM(UnlockMutex); SCHISM_SDL2_SYM(CreateCond); SCHISM_SDL2_SYM(DestroyCond); SCHISM_SDL2_SYM(CondSignal); SCHISM_SDL2_SYM(CondWait); SCHISM_SDL2_SYM(CondWaitTimeout); return 0; } static int sdl2_threads_init(void) { if (!sdl2_init()) return 0; if (sdl2_threads_load_syms()) return 0; return 1; } static void sdl2_threads_quit(void) { sdl2_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_mt_backend_t schism_mt_backend_sdl2 = { .init = sdl2_threads_init, .quit = sdl2_threads_quit, .thread_create = sdl2_thread_create, .thread_wait = sdl2_thread_wait, .thread_set_priority = sdl2_thread_set_priority, .thread_id = sdl2_thread_id, .mutex_create = sdl2_mutex_create, .mutex_delete = sdl2_mutex_delete, .mutex_lock = sdl2_mutex_lock, .mutex_unlock = sdl2_mutex_unlock, .cond_create = sdl2_cond_create, .cond_delete = sdl2_cond_delete, .cond_signal = sdl2_cond_signal, .cond_wait = sdl2_cond_wait, .cond_wait_timeout = sdl2_cond_wait_timeout, }; schismtracker-20250313/sys/sdl2/timer.c000066400000000000000000000107001476471630300175740ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "init.h" #include "headers.h" #include "mt.h" #include "mem.h" #include "backend/timer.h" static int (SDLCALL *sdl2_InitSubSystem)(Uint32 flags) = NULL; static void (SDLCALL *sdl2_QuitSubSystem)(Uint32 flags) = NULL; static void (SDLCALL *sdl2_Delay)(uint32_t ms) = NULL; static uint64_t (SDLCALL *sdl2_GetPerformanceFrequency)(void) = NULL; static uint64_t (SDLCALL *sdl2_GetPerformanceCounter)(void) = NULL; // Introduced in SDL 2.0.18 static uint64_t (SDLCALL *sdl2_GetTicks64)(void) = NULL; static int sdl2_have_timer64 = 0; static uint64_t sdl2_performance_start = 0; static uint64_t sdl2_performance_frequency = 0; static SDL_TimerID (SDLCALL *sdl2_AddTimer)(uint32_t ms, SDL_TimerCallback callback, void *param); static timer_ticks_t sdl2_timer_ticks(void) { #if defined(SDL2_DYNAMIC_LOAD) || SDL_VERSION_ATLEAST(2, 0, 18) if (sdl2_have_timer64) return sdl2_GetTicks64(); #endif timer_ticks_t ticks = sdl2_GetPerformanceCounter(); ticks -= sdl2_performance_start; ticks *= UINT64_C(1000); ticks /= sdl2_performance_frequency; return ticks; } static timer_ticks_t sdl2_timer_ticks_us(void) { timer_ticks_t ticks = sdl2_GetPerformanceCounter(); ticks -= sdl2_performance_start; ticks *= UINT64_C(1000000); ticks /= sdl2_performance_frequency; return ticks; } static void sdl2_timer_usleep(uint64_t us) { sdl2_Delay(us / 1000); } static void sdl2_timer_msleep(uint32_t ms) { sdl2_Delay(ms); } ////////////////////////////////////////////////////////////////////////////// // oneshot timer struct _sdl2_timer_oneshot_curry { void (*callback)(void *param); void *param; }; static uint32_t SDLCALL _sdl2_timer_oneshot_callback(SCHISM_UNUSED uint32_t interval, void *param) { struct _sdl2_timer_oneshot_curry *curry = (struct _sdl2_timer_oneshot_curry *)param; curry->callback(curry->param); free(curry); return 0; } static int sdl2_timer_oneshot(uint32_t interval, void (*callback)(void *param), void *param) { struct _sdl2_timer_oneshot_curry *curry = mem_alloc(sizeof(*curry)); curry->callback = callback; curry->param = param; SDL_TimerID id = sdl2_AddTimer(interval, _sdl2_timer_oneshot_callback, curry); if (!id) free(curry); return !!id; } ////////////////////////////////////////////////////////////////////////////// static int sdl2_timer_load_syms(void) { SCHISM_SDL2_SYM(InitSubSystem); SCHISM_SDL2_SYM(QuitSubSystem); SCHISM_SDL2_SYM(Delay); SCHISM_SDL2_SYM(GetPerformanceCounter); SCHISM_SDL2_SYM(GetPerformanceFrequency); SCHISM_SDL2_SYM(AddTimer); return 0; } static int sdl2_timer64_load_syms(void) { #if defined(SDL2_DYNAMIC_LOAD) || SDL_VERSION_ATLEAST(2, 0, 18) SCHISM_SDL2_SYM(GetTicks64); return 0; #else return -1; #endif } static int sdl2_timer_init(void) { if (!sdl2_init()) return 0; if (sdl2_timer_load_syms()) return 0; if (!sdl2_timer64_load_syms()) sdl2_have_timer64 = 1; sdl2_performance_frequency = sdl2_GetPerformanceFrequency(); sdl2_performance_start = sdl2_GetPerformanceCounter(); if (sdl2_InitSubSystem(SDL_INIT_TIMER) < 0) return 0; return 1; } static void sdl2_timer_quit(void) { sdl2_QuitSubSystem(SDL_INIT_TIMER); sdl2_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_timer_backend_t schism_timer_backend_sdl2 = { .init = sdl2_timer_init, .quit = sdl2_timer_quit, .ticks = sdl2_timer_ticks, .ticks_us = sdl2_timer_ticks_us, .usleep = sdl2_timer_usleep, .msleep = sdl2_timer_msleep, .oneshot = sdl2_timer_oneshot, }; schismtracker-20250313/sys/sdl2/video.c000066400000000000000000000607161476471630300175760ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "init.h" #define NATIVE_SCREEN_WIDTH 640 #define NATIVE_SCREEN_HEIGHT 400 #define WINDOW_TITLE "Schism Tracker" #include "headers.h" #include "it.h" #include "charset.h" #include "bswap.h" #include "config.h" #include "video.h" #include "osdefs.h" #include "vgamem.h" #include "events.h" #include "backend/video.h" #include #ifndef SCHISM_MACOSX #include "auto/schismico_hires.h" #endif static int (SDLCALL *sdl2_InitSubSystem)(Uint32 flags) = NULL; static void (SDLCALL *sdl2_QuitSubSystem)(Uint32 flags) = NULL; static const char *(SDLCALL *sdl2_GetCurrentVideoDriver)(void); static int (SDLCALL *sdl2_GetCurrentDisplayMode)(int displayIndex, SDL_DisplayMode * mode); static int (SDLCALL *sdl2_GetRendererInfo)(SDL_Renderer * renderer, SDL_RendererInfo * info); static int (SDLCALL *sdl2_ShowCursor)(int toggle); static SDL_bool (SDLCALL *sdl2_GetWindowWMInfo)(SDL_Window * window, SDL_SysWMinfo * info); static Uint32 (SDLCALL *sdl2_GetWindowFlags)(SDL_Window * window); static Uint32 (SDLCALL *sdl2_MapRGB)(const SDL_PixelFormat * format, Uint8 r, Uint8 g, Uint8 b); static void (SDLCALL *sdl2_SetWindowPosition)(SDL_Window * window, int x, int y); static void (SDLCALL *sdl2_SetWindowSize)(SDL_Window * window, int w, int h); static int (SDLCALL *sdl2_SetWindowFullscreen)(SDL_Window * window, Uint32 flags); static void (SDLCALL *sdl2_GetWindowPosition)(SDL_Window * window, int *x, int *y); static SDL_Window * (SDLCALL *sdl2_CreateWindow)(const char *title, int x, int y, int w, int h, Uint32 flags); static SDL_Renderer * (SDLCALL *sdl2_CreateRenderer)(SDL_Window * window, int index, Uint32 flags); static SDL_Texture * (SDLCALL *sdl2_CreateTexture)(SDL_Renderer * renderer, Uint32 format, int access, int w, int h); static SDL_PixelFormat * (SDLCALL *sdl2_AllocFormat)(Uint32 pixel_format); static void (SDLCALL *sdl2_FreeFormat)(SDL_PixelFormat *format); static void (SDLCALL *sdl2_DestroyTexture)(SDL_Texture * texture); static void (SDLCALL *sdl2_DestroyRenderer)(SDL_Renderer * renderer); static void (SDLCALL *sdl2_DestroyWindow)(SDL_Window * window); static Uint8 (SDLCALL *sdl2_EventState)(Uint32 type, int state); static SDL_bool (SDLCALL *sdl2_IsScreenSaverEnabled)(void); static void (SDLCALL *sdl2_EnableScreenSaver)(void); static void (SDLCALL *sdl2_DisableScreenSaver)(void); static void (SDLCALL *sdl2_RenderGetScale)(SDL_Renderer * renderer, float *scaleX, float *scaleY); static void (SDLCALL *sdl2_SetWindowGrab)(SDL_Window * window, SDL_bool grabbed); static void (SDLCALL *sdl2_WarpMouseInWindow)(SDL_Window * window, int x, int y); static Uint32 (SDLCALL *sdl2_GetWindowFlags)(SDL_Window * window); static void (SDLCALL *sdl2_GetWindowSize)(SDL_Window * window, int *w, int *h); static void (SDLCALL *sdl2_SetWindowSize)(SDL_Window * window, int w, int h); static int (SDLCALL *sdl2_RenderClear)(SDL_Renderer * renderer); static int (SDLCALL *sdl2_LockTexture)(SDL_Texture * texture, const SDL_Rect * rect, void **pixels, int *pitch); static void (SDLCALL *sdl2_UnlockTexture)(SDL_Texture * texture); static int (SDLCALL *sdl2_RenderCopy)(SDL_Renderer * renderer, SDL_Texture * texture, const SDL_Rect * srcrect, const SDL_Rect * dstrect); static void (SDLCALL *sdl2_RenderPresent)(SDL_Renderer * renderer); static void (SDLCALL *sdl2_SetWindowTitle)(SDL_Window * window, const char *title); static SDL_Surface* (SDLCALL *sdl2_CreateRGBSurfaceFrom)(void *pixels, int width, int height, int depth, int pitch, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); static void (SDLCALL *sdl2_SetWindowIcon)(SDL_Window * window, SDL_Surface * icon); static void (SDLCALL *sdl2_FreeSurface)(SDL_Surface * surface); static SDL_bool (SDLCALL *sdl2_SetHint)(const char *name, const char *value); static int (SDLCALL *sdl2_RenderSetLogicalSize)(SDL_Renderer * renderer, int w, int h); static SDL_bool (SDLCALL *sdl2_GetWindowGrab)(SDL_Window * window); // ONLY in SDL 2.0.18 static void (SDLCALL *sdl2_RenderWindowToLogical)(SDL_Renderer * renderer, int windowX, int windowY, float *logicalX, float *logicalY); #if !SDL_VERSION_ATLEAST(2, 0, 4) #define SDL_PIXELFORMAT_NV12 (SDL_DEFINE_PIXELFOURCC('N', 'V', '1', '2')) #define SDL_PIXELFORMAT_NV21 (SDL_DEFINE_PIXELFOURCC('N', 'V', '2', '1')) #endif static struct { SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *texture; SDL_PixelFormat *pixel_format; // may be NULL uint32_t format; uint32_t bpp; // BYTES per pixel int width, height; struct { unsigned int x, y; } mouse; struct { /* TODO: need to save the state of the menu bar or else * these will be wrong if it's toggled while in fullscreen */ int width, height; int x, y; } saved; int fullscreen; struct { uint32_t pal_y[256]; uint32_t pal_u[256]; uint32_t pal_v[256]; } yuv; uint32_t pal[256]; } video = {0}; // Native formats, in order of preference. static const struct { uint32_t format; const char *name; } native_formats[] = { // RGB // ---------------- {SDL_PIXELFORMAT_RGB888, "RGB888"}, {SDL_PIXELFORMAT_ARGB8888, "ARGB8888"}, {SDL_PIXELFORMAT_RGB24, "RGB24"}, {SDL_PIXELFORMAT_RGB565, "RGB565"}, {SDL_PIXELFORMAT_RGB555, "RGB555"}, {SDL_PIXELFORMAT_ARGB1555, "ARGB1555"}, {SDL_PIXELFORMAT_RGB444, "RGB444"}, {SDL_PIXELFORMAT_ARGB4444, "ARGB4444"}, {SDL_PIXELFORMAT_RGB332, "RGB332"}, // ---------------- // YUV // ---------------- {SDL_PIXELFORMAT_IYUV, "IYUV"}, {SDL_PIXELFORMAT_YV12, "YV12"}, // {SDL_PIXELFORMAT_UYVY, "UYVY"}, // {SDL_PIXELFORMAT_YVYU, "YVYU"}, // {SDL_PIXELFORMAT_YUY2, "YUY2"}, // {SDL_PIXELFORMAT_NV12, "NV12"}, // {SDL_PIXELFORMAT_NV21, "NV21"}, // ---------------- }; static int sdl2_video_is_fullscreen(void) { return video.fullscreen; } static int sdl2_video_width(void) { return video.width; } static int sdl2_video_height(void) { return video.height; } static const char *sdl2_video_driver_name(void) { return sdl2_GetCurrentVideoDriver(); } static void sdl2_video_report(void) { struct { uint32_t num; const char *name, *type; } yuv_layouts[] = { {SDL_PIXELFORMAT_YV12, "YV12", "planar+tv"}, {SDL_PIXELFORMAT_IYUV, "IYUV", "planar+tv"}, {SDL_PIXELFORMAT_YVYU, "YVYU", "packed"}, {SDL_PIXELFORMAT_UYVY, "UYVY", "packed"}, {SDL_PIXELFORMAT_YUY2, "YUY2", "packed"}, {SDL_PIXELFORMAT_NV12, "NV12", "planar"}, {SDL_PIXELFORMAT_NV21, "NV21", "planar"}, {0, NULL, NULL}, }, *layout = yuv_layouts; { SDL_RendererInfo renderer; sdl2_GetRendererInfo(video.renderer, &renderer); log_appendf(5, " Using driver '%s'", sdl2_GetCurrentVideoDriver()); log_appendf(5, " %sware%s renderer '%s'", (renderer.flags & SDL_RENDERER_SOFTWARE) ? "Soft" : "Hard", (renderer.flags & SDL_RENDERER_ACCELERATED) ? "-accelerated" : "", renderer.name); } switch (video.format) { case SDL_PIXELFORMAT_IYUV: case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_YVYU: case SDL_PIXELFORMAT_UYVY: case SDL_PIXELFORMAT_YUY2: case SDL_PIXELFORMAT_NV12: case SDL_PIXELFORMAT_NV21: while (video.format != layout->num && layout->name != NULL) layout++; if (layout->name) log_appendf(5, " Display format: %s (%s)", layout->name, layout->type); else log_appendf(5, " Display format: %" PRIx32, video.format); break; default: log_appendf(5, " Display format: %"PRIu32" bits/pixel", SDL_BITSPERPIXEL(video.format)); break; } { SDL_DisplayMode display; if (!sdl2_GetCurrentDisplayMode(0, &display) && video.fullscreen) log_appendf(5, " Display dimensions: %dx%d", display.w, display.h); } } static void set_icon(void) { sdl2_SetWindowTitle(video.window, WINDOW_TITLE); #ifndef SCHISM_MACOSX /* apple/macs use a bundle; this overrides their nice pretty icon */ { uint32_t *pixels; int width, height; if (!xpmdata(_schism_icon_xpm_hires, &pixels, &width, &height)) { SDL_Surface *icon = sdl2_CreateRGBSurfaceFrom(pixels, width, height, 32, width * sizeof(uint32_t), 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); if (icon) { sdl2_SetWindowIcon(video.window, icon); sdl2_FreeSurface(icon); } free(pixels); } } #endif } static void video_redraw_texture(void) { size_t pref_last = ARRAY_SIZE(native_formats); uint32_t format = SDL_PIXELFORMAT_RGB888; if (video.texture) sdl2_DestroyTexture(video.texture); if (video.pixel_format) sdl2_FreeFormat(video.pixel_format); if (*cfg_video_format) { for (size_t i = 0; i < ARRAY_SIZE(native_formats); i++) { if (!charset_strcasecmp(cfg_video_format, CHARSET_UTF8, native_formats[i].name, CHARSET_UTF8)) { format = native_formats[i].format; goto got_format; } } } // We want to find the best format we can natively // output to. If we can't, then we fall back to // SDL_PIXELFORMAT_RGB888 and let SDL deal with the // conversion. SDL_RendererInfo info; if (!sdl2_GetRendererInfo(video.renderer, &info)) { for (uint32_t i = 0; i < info.num_texture_formats; i++) for (size_t j = 0; j < ARRAY_SIZE(native_formats); j++) if (info.texture_formats[i] == native_formats[j].format && j < pref_last) format = native_formats[pref_last = j].format; } got_format: video.texture = sdl2_CreateTexture(video.renderer, format, SDL_TEXTUREACCESS_STREAMING, NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT); video.pixel_format = sdl2_AllocFormat(format); video.format = format; // find the bytes per pixel switch (video.format) { // irrelevant case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: break; default: video.bpp = video.pixel_format->BytesPerPixel; break; } } static void sdl2_video_set_hardware(int hardware) { sdl2_DestroyTexture(video.texture); sdl2_DestroyRenderer(video.renderer); video.renderer = sdl2_CreateRenderer(video.window, -1, hardware ? SDL_RENDERER_ACCELERATED : SDL_RENDERER_SOFTWARE); if (!video.renderer) video.renderer = sdl2_CreateRenderer(video.window, -1, 0); // welp video_redraw_texture(); video_report(); } static void sdl2_video_shutdown(void) { sdl2_DestroyTexture(video.texture); sdl2_DestroyRenderer(video.renderer); sdl2_DestroyWindow(video.window); } static void sdl2_video_setup(const char *quality) { // lol if (cfg_video_interpolation != quality) strncpy(cfg_video_interpolation, quality, 7); sdl2_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, quality); video_redraw_texture(); } static void sdl2_video_startup(void) { vgamem_clear(); vgamem_flip(); video_setup(cfg_video_interpolation); #ifndef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* older SDL2 versions don't define this, don't fail the build for it */ #define SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR "SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR" #endif sdl2_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); video.width = cfg_video_width; video.height = cfg_video_height; video.saved.x = video.saved.y = SDL_WINDOWPOS_CENTERED; video.window = sdl2_CreateWindow(WINDOW_TITLE, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, video.width, video.height, SDL_WINDOW_RESIZABLE); video_fullscreen(cfg_video_fullscreen); video_set_hardware(cfg_video_hardware); /* Aspect ratio correction if it's wanted */ if (cfg_video_want_fixed) sdl2_RenderSetLogicalSize(video.renderer, cfg_video_want_fixed_width, cfg_video_want_fixed_height); if (video_have_menu() && !video.fullscreen) { sdl2_SetWindowSize(video.window, video.width, video.height); sdl2_SetWindowPosition(video.window, video.saved.x, video.saved.y); } /* okay, i think we're ready */ sdl2_ShowCursor(SDL_DISABLE); set_icon(); } static void sdl2_video_fullscreen(int new_fs_flag) { const int have_menu = video_have_menu(); /* positive new_fs_flag == set, negative == toggle */ video.fullscreen = (new_fs_flag >= 0) ? !!new_fs_flag : !video.fullscreen; if (video.fullscreen) { if (have_menu) { sdl2_GetWindowSize(video.window, &video.saved.width, &video.saved.height); sdl2_GetWindowPosition(video.window, &video.saved.x, &video.saved.y); } sdl2_SetWindowFullscreen(video.window, SDL_WINDOW_FULLSCREEN_DESKTOP); if (have_menu) video_toggle_menu(0); } else { sdl2_SetWindowFullscreen(video.window, 0); if (have_menu) { /* the menu must be toggled first here */ video_toggle_menu(1); sdl2_SetWindowSize(video.window, video.saved.width, video.saved.height); sdl2_SetWindowPosition(video.window, video.saved.x, video.saved.y); } set_icon(); /* XXX is this necessary */ } } static void sdl2_video_resize(unsigned int width, unsigned int height) { video.width = width; video.height = height; status.flags |= (NEED_UPDATE); } static void yuv_pal_(int i, unsigned char rgb[3]) { unsigned int y, u, v; video_rgb_to_yuv(&y, &u, &v, rgb); switch (video.format) { case SDL_PIXELFORMAT_IYUV: case SDL_PIXELFORMAT_YV12: video.yuv.pal_y[i] = y; video.yuv.pal_u[i] = (u >> 4) & 0xF; video.yuv.pal_v[i] = (v >> 4) & 0xF; break; default: break; // err } } static void sdl_pal_(int i, unsigned char rgb[3]) { video.pal[i] = sdl2_MapRGB(video.pixel_format, rgb[0], rgb[1], rgb[2]); } static void sdl2_video_colors(unsigned char palette[16][3]) { static const int lastmap[] = { 0, 1, 2, 3, 5 }; int i, p; void (*fun)(int i, unsigned char rgb[3]); switch (video.format) { case SDL_PIXELFORMAT_IYUV: case SDL_PIXELFORMAT_YV12: fun = yuv_pal_; break; default: fun = sdl_pal_; break; } /* make our "base" space */ for (i = 0; i < 16; i++) { unsigned char b[3]; for (size_t j = 0; j < ARRAY_SIZE(b); j++) b[j] = palette[i][j]; fun(i, b); } /* make our "gradient" space */ for (i = 0; i < 128; i++) { p = lastmap[(i>>5)]; unsigned char b[3]; for (size_t j = 0; j < ARRAY_SIZE(b); j++) b[j] = (int)palette[p][j] + (((int)(palette[p+1][j] - palette[p][j]) * (i & 0x1F)) / 0x20); fun(i + 128, b); } } static int sdl2_video_is_focused(void) { return !!(sdl2_GetWindowFlags(video.window) & SDL_WINDOW_INPUT_FOCUS); } static int sdl2_video_is_visible(void) { return !!(sdl2_GetWindowFlags(video.window) & SDL_WINDOW_SHOWN); } static int sdl2_video_is_wm_available(void) { SDL_SysWMinfo info; SDL_VERSION(&info.version); return !!sdl2_GetWindowWMInfo(video.window, &info); } static int sdl2_video_is_hardware(void) { SDL_RendererInfo info; sdl2_GetRendererInfo(video.renderer, &info); return !!(info.flags & SDL_RENDERER_ACCELERATED); } /* -------------------------------------------------------- */ static int sdl2_video_is_screensaver_enabled(void) { return sdl2_IsScreenSaverEnabled(); } static void sdl2_video_toggle_screensaver(int enabled) { if (enabled) sdl2_EnableScreenSaver(); else sdl2_DisableScreenSaver(); } /* ---------------------------------------------------------- */ /* coordinate translation */ static void sdl2_video_translate(unsigned int vx, unsigned int vy, unsigned int *x, unsigned int *y) { if (video_mousecursor_visible() && (video.mouse.x != vx || video.mouse.y != vy)) status.flags |= SOFTWARE_MOUSE_MOVED; vx *= NATIVE_SCREEN_WIDTH; vy *= NATIVE_SCREEN_HEIGHT; vx /= (cfg_video_want_fixed) ? cfg_video_want_fixed_width : video.width; vy /= (cfg_video_want_fixed) ? cfg_video_want_fixed_height : video.height; vx = MIN(vx, NATIVE_SCREEN_WIDTH - 1); vy = MIN(vy, NATIVE_SCREEN_HEIGHT - 1); video.mouse.x = vx; video.mouse.y = vy; if (x) *x = vx; if (y) *y = vy; } static void sdl2_video_get_logical_coordinates(int x, int y, int *trans_x, int *trans_y) { if (!cfg_video_want_fixed) { *trans_x = x; *trans_y = y; } else { float xx, yy; #if SDL2_DYNAMIC_LOAD || SDL_VERSION_ATLEAST(2, 0, 18) if (sdl2_RenderWindowToLogical) { sdl2_RenderWindowToLogical(video.renderer, x, y, &xx, &yy); } else #endif { /* Alternative for older SDL versions. MIGHT work with high DPI */ float scale_x = 1, scale_y = 1; sdl2_RenderGetScale(video.renderer, &scale_x, &scale_y); xx = x - (video.width / 2) - (((float)cfg_video_want_fixed_width * scale_x) / 2); yy = y - (video.height / 2) - (((float)cfg_video_want_fixed_height * scale_y) / 2); xx /= (float)video.width * cfg_video_want_fixed_width; yy /= (float)video.height * cfg_video_want_fixed_height; } *trans_x = (int)xx; *trans_y = (int)yy; } } /* -------------------------------------------------- */ /* input grab */ static int sdl2_video_is_input_grabbed(void) { return !!sdl2_GetWindowGrab(video.window); } static void sdl2_video_set_input_grabbed(int enabled) { sdl2_SetWindowGrab(video.window, enabled ? SDL_TRUE : SDL_FALSE); } /* -------------------------------------------------- */ /* warp mouse position */ static void sdl2_video_warp_mouse(unsigned int x, unsigned int y) { sdl2_WarpMouseInWindow(video.window, x, y); } static void sdl2_video_get_mouse_coordinates(unsigned int *x, unsigned int *y) { *x = video.mouse.x; *y = video.mouse.y; } /* -------------------------------------------------- */ /* menu toggling */ static int sdl2_video_have_menu(void) { #ifdef SCHISM_WIN32 return 1; #else return 0; #endif } static void sdl2_video_toggle_menu(SCHISM_UNUSED int on) { if (!sdl2_video_have_menu()) return; const int flags = sdl2_GetWindowFlags(video.window); int width, height; const int cache_size = !(flags & SDL_WINDOW_MAXIMIZED); if (cache_size) sdl2_GetWindowSize(video.window, &width, &height); #ifdef SCHISM_WIN32 /* Get the HWND */ SDL_SysWMinfo wm_info; SDL_VERSION(&wm_info.version); if (!sdl2_GetWindowWMInfo(video.window, &wm_info)) return; win32_toggle_menu(wm_info.info.win.window, on); #else /* ... */ #endif if (cache_size) sdl2_SetWindowSize(video.window, width, height); } /* ------------------------------------------------------------ */ SCHISM_HOT static void sdl2_video_blit(void) { SDL_Rect dstrect; if (cfg_video_want_fixed) { dstrect.x = 0; dstrect.y = 0; dstrect.w = cfg_video_want_fixed_width; dstrect.h = cfg_video_want_fixed_height; } sdl2_RenderClear(video.renderer); // regular format blitter unsigned char *pixels; int pitch; sdl2_LockTexture(video.texture, NULL, (void **)&pixels, &pitch); switch (video.format) { case SDL_PIXELFORMAT_IYUV: { video_blitUV(pixels, pitch, video.yuv.pal_y); pixels += (NATIVE_SCREEN_HEIGHT * pitch); video_blitTV(pixels, pitch, video.yuv.pal_u); pixels += (NATIVE_SCREEN_HEIGHT * pitch) / 4; video_blitTV(pixels, pitch, video.yuv.pal_v); break; } case SDL_PIXELFORMAT_YV12: { video_blitUV(pixels, pitch, video.yuv.pal_y); pixels += (NATIVE_SCREEN_HEIGHT * pitch); video_blitTV(pixels, pitch, video.yuv.pal_v); pixels += (NATIVE_SCREEN_HEIGHT * pitch) / 4; video_blitTV(pixels, pitch, video.yuv.pal_u); break; } default: { video_blit11(video.bpp, pixels, pitch, video.pal); break; } } sdl2_UnlockTexture(video.texture); sdl2_RenderCopy(video.renderer, video.texture, NULL, (cfg_video_want_fixed) ? &dstrect : NULL); sdl2_RenderPresent(video.renderer); } /* ------------------------------------------------- */ static void sdl2_video_mousecursor_changed(void) { const int vis = video_mousecursor_visible(); sdl2_ShowCursor(vis == MOUSE_SYSTEM); // Totally turn off mouse event sending when the mouse is disabled int evstate = (vis == MOUSE_DISABLED) ? SDL_DISABLE : SDL_ENABLE; if (evstate != sdl2_EventState(SDL_MOUSEMOTION, SDL_QUERY)) { sdl2_EventState(SDL_MOUSEMOTION, evstate); sdl2_EventState(SDL_MOUSEBUTTONDOWN, evstate); sdl2_EventState(SDL_MOUSEBUTTONUP, evstate); } } ////////////////////////////////////////////////////////////////////////////// static int sdl2_video_get_wm_data(video_wm_data_t *wm_data) { SDL_SysWMinfo info; SDL_VERSION(&info.version); if (!sdl2_GetWindowWMInfo(video.window, &info)) return 0; switch (info.subsystem) { #ifdef SDL_VIDEO_DRIVER_WINDOWS case SDL_SYSWM_WINDOWS: wm_data->subsystem = VIDEO_WM_DATA_SUBSYSTEM_WINDOWS; wm_data->data.windows.hwnd = info.info.win.window; break; #endif #ifdef SDL_VIDEO_DRIVER_X11 case SDL_SYSWM_X11: wm_data->subsystem = VIDEO_WM_DATA_SUBSYSTEM_X11; wm_data->data.x11.display = info.info.x11.display; wm_data->data.x11.window = info.info.x11.window; wm_data->data.x11.lock_func = NULL; wm_data->data.x11.unlock_func = NULL; break; #endif default: return 0; } return 1; } static void sdl2_video_show_cursor(int enabled) { sdl2_ShowCursor(enabled ? SDL_ENABLE : SDL_DISABLE); } ////////////////////////////////////////////////////////////////////////////// static int sdl2_video_load_syms(void) { SCHISM_SDL2_SYM(InitSubSystem); SCHISM_SDL2_SYM(QuitSubSystem); SCHISM_SDL2_SYM(GetCurrentVideoDriver); SCHISM_SDL2_SYM(GetCurrentDisplayMode); SCHISM_SDL2_SYM(GetRendererInfo); SCHISM_SDL2_SYM(ShowCursor); SCHISM_SDL2_SYM(GetWindowWMInfo); SCHISM_SDL2_SYM(GetWindowFlags); SCHISM_SDL2_SYM(MapRGB); SCHISM_SDL2_SYM(SetWindowPosition); SCHISM_SDL2_SYM(SetWindowSize); SCHISM_SDL2_SYM(SetWindowFullscreen); SCHISM_SDL2_SYM(GetWindowPosition); SCHISM_SDL2_SYM(CreateWindow); SCHISM_SDL2_SYM(CreateRenderer); SCHISM_SDL2_SYM(CreateTexture); SCHISM_SDL2_SYM(AllocFormat); SCHISM_SDL2_SYM(FreeFormat); SCHISM_SDL2_SYM(DestroyTexture); SCHISM_SDL2_SYM(DestroyRenderer); SCHISM_SDL2_SYM(DestroyWindow); SCHISM_SDL2_SYM(EventState); SCHISM_SDL2_SYM(IsScreenSaverEnabled); SCHISM_SDL2_SYM(EnableScreenSaver); SCHISM_SDL2_SYM(DisableScreenSaver); SCHISM_SDL2_SYM(RenderGetScale); SCHISM_SDL2_SYM(SetWindowGrab); SCHISM_SDL2_SYM(WarpMouseInWindow); SCHISM_SDL2_SYM(GetWindowFlags); SCHISM_SDL2_SYM(GetWindowSize); SCHISM_SDL2_SYM(SetWindowSize); SCHISM_SDL2_SYM(RenderClear); SCHISM_SDL2_SYM(LockTexture); SCHISM_SDL2_SYM(UnlockTexture); SCHISM_SDL2_SYM(RenderCopy); SCHISM_SDL2_SYM(RenderPresent); SCHISM_SDL2_SYM(SetWindowTitle); SCHISM_SDL2_SYM(CreateRGBSurfaceFrom); SCHISM_SDL2_SYM(SetWindowIcon); SCHISM_SDL2_SYM(FreeSurface); SCHISM_SDL2_SYM(SetHint); SCHISM_SDL2_SYM(RenderSetLogicalSize); SCHISM_SDL2_SYM(GetWindowGrab); return 0; } static int sdl2_0_18_video_load_syms(void) { #if SDL2_DYNAMIC_LOAD || SDL_VERSION_ATLEAST(2, 0, 18) SCHISM_SDL2_SYM(RenderWindowToLogical); return 0; #else return -1; #endif } static int sdl2_video_init(void) { if (!sdl2_init()) return 0; if (sdl2_video_load_syms()) { sdl2_quit(); return 0; } // :) sdl2_0_18_video_load_syms(); if (sdl2_InitSubSystem(SDL_INIT_VIDEO) < 0) { sdl2_quit(); return 0; } if (!events_init(&schism_events_backend_sdl2)) { sdl2_QuitSubSystem(SDL_INIT_VIDEO); sdl2_quit(); return 0; } // also grab the keyboard as well sdl2_SetHint("SDL_GRAB_KEYBOARD", "1"); return 1; } static void sdl2_video_quit(void) { sdl2_QuitSubSystem(SDL_INIT_VIDEO); sdl2_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_video_backend_t schism_video_backend_sdl2 = { .init = sdl2_video_init, .quit = sdl2_video_quit, .startup = sdl2_video_startup, .shutdown = sdl2_video_shutdown, .is_fullscreen = sdl2_video_is_fullscreen, .width = sdl2_video_width, .height = sdl2_video_height, .driver_name = sdl2_video_driver_name, .report = sdl2_video_report, .set_hardware = sdl2_video_set_hardware, .setup = sdl2_video_setup, .fullscreen = sdl2_video_fullscreen, .resize = sdl2_video_resize, .colors = sdl2_video_colors, .is_focused = sdl2_video_is_focused, .is_visible = sdl2_video_is_visible, .is_wm_available = sdl2_video_is_wm_available, .is_hardware = sdl2_video_is_hardware, .is_screensaver_enabled = sdl2_video_is_screensaver_enabled, .toggle_screensaver = sdl2_video_toggle_screensaver, .translate = sdl2_video_translate, .get_logical_coordinates = sdl2_video_get_logical_coordinates, .is_input_grabbed = sdl2_video_is_input_grabbed, .set_input_grabbed = sdl2_video_set_input_grabbed, .warp_mouse = sdl2_video_warp_mouse, .get_mouse_coordinates = sdl2_video_get_mouse_coordinates, .have_menu = sdl2_video_have_menu, .toggle_menu = sdl2_video_toggle_menu, .blit = sdl2_video_blit, .mousecursor_changed = sdl2_video_mousecursor_changed, .get_wm_data = sdl2_video_get_wm_data, .show_cursor = sdl2_video_show_cursor, }; schismtracker-20250313/sys/sdl3/000077500000000000000000000000001476471630300163135ustar00rootroot00000000000000schismtracker-20250313/sys/sdl3/audio.c000066400000000000000000000217321476471630300175650ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "mem.h" #include "str.h" #include "backend/audio.h" #include "mt.h" #include "init.h" struct schism_audio_device { // In SDL3, everything is just a stream. SDL_AudioStream *stream; // We have to do this ourselves now mt_mutex_t *mutex; void (*callback)(uint8_t *stream, int len); }; static bool (SDLCALL *sdl3_InitSubSystem)(SDL_InitFlags flags) = NULL; static void (SDLCALL *sdl3_QuitSubSystem)(SDL_InitFlags flags) = NULL; static int (SDLCALL *sdl3_GetNumAudioDrivers)(void) = NULL; static const char *(SDLCALL *sdl3_GetAudioDriver)(int i) = NULL; static bool (SDLCALL *sdl3_GetAudioDeviceFormat)(SDL_AudioDeviceID devid, SDL_AudioSpec *spec, int *sample_frames) = NULL; static const char * (SDLCALL *sdl3_GetAudioDeviceName)(SDL_AudioDeviceID devid) = NULL; static SDL_AudioDeviceID * (SDLCALL *sdl3_GetAudioPlaybackDevices)(int *count) = NULL; static SDL_AudioStream *(SDLCALL *sdl3_OpenAudioDeviceStream)(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec, SDL_AudioStreamCallback callback, void *userdata) = NULL; static void (SDLCALL *sdl3_DestroyAudioStream)(SDL_AudioStream *stream) = NULL; static bool (SDLCALL *sdl3_PauseAudioDevice)(SDL_AudioDeviceID dev) = NULL; static bool (SDLCALL *sdl3_ResumeAudioDevice)(SDL_AudioDeviceID dev) = NULL; static SDL_AudioDeviceID (SDLCALL *sdl3_GetAudioStreamDevice)(SDL_AudioStream *stream) = NULL; static bool (SDLCALL *sdl3_PutAudioStreamData)(SDL_AudioStream *stream, const void *buf, int len) = NULL; // used to request a specific sample frame size static bool (SDLCALL *sdl3_SetHint)(const char *name, const char *value); static bool (SDLCALL *sdl3_ResetHint)(const char *name); static void (SDLCALL *sdl3_free)(void *ptr) = NULL; /* SDL_AudioInit and SDL_AudioQuit were completely removed * in SDL3, which means we have to do this always regardless. */ static int schism_init_audio_impl(const char *name) { const char *orig_drv = getenv("SDL_AUDIO_DRIVER"); if (name) setenv("SDL_AUDIO_DRIVER", name, 1); int ret = sdl3_InitSubSystem(SDL_INIT_AUDIO); /* clean up our dirty work, or empty the var */ if (name) { if (orig_drv) { setenv("SDL_AUDIO_DRIVER", orig_drv, 1); } else { unsetenv("SDL_AUDIO_DRIVER"); } } /* forward any error, if any */ return ret; } static void schism_quit_audio_impl(void) { sdl3_QuitSubSystem(SDL_INIT_AUDIO); } /* ---------------------------------------------------------- */ /* drivers */ static int sdl3_audio_driver_count(void) { return sdl3_GetNumAudioDrivers(); } static const char *sdl3_audio_driver_name(int i) { return sdl3_GetAudioDriver(i); } /* --------------------------------------------------------------- */ static SDL_AudioDeviceID *devices = NULL; static int device_count = 0; static inline SCHISM_ALWAYS_INLINE void _free_devices(void) { if (devices) { sdl3_free(devices); devices = NULL; } } static uint32_t sdl3_audio_device_count(void) { _free_devices(); devices = sdl3_GetAudioPlaybackDevices(&device_count); return device_count; } static const char *sdl3_audio_device_name(uint32_t i) { if (i >= INT_MAX) return NULL; if ((int)i >= device_count) return NULL; return sdl3_GetAudioDeviceName(devices[i]); } /* ---------------------------------------------------------- */ static int sdl3_audio_init_driver(const char *driver) { if (!schism_init_audio_impl(driver)) return -1; // force poll for audio devices sdl3_audio_device_count(); return 0; } static void sdl3_audio_quit_driver(void) { schism_quit_audio_impl(); devices = NULL; } /* -------------------------------------------------------- */ static void SDLCALL sdl3_audio_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, SCHISM_UNUSED int total_amount) { schism_audio_device_t *dev = (schism_audio_device_t *)userdata; if (additional_amount > 0) { SCHISM_VLA_ALLOC(uint8_t, data, additional_amount); mt_mutex_lock(dev->mutex); dev->callback(data, additional_amount); mt_mutex_unlock(dev->mutex); sdl3_PutAudioStreamData(stream, data, additional_amount); SCHISM_VLA_FREE(data); } } static void sdl3_audio_close_device(schism_audio_device_t *dev); static schism_audio_device_t *sdl3_audio_open_device(uint32_t id, const schism_audio_spec_t *desired, schism_audio_spec_t *obtained) { schism_audio_device_t *dev = mem_calloc(1, sizeof(*dev)); dev->callback = desired->callback; uint32_t format; switch (desired->bits) { case 8: format = SDL_AUDIO_U8; break; default: case 16: format = SDL_AUDIO_S16; break; case 32: format = SDL_AUDIO_S32; break; } const SDL_AudioSpec sdl_desired = { .freq = desired->freq, .format = format, .channels = desired->channels, }; dev->mutex = mt_mutex_create(); if (!dev->mutex) goto fail; { // As it turns out, SDL is still just a shell script in disguise, and requires you to // pass everything as strings in order to change behavior. As for why they don't just // include this in the spec structure anymore is beyond me. char buf[64]; str_from_num(0, desired->samples, buf); sdl3_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, buf); } dev->stream = sdl3_OpenAudioDeviceStream((id == AUDIO_BACKEND_DEFAULT || id >= (uint32_t)device_count) ? SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK : devices[id], &sdl_desired, sdl3_audio_callback, dev); // reset this before checking if opening succeeded sdl3_ResetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES); if (!dev->stream) goto fail; // PAUSE! sdl3_PauseAudioDevice(sdl3_GetAudioStreamDevice(dev->stream)); // lolwut memcpy(obtained, desired, sizeof(schism_audio_spec_t)); // Retrieve the actual buffer size SDL is using (i.e., don't lie to the user) int samples; { SDL_AudioSpec xyzzy; sdl3_GetAudioDeviceFormat(id, &xyzzy, &samples); } obtained->samples = samples; return dev; fail: if (dev) sdl3_audio_close_device(dev); return NULL; } static void sdl3_audio_close_device(schism_audio_device_t *dev) { if (!dev) return; if (dev->stream) sdl3_DestroyAudioStream(dev->stream); if (dev->mutex) mt_mutex_delete(dev->mutex); free(dev); } static void sdl3_audio_lock_device(schism_audio_device_t *dev) { if (!dev) return; mt_mutex_lock(dev->mutex); } static void sdl3_audio_unlock_device(schism_audio_device_t *dev) { if (!dev) return; mt_mutex_unlock(dev->mutex); } static void sdl3_audio_pause_device(schism_audio_device_t *dev, int pause) { if (!dev) return; (pause ? sdl3_PauseAudioDevice : sdl3_ResumeAudioDevice)(sdl3_GetAudioStreamDevice(dev->stream)); } ////////////////////////////////////////////////////////////////////////////// // dynamic loading static int sdl3_audio_load_syms(void) { SCHISM_SDL3_SYM(InitSubSystem); SCHISM_SDL3_SYM(QuitSubSystem); SCHISM_SDL3_SYM(GetNumAudioDrivers); SCHISM_SDL3_SYM(GetAudioDriver); SCHISM_SDL3_SYM(GetAudioPlaybackDevices); SCHISM_SDL3_SYM(GetAudioDeviceName); SCHISM_SDL3_SYM(OpenAudioDeviceStream); SCHISM_SDL3_SYM(DestroyAudioStream); SCHISM_SDL3_SYM(PauseAudioDevice); SCHISM_SDL3_SYM(ResumeAudioDevice); SCHISM_SDL3_SYM(GetAudioStreamDevice); SCHISM_SDL3_SYM(GetAudioDeviceFormat); SCHISM_SDL3_SYM(PutAudioStreamData); SCHISM_SDL3_SYM(SetHint); SCHISM_SDL3_SYM(ResetHint); SCHISM_SDL3_SYM(free); return 0; } static int sdl3_audio_init(void) { if (!sdl3_init()) return 0; if (sdl3_audio_load_syms()) return 0; return 1; } static void sdl3_audio_quit(void) { // the subsystem quitting is handled by the quit driver function sdl3_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_audio_backend_t schism_audio_backend_sdl3 = { .init = sdl3_audio_init, .quit = sdl3_audio_quit, .driver_count = sdl3_audio_driver_count, .driver_name = sdl3_audio_driver_name, .device_count = sdl3_audio_device_count, .device_name = sdl3_audio_device_name, .init_driver = sdl3_audio_init_driver, .quit_driver = sdl3_audio_quit_driver, .open_device = sdl3_audio_open_device, .close_device = sdl3_audio_close_device, .lock_device = sdl3_audio_lock_device, .unlock_device = sdl3_audio_unlock_device, .pause_device = sdl3_audio_pause_device, }; schismtracker-20250313/sys/sdl3/clippy.c000066400000000000000000000064311476471630300177630ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" /* always include this one first, kthx */ #include "backend/clippy.h" #include "mem.h" #include "init.h" static bool (SDLCALL *sdl3_HasPrimarySelectionText)(void); static bool (SDLCALL *sdl3_SetPrimarySelectionText)(const char *text); static char * (SDLCALL *sdl3_GetPrimarySelectionText)(void); static bool (SDLCALL *sdl3_HasClipboardText)(void); static bool (SDLCALL *sdl3_SetClipboardText)(const char *text); static char * (SDLCALL *sdl3_GetClipboardText)(void); static void (SDLCALL *sdl3_free)(void *); static int sdl3_clippy_have_selection(void) { return sdl3_HasPrimarySelectionText(); } static int sdl3_clippy_have_clipboard(void) { return sdl3_HasClipboardText(); } static void sdl3_clippy_set_selection(const char *text) { sdl3_SetPrimarySelectionText(text); } static void sdl3_clippy_set_clipboard(const char *text) { sdl3_SetClipboardText(text); } static char *sdl3_clippy_get_selection(void) { char *sdl = sdl3_GetPrimarySelectionText(); if (sdl) { char *us = str_dup(sdl); sdl3_free(sdl); return us; } return str_dup(""); } static char *sdl3_clippy_get_clipboard(void) { char *sdl = sdl3_GetClipboardText(); if (sdl) { char *us = str_dup(sdl); sdl3_free(sdl); return us; } return str_dup(""); } ////////////////////////////////////////////////////////////////////////////// // dynamic loading static int sdl3_clippy_load_syms(void) { SCHISM_SDL3_SYM(HasClipboardText); SCHISM_SDL3_SYM(SetClipboardText); SCHISM_SDL3_SYM(GetClipboardText); SCHISM_SDL3_SYM(HasPrimarySelectionText); SCHISM_SDL3_SYM(SetPrimarySelectionText); SCHISM_SDL3_SYM(GetPrimarySelectionText); SCHISM_SDL3_SYM(free); return 0; } static int sdl3_clippy_init(void) { if (!sdl3_init()) return 0; if (sdl3_clippy_load_syms()) return 0; return 1; } static void sdl3_clippy_quit(void) { sdl3_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_clippy_backend_t schism_clippy_backend_sdl3 = { .init = sdl3_clippy_init, .quit = sdl3_clippy_quit, .have_selection = sdl3_clippy_have_selection, .get_selection = sdl3_clippy_get_selection, .set_selection = sdl3_clippy_set_selection, .have_clipboard = sdl3_clippy_have_clipboard, .get_clipboard = sdl3_clippy_get_clipboard, .set_clipboard = sdl3_clippy_set_clipboard, }; schismtracker-20250313/sys/sdl3/dmoz.c000066400000000000000000000036231476471630300174340ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" /* always include this one first, kthx */ #include "backend/dmoz.h" #include "mem.h" #include "init.h" static const char * (SDLCALL *sdl3_GetBasePath)(void); static char *sdl3_dmoz_get_exe_path(void) { const char *sdl = sdl3_GetBasePath(); if (!sdl) return NULL; char *us = str_dup(sdl); return us; } ////////////////////////////////////////////////////////////////////////////// // dynamic loading static int sdl3_dmoz_load_syms(void) { SCHISM_SDL3_SYM(GetBasePath); return 0; } static int sdl3_dmoz_init(void) { if (!sdl3_init()) return 0; if (sdl3_dmoz_load_syms()) return 0; return 1; } static void sdl3_dmoz_quit(void) { sdl3_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_dmoz_backend_t schism_dmoz_backend_sdl3 = { .init = sdl3_dmoz_init, .quit = sdl3_dmoz_quit, .get_exe_path = sdl3_dmoz_get_exe_path, }; schismtracker-20250313/sys/sdl3/events.c000066400000000000000000000264731476471630300177770ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "events.h" #include "backend/events.h" #include "util.h" #include "mem.h" #include "video.h" #include "init.h" static bool (SDLCALL *sdl3_InitSubSystem)(SDL_InitFlags flags) = NULL; static void (SDLCALL *sdl3_QuitSubSystem)(SDL_InitFlags flags) = NULL; static SDL_Keymod (SDLCALL *sdl3_GetModState)(void) = NULL; static bool (SDLCALL *sdl3_PollEvent)(SDL_Event *event) = NULL; static void (SDLCALL *sdl3_free)(void *) = NULL; #ifdef SCHISM_WIN32 static void (SDLCALL *sdl3_SetWindowsMessageHook)(SDL_WindowsMessageHook callback, void *userdata); #endif #ifdef SCHISM_USE_X11 static void (SDLCALL *sdl3_SetX11EventHook)(SDL_X11EventHook callback, void *userdata); #endif #ifndef SDLK_EXTENDED_MASK // Debian doesn't have this for some reason #define SDLK_EXTENDED_MASK (1u << 29) #endif // TODO: Re-add controller support, preferably in a global controller.c // that only receives raw events to translate them into keyboard/mouse events static schism_keymod_t sdl3_modkey_trans(SDL_Keymod mod) { schism_keymod_t res = 0; if (mod & SDL_KMOD_LSHIFT) res |= SCHISM_KEYMOD_LSHIFT; if (mod & SDL_KMOD_RSHIFT) res |= SCHISM_KEYMOD_RSHIFT; if (mod & SDL_KMOD_LCTRL) res |= SCHISM_KEYMOD_LCTRL; if (mod & SDL_KMOD_RCTRL) res |= SCHISM_KEYMOD_RCTRL; if (mod & SDL_KMOD_LALT) res |= SCHISM_KEYMOD_LALT; if (mod & SDL_KMOD_RALT) res |= SCHISM_KEYMOD_RALT; if (mod & SDL_KMOD_LGUI) res |= SCHISM_KEYMOD_LGUI; if (mod & SDL_KMOD_RGUI) res |= SCHISM_KEYMOD_RGUI; if (mod & SDL_KMOD_NUM) res |= SCHISM_KEYMOD_NUM; if (mod & SDL_KMOD_CAPS) res |= SCHISM_KEYMOD_CAPS; if (mod & SDL_KMOD_MODE) res |= SCHISM_KEYMOD_MODE; return res; } static schism_keymod_t sdl3_event_mod_state(void) { return sdl3_modkey_trans(sdl3_GetModState()); } /* These are here for linking text input to keyboard inputs. * If no keyboard input can be found, then the text will * be sent as a SCHISM_TEXTINPUT event. * * - paper */ static schism_event_t pending_keydown; static int have_pending_keydown = 0; static void push_pending_keydown(schism_event_t *event) { if (!have_pending_keydown) { pending_keydown = *event; have_pending_keydown = 1; } } static void pop_pending_keydown(const char *text) { /* text should always be in UTF-8 here */ if (have_pending_keydown) { if (text) { strncpy(pending_keydown.key.text, text, ARRAY_SIZE(pending_keydown.text.text)); pending_keydown.key.text[ARRAY_SIZE(pending_keydown.text.text)-1] = '\0'; } events_push_event(&pending_keydown); have_pending_keydown = 0; } } #ifdef SCHISM_WIN32 # include static bool SDLCALL sdl3_win32_msg_hook(SCHISM_UNUSED void *userdata, MSG *msg) { schism_event_t e; e.type = SCHISM_EVENT_WM_MSG; e.wm_msg.subsystem = SCHISM_WM_MSG_SUBSYSTEM_WINDOWS; e.wm_msg.msg.win.hwnd = msg->hwnd; e.wm_msg.msg.win.msg = msg->message; e.wm_msg.msg.win.wparam = msg->wParam; e.wm_msg.msg.win.lparam = msg->lParam; // ignore WM_DROPFILES messages. these are already handled // by the SDL_DROPFILES event and trying to use our implementation // only results in an empty string which is undesirable if (e.wm_msg.msg.win.msg != WM_DROPFILES) events_push_event(&e); return true; } #endif #ifdef SCHISM_USE_X11 # include static bool SDLCALL sdl3_x11_msg_hook(SCHISM_UNUSED void *userdata, SCHISM_UNUSED XEvent *xevent) { #if 0 schism_event_t e; e.type = SCHISM_EVENT_WM_MSG; e.wm_msg.subsystem = SCHISM_WM_MSG_SUBSYSTEM_X11; e.wm_msg.msg.x11.event.type = xevent->type; #if 0 // handled by SDL's clipboard if (e.syswm.msg->msg.x11.event.type == SelectionRequest) { e.wm_msg.msg.x11.event.selection_request.serial = xevent->xselectionrequest.serial; e.wm_msg.msg.x11.event.selection_request.send_event = xevent->xselectionrequest.send_event; // `Bool' in Xlib e.wm_msg.msg.x11.event.selection_request.display = xevent->xselectionrequest.display; // `Display *' in Xlib e.wm_msg.msg.x11.event.selection_request.owner = xevent->xselectionrequest.owner; // `Window' in Xlib e.wm_msg.msg.x11.event.selection_request.requestor = xevent->xselectionrequest.requestor; // `Window' in Xlib e.wm_msg.msg.x11.event.selection_request.selection = xevent->xselectionrequest.selection; // `Atom' in Xlib e.wm_msg.msg.x11.event.selection_request.target = xevent->xselectionrequest.target; // `Atom' in Xlib e.wm_msg.msg.x11.event.selection_request.property = xevent->xselectionrequest.property; // `Atom' in Xlib e.wm_msg.msg.x11.event.selection_request.time = xevent->xselectionrequest.time; // `Time' in Xlib events_push_event(&e); } #endif #endif return true; } #endif static void sdl3_pump_events(void) { SDL_Event e; while (sdl3_PollEvent(&e)) { schism_event_t schism_event = {0}; #ifdef SCHISM_CONTROLLER if (!sdl3_controller_sdlevent(&e)) continue; #endif switch (e.type) { case SDL_EVENT_QUIT: schism_event.type = SCHISM_QUIT; events_push_event(&schism_event); break; case SDL_EVENT_WINDOW_SHOWN: schism_event.type = SCHISM_WINDOWEVENT_SHOWN; events_push_event(&schism_event); break; case SDL_EVENT_WINDOW_EXPOSED: schism_event.type = SCHISM_WINDOWEVENT_EXPOSED; events_push_event(&schism_event); break; case SDL_EVENT_WINDOW_FOCUS_LOST: schism_event.type = SCHISM_WINDOWEVENT_FOCUS_LOST; events_push_event(&schism_event); break; case SDL_EVENT_WINDOW_FOCUS_GAINED: schism_event.type = SCHISM_WINDOWEVENT_FOCUS_GAINED; events_push_event(&schism_event); break; case SDL_EVENT_WINDOW_RESIZED: // the RESIZED event was changed in SDL 3 and now // is basically what SDL_WINDOWEVENT_SIZE_CHANGED // once was. doesn't matter for us, we handled // both events the same anyway. schism_event.type = SCHISM_WINDOWEVENT_RESIZED; schism_event.window.data.resized.width = e.window.data1; schism_event.window.data.resized.height = e.window.data2; events_push_event(&schism_event); break; case SDL_EVENT_KEY_DOWN: // pop any pending keydowns pop_pending_keydown(NULL); if (e.key.key & SDLK_EXTENDED_MASK) break; // I don't know how to handle these schism_event.type = SCHISM_KEYDOWN; schism_event.key.state = KEY_PRESS; schism_event.key.repeat = e.key.repeat; // Schism's and SDL3's representation of these are the same. schism_event.key.sym = e.key.key; schism_event.key.scancode = e.key.scancode; schism_event.key.mod = sdl3_modkey_trans(e.key.mod); // except this one! //if (!sdl3_TextInputActive()) { // push it NOW // events_push_event(&schism_event); //} else { push_pending_keydown(&schism_event); //} break; case SDL_EVENT_KEY_UP: // pop any pending keydowns pop_pending_keydown(NULL); if (e.key.key & SDLK_EXTENDED_MASK) break; // I don't know how to handle these schism_event.type = SCHISM_KEYUP; schism_event.key.state = KEY_RELEASE; schism_event.key.sym = e.key.key; schism_event.key.scancode = e.key.scancode; schism_event.key.mod = sdl3_modkey_trans(e.key.mod); events_push_event(&schism_event); break; case SDL_EVENT_TEXT_INPUT: if (have_pending_keydown) { pop_pending_keydown(e.text.text); } else { schism_event.type = SCHISM_TEXTINPUT; strncpy(schism_event.text.text, e.text.text, ARRAY_SIZE(schism_event.text.text)); schism_event.text.text[ARRAY_SIZE(schism_event.text.text)-1] = '\0'; events_push_event(&schism_event); } break; case SDL_EVENT_MOUSE_MOTION: schism_event.type = SCHISM_MOUSEMOTION; schism_event.motion.x = e.motion.x; schism_event.motion.y = e.motion.y; events_push_event(&schism_event); break; case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: schism_event.type = (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) ? SCHISM_MOUSEBUTTONDOWN : SCHISM_MOUSEBUTTONUP; switch (e.button.button) { case SDL_BUTTON_LEFT: schism_event.button.button = MOUSE_BUTTON_LEFT; break; case SDL_BUTTON_MIDDLE: schism_event.button.button = MOUSE_BUTTON_MIDDLE; break; case SDL_BUTTON_RIGHT: schism_event.button.button = MOUSE_BUTTON_RIGHT; break; } schism_event.button.state = (int)e.button.down; schism_event.button.clicks = e.button.clicks; schism_event.button.x = e.button.x; schism_event.button.y = e.button.y; events_push_event(&schism_event); break; case SDL_EVENT_MOUSE_WHEEL: schism_event.type = SCHISM_MOUSEWHEEL; schism_event.wheel.x = e.wheel.x; schism_event.wheel.y = e.wheel.y; schism_event.wheel.mouse_x = e.wheel.mouse_x; schism_event.wheel.mouse_y = e.wheel.mouse_y; events_push_event(&schism_event); break; case SDL_EVENT_DROP_FILE: schism_event.type = SCHISM_DROPFILE; schism_event.drop.file = str_dup(e.drop.data); events_push_event(&schism_event); break; /* these two have no structures because we don't use them */ case SDL_EVENT_AUDIO_DEVICE_ADDED: schism_event.type = SCHISM_AUDIODEVICEADDED; events_push_event(&schism_event); break; case SDL_EVENT_AUDIO_DEVICE_REMOVED: schism_event.type = SCHISM_AUDIODEVICEREMOVED; events_push_event(&schism_event); break; default: break; } } pop_pending_keydown(NULL); } ////////////////////////////////////////////////////////////////////////////// // dynamic loading static int sdl3_events_load_syms(void) { SCHISM_SDL3_SYM(InitSubSystem); SCHISM_SDL3_SYM(QuitSubSystem); SCHISM_SDL3_SYM(GetModState); SCHISM_SDL3_SYM(PollEvent); #ifdef SCHISM_WIN32 SCHISM_SDL3_SYM(SetWindowsMessageHook); #endif #ifdef SCHISM_USE_X11 SCHISM_SDL3_SYM(SetX11EventHook); #endif SCHISM_SDL3_SYM(free); return 0; } static int sdl3_events_init(void) { if (!sdl3_init()) return 0; if (sdl3_events_load_syms()) return 0; if (!sdl3_InitSubSystem(SDL_INIT_EVENTS)) return 0; #ifdef SCHISM_USE_X11 sdl3_SetX11EventHook(sdl3_x11_msg_hook, NULL); #endif #ifdef SCHISM_WIN32 sdl3_SetWindowsMessageHook(sdl3_win32_msg_hook, NULL); #endif return 1; } static void sdl3_events_quit(void) { sdl3_QuitSubSystem(SDL_INIT_EVENTS); sdl3_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_events_backend_t schism_events_backend_sdl3 = { .init = sdl3_events_init, .quit = sdl3_events_quit, #if !defined(SCHISM_WII) && !defined(SCHISM_WIIU) /* These ports have no key repeat... */ .flags = SCHISM_EVENTS_BACKEND_HAS_KEY_REPEAT, #else .flags = 0, #endif .keymod_state = sdl3_event_mod_state, .pump_events = sdl3_pump_events, }; schismtracker-20250313/sys/sdl3/init.c000066400000000000000000000070231476471630300174240ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "osdefs.h" // os_show_message_box #include "init.h" static bool (SDLCALL *sdl3_Init)(SDL_InitFlags flags); static void (SDLCALL *sdl3_Quit)(void); static const char *(SDLCALL *sdl3_GetError)(void); static int (SDLCALL *sdl3_GetVersion)(void); static int load_sdl3_syms(void); #ifdef SDL3_DYNAMIC_LOAD #include "loadso.h" static void *sdl3_dltrick_handle_ = NULL; static void sdl3_dlend(void) { if (sdl3_dltrick_handle_) { loadso_object_unload(sdl3_dltrick_handle_); sdl3_dltrick_handle_ = NULL; } } static int sdl3_dlinit(void) { // already have it? if (sdl3_dltrick_handle_) return 0; sdl3_dltrick_handle_ = library_load("SDL3-3.0", 0, 0); if (!sdl3_dltrick_handle_) { sdl3_dltrick_handle_ = library_load("SDL3", 0, 0); if (!sdl3_dltrick_handle_) return -1; } int retval = load_sdl3_syms(); if (retval < 0) sdl3_dlend(); return retval; } SCHISM_STATIC_ASSERT(sizeof(void (*)) == sizeof(void *), "dynamic loading code assumes function pointer and void pointer are of equivalent size"); int sdl3_load_sym(const char *fn, void *addr) { void *func = loadso_function_load(sdl3_dltrick_handle_, fn); if (!func) { printf("no such function: %s\n", fn); return 0; } memcpy(addr, &func, sizeof(void *)); return 1; } #else static int sdl3_dlinit(void) { load_sdl3_syms(); return 0; } #define sdl3_dlend() // nothing #endif static int load_sdl3_syms(void) { SCHISM_SDL3_SYM(Init); SCHISM_SDL3_SYM(Quit); SCHISM_SDL3_SYM(GetError); SCHISM_SDL3_SYM(GetVersion); return 0; } ////////////////////////////////////////////////////////////////////////////// // this is used so that SDL_Quit is only called // once every backend is done static int roll = 0; // returns non-zero on success or zero on error int sdl3_init(void) { if (!roll) { if (sdl3_dlinit()) return 0; // versions prior to the first stable release (3.2.0) have not been tested at all, // and will likely have serious issues if we even attempt to use them, so punt, // and fallback to an older version of SDL, possibly sdl2-compat or sdl12-compat. const int ver = sdl3_GetVersion(); if (!SCHISM_SEMVER_ATLEAST(3, 2, 0, SDL_VERSIONNUM_MAJOR(ver), SDL_VERSIONNUM_MINOR(ver), SDL_VERSIONNUM_MICRO(ver))) return 0; // the subsystems are initialized by the actual backends int r = sdl3_Init(0); if (!r) { os_show_message_box("SDL3: SDL_Init failed!", sdl3_GetError()); return 0; } } roll++; return roll; } void sdl3_quit(void) { if (roll > 0) roll--; if (roll == 0) { sdl3_Quit(); sdl3_dlend(); } } schismtracker-20250313/sys/sdl3/init.h000066400000000000000000000031071476471630300174300ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_SYS_SDL3_INIT_H_ #define SCHISM_SYS_SDL3_INIT_H_ #include #include "headers.h" int sdl3_init(void); void sdl3_quit(void); #ifdef SDL3_DYNAMIC_LOAD // must be called AFTER sdl3_init() int sdl3_load_sym(const char *fn, void *addr); #define SCHISM_SDL3_SYM(x) \ if (!sdl3_load_sym("SDL_" #x, &sdl3_##x)) return -1 #else #define SCHISM_SDL3_SYM(x) \ sdl3_##x = SDL_##x #endif #define SDL3_VERSION_ATLEAST(ver, mmajor, mminor, mpatch) \ SCHISM_SEMVER_ATLEAST(mmajor, mminor, mpatch, ver.major, ver.minor, ver.patch) #endif /* SCHISM_SYS_SDL3_INIT_H_ */ schismtracker-20250313/sys/sdl3/mt.c000066400000000000000000000141021476471630300170750ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "mem.h" #include "backend/mt.h" #include "init.h" /* ------------------------------------ */ static SDL_Thread *(SDLCALL *sdl3_CreateThreadRuntime)(SDL_ThreadFunction fn, const char *name, void *data, SDL_FunctionPointer begin, SDL_FunctionPointer end) = NULL; static void (SDLCALL *sdl3_WaitThread)(SDL_Thread * thread, int *status) = NULL; static bool (SDLCALL *sdl3_SetCurrentThreadPriority)(SDL_ThreadPriority priority) = NULL; static Uint64 (SDLCALL *sdl3_GetCurrentThreadID)(void) = NULL; struct mt_thread { SDL_Thread *thread; schism_thread_function_t func; void *userdata; }; static int SDLCALL sdl3_dummy_thread_func(void *userdata) { mt_thread_t *thread = userdata; return thread->func(thread->userdata); } static mt_thread_t *sdl3_thread_create(schism_thread_function_t func, const char *name, void *userdata) { mt_thread_t *thread = mem_alloc(sizeof(*thread)); thread->func = func; thread->userdata = userdata; /* ew */ SDL_Thread *sdl_thread = sdl3_CreateThreadRuntime(sdl3_dummy_thread_func, name, thread, (SDL_FunctionPointer)(SDL_BeginThreadFunction), (SDL_FunctionPointer)(SDL_EndThreadFunction)); if (!sdl_thread) { free(thread); return NULL; } thread->thread = sdl_thread; return thread; } static void sdl3_thread_wait(mt_thread_t *thread, int *status) { sdl3_WaitThread(thread->thread, status); free(thread); } static void sdl3_thread_set_priority(int priority) { // !!! FIXME this should use a switch statement, // or this API should be removed altogether sdl3_SetCurrentThreadPriority(priority); } // returns the current thread's ID static mt_thread_id_t sdl3_thread_id(void) { return sdl3_GetCurrentThreadID(); } /* -------------------------------------------------------------- */ /* mutexes */ static SDL_Mutex *(SDLCALL *sdl3_CreateMutex)(void) = NULL; static void (SDLCALL *sdl3_DestroyMutex)(SDL_Mutex * mutex) = NULL; static void (SDLCALL *sdl3_LockMutex)(SDL_Mutex * mutex) = NULL; static void (SDLCALL *sdl3_UnlockMutex)(SDL_Mutex * mutex) = NULL; struct mt_mutex { SDL_Mutex *mutex; }; static mt_mutex_t *sdl3_mutex_create(void) { mt_mutex_t *mutex = mem_alloc(sizeof(*mutex)); mutex->mutex = sdl3_CreateMutex(); if (!mutex->mutex) { free(mutex); return NULL; } return mutex; } static void sdl3_mutex_delete(mt_mutex_t *mutex) { sdl3_DestroyMutex(mutex->mutex); free(mutex); } static void sdl3_mutex_lock(mt_mutex_t *mutex) { sdl3_LockMutex(mutex->mutex); } static void sdl3_mutex_unlock(mt_mutex_t *mutex) { sdl3_UnlockMutex(mutex->mutex); } /* -------------------------------------------------------------- */ static SDL_Condition *(SDLCALL *sdl3_CreateCondition)(void) = NULL; static void (SDLCALL *sdl3_DestroyCondition)(SDL_Condition *cond) = NULL; static void (SDLCALL *sdl3_SignalCondition)(SDL_Condition *cond) = NULL; static void (SDLCALL *sdl3_WaitCondition)(SDL_Condition *cond, SDL_Mutex *mutex) = NULL; static bool (SDLCALL *sdl3_WaitConditionTimeout)(SDL_Condition *cond, SDL_Mutex *mutex, int32_t timeout) = NULL; struct mt_cond { SDL_Condition *cond; }; static mt_cond_t *sdl3_cond_create(void) { mt_cond_t *cond = mem_alloc(sizeof(*cond)); cond->cond = sdl3_CreateCondition(); if (!cond->cond) { free(cond); return NULL; } return cond; } static void sdl3_cond_delete(mt_cond_t *cond) { sdl3_DestroyCondition(cond->cond); free(cond); } static void sdl3_cond_signal(mt_cond_t *cond) { sdl3_SignalCondition(cond->cond); } static void sdl3_cond_wait(mt_cond_t *cond, mt_mutex_t *mutex) { sdl3_WaitCondition(cond->cond, mutex->mutex); } static void sdl3_cond_wait_timeout(mt_cond_t *cond, mt_mutex_t *mutex, uint32_t timeout) { sdl3_WaitConditionTimeout(cond->cond, mutex->mutex, timeout); } ////////////////////////////////////////////////////////////////////////////// static int sdl3_threads_load_syms(void) { SCHISM_SDL3_SYM(CreateThreadRuntime); SCHISM_SDL3_SYM(WaitThread); SCHISM_SDL3_SYM(SetCurrentThreadPriority); SCHISM_SDL3_SYM(GetCurrentThreadID); SCHISM_SDL3_SYM(CreateMutex); SCHISM_SDL3_SYM(DestroyMutex); SCHISM_SDL3_SYM(LockMutex); SCHISM_SDL3_SYM(UnlockMutex); SCHISM_SDL3_SYM(CreateCondition); SCHISM_SDL3_SYM(DestroyCondition); SCHISM_SDL3_SYM(SignalCondition); SCHISM_SDL3_SYM(WaitCondition); SCHISM_SDL3_SYM(WaitConditionTimeout); return 0; } static int sdl3_threads_init(void) { if (!sdl3_init()) return 0; if (sdl3_threads_load_syms()) return 0; return 1; } static void sdl3_threads_quit(void) { sdl3_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_mt_backend_t schism_mt_backend_sdl3 = { .init = sdl3_threads_init, .quit = sdl3_threads_quit, .thread_create = sdl3_thread_create, .thread_wait = sdl3_thread_wait, .thread_set_priority = sdl3_thread_set_priority, .thread_id = sdl3_thread_id, .mutex_create = sdl3_mutex_create, .mutex_delete = sdl3_mutex_delete, .mutex_lock = sdl3_mutex_lock, .mutex_unlock = sdl3_mutex_unlock, .cond_create = sdl3_cond_create, .cond_delete = sdl3_cond_delete, .cond_signal = sdl3_cond_signal, .cond_wait = sdl3_cond_wait, .cond_wait_timeout = sdl3_cond_wait_timeout, }; schismtracker-20250313/sys/sdl3/timer.c000066400000000000000000000064351476471630300176070ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "mt.h" #include "mem.h" #include "backend/timer.h" #include "init.h" static void (SDLCALL *sdl3_Delay)(uint32_t ms) = NULL; static void (SDLCALL *sdl3_DelayNS)(uint64_t ns) = NULL; static uint64_t (SDLCALL *sdl3_GetTicks)(void) = NULL; static uint64_t (SDLCALL *sdl3_GetTicksNS)(void) = NULL; static SDL_TimerID (SDLCALL *sdl3_AddTimer)(uint32_t ms, SDL_TimerCallback callback, void *param); static timer_ticks_t sdl3_timer_ticks(void) { return sdl3_GetTicks(); } static timer_ticks_t sdl3_timer_ticks_us(void) { return sdl3_GetTicksNS() / 1000ULL; } static void sdl3_timer_usleep(uint64_t us) { sdl3_DelayNS(us * 1000ULL); } static void sdl3_timer_msleep(uint32_t ms) { sdl3_Delay(ms); } ////////////////////////////////////////////////////////////////////////////// // oneshot timer struct _sdl3_timer_oneshot_curry { void (*callback)(void *param); void *param; }; static uint32_t SDLCALL _sdl3_timer_oneshot_callback(void *param, SCHISM_UNUSED SDL_TimerID timerID, SCHISM_UNUSED uint32_t interval) { struct _sdl3_timer_oneshot_curry *curry = (struct _sdl3_timer_oneshot_curry *)param; curry->callback(curry->param); free(curry); return 0; } static int sdl3_timer_oneshot(uint32_t interval, void (*callback)(void *param), void *param) { struct _sdl3_timer_oneshot_curry *curry = mem_alloc(sizeof(*curry)); curry->callback = callback; curry->param = param; SDL_TimerID id = sdl3_AddTimer(interval, _sdl3_timer_oneshot_callback, curry); if (!id) free(curry); return !!id; } ////////////////////////////////////////////////////////////////////////////// static int sdl3_timer_load_syms(void) { SCHISM_SDL3_SYM(Delay); SCHISM_SDL3_SYM(DelayNS); SCHISM_SDL3_SYM(GetTicks); SCHISM_SDL3_SYM(GetTicksNS); SCHISM_SDL3_SYM(AddTimer); return 0; } static int sdl3_timer_init(void) { if (!sdl3_init()) return 0; if (sdl3_timer_load_syms()) return 0; return 1; } static void sdl3_timer_quit(void) { sdl3_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_timer_backend_t schism_timer_backend_sdl3 = { .init = sdl3_timer_init, .quit = sdl3_timer_quit, .ticks = sdl3_timer_ticks, .ticks_us = sdl3_timer_ticks_us, .usleep = sdl3_timer_usleep, .msleep = sdl3_timer_msleep, .oneshot = sdl3_timer_oneshot, }; schismtracker-20250313/sys/sdl3/video.c000066400000000000000000000632031476471630300175710ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define NATIVE_SCREEN_WIDTH 640 #define NATIVE_SCREEN_HEIGHT 400 #define WINDOW_TITLE "Schism Tracker" #include "headers.h" #include "it.h" #include "charset.h" #include "bswap.h" #include "config.h" #include "video.h" #include "osdefs.h" #include "vgamem.h" #include "events.h" #include "backend/video.h" #include "init.h" #ifndef SCHISM_MACOSX #include "auto/schismico_hires.h" #endif static bool (SDLCALL *sdl3_InitSubSystem)(SDL_InitFlags flags) = NULL; static void (SDLCALL *sdl3_QuitSubSystem)(SDL_InitFlags flags) = NULL; static const char *(SDLCALL *sdl3_GetCurrentVideoDriver)(void); static SDL_DisplayID (SDLCALL *sdl3_GetDisplayForWindow)(SDL_Window *window); static const SDL_DisplayMode *(SDLCALL *sdl3_GetCurrentDisplayMode)(SDL_DisplayID id); static const char * (SDLCALL *sdl3_GetRendererName)(SDL_Renderer * renderer); static bool (SDLCALL *sdl3_ShowCursor)(void); static bool (SDLCALL *sdl3_HideCursor)(void); static SDL_WindowFlags (SDLCALL *sdl3_GetWindowFlags)(SDL_Window * window); static Uint32 (SDLCALL *sdl3_MapRGB)(const SDL_PixelFormatDetails *format, const SDL_Palette *palette, Uint8 r, Uint8 g, Uint8 b); static bool (SDLCALL *sdl3_SetWindowPosition)(SDL_Window * window, int x, int y); static bool (SDLCALL *sdl3_SetWindowSize)(SDL_Window * window, int w, int h); static bool (SDLCALL *sdl3_SetWindowFullscreen)(SDL_Window * window, bool fullscreen); static bool (SDLCALL *sdl3_GetWindowPosition)(SDL_Window * window, int *x, int *y); static SDL_Window * (SDLCALL *sdl3_CreateWindow)(const char *title, int w, int h, SDL_WindowFlags flags); static SDL_Renderer * (SDLCALL *sdl3_CreateRenderer)(SDL_Window *window, const char *name); static SDL_Texture * (SDLCALL *sdl3_CreateTexture)(SDL_Renderer * renderer, SDL_PixelFormat format, SDL_TextureAccess access, int w, int h); static const SDL_PixelFormatDetails * (SDLCALL *sdl3_GetPixelFormatDetails)(Uint32 pixel_format); static void (SDLCALL *sdl3_DestroyTexture)(SDL_Texture * texture); static void (SDLCALL *sdl3_DestroyRenderer)(SDL_Renderer * renderer); static void (SDLCALL *sdl3_DestroyWindow)(SDL_Window * window); static bool (SDLCALL *sdl3_ScreenSaverEnabled)(void); static bool (SDLCALL *sdl3_EnableScreenSaver)(void); static bool (SDLCALL *sdl3_DisableScreenSaver)(void); static bool (SDLCALL *sdl3_GetRenderScale)(SDL_Renderer * renderer, float *scaleX, float *scaleY); static void (SDLCALL *sdl3_WarpMouseInWindow)(SDL_Window * window, float x, float y); static bool (SDLCALL *sdl3_GetWindowSize)(SDL_Window * window, int *w, int *h); static bool (SDLCALL *sdl3_SetWindowSize)(SDL_Window * window, int w, int h); static bool (SDLCALL *sdl3_RenderClear)(SDL_Renderer * renderer); static bool (SDLCALL *sdl3_LockTexture)(SDL_Texture * texture, const SDL_Rect * rect, void **pixels, int *pitch); static void (SDLCALL *sdl3_UnlockTexture)(SDL_Texture * texture); static bool (SDLCALL *sdl3_RenderTexture)(SDL_Renderer * renderer, SDL_Texture * texture, const SDL_FRect * srcrect, const SDL_FRect * dstrect); static bool (SDLCALL *sdl3_RenderPresent)(SDL_Renderer * renderer); static bool (SDLCALL *sdl3_SetWindowTitle)(SDL_Window * window, const char *title); static bool (SDLCALL *sdl3_SetWindowIcon)(SDL_Window * window, SDL_Surface * icon); static void (SDLCALL *sdl3_DestroySurface)(SDL_Surface * surface); static bool (SDLCALL *sdl3_SetHint)(const char *name, const char *value); static bool (SDLCALL *sdl3_SetRenderLogicalPresentation)(SDL_Renderer *renderer, int w, int h, SDL_RendererLogicalPresentation mode); static bool (SDLCALL *sdl3_GetWindowMouseGrab)(SDL_Window * window); static bool (SDLCALL *sdl3_GetWindowKeyboardGrab)(SDL_Window * window); static bool (SDLCALL *sdl3_SetWindowMouseGrab)(SDL_Window * window, bool grabbed); static bool (SDLCALL *sdl3_SetWindowKeyboardGrab)(SDL_Window * window, bool grabbed); static bool (SDLCALL *sdl3_EventEnabled)(Uint32 type); static void (SDLCALL *sdl3_SetEventEnabled)(Uint32 type, bool enabled); static bool (SDLCALL *sdl3_RenderCoordinatesFromWindow)(SDL_Renderer * renderer, float windowX, float windowY, float *logicalX, float *logicalY); static bool (SDLCALL *sdl3_SetTextureScaleMode)(SDL_Texture * texture, SDL_ScaleMode scaleMode); static SDL_PropertiesID (SDLCALL *sdl3_GetRendererProperties)(SDL_Renderer *renderer); static SDL_PropertiesID (SDLCALL *sdl3_GetWindowProperties)(SDL_Window *window); static void *(SDLCALL *sdl3_GetPointerProperty)(SDL_PropertiesID props, const char *name, void *default_value); static Sint64 (SDLCALL *sdl3_GetNumberProperty)(SDL_PropertiesID props, const char *name, Sint64 default_value); static bool (SDLCALL *sdl3_StartTextInput)(SDL_Window *window) = NULL; // this is used in multiple parts here static int sdl3_video_get_wm_data(video_wm_data_t *wm_data); static SDL_Surface *(SDLCALL *sdl3_CreateSurfaceFrom)(int width, int height, SDL_PixelFormat format, void *pixels, int pitch); static SDL_PixelFormat (SDLCALL *sdl3_GetPixelFormatForMasks)(int bpp, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); #define sdl3_CreateRGBSurfaceFrom(pixels, width, height, depth, pitch, Rmask, Gmask, Bmask, Amask) \ sdl3_CreateSurfaceFrom(width, height, sdl3_GetPixelFormatForMasks(depth, Rmask, Gmask, Bmask, Amask), pixels, pitch); // This is just a macro that emulates the old SDL_ShowCursor behavior, // excluding SDL_QUERY. #define sdl3_compat_ShowCursor(x) \ ((int)((x) ? sdl3_ShowCursor() : sdl3_HideCursor())) static void sdl3_video_setup(const char *quality); static struct { SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *texture; const SDL_PixelFormatDetails *pixel_format; // may be NULL SDL_PixelFormat format; uint32_t bpp; // BYTES per pixel int width, height; struct { unsigned int x, y; } mouse; struct { /* TODO: need to save the state of the menu bar or else * these will be wrong if it's toggled while in fullscreen */ int width, height; int x, y; } saved; int fullscreen; struct { uint32_t pal_y[256]; uint32_t pal_u[256]; uint32_t pal_v[256]; } yuv; uint32_t pal[256]; } video = {0}; // Native formats, in order of preference. static const struct { uint32_t format; const char *name; } native_formats[] = { // RGB // ---------------- {SDL_PIXELFORMAT_XRGB8888, "RGB888"}, {SDL_PIXELFORMAT_ARGB8888, "ARGB8888"}, {SDL_PIXELFORMAT_RGB24, "RGB24"}, {SDL_PIXELFORMAT_RGB565, "RGB565"}, {SDL_PIXELFORMAT_XRGB1555, "RGB555"}, {SDL_PIXELFORMAT_ARGB1555, "ARGB1555"}, {SDL_PIXELFORMAT_XRGB4444, "RGB444"}, {SDL_PIXELFORMAT_ARGB4444, "ARGB4444"}, {SDL_PIXELFORMAT_RGB332, "RGB332"}, // ---------------- // YUV // ---------------- {SDL_PIXELFORMAT_IYUV, "IYUV"}, {SDL_PIXELFORMAT_YV12, "YV12"}, // {SDL_PIXELFORMAT_UYVY, "UYVY"}, // {SDL_PIXELFORMAT_YVYU, "YVYU"}, // {SDL_PIXELFORMAT_YUY2, "YUY2"}, // {SDL_PIXELFORMAT_NV12, "NV12"}, // {SDL_PIXELFORMAT_NV21, "NV21"}, // ---------------- }; static int sdl3_video_is_fullscreen(void) { return video.fullscreen; } static int sdl3_video_width(void) { return video.width; } static int sdl3_video_height(void) { return video.height; } static const char *sdl3_video_driver_name(void) { return sdl3_GetCurrentVideoDriver(); } static void sdl3_video_report(void) { struct { uint32_t num; const char *name, *type; } yuv_layouts[] = { {SDL_PIXELFORMAT_YV12, "YV12", "planar+tv"}, {SDL_PIXELFORMAT_IYUV, "IYUV", "planar+tv"}, {SDL_PIXELFORMAT_YVYU, "YVYU", "packed"}, {SDL_PIXELFORMAT_UYVY, "UYVY", "packed"}, {SDL_PIXELFORMAT_YUY2, "YUY2", "packed"}, {SDL_PIXELFORMAT_NV12, "NV12", "planar"}, {SDL_PIXELFORMAT_NV21, "NV21", "planar"}, {0, NULL, NULL}, }, *layout = yuv_layouts; { const char *name = sdl3_GetRendererName(video.renderer); log_appendf(5, " Using driver '%s'", sdl3_GetCurrentVideoDriver()); log_appendf(5, " %s renderer '%s'", (!strcmp(name, SDL_SOFTWARE_RENDERER)) ? "Software" : "Hardware-accelerated", name); } switch (video.format) { case SDL_PIXELFORMAT_IYUV: case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_YVYU: case SDL_PIXELFORMAT_UYVY: case SDL_PIXELFORMAT_YUY2: case SDL_PIXELFORMAT_NV12: case SDL_PIXELFORMAT_NV21: while (video.format != layout->num && layout->name != NULL) layout++; if (layout->name) log_appendf(5, " Display format: %s (%s)", layout->name, layout->type); else log_appendf(5, " Display format: %" PRIx32, video.format); break; default: log_appendf(5, " Display format: %"PRIu32" bits/pixel", SDL_BITSPERPIXEL(video.format)); break; } if (video.fullscreen) { const SDL_DisplayID id = sdl3_GetDisplayForWindow(video.window); if (id) { const SDL_DisplayMode *display = sdl3_GetCurrentDisplayMode(id); if (display) log_appendf(5, " Display dimensions: %dx%d", display->w, display->h); } } } static void set_icon(void) { sdl3_SetWindowTitle(video.window, WINDOW_TITLE); #ifndef SCHISM_MACOSX /* apple/macs use a bundle; this overrides their nice pretty icon */ { uint32_t *pixels; int width, height; if (!xpmdata(_schism_icon_xpm_hires, &pixels, &width, &height)) { SDL_Surface *icon = sdl3_CreateRGBSurfaceFrom(pixels, width, height, 32, width * sizeof(uint32_t), 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); if (icon) { sdl3_SetWindowIcon(video.window, icon); sdl3_DestroySurface(icon); } free(pixels); } } #endif } static void sdl3_video_redraw_texture(void) { size_t pref_last = ARRAY_SIZE(native_formats); uint32_t format = SDL_PIXELFORMAT_XRGB8888; if (video.texture) sdl3_DestroyTexture(video.texture); if (*cfg_video_format) { for (size_t i = 0; i < ARRAY_SIZE(native_formats); i++) { if (!charset_strcasecmp(cfg_video_format, CHARSET_UTF8, native_formats[i].name, CHARSET_UTF8)) { format = native_formats[i].format; goto got_format; } } } // We want to find the best format we can natively // output to. If we can't, then we fall back to // SDL_PIXELFORMAT_RGB888 and let SDL deal with the // conversion. // LOL WOW THIS API SUCKS SDL_PropertiesID rprop = sdl3_GetRendererProperties(video.renderer); if (rprop) { const SDL_PixelFormat *formats = sdl3_GetPointerProperty(rprop, SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, NULL); if (formats) { for (uint32_t i = 0; formats[i] != SDL_PIXELFORMAT_UNKNOWN; i++) for (size_t j = 0; j < ARRAY_SIZE(native_formats); j++) if (formats[i] == native_formats[j].format && j < pref_last) format = native_formats[pref_last = j].format; } } got_format: video.texture = sdl3_CreateTexture(video.renderer, format, SDL_TEXTUREACCESS_STREAMING, NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT); video.pixel_format = sdl3_GetPixelFormatDetails(format); video.format = format; // fix interpolation setting sdl3_video_setup(cfg_video_interpolation); // find the bytes per pixel switch (video.format) { // irrelevant case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: break; default: video.bpp = video.pixel_format->bytes_per_pixel; break; } } static void sdl3_video_set_hardware(int hardware) { sdl3_DestroyTexture(video.texture); sdl3_DestroyRenderer(video.renderer); video.renderer = sdl3_CreateRenderer(video.window, (hardware) ? (const char *)NULL : SDL_SOFTWARE_RENDERER); // hope that all worked! sdl3_video_redraw_texture(); video_report(); } static void sdl3_video_shutdown(void) { sdl3_DestroyTexture(video.texture); sdl3_DestroyRenderer(video.renderer); sdl3_DestroyWindow(video.window); } static void sdl3_video_setup(const char *quality) { SDL_ScaleMode mode; if (!strcmp(quality, "nearest")) { mode = SDL_SCALEMODE_NEAREST; } else /*if (!strcmp(quality, "linear") || !strcmp(quality, "best"))*/ { mode = SDL_SCALEMODE_LINEAR; } // XXX Can we not have this option as a string? if (cfg_video_interpolation != quality) strncpy(cfg_video_interpolation, quality, 7); sdl3_SetTextureScaleMode(video.texture, mode); } static void sdl3_video_startup(void) { vgamem_clear(); vgamem_flip(); sdl3_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); sdl3_SetHint("SDL_WINDOWS_DPI_AWARENESS", "unaware"); video.width = cfg_video_width; video.height = cfg_video_height; video.saved.x = video.saved.y = SDL_WINDOWPOS_CENTERED; video.window = sdl3_CreateWindow(WINDOW_TITLE, video.width, video.height, SDL_WINDOW_RESIZABLE); video_fullscreen(cfg_video_fullscreen); video_set_hardware(cfg_video_hardware); /* Aspect ratio correction if it's wanted */ if (cfg_video_want_fixed) sdl3_SetRenderLogicalPresentation(video.renderer, cfg_video_want_fixed_width, cfg_video_want_fixed_height, SDL_LOGICAL_PRESENTATION_LETTERBOX); if (video_have_menu() && !video.fullscreen) { sdl3_SetWindowSize(video.window, video.width, video.height); sdl3_SetWindowPosition(video.window, video.saved.x, video.saved.y); } /* okay, i think we're ready */ sdl3_HideCursor(); sdl3_StartTextInput(video.window); set_icon(); } static void sdl3_video_fullscreen(int new_fs_flag) { const int have_menu = video_have_menu(); /* positive new_fs_flag == set, negative == toggle */ video.fullscreen = (new_fs_flag >= 0) ? !!new_fs_flag : !video.fullscreen; if (video.fullscreen) { if (have_menu) { sdl3_GetWindowSize(video.window, &video.saved.width, &video.saved.height); sdl3_GetWindowPosition(video.window, &video.saved.x, &video.saved.y); } sdl3_SetWindowFullscreen(video.window, 1); if (have_menu) video_toggle_menu(0); } else { sdl3_SetWindowFullscreen(video.window, 0); if (have_menu) { /* the menu must be toggled first here */ video_toggle_menu(1); sdl3_SetWindowSize(video.window, video.saved.width, video.saved.height); sdl3_SetWindowPosition(video.window, video.saved.x, video.saved.y); } set_icon(); /* XXX is this necessary */ } } static void sdl3_video_resize(unsigned int width, unsigned int height) { video.width = width; video.height = height; status.flags |= (NEED_UPDATE); } static void yuv_pal_(int i, unsigned char rgb[3]) { unsigned int y, u, v; video_rgb_to_yuv(&y, &u, &v, rgb); switch (video.format) { case SDL_PIXELFORMAT_IYUV: case SDL_PIXELFORMAT_YV12: video.yuv.pal_y[i] = y; video.yuv.pal_u[i] = (u >> 4) & 0xF; video.yuv.pal_v[i] = (v >> 4) & 0xF; break; default: break; // err } } static void sdl_pal_(int i, unsigned char rgb[3]) { video.pal[i] = sdl3_MapRGB(video.pixel_format, NULL, rgb[0], rgb[1], rgb[2]); } static void sdl3_video_colors(unsigned char palette[16][3]) { static const int lastmap[] = { 0, 1, 2, 3, 5 }; int i; void (*fun)(int i, unsigned char rgb[3]); switch (video.format) { case SDL_PIXELFORMAT_IYUV: case SDL_PIXELFORMAT_YV12: fun = yuv_pal_; break; default: fun = sdl_pal_; break; } /* make our "base" space */ for (i = 0; i < 16; i++) fun(i, (unsigned char []){palette[i][0], palette[i][1], palette[i][2]}); /* make our "gradient" space */ for (i = 0; i < 128; i++) { int p = lastmap[(i>>5)]; fun(i + 128, (unsigned char []){ (int)palette[p][0] + (((int)(palette[p+1][0] - palette[p][0]) * (i & 0x1F)) / 0x20), (int)palette[p][1] + (((int)(palette[p+1][1] - palette[p][1]) * (i & 0x1F)) / 0x20), (int)palette[p][2] + (((int)(palette[p+1][2] - palette[p][2]) * (i & 0x1F)) / 0x20), }); } } static int sdl3_video_is_focused(void) { return !!(sdl3_GetWindowFlags(video.window) & SDL_WINDOW_INPUT_FOCUS); } static int sdl3_video_is_visible(void) { return !(sdl3_GetWindowFlags(video.window) & SDL_WINDOW_HIDDEN); } static int sdl3_video_is_wm_available(void) { // Ok return 1; } static int sdl3_video_is_hardware(void) { const char *name = sdl3_GetRendererName(video.renderer); return strcmp(name, SDL_SOFTWARE_RENDERER); } /* -------------------------------------------------------- */ static int sdl3_video_is_screensaver_enabled(void) { return sdl3_ScreenSaverEnabled(); } static void sdl3_video_toggle_screensaver(int enabled) { if (enabled) sdl3_EnableScreenSaver(); else sdl3_DisableScreenSaver(); } /* ---------------------------------------------------------- */ /* coordinate translation */ static void sdl3_video_translate(unsigned int vx, unsigned int vy, unsigned int *x, unsigned int *y) { if (video_mousecursor_visible() && (video.mouse.x != vx || video.mouse.y != vy)) status.flags |= SOFTWARE_MOUSE_MOVED; vx *= NATIVE_SCREEN_WIDTH; vy *= NATIVE_SCREEN_HEIGHT; vx /= (cfg_video_want_fixed) ? cfg_video_want_fixed_width : video.width; vy /= (cfg_video_want_fixed) ? cfg_video_want_fixed_height : video.height; vx = MIN(vx, NATIVE_SCREEN_WIDTH - 1); vy = MIN(vy, NATIVE_SCREEN_HEIGHT - 1); video.mouse.x = vx; video.mouse.y = vy; if (x) *x = vx; if (y) *y = vy; } static void sdl3_video_get_logical_coordinates(int x, int y, int *trans_x, int *trans_y) { if (!cfg_video_want_fixed) { *trans_x = x; *trans_y = y; } else { float xx, yy; sdl3_RenderCoordinatesFromWindow(video.renderer, x, y, &xx, &yy); } } /* -------------------------------------------------- */ /* input grab */ static int sdl3_video_is_input_grabbed(void) { return sdl3_GetWindowMouseGrab(video.window) && sdl3_GetWindowKeyboardGrab(video.window); } static void sdl3_video_set_input_grabbed(int enabled) { sdl3_SetWindowMouseGrab(video.window, (bool)enabled); sdl3_SetWindowKeyboardGrab(video.window, (bool)enabled); } /* -------------------------------------------------- */ /* warp mouse position */ static void sdl3_video_warp_mouse(unsigned int x, unsigned int y) { sdl3_WarpMouseInWindow(video.window, x, y); } static void sdl3_video_get_mouse_coordinates(unsigned int *x, unsigned int *y) { *x = video.mouse.x; *y = video.mouse.y; } /* -------------------------------------------------- */ /* menu toggling */ static int sdl3_video_have_menu(void) { #ifdef SCHISM_WIN32 return 1; #else return 0; #endif } static void sdl3_video_toggle_menu(SCHISM_UNUSED int on) { if (!sdl3_video_have_menu()) return; const int flags = sdl3_GetWindowFlags(video.window); int width, height; const int cache_size = !(flags & SDL_WINDOW_MAXIMIZED); if (cache_size) sdl3_GetWindowSize(video.window, &width, &height); #ifdef SCHISM_WIN32 /* Get the HWND */ video_wm_data_t wm_data; if (!sdl3_video_get_wm_data(&wm_data)) return; win32_toggle_menu(wm_data.data.windows.hwnd, on); #else /* ... */ #endif if (cache_size) sdl3_SetWindowSize(video.window, width, height); } /* ------------------------------------------------------------ */ SCHISM_HOT static void sdl3_video_blit(void) { SDL_FRect dstrect; if (cfg_video_want_fixed) { dstrect = (SDL_FRect){ .x = 0, .y = 0, .w = cfg_video_want_fixed_width, .h = cfg_video_want_fixed_height, }; } sdl3_RenderClear(video.renderer); // regular format blitter unsigned char *pixels; int pitch; sdl3_LockTexture(video.texture, NULL, (void **)&pixels, &pitch); switch (video.format) { case SDL_PIXELFORMAT_IYUV: { video_blitUV(pixels, pitch, video.yuv.pal_y); pixels += (NATIVE_SCREEN_HEIGHT * pitch); video_blitTV(pixels, pitch, video.yuv.pal_u); pixels += (NATIVE_SCREEN_HEIGHT * pitch) / 4; video_blitTV(pixels, pitch, video.yuv.pal_v); break; } case SDL_PIXELFORMAT_YV12: { video_blitUV(pixels, pitch, video.yuv.pal_y); pixels += (NATIVE_SCREEN_HEIGHT * pitch); video_blitTV(pixels, pitch, video.yuv.pal_v); pixels += (NATIVE_SCREEN_HEIGHT * pitch) / 4; video_blitTV(pixels, pitch, video.yuv.pal_u); break; } default: { video_blit11(video.bpp, pixels, pitch, video.pal); break; } } sdl3_UnlockTexture(video.texture); sdl3_RenderTexture(video.renderer, video.texture, NULL, (cfg_video_want_fixed) ? &dstrect : NULL); sdl3_RenderPresent(video.renderer); } /* ------------------------------------------------- */ static void sdl3_video_mousecursor_changed(void) { const int vis = video_mousecursor_visible(); sdl3_compat_ShowCursor(vis == MOUSE_SYSTEM); // Totally turn off mouse event sending when the mouse is disabled bool evstate = !(vis == MOUSE_DISABLED); if (evstate != sdl3_EventEnabled(SDL_EVENT_MOUSE_MOTION)) { sdl3_SetEventEnabled(SDL_EVENT_MOUSE_MOTION, evstate); sdl3_SetEventEnabled(SDL_EVENT_MOUSE_BUTTON_DOWN, evstate); sdl3_SetEventEnabled(SDL_EVENT_MOUSE_BUTTON_UP, evstate); } } ////////////////////////////////////////////////////////////////////////////// static int sdl3_video_get_wm_data(video_wm_data_t *wm_data) { SDL_PropertiesID wprops; if (!wm_data) return 0; wprops = sdl3_GetWindowProperties(video.window); if (!wprops) return 0; if (!strcmp(sdl3_GetCurrentVideoDriver(), "windows")) { wm_data->subsystem = VIDEO_WM_DATA_SUBSYSTEM_WINDOWS; wm_data->data.windows.hwnd = sdl3_GetPointerProperty(wprops, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); return !!wm_data->data.windows.hwnd; } else if (!strcmp(sdl3_GetCurrentVideoDriver(), "x11")) { wm_data->subsystem = VIDEO_WM_DATA_SUBSYSTEM_X11; wm_data->data.x11.display = sdl3_GetPointerProperty(wprops, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL); wm_data->data.x11.window = sdl3_GetNumberProperty(wprops, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); wm_data->data.x11.lock_func = NULL; wm_data->data.x11.unlock_func = NULL; return (wm_data->data.x11.display && wm_data->data.x11.window); } // maybe the real WM data was the friends we made along the way return 0; } static void sdl3_video_show_cursor(int enabled) { sdl3_compat_ShowCursor(enabled); } ////////////////////////////////////////////////////////////////////////////// static int sdl3_video_load_syms(void) { SCHISM_SDL3_SYM(InitSubSystem); SCHISM_SDL3_SYM(QuitSubSystem); SCHISM_SDL3_SYM(GetCurrentVideoDriver); SCHISM_SDL3_SYM(GetCurrentDisplayMode); SCHISM_SDL3_SYM(GetRendererName); SCHISM_SDL3_SYM(ShowCursor); SCHISM_SDL3_SYM(HideCursor); SCHISM_SDL3_SYM(GetWindowFlags); SCHISM_SDL3_SYM(MapRGB); SCHISM_SDL3_SYM(SetWindowPosition); SCHISM_SDL3_SYM(SetWindowSize); SCHISM_SDL3_SYM(SetWindowFullscreen); SCHISM_SDL3_SYM(GetWindowPosition); SCHISM_SDL3_SYM(CreateWindow); SCHISM_SDL3_SYM(CreateRenderer); SCHISM_SDL3_SYM(CreateTexture); SCHISM_SDL3_SYM(GetPixelFormatDetails); SCHISM_SDL3_SYM(DestroyTexture); SCHISM_SDL3_SYM(DestroyRenderer); SCHISM_SDL3_SYM(DestroyWindow); SCHISM_SDL3_SYM(EventEnabled); SCHISM_SDL3_SYM(SetEventEnabled); SCHISM_SDL3_SYM(ScreenSaverEnabled); SCHISM_SDL3_SYM(EnableScreenSaver); SCHISM_SDL3_SYM(DisableScreenSaver); SCHISM_SDL3_SYM(GetRenderScale); SCHISM_SDL3_SYM(WarpMouseInWindow); SCHISM_SDL3_SYM(GetWindowSize); SCHISM_SDL3_SYM(SetWindowSize); SCHISM_SDL3_SYM(RenderClear); SCHISM_SDL3_SYM(LockTexture); SCHISM_SDL3_SYM(UnlockTexture); SCHISM_SDL3_SYM(RenderTexture); SCHISM_SDL3_SYM(RenderPresent); SCHISM_SDL3_SYM(SetWindowTitle); SCHISM_SDL3_SYM(CreateSurfaceFrom); SCHISM_SDL3_SYM(GetPixelFormatForMasks); SCHISM_SDL3_SYM(SetWindowIcon); SCHISM_SDL3_SYM(DestroySurface); SCHISM_SDL3_SYM(SetHint); SCHISM_SDL3_SYM(SetRenderLogicalPresentation); SCHISM_SDL3_SYM(RenderCoordinatesFromWindow); SCHISM_SDL3_SYM(SetTextureScaleMode); SCHISM_SDL3_SYM(GetRendererProperties); SCHISM_SDL3_SYM(GetWindowProperties); SCHISM_SDL3_SYM(GetPointerProperty); SCHISM_SDL3_SYM(GetNumberProperty); SCHISM_SDL3_SYM(StartTextInput); SCHISM_SDL3_SYM(GetDisplayForWindow); SCHISM_SDL3_SYM(SetWindowKeyboardGrab); SCHISM_SDL3_SYM(GetWindowKeyboardGrab); SCHISM_SDL3_SYM(SetWindowMouseGrab); SCHISM_SDL3_SYM(GetWindowMouseGrab); return 0; } static int sdl3_video_init(void) { if (!sdl3_init()) return 0; if (sdl3_video_load_syms() || !sdl3_InitSubSystem(SDL_INIT_VIDEO)) { sdl3_quit(); return 0; } if (!events_init(&schism_events_backend_sdl3)) { sdl3_QuitSubSystem(SDL_INIT_VIDEO); sdl3_quit(); return 0; } return 1; } static void sdl3_video_quit(void) { sdl3_QuitSubSystem(SDL_INIT_VIDEO); sdl3_quit(); } ////////////////////////////////////////////////////////////////////////////// const schism_video_backend_t schism_video_backend_sdl3 = { .init = sdl3_video_init, .quit = sdl3_video_quit, .startup = sdl3_video_startup, .shutdown = sdl3_video_shutdown, .is_fullscreen = sdl3_video_is_fullscreen, .width = sdl3_video_width, .height = sdl3_video_height, .driver_name = sdl3_video_driver_name, .report = sdl3_video_report, .set_hardware = sdl3_video_set_hardware, .setup = sdl3_video_setup, .fullscreen = sdl3_video_fullscreen, .resize = sdl3_video_resize, .colors = sdl3_video_colors, .is_focused = sdl3_video_is_focused, .is_visible = sdl3_video_is_visible, .is_wm_available = sdl3_video_is_wm_available, .is_hardware = sdl3_video_is_hardware, .is_screensaver_enabled = sdl3_video_is_screensaver_enabled, .toggle_screensaver = sdl3_video_toggle_screensaver, .translate = sdl3_video_translate, .get_logical_coordinates = sdl3_video_get_logical_coordinates, .is_input_grabbed = sdl3_video_is_input_grabbed, .set_input_grabbed = sdl3_video_set_input_grabbed, .warp_mouse = sdl3_video_warp_mouse, .get_mouse_coordinates = sdl3_video_get_mouse_coordinates, .have_menu = sdl3_video_have_menu, .toggle_menu = sdl3_video_toggle_menu, .blit = sdl3_video_blit, .mousecursor_changed = sdl3_video_mousecursor_changed, .get_wm_data = sdl3_video_get_wm_data, .show_cursor = sdl3_video_show_cursor, }; schismtracker-20250313/sys/stdlib/000077500000000000000000000000001476471630300167275ustar00rootroot00000000000000schismtracker-20250313/sys/stdlib/asprintf.c000066400000000000000000000022471476471630300207260ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" int asprintf(char **buf, const char *fmt, ...) { va_list ap; va_start(ap, fmt); int status = vasprintf(buf, fmt, ap); va_end(ap); return status; } schismtracker-20250313/sys/stdlib/getopt.c000066400000000000000000000236141476471630300204030ustar00rootroot00000000000000/* -*- indent-tabs-mode: nil -*- * * ya_getopt - Yet another getopt * https://github.com/kubo/ya_getopt * * Copyright 2015 Kubo Takehiro * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of the authors. * */ #include "headers.h" char *ya_optarg = NULL; int ya_optind = 1; int ya_opterr = 1; int ya_optopt = '?'; static char *ya_optnext = NULL; static int posixly_correct = -1; static int handle_nonopt_argv = 0; static void ya_getopt_error(const char *optstring, const char *format, ...); static void check_gnu_extension(const char *optstring); static int ya_getopt_internal(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex, int long_only); static int ya_getopt_shortopts(int argc, char * const argv[], const char *optstring, int long_only); static int ya_getopt_longopts(int argc, char * const argv[], char *arg, const char *optstring, const struct option *longopts, int *longindex, int *long_only_flag); static void ya_getopt_error(const char *optstring, const char *format, ...) { if (ya_opterr && optstring[0] != ':') { va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); } } static void check_gnu_extension(const char *optstring) { if (optstring[0] == '+' || getenv("POSIXLY_CORRECT") != NULL) { posixly_correct = 1; } else { posixly_correct = 0; } if (optstring[0] == '-') { handle_nonopt_argv = 1; } else { handle_nonopt_argv = 0; } } static int is_option(const char *arg) { return arg[0] == '-' && arg[1] != '\0'; } int ya_getopt(int argc, char * const argv[], const char *optstring) { return ya_getopt_internal(argc, argv, optstring, NULL, NULL, 0); } int ya_getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex) { return ya_getopt_internal(argc, argv, optstring, longopts, longindex, 0); } int ya_getopt_long_only(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex) { return ya_getopt_internal(argc, argv, optstring, longopts, longindex, 1); } static int ya_getopt_internal(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex, int long_only) { static int start, end; if (ya_optopt == '?') { ya_optopt = 0; } if (posixly_correct == -1) { check_gnu_extension(optstring); } if (ya_optind == 0) { check_gnu_extension(optstring); ya_optind = 1; ya_optnext = NULL; } switch (optstring[0]) { case '+': case '-': optstring++; } if (ya_optnext == NULL && start != 0) { int last_pos = ya_optind - 1; ya_optind -= end - start; if (ya_optind <= 0) { ya_optind = 1; } while (start < end--) { int i; char *arg = argv[end]; for (i = end; i < last_pos; i++) { ((char **)argv)[i] = argv[i + 1]; } ((char const **)argv)[i] = arg; last_pos--; } start = 0; } if (ya_optind >= argc) { ya_optarg = NULL; return -1; } if (ya_optnext == NULL) { const char *arg = argv[ya_optind]; if (!is_option(arg)) { if (handle_nonopt_argv) { ya_optarg = argv[ya_optind++]; start = 0; return 1; } else if (posixly_correct) { ya_optarg = NULL; return -1; } else { int i; start = ya_optind; for (i = ya_optind + 1; i < argc; i++) { if (is_option(argv[i])) { end = i; break; } } if (i == argc) { ya_optarg = NULL; return -1; } ya_optind = i; arg = argv[ya_optind]; } } if (strcmp(arg, "--") == 0) { ya_optind++; return -1; } if (longopts != NULL && arg[1] == '-') { return ya_getopt_longopts(argc, argv, argv[ya_optind] + 2, optstring, longopts, longindex, NULL); } } if (ya_optnext == NULL) { ya_optnext = argv[ya_optind] + 1; } if (long_only) { int long_only_flag = 0; int rv = ya_getopt_longopts(argc, argv, ya_optnext, optstring, longopts, longindex, &long_only_flag); if (!long_only_flag) { ya_optnext = NULL; return rv; } } return ya_getopt_shortopts(argc, argv, optstring, long_only); } static int ya_getopt_shortopts(int argc, char * const argv[], const char *optstring, int long_only) { int opt = *ya_optnext; const char *os = strchr(optstring, opt); if (os == NULL) { ya_optarg = NULL; if (long_only) { ya_getopt_error(optstring, "%s: unrecognized option '-%s'\n", argv[0], ya_optnext); ya_optind++; ya_optnext = NULL; } else { ya_optopt = opt; ya_getopt_error(optstring, "%s: invalid option -- '%c'\n", argv[0], opt); if (*(++ya_optnext) == 0) { ya_optind++; ya_optnext = NULL; } } return '?'; } if (os[1] == ':') { if (ya_optnext[1] == 0) { ya_optind++; ya_optnext = NULL; if (os[2] == ':') { /* optional argument */ ya_optarg = NULL; } else { if (ya_optind == argc) { ya_optarg = NULL; ya_optopt = opt; ya_getopt_error(optstring, "%s: option requires an argument -- '%c'\n", argv[0], opt); if (optstring[0] == ':') { return ':'; } else { return '?'; } } ya_optarg = argv[ya_optind]; ya_optind++; } } else { ya_optarg = ya_optnext + 1; ya_optind++; } ya_optnext = NULL; } else { ya_optarg = NULL; if (ya_optnext[1] == 0) { ya_optnext = NULL; ya_optind++; } else { ya_optnext++; } } return opt; } static int ya_getopt_longopts(int argc, char * const argv[], char *arg, const char *optstring, const struct option *longopts, int *longindex, int *long_only_flag) { char *val = NULL; const struct option *opt; size_t namelen; int idx; for (idx = 0; longopts[idx].name != NULL; idx++) { opt = &longopts[idx]; namelen = strlen(opt->name); if (strncmp(arg, opt->name, namelen) == 0) { switch (arg[namelen]) { case '\0': switch (opt->has_arg) { case ya_required_argument: ya_optind++; if (ya_optind == argc) { ya_optarg = NULL; ya_optopt = opt->val; ya_getopt_error(optstring, "%s: option '--%s' requires an argument\n", argv[0], opt->name); if (optstring[0] == ':') { return ':'; } else { return '?'; } } val = argv[ya_optind]; break; } goto found; case '=': if (opt->has_arg == ya_no_argument) { const char *hyphens = (argv[ya_optind][1] == '-') ? "--" : "-"; ya_optind++; ya_optarg = NULL; ya_optopt = opt->val; ya_getopt_error(optstring, "%s: option '%s%s' doesn't allow an argument\n", argv[0], hyphens, opt->name); return '?'; } val = arg + namelen + 1; goto found; } } } if (long_only_flag) { *long_only_flag = 1; } else { ya_getopt_error(optstring, "%s: unrecognized option '%s'\n", argv[0], argv[ya_optind]); ya_optind++; } return '?'; found: ya_optarg = val; ya_optind++; if (opt->flag) { *opt->flag = opt->val; } if (longindex) { *longindex = idx; } return opt->flag ? 0 : opt->val; } schismtracker-20250313/sys/stdlib/localtime_r.c000066400000000000000000000031211476471630300213620ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "mt.h" static mt_mutex_t *localtime_r_mutex = NULL; static int initialized = 0; void localtime_r_quit(void) { mt_mutex_delete(localtime_r_mutex); } int localtime_r_init(void) { localtime_r_mutex = mt_mutex_create(); if (!localtime_r_mutex) return 0; initialized = 1; return 1; } struct tm *localtime_r(const time_t *timep, struct tm *result) { static struct tm *our_tm; // huh? if (!initialized) return NULL; mt_mutex_lock(localtime_r_mutex); our_tm = localtime(timep); *result = *our_tm; mt_mutex_unlock(localtime_r_mutex); return result; } schismtracker-20250313/sys/stdlib/memcmp.c000066400000000000000000000005171476471630300203540ustar00rootroot00000000000000#include /* size_t */ int memcmp(const void *s1, const void *s2, size_t n); int memcmp(const void *s1, const void *s2, size_t n) { register unsigned char *c1 = (unsigned char *) s1; register unsigned char *c2 = (unsigned char *) s2; while (n-- > 0) if (*c1++ != *c2++) return c1[-1] > c2[-1] ? 1 : -1; return 0; } schismtracker-20250313/sys/stdlib/setenv.c000066400000000000000000000026301476471630300204000ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" int setenv(const char *name, const char *value, int overwrite) { if (strchr(name, '=')) { errno = EINVAL; return -1; } if (overwrite || !getenv(name)) { char *penv; if (asprintf(&penv, "%s=%s", name, value) < 0) { errno = ENOMEM; return -1; } if (putenv(penv)) { free(penv); return -1; } // don't free -- penv is part of the environment now } return 0; } schismtracker-20250313/sys/stdlib/snprintf.c000066400000000000000000000022761476471630300207450ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" int snprintf(char *buffer, size_t count, const char *fmt, va_list ap) { va_list ap; va_start(ap, fmt); int x = vsnprintf(buffer, count, fmt, ap); va_end(ap); return x; } schismtracker-20250313/sys/stdlib/unsetenv.c000066400000000000000000000021171476471630300207430ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" int unsetenv(const char *name) { return setenv(name, "", 1); } schismtracker-20250313/sys/stdlib/vasprintf.c000066400000000000000000000025651476471630300211170ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" int vasprintf(char **result, const char *format, va_list args) { va_list args2; va_copy(args2, args); int num = vsnprintf(NULL, 0, format, args); if (num < 0) { va_end(args2); return -1; } *result = malloc(num + 1); if (!*result) { va_end(args2); return -1; } vsnprintf(*result, num + 1, format, args2); va_end(args2); return num; } schismtracker-20250313/sys/stdlib/vsnprintf.c000066400000000000000000000037611476471630300211330ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" // An implementation of the C99 vsnprintf() function that only uses // functions required by C89. It's kind of bloat-y, but I don't see // any other way of doing this besides using tmpfile() int vsnprintf(char *buffer, size_t count, const char *fmt, va_list ap) { FILE *f; int x; // Add a NUL terminator to the buffer before anything else, // as to prevent cases where this function fails and the // surrounding code always expects the result to be a valid // C string. if (count > 0) buffer[0] = '\0'; f = tmpfile(); if (!f) return -1; x = vfprintf(f, fmt, ap); if (x < 0) { // that's a wrap! fclose(f); return -1; } if (count > 0) { count = CLAMP(x, 0, count - 1); // Now read back the file into our buffer if (fseek(f, 0, SEEK_SET)) { fclose(f); return -1; } if (fread(buffer, 1, count, f) != count) { fclose(f); buffer[count] = '\0'; // WUH? return -1; } // Force NUL terminate buffer[count] = '\0'; } fclose(f); return x; }schismtracker-20250313/sys/wii/000077500000000000000000000000001476471630300162365ustar00rootroot00000000000000schismtracker-20250313/sys/wii/certs_bin.h000066400000000000000000000500571476471630300203660ustar00rootroot00000000000000static unsigned const char certs_bin[] = { '\000', '\001', '\000', '\000', '\263', '\255', '\263', '\042', '\153', '\074', '\075', '\377', '\033', '\113', '\100', '\167', '\026', '\377', '\117', '\172', '\327', '\144', '\206', '\310', '\225', '\254', '\126', '\055', '\041', '\361', '\006', '\001', '\324', '\366', '\144', '\050', '\031', '\034', '\007', '\166', '\217', '\337', '\032', '\342', '\316', '\173', '\047', '\311', '\017', '\274', '\012', '\320', '\061', '\045', '\170', '\354', '\007', '\171', '\266', '\127', '\324', '\067', '\044', '\023', '\247', '\370', '\157', '\014', '\024', '\300', '\357', '\156', '\011', '\101', '\355', '\053', '\005', '\354', '\071', '\127', '\066', '\007', '\211', '\000', '\112', '\207', '\215', '\056', '\235', '\370', '\307', '\245', '\251', '\370', '\312', '\263', '\021', '\261', '\030', '\171', '\127', '\273', '\370', '\230', '\342', '\242', '\124', '\002', '\317', '\124', '\071', '\317', '\053', '\277', '\240', '\341', '\370', '\134', '\006', '\156', '\203', '\232', '\340', '\224', '\312', '\107', '\340', '\025', '\130', '\365', '\156', '\157', '\064', '\351', '\052', '\242', '\334', '\070', '\223', '\176', '\067', '\315', '\214', '\134', '\115', '\375', '\057', '\021', '\117', '\350', '\150', '\311', '\250', '\331', '\376', '\330', '\156', '\014', '\041', '\165', '\242', '\275', '\176', '\211', '\271', '\307', '\265', '\023', '\364', '\032', '\171', '\141', '\104', '\071', '\020', '\357', '\371', '\327', '\376', '\127', '\042', '\030', '\325', '\155', '\373', '\177', '\111', '\172', '\244', '\313', '\220', '\324', '\361', '\256', '\261', '\166', '\344', '\150', '\135', '\247', '\224', '\100', '\140', '\230', '\057', '\004', '\110', '\100', '\037', '\317', '\306', '\272', '\353', '\332', '\026', '\060', '\264', '\163', '\264', '\025', '\043', '\065', '\010', '\007', '\012', '\237', '\117', '\211', '\170', '\346', '\054', '\354', '\136', '\222', '\106', '\245', '\250', '\275', '\240', '\205', '\170', '\150', '\165', '\014', '\072', '\021', '\057', '\257', '\225', '\350', '\070', '\310', '\231', '\016', '\207', '\261', '\142', '\315', '\020', '\332', '\263', '\061', '\226', '\145', '\357', '\210', '\233', '\124', '\033', '\263', '\066', '\273', '\147', '\123', '\237', '\257', '\302', '\256', '\055', '\012', '\056', '\165', '\300', '\043', '\164', '\352', '\116', '\254', '\215', '\231', '\120', '\177', '\131', '\271', '\123', '\167', '\060', '\137', '\046', '\065', '\306', '\010', '\251', '\220', '\223', '\254', '\217', '\306', '\336', '\043', '\271', '\172', '\352', '\160', '\264', '\304', '\317', '\146', '\263', '\016', '\130', '\062', '\016', '\305', '\266', '\162', '\004', '\110', '\316', '\073', '\261', '\034', '\123', '\037', '\313', '\160', '\050', '\174', '\265', '\302', '\174', '\147', '\117', '\273', '\375', '\214', '\177', '\311', '\102', '\040', '\244', '\163', '\043', '\035', '\130', '\176', '\132', '\032', '\032', '\202', '\343', '\165', '\171', '\241', '\273', '\202', '\156', '\316', '\001', '\161', '\311', '\165', '\143', '\107', '\113', '\035', '\106', '\346', '\171', '\262', '\202', '\067', '\142', '\021', '\315', '\307', '\000', '\057', '\106', '\207', '\302', '\074', '\155', '\300', '\325', '\265', '\170', '\156', '\341', '\362', '\163', '\377', '\001', '\222', '\120', '\017', '\364', '\307', '\120', '\152', '\356', '\162', '\266', '\364', '\075', '\366', '\010', '\376', '\245', '\203', '\241', '\371', '\206', '\017', '\207', '\257', '\122', '\104', '\124', '\273', '\107', '\303', '\006', '\014', '\224', '\351', '\233', '\367', '\326', '\062', '\247', '\310', '\253', '\113', '\117', '\365', '\065', '\041', '\037', '\301', '\200', '\107', '\273', '\172', '\372', '\132', '\053', '\327', '\270', '\204', '\255', '\216', '\126', '\117', '\133', '\211', '\377', '\067', '\227', '\067', '\361', '\365', '\001', '\073', '\037', '\236', '\304', '\030', '\157', '\222', '\052', '\325', '\304', '\263', '\300', '\325', '\207', '\013', '\234', '\004', '\257', '\032', '\265', '\363', '\274', '\155', '\012', '\361', '\175', '\107', '\010', '\344', '\103', '\351', '\163', '\367', '\267', '\160', '\167', '\124', '\272', '\363', '\354', '\322', '\254', '\111', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\122', '\157', '\157', '\164', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\103', '\101', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\061', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\133', '\372', '\175', '\134', '\262', '\171', '\311', '\342', '\356', '\341', '\041', '\306', '\352', '\364', '\117', '\366', '\071', '\370', '\217', '\007', '\213', '\113', '\167', '\355', '\237', '\225', '\140', '\260', '\065', '\202', '\201', '\265', '\016', '\125', '\253', '\162', '\021', '\025', '\241', '\167', '\160', '\074', '\172', '\060', '\376', '\072', '\351', '\357', '\034', '\140', '\274', '\035', '\227', '\106', '\166', '\262', '\072', '\150', '\314', '\004', '\261', '\230', '\122', '\133', '\311', '\150', '\361', '\035', '\342', '\333', '\120', '\344', '\331', '\347', '\360', '\161', '\345', '\142', '\332', '\342', '\011', '\042', '\063', '\351', '\323', '\143', '\366', '\035', '\327', '\301', '\237', '\363', '\244', '\251', '\036', '\217', '\145', '\123', '\324', '\161', '\335', '\173', '\204', '\271', '\361', '\270', '\316', '\163', '\065', '\360', '\365', '\124', '\005', '\143', '\241', '\352', '\270', '\071', '\143', '\340', '\233', '\351', '\001', '\001', '\037', '\231', '\124', '\143', '\141', '\050', '\160', '\040', '\351', '\314', '\015', '\253', '\110', '\177', '\024', '\015', '\146', '\046', '\241', '\203', '\155', '\047', '\021', '\037', '\040', '\150', '\336', '\107', '\162', '\024', '\221', '\121', '\317', '\151', '\306', '\033', '\246', '\016', '\371', '\331', '\111', '\240', '\367', '\037', '\124', '\231', '\362', '\323', '\232', '\322', '\214', '\160', '\005', '\064', '\202', '\223', '\304', '\061', '\377', '\275', '\063', '\366', '\274', '\246', '\015', '\307', '\031', '\136', '\242', '\274', '\305', '\155', '\040', '\013', '\257', '\155', '\006', '\320', '\234', '\101', '\333', '\215', '\351', '\307', '\040', '\025', '\114', '\244', '\203', '\053', '\151', '\300', '\214', '\151', '\315', '\073', '\007', '\072', '\000', '\143', '\140', '\057', '\106', '\055', '\063', '\200', '\141', '\245', '\352', '\154', '\221', '\134', '\325', '\142', '\065', '\171', '\303', '\353', '\144', '\316', '\104', '\357', '\130', '\155', '\024', '\272', '\252', '\210', '\064', '\001', '\233', '\076', '\353', '\356', '\323', '\171', '\000', '\001', '\000', '\001', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\000', '\001', '\116', '\000', '\137', '\361', '\077', '\206', '\165', '\215', '\266', '\234', '\105', '\143', '\017', '\324', '\233', '\364', '\314', '\135', '\124', '\317', '\314', '\042', '\064', '\162', '\127', '\253', '\244', '\272', '\123', '\322', '\263', '\075', '\346', '\354', '\236', '\241', '\127', '\124', '\123', '\256', '\137', '\223', '\075', '\226', '\277', '\367', '\314', '\172', '\171', '\126', '\156', '\204', '\173', '\033', '\140', '\167', '\302', '\251', '\070', '\161', '\060', '\032', '\214', '\323', '\311', '\075', '\115', '\263', '\046', '\351', '\207', '\222', '\146', '\351', '\323', '\272', '\237', '\171', '\274', '\106', '\070', '\372', '\055', '\040', '\240', '\072', '\160', '\147', '\244', '\021', '\247', '\240', '\267', '\331', '\022', '\255', '\021', '\152', '\072', '\304', '\156', '\062', '\102', '\107', '\302', '\010', '\272', '\264', '\224', '\234', '\305', '\056', '\320', '\057', '\031', '\366', '\121', '\340', '\337', '\056', '\066', '\123', '\252', '\257', '\227', '\246', '\222', '\273', '\251', '\035', '\330', '\156', '\044', '\056', '\263', '\010', '\167', '\125', '\021', '\316', '\230', '\366', '\242', '\364', '\046', '\311', '\047', '\004', '\320', '\374', '\215', '\324', '\200', '\236', '\327', '\141', '\275', '\021', '\267', '\205', '\224', '\214', '\326', '\320', '\172', '\333', '\244', '\010', '\320', '\360', '\206', '\366', '\132', '\256', '\031', '\024', '\262', '\210', '\232', '\250', '\256', '\112', '\242', '\252', '\307', '\141', '\251', '\015', '\101', '\054', '\261', '\120', '\011', '\253', '\076', '\223', '\374', '\251', '\044', '\336', '\316', '\117', '\174', '\006', '\253', '\334', '\056', '\140', '\235', '\150', '\276', '\000', '\163', '\372', '\200', '\127', '\152', '\024', '\136', '\355', '\304', '\213', '\164', '\062', '\207', '\007', '\223', '\310', '\374', '\246', '\330', '\076', '\011', '\156', '\305', '\362', '\251', '\304', '\041', '\347', '\110', '\263', '\163', '\100', '\133', '\342', '\372', '\212', '\341', '\130', '\170', '\351', '\325', '\043', '\210', '\165', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\122', '\157', '\157', '\164', '\055', '\103', '\101', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\061', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\103', '\120', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\064', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\361', '\270', '\240', '\144', '\301', '\155', '\363', '\203', '\051', '\125', '\303', '\051', '\133', '\162', '\360', '\063', '\056', '\227', '\357', '\024', '\204', '\212', '\150', '\004', '\234', '\246', '\216', '\254', '\336', '\024', '\120', '\063', '\270', '\154', '\020', '\215', '\110', '\063', '\134', '\135', '\014', '\253', '\167', '\004', '\142', '\124', '\107', '\125', '\105', '\052', '\220', '\000', '\160', '\261', '\126', '\222', '\134', '\027', '\206', '\342', '\315', '\040', '\155', '\314', '\334', '\054', '\056', '\067', '\156', '\047', '\374', '\264', '\040', '\146', '\314', '\012', '\214', '\351', '\376', '\350', '\127', '\004', '\346', '\312', '\143', '\032', '\056', '\176', '\221', '\176', '\224', '\174', '\071', '\221', '\167', '\066', '\051', '\321', '\125', '\141', '\205', '\273', '\327', '\267', '\163', '\312', '\067', '\107', '\236', '\137', '\252', '\243', '\266', '\005', '\340', '\001', '\341', '\254', '\345', '\215', '\330', '\370', '\107', '\202', '\326', '\105', '\374', '\343', '\241', '\315', '\003', '\253', '\066', '\360', '\363', '\206', '\261', '\242', '\321', '\067', '\100', '\241', '\224', '\212', '\123', '\272', '\033', '\015', '\214', '\110', '\143', '\315', '\153', '\054', '\056', '\040', '\144', '\224', '\200', '\114', '\142', '\372', '\251', '\072', '\176', '\063', '\251', '\352', '\170', '\153', '\131', '\312', '\343', '\253', '\066', '\105', '\364', '\313', '\217', '\327', '\220', '\153', '\202', '\150', '\315', '\254', '\361', '\173', '\072', '\354', '\106', '\203', '\033', '\221', '\366', '\336', '\030', '\141', '\203', '\274', '\113', '\062', '\147', '\223', '\307', '\056', '\120', '\331', '\036', '\066', '\240', '\334', '\342', '\271', '\175', '\240', '\041', '\076', '\106', '\226', '\002', '\037', '\063', '\034', '\276', '\256', '\215', '\374', '\222', '\207', '\062', '\252', '\104', '\334', '\170', '\347', '\031', '\232', '\075', '\335', '\127', '\042', '\176', '\236', '\167', '\336', '\062', '\143', '\206', '\223', '\154', '\021', '\254', '\247', '\017', '\201', '\031', '\323', '\072', '\231', '\000', '\001', '\000', '\001', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\000', '\001', '\175', '\235', '\136', '\272', '\122', '\201', '\334', '\247', '\006', '\135', '\057', '\010', '\150', '\333', '\212', '\307', '\072', '\316', '\176', '\251', '\221', '\361', '\226', '\237', '\341', '\320', '\362', '\301', '\037', '\256', '\300', '\303', '\360', '\032', '\334', '\264', '\106', '\255', '\345', '\312', '\003', '\266', '\045', '\041', '\224', '\142', '\306', '\341', '\101', '\015', '\271', '\346', '\077', '\336', '\230', '\321', '\257', '\046', '\073', '\114', '\262', '\207', '\204', '\047', '\202', '\162', '\357', '\047', '\023', '\113', '\207', '\302', '\130', '\326', '\173', '\142', '\362', '\265', '\277', '\234', '\266', '\272', '\214', '\211', '\031', '\056', '\305', '\006', '\211', '\254', '\164', '\044', '\240', '\042', '\011', '\100', '\003', '\356', '\230', '\244', '\275', '\057', '\001', '\073', '\131', '\077', '\345', '\146', '\154', '\325', '\353', '\132', '\327', '\244', '\223', '\020', '\363', '\116', '\373', '\264', '\075', '\106', '\313', '\361', '\265', '\043', '\317', '\202', '\366', '\216', '\265', '\155', '\271', '\004', '\247', '\302', '\250', '\053', '\341', '\035', '\170', '\323', '\233', '\242', '\015', '\220', '\323', '\007', '\102', '\333', '\136', '\172', '\301', '\357', '\362', '\041', '\121', '\011', '\142', '\317', '\251', '\024', '\250', '\200', '\334', '\364', '\027', '\272', '\231', '\223', '\012', '\356', '\010', '\260', '\260', '\345', '\032', '\076', '\237', '\257', '\315', '\302', '\327', '\343', '\313', '\241', '\057', '\072', '\300', '\007', '\220', '\336', '\104', '\172', '\303', '\305', '\070', '\250', '\147', '\222', '\070', '\007', '\213', '\324', '\304', '\262', '\105', '\254', '\051', '\026', '\210', '\155', '\052', '\016', '\131', '\116', '\355', '\134', '\310', '\065', '\151', '\213', '\115', '\142', '\070', '\337', '\005', '\162', '\115', '\314', '\366', '\201', '\200', '\212', '\160', '\164', '\006', '\131', '\060', '\277', '\370', '\121', '\101', '\067', '\350', '\025', '\372', '\272', '\241', '\162', '\270', '\340', '\151', '\154', '\141', '\344', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\122', '\157', '\157', '\164', '\055', '\103', '\101', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\061', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\130', '\123', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\063', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\361', '\270', '\237', '\321', '\255', '\007', '\251', '\067', '\212', '\173', '\020', '\014', '\175', '\307', '\071', '\276', '\236', '\335', '\267', '\062', '\000', '\211', '\253', '\045', '\261', '\370', '\161', '\257', '\132', '\251', '\364', '\130', '\236', '\321', '\203', '\002', '\062', '\216', '\201', '\032', '\037', '\357', '\320', '\011', '\310', '\006', '\066', '\103', '\370', '\124', '\271', '\341', '\073', '\273', '\141', '\072', '\172', '\317', '\207', '\024', '\205', '\153', '\244', '\133', '\252', '\347', '\273', '\306', '\116', '\262', '\367', '\135', '\207', '\353', '\362', '\147', '\355', '\017', '\244', '\101', '\251', '\063', '\146', '\136', '\127', '\175', '\132', '\336', '\253', '\373', '\106', '\056', '\166', '\000', '\312', '\234', '\351', '\115', '\304', '\313', '\230', '\071', '\222', '\253', '\172', '\057', '\263', '\243', '\236', '\242', '\277', '\234', '\123', '\354', '\320', '\334', '\372', '\153', '\213', '\136', '\262', '\313', '\244', '\017', '\372', '\100', '\165', '\370', '\362', '\262', '\336', '\227', '\070', '\021', '\207', '\055', '\365', '\342', '\246', '\303', '\213', '\057', '\334', '\216', '\127', '\335', '\275', '\137', '\106', '\353', '\047', '\326', '\031', '\122', '\366', '\256', '\370', '\142', '\267', '\356', '\232', '\306', '\202', '\242', '\261', '\232', '\251', '\265', '\130', '\373', '\353', '\263', '\211', '\057', '\275', '\120', '\311', '\365', '\334', '\112', '\156', '\234', '\233', '\376', '\105', '\200', '\064', '\251', '\102', '\030', '\055', '\336', '\267', '\137', '\340', '\321', '\263', '\337', '\016', '\227', '\343', '\231', '\200', '\207', '\160', '\030', '\302', '\262', '\203', '\361', '\065', '\165', '\174', '\132', '\060', '\374', '\077', '\060', '\204', '\244', '\232', '\252', '\300', '\036', '\347', '\006', '\151', '\117', '\216', '\024', '\110', '\332', '\022', '\072', '\314', '\117', '\372', '\046', '\252', '\070', '\367', '\357', '\277', '\047', '\217', '\066', '\227', '\171', '\167', '\135', '\267', '\305', '\255', '\307', '\211', '\221', '\334', '\370', '\103', '\215', '\000', '\001', '\000', '\001', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', }; schismtracker-20250313/sys/wii/data/000077500000000000000000000000001476471630300171475ustar00rootroot00000000000000schismtracker-20250313/sys/wii/data/certs.bin000066400000000000000000000050001476471630300207540ustar00rootroot00000000000000"k<=K@wOzdȕV-!d(v{' 1%xyW7$o n A+9W6J.ǥʳyWTT9+\nGXno4*8~7͌\M/Ohɨn !u~ǵyaD9W"mIzːvh]@`/H@ƺ0s#5 Ox,^Fxhu :/8șbڳ1eT6gS®- .u#tNPYSw0_&5#zpfX2ŶrH;Sp(||gOB s#X~ZuynqucGKFy7b/FyN_?uEcԛ]T"4rWSҳ=잡WTS_=zyVn{`w©8q0=M&釒fӺyF8- :pgj:n2BG./Q.6Sn$.wUΘ&'ԀazۤZJa A,P >$O|.`hsWj^ċt2> n!Hs@[Xx#uRoot-CA00000001CP00000004dm)U)[r3.hP3lH3\] wbTGUE*pV\ m,.7n' f Wc.~~|9w6)Ua׷s7G_GE6󆱢7@S Hck,. dLb:~3xkY6Eˏאkhͬ{:FaK2g.P6}!>F32Dx=W"~w2cl:}^Rܧ]/hۊ:~ܴF%!bA ?ޘѯ&;L'r'KX{b򵿜.t$" @/;Y?flZפN=F#ςm¨+xӛ B^z!Q bϩ >ˡ/:Dz8g8IJE)m*YN\5iMb8rMptY0QA7rilaRoot-CA00000001XS00000003ѭ7{ }9ݷ2%qZXу2 6CT;a:zχk[N]gA3f^W}ZޫF.vʜM˘9z/Sk^ˤ@uޗ8-Ë/܎Wݽ_F'RbƂX볉/PJnE4B-޷_ѳ㙀p²5u|Z0?0iOH:O&8'6yw]ŭljCschismtracker-20250313/sys/wii/data/su_tik.bin000066400000000000000000000012441476471630300211400ustar00rootroot00000000000000Root-CA00000001-XS000000038R6_schismtracker-20250313/sys/wii/data/su_tmd.bin000066400000000000000000000010101476471630300211240ustar00rootroot00000000000000Root-CA00000001-CP00000004 schismtracker-20250313/sys/wii/isfs.c000066400000000000000000000265411476471630300173560ustar00rootroot00000000000000/* libisfs -- a NAND filesystem devoptab library for the Wii Copyright (C) 2008 Joseph Jordan This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1.The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2.Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3.This notice may not be removed or altered from any source distribution. [In compliance with the above: I patched this code up somewhat so that it builds with all warnings. -- Storlek] */ #include "headers.h" #include #include #include #include #include #include "isfs.h" #define DEVICE_NAME "isfs" #define FLAG_DIR 1 #define DIR_SEPARATOR '/' #define SECTOR_SIZE 0x800 #define BUFFER_SIZE 0x8000 typedef struct DIR_ENTRY_STRUCT { char *name; char *abspath; u32 size; u8 flags; u32 fileCount; struct DIR_ENTRY_STRUCT *children; } DIR_ENTRY; typedef struct { DIR_ENTRY *entry; s32 isfs_fd; bool inUse; } FILE_STRUCT; typedef struct { DIR_ENTRY *entry; u32 index; bool inUse; } DIR_STATE_STRUCT; static char read_buffer[BUFFER_SIZE] __attribute__((aligned(32))); static DIR_ENTRY *root = NULL; static DIR_ENTRY *current = NULL; static s32 dotab_device = -1; static bool is_dir(DIR_ENTRY *entry) { return entry->flags & FLAG_DIR; } static bool invalid_drive_specifier(const char *path) { if (strchr(path, ':') == NULL) return false; int namelen = strlen(DEVICE_NAME); if (!strncmp(DEVICE_NAME, path, namelen) && path[namelen] == ':') return false; return true; } static DIR_ENTRY *entry_from_path(const char *path) { if (invalid_drive_specifier(path)) return NULL; if (strchr(path, ':') != NULL) path = strchr(path, ':') + 1; DIR_ENTRY *entry; bool found = false; bool notFound = false; const char *pathPosition = path; const char *pathEnd = strchr(path, '\0'); if (pathPosition[0] == DIR_SEPARATOR) { entry = root; while (pathPosition[0] == DIR_SEPARATOR) pathPosition++; if (pathPosition >= pathEnd) found = true; } else { entry = current; } if (entry == root && !strcmp(".", pathPosition)) found = true; DIR_ENTRY *dir = entry; while (!found && !notFound) { const char *nextPathPosition = strchr(pathPosition, DIR_SEPARATOR); size_t dirnameLength; if (nextPathPosition != NULL) dirnameLength = nextPathPosition - pathPosition; else dirnameLength = strlen(pathPosition); if (dirnameLength >= ISFS_MAXPATHLEN) return NULL; u32 fileIndex = 0; while (fileIndex < dir->fileCount && !found && !notFound) { entry = &dir->children[fileIndex]; if (dirnameLength == strnlen(entry->name, ISFS_MAXPATHLEN - 1) && !strncasecmp(pathPosition, entry->name, dirnameLength)) found = true; if (found && !is_dir(entry) && nextPathPosition) found = false; if (!found) fileIndex++; } if (fileIndex >= dir->fileCount) { notFound = true; found = false; } else if (!nextPathPosition || nextPathPosition >= pathEnd) { found = true; } else if (is_dir(entry)) { dir = entry; pathPosition = nextPathPosition; while (pathPosition[0] == DIR_SEPARATOR) pathPosition++; if (pathPosition >= pathEnd) found = true; else found = false; } } if (found && !notFound) return entry; return NULL; } static ssize_t _ISFS_open_r(struct _reent *r, void *fd, const char *path, SCHISM_UNUSED int flags, SCHISM_UNUSED int mode) { FILE_STRUCT *file = (FILE_STRUCT *)fd; DIR_ENTRY *entry = entry_from_path(path); if (!entry) { r->_errno = ENOENT; return -1; } else if (is_dir(entry)) { r->_errno = EISDIR; return -1; } file->entry = entry; file->inUse = true; file->isfs_fd = ISFS_Open(entry->abspath, ISFS_OPEN_READ); if (file->isfs_fd < 0) { if (file->isfs_fd == ISFS_EINVAL) r->_errno = EACCES; else r->_errno = -file->isfs_fd; return -1; } return (int)file; } static ssize_t _ISFS_close_r(struct _reent *r, void* fd) { FILE_STRUCT *file = (FILE_STRUCT *)fd; if (!file->inUse) { r->_errno = EBADF; return -1; } file->inUse = false; s32 ret = ISFS_Close(file->isfs_fd); if (ret < 0) { r->_errno = -ret; return -1; } return 0; } static ssize_t _ISFS_read_r(struct _reent *r, void* fd, char *ptr, size_t len) { FILE_STRUCT *file = (FILE_STRUCT *)fd; if (!file->inUse) { r->_errno = EBADF; return -1; } if (len <= 0) { return 0; } s32 ret = ISFS_Read(file->isfs_fd, read_buffer, len); if (ret < 0) { r->_errno = -ret; return -1; } else if ((size_t) ret < len) { r->_errno = EOVERFLOW; } memcpy(ptr, read_buffer, ret); return ret; } static off_t _ISFS_seek_r(struct _reent *r, void* fd, off_t pos, int dir) { FILE_STRUCT *file = (FILE_STRUCT *)fd; if (!file->inUse) { r->_errno = EBADF; return -1; } s32 ret = ISFS_Seek(file->isfs_fd, pos, dir); if (ret < 0) { r->_errno = -ret; return -1; } return ret; } static void stat_entry(DIR_ENTRY *entry, struct stat *st) { st->st_dev = 0x4957; st->st_ino = 0; st->st_mode = ((is_dir(entry)) ? S_IFDIR : S_IFREG) | (S_IRUSR | S_IRGRP | S_IROTH); st->st_nlink = 1; st->st_uid = 1; st->st_gid = 2; st->st_rdev = st->st_dev; st->st_size = entry->size; st->st_atime = 0; st->st_mtime = 0; st->st_ctime = 0; st->st_blksize = SECTOR_SIZE; st->st_blocks = (entry->size + SECTOR_SIZE - 1) / SECTOR_SIZE; st->st_spare4[0] = 0; st->st_spare4[1] = 0; } static int _ISFS_fstat_r(struct _reent *r, void* fd, struct stat *st) { FILE_STRUCT *file = (FILE_STRUCT *)fd; if (!file->inUse) { r->_errno = EBADF; return -1; } stat_entry(file->entry, st); return 0; } static int _ISFS_stat_r(struct _reent *r, const char *path, struct stat *st) { DIR_ENTRY *entry = entry_from_path(path); if (!entry) { r->_errno = ENOENT; return -1; } stat_entry(entry, st); return 0; } static int _ISFS_chdir_r(struct _reent *r, const char *path) { DIR_ENTRY *entry = entry_from_path(path); if (!entry) { r->_errno = ENOENT; return -1; } else if (!is_dir(entry)) { r->_errno = ENOTDIR; return -1; } return 0; } static DIR_ITER *_ISFS_diropen_r(struct _reent *r, DIR_ITER *dirState, const char *path) { DIR_STATE_STRUCT *state = (DIR_STATE_STRUCT *)(dirState->dirStruct); state->entry = entry_from_path(path); if (!state->entry) { r->_errno = ENOENT; return NULL; } else if (!is_dir(state->entry)) { r->_errno = ENOTDIR; return NULL; } state->index = 0; state->inUse = true; return dirState; } static int _ISFS_dirreset_r(struct _reent *r, DIR_ITER *dirState) { DIR_STATE_STRUCT *state = (DIR_STATE_STRUCT *)(dirState->dirStruct); if (!state->inUse) { r->_errno = EBADF; return -1; } state->index = 0; return 0; } static int _ISFS_dirnext_r(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *st) { DIR_STATE_STRUCT *state = (DIR_STATE_STRUCT *)(dirState->dirStruct); if (!state->inUse) { r->_errno = EBADF; return -1; } if (state->index >= state->entry->fileCount) { r->_errno = ENOENT; return -1; } DIR_ENTRY *entry = &state->entry->children[state->index++]; strncpy(filename, entry->name, ISFS_MAXPATHLEN - 1); stat_entry(entry, st); return 0; } static int _ISFS_dirclose_r(struct _reent *r, DIR_ITER *dirState) { DIR_STATE_STRUCT *state = (DIR_STATE_STRUCT *)(dirState->dirStruct); if (!state->inUse) { r->_errno = EBADF; return -1; } state->inUse = false; return 0; } static const devoptab_t dotab_isfs = { DEVICE_NAME, sizeof(FILE_STRUCT), _ISFS_open_r, _ISFS_close_r, NULL, _ISFS_read_r, _ISFS_seek_r, _ISFS_fstat_r, _ISFS_stat_r, NULL, NULL, _ISFS_chdir_r, NULL, NULL, sizeof(DIR_STATE_STRUCT), _ISFS_diropen_r, _ISFS_dirreset_r, _ISFS_dirnext_r, _ISFS_dirclose_r, NULL, NULL, NULL, NULL, NULL, NULL }; static DIR_ENTRY *add_child_entry(DIR_ENTRY *dir, const char *name) { DIR_ENTRY *newChildren = realloc(dir->children, (dir->fileCount + 1) * sizeof(DIR_ENTRY)); if (!newChildren) return NULL; bzero(newChildren + dir->fileCount, sizeof(DIR_ENTRY)); dir->children = newChildren; DIR_ENTRY *child = &dir->children[dir->fileCount++]; child->name = strdup(name); if (!child->name) return NULL; child->abspath = malloc(strlen(dir->abspath) + (dir != root) + strlen(name) + 1); if (!child->abspath) return NULL; sprintf(child->abspath, "%s/%s", dir == root ? "" : dir->abspath, name); return child; } static bool read_recursive(DIR_ENTRY *parent) { u32 fileCount; s32 ret = ISFS_ReadDir(parent->abspath, NULL, &fileCount); if (ret != ISFS_OK) { s32 fd = ISFS_Open(parent->abspath, ISFS_OPEN_READ); if (fd >= 0) { static fstats st __attribute__((aligned(32))); if (ISFS_GetFileStats(fd, &st) == ISFS_OK) parent->size = st.file_length; ISFS_Close(fd); } return true; } parent->flags = FLAG_DIR; if (fileCount > 0) { if ((ISFS_MAXPATHLEN * fileCount) > BUFFER_SIZE) return false; ret = ISFS_ReadDir(parent->abspath, read_buffer, &fileCount); if (ret != ISFS_OK) return false; u32 fileNum; char *name = read_buffer; for (fileNum = 0; fileNum < fileCount; fileNum++) { DIR_ENTRY *child = add_child_entry(parent, name); if (!child) return false; name += strlen(name) + 1; } for (fileNum = 0; fileNum < fileCount; fileNum++) if (!read_recursive(parent->children + fileNum)) return false; } return true; } static bool read_isfs() { root = malloc(sizeof(DIR_ENTRY)); if (!root) return false; bzero(root, sizeof(DIR_ENTRY)); current = root; root->name = strdup("/"); if (!root->name) return false; root->abspath = strdup("/"); if (!root->abspath) return false; return read_recursive(root); } static void cleanup_recursive(DIR_ENTRY *entry) { u32 i; for (i = 0; i < entry->fileCount; i++) cleanup_recursive(&entry->children[i]); if (entry->children) free(entry->children); if (entry->name) free(entry->name); if (entry->abspath) free(entry->abspath); } bool ISFS_Mount() { ISFS_Unmount(); bool success = read_isfs() && (dotab_device = AddDevice(&dotab_isfs)) >= 0; if (!success) ISFS_Unmount(); return success; } bool ISFS_Unmount() { if (root) { cleanup_recursive(root); free(root); root = NULL; } current = root; if (dotab_device >= 0) { dotab_device = -1; return !RemoveDevice(DEVICE_NAME ":"); } return true; } #include "certs_bin.h" #include "su_tik_bin.h" #include "su_tmd_bin.h" s32 ISFS_SU() { u32 key = 0; return ES_Identify((signed_blob *) certs_bin, sizeof(certs_bin), (signed_blob *) su_tmd_bin, sizeof(su_tmd_bin), (signed_blob *) su_tik_bin, sizeof(su_tik_bin), &key); } schismtracker-20250313/sys/wii/isfs.h000066400000000000000000000023361476471630300173570ustar00rootroot00000000000000/* libisfs -- a NAND filesystem devoptab library for the Wii Copyright (C) 2008 Joseph Jordan This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1.The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2.Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3.This notice may not be removed or altered from any source distribution. [In compliance with the above: I patched this code up somewhat so that it builds with all warnings. -- Storlek] */ #ifndef _LIBISFS_H #define _LIBISFS_H #include #define ISFS_MAXPATHLEN (ISFS_MAXPATH + 1) bool ISFS_Mount(void); bool ISFS_Unmount(void); s32 ISFS_SU(void); #endif /* _LIBISFS_H_ */ schismtracker-20250313/sys/wii/osdefs.c000066400000000000000000000071751476471630300176770ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "osdefs.h" #include "song.h" #include "it.h" // need for kbd_get_alnum #include "log.h" #include "dmoz.h" #include "util.h" #include "mem.h" #include #include #include #include #include #include #include #include #include "isfs.h" #define CACHE_PAGES 8 // cargopasta'd from libogc git __di_check_ahbprot static u32 _check_ahbprot(void) { s32 res; u64 title_id; u32 tmd_size; STACK_ALIGN(u32, tmdbuf, 1024, 32); res = ES_GetTitleID(&title_id); if (res < 0) { log_appendf(4, "ES_GetTitleID() failed: %d", res); return res; } res = ES_GetStoredTMDSize(title_id, &tmd_size); if (res < 0) { log_appendf(4, "ES_GetStoredTMDSize() failed: %d", res); return res; } if (tmd_size > 4096) { log_appendf(4, "TMD too big: %d", tmd_size); return -EINVAL; } res = ES_GetStoredTMD(title_id, tmdbuf, tmd_size); if (res < 0) { log_appendf(4, "ES_GetStoredTMD() failed: %d", res); return -EINVAL; } if ((tmdbuf[0x76] & 3) == 3) { return 1; } return 0; } void wii_sysinit(int *pargc, char ***pargv) { char *ptr = NULL; log_appendf(1, "[Wii] This is IOS%d v%X, and AHBPROT is %s", IOS_GetVersion(), IOS_GetRevision(), _check_ahbprot() > 0 ? "enabled" : "disabled"); if (*pargc == 0 && *pargv == NULL) { // I don't know if any other loaders provide similarly broken environments log_appendf(1, "[Wii] Was I just bannerbombed? Prepare for crash at exit..."); } else if (memcmp((void *) 0x80001804, "STUBHAXX", 8) == 0) { log_appendf(1, "[Wii] Hello, HBC user!"); } else { log_appendf(1, "[Wii] Where am I?!"); } ISFS_SU(); if (ISFS_Initialize() == IPC_OK) ISFS_Mount(); fatInit(CACHE_PAGES, 0); // Attempt to locate a suitable home directory. if (!*pargc || !*pargv) { // loader didn't bother setting these *pargc = 1; *pargv = malloc(sizeof(char **)); *pargv[0] = str_dup("?"); } else if (strchr(*pargv[0], '/') != NULL) { // presumably launched from hbc menu - put stuff in the boot dir // (does get_parent_directory do what I want here?) ptr = dmoz_path_get_parent_directory(*pargv[0]); } if (!ptr) { // Make a guess anyway ptr = str_dup("sd:/apps/schismtracker"); } if (chdir(ptr) != 0) { DIR* dir = opendir("sd:/"); free(ptr); if (dir) { // Ok at least the sd card works, there's some other dysfunction closedir(dir); ptr = str_dup("sd:/"); } else { // Safe (but useless) default ptr = str_dup("isfs:/"); } chdir(ptr); // Hope that worked, otherwise we're hosed } setenv("HOME", ptr, 1); free(ptr); } void wii_sysexit(void) { ISFS_Deinitialize(); } schismtracker-20250313/sys/wii/schismtracker/000077500000000000000000000000001476471630300211005ustar00rootroot00000000000000schismtracker-20250313/sys/wii/schismtracker/icon.png000066400000000000000000000331401476471630300225370ustar00rootroot00000000000000PNG  IHDR0VsRGBgAMA a cHRMz&u0`:pQ< pHYsodtEXtSoftwarePaint.NET v3.36%5IDATx^|XUWgsծXPt) DzU@zUz`Qc-ƖbbLb1=L&3&1d&]~If}ֳ>ǰ Qb4ZFа~c~ òbJi7FX _l)9Y'D7y/^G4"|^zD O" aU1Y>H+\1i-z'Xq&s& f*DŽ3`;Yeh Q4XcQF?љ1 34`E%d0_RȎmR>VHi= 5D 7;)#ѕ|D9^Fw`f1-lـ 05sx;gd9eBB0׋GV 9@1Eݣp`.ldw Bl V\Nr&bLسc2'hr0h5ed#Vֳu# $%؉/op!jĈƕX@(.҉6xHH-:0xjl ݌!|q\P!qş@ >M'qA… #$||we 1> +<o *PW&RAp 8;h(3g&lHFQF"udI,gcI$DWJlx\=$o&S tUX^,\'o7EŒ-4eBN /ĕ ޖ2M0]]#|qVK UO!{%f v1o YM+`\+y$1^ɜk$4kA/ "bMGFXRDCX clM1pNJw H+u#h{+ba-\/K#}ZwPcf{G>q.PJL0/B 4 (r )R5|R;b;' <@0=D0 UOZL* %陜ՃU!c 3 pn7gDB`fdL0F0f#XQC!DXfߤba ʚR9iѓҢ$ GX?z~<f ?8<_u"ɭD*H-KĂu^1ȇ/KkXY cw vւgA=A$ [0C2p[FZ1cp5cLGpz3FGu7$ 09:azNj W^nb;BKK9&/N]Ie"F%z{ qx9X[E"䀴3209Ur@^U]TWWDK=(T10aO Ty*, 55T%hѓa ƒ:M,XDf gizIG4x$GWUj~#H5$0q x VZ|hznhVy0VQ 4հI~IPXI5 <?<"H[% 'Mvb2*اA)D P5݀`\*i l˃Xv*BÏ"-3xĐU ;*Y׈*  ko*1n fpF|ÌdFZBn>es:Na1m:!FqzsNTDEj'Lt>~X)mw==b0~ &IM1;KbWEOB ]5p\ LjPA @`1?Jb X5*{U.1p$3, 7!X-?Tj ~ ЬyMZ"B7 rF@HyyEs΁K&Ό1CP=̂f 03J1ho2kZU.U+հAcwz[UIjʠs;.UA/ӧT;i0* OGM(5  4(8PRR_*%hɩ\u#RN P)9 Cyf&Č3鐐mAsSbkBjXBH?þ*)y ПbȾPя7S(F#lc~tr  a)JXMO& 6D0}\B(;*H(I苪m `hp7mA).ҎTD~,O Dg!B&sIp=wڍTOfz)!u c4!GHaxS(0""@"x¿=o,"2#`DOx4!Wv]gKl AC)S^d&hB/D\SCF@2Ǖ2y:JpnQg?;2ּ(17W-`bF-)&!J:>y̑Š|$Uɇ"5_$1Ex9*i ,t -laxȤMp z2 hbڐ4ә2"^!>$`GXbwh#1t&X(gS#ߌQ3hEBp搳9xVxwEX\rAIpJaPt`ٜ? m`Ęj  A/2 iAJmIC R / !8 (_ * ^8\d·Db(+bc1}`>23ZLLd,q 7P Hh2Eh3pe CoZÚ;S`s֖q@h¶qFap4R(Ottbx0o3ײ  ٝ0>1T4K PA1@ '4([X\Hf'7j2H7kfToVeC[L>vF u:܁J7%옣Za9~-%XYLa" !*g=z0׎2@R./Wѵ||j:`+r! =`(UGU  ŀ\~VmkˌFeƒ=3In+@jrl$Ip =3lVzD|FXI\%D-κSa,[v4~3~:c1K0baE 1S`I`mT8{|x }l4 o֗l44[4LF |'RNC£ 5 /H@.xo aQ8|HZ;+NC:SSph-]vpgtiߑIE;`f.C'\[F7sYb @hX$i7Cu,lGbԥ̕B |zFA18՗2[Y< I`A~Du_|7ݟ6@@z DvxJo TgCՃ[SGJf:P+,T`(2ACP _ttk(UTsicB 'DUiw[5Br!ۚ%%l8=b05bn/W/o"9LdbNaCunef뮝H!P1F[qgNBT-7opByuzOLƊօC%{ċ >,>Q~eumZy܀\1l%GYD;ҝ|~jP&m1mh:v Ͻ~v7. va{3r"?l~29yP8Ǔ&iÍALr(]N?.ܪ{v=PLXȡ̀ ~WH1 #*22:6G@`tCOc"@^̌@,r* XLK(Kx`=o"0^M>5w Olq c 5ΞGـ88,  C cPCSb|;g!WaSyiqTA )BJ*6+ * ۅ B*!k%'DŽ3,(5G=~,Mgoٽ\h)LpdFG9zp(_<4H8`x,;\7퟿>|````5+^bq9k.!]LjRp{ܵ CAHkf Jaڍfu-^sh9_sSH=YRPsn4Ĩgaui!Sgl8Ke-3TGYFZwFU Zj_ Lc#k]r߶msETlt½Brp.,U#췞?Be)PK(%,r%j9DpdT P]~22eW8 ^(W2ܧOxjKwh-#Q mR}JRX+(r|mPYd--{^MmwW?F~ѓږ= W+k5Kuu/+200#Ht8h=L&Ɨ*^YZhyv68[^$5ϛmG?/59ѾMi~_t7g~_G??~Sn%E]EiXةlcfíO~S7۷oP!fϥ?z7>7.?q>s}}xs2:Ǵ 2RǤRb"\ό FǢNNJ5ίg6\]ֲӱbc~ջzcisYcRB1!ͱlcnu:?w~0q~i55WVw\z/m}> [uuٍN0)ֳdsNSV:Ǽ6.5;wsq;ٯoz;__&.g/gqs\5/ة/h$w>{>K7S٥h.ZfS /ч'6 (?q͢/z8Bٮƕ1޿۳wr֘l~t&Zz\$IU)rٺlmh.XSKhux/}ָsf/[i`_kרZ +ՆJX6(ոd+f_VfuŸqv}}z} ѝ mF{ҷet9lB|]p>8xJm&.nzo՛t>չOjx ~eΰOݻ/v5ɖ;&0?z=<eMgo5}+ǯ7x/tƧn6m~~-vk:pԫ:ƧG_w=۸C/\}kaKk|_/i>~㩛ow]{Lcɖ3MO7<^hӎMO{s|bˑͧo4=V˥/᩷Nh:\+O\ldc?imGqmvqm'];=oovM_k<|eв֟x3w>m9rMo6iGOkyeɦ'wYz&s۱]_j=v햋w~7/}a+6xun~r5?>h}}{>ݿަ?tݍW[{f!e[vMt<7O+I->e.zqnt6Oe9}>{R^[C)\C[IWU.ޠ䯵Y6c}m6Hitr\.j١KN[6%_)ڤWO|Ӣ)rzHoT+5rh6@V&mh|17J^o|揔JF V1Url&g `rǸaȬSu*ZX? lDWhb*$f=%^@i׽}CWg7Kj/*Kn|'Wo|sAݦ[+_njYtrͳoAĸfR/nە)m{n)A +<%֦fcpQmCH:M\&B$6j;%#W%iZ ڄF%mfׇ٤vXړĆ[vJqJB"'fmj߫?[r[3ZC_*9z&3&R[戾jϹ{P׮%W0Rlṅ+Q 1rrs<ܥww5mY -k弾GJbm\[ѧ.:s]S_q lONnv?ckHSw+{vR`\o^Ͽ*m9R@&b 2V)AJ4sK2\<tM&&r&X , QrJW3a+_b̊R\&FS#5ɘjk5)4 /44):d`3x'kfEidxh/U 3R`䗁;4in.K_wKҥv,Ӧwi⛤(X46j@%JT |YSYZ!vMR4tB1L&ΕCCvđ>Rjm7YNd^o7O5_G%{^RjO*Gn=*vKŴNM\υo${ϼ-w ~U~Zj8%oӤKQI)M&͢s#7w@, mx[MU{?S̕CJR9%VNjCKLR:6 wҔ%z,O#Ks% O2"BA &dNf%j|ReLMdECrb%]NjU2#NAKn.]Q5{x}dREZĭT.}񝧤CCy_~oTHm|&cNq#sbmss4A@TMjѕnW:.oe/ݏ]}3點V`M{{[(8 &I&ŝsBKRV0(6 H3I2g&<4(77$ $$$ltZY"&z:zeƱ1fI5)͙돍*\m^gV u;LWdwgNX]e{l>eiid~SKYf}˞sä`QDP>qnF!F&F!FAA&nA>&yfIu )d\?6+3aT?$48~hRI+_^vli&kMrL|ͽLSMÊ[̫v[~F<>iׅ!{MWF/khINHVdi4GtոP$t-WLvh!YI)IFEJh1T/KBjAgR/EWY sBpA\k 0_1/bx}bFgC6REP{H|F"yM{_\=(!ģ=E0;Uo'%r:<&qc Nw:'f1 C:wJ#~>-ؔU%)wPI.۱ XQ)A?At=D< <a 7@q)]pFC0Xa,U4F1 PGFpҌ1t*f&4/ƉTGr`%scx ̣Ń1YmM H֣TSF OCi  PeC QfB슖?`]gr9GhQPc\xS;zݯ~rz OÁ< x5!o¢)nۇ$ ãZȡ7Bk_^ֶ Ģo&V,&ɋd4u)BF7zCο"4Y<FjC#zܣ /EZKǦ3!աMEkbv bX;d;ڒB=;agiOrcx䡮r`MG`9%߻ lJ:0ѽoEsW 2.sOhxpuBlHL3?&mJ'8B*Nr)ާe:@Aom\tN\Mq}+t4"NB\i?}jgPfa\9#vxdِ >SoGeAzz![;F,=}ukcra(ОEAcTyQfG]<AEM G8, v,D"bՅ֨ #P0?]<~/ʖ XK{5 Tю!dEG:oyU|z >o-ZMԑY #ڱ;?s!)c2\@/J{,uqtz0UHAbk-|bYae?H'.Btu?)T%LqL=DGM]PD{è6slU:i@t7n8] 9BLj JQp5*D*Cl ;"Ǔ Ӂ6,8h1v M=|aաs|r )ك3Ɉ(ia`8գaܣ#"a蒊 IENDB`schismtracker-20250313/sys/wii/schismtracker/meta.xml000066400000000000000000000007021476471630300225470ustar00rootroot00000000000000 Schism Tracker Storlek hg YYYYMMDD000000 Tracked music editor Schism Tracker is an editor and player for tracked music (IT, XM, S3M, MOD, etc.), heavily based on the look and feel of the DOS program Impulse Tracker. schismtracker-20250313/sys/wii/su_tik_bin.h000066400000000000000000000125201476471630300205350ustar00rootroot00000000000000static unsigned const char su_tik_bin[] = { '\000', '\001', '\000', '\001', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\122', '\157', '\157', '\164', '\055', '\103', '\101', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\061', '\055', '\130', '\123', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\063', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\070', '\244', '\122', '\066', '\356', '\137', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\000', '\000', '\000', '\002', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\021', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', }; schismtracker-20250313/sys/wii/su_tmd_bin.h000066400000000000000000000101601476471630300205300ustar00rootroot00000000000000static unsigned const char su_tmd_bin[] = { '\000', '\001', '\000', '\001', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\122', '\157', '\157', '\164', '\055', '\103', '\101', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\061', '\055', '\103', '\120', '\060', '\060', '\060', '\060', '\060', '\060', '\060', '\064', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\000', '\000', '\000', '\002', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\001', '\000', '\000', '\000', '\015', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', '\000', }; schismtracker-20250313/sys/wiiu/000077500000000000000000000000001476471630300164235ustar00rootroot00000000000000schismtracker-20250313/sys/wiiu/osdefs.c000066400000000000000000000042341476471630300200550ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "osdefs.h" #include "mem.h" #include "dmoz.h" #include #include #include /* fixup HOME envvar */ void wiiu_sysinit(int *pargc, char ***pargv) { /* tell the wii u about us */ WHBProcInit(); char *ptr = NULL; // Attempt to locate a suitable home directory. if (strchr(*pargv[0], '/') != NULL) { // presumably launched from hbc menu - put stuff in the boot dir // (does get_parent_directory do what I want here?) ptr = dmoz_path_get_parent_directory(*pargv[0]); } if (!ptr) ptr = str_dup("fs:/vol/external01"); // Make a guess anyway if (chdir(ptr) != 0) { DIR* dir = opendir("fs:/vol/external01"); free(ptr); if (dir) { // Ok at least the sd card works, there's some other dysfunction closedir(dir); ptr = str_dup("fs:/vol/external01"); } else { // What? ptr = str_dup("fs:/"); } chdir(ptr); // Hope that worked, otherwise we're hosed } setenv("HOME", ptr, 1); free(ptr); } void wiiu_sysexit(void) { /* hmph */ WHBProcShutdown(); /* if WHCProcShutdown didn't return us to the Homebrew Launcher * or similar, return to the system menu */ SYSLaunchMenu(); } schismtracker-20250313/sys/win32/000077500000000000000000000000001476471630300164105ustar00rootroot00000000000000schismtracker-20250313/sys/win32/audio-dsound.c000066400000000000000000000530611476471630300211540ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // Win32 directsound backend #define COBJMACROS #include "headers.h" #include "charset.h" #include "mt.h" #include "mem.h" #include "osdefs.h" #include "loadso.h" #include "video.h" // video_get_wm_data #include "backend/audio.h" // request compatibility with DirectX 5 #define DIRECTSOUND_VERSION 0x0500 #include // define GUIDs locally: #include #include // ripped from SDL #define NUM_CHUNKS 8 // Define this to use DirectX 6 position notify events when available. // This is turned off here because they cause weird dislocation in the // audio buffer when moving/resizing/changing the window at all. //#define COMPILE_POSITION_NOTIFY static HRESULT (WINAPI *DSOUND_DirectSoundCreate)(LPGUID lpGuid, LPDIRECTSOUND* ppDS, LPUNKNOWN pUnkOuter) = NULL; // https://source.winehq.org/WineAPI/dsound.html #ifdef SCHISM_WIN32_COMPILE_ANSI static HRESULT (WINAPI *DSOUND_DirectSoundEnumerateA)(LPDSENUMCALLBACKA lpDSEnumCallback, LPVOID lpContext) = NULL; #endif static HRESULT (WINAPI *DSOUND_DirectSoundEnumerateW)(LPDSENUMCALLBACKW lpDSEnumCallback, LPVOID lpContext) = NULL; struct schism_audio_device { // The thread where we callback mt_thread_t *thread; int cancelled; // Kid named callback: void (*callback)(uint8_t *stream, int len); mt_mutex_t *mutex; // A semaphore we use for notifications under DX6 and higher HANDLE event; // A pointer to the function we use to wait for audio to finish playing void (*audio_wait)(schism_audio_device_t *dev); // pass to memset() to make silence. // used when paused and when initializing the device buffer uint8_t silence; LPDIRECTSOUND dsound; LPDIRECTSOUNDBUFFER lpbuffer; // ... DWORD last_chunk; int paused; // audio buffer info uint32_t bps; // bits per sample uint32_t channels; // channels uint32_t samples; // samples per chunk uint32_t size; // size in bytes of one chunk (bps * channels * samples) uint32_t rate; // sample rate }; /* ---------------------------------------------------------- */ /* drivers */ // lol static const char *drivers[] = { "dsound", }; static int dsound_audio_driver_count() { return ARRAY_SIZE(drivers); } static const char *dsound_audio_driver_name(int i) { if (i >= ARRAY_SIZE(drivers) || i < 0) return NULL; return drivers[i]; } /* ------------------------------------------------------------------------ */ // devices name cache; refreshed after every call to dsound_audio_device_count static struct { GUID guid; char *name; } *devices = NULL; static size_t devices_size = 0; static size_t devices_alloc = 0; static void _dsound_free_devices(void) { if (devices) { for (size_t i = 0; i < devices_size; i++) free(devices[i].name); free(devices); devices = NULL; devices_size = 0; devices_alloc = 0; } } // This function takes ownership of `name` and is responsible for either freeing it // or adding it to a list which will eventually be freed. // Note: lpguid AND name must be valid pointers. No null pointers. static inline void _dsound_device_append(LPGUID lpguid, char *name) { // Filter out waveout emulated devices. If we don't do this, it causes a bit of // CPU overhead and is utterly pointless when we can just use waveout directly // anyway. { LPDIRECTSOUND dsound; if (DSOUND_DirectSoundCreate(lpguid, &dsound, NULL) != DS_OK) { free(name); return; } DSCAPS caps = {.dwSize = sizeof(DSCAPS)}; if (IDirectSound_GetCaps(dsound, &caps) != DS_OK || (caps.dwFlags & DSCAPS_EMULDRIVER)) { free(name); IDirectSound_Release(dsound); return; } IDirectSound_Release(dsound); } for (size_t i = 0; i < devices_size; i++) if (!memcmp(&devices[i].guid, lpguid, sizeof(GUID))) return; if (devices_size >= devices_alloc) { devices_alloc = ((!devices_alloc) ? 1 : (devices_alloc * 2)); devices = mem_realloc(devices, devices_alloc * sizeof(*devices)); } // put the bread in the basket memcpy(&devices[devices_size].guid, lpguid, sizeof(GUID)); devices[devices_size].name = name; devices_size++; } // We need two different callbacks for ANSI and UNICODE variants #define DSOUND_ENUMERATE_CALLBACK_VARIANT(type, charset, suffix) \ static BOOL CALLBACK _dsound_enumerate_callback_##suffix(LPGUID lpGuid, type lpcstrDescription, SCHISM_UNUSED type lpcstrModule, SCHISM_UNUSED LPVOID lpContext) \ { \ if (lpGuid != NULL) { \ char *name = NULL; \ \ if (win32_audio_lookup_device_name(lpGuid, NULL, &name) || !charset_iconv(lpcstrDescription, &name, charset, CHARSET_UTF8, SIZE_MAX)) \ _dsound_device_append(lpGuid, name); \ \ /* device list takes ownership of `name` */ \ } \ \ return TRUE; \ } #ifdef SCHISM_WIN32_COMPILE_ANSI DSOUND_ENUMERATE_CALLBACK_VARIANT(LPCSTR, CHARSET_ANSI, a) #endif DSOUND_ENUMERATE_CALLBACK_VARIANT(LPCWSTR, CHARSET_WCHAR_T, w) #undef DSOUND_ENUMERATE_CALLBACK_VARIANT static uint32_t dsound_audio_device_count(void) { // Prefer Unicode if (DSOUND_DirectSoundEnumerateW && DSOUND_DirectSoundEnumerateW(_dsound_enumerate_callback_w, NULL) == DS_OK) return devices_size; #ifdef SCHISM_WIN32_COMPILE_ANSI if (DSOUND_DirectSoundEnumerateA && DSOUND_DirectSoundEnumerateA(_dsound_enumerate_callback_a, NULL) == DS_OK) return devices_size; #endif return 0; } static const char *dsound_audio_device_name(uint32_t i) { // If this ever happens it is a catastrophic bug and we // should crash before anything bad happens. if (i >= devices_size) return NULL; return devices[i].name; } /* ---------------------------------------------------------- */ static int dsound_audio_init_driver(const char *driver) { for (int i = 0; i < ARRAY_SIZE(drivers); i++) { if (!strcmp(drivers[i], driver)) { // Get the devices (void)dsound_audio_device_count(); return 0; } } return -1; } static void dsound_audio_quit_driver(void) { _dsound_free_devices(); } /* -------------------------------------------------------- */ static void _dsound_audio_wait_dx5(schism_audio_device_t *dev) { DWORD cursor, xyzzy; HRESULT res; while (!dev->cancelled) { DWORD status; IDirectSoundBuffer_GetStatus(dev->lpbuffer, &status); if (status & DSBSTATUS_BUFFERLOST) { IDirectSoundBuffer_Restore(dev->lpbuffer); IDirectSoundBuffer_GetStatus(dev->lpbuffer, &status); if (status & DSBSTATUS_BUFFERLOST) break; } if (!(status & DSBSTATUS_PLAYING)) { if (IDirectSoundBuffer_Play(dev->lpbuffer, 0, 0, DSBPLAY_LOOPING) != DS_OK) // This should never happen break; } else { res = IDirectSoundBuffer_GetCurrentPosition(dev->lpbuffer, &xyzzy, &cursor); if (res == DSERR_BUFFERLOST) { IDirectSoundBuffer_Restore(dev->lpbuffer); res = IDirectSoundBuffer_GetCurrentPosition(dev->lpbuffer, &xyzzy, &cursor); } if (res != DS_OK) continue; // what? if ((cursor / dev->size) != dev->last_chunk) break; cursor -= (dev->last_chunk * dev->size); uint32_t ms = (cursor / dev->bps / dev->channels) * 1000UL / dev->rate; ms = MAX(1, ms); timer_msleep(ms); } } } #ifdef COMPILE_POSITION_NOTIFY static void _dsound_audio_wait_dx6(schism_audio_device_t *dev) { DWORD status; IDirectSoundBuffer_GetStatus(dev->lpbuffer, &status); if (status & DSBSTATUS_BUFFERLOST) { IDirectSoundBuffer_Restore(dev->lpbuffer); IDirectSoundBuffer_GetStatus(dev->lpbuffer, &status); if (status & DSBSTATUS_BUFFERLOST) return; } if (!(status & DSBSTATUS_PLAYING)) { if (IDirectSoundBuffer_Play(dev->lpbuffer, 0, 0, DSBPLAY_LOOPING) != DS_OK) // This should never happen return; } while (!dev->cancelled && (WaitForSingleObject(dev->event, 10) == WAIT_TIMEOUT)); } #endif static int _dsound_audio_thread(void *data) { schism_audio_device_t *dev = data; mt_thread_set_priority(MT_THREAD_PRIORITY_TIME_CRITICAL); DWORD cursor = 0; HRESULT res = DS_OK; while (!dev->cancelled) { void *buf; DWORD buflen, xyzzy; dev->last_chunk = cursor; cursor = (cursor + 1) % NUM_CHUNKS; res = IDirectSoundBuffer_Lock(dev->lpbuffer, cursor * dev->size, dev->size, &buf, &buflen, NULL, &xyzzy, 0); if (res == DSERR_BUFFERLOST) { IDirectSoundBuffer_Restore(dev->lpbuffer); res = IDirectSoundBuffer_Lock(dev->lpbuffer, cursor * dev->size, dev->size, &buf, &buflen, NULL, &xyzzy, 0); } if (res != DS_OK) { timer_msleep(5); continue; } if (dev->paused) { memset(buf, dev->silence, buflen); } else { mt_mutex_lock(dev->mutex); dev->callback(buf, buflen); mt_mutex_unlock(dev->mutex); } IDirectSoundBuffer_Unlock(dev->lpbuffer, buf, buflen, NULL, 0); dev->audio_wait(dev); } return 0; } static void dsound_audio_close_device(schism_audio_device_t *dev); #ifdef COMPILE_POSITION_NOTIFY static int _dsound_dx6_init_notify_position(schism_audio_device_t *dev) { LPDIRECTSOUNDNOTIFY notify = NULL; int res = -1; // default to failing size_t i; DSBPOSITIONNOTIFY notify_positions[NUM_CHUNKS]; if (IDirectSoundBuffer_QueryInterface(dev->lpbuffer, &IID_IDirectSoundNotify, (void *)¬ify) != DS_OK) goto done; dev->event = CreateEvent(NULL, FALSE, FALSE, NULL); if (!dev->event) goto done; for (i = 0; i < ARRAY_SIZE(notify_positions); i++) { notify_positions[i].dwOffset = i * dev->size; notify_positions[i].hEventNotify = dev->event; } if (IDirectSoundNotify_SetNotificationPositions(notify, ARRAY_SIZE(notify_positions), notify_positions) != DS_OK) goto done; res = 0; done: if (notify) IDirectSoundNotify_Release(notify); return res; } #endif // nonzero on success static schism_audio_device_t *dsound_audio_open_device(uint32_t id, const schism_audio_spec_t *desired, schism_audio_spec_t *obtained) { // If no device is specified pass NULL to DirectSoundCreate LPGUID guid = (id != AUDIO_BACKEND_DEFAULT) ? &devices[id].guid : NULL; // Fill in the format structure WAVEFORMATEX format = { .wFormatTag = WAVE_FORMAT_PCM, .nChannels = desired->channels, .nSamplesPerSec = desired->freq, }; // filter invalid bps values (should never happen, but eh...) switch (desired->bits) { case 8: format.wBitsPerSample = 8; break; default: case 16: format.wBitsPerSample = 16; break; case 32: format.wBitsPerSample = 32; break; } // ok, now allocate schism_audio_device_t *dev = mem_calloc(1, sizeof(*dev)); dev->callback = desired->callback; dev->paused = 1; // always start paused dev->mutex = mt_mutex_create(); if (!dev->mutex) goto fail; if (DSOUND_DirectSoundCreate(guid, &dev->dsound, NULL) != DS_OK) { goto fail; } // Set the cooperative level { DWORD dwlevel; HWND hwnd; video_wm_data_t wm_data; if (video_get_wm_data(&wm_data)) { hwnd = wm_data.data.windows.hwnd; dwlevel = DSSCL_PRIORITY; } else { hwnd = GetDesktopWindow(); dwlevel = DSSCL_NORMAL; } if (IDirectSound_SetCooperativeLevel(dev->dsound, hwnd, dwlevel) != DS_OK) goto fail; } DSBUFFERDESC dsformat = { .dwSize = sizeof(DSBUFFERDESC), .dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_STATIC | DSBCAPS_GETCURRENTPOSITION2 #ifdef COMPILE_POSITION_NOTIFY | DSBCAPS_CTRLPOSITIONNOTIFY #endif , .lpwfxFormat = &format, }; for (;;) { // Recalculate wave format format.nBlockAlign = format.nChannels * (format.wBitsPerSample / 8); format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; dev->bps = format.wBitsPerSample; dev->channels = format.nChannels; dev->samples = desired->samples; dev->size = dev->samples * dev->channels * (dev->bps / 8); dev->rate = format.nSamplesPerSec; dsformat.dwBufferBytes = NUM_CHUNKS * dev->size; if ((dsformat.dwBufferBytes < DSBSIZE_MIN) || (dsformat.dwBufferBytes > DSBSIZE_MAX)) goto DS_badformat; // UGH! HRESULT err = IDirectSound_CreateSoundBuffer(dev->dsound, &dsformat, &dev->lpbuffer, NULL); if (err == DS_OK) { break; } else if (err == DSERR_BADFORMAT || err == DSERR_INVALIDPARAM /* Win2K */) { DS_badformat: if (format.wBitsPerSample == 32) { // Retry again, with 16-bit audio. 32-bit audio doesn't seem // to work on Win2k at all... format.wBitsPerSample = 16; continue; } #ifdef COMPILE_POSITION_NOTIFY // Maybe we're on DX5 or lower? if (dsformat.dwFlags & DSBCAPS_CTRLPOSITIONNOTIFY) { dsformat.dwFlags &= ~(DSBCAPS_CTRLPOSITIONNOTIFY); continue; } #endif } // NOTE: Many VM audio drivers (namely virtual pc and vmware) are broken // under Win2k and return DSERR_CONTROLUNAVAIL. This doesn't seem to be the // full story however, since SDL seems to create the buffer just fine. // I'm just not going to worry about it for now... // Punt if nothing worked goto fail; } if (IDirectSoundBuffer_SetFormat(dev->lpbuffer, &format) != DS_OK) { #if 0 // SDL doesn't error here, and it seems to cause issues on my mac mini goto fail; #endif } dev->silence = (format.wBitsPerSample == 8) ? 0x80 : 0; { // Silence the initial buffer (ripped from SDL) // FIXME why are we retrieving ptr2 and bytes2? LPVOID ptr1, ptr2; DWORD bytes1, bytes2; if (IDirectSoundBuffer_Lock(dev->lpbuffer, 0, dsformat.dwBufferBytes, &ptr1, &bytes1, &ptr2, &bytes2, DSBLOCK_ENTIREBUFFER) == DS_OK) { memset(ptr1, dev->silence, bytes1); IDirectSoundBuffer_Unlock(dev->lpbuffer, ptr1, bytes1, ptr2, bytes2); } } #ifdef COMPILE_POSITION_NOTIFY // Use position notify events to wait for audio to finish under DX6 dev->audio_wait = ((dsformat.dwFlags & DSBCAPS_CTRLPOSITIONNOTIFY) && !_dsound_dx6_init_notify_position(dev)) ? _dsound_audio_wait_dx6 : _dsound_audio_wait_dx5; #else dev->audio_wait = _dsound_audio_wait_dx5; #endif // ok, now start the full thread dev->thread = mt_thread_create(_dsound_audio_thread, "DirectSound audio thread", dev); if (!dev->thread) goto fail; obtained->freq = format.nSamplesPerSec; obtained->channels = format.nChannels; obtained->bits = format.wBitsPerSample; obtained->samples = desired->samples; return dev; fail: dsound_audio_close_device(dev); return NULL; } static void dsound_audio_close_device(schism_audio_device_t *dev) { if (!dev) return; if (dev->thread) { dev->cancelled = 1; mt_thread_wait(dev->thread, NULL); } if (dev->lpbuffer) { IDirectSoundBuffer_Stop(dev->lpbuffer); IDirectSoundBuffer_Release(dev->lpbuffer); } if (dev->dsound) { IDirectSound_Release(dev->dsound); } if (dev->mutex) { mt_mutex_delete(dev->mutex); } if (dev->event) { CloseHandle(dev->event); } free(dev); } static void dsound_audio_lock_device(schism_audio_device_t *dev) { if (!dev) return; mt_mutex_lock(dev->mutex); } static void dsound_audio_unlock_device(schism_audio_device_t *dev) { if (!dev) return; mt_mutex_unlock(dev->mutex); } static void dsound_audio_pause_device(schism_audio_device_t *dev, int paused) { if (!dev) return; mt_mutex_lock(dev->mutex); dev->paused = !!paused; mt_mutex_unlock(dev->mutex); } ////////////////////////////////////////////////////////////////////////////// // dynamic loading #include #include static void *lib_ole32 = NULL; static void *lib_dsound = NULL; static HRESULT (WINAPI *OLE32_CoInitializeEx)(LPVOID, DWORD) = NULL; static void (WINAPI *OLE32_CoUninitialize)(void) = NULL; static IKsPropertySet *dsound_propset = NULL; static int dsound_audio_init(void) { lib_dsound = loadso_object_load("DSOUND.DLL"); if (!lib_dsound) return 0; lib_ole32 = loadso_object_load("OLE32.DLL"); if (!lib_ole32) { loadso_object_unload(lib_dsound); return 0; } DSOUND_DirectSoundCreate = loadso_function_load(lib_dsound, "DirectSoundCreate"); #ifdef SCHISM_WIN32_COMPILE_ANSI DSOUND_DirectSoundEnumerateA = loadso_function_load(lib_dsound, "DirectSoundEnumerateA"); #endif DSOUND_DirectSoundEnumerateW = loadso_function_load(lib_dsound, "DirectSoundEnumerateW"); OLE32_CoInitializeEx = loadso_function_load(lib_ole32, "CoInitializeEx"); OLE32_CoUninitialize = loadso_function_load(lib_ole32, "CoUninitialize"); if (!OLE32_CoInitializeEx || !OLE32_CoUninitialize) { loadso_object_unload(lib_dsound); loadso_object_unload(lib_ole32); return 0; } switch (OLE32_CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)) { case S_OK: case S_FALSE: case RPC_E_CHANGED_MODE: break; default: loadso_object_unload(lib_dsound); loadso_object_unload(lib_ole32); return 0; } dsound_propset = NULL; // wuh? HRESULT (WINAPI *DSOUND_DllGetClassObject)(REFCLSID, REFIID, LPVOID *) = loadso_function_load(lib_dsound, "DllGetClassObject"); if (DSOUND_DllGetClassObject) { IClassFactory *factory; if (SUCCEEDED(DSOUND_DllGetClassObject(&CLSID_DirectSoundPrivate, &IID_IClassFactory, (LPVOID *)&factory))) IClassFactory_CreateInstance(factory, NULL, &IID_IKsPropertySet, (LPVOID *)&dsound_propset); } if (!DSOUND_DirectSoundCreate || !loadso_function_load(lib_dsound, "DirectSoundCaptureCreate")) { loadso_object_unload(lib_dsound); loadso_object_unload(lib_ole32); return 0; } return 1; } static void dsound_audio_quit(void) { DSOUND_DirectSoundCreate = NULL; #ifdef SCHISM_WIN32_COMPILE_ANSI DSOUND_DirectSoundEnumerateA = NULL; #endif DSOUND_DirectSoundEnumerateW = NULL; if (lib_dsound) { loadso_object_unload(lib_dsound); lib_dsound = NULL; } OLE32_CoUninitialize(); if (lib_ole32) { loadso_object_unload(lib_ole32); lib_ole32 = NULL; } if (dsound_propset) { IKsPropertySet_Release(dsound_propset); dsound_propset = NULL; } } ////////////////////////////////////////////////////////////////////////////// const schism_audio_backend_t schism_audio_backend_dsound = { .init = dsound_audio_init, .quit = dsound_audio_quit, .driver_count = dsound_audio_driver_count, .driver_name = dsound_audio_driver_name, .device_count = dsound_audio_device_count, .device_name = dsound_audio_device_name, .init_driver = dsound_audio_init_driver, .quit_driver = dsound_audio_quit_driver, .open_device = dsound_audio_open_device, .close_device = dsound_audio_close_device, .lock_device = dsound_audio_lock_device, .unlock_device = dsound_audio_unlock_device, .pause_device = dsound_audio_pause_device, }; ////////////////////////////////////////////////////////////////////////////// // no charset-specific stuff here, that cruft is handled in the callbacks struct dsound_audio_lookup_callback_data { UINT id; // input GUID guid; // output for description callback, input for device callback char *result; // output }; #define WIN32_DSOUND_AUDIO_LOOKUP_WAVEOUT_NAME_IMPL(AorW, TYPE, CHARSET) \ static BOOL CALLBACK dsound_enumerate_lookup_device_callback_##AorW##_(LPGUID lpGuid, const TYPE *lpcstrDescription, SCHISM_UNUSED const TYPE *lpcstrModule, LPVOID lpContext) \ { \ struct dsound_audio_lookup_callback_data *data = lpContext; \ \ if (lpGuid && !memcmp(lpGuid, &data->guid, sizeof(GUID))) { \ data->result = charset_iconv_easy(lpcstrDescription, CHARSET, CHARSET_UTF8); \ return FALSE; \ } \ \ return TRUE; \ } \ \ static BOOL CALLBACK dsound_enumerate_lookup_description_callback_##AorW##_(PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_##AorW##_DATA pdata, LPVOID puserdata) \ { \ struct dsound_audio_lookup_callback_data *pcbdata = puserdata; \ \ if (pdata && (pdata->WaveDeviceId == pcbdata->id)) { \ memcpy(&pcbdata->guid, &pdata->DeviceId, sizeof(GUID)); \ if (pdata->Description) pcbdata->result = charset_iconv_easy(pdata->Description, CHARSET, CHARSET_ANSI); \ return FALSE; \ } \ \ return TRUE; \ } \ \ static inline int SCHISM_ALWAYS_INLINE win32_dsound_audio_lookup_waveout_name_##AorW(uint32_t waveoutdevid, char **result) \ { \ ULONG ulxyzzy; \ \ if (!DSOUND_DirectSoundEnumerate##AorW) \ return 0; \ \ struct dsound_audio_lookup_callback_data cbdata = { .id = waveoutdevid }; \ \ DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_##AorW##_DATA data = { \ .Callback = dsound_enumerate_lookup_description_callback_##AorW##_, \ .Context = &cbdata, \ }; \ \ if (SUCCEEDED(IKsPropertySet_Get(dsound_propset, &DSPROPSETID_DirectSoundDevice, DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_##AorW, &data, sizeof(data), &data, sizeof(data), &ulxyzzy))) { \ /* we don't need to enumerate twice if we already received the result */ \ if (cbdata.result) { *result = cbdata.result; return 1; } \ \ DSOUND_DirectSoundEnumerate##AorW(dsound_enumerate_lookup_device_callback_##AorW##_, &cbdata); \ if (cbdata.result) { *result = cbdata.result; return 1; } \ } \ \ return 0; \ } #ifdef SCHISM_WIN32_COMPILE_ANSI WIN32_DSOUND_AUDIO_LOOKUP_WAVEOUT_NAME_IMPL(A, CHAR, CHARSET_ANSI) #endif WIN32_DSOUND_AUDIO_LOOKUP_WAVEOUT_NAME_IMPL(W, WCHAR, CHARSET_WCHAR_T) #undef WIN32_DSOUND_AUDIO_LOOKUP_WAVEOUT_NAME_IMPL int win32_dsound_audio_lookup_waveout_name(const uint32_t *waveoutdevid, char **result) { if (!waveoutdevid || !dsound_propset) return 0; #ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & 0x80000000U) { // Win9x return win32_dsound_audio_lookup_waveout_name_A(*waveoutdevid, result); } else #endif { // WinNT return win32_dsound_audio_lookup_waveout_name_W(*waveoutdevid, result); } } schismtracker-20250313/sys/win32/audio-waveout.c000066400000000000000000000275231476471630300213560ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // Win32 Waveout audio backend - written because SDL 1.2 kind of sucks, and // this driver is especially terrible there. // - paper #include "headers.h" #include "charset.h" #include "mt.h" #include "mem.h" #include "osdefs.h" #include "backend/audio.h" #include #define WAVEHDR_DWUSER_PREPARED (~((DWORD_PTR)0)) // Define this ourselves; old toolchains don't have it struct waveoutcaps2w { WORD wMid; WORD wPid; MMVERSION vDriverVersion; WCHAR szPname[MAXPNAMELEN]; DWORD dwFormats; WORD wChannels; WORD wReserved1; DWORD dwSupport; GUID ManufacturerGuid; GUID ProductGuid; GUID NameGuid; }; // This is needed because waveout is weird, and the WAVEHDR buffers need // some time to "cool down", so we cycle between buffers. #define NUM_BUFFERS 2 SCHISM_STATIC_ASSERT(NUM_BUFFERS >= 2, "NUM_BUFFERS must be at least 2"); struct schism_audio_device { // The thread where we callback mt_thread_t *thread; int cancelled; // The callback and the protecting mutex void (*callback)(uint8_t *stream, int len); mt_mutex_t *mutex; // This is for synchronizing the audio thread with // the actual audio device HANDLE sem; HWAVEOUT hwaveout; // The allocated raw mixing buffer uint8_t *buffer; WAVEHDR wavehdr[NUM_BUFFERS]; int next_buffer; }; /* ---------------------------------------------------------- */ /* drivers */ // lol static const char *drivers[] = { "waveout", }; static int waveout_audio_driver_count() { return ARRAY_SIZE(drivers); } static const char *waveout_audio_driver_name(int i) { if (i >= ARRAY_SIZE(drivers) || i < 0) return NULL; return drivers[i]; } /* ------------------------------------------------------------------------ */ // devices name cache; refreshed after every call to waveout_audio_device_count static struct { uint32_t id; char *name; } *devices = NULL; static size_t devices_size = 0; // FIXME: This screws up the GUI royally if someone hotplugs a device. // The IDs of waveout devices aren't necessarily "unique", so we can't // use those; they change any time an audio device is added or removed // (annoying!!) // The only thing I can think of is opening literally every single // device and then calling waveOutGetID() to check if it changed, // which is obviously stupid and a waste of resources. static uint32_t waveout_audio_device_count(void) { const UINT devs = waveOutGetNumDevs(); if (devices) { for (size_t i = 0; i < devices_size; i++) free(devices[i].name); free(devices); } devices = mem_alloc(sizeof(*devices) * devs); devices_size = 0; UINT i; for (i = 0; i < devs; i++) { union { #ifdef SCHISM_WIN32_COMPILE_ANSI WAVEOUTCAPSA a; #endif WAVEOUTCAPSW w; struct waveoutcaps2w w2; } caps = {0}; #ifdef SCHISM_WIN32_COMPILE_ANSI int win9x = (GetVersion() & UINT32_C(0x80000000)); if (win9x) { if (waveOutGetDevCapsA(i, &caps.a, sizeof(caps.a)) != MMSYSERR_NOERROR) continue; // Try receiving based on the name GUID. Otherwise, fall back to the short name. if (!win32_audio_lookup_device_name(NULL, &i, &devices[devices_size].name) && charset_iconv(caps.a.szPname, &devices[devices_size].name, CHARSET_ANSI, CHARSET_UTF8, sizeof(caps.a.szPname))) continue; } else #endif { // Try WAVEOUTCAPS2 before WAVEOUTCAPS if (waveOutGetDevCapsW(i, (LPWAVEOUTCAPSW)&caps.w2, sizeof(caps.w2)) == MMSYSERR_NOERROR) { // Try receiving based on the name GUID. Otherwise, fall back to the short name. if (!win32_audio_lookup_device_name(&caps.w2.NameGuid, &i, &devices[devices_size].name) && charset_iconv(caps.w2.szPname, &devices[devices_size].name, CHARSET_WCHAR_T, CHARSET_UTF8, sizeof(caps.w2.szPname))) continue; } else if (waveOutGetDevCapsW(i, &caps.w, sizeof(caps.w)) == MMSYSERR_NOERROR) { if (!win32_audio_lookup_device_name(NULL, &i, &devices[devices_size].name) && charset_iconv(caps.w.szPname, &devices[devices_size].name, CHARSET_WCHAR_T, CHARSET_UTF8, sizeof(caps.w.szPname))) continue; } else { continue; } } devices[devices_size].id = i; devices_size++; } return devs; } static const char *waveout_audio_device_name(uint32_t i) { // If this ever happens it is a catastrophic bug and we // should crash before anything bad happens. if (i >= devices_size) return NULL; return devices[i].name; } /* ---------------------------------------------------------- */ static int waveout_audio_init_driver(const char *driver) { int fnd = 0; for (int i = 0; i < ARRAY_SIZE(drivers); i++) { if (!strcmp(drivers[i], driver)) { fnd = 1; break; } } if (!fnd) return -1; // Get the devices (void)waveout_audio_device_count(); return 0; } static void waveout_audio_quit_driver(void) { // Free the devices if (devices) { for (size_t i = 0; i < devices_size; i++) free(devices[i].name); free(devices); devices = NULL; } } /* -------------------------------------------------------- */ static int waveout_audio_thread(void *data) { schism_audio_device_t *dev = data; mt_thread_set_priority(MT_THREAD_PRIORITY_TIME_CRITICAL); while (!dev->cancelled) { // FIXME add a timeout here mt_mutex_lock(dev->mutex); dev->callback((uint8_t *)dev->wavehdr[dev->next_buffer].lpData, dev->wavehdr[dev->next_buffer].dwBufferLength); mt_mutex_unlock(dev->mutex); waveOutWrite(dev->hwaveout, &dev->wavehdr[dev->next_buffer], sizeof(dev->wavehdr[dev->next_buffer])); dev->next_buffer = (dev->next_buffer + 1) % NUM_BUFFERS; // Wait until either the semaphore is polled or we've been asked to exit // This prevents a hang on device close, since after waveOutClose is called // the semaphore is never released, which will cause us to wait forever. while (!dev->cancelled && (WaitForSingleObject(dev->sem, 10) == WAIT_TIMEOUT)); } return 0; } static void CALLBACK waveout_audio_callback(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2) { schism_audio_device_t *dev = (schism_audio_device_t *)dwInstance; // don't care about other messages switch (uMsg) { case WOM_DONE: case WOM_CLOSE: ReleaseSemaphore(dev->sem, 1, NULL); default: return; } } // decl static void waveout_audio_close_device(schism_audio_device_t *dev); // nonzero on success static schism_audio_device_t *waveout_audio_open_device(uint32_t id, const schism_audio_spec_t *desired, schism_audio_spec_t *obtained) { // Default to some device that can handle our output UINT device_id = (id == AUDIO_BACKEND_DEFAULT || id < devices_size) ? (WAVE_MAPPER) : devices[id].id; // Fill in the format structure WAVEFORMATEX format = { .wFormatTag = WAVE_FORMAT_PCM, .nChannels = desired->channels, .nSamplesPerSec = desired->freq, }; // filter invalid bps values (should never happen, but eh...) switch (desired->bits) { case 8: format.wBitsPerSample = 8; break; default: case 16: format.wBitsPerSample = 16; break; case 32: format.wBitsPerSample = 32; break; } // ok, now we can allocate the device schism_audio_device_t *dev = mem_calloc(1, sizeof(*dev)); dev->callback = desired->callback; for (;;) { // Recalculate format format.nBlockAlign = format.nChannels * (format.wBitsPerSample / 8); format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; MMRESULT err = waveOutOpen(&dev->hwaveout, device_id, &format, (UINT_PTR)waveout_audio_callback, (UINT_PTR)dev, CALLBACK_FUNCTION | WAVE_ALLOWSYNC); if (err == MMSYSERR_NOERROR) { // We're done here break; } else if (err == WAVERR_BADFORMAT) { // Retry with 16-bit. 32-bit samples // don't work everywhere (notably windows xp // is broken and so is everything before it) if (format.wBitsPerSample == 32) { format.wBitsPerSample = 16; continue; } } // Punt if we failed and we can't do anything about it goto fail; } dev->mutex = mt_mutex_create(); if (!dev->mutex) goto fail; dev->sem = CreateSemaphoreA(NULL, 1, NUM_BUFFERS, "WinMM audio sync semaphore"); if (!dev->sem) goto fail; // allocate the buffer DWORD buflen = desired->samples * format.nChannels * (format.wBitsPerSample / 8); dev->buffer = mem_alloc(buflen * NUM_BUFFERS); // fill in the wavehdrs for (int i = 0; i < NUM_BUFFERS; i++) { dev->wavehdr[i].lpData = (LPSTR)dev->buffer + (buflen * i); dev->wavehdr[i].dwBufferLength = buflen; dev->wavehdr[i].dwFlags = WHDR_DONE; if (waveOutPrepareHeader(dev->hwaveout, &dev->wavehdr[i], sizeof(dev->wavehdr[i])) != MMSYSERR_NOERROR) goto fail; dev->wavehdr[i].dwUser = WAVEHDR_DWUSER_PREPARED; } // ok, now start the full thread dev->thread = mt_thread_create(waveout_audio_thread, "WinMM audio thread", dev); if (!dev->thread) goto fail; obtained->freq = format.nSamplesPerSec; obtained->channels = format.nChannels; obtained->bits = format.wBitsPerSample; obtained->samples = desired->samples; return dev; fail: waveout_audio_close_device(dev); return NULL; } static void waveout_audio_close_device(schism_audio_device_t *dev) { if (!dev) return; // kill the thread before doing anything else if (dev->thread) { dev->cancelled = 1; mt_thread_wait(dev->thread, NULL); } // close the device if (dev->hwaveout) waveOutClose(dev->hwaveout); for (int i = 0; i < NUM_BUFFERS; i++) { if (dev->wavehdr[i].dwUser != WAVEHDR_DWUSER_PREPARED) continue; // sleep until the device is done with our buffer while (!(dev->wavehdr[i].dwFlags & WHDR_DONE)) timer_delay(10); waveOutUnprepareHeader(dev->hwaveout, &dev->wavehdr[i], sizeof(dev->wavehdr[i])); } if (dev->buffer) free(dev->buffer); if (dev->sem) CloseHandle(dev->sem); if (dev->mutex) mt_mutex_delete(dev->mutex); free(dev); } static void waveout_audio_lock_device(schism_audio_device_t *dev) { if (!dev) return; mt_mutex_lock(dev->mutex); } static void waveout_audio_unlock_device(schism_audio_device_t *dev) { if (!dev) return; mt_mutex_unlock(dev->mutex); } static void waveout_audio_pause_device(schism_audio_device_t *dev, int paused) { if (!dev) return; mt_mutex_lock(dev->mutex); if (paused) { waveOutPause(dev->hwaveout); } else { waveOutRestart(dev->hwaveout); } mt_mutex_unlock(dev->mutex); } ////////////////////////////////////////////////////////////////////////////// // dynamic loading static int waveout_audio_init(void) { return 1; } static void waveout_audio_quit(void) { // dont do anything } ////////////////////////////////////////////////////////////////////////////// const schism_audio_backend_t schism_audio_backend_waveout = { .init = waveout_audio_init, .quit = waveout_audio_quit, .driver_count = waveout_audio_driver_count, .driver_name = waveout_audio_driver_name, .device_count = waveout_audio_device_count, .device_name = waveout_audio_device_name, .init_driver = waveout_audio_init_driver, .quit_driver = waveout_audio_quit_driver, .open_device = waveout_audio_open_device, .close_device = waveout_audio_close_device, .lock_device = waveout_audio_lock_device, .unlock_device = waveout_audio_unlock_device, .pause_device = waveout_audio_pause_device, }; schismtracker-20250313/sys/win32/clippy.c000066400000000000000000000126061476471630300200610ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "backend/clippy.h" #include "loadso.h" #include "charset.h" #include "mem.h" #include "video.h" #include "util.h" #include "osdefs.h" #include static int win32_clippy_have_selection(void) { return 0; } static int win32_clippy_have_clipboard(void) { return IsClipboardFormatAvailable(CF_UNICODETEXT) || IsClipboardFormatAvailable(CF_TEXT); } static void win32_clippy_set_selection(const char *text) { // win32 doesn't have this. } static void win32_clippy_set_clipboard(const char *text) { // Only use CF_UNICODETEXT on Windows NT machines #ifdef SCHISM_WIN32_COMPILE_ANSI const UINT fmt = (GetVersion() & UINT32_C(0x80000000)) ? CF_TEXT : CF_UNICODETEXT; #else static const UINT fmt = CF_UNICODETEXT; #endif union { LPWSTR w; #ifdef SCHISM_WIN32_COMPILE_ANSI LPSTR a; #endif } str; size_t i; size_t size = 0; video_wm_data_t wm_data; if (!video_get_wm_data(&wm_data) && wm_data.subsystem != VIDEO_WM_DATA_SUBSYSTEM_WINDOWS) return; if (!OpenClipboard(wm_data.data.windows.hwnd)) return; // Convert from LF to CRLF if (fmt == CF_UNICODETEXT && !charset_iconv(text, &str.w, CHARSET_UTF8, CHARSET_WCHAR_T, SIZE_MAX)) { for (i = 0; str.w[i]; i++, size++) if (str.w[i] == L'\n' && (i == 0 || str.w[i - 1] != L'\r')) size++; #ifdef SCHISM_WIN32_COMPILE_ANSI } else if (fmt == CF_TEXT && !charset_iconv(text, &str.a, CHARSET_UTF8, CHARSET_ANSI, SIZE_MAX)) { for (i = 0; str.a[i]; i++, size++) if (str.a[i] == '\n' && (i == 0 || str.a[i - 1] != '\r')) size++; #endif } else { // give up return; } size = (size + 1) * ((fmt == CF_UNICODETEXT) ? sizeof(wchar_t) : sizeof(char)); // Copy the result to the clipboard HANDLE mem = GlobalAlloc(GMEM_MOVEABLE, size); if (mem) { if (fmt == CF_UNICODETEXT) { wchar_t *dst = (wchar_t *)GlobalLock(mem); if (dst) { for (i = 0; str.w[i]; i++) { if (str.w[i] == L'\n' && (i == 0 || str.w[i - 1] != L'\r')) *dst++ = L'\r'; *dst++ = str.w[i]; } *dst = L'\0'; GlobalUnlock(mem); } free(str.w); } #ifdef SCHISM_WIN32_COMPILE_ANSI else if (fmt == CF_TEXT) { char *dst = (char *)GlobalLock(mem); if (dst) { for (i = 0; str.a[i]; i++) { if (str.a[i] == '\n' && (i == 0 || str.a[i - 1] != '\r')) *dst++ = '\r'; *dst++ = str.a[i]; } *dst = '\0'; GlobalUnlock(mem); } free(str.a); } #endif if (!EmptyClipboard() || !SetClipboardData(fmt, mem)) GlobalFree(mem); } CloseClipboard(); } static char *win32_clippy_get_selection(void) { // doesn't exist, ever return str_dup(""); } static char *win32_clippy_get_clipboard(void) { char *text = NULL; int i; int fmt; video_wm_data_t wm_data; if (!video_get_wm_data(&wm_data) && wm_data.subsystem != VIDEO_WM_DATA_SUBSYSTEM_WINDOWS) return str_dup(""); #ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & UINT32_C(0x80000000)) { // Believe it or not, CF_UNICODETEXT *does* actually work on // Windows 95. However, practically every application that runs // will completely ignore it and just use CF_TEXT instead. fmt = CF_TEXT; } else #endif { UINT formats[] = {CF_UNICODETEXT, CF_TEXT}; fmt = GetPriorityClipboardFormat(formats, ARRAY_SIZE(formats)); if (fmt < 0) return str_dup(""); } // try a couple times to open the clipboard for (i = 0; i < 5; i++) { if (OpenClipboard(wm_data.data.windows.hwnd)) { HANDLE mem = GetClipboardData(fmt); SIZE_T len = GlobalSize(mem); // no overflow! if (mem) { if (fmt == CF_UNICODETEXT) { LPWSTR str = (LPWSTR)GlobalLock(mem); if (str) charset_iconv(str, &text, CHARSET_WCHAR_T, CHARSET_UTF8, len); } else if (fmt == CF_TEXT) { LPSTR str = (LPSTR)GlobalLock(mem); if (str) charset_iconv(str, &text, CHARSET_ANSI, CHARSET_UTF8, len); } GlobalUnlock(mem); } CloseClipboard(); break; } SleepEx(10, FALSE); } return text ? text : str_dup(""); } static int win32_clippy_init(void) { // nothing to do return 1; } static void win32_clippy_quit(void) { // nothing to do } const schism_clippy_backend_t schism_clippy_backend_win32 = { .init = win32_clippy_init, .quit = win32_clippy_quit, .have_selection = win32_clippy_have_selection, .get_selection = win32_clippy_get_selection, .set_selection = win32_clippy_set_selection, .have_clipboard = win32_clippy_have_clipboard, .get_clipboard = win32_clippy_get_clipboard, .set_clipboard = win32_clippy_set_clipboard, }; schismtracker-20250313/sys/win32/dmoz.c000066400000000000000000000047621476471630300175360ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "backend/dmoz.h" #include "loadso.h" #include "charset.h" #include "dmoz.h" #include "util.h" #include "mem.h" #include "osdefs.h" #include static char *win32_dmoz_get_exe_path(void) { char *utf8 = NULL; #ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & UINT32_C(0x80000000)) { // Windows 9x char path[MAX_PATH]; if (GetModuleFileNameA(NULL, path, ARRAY_SIZE(path))) charset_iconv(path, &utf8, CHARSET_ANSI, CHARSET_UTF8, sizeof(path)); } else #endif { // Windows NT. This uses dynamic allocation to account for e.g. UNC paths. DWORD pathsize = MAX_PATH; WCHAR *path = NULL; for (;;) { { void *new = mem_realloc(path, pathsize * sizeof(*path)); if (!new) { free(path); return NULL; } path = new; } DWORD len = GetModuleFileNameW(NULL, path, pathsize); if (len < pathsize - 1) break; pathsize *= 2; } charset_iconv(path, &utf8, CHARSET_WCHAR_T, CHARSET_UTF8, pathsize * sizeof(*path)); free(path); } if (utf8) { char *parent = dmoz_path_get_parent_directory(utf8); free(utf8); if (parent) return parent; } return NULL; } ////////////////////////////////////////////////////////////////////////////// // init/quit static int win32_dmoz_init(void) { // nothing to do return 1; } static void win32_dmoz_quit(void) { // nothing } const schism_dmoz_backend_t schism_dmoz_backend_win32 = { .init = win32_dmoz_init, .quit = win32_dmoz_quit, .get_exe_path = win32_dmoz_get_exe_path, };schismtracker-20250313/sys/win32/filetype.c000066400000000000000000000031461476471630300204010ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "osdefs.h" #include "charset.h" #include #include #include void win32_filecreated_callback(const char *filename) { /* let explorer know when we create a file. */ const charset_t explorer_charset = #ifdef SCHISM_WIN32_COMPILE_ANSI (GetVersion() & 0x80000000) ? CHARSET_ANSI : #endif CHARSET_WCHAR_T; void *wc = charset_iconv_easy(filename, CHARSET_UTF8, explorer_charset); if (wc) { SHChangeNotify(SHCNE_CREATE, SHCNF_PATH|SHCNF_FLUSHNOWAIT, wc, NULL); SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH|SHCNF_FLUSHNOWAIT, wc, NULL); free(wc); } } schismtracker-20250313/sys/win32/midi-win32mm.c000066400000000000000000000164421476471630300207770ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "log.h" #include "midi.h" #include "timer.h" #include "loadso.h" #include "charset.h" #include "mem.h" #include "osdefs.h" #include "util.h" #include #include #include struct win32mm_midi { DWORD id; HMIDIOUT out; HMIDIIN in; union { #ifdef SCHISM_WIN32_COMPILE_ANSI MIDIINCAPSA a; #endif MIDIINCAPSW w; } icp; union { #ifdef SCHISM_WIN32_COMPILE_ANSI MIDIOUTCAPSA a; #endif MIDIOUTCAPSW w; } ocp; MIDIHDR hh; LPMIDIHDR obuf; unsigned char sysx[1024]; }; // whether to use ANSI or Unicode versions of functions // (currently based on whether we're running on win9x or not) #ifdef SCHISM_WIN32_COMPILE_ANSI static int use_ansi_funcs = 0; #endif static void _win32mm_sysex(LPMIDIHDR *q, const unsigned char *data, uint32_t len) { struct { MIDIHDR hdr; char data[SCHISM_FAM_SIZE]; } *m; if (!data) len = 0; m = mem_calloc(1, sizeof(*m) + len); if (len) memcpy(m->data, data, len); m->hdr.lpData = m->data; m->hdr.dwBufferLength = len; m->hdr.lpNext = *q; m->hdr.dwOffset = 0; *q = &m->hdr; } static void _win32mm_send(struct midi_port *p, const unsigned char *data, uint32_t len, SCHISM_UNUSED uint32_t delay) { struct win32mm_midi *m; DWORD q; if (len == 0) return; // FIXME this shouldn't be done here! //if (!(p->io & MIDI_OUTPUT)) 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_PTR inst, DWORD_PTR param1, DWORD_PTR param2) { struct midi_port *p = (struct midi_port *)inst; struct win32mm_midi *m; unsigned char c[4]; switch (wmsg) { case MIM_OPEN: timer_msleep(0); /* eh? */ case MIM_CLOSE: break; case MIM_DATA: c[0] = param1 & 255; c[1] = (param1 >> 8) & 255; c[2] = (param1 >> 16) & 255; midi_received_cb(p, c, 3); break; case MIM_LONGDATA: { MIDIHDR* hdr = (MIDIHDR*) param1; if (hdr->dwBytesRecorded > 0) { /* long data */ m = p->userdata; midi_received_cb(p, (unsigned char *) m->hh.lpData, m->hh.dwBytesRecorded); // I don't see any reason we can't do this here (midi_received_cb does not // retain the pointer to the data after it returns) midiInAddBuffer(in, &m->hh, sizeof(m->hh)); } break; } } } static int _win32mm_start(struct midi_port *p) { struct win32mm_midi *m; UINT id; WORD r; m = p->userdata; id = m->id; if (p->io == MIDI_INPUT) { m->in = NULL; r = midiInOpen(&m->in, (UINT_PTR)id, (DWORD_PTR)_win32mm_inputcb, (DWORD_PTR)p, CALLBACK_FUNCTION); if (r != MMSYSERR_NOERROR) return 0; memset(&m->hh, 0, sizeof(m->hh)); m->hh.lpData = (LPSTR)m->sysx; m->hh.dwBufferLength = sizeof(m->sysx); m->hh.dwFlags = 0; r = midiInPrepareHeader(m->in, &m->hh, sizeof(MIDIHDR)); if (r != MMSYSERR_NOERROR) return 0; r = midiInAddBuffer(m->in, &m->hh, sizeof(MIDIHDR)); if (r != MMSYSERR_NOERROR) return 0; if (midiInStart(m->in) != MMSYSERR_NOERROR) return 0; } if (p->io & MIDI_OUTPUT) { m->out = NULL; if (midiOutOpen(&m->out, (UINT_PTR)id, 0, 0, CALLBACK_NULL) != MMSYSERR_NOERROR) return 0; } return 1; } static int _win32mm_stop(struct midi_port *p) { struct win32mm_midi *m; LPMIDIHDR ptr; m = p->userdata; if (p->io & MIDI_INPUT) { /* portmidi appears to (essentially) ignore the error codes for these guys */ (void)midiInStop(m->in); (void)midiInReset(m->in); (void)midiInUnprepareHeader(m->in,&m->hh,sizeof(m->hh)); (void)midiInClose(m->in); } if (p->io & MIDI_OUTPUT) { (void)midiOutReset(m->out); (void)midiOutClose(m->out); /* free output chain */ ptr = m->obuf; while (ptr) { m->obuf = m->obuf->lpNext; (void)free(m->obuf); ptr = m->obuf; } } return 1; } static void _win32mm_poll(struct midi_provider *p) { static uint32_t last_known_in_port = 0; static uint32_t last_known_out_port = 0; struct win32mm_midi *data; UINT i; UINT mmin, mmout; WORD r; mmin = midiInGetNumDevs(); for (i = last_known_in_port; i < mmin; i++) { data = mem_calloc(1, sizeof(struct win32mm_midi)); #ifdef SCHISM_WIN32_COMPILE_ANSI if (use_ansi_funcs) { r = midiInGetDevCapsA(i, &data->icp.a, sizeof(data->icp.a)); } else #endif { r = midiInGetDevCapsW(i, &data->icp.w, sizeof(data->icp.w)); } if (r != MMSYSERR_NOERROR) { free(data); continue; } data->id = i; char *utf8; #ifdef SCHISM_WIN32_COMPILE_ANSI if (use_ansi_funcs) { if (charset_iconv(data->ocp.a.szPname, &utf8, CHARSET_ANSI, CHARSET_UTF8, sizeof(data->ocp.a.szPname))) continue; } else #endif { if (charset_iconv(data->ocp.w.szPname, &utf8, CHARSET_WCHAR_T, CHARSET_UTF8, sizeof(data->ocp.w.szPname))) continue; } midi_port_register(p, MIDI_INPUT, utf8, data, 1); } last_known_in_port = mmin; mmout = midiOutGetNumDevs(); for (i = last_known_out_port; i < mmout; i++) { data = mem_calloc(1, sizeof(struct win32mm_midi)); #ifdef SCHISM_WIN32_COMPILE_ANSI if (use_ansi_funcs) { r = midiOutGetDevCapsA(i, &data->ocp.a, sizeof(data->ocp.a)); } else #endif { r = midiOutGetDevCapsW(i, &data->ocp.w, sizeof(data->ocp.w)); } if (r != MMSYSERR_NOERROR) { if (data) free(data); continue; } data->id = i; char *utf8; #ifdef SCHISM_WIN32_COMPILE_ANSI if (use_ansi_funcs) { if (charset_iconv(data->ocp.a.szPname, &utf8, CHARSET_ANSI, CHARSET_UTF8, sizeof(data->ocp.a.szPname))) continue; } else #endif { if (charset_iconv(data->ocp.w.szPname, &utf8, CHARSET_WCHAR_T, CHARSET_UTF8, sizeof(data->ocp.w.szPname))) continue; } midi_port_register(p, MIDI_OUTPUT, utf8, data, 1); } last_known_out_port = mmout; } int win32mm_midi_setup(void) { static const struct midi_driver driver = { .flags = 0, .poll = _win32mm_poll, .thread = NULL, .enable = _win32mm_start, .disable = _win32mm_stop, .send = _win32mm_send, }; #ifdef SCHISM_WIN32_COMPILE_ANSI use_ansi_funcs = (GetVersion() & UINT32_C(0x80000000)); #endif if (!midi_provider_register("Win32MM", &driver)) return 0; return 1; } schismtracker-20250313/sys/win32/mt.c000066400000000000000000000536111476471630300172020ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _WIN32_WINNT 0x0600 // Windows Vista #include "headers.h" #include "mem.h" #include "log.h" #include "loadso.h" #include "backend/mt.h" #include #include #define MSVC_EXCEPTION_NAME_CODE UINT32_C(0x406D1388) // Multithreading implementation for Win32 // // For the most part, this was created because of SDL 1.2 using the CreateMutex() API, // even though critical sections existed within Windows since NT 3.1 (and possibly // this can be attributed to MSDN for having wrong minimum system requirements) // In short, Win32 mutexes are kernel-level, while critical sections are process-level. // This means, theoretically, there should be no calling into the kernel when using // any multithreading structures (including mutexes, which we use quite a lot in the // source, especially for MIDI and events) making in particular MIDI quite a bit faster // on older windows systems. Additionally we implement the SRWlock API that SDL uses // as well (though I can't say for sure exactly how performant it is. mileage may vary) /* ------------------------------------ */ static PVOID (WINAPI *KERNEL32_AddVectoredExceptionHandler)(ULONG, PVECTORED_EXCEPTION_HANDLER); static ULONG (WINAPI *KERNEL32_RemoveVectoredExceptionHandler)(PVOID); static BOOL (WINAPI *KERNEL32_IsDebuggerPresent)(void); // NT 4+ // not necessarily kernel32, but most likely. sometimes it can be kernelbase! static HRESULT (WINAPI *KERNEL32_SetThreadDescription)(HANDLE, PCWSTR); // win10+ struct mt_thread { HANDLE thread; int status; char *name; schism_thread_function_t func; void *userdata; }; static LONG __stdcall win32_exception_handler_noop(EXCEPTION_POINTERS *info) { return (info && info->ExceptionRecord && info->ExceptionRecord->ExceptionCode == MSVC_EXCEPTION_NAME_CODE) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH; } // This raises an exception that notifies the current debugger // about a name change. static inline void win32_raise_name_exception(const char *name) { char *name_a = charset_iconv_easy(name, CHARSET_UTF8, CHARSET_ANSI); if (name_a) { // Based on other code that uses ugly non-standard packing; this is only // tested for x86 and x86_64, no idea about arm. SCHISM_STATIC_ASSERT(sizeof(ULONG_PTR) >= sizeof(DWORD), "This code assumes ULONG_PTR is at least the size of a DWORD"); SCHISM_STATIC_ASSERT(sizeof(ULONG_PTR) % sizeof(DWORD) == 0, "This code assumes the size of ULONG_PTR is a multiple of the size of a DWORD"); #define DIVIDE_ROUNDING_UP(x, y) (((x) + (y) - 1) / (y)) union { DWORD dw; ULONG_PTR ulp; } info[2 + DIVIDE_ROUNDING_UP(sizeof(DWORD) * 2, sizeof(ULONG_PTR))] = {0}; #undef DIVIDE_ROUNDING_UP info[0].dw = 0x1000; // Magic number info[1].ulp = (ULONG_PTR)name_a; // ANSI string w/ the name of the thread { // These two DWORDs are either packed into one or two ULONG_PTRs, depending // on whether it's 32 or 64-bit; either way, they're just right beside each // other in memory, so we can just do this. static const DWORD dw[2] = { -1 /* Thread ID (-1 for current thread) */, 0 /* Reserved */}; memcpy(info + 2, dw, sizeof(dw)); } RaiseException(MSVC_EXCEPTION_NAME_CODE, 0, ARRAY_SIZE(info), (const ULONG_PTR *)&info); free(name_a); } } static unsigned int __stdcall SCHISM_FORCE_ALIGN_ARG_POINTER win32_dummy_thread_func(void *userdata) { mt_thread_t *thread = userdata; if (thread->name) { if (KERNEL32_SetThreadDescription) { LPWSTR strw = charset_iconv_easy(thread->name, CHARSET_UTF8, CHARSET_WCHAR_T); if (strw) { KERNEL32_SetThreadDescription(GetCurrentThread(), strw); free(strw); } } // SOMEBODY TOUCHA MY SPAGHET! if (KERNEL32_AddVectoredExceptionHandler && KERNEL32_RemoveVectoredExceptionHandler) { PVOID handler = KERNEL32_AddVectoredExceptionHandler(1, win32_exception_handler_noop); if (handler) { win32_raise_name_exception(thread->name); KERNEL32_RemoveVectoredExceptionHandler(handler); } // else ... ? } else if (KERNEL32_IsDebuggerPresent && KERNEL32_IsDebuggerPresent()) { win32_raise_name_exception(thread->name); } } thread->status = thread->func(thread->userdata); _endthreadex(0); return 0; } mt_thread_t *win32_thread_create(schism_thread_function_t func, const char *name, void *userdata) { mt_thread_t *thread = mem_calloc(1, sizeof(*thread)); thread->func = func; thread->userdata = userdata; thread->name = (name) ? str_dup(name) : NULL; unsigned int threadid; thread->thread = (HANDLE)_beginthreadex(NULL, 0, win32_dummy_thread_func, thread, 0, &threadid); if (!thread->thread) { free(thread); return NULL; } return thread; } void win32_thread_wait(mt_thread_t *thread, int *status) { if (!thread) return; WaitForSingleObject(thread->thread, INFINITE); CloseHandle(thread->thread); free(thread->name); if (status) *status = thread->status; free(thread); } void win32_thread_set_priority(int priority) { // ok int npri; switch (priority) { #define PRIORITY(x, y) case MT_THREAD_PRIORITY_##x: npri = THREAD_PRIORITY_##y; break PRIORITY(LOW, LOWEST); PRIORITY(NORMAL, NORMAL); PRIORITY(HIGH, HIGHEST); PRIORITY(TIME_CRITICAL, TIME_CRITICAL); default: return; #undef PRIORITY } SetThreadPriority(GetCurrentThread(), npri); } // returns the current thread's ID static mt_thread_id_t win32_thread_id(void) { return GetCurrentThreadId(); } /* -------------------------------------------------------------- */ /* mutexes */ // Critical section pointers static void (WINAPI *KERNEL32_InitializeCriticalSection)(LPCRITICAL_SECTION); static DWORD (WINAPI *KERNEL32_SetCriticalSectionSpinCount)(LPCRITICAL_SECTION, DWORD); static void (WINAPI *KERNEL32_DeleteCriticalSection)(LPCRITICAL_SECTION); static void (WINAPI *KERNEL32_EnterCriticalSection)(LPCRITICAL_SECTION); static void (WINAPI *KERNEL32_LeaveCriticalSection)(LPCRITICAL_SECTION); static void (WINAPI *KERNEL32_InitializeSRWLock)(PSRWLOCK); static void (WINAPI *KERNEL32_AcquireSRWLockExclusive)(PSRWLOCK); static void (WINAPI *KERNEL32_ReleaseSRWLockExclusive)(PSRWLOCK); // Should be set on init and never touched again until quit. static enum { WIN32_MUTEX_IMPL_MUTEX = 0, WIN32_MUTEX_IMPL_CRITICALSECTION, WIN32_MUTEX_IMPL_SRWLOCK, } win32_mutex_impl = WIN32_MUTEX_IMPL_MUTEX; struct mt_mutex { union { HANDLE mutex; CRITICAL_SECTION critsec; struct { SRWLOCK srw; uint32_t count; // thread ID of owner // zero is never a valid ID, as implied // via the docs for GetThreadId(), so // this is initialized to zero. DWORD owner; } srwlock; } impl; }; mt_mutex_t *win32_mutex_create(void) { mt_mutex_t *mutex = mem_calloc(1, sizeof(*mutex)); switch (win32_mutex_impl) { case WIN32_MUTEX_IMPL_MUTEX: mutex->impl.mutex = CreateMutexA(NULL, FALSE, NULL); if (!mutex->impl.mutex) { free(mutex); return NULL; } break; case WIN32_MUTEX_IMPL_CRITICALSECTION: KERNEL32_InitializeCriticalSection(&mutex->impl.critsec); // Setting a higher spin count generally results in better // performance on multi-core/multi-processor systems. if (KERNEL32_SetCriticalSectionSpinCount) KERNEL32_SetCriticalSectionSpinCount(&mutex->impl.critsec, 2000u); break; case WIN32_MUTEX_IMPL_SRWLOCK: KERNEL32_InitializeSRWLock(&mutex->impl.srwlock.srw); mutex->impl.srwlock.count = 0; mutex->impl.srwlock.owner = 0; // never a valid ID break; } return mutex; } void win32_mutex_delete(mt_mutex_t *mutex) { if (!mutex) return; switch (win32_mutex_impl) { case WIN32_MUTEX_IMPL_MUTEX: CloseHandle(mutex->impl.mutex); break; case WIN32_MUTEX_IMPL_CRITICALSECTION: KERNEL32_DeleteCriticalSection(&mutex->impl.critsec); break; case WIN32_MUTEX_IMPL_SRWLOCK: // STOOPID! YOU'RE SO STOOPID! break; } free(mutex); } void win32_mutex_lock(mt_mutex_t *mutex) { if (!mutex) return; switch (win32_mutex_impl) { case WIN32_MUTEX_IMPL_MUTEX: assert(WaitForSingleObject(mutex->impl.mutex, INFINITE) == WAIT_OBJECT_0); break; case WIN32_MUTEX_IMPL_CRITICALSECTION: KERNEL32_EnterCriticalSection(&mutex->impl.critsec); break; case WIN32_MUTEX_IMPL_SRWLOCK: { const DWORD id = GetCurrentThreadId(); if (mutex->impl.srwlock.owner == id) { ++mutex->impl.srwlock.count; } else { KERNEL32_AcquireSRWLockExclusive(&mutex->impl.srwlock.srw); assert(!mutex->impl.srwlock.count && !mutex->impl.srwlock.owner); mutex->impl.srwlock.count = 1; mutex->impl.srwlock.owner = id; } break; } } } void win32_mutex_unlock(mt_mutex_t *mutex) { if (!mutex) return; switch (win32_mutex_impl) { case WIN32_MUTEX_IMPL_MUTEX: assert(ReleaseMutex(mutex->impl.mutex)); break; case WIN32_MUTEX_IMPL_CRITICALSECTION: KERNEL32_LeaveCriticalSection(&mutex->impl.critsec); break; case WIN32_MUTEX_IMPL_SRWLOCK: if (mutex->impl.srwlock.owner == GetCurrentThreadId()) { if (!--mutex->impl.srwlock.count) { mutex->impl.srwlock.owner = 0; KERNEL32_ReleaseSRWLockExclusive(&mutex->impl.srwlock.srw); } } else { assert(!"You are in a maze of twisty little passages, all alike."); } break; } } /* -------------------------------------------------------------- */ // Condition variables, emulated using semaphores on systems prior // to Vista. Implementation using semaphores heavily borrowed from // the BeOS condition variable emulation, by Christopher Tate and // Owen Smith, including most comments. static void (WINAPI *KERNEL32_InitializeConditionVariable)(PCONDITION_VARIABLE); static BOOL (WINAPI *KERNEL32_SleepConditionVariableCS)(PCONDITION_VARIABLE, PCRITICAL_SECTION, DWORD); static BOOL (WINAPI *KERNEL32_SleepConditionVariableSRW)(PCONDITION_VARIABLE, PSRWLOCK, DWORD, ULONG); static void (WINAPI *KERNEL32_WakeConditionVariable)(PCONDITION_VARIABLE); static enum { WIN32_COND_IMPL_FAKE = 0, WIN32_COND_IMPL_VISTA, // CONDITION_VARIABLE API } win32_cond_impl = WIN32_COND_IMPL_FAKE; struct mt_cond { union { struct { HANDLE sem; HANDLE handshake_sem; HANDLE signal_sem; int32_t nw; // number waiting int32_t ns; // number signaled } fake; CONDITION_VARIABLE vista; } impl; }; void win32_cond_delete(mt_cond_t *cond); mt_cond_t *win32_cond_create(void) { mt_cond_t *cond = mem_calloc(1, sizeof(*cond)); switch (win32_cond_impl) { case WIN32_COND_IMPL_FAKE: cond->impl.fake.sem = CreateSemaphoreA(NULL, 0, LONG_MAX, NULL); cond->impl.fake.handshake_sem = CreateSemaphoreA(NULL, 0, LONG_MAX, NULL); cond->impl.fake.signal_sem = CreateSemaphoreA(NULL, 1, LONG_MAX, NULL); cond->impl.fake.ns = 0; cond->impl.fake.nw = 0; if (!cond->impl.fake.sem || !cond->impl.fake.handshake_sem || !cond->impl.fake.signal_sem) { win32_cond_delete(cond); return NULL; } break; case WIN32_COND_IMPL_VISTA: KERNEL32_InitializeConditionVariable(&cond->impl.vista); break; } return cond; } void win32_cond_delete(mt_cond_t *cond) { if (!cond) return; switch (win32_cond_impl) { case WIN32_COND_IMPL_FAKE: if (cond->impl.fake.sem) CloseHandle(cond->impl.fake.sem); if (cond->impl.fake.handshake_sem) CloseHandle(cond->impl.fake.handshake_sem); if (cond->impl.fake.signal_sem) CloseHandle(cond->impl.fake.signal_sem); break; case WIN32_COND_IMPL_VISTA: // LET'S SEE WHAT'S IN THE BOX! break; } free(cond); } void win32_cond_signal(mt_cond_t *cond) { if (!cond) return; switch (win32_cond_impl) { case WIN32_COND_IMPL_FAKE: // we need exclusive access to the waiter count while we figure out whether // we need a handshake with an awakening waiter thread WaitForSingleObject(cond->impl.fake.signal_sem, INFINITE); // are there waiters to be awakened? if (cond->impl.fake.nw > cond->impl.fake.ns) { // inform the next awakening waiter that we need a handshake, then release // all the locks and block until we get the handshake. We need to go through the // handshake process even if we're interrupted, to avoid breaking the CV, so we // just set the eventual return code if we are interrupted in the middle. cond->impl.fake.ns += 1; ReleaseSemaphore(cond->impl.fake.sem, 1, NULL); ReleaseSemaphore(cond->impl.fake.signal_sem, 1, NULL); WaitForSingleObject(cond->impl.fake.handshake_sem, INFINITE); } else { // nobody is waiting, so the signal operation is a no-op ReleaseSemaphore(cond->impl.fake.signal_sem, 1, NULL); } break; case WIN32_COND_IMPL_VISTA: KERNEL32_WakeConditionVariable(&cond->impl.vista); break; } } static inline SCHISM_ALWAYS_INLINE int win32_cond_wait_fake_impl_(mt_cond_t *cond, mt_mutex_t *mutex, uint32_t *timeout) { int result = 0; // validate the arguments if (!cond || !mutex) return 0; // record the fact that we're waiting on the semaphore. This action is // protected by a mutex because exclusive access to the waiter count is // needed by both waiting threads and signalling threads. If someone interrupts // us while we're waiting for the lock (e.g. by calling kill() or send_signal()), we // abort and return the appropriate failure code. WaitForSingleObject(cond->impl.fake.signal_sem, INFINITE); cond->impl.fake.nw++; ReleaseSemaphore(cond->impl.fake.signal_sem, 1, NULL); // actually wait for a signal -- we have to unlock the mutex before calling the // underlying blocking primitive. The potential preemption between unlocking // the mutex and calling acquire_sem() is why we needed to record, prior to // this point, that we're in the process of waiting on the condition variable. win32_mutex_unlock(mutex); result = (WaitForSingleObject(cond->impl.fake.sem, timeout ? *timeout : INFINITE) == WAIT_OBJECT_0); // we just awoke, either via a signal or by being interrupted. If there's // a signaller running, he'll think he needs to handshake whether or not // we actually awoke due to his signal. So, we reacquire the signalSem // mutex, and handshake if there's a positive signaller count. It's critical // that we continue with the handshake process even if we've been interrupted, // so we just set the eventual error code and proceed with the CV state // unwinding in that case. WaitForSingleObject(cond->impl.fake.signal_sem, INFINITE); if (cond->impl.fake.ns > 0) { ReleaseSemaphore(cond->impl.fake.handshake_sem, 1, NULL); cond->impl.fake.ns--; } cond->impl.fake.nw--; ReleaseSemaphore(cond->impl.fake.signal_sem, 1, NULL); // always reacquire the mutex before returning, even in error cases win32_mutex_lock(mutex); return result; } static inline SCHISM_ALWAYS_INLINE int win32_cond_wait_vista_impl_(mt_cond_t *cond, mt_mutex_t *mutex, uint32_t *timeout) { switch (win32_mutex_impl) { case WIN32_MUTEX_IMPL_SRWLOCK: { const DWORD id = GetCurrentThreadId(); if (mutex->impl.srwlock.count != 1 || mutex->impl.srwlock.owner != id) return 0; mutex->impl.srwlock.count = 0; mutex->impl.srwlock.owner = 0; int result = KERNEL32_SleepConditionVariableSRW(&cond->impl.vista, &mutex->impl.srwlock.srw, timeout ? *timeout : INFINITE, 0); // Mutex is always owned by us now assert(!mutex->impl.srwlock.count && !mutex->impl.srwlock.owner); mutex->impl.srwlock.count = 1; mutex->impl.srwlock.owner = id; return result; } case WIN32_MUTEX_IMPL_CRITICALSECTION: return KERNEL32_SleepConditionVariableCS(&cond->impl.vista, &mutex->impl.critsec, timeout ? *timeout : INFINITE); default: // Should never happen return 0; } } void win32_cond_wait(mt_cond_t *cond, mt_mutex_t *mutex) { switch (win32_cond_impl) { case WIN32_COND_IMPL_FAKE: win32_cond_wait_fake_impl_(cond, mutex, NULL); break; case WIN32_COND_IMPL_VISTA: win32_cond_wait_vista_impl_(cond, mutex, NULL); break; } } // this function is somewhat useless if we don't know whether we timed out or not void win32_cond_wait_timeout(mt_cond_t *cond, mt_mutex_t *mutex, uint32_t timeout) { switch (win32_cond_impl) { case WIN32_COND_IMPL_FAKE: win32_cond_wait_fake_impl_(cond, mutex, &timeout); break; case WIN32_COND_IMPL_VISTA: win32_cond_wait_vista_impl_(cond, mutex, &timeout); break; } } ////////////////////////////////////////////////////////////////////////////// static void *lib_kernel32 = NULL; static void *lib_kernelbase = NULL; static int win32_threads_init(void) { lib_kernel32 = loadso_object_load("KERNEL32.DLL"); lib_kernelbase = loadso_object_load("KERNELBASE.DLL"); if (lib_kernel32) { KERNEL32_AddVectoredExceptionHandler = loadso_function_load(lib_kernel32, "AddVectoredExceptionHandler"); KERNEL32_RemoveVectoredExceptionHandler = loadso_function_load(lib_kernel32, "RemoveVectoredExceptionHandler"); KERNEL32_SetThreadDescription = loadso_function_load(lib_kernel32, "SetThreadDescription"); KERNEL32_IsDebuggerPresent = loadso_function_load(lib_kernel32, "IsDebuggerPresent"); KERNEL32_InitializeCriticalSection = loadso_function_load(lib_kernel32, "InitializeCriticalSection"); KERNEL32_SetCriticalSectionSpinCount = loadso_function_load(lib_kernel32, "SetCriticalSectionSpinCount"); KERNEL32_DeleteCriticalSection = loadso_function_load(lib_kernel32, "DeleteCriticalSection"); KERNEL32_EnterCriticalSection = loadso_function_load(lib_kernel32, "EnterCriticalSection"); KERNEL32_LeaveCriticalSection = loadso_function_load(lib_kernel32, "LeaveCriticalSection"); KERNEL32_InitializeSRWLock = loadso_function_load(lib_kernel32, "InitializeSRWLock"); KERNEL32_AcquireSRWLockExclusive = loadso_function_load(lib_kernel32, "AcquireSRWLockExclusive"); KERNEL32_ReleaseSRWLockExclusive = loadso_function_load(lib_kernel32, "ReleaseSRWLockExclusive"); KERNEL32_InitializeConditionVariable = loadso_function_load(lib_kernel32, "InitializeConditionVariable"); KERNEL32_WakeConditionVariable = loadso_function_load(lib_kernel32, "WakeConditionVariable"); KERNEL32_SleepConditionVariableSRW = loadso_function_load(lib_kernel32, "SleepConditionVariableSRW"); KERNEL32_SleepConditionVariableCS = loadso_function_load(lib_kernel32, "SleepConditionVariableCS"); } else { // reset all to null. KERNEL32_AddVectoredExceptionHandler = NULL; KERNEL32_RemoveVectoredExceptionHandler = NULL; KERNEL32_SetThreadDescription = NULL; KERNEL32_IsDebuggerPresent = NULL; KERNEL32_InitializeCriticalSection = NULL; KERNEL32_SetCriticalSectionSpinCount = NULL; KERNEL32_DeleteCriticalSection = NULL; KERNEL32_EnterCriticalSection = NULL; KERNEL32_LeaveCriticalSection = NULL; KERNEL32_InitializeSRWLock = NULL; KERNEL32_AcquireSRWLockExclusive = NULL; KERNEL32_ReleaseSRWLockExclusive = NULL; KERNEL32_InitializeConditionVariable = NULL; KERNEL32_WakeConditionVariable = NULL; KERNEL32_SleepConditionVariableSRW = NULL; KERNEL32_SleepConditionVariableCS = NULL; } if (lib_kernelbase && !KERNEL32_SetThreadDescription) { KERNEL32_SetThreadDescription = loadso_function_load(lib_kernelbase, "SetThreadDescription"); } const int critsec_ok = KERNEL32_InitializeCriticalSection && KERNEL32_DeleteCriticalSection && KERNEL32_EnterCriticalSection && KERNEL32_LeaveCriticalSection; const int srwlock_ok = KERNEL32_InitializeSRWLock && KERNEL32_AcquireSRWLockExclusive && KERNEL32_ReleaseSRWLockExclusive; if (srwlock_ok) { win32_mutex_impl = WIN32_MUTEX_IMPL_SRWLOCK; } else if (critsec_ok) { win32_mutex_impl = WIN32_MUTEX_IMPL_CRITICALSECTION; } else { win32_mutex_impl = WIN32_MUTEX_IMPL_MUTEX; } if (KERNEL32_InitializeConditionVariable && KERNEL32_WakeConditionVariable) { win32_cond_impl = WIN32_COND_IMPL_VISTA; switch (win32_mutex_impl) { case WIN32_MUTEX_IMPL_SRWLOCK: if (KERNEL32_SleepConditionVariableSRW) break; SCHISM_FALLTHROUGH; case WIN32_MUTEX_IMPL_CRITICALSECTION: if (KERNEL32_SleepConditionVariableCS && critsec_ok) { // This might be srwlock, so set it to critical section to be safe win32_mutex_impl = WIN32_MUTEX_IMPL_CRITICALSECTION; break; } SCHISM_FALLTHROUGH; default: win32_cond_impl = WIN32_COND_IMPL_FAKE; break; } } else { win32_cond_impl = WIN32_COND_IMPL_FAKE; } #if 0 static const char *mutex_impl_names[] = { [WIN32_MUTEX_IMPL_MUTEX] = "Mutex", [WIN32_MUTEX_IMPL_CRITICALSECTION] = "CriticalSection", [WIN32_MUTEX_IMPL_SRWLOCK] = "SRWLock", }; static const char *cond_impl_names[] = { [WIN32_COND_IMPL_FAKE] = "emulated", [WIN32_COND_IMPL_VISTA] = "Vista", }; log_appendf(1, "WIN32: using %s mutexes and %s condition variables", mutex_impl_names[win32_mutex_impl], cond_impl_names[win32_cond_impl]); #endif return 1; } static void win32_threads_quit(void) { if (lib_kernel32) { loadso_object_unload(lib_kernel32); lib_kernel32 = NULL; } if (lib_kernelbase) { loadso_object_unload(lib_kernelbase); lib_kernelbase = NULL; } } ////////////////////////////////////////////////////////////////////////////// const schism_mt_backend_t schism_mt_backend_win32 = { .init = win32_threads_init, .quit = win32_threads_quit, .thread_create = win32_thread_create, .thread_wait = win32_thread_wait, .thread_set_priority = win32_thread_set_priority, .thread_id = win32_thread_id, .mutex_create = win32_mutex_create, .mutex_delete = win32_mutex_delete, .mutex_lock = win32_mutex_lock, .mutex_unlock = win32_mutex_unlock, .cond_create = win32_cond_create, .cond_delete = win32_cond_delete, .cond_signal = win32_cond_signal, .cond_wait = win32_cond_wait, .cond_wait_timeout = win32_cond_wait_timeout, }; schismtracker-20250313/sys/win32/osdefs.c000066400000000000000000001147741476471630300200550ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* WOW this file got ugly */ #include "headers.h" #include "config.h" #include "it.h" #include "osdefs.h" #include "fmt.h" #include "charset.h" #include "loadso.h" #include "mem.h" #include #include #include #include #include #include #include #define IDM_FILE_NEW 101 #define IDM_FILE_LOAD 102 #define IDM_FILE_SAVE_CURRENT 103 #define IDM_FILE_SAVE_AS 104 #define IDM_FILE_EXPORT 105 #define IDM_FILE_MESSAGE_LOG 106 #define IDM_FILE_QUIT 107 #define IDM_PLAYBACK_SHOW_INFOPAGE 201 #define IDM_PLAYBACK_PLAY_SONG 202 #define IDM_PLAYBACK_PLAY_PATTERN 203 #define IDM_PLAYBACK_PLAY_FROM_ORDER 204 #define IDM_PLAYBACK_PLAY_FROM_MARK_CURSOR 205 #define IDM_PLAYBACK_STOP 206 #define IDM_PLAYBACK_CALCULATE_LENGTH 207 #define IDM_SAMPLES_SAMPLE_LIST 301 #define IDM_SAMPLES_SAMPLE_LIBRARY 302 #define IDM_SAMPLES_RELOAD_SOUNDCARD 303 #define IDM_INSTRUMENTS_INSTRUMENT_LIST 401 #define IDM_INSTRUMENTS_INSTRUMENT_LIBRARY 402 #define IDM_VIEW_HELP 501 #define IDM_VIEW_VIEW_PATTERNS 502 #define IDM_VIEW_ORDERS_PANNING 503 #define IDM_VIEW_VARIABLES 504 #define IDM_VIEW_MESSAGE_EDITOR 505 #define IDM_VIEW_TOGGLE_FULLSCREEN 506 #define IDM_SETTINGS_PREFERENCES 601 #define IDM_SETTINGS_MIDI_CONFIGURATION 602 #define IDM_SETTINGS_PALETTE_EDITOR 603 #define IDM_SETTINGS_FONT_EDITOR 604 #define IDM_SETTINGS_SYSTEM_CONFIGURATION 605 // MinGW32 doesn't have nor ------ typedef int (WINAPI *DTT_CALLBACK_PROC)(HDC,LPWSTR,int,RECT*,UINT,LPARAM); typedef struct _DTTOPTS { DWORD dwSize; DWORD dwFlags; COLORREF crText; COLORREF crBorder; COLORREF crShadow; int iTextShadowType; POINT ptShadowOffset; int iBorderSize; int iFontPropId; int iColorPropId; int iStateId; BOOL fApplyOverlay; int iGlowSize; DTT_CALLBACK_PROC pfnDrawTextCallback; LPARAM lParam; } DTTOPTS, *PDTTOPTS; /* DTTOPTS.dwFlags bits */ #ifndef DTT_TEXTCOLOR #define DTT_TEXTCOLOR 0x00000001 #endif #ifndef DTT_BORDERCOLOR #define DTT_BORDERCOLOR 0x00000002 #endif #ifndef DTT_SHADOWCOLOR #define DTT_SHADOWCOLOR 0x00000004 #endif #ifndef DTT_SHADOWTYPE #define DTT_SHADOWTYPE 0x00000008 #endif #ifndef DTT_SHADOWOFFSET #define DTT_SHADOWOFFSET 0x00000010 #endif #ifndef DTT_BORDERSIZE #define DTT_BORDERSIZE 0x00000020 #endif #ifndef DTT_FONTPROP #define DTT_FONTPROP 0x00000040 #endif #ifndef DTT_COLORPROP #define DTT_COLORPROP 0x00000080 #endif #ifndef DTT_STATEID #define DTT_STATEID 0x00000100 #endif #ifndef DTT_CALCRECT #define DTT_CALCRECT 0x00000200 #endif #ifndef DTT_APPLYOVERLAY #define DTT_APPLYOVERLAY 0x00000400 #endif #ifndef DTT_GLOWSIZE #define DTT_GLOWSIZE 0x00000800 #endif #ifndef DTT_CALLBACK #define DTT_CALLBACK 0x00001000 #endif #ifndef DTT_COMPOSITED #define DTT_COMPOSITED 0x00002000 #endif #ifndef DTT_VALIDBITS #define DTT_VALIDBITS 0x00003fff #endif #ifndef ODS_SELECTED #define ODS_SELECTED 0x0001 /* Selected */ #endif #ifndef ODS_GRAYED #define ODS_GRAYED 0x0002 /* Grayed (Menus only) */ #endif #ifndef ODS_DISABLED #define ODS_DISABLED 0x0004 /* Disabled */ #endif #ifndef ODS_CHECKED #define ODS_CHECKED 0x0008 /* Checked (Menus only) */ #endif #ifndef ODS_FOCUS #define ODS_FOCUS 0x0010 /* Has focus */ #endif #ifndef ODS_DEFAULT #define ODS_DEFAULT 0x0020 /* Default */ #endif #ifndef ODS_HOTLIGHT #define ODS_HOTLIGHT 0x0040 /* Highlighted when under mouse */ #endif #ifndef ODS_INACTIVE #define ODS_INACTIVE 0x0080 /* Inactive */ #endif #ifndef ODS_NOACCEL #define ODS_NOACCEL 0x0100 /* No keyboard accelerator */ #endif #ifndef ODS_NOFOCUSRECT #define ODS_NOFOCUSRECT 0x0200 /* No focus rectangle */ #endif #ifndef ODS_COMBOBOXEDIT #define ODS_COMBOBOXEDIT 0x1000 /* Edit of a combo box */ #endif #ifndef DT_TOP #define DT_TOP 0x00000000 #endif #ifndef DT_LEFT #define DT_LEFT 0x00000000 #endif #ifndef DT_CENTER #define DT_CENTER 0x00000001 #endif #ifndef DT_RIGHT #define DT_RIGHT 0x00000002 #endif #ifndef DT_VCENTER #define DT_VCENTER 0x00000004 #endif #ifndef DT_BOTTOM #define DT_BOTTOM 0x00000008 #endif #ifndef DT_WORDBREAK #define DT_WORDBREAK 0x00000010 #endif #ifndef DT_SINGLELINE #define DT_SINGLELINE 0x00000020 #endif #ifndef DT_EXPANDTABS #define DT_EXPANDTABS 0x00000040 #endif #ifndef DT_TABSTOP #define DT_TABSTOP 0x00000080 #endif #ifndef DT_NOCLIP #define DT_NOCLIP 0x00000100 #endif #ifndef DT_EXTERNALLEADING #define DT_EXTERNALLEADING 0x00000200 #endif #ifndef DT_CALCRECT #define DT_CALCRECT 0x00000400 #endif #ifndef DT_NOPREFIX #define DT_NOPREFIX 0x00000800 #endif #ifndef DT_INTERNAL #define DT_INTERNAL 0x00001000 #endif #ifndef DT_EDITCONTROL #define DT_EDITCONTROL 0x00002000 #endif #ifndef DT_PATH_ELLIPSIS #define DT_PATH_ELLIPSIS 0x00004000 #endif #ifndef DT_END_ELLIPSIS #define DT_END_ELLIPSIS 0x00008000 #endif #ifndef DT_MODIFYSTRING #define DT_MODIFYSTRING 0x00010000 #endif #ifndef DT_RTLREADING #define DT_RTLREADING 0x00020000 #endif #ifndef DT_WORD_ELLIPSIS #define DT_WORD_ELLIPSIS 0x00040000 #endif #ifndef DT_NOFULLWIDTHCHARBREAK #define DT_NOFULLWIDTHCHARBREAK 0x00080000 #endif #ifndef DT_HIDEPREFIX #define DT_HIDEPREFIX 0x00100000 #endif #ifndef DT_PREFIXONLY #define DT_PREFIXONLY 0x00200000 #endif enum BARITEMSTATES { MBI_NORMAL = 1, MBI_HOT = 2, MBI_PUSHED = 3, MBI_DISABLED = 4, MBI_DISABLEDHOT = 5, MBI_DISABLEDPUSHED = 6, }; enum MENUPARTS { MENU_MENUITEM_TMSCHEMA = 1, MENU_MENUDROPDOWN_TMSCHEMA = 2, MENU_MENUBARITEM_TMSCHEMA = 3, MENU_MENUBARDROPDOWN_TMSCHEMA = 4, MENU_CHEVRON_TMSCHEMA = 5, MENU_SEPARATOR_TMSCHEMA = 6, MENU_BARBACKGROUND = 7, MENU_BARITEM = 8, MENU_POPUPBACKGROUND = 9, MENU_POPUPBORDERS = 10, MENU_POPUPCHECK = 11, MENU_POPUPCHECKBACKGROUND = 12, MENU_POPUPGUTTER = 13, MENU_POPUPITEM = 14, MENU_POPUPSEPARATOR = 15, MENU_POPUPSUBMENU = 16, MENU_SYSTEMCLOSE = 17, MENU_SYSTEMMAXIMIZE = 18, MENU_SYSTEMMINIMIZE = 19, MENU_SYSTEMRESTORE = 20, }; // This sucks #define HTHEME HANDLE // -------------------------------------------------------- typedef enum { WCA_UNDEFINED = 0, WCA_USEDARKMODECOLORS = 26, WCA_LAST = 27 } WINDOWCOMPOSITIONATTRIB; typedef struct { WINDOWCOMPOSITIONATTRIB Attrib; PVOID pvData; SIZE_T cbData; } WINDOWCOMPOSITIONATTRIBDATA; /* global menu object */ static HMENU menu = NULL; /* used for dark theme crap. */ static void *lib_dwmapi = NULL; static HRESULT (WINAPI *DWMAPI_DwmSetWindowAttribute)(HWND hwnd, DWORD key, LPCVOID data, DWORD sz_data) = NULL; static void *lib_user32 = NULL; static BOOL (WINAPI *USER32_SetWindowCompositionAttribute)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *) = NULL; static BOOL (WINAPI *USER32_GetMenuItemInfoW)(HMENU, UINT, BOOL, LPMENUITEMINFOW); static BOOL (WINAPI *USER32_GetMenuBarInfo)(HWND, LONG, LONG, PMENUBARINFO); /* `bool`, which is 1 byte, call that `unsigned char` ;) */ static void *lib_uxtheme = NULL; static unsigned char (WINAPI *UXTHEME_ShouldAppsUseDarkMode)(void) = NULL; static unsigned char (WINAPI *UXTHEME_AllowDarkModeForWindow)(HWND hwnd, unsigned char allow) = NULL; static void (WINAPI *UXTHEME_AllowDarkModeForApp)(unsigned char allow) = NULL; // v1809 static DWORD (WINAPI *UXTHEME_SetPreferredAppMode)(DWORD app_mode) = NULL; // v1903 static HTHEME (WINAPI *UXTHEME_OpenThemeData)(HWND hwnd, LPCWSTR pszClassList) = NULL; static HRESULT (WINAPI *UXTHEME_DrawThemeTextEx)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, LPCWSTR pszText, int cchText, DWORD dwTextFlags, LPRECT pRect, const DTTOPTS *pOptions) = NULL; static void (WINAPI *UXTHEME_RefreshImmersiveColorPolicyState)(void) = NULL; #define APPMODE_DEFAULT 0 #define APPMODE_ALLOWDARK 1 #define APPMODE_FORCEDARK 2 #define APPMODE_FORCELIGHT 3 static void *lib_ntdll = NULL; static long /*NTSTATUS*/ (WINAPI *NTDLL_RtlGetVersion)(OSVERSIONINFOEXW *info) = NULL; // Set on video startup. static int win32_dark_mode_enabled = 0; #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE_OLD # define DWMWA_USE_IMMERSIVE_DARK_MODE_OLD 19 #endif #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE # define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif void win32_get_modkey(schism_keymod_t *mk) { // Translation from virtual keys to keymods. We have to do // this because SDL's key modifier stuff is quite buggy // and has caused weird modifier shenanigans in the past. static const struct { uint8_t vk; schism_keymod_t km; // whether this key is a held modifier (i.e. // ctrl, alt, shift, win) or is toggled (i.e. // numlock, scrolllock) int toggle; // does this key work on win9x? int win9x; } conv[] = { {VK_NUMLOCK, SCHISM_KEYMOD_NUM, 1, 1}, {VK_CAPITAL, SCHISM_KEYMOD_CAPS, 1, 1}, {VK_CAPITAL, SCHISM_KEYMOD_CAPS_PRESSED, 0, 1}, {VK_LSHIFT, SCHISM_KEYMOD_LSHIFT, 0, 0}, {VK_RSHIFT, SCHISM_KEYMOD_RSHIFT, 0, 0}, {VK_LMENU, SCHISM_KEYMOD_LALT, 0, 0}, {VK_RMENU, SCHISM_KEYMOD_RALT, 0, 0}, {VK_LCONTROL, SCHISM_KEYMOD_LCTRL, 0, 0}, {VK_RCONTROL, SCHISM_KEYMOD_RCTRL, 0, 0}, {VK_LWIN, SCHISM_KEYMOD_LGUI, 0, 0}, {VK_RWIN, SCHISM_KEYMOD_RGUI, 0, 0}, }; const int on_windows_9x = (GetVersion() & UINT32_C(0x80000000)); // Sometimes GetKeyboardState is out of date and calling GetKeyState // fixes it. Any random key will work. (void)GetKeyState(VK_CAPITAL); BYTE ks[256] = {0}; if (!GetKeyboardState(ks)) return; for (int i = 0; i < ARRAY_SIZE(conv); i++) { // FIXME: Some keys (notably left and right variations) simply // do not get filled by windows 9x and as such get completely // ignored by this code. In that case just use whatever values // SDL gave and punt. if (on_windows_9x && !conv[i].win9x) continue; // Clear the original value (*mk) &= ~(conv[i].km); // Put in our result if (ks[conv[i].vk] & (conv[i].toggle ? 0x01 : 0x80)) (*mk) |= conv[i].km; } } void win32_sysinit(SCHISM_UNUSED int *pargc, SCHISM_UNUSED char ***pargv) { /* Initialize winsocks */ static WSADATA ignored = {0}; if (WSAStartup(0x202, &ignored) == SOCKET_ERROR) { WSACleanup(); /* ? */ status.flags |= NO_NETWORK; } /* Build the menus */ menu = CreateMenu(); { HMENU file = CreatePopupMenu(); AppendMenuA(file, MF_STRING, IDM_FILE_NEW, "&New\tCtrl+N"); AppendMenuA(file, MF_STRING, IDM_FILE_LOAD, "&Load\tF9"); AppendMenuA(file, MF_STRING, IDM_FILE_SAVE_CURRENT, "&Save Current\tCtrl+S"); AppendMenuA(file, MF_STRING, IDM_FILE_SAVE_AS, "Save &As...\tF10"); AppendMenuA(file, MF_STRING, IDM_FILE_EXPORT, "&Export...\tShift+F10"); AppendMenuA(file, MF_STRING, IDM_FILE_MESSAGE_LOG, "&Message Log\tCtrl+F11"); AppendMenuA(file, MF_SEPARATOR, 0, NULL); AppendMenuA(file, MF_STRING, IDM_FILE_QUIT, "&Quit\tCtrl+Q"); AppendMenuA(menu, MF_POPUP, (uintptr_t)file, "&File"); } { /* this is equivalent to the "Schism Tracker" menu on Mac OS X */ HMENU view = CreatePopupMenu(); AppendMenuA(view, MF_STRING, IDM_VIEW_HELP, "Help\tF1"); AppendMenuA(view, MF_SEPARATOR, 0, NULL); AppendMenuA(view, MF_STRING, IDM_VIEW_VIEW_PATTERNS, "View Patterns\tF2"); AppendMenuA(view, MF_STRING, IDM_VIEW_ORDERS_PANNING, "Orders/Panning\tF11"); AppendMenuA(view, MF_STRING, IDM_VIEW_VARIABLES, "Variables\tF12"); AppendMenuA(view, MF_STRING, IDM_VIEW_MESSAGE_EDITOR, "Message Editor\tShift+F9"); AppendMenuA(view, MF_SEPARATOR, 0, NULL); AppendMenuA(view, MF_STRING, IDM_VIEW_TOGGLE_FULLSCREEN, "Toggle Fullscreen\tCtrl+Alt+Return"); AppendMenuA(menu, MF_POPUP, (uintptr_t)view, "&View"); } { HMENU playback = CreatePopupMenu(); AppendMenuA(playback, MF_STRING, IDM_PLAYBACK_SHOW_INFOPAGE, "Show Infopage\tF5"); AppendMenuA(playback, MF_STRING, IDM_PLAYBACK_PLAY_SONG, "Play Song\tCtrl+F5"); AppendMenuA(playback, MF_STRING, IDM_PLAYBACK_PLAY_PATTERN, "Play Pattern\tF6"); AppendMenuA(playback, MF_STRING, IDM_PLAYBACK_PLAY_FROM_ORDER, "Play from Order\tShift+F6"); AppendMenuA(playback, MF_STRING, IDM_PLAYBACK_PLAY_FROM_MARK_CURSOR, "Play from Mark/Cursor\tF7"); AppendMenuA(playback, MF_STRING, IDM_PLAYBACK_STOP, "Stop\tF8"); AppendMenuA(playback, MF_STRING, IDM_PLAYBACK_CALCULATE_LENGTH, "Calculate Length\tCtrl+P"); AppendMenuA(menu, MF_POPUP, (uintptr_t)playback, "&Playback"); } { HMENU samples = CreatePopupMenu(); AppendMenuA(samples, MF_STRING, IDM_SAMPLES_SAMPLE_LIST, "&Sample List\tF3"); AppendMenuA(samples, MF_STRING, IDM_SAMPLES_SAMPLE_LIBRARY, "Sample &Library\tShift+F3"); AppendMenuA(samples, MF_STRING, IDM_SAMPLES_RELOAD_SOUNDCARD, "&Reload Soundcard\tCtrl+G"); AppendMenuA(menu, MF_POPUP, (uintptr_t)samples, "&Samples"); } { HMENU instruments = CreatePopupMenu(); AppendMenuA(instruments, MF_STRING, IDM_INSTRUMENTS_INSTRUMENT_LIST, "Instrument List\tF4"); AppendMenuA(instruments, MF_STRING, IDM_INSTRUMENTS_INSTRUMENT_LIBRARY, "Instrument Library\tShift+F4"); AppendMenuA(menu, MF_POPUP, (uintptr_t)instruments, "&Instruments"); } { HMENU settings = CreatePopupMenu(); AppendMenuA(settings, MF_STRING, IDM_SETTINGS_PREFERENCES, "Preferences\tShift+F5"); AppendMenuA(settings, MF_STRING, IDM_SETTINGS_MIDI_CONFIGURATION, "MIDI Configuration\tShift+F1"); AppendMenuA(settings, MF_STRING, IDM_SETTINGS_PALETTE_EDITOR, "Palette Editor\tCtrl+F12"); AppendMenuA(settings, MF_STRING, IDM_SETTINGS_FONT_EDITOR, "Font Editor\tShift+F12"); AppendMenuA(settings, MF_STRING, IDM_SETTINGS_SYSTEM_CONFIGURATION, "System Configuration\tCtrl+F1"); AppendMenuA(menu, MF_POPUP, (uintptr_t)settings, "S&ettings"); } #ifdef USE_MEDIAFOUNDATION win32mf_init(); #endif lib_ntdll = loadso_object_load("ntdll.dll"); if (lib_ntdll) { NTDLL_RtlGetVersion = loadso_function_load(lib_ntdll, "RtlGetVersion"); } // Load dark mode cruft if (win32_ntver_atleast(10, 0, 17763)) { lib_dwmapi = loadso_object_load("dwmapi.dll"); if (lib_dwmapi) { DWMAPI_DwmSetWindowAttribute = loadso_function_load(lib_dwmapi, "DwmSetWindowAttribute"); } lib_uxtheme = loadso_object_load("uxtheme.dll"); if (lib_uxtheme) { // Just in case MS decides to actually export these functions, I'm attempting // to load functions by name before trying exact ordinals. #define LOAD_UNDOCUMENTED_FUNC(name, ordinal) \ do { \ UXTHEME_##name = loadso_function_load(lib_uxtheme, #name); \ if (!UXTHEME_##name) UXTHEME_##name = loadso_function_load(lib_uxtheme, MAKEINTRESOURCEA(ordinal)); \ } while (0) LOAD_UNDOCUMENTED_FUNC(RefreshImmersiveColorPolicyState, 104); LOAD_UNDOCUMENTED_FUNC(ShouldAppsUseDarkMode, 132); LOAD_UNDOCUMENTED_FUNC(AllowDarkModeForWindow, 133); UXTHEME_OpenThemeData = loadso_function_load(lib_uxtheme, "OpenThemeData"); UXTHEME_DrawThemeTextEx = loadso_function_load(lib_uxtheme, "DrawThemeTextEx"); // SDL 3 enables dark mode, even when we don't want it. if (win32_ntver_atleast(10, 0, 18362)) { LOAD_UNDOCUMENTED_FUNC(SetPreferredAppMode, 135); } else { LOAD_UNDOCUMENTED_FUNC(AllowDarkModeForApp, 135); } #undef LOAD_UNDOCUMENTED_FUNC } lib_user32 = loadso_object_load("user32.dll"); if (lib_user32) { USER32_SetWindowCompositionAttribute = loadso_function_load(lib_user32, "SetWindowCompositionAttribute"); USER32_GetMenuItemInfoW = loadso_function_load(lib_user32, "GetMenuItemInfoW"); USER32_GetMenuBarInfo = loadso_function_load(lib_user32, "GetMenuBarInfo"); } } /* Convert command line arguments to UTF-8 */ { char **utf8_argv; int utf8_argc; int i; // Windows NT: use Unicode arguments if available LPWSTR cmdline = GetCommandLineW(); if (cmdline) { LPWSTR *argvw = CommandLineToArgvW(cmdline, &utf8_argc); if (argvw) { // now we have Unicode arguments, so convert them to UTF-8 utf8_argv = mem_alloc(sizeof(char *) * utf8_argc); for (i = 0; i < utf8_argc; i++) { charset_iconv(argvw[i], &utf8_argv[i], CHARSET_WCHAR_T, CHARSET_CHAR, SIZE_MAX); if (!utf8_argv[i]) utf8_argv[i] = str_dup(""); // ... } LocalFree(argvw); goto have_utf8_args; } } // well, that didn't work, fallback to ANSI. utf8_argc = *pargc; utf8_argv = mem_alloc(sizeof(char *) * utf8_argc); for (i = 0; i < utf8_argc; i++) { charset_iconv((*pargv)[i], &utf8_argv[i], CHARSET_ANSI, CHARSET_CHAR, SIZE_MAX); if (!utf8_argv[i]) utf8_argv[i] = str_dup(""); // ... } have_utf8_args: ; *pargv = utf8_argv; *pargc = utf8_argc; } } void win32_sysexit(void) { #ifdef USE_MEDIAFOUNDATION win32mf_quit(); #endif if (lib_dwmapi) loadso_object_unload(lib_dwmapi); if (lib_uxtheme) loadso_object_unload(lib_uxtheme); if (lib_ntdll) loadso_object_unload(lib_ntdll); if (lib_user32) loadso_object_unload(lib_user32); } static LRESULT (CALLBACK *old_wndproc)(HWND, UINT, WPARAM, LPARAM) = NULL; // // Hijacked from wxWidgets // Copyright (c) 2022 Vadim Zeitlin // static LRESULT CALLBACK win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (!win32_dark_mode_enabled) return old_wndproc(hwnd, msg, wparam, lparam); #ifndef WM_MENUBAR_DRAWMENUITEM # define WM_MENUBAR_DRAWMENUITEM 0x92 #endif #ifndef WM_MENUBAR_DRAWMENU # define WM_MENUBAR_DRAWMENU 0x91 #endif // This is passed via LPARAM of WM_MENUBAR_DRAWMENU. struct MenuBarDrawMenu { HMENU hmenu; HDC hdc; DWORD dwReserved; }; struct MenuBarMenuItem { int iPosition; // There are more fields in this (undocumented) struct but we don't // currently need them, so don't bother with declaring them. }; struct MenuBarDrawMenuItem { DRAWITEMSTRUCT dis; struct MenuBarDrawMenu mbdm; struct MenuBarMenuItem mbmi; }; switch (msg) { case WM_NCPAINT: case WM_NCACTIVATE: { // Drawing the menu bar background in WM_MENUBAR_DRAWMENU somehow // leaves a single pixel line unpainted (and increasing the size of // the rectangle doesn't help, i.e. drawing is clipped to an area // which is one pixel too small), so we have to draw over it here // to get rid of it. LRESULT result = DefWindowProc(hwnd, msg, wparam, lparam); // Create a RECT one pixel above the client area: note that we // have to use window (and not client) coordinates for this as // this is outside of the client area of the window. RECT rcWindow, rc; if (!GetWindowRect(hwnd, &rcWindow) || !GetClientRect(hwnd, &rc)) break; // Convert client coordinates to window ones. MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)&rc, 2); OffsetRect(&rc, -rcWindow.left, -rcWindow.top); rc.bottom = rc.top; rc.top--; HDC hdc = GetWindowDC(hwnd); HBRUSH hbr = CreateSolidBrush(0x2B2B2B); FillRect(hdc, &rc, hbr); DeleteObject(hbr); ReleaseDC(hwnd, hdc); return result; } case WM_MENUBAR_DRAWMENU: { // Erase the menu bar background using custom brush. struct MenuBarDrawMenu *drawmenu = (struct MenuBarDrawMenu *)lparam; if (drawmenu) { MENUBARINFO mbi = {.cbSize = sizeof(MENUBARINFO)}; if (!USER32_GetMenuBarInfo || !USER32_GetMenuBarInfo(hwnd, OBJID_MENU, 0, &mbi)) break; RECT rcWindow; if (!GetWindowRect(hwnd, &rcWindow)) break; // rcBar is expressed in screen coordinates. OffsetRect(&mbi.rcBar, -rcWindow.left, -rcWindow.top); HBRUSH hbr = CreateSolidBrush(0x2B2B2B); FillRect(drawmenu->hdc, &mbi.rcBar, hbr); DeleteObject(hbr); } return 1; } case WM_MENUBAR_DRAWMENUITEM: { struct MenuBarDrawMenuItem *drawmenuitem = (struct MenuBarDrawMenuItem *)lparam; if (drawmenuitem) { // Just a sanity check. if (drawmenuitem->dis.CtlType != ODT_MENU) break; WCHAR buf[256]; MENUITEMINFOW mii = { .cbSize = sizeof(MENUITEMINFOW), .fMask = MIIM_STRING, .dwTypeData = buf, .cch = sizeof(buf) - 2, }; // Note that we need to use the iPosition field of the // undocumented struct here because DRAWITEMSTRUCT::itemID is // not initialized in the struct passed to us here, so this is // the only way to identify the item we're dealing with. if (!USER32_GetMenuItemInfoW || !USER32_GetMenuItemInfoW((HMENU)drawmenuitem->dis.hwndItem, drawmenuitem->mbmi.iPosition, TRUE, &mii)) break; const UINT itemState = drawmenuitem->dis.itemState; int partState; uint32_t colText = 0xFFFFFF, colBg = 0x2b2b2b; if (itemState & ODS_INACTIVE) { partState = MBI_DISABLED; colText = 0x6D6D6D; } else if ((itemState & (ODS_GRAYED|ODS_HOTLIGHT)) == (ODS_GRAYED|ODS_HOTLIGHT)) { partState = MBI_DISABLEDHOT; } else if (itemState & ODS_GRAYED ) { partState = MBI_DISABLED; colText = 0x6D6D6D; } else if (itemState & (ODS_HOTLIGHT | ODS_SELECTED)) { partState = MBI_HOT; colBg = 0x414141; } else { partState = MBI_NORMAL; } // Don't use DrawThemeBackground() here, as it doesn't use the // correct colours in the dark mode, at least not when using // the "Menu" theme. { HBRUSH hbr = CreateSolidBrush(colBg); FillRect(drawmenuitem->dis.hDC, &drawmenuitem->dis.rcItem, hbr); DeleteObject(hbr); } // We have to specify the text colour explicitly as by default // black would be used, making the menu label unreadable on the // (almost) black background. DTTOPTS textOpts; textOpts.dwSize = sizeof(textOpts); textOpts.dwFlags = DTT_TEXTCOLOR; textOpts.crText = colText; DWORD drawTextFlags = DT_CENTER | DT_SINGLELINE | DT_VCENTER; if (itemState & ODS_NOACCEL) drawTextFlags |= DT_HIDEPREFIX; HTHEME theme = UXTHEME_OpenThemeData(hwnd, L"Menu"); UXTHEME_DrawThemeTextEx(theme, drawmenuitem->dis.hDC, MENU_BARITEM, partState, buf, mii.cch, drawTextFlags, &drawmenuitem->dis.rcItem, &textOpts); } return 0; } default: break; } return old_wndproc(hwnd, msg, wparam, lparam); } int win32_event(schism_event_t *event) { if (event->type == SCHISM_EVENT_WM_MSG) { if (event->wm_msg.subsystem != SCHISM_WM_MSG_SUBSYSTEM_WINDOWS) return 1; if (event->wm_msg.msg.win.msg == WM_COMMAND) { schism_event_t e = {0}; e.type = SCHISM_EVENT_NATIVE_SCRIPT; switch (LOWORD(event->wm_msg.msg.win.wparam)) { case IDM_FILE_NEW: e.script.which = str_dup("new"); break; case IDM_FILE_LOAD: e.script.which = str_dup("load"); break; case IDM_FILE_SAVE_CURRENT: e.script.which = str_dup("save"); break; case IDM_FILE_SAVE_AS: e.script.which = str_dup("save_as"); break; case IDM_FILE_EXPORT: e.script.which = str_dup("export_song"); break; case IDM_FILE_MESSAGE_LOG: e.script.which = str_dup("logviewer"); break; case IDM_FILE_QUIT: e.type = SCHISM_QUIT; break; case IDM_PLAYBACK_SHOW_INFOPAGE: e.script.which = str_dup("info"); break; case IDM_PLAYBACK_PLAY_SONG: e.script.which = str_dup("play"); break; case IDM_PLAYBACK_PLAY_PATTERN: e.script.which = str_dup("play_pattern"); break; case IDM_PLAYBACK_PLAY_FROM_ORDER: e.script.which = str_dup("play_order"); break; case IDM_PLAYBACK_PLAY_FROM_MARK_CURSOR: e.script.which = str_dup("play_mark"); break; case IDM_PLAYBACK_STOP: e.script.which = str_dup("stop"); break; case IDM_PLAYBACK_CALCULATE_LENGTH: e.script.which = str_dup("calc_length"); break; case IDM_SAMPLES_SAMPLE_LIST: e.script.which = str_dup("sample_page"); break; case IDM_SAMPLES_SAMPLE_LIBRARY: e.script.which = str_dup("sample_library"); break; case IDM_SAMPLES_RELOAD_SOUNDCARD: e.script.which = str_dup("init_sound"); break; case IDM_INSTRUMENTS_INSTRUMENT_LIST: e.script.which = str_dup("inst_page"); break; case IDM_INSTRUMENTS_INSTRUMENT_LIBRARY: e.script.which = str_dup("inst_library"); break; case IDM_VIEW_HELP: e.script.which = str_dup("help"); break; case IDM_VIEW_VIEW_PATTERNS: e.script.which = str_dup("pattern"); break; case IDM_VIEW_ORDERS_PANNING: e.script.which = str_dup("orders"); break; case IDM_VIEW_VARIABLES: e.script.which = str_dup("variables"); break; case IDM_VIEW_MESSAGE_EDITOR: e.script.which = str_dup("message_edit"); break; case IDM_VIEW_TOGGLE_FULLSCREEN: e.script.which = str_dup("fullscreen"); break; case IDM_SETTINGS_PREFERENCES: e.script.which = str_dup("preferences"); break; case IDM_SETTINGS_MIDI_CONFIGURATION: e.script.which = str_dup("midi_config"); break; case IDM_SETTINGS_PALETTE_EDITOR: e.script.which = str_dup("palette_page"); break; case IDM_SETTINGS_FONT_EDITOR: e.script.which = str_dup("font_editor"); break; case IDM_SETTINGS_SYSTEM_CONFIGURATION: e.script.which = str_dup("system_config"); break; default: return 0; } *event = e; return 1; } else if (event->wm_msg.msg.win.msg == WM_DROPFILES) { /* Drag and drop support */ schism_event_t e = {0}; e.type = SCHISM_DROPFILE; HDROP drop = (HDROP)event->wm_msg.msg.win.wparam; #ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & UINT32_C(0x80000000)) { int needed = DragQueryFileA(drop, 0, NULL, 0); char *f = mem_alloc((needed + 1) * sizeof(char)); DragQueryFileA(drop, 0, f, needed); f[needed] = 0; charset_iconv(f, &e.drop.file, CHARSET_ANSI, CHARSET_CHAR, needed + 1); } else #endif { int needed = DragQueryFileW(drop, 0, NULL, 0); wchar_t *f = mem_alloc((needed + 1) * sizeof(wchar_t)); DragQueryFileW(drop, 0, f, needed + 1); f[needed] = 0; charset_iconv(f, &e.drop.file, CHARSET_WCHAR_T, CHARSET_CHAR, (needed + 1) * sizeof(wchar_t)); } if (!e.drop.file) return 0; *event = e; return 1; } return 0; } else if (event->type == SCHISM_KEYDOWN || event->type == SCHISM_KEYUP) { // We get bogus keydowns for Ctrl-Pause. // As a workaround, we can check what Windows thinks, but only for Right Ctrl. // Left Ctrl just gets completely ignored and there's nothing we can do about it. if (event->key.sym == SCHISM_KEYSYM_SCROLLLOCK && (event->key.mod & SCHISM_KEYMOD_RCTRL) && !(GetKeyState(VK_SCROLL) & 0x80)) event->key.sym = SCHISM_KEYSYM_PAUSE; return 1; } return 1; } // TODO: Check for changes in theme settings. static inline SCHISM_ALWAYS_INLINE void win32_toggle_dark_title_bar(void *window, int on) { // wow win32_dark_mode_enabled = (on && (UXTHEME_ShouldAppsUseDarkMode && UXTHEME_ShouldAppsUseDarkMode())); const BOOL b = win32_dark_mode_enabled; if (DWMAPI_DwmSetWindowAttribute) { // Initialize dark theme on title bar. 20 is used on newer versions of Windows 10, // but 19 was used before they switched to 20 for some reason. if (FAILED(DWMAPI_DwmSetWindowAttribute((HWND)window, 20, &b, sizeof(b)))) DWMAPI_DwmSetWindowAttribute((HWND)window, 19, &b, sizeof(b)); } if (UXTHEME_AllowDarkModeForWindow) { UXTHEME_AllowDarkModeForWindow((HWND)window, on); } if (win32_ntver_atleast(10, 0, 18362)) { BOOL v = b; // this value isn't const ? if (USER32_SetWindowCompositionAttribute) { WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &v, sizeof(v) }; USER32_SetWindowCompositionAttribute((HWND)window, &data); } } else if (win32_ntver_atleast(10, 0, 17763)) { SetPropW((HWND)window, L"UseImmersiveDarkModeColors", (HANDLE)(INT_PTR)on); } if (UXTHEME_SetPreferredAppMode) // 1904 UXTHEME_SetPreferredAppMode(b ? APPMODE_FORCEDARK : APPMODE_FORCELIGHT); else if (UXTHEME_AllowDarkModeForApp) // old win10 UXTHEME_AllowDarkModeForApp(b); if (UXTHEME_RefreshImmersiveColorPolicyState) UXTHEME_RefreshImmersiveColorPolicyState(); } void win32_toggle_menu(void *window, int on) { // hax static int init = 0; SetMenu((HWND)window, (cfg_video_want_menu_bar && on) ? menu : NULL); DrawMenuBar((HWND)window); if (!init) { init = 1; // Enable Dark Mode support on Windows 10 >= 1809 if (win32_ntver_atleast(10, 0, 17763)) { const BOOL unicode = IsWindowUnicode((HWND)window); win32_toggle_dark_title_bar(window, 1); old_wndproc = (WNDPROC)(unicode ? GetWindowLongPtrW : GetWindowLongPtrA)((HWND)window, GWLP_WNDPROC); (void)(unicode ? SetWindowLongPtrW : SetWindowLongPtrA)((HWND)window, GWLP_WNDPROC, (LONG_PTR)win32_wndproc); } else { // SDL 3 sets this to true, even on older versions, which means the // color of the menu bar and title bar clash. Reset it to zero. win32_toggle_dark_title_bar(window, 0); } } } /* -------------------------------------------------------------------- */ void win32_show_message_box(const char *title, const char *text) { #ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & UINT32_C(0x80000000)) { char *title_a = NULL, *text_a = NULL; if (!charset_iconv(title, &title_a, CHARSET_UTF8, CHARSET_ANSI, SIZE_MAX) && !charset_iconv(text, &text_a, CHARSET_UTF8, CHARSET_ANSI, SIZE_MAX)) MessageBoxA(NULL, text_a, title_a, MB_OK | MB_ICONINFORMATION); free(title_a); free(text_a); } else #endif { wchar_t *title_w = NULL, *text_w = NULL; if (!charset_iconv(title, &title_w, CHARSET_UTF8, CHARSET_WCHAR_T, SIZE_MAX) && !charset_iconv(text, &text_w, CHARSET_UTF8, CHARSET_WCHAR_T, SIZE_MAX)) MessageBoxW(NULL, text_w, title_w, MB_OK | MB_ICONINFORMATION); free(title_w); free(text_w); } } /* -------------------------------------------------------------------- */ /* Key repeat */ int win32_get_key_repeat(int *pdelay, int *prate) { DWORD speed, delay; if (!SystemParametersInfoA(SPI_GETKEYBOARDSPEED, 0, &speed, 0) || !SystemParametersInfoA(SPI_GETKEYBOARDDELAY, 0, &delay, 0)) return 0; // Sanity check if (speed > 31 || delay > 3) return 0; // This value is somewhat odd; it's a value from // 0 - 31, and even weirder is that it's non-linear, // that is, 0 is about a repeat every 400 ms and 31 // is a repeat every ~33.33 ms. // // Eventually I came up with this formula to translate // it to the repeat rate in milliseconds. *prate = (int)(1000.0/((speed/(62.0/55.0)) + 2.5)); // This one is much simpler. *pdelay = (delay + 1) * 250; return 1; } /* -------------------------------------------------------------------- */ // Checks for at least some NT kernel version. // Calls NTDLL.DLL directly because Microsoft artificially // caps GetVersion() to Windows 8.1 for some reason. int win32_ntver_atleast(int major, int minor, int build) { // why the hell do they have to make this so difficult? union { OSVERSIONINFOA a; OSVERSIONINFOEXW w; } ver; ver.w.dwOSVersionInfoSize = sizeof(ver.w); if (NTDLL_RtlGetVersion && !NTDLL_RtlGetVersion(&ver.w)) { return SCHISM_SEMVER_ATLEAST(major, minor, build, ver.w.dwMajorVersion, ver.w.dwMinorVersion, ver.w.dwBuildNumber); } // fallback to GetVersionExA ver.a.dwOSVersionInfoSize = sizeof(ver.a); if (GetVersionExA(&ver.a) && ver.a.dwPlatformId == VER_PLATFORM_WIN32_NT) return SCHISM_SEMVER_ATLEAST(major, minor, build, ver.a.dwMajorVersion, ver.a.dwMinorVersion, ver.a.dwBuildNumber); // Probably win9x or something. return 0; } /* -------------------------------------------------------------------- */ // By default, waveout devices are limited to 31 chars, which means we get // lovely device names like "Headphones (USB-C to 3.5mm Head". // // Doing this gives us access to longer and more "general" devices names, // such as "G432 Gaming Headset", but only if the device supports it! static int win32_audio_lookup_device_name_registry_(const void *nameguid, char **result) { // format for printing GUIDs with printf #define GUIDF "%08" PRIx32 "-%04" PRIx16 "-%04" PRIx16 "-%02" PRIx8 "%02" PRIx8 "-%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 #define GUIDX(x) (x).Data1, (x).Data2, (x).Data3, (x).Data4[0], (x).Data4[1], (x).Data4[2], (x).Data4[3], (x).Data4[4], (x).Data4[5], (x).Data4[6], (x).Data4[7] // Set this to NULL before doing anything *result = NULL; WCHAR *strw = NULL; DWORD len = 0; static const GUID nullguid = {0}; if (!memcmp(nameguid, &nullguid, sizeof(nullguid))) return 0; { HKEY hkey; DWORD type; WCHAR keystr[256] = {0}; _snwprintf(keystr, ARRAY_SIZE(keystr) - 1, L"System\\CurrentControlSet\\Control\\MediaCategories\\{" GUIDF "}", GUIDX(*(const GUID *)nameguid)); if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, keystr, 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) return 0; if (RegQueryValueExW(hkey, L"Name", NULL, &type, NULL, &len) != ERROR_SUCCESS || type != REG_SZ) { RegCloseKey(hkey); return 0; } strw = mem_alloc(len + sizeof(WCHAR)); if (RegQueryValueExW(hkey, L"Name", NULL, NULL, (LPBYTE)strw, &len) != ERROR_SUCCESS) { RegCloseKey(hkey); free(strw); return 0; } RegCloseKey(hkey); } // force NUL terminate strw[len >> 1] = L'\0'; if (charset_iconv(strw, result, CHARSET_WCHAR_T, CHARSET_UTF8, len + sizeof(WCHAR))) { free(strw); return 0; } free(strw); return 1; #undef GUIDF #undef GUIDX } // win32_audio_lookup_device_name: look up a device name based on // some given data. // "nameguid" is a directsound device GUID. // "waveoutdevid" is a waveout device ID, which is looked up in the // directsound DLL to find a long device name if provided int win32_audio_lookup_device_name(const void *nameguid, const uint32_t *waveoutdevid, char **result) { if (nameguid && win32_audio_lookup_device_name_registry_(nameguid, result)) return 1; if (waveoutdevid && win32_dsound_audio_lookup_waveout_name(waveoutdevid, result)) return 1; return 0; } /* -------------------------------------------------------------------- */ static inline SCHISM_ALWAYS_INLINE void win32_stat_conv(struct _stat *mst, struct stat *st) { st->st_gid = mst->st_gid; st->st_atime = mst->st_atime; st->st_ctime = mst->st_ctime; st->st_dev = mst->st_dev; st->st_ino = mst->st_ino; st->st_mode = mst->st_mode; st->st_mtime = mst->st_mtime; st->st_nlink = mst->st_nlink; st->st_rdev = mst->st_rdev; st->st_size = mst->st_size; st->st_uid = mst->st_uid; } int win32_stat(const char* path, struct stat* st) { struct _stat mst; #ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & UINT32_C(0x80000000)) { // Windows 9x char* ac = NULL; if (!charset_iconv(path, &ac, CHARSET_UTF8, CHARSET_ANSI, SIZE_MAX)) { int ret = _stat(ac, &mst); free(ac); win32_stat_conv(&mst, st); return ret; } } else #endif { wchar_t* wc = NULL; if (!charset_iconv(path, &wc, CHARSET_UTF8, CHARSET_WCHAR_T, SIZE_MAX)) { int ret = _wstat(wc, &mst); free(wc); win32_stat_conv(&mst, st); return ret; } } return -1; } FILE* win32_fopen(const char* path, const char* flags) { #ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & UINT32_C(0x80000000)) { // Windows 9x char *ac = NULL, *ac_flags = NULL; if (charset_iconv(path, &ac, CHARSET_UTF8, CHARSET_ANSI, SIZE_MAX) || charset_iconv(flags, &ac_flags, CHARSET_UTF8, CHARSET_ANSI, SIZE_MAX)) return NULL; FILE *ret = fopen(ac, ac_flags); free(ac); free(ac_flags); return ret; } else #endif { // Windows NT wchar_t* wc = NULL, *wc_flags = NULL; if (charset_iconv(path, &wc, CHARSET_UTF8, CHARSET_WCHAR_T, SIZE_MAX) || charset_iconv(flags, &wc_flags, CHARSET_UTF8, CHARSET_WCHAR_T, SIZE_MAX)) return NULL; FILE* ret = _wfopen(wc, wc_flags); free(wc); free(wc_flags); return ret; } // err return NULL; } int win32_mkdir(const char *path, SCHISM_UNUSED mode_t mode) { #ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & UINT32_C(0x80000000)) { char* ac = NULL; if (charset_iconv(path, &ac, CHARSET_UTF8, CHARSET_ANSI, SIZE_MAX)) return -1; int ret = mkdir(ac); free(ac); return ret; } else #endif { wchar_t* wc = NULL; if (charset_iconv(path, &wc, CHARSET_UTF8, CHARSET_WCHAR_T, SIZE_MAX)) return -1; int ret = _wmkdir(wc); free(wc); return ret; } return -1; } /* ------------------------------------------------------------------------------- */ /* run hook */ #define WIN32_RUN_HOOK_VARIANT(name, charset, char_type, char_len_func, char_getcwd, char_chdir, char_getenv, char_spawnlp, char_stat, const_prefix) \ static inline SCHISM_ALWAYS_INLINE int _win32_run_hook_##name(const char *dir, const char *name, const char *maybe_arg) \ { \ char_type cwd[MAX_PATH] = {0}; \ if (!char_getcwd(cwd, MAX_PATH)) \ return 0; \ \ char_type batch_file[MAX_PATH] = {0}; \ \ { \ char_type *name_w; \ if (charset_iconv(name, &name_w, CHARSET_UTF8, charset, SIZE_MAX)) \ return 0; \ \ size_t name_len = char_len_func(name_w); \ if ((name_len * sizeof(char_type)) + sizeof(const_prefix##".bat") >= sizeof(batch_file)) { \ free(name_w); \ return 0; \ } \ \ memcpy(batch_file, name_w, name_len * sizeof(char_type)); \ memcpy(batch_file + name_len, const_prefix##".bat", sizeof(const_prefix##".bat")); \ \ free(name_w); \ } \ \ { \ char_type *dir_w; \ if (charset_iconv(dir, &dir_w, CHARSET_UTF8, charset, SIZE_MAX)) \ return 0; \ \ if (char_chdir(dir_w) == -1) { \ free(dir_w); \ return 0; \ } \ \ free(dir_w); \ } \ \ intptr_t r; \ \ { \ char_type *maybe_arg_w = NULL; \ charset_iconv(maybe_arg, &maybe_arg_w, CHARSET_UTF8, charset, SIZE_MAX); \ \ struct _stat sb; \ if (char_stat(batch_file, &sb) < 0) { \ r = 0; \ } else { \ const char_type *cmd; \ \ cmd = char_getenv(const_prefix##"COMSPEC"); \ if (!cmd) \ cmd = const_prefix##"command.com"; \ \ r = char_spawnlp(_P_WAIT, cmd, cmd, const_prefix##"/c", batch_file, maybe_arg_w, NULL); \ } \ \ free(maybe_arg_w); \ } \ \ char_chdir(cwd); \ return (r == 0); \ } WIN32_RUN_HOOK_VARIANT(wide, CHARSET_WCHAR_T, WCHAR, wcslen, _wgetcwd, _wchdir, _wgetenv, _wspawnlp, _wstat, L) #ifdef SCHISM_WIN32_COMPILE_ANSI WIN32_RUN_HOOK_VARIANT(ansi, CHARSET_ANSI, char, strlen, getcwd, _chdir, getenv, _spawnlp, _stat, /* none */) #endif #undef WIN32_RUN_HOOK_VARIANT int win32_run_hook(const char *dir, const char *name, const char *maybe_arg) { #ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & UINT32_C(0x80000000)) { return _win32_run_hook_ansi(dir, name, maybe_arg); } else #endif { return _win32_run_hook_wide(dir, name, maybe_arg); } } schismtracker-20250313/sys/win32/schism.nsis000066400000000000000000000053451476471630300206030ustar00rootroot00000000000000Name "Schism Tracker" Caption 'Schism Tracker' OutFile 'install.exe' InstallDir "$PROGRAMFILES\Schism Tracker" LicenseData 'COPYING.txt' LicenseBkColor 0xFFFFFF ShowInstDetails show XpStyle on Page License Page Directory Page InstFiles UninstPage uninstConfirm UninstPage instfiles AutoCloseWindow false Section SetOutPath $INSTDIR File "schismtracker.exe" File "schism.ico" File "SDL.dll" File "COPYING.txt" File "README.txt" File "NEWS.txt" File "ChangeLog.txt" WriteUninstaller "uninstall.exe" WriteRegStr HKCR ".it" "" "Schism Tracker" WriteRegStr HKCR ".s3m" "" "Schism Tracker" WriteRegStr HKCR ".mod" "" "Schism Tracker" WriteRegStr HKCR ".669" "" "Schism Tracker" WriteRegStr HKCR ".amf" "" "Schism Tracker" WriteRegStr HKCR ".ams" "" "Schism Tracker" WriteRegStr HKCR ".dbm" "" "Schism Tracker" WriteRegStr HKCR ".dmf" "" "Schism Tracker" WriteRegStr HKCR ".dsm" "" "Schism Tracker" WriteRegStr HKCR ".far" "" "Schism Tracker" WriteRegStr HKCR ".mdl" "" "Schism Tracker" WriteRegStr HKCR ".med" "" "Schism Tracker" WriteRegStr HKCR ".mt2" "" "Schism Tracker" WriteRegStr HKCR ".mtm" "" "Schism Tracker" WriteRegStr HKCR ".okt" "" "Schism Tracker" WriteRegStr HKCR ".psm" "" "Schism Tracker" WriteRegStr HKCR ".ptm" "" "Schism Tracker" WriteRegStr HKCR ".stm" "" "Schism Tracker" WriteRegStr HKCR ".ult" "" "Schism Tracker" WriteRegStr HKCR ".umx" "" "Schism Tracker" WriteRegStr HKCR ".xm" "" "Schism Tracker" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Schism Tracker" "DisplayName" "Schism Tracker (remove only)" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Schism Tracker" "UninstallString" '"$INSTDIR\uninstall.exe"' WriteRegStr HKCR "Schism Tracker\Shell\open\command\" "" '"$INSTDIR\schismtracker.exe" "%1"' WriteRegStr HKCR "Schism Tracker\DefaultIcon" "" "$INSTDIR\schism.ico" CreateDirectory "$SMPROGRAMS\Schism Tracker" CreateShortCut "$SMPROGRAMS\Schism Tracker\Schism Tracker.lnk" "$INSTDIR\schismtracker.exe" "" "$INSTDIR\schism.ico" CreateShortCut "$SMPROGRAMS\Schism Tracker\Schism Font Editor.lnk" "$INSTDIR\schismtracker.exe" "--font-editor" "$INSTDIR\schism.ico" CreateShortCut "$SMPROGRAMS\Schism Tracker\Uninstall Schism Tracker.lnk" "$INSTDIR\uninstall.exe" SectionEnd Section "Uninstall" Delete "$INSTDIR\schismtracker.exe" Delete "$INSTDIR\SDL.dll" Delete "$INSTDIR\schism.ico" Delete "$INSTDIR\COPYING.txt" Delete "$INSTDIR\README.txt" Delete "$INSTDIR\NEWS.txt" Delete "$INSTDIR\ChangeLog.txt" Delete "$SMPROGRAMS\Schism Tracker\Schism Tracker.lnk" Delete "$SMPROGRAMS\Schism Tracker\Font Editor.lnk" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Schism Tracker" SectionEnd schismtracker-20250313/sys/win32/schismres.rc000066400000000000000000000025611476471630300207420ustar00rootroot00000000000000//vi:set bd=syn\ c: #include #include // grab PACKAGE_VERSION #ifndef WRC_VERSION # define WRC_VERSION 0,0,0,0 #endif #define WRC_PRODUCTVER_STR PACKAGE_VERSION #define VER_PRODUCTNAME "Schism Tracker" schismicon ICON "icons/schismres.ico" VS_VERSION_INFO VERSIONINFO FILEVERSION WRC_VERSION PRODUCTVERSION WRC_VERSION FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEFLAGS 0 FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE VFT2_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904E4" BEGIN VALUE "CompanyName", "Storlek" VALUE "LegalCopyright", "Copyright \xA9 2003-2012 Storlek" VALUE "Comments", "http://schismtracker.org/" VALUE "ProductName", VER_PRODUCTNAME VALUE "FileDescription", VER_PRODUCTNAME VALUE "InternalName", PACKAGE_NAME VALUE "OriginalFilename", "schismtracker.exe" VALUE "FileVersion", WRC_PRODUCTVER_STR VALUE "ProductVersion", WRC_PRODUCTVER_STR END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END schismtracker-20250313/sys/win32/slurp-win32.c000066400000000000000000000121521476471630300206620ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #ifndef SCHISM_WIN32 # error You are not on Windows. What are you doing? #endif #include #include #include "util.h" #include "log.h" #include "slurp.h" #include "charset.h" #include "loadso.h" #include "osdefs.h" static void win32_unmap_(slurp_t *slurp) { if (slurp->internal.memory.data) { UnmapViewOfFile(slurp->internal.memory.data); slurp->internal.memory.data = NULL; } // collect and free all of the handles HANDLE *handles[] = { &slurp->internal.memory.interfaces.win32.file, &slurp->internal.memory.interfaces.win32.mapping, }; for (int i = 0; i < ARRAY_SIZE(handles); i++) { if (*handles[i] != NULL && *handles[i] != INVALID_HANDLE_VALUE) CloseHandle(*handles[i]); *handles[i] = NULL; } } // This reader used to return -1 sometimes, which is kind of a hack to tell the // the rest of the loading code to try some other means of opening the file, // which on win32 is basically just fopen + malloc + fread. If MapViewOfFile // won't work, chances are pretty good that stdio is going to fail as well, so // I'm just writing these cases off as every bit as unrecoverable as if the // file didn't exist. // Note: this doesn't bother setting errno; maybe it should? static int win32_error_unmap_(slurp_t *slurp, const char *filename, const char *function, int val) { DWORD err = GetLastError(); char *ptr = NULL; #ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & UINT32_C(0x80000000)) { LPSTR errmsg = NULL; FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&errmsg, 0, NULL); charset_iconv(errmsg, &ptr, CHARSET_ANSI, CHARSET_UTF8, SIZE_MAX); LocalFree(errmsg); } else #endif { LPWSTR errmsg = NULL; FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&errmsg, 0, NULL); charset_iconv(errmsg, &ptr, CHARSET_WCHAR_T, CHARSET_UTF8, SIZE_MAX); LocalFree(errmsg); } // I don't particularly want to split this stuff onto two lines, but // it's the only way to make the error message readable in some cases // (though no matter what, the message is still probably going to be // truncated because Windows is excessively verbose) log_appendf(4, "%s: %s: error %lu:", filename, function, err); if (ptr) { log_appendf(4, " %s", ptr); free(ptr); } win32_unmap_(slurp); return val; } int slurp_win32(slurp_t *slurp, const char *filename, size_t st) { #ifdef SCHISM_WIN32_COMPILE_ANSI if (GetVersion() & UINT32_C(0x80000000)) { // Windows 9x char *filename_a; if (charset_iconv(filename, &filename_a, CHARSET_UTF8, CHARSET_ANSI, SIZE_MAX)) return win32_error_unmap_(slurp, filename, "charset_iconv", 0); slurp->internal.memory.interfaces.win32.file = CreateFileA(filename_a, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); free(filename_a); } else #endif { wchar_t *filename_w; if (charset_iconv(filename, &filename_w, CHARSET_UTF8, CHARSET_WCHAR_T, SIZE_MAX)) return win32_error_unmap_(slurp, filename, "charset_iconv", 0); slurp->internal.memory.interfaces.win32.file = CreateFileW(filename_w, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); free(filename_w); } if (slurp->internal.memory.interfaces.win32.file == INVALID_HANDLE_VALUE) return win32_error_unmap_(slurp, filename, "CreateFile", 0); // These functions are stubs on Windows 95 & 98, so simply ignore if // they fail and fall back to the stdio implementation slurp->internal.memory.interfaces.win32.mapping = CreateFileMapping(slurp->internal.memory.interfaces.win32.file, NULL, PAGE_READONLY, 0, 0, NULL); if (!slurp->internal.memory.interfaces.win32.mapping) { win32_unmap_(slurp); return -1; } slurp->internal.memory.data = MapViewOfFile(slurp->internal.memory.interfaces.win32.mapping, FILE_MAP_READ, 0, 0, 0); if (!slurp->internal.memory.data) { win32_unmap_(slurp); return -1; } slurp->internal.memory.length = st; slurp->closure = win32_unmap_; return 1; } schismtracker-20250313/sys/win32/timer.c000066400000000000000000000170771476471630300177100ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "mt.h" #include "loadso.h" #include "osdefs.h" #include "mem.h" #include "backend/timer.h" #include #include // Old toolchains don't have these: #ifndef CREATE_WAITABLE_TIMER_MANUAL_RESET # define CREATE_WAITABLE_TIMER_MANUAL_RESET 0x00000001 #endif #ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION # define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002 #endif // QueryPerformanceCounter always succeeds on XP and up, // which means compiling the winmm version as a fallback // is redundant for amd64 and arm architectures, since // those versions never existed before XP. #if defined(__386__) || defined(__i386) || defined(__i386__) || defined(i386) || defined(_M_IX86) # define WIN32_TIMER_COMPILE_WINMM 1 #endif #ifdef WIN32_TIMER_COMPILE_WINMM static enum { WIN32_TIMER_IMPL_QPC, WIN32_TIMER_IMPL_WINMM, } win32_timer_impl = WIN32_TIMER_IMPL_WINMM; #endif // The counter value when we init static LARGE_INTEGER win32_timer_start = {0}; // Ticks per second static LARGE_INTEGER win32_timer_resolution = {0}; static uint32_t win32_mm_period = 0; static inline SCHISM_ALWAYS_INLINE LARGE_INTEGER _win32_timer_ticks_impl(void) { LARGE_INTEGER ticks; #ifdef WIN32_TIMER_COMPILE_WINMM switch (win32_timer_impl) { case WIN32_TIMER_IMPL_QPC: #endif QueryPerformanceCounter(&ticks); #ifdef WIN32_TIMER_COMPILE_WINMM break; case WIN32_TIMER_IMPL_WINMM: default: // This used to have overflow detection, but in some experiments // with Windows 2000 in Virtual PC timeGetTime() actually seems // to ~fluctuate~, which broke our heuristics. Just get the low // part for now I guess. ticks.LowPart = timeGetTime(); ticks.HighPart = 0; break; } #endif return ticks; } static timer_ticks_t win32_timer_ticks(void) { LARGE_INTEGER ticks = _win32_timer_ticks_impl(); ticks.QuadPart -= win32_timer_start.QuadPart; ticks.QuadPart *= INT64_C(1000); ticks.QuadPart /= win32_timer_resolution.QuadPart; return ticks.QuadPart; } static timer_ticks_t win32_timer_ticks_us(void) { LARGE_INTEGER ticks = _win32_timer_ticks_impl(); ticks.QuadPart -= win32_timer_start.QuadPart; ticks.QuadPart *= INT64_C(1000000); ticks.QuadPart /= win32_timer_resolution.QuadPart; return ticks.QuadPart; } // based off old code from midi-core.c but less obfuscated // These are in much, much older versions of Windows. // Really, Windows 95 is the only version that doesn't have these symbols. // FIXME is this actually true? Win95 at least has WaitForSingleObject. static HANDLE (WINAPI *WIN32_CreateWaitableTimer)(LPSECURITY_ATTRIBUTES, BOOL, LPCSTR) = NULL; static BOOL (WINAPI *WIN32_SetWaitableTimer)(HANDLE, const LARGE_INTEGER *, LONG, PTIMERAPCROUTINE, LPVOID, BOOL) = NULL; // internal NTDLL functions; this is what SleepEx actually calls under the hood // on NT 4 and newer. // // XXX For architectures that never had 9x (namely, amd64, arm, arm64, ppc, alpha) // should we only compile NtDelayExecution and simply not bother with the timer // crap? static LONG /*NTSTATUS*/ (__stdcall /*NTAPI*/ *NTDLL_NtDelayExecution)(BOOLEAN Alertable, PLARGE_INTEGER Interval) = NULL; // FIXME: we're leaking timers here on thread exit static DWORD dw_timer_tls_index = TLS_OUT_OF_INDEXES; static inline SCHISM_ALWAYS_INLINE int win32_timer_usleep_timer_impl(uint64_t usec) { LARGE_INTEGER due; HANDLE timer; if (!WIN32_SetWaitableTimer || !WIN32_CreateWaitableTimer || dw_timer_tls_index == TLS_OUT_OF_INDEXES) return 0; // NOPE timer = TlsGetValue(dw_timer_tls_index); if (!timer) { timer = WIN32_CreateWaitableTimer(NULL, TRUE, NULL); if (!timer) return 0; TlsSetValue(dw_timer_tls_index, timer); } due.QuadPart = -(10 * (int64_t)usec); if (!WIN32_SetWaitableTimer(timer, &due, 0, NULL, NULL, 0)) return 0; if (WaitForSingleObject(timer, INFINITE) == WAIT_FAILED) return 0; return 1; } static void win32_timer_usleep(uint64_t usec) { // Prioritize NtDelayExecution, it's what SleepEx actually uses // under the hood and removes the overhead of alerting. if (NTDLL_NtDelayExecution) { LARGE_INTEGER due; due.QuadPart = -(10 * (int64_t)usec); if (!NTDLL_NtDelayExecution(FALSE, &due)) return; } // Do we even *need* the timer implementation? if (usec % 1000) { if (win32_timer_usleep_timer_impl(usec)) return; // divide, rounding up SleepEx((usec + 999) / 1000, FALSE); } else { SleepEx(usec / 1000, FALSE); } } static void win32_timer_msleep(uint32_t msec) { SleepEx(msec, FALSE); } ////////////////////////////////////////////////////////////////////////////// static void *kernel32 = NULL; static void *ntdll = NULL; static int win32_timer_must_end_period = 0; static int win32_timer_init(void) { TIMECAPS caps; // we check if this is valid in the timer impl dw_timer_tls_index = TlsAlloc(); if (!timeGetDevCaps(&caps, sizeof(caps))) { win32_mm_period = caps.wPeriodMin; if (!timeBeginPeriod(win32_mm_period)) win32_timer_must_end_period = 1; } #ifdef WIN32_TIMER_COMPILE_WINMM if (win32_ntver_atleast(5, 1, 0) // This is buggy and broken on Win2k && QueryPerformanceFrequency(&win32_timer_resolution) && win32_timer_resolution.QuadPart && QueryPerformanceCounter(&win32_timer_start)) { win32_timer_impl = WIN32_TIMER_IMPL_QPC; } else { // This works everywhere and is hopefully good enough win32_timer_start.QuadPart = timeGetTime(); win32_timer_resolution.QuadPart = 1000; win32_timer_impl = WIN32_TIMER_IMPL_WINMM; } #else QueryPerformanceFrequency(&win32_timer_resolution); QueryPerformanceCounter(&win32_timer_start); #endif kernel32 = loadso_object_load("KERNEL32.DLL"); if (kernel32) { WIN32_CreateWaitableTimer = loadso_function_load(kernel32, "CreateWaitableTimerA"); if (!WIN32_CreateWaitableTimer) WIN32_CreateWaitableTimer = loadso_function_load(kernel32, "CreateWaitableTimer"); WIN32_SetWaitableTimer = loadso_function_load(kernel32, "SetWaitableTimer"); } ntdll = loadso_object_load("NTDLL.DLL"); if (ntdll) { NTDLL_NtDelayExecution = loadso_function_load(ntdll, "NtDelayExecution"); } // ok return 1; } static void win32_timer_quit(void) { if (win32_timer_must_end_period) timeEndPeriod(win32_mm_period); if (kernel32) loadso_object_unload(kernel32); if (ntdll) loadso_object_unload(ntdll); if (dw_timer_tls_index == TLS_OUT_OF_INDEXES) TlsFree(dw_timer_tls_index); } ////////////////////////////////////////////////////////////////////////////// const schism_timer_backend_t schism_timer_backend_win32 = { .init = win32_timer_init, .quit = win32_timer_quit, .ticks = win32_timer_ticks, .ticks_us = win32_timer_ticks_us, .usleep = win32_timer_usleep, .msleep = win32_timer_msleep, }; schismtracker-20250313/sys/x11/000077500000000000000000000000001476471630300160575ustar00rootroot00000000000000schismtracker-20250313/sys/x11/clippy.c000066400000000000000000000222561476471630300175320ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "video.h" #include "events.h" #include "clippy.h" #include "backend/clippy.h" #include "mem.h" #include "init.h" /* This clipboard code was mostly stolen from SDL 2: */ /* Simple DirectMedia Layer Copyright (C) 1997-2024 Sam Lantinga This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ int x11_clippy_selection_waiting = 1; /* We use our own cut-buffer for intermediate storage instead of * XA_CUT_BUFFER0 because their use isn't really defined for holding UTF-8. */ Atom x11_get_cut_buffer_type(Display *display, int mime_type, Atom selection_type) { switch (mime_type) { case X11_CLIPBOARD_MIME_TYPE_STRING: case X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN: case X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN_UTF8: case X11_CLIPBOARD_MIME_TYPE_TEXT: return X11_XInternAtom(display, selection_type == XA_PRIMARY ? "SCHISM_CUTBUFFER_SELECTION" : "SCHISM_CUTBUFFER_CLIPBOARD", False); default: return XA_STRING; } } Atom x11_get_cut_buffer_external_fmt(Display *display, int mime_type) { switch (mime_type) { case X11_CLIPBOARD_MIME_TYPE_STRING: /* If you don't support UTF-8, you might use XA_STRING here... */ return X11_XInternAtom(display, "UTF8_STRING", False); case X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN: return X11_XInternAtom(display, "text/plain", False); case X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN_UTF8: return X11_XInternAtom(display, "text/plain;charset=utf-8", False); case X11_CLIPBOARD_MIME_TYPE_TEXT: return X11_XInternAtom(display, "TEXT", False); default: return XA_STRING; } } Atom x11_get_cut_buffer_internal_fmt(Display *display, int mime_type) { switch (mime_type) { case X11_CLIPBOARD_MIME_TYPE_STRING: case X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN: case X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN_UTF8: case X11_CLIPBOARD_MIME_TYPE_TEXT: /* If you don't support UTF-8, you might use XA_STRING here... */ return X11_XInternAtom(display, "UTF8_STRING", True); default: return XA_STRING; } } static int x11_set_selection_text(video_wm_data_t *wm_data, const char *text, Atom selection_type) { Display *display = wm_data->data.x11.display; Window window = wm_data->data.x11.window; /* Lock the display; only required for SDL 1.2 for now */ if (wm_data->data.x11.lock_func) wm_data->data.x11.lock_func(); /* Save the selection on the root window */ X11_XChangeProperty(display, DefaultRootWindow(display), x11_get_cut_buffer_type(display, X11_CLIPBOARD_MIME_TYPE_STRING, selection_type), x11_get_cut_buffer_internal_fmt(display, X11_CLIPBOARD_MIME_TYPE_STRING), 8, PropModeReplace, (const unsigned char *)text, strlen(text)); X11_XSetSelectionOwner(display, selection_type, window, CurrentTime); if (wm_data->data.x11.unlock_func) wm_data->data.x11.unlock_func(); return 0; } static char *x11_get_selection_text(video_wm_data_t *wm_data, Atom selection_type) { Atom format; Window owner; Atom selection; Atom seln_type; int seln_format; unsigned long nbytes; unsigned long overflow; unsigned char *src; char *text = NULL; Display *display = wm_data->data.x11.display; Window window = wm_data->data.x11.window; if (wm_data->data.x11.lock_func) wm_data->data.x11.lock_func(); format = x11_get_cut_buffer_internal_fmt(display, X11_CLIPBOARD_MIME_TYPE_STRING); owner = X11_XGetSelectionOwner(display, selection_type); if (owner == None) { /* Fall back to ancient X10 cut-buffers which do not support UTF-8 strings */ owner = DefaultRootWindow(display); selection = XA_CUT_BUFFER0; format = XA_STRING; } else if (owner == window) { owner = DefaultRootWindow(display); selection = x11_get_cut_buffer_type(display, X11_CLIPBOARD_MIME_TYPE_STRING, selection_type); } else { /* Request that the selection owner copy the data to our window */ owner = window; selection = X11_XInternAtom(display, "SCHISM_SELECTION", False); X11_XConvertSelection(display, selection_type, format, selection, owner, CurrentTime); /* Time out if the other window never responds... */ timer_ticks_t start = timer_ticks(); x11_clippy_selection_waiting = 1; do { events_pump_events(); timer_ticks_t elapsed = timer_ticks() - start; /* Wait one second for a selection response. */ if (elapsed > 1000) { x11_clippy_selection_waiting = 0; if (wm_data->data.x11.unlock_func) wm_data->data.x11.unlock_func(); /* We need to set the selection text so that next time we won't * timeout, otherwise we will hang on every call to this function. */ x11_set_selection_text(wm_data, "", selection_type); return str_dup(""); } } while (x11_clippy_selection_waiting); } if (X11_XGetWindowProperty(display, owner, selection, 0, INT_MAX / 4, False, format, &seln_type, &seln_format, &nbytes, &overflow, &src) == Success) { if (seln_type == format) { text = mem_alloc(nbytes + 1); if (text) { memcpy(text, src, nbytes); text[nbytes] = '\0'; } } X11_XFree(src); } if (wm_data->data.x11.unlock_func) wm_data->data.x11.unlock_func(); return text ? text : str_dup(""); } static void x11_clippy_set_clipboard(const char *text) { // XXX what was this used for? what? //int x_needs_close = 0; video_wm_data_t wm_data; if (!video_get_wm_data(&wm_data) || wm_data.subsystem != VIDEO_WM_DATA_SUBSYSTEM_X11) return; if (wm_data.data.x11.lock_func) wm_data.data.x11.lock_func(); Atom XA_CLIPBOARD = X11_XInternAtom(wm_data.data.x11.display, "CLIPBOARD", False); if (wm_data.data.x11.unlock_func) wm_data.data.x11.unlock_func(); if (XA_CLIPBOARD == None) return; x11_set_selection_text(&wm_data, text, XA_CLIPBOARD); } static void x11_clippy_set_selection(const char *text) { video_wm_data_t wm_data; if (!video_get_wm_data(&wm_data) || wm_data.subsystem != VIDEO_WM_DATA_SUBSYSTEM_X11) return; x11_set_selection_text(&wm_data, text, XA_PRIMARY); } static char *x11_clippy_get_clipboard(void) { video_wm_data_t wm_data; if (!video_get_wm_data(&wm_data) || wm_data.subsystem != VIDEO_WM_DATA_SUBSYSTEM_X11) return str_dup(""); if (wm_data.data.x11.lock_func) wm_data.data.x11.lock_func(); Atom XA_CLIPBOARD = X11_XInternAtom(wm_data.data.x11.display, "CLIPBOARD", False); if (wm_data.data.x11.unlock_func) wm_data.data.x11.unlock_func(); if (XA_CLIPBOARD == None) return str_dup(""); return x11_get_selection_text(&wm_data, XA_CLIPBOARD); } static char *x11_clippy_get_selection(void) { video_wm_data_t wm_data; if (!video_get_wm_data(&wm_data) || wm_data.subsystem != VIDEO_WM_DATA_SUBSYSTEM_X11) return str_dup(""); return x11_get_selection_text(&wm_data, XA_PRIMARY); } static int x11_clippy_have_clipboard(void) { int result = 0; char *text = x11_clippy_get_clipboard(); if (text) { result = text[0] != '\0'; free(text); } return result; } static int x11_clippy_have_selection(void) { int result = 0; char *text = x11_clippy_get_selection(); if (text) { result = text[0] != '\0'; free(text); } return result; } /////////////////////////////////////////////////////////////////// static int x11_clippy_init(void) { if (!x11_init()) return 0; // wew return 1; } static void x11_clippy_quit(void) { x11_quit(); } const schism_clippy_backend_t schism_clippy_backend_x11 = { .init = x11_clippy_init, .quit = x11_clippy_quit, .have_selection = x11_clippy_have_selection, .get_selection = x11_clippy_get_selection, .set_selection = x11_clippy_set_selection, .have_clipboard = x11_clippy_have_clipboard, .get_clipboard = x11_clippy_get_clipboard, .set_clipboard = x11_clippy_set_clipboard, }; schismtracker-20250313/sys/x11/events.c000066400000000000000000000111441476471630300175300ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "osdefs.h" #include "events.h" #include "video.h" #include "init.h" /* The clipboard code in this file was taken from SDL 2, * whose license is included in sys/x11/clippy.c */ // in sys/x11/clippy.c, set to 0 when SelectionNotify event received. extern int x11_clippy_selection_waiting; int x11_event(schism_event_t *event) { int i; if (event->type != SCHISM_EVENT_WM_MSG || event->wm_msg.subsystem != SCHISM_WM_MSG_SUBSYSTEM_X11) return 1; video_wm_data_t wm_data; if (!video_get_wm_data(&wm_data) || wm_data.subsystem != VIDEO_WM_DATA_SUBSYSTEM_X11) return 1; // ??? if (wm_data.data.x11.lock_func) wm_data.data.x11.lock_func(); switch (event->wm_msg.msg.x11.event.type) { case SelectionNotify: // sent when a selection is received from XConvertSelection x11_clippy_selection_waiting = 0; break; case SelectionRequest: { int seln_format, mime_formats; unsigned long nbytes; unsigned long overflow; unsigned char *seln_data; Atom supportedFormats[X11_CLIPBOARD_MIME_TYPE_MAX_ + 1]; Atom XA_TARGETS = X11_XInternAtom(wm_data.data.x11.display, "TARGETS", False); XEvent sevent = { .xselection = { .type = SelectionNotify, .selection = event->wm_msg.msg.x11.event.selection_request.selection, .target = None, .property = None, .requestor = event->wm_msg.msg.x11.event.selection_request.requestor, .time = event->wm_msg.msg.x11.event.selection_request.time, }, }; if (event->wm_msg.msg.x11.event.selection_request.target == XA_TARGETS) { supportedFormats[0] = XA_TARGETS; mime_formats = 1; for (i = 0; i < X11_CLIPBOARD_MIME_TYPE_MAX_; i++) supportedFormats[mime_formats++] = x11_get_cut_buffer_external_fmt(wm_data.data.x11.display, i); X11_XChangeProperty(wm_data.data.x11.display, event->wm_msg.msg.x11.event.selection_request.requestor, event->wm_msg.msg.x11.event.selection_request.property, XA_ATOM, 32, PropModeReplace, (unsigned char *)supportedFormats, mime_formats); sevent.xselection.property = event->wm_msg.msg.x11.event.selection_request.property; sevent.xselection.target = XA_TARGETS; } else { for (i = 0; i < X11_CLIPBOARD_MIME_TYPE_MAX_; ++i) { if (x11_get_cut_buffer_external_fmt(wm_data.data.x11.display, i) != event->wm_msg.msg.x11.event.selection_request.target) continue; if (X11_XGetWindowProperty(wm_data.data.x11.display, DefaultRootWindow(wm_data.data.x11.display), x11_get_cut_buffer_type(wm_data.data.x11.display, i, event->wm_msg.msg.x11.event.selection_request.selection), 0, INT_MAX / 4, False, x11_get_cut_buffer_internal_fmt(wm_data.data.x11.display, i), &sevent.xselection.target, &seln_format, &nbytes, &overflow, &seln_data) == Success) { if (seln_format != None) { X11_XChangeProperty(wm_data.data.x11.display, event->wm_msg.msg.x11.event.selection_request.requestor, event->wm_msg.msg.x11.event.selection_request.property, event->wm_msg.msg.x11.event.selection_request.target, 8, PropModeReplace, seln_data, nbytes); sevent.xselection.property = event->wm_msg.msg.x11.event.selection_request.property; sevent.xselection.target = event->wm_msg.msg.x11.event.selection_request.target; X11_XFree(seln_data); break; } else { X11_XFree(seln_data); } } } } X11_XSendEvent(wm_data.data.x11.display, event->wm_msg.msg.x11.event.selection_request.requestor, False, 0, &sevent); X11_XSync(wm_data.data.x11.display, False); break; } } if (wm_data.data.x11.unlock_func) wm_data.data.x11.unlock_func(); return 0; }schismtracker-20250313/sys/x11/init.c000066400000000000000000000117231476471630300171720ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "headers.h" #include "loadso.h" #include "util.h" #include "init.h" #include /* Our dynamically loaded symbols */ Display *(*X11_XOpenDisplay)(const char *display_name) = NULL; int (*X11_XCloseDisplay)(Display *display) = NULL; Atom (*X11_XInternAtom)(Display *display, const char *atom_name, Bool only_if_exists) = NULL; int (*X11_XChangeProperty)(Display *display, Window w, Atom property, Atom type, int format, int mode, const unsigned char *data, int nelements) = NULL; Window (*X11_XGetSelectionOwner)(Display *display, Atom selection) = NULL; int (*X11_XConvertSelection)(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time) = NULL; int (*X11_XFree)(void *ptr) = NULL; int (*X11_XGetWindowProperty)(Display *display, Window w, Atom property, long long_offset, long long_length, Bool delete, Atom req_type, Atom *actual_type_return, int *actual_format_return, unsigned long *nitems_return, unsigned long *bytes_after_return, unsigned char **prop_return) = NULL; int (*X11_XSetSelectionOwner)(Display *display, Atom selection, Window owner, Time time) = NULL; Status (*X11_XSendEvent)(Display *display, Window w, Bool propagate, long event_mask, XEvent *event_send) = NULL; int (*X11_XSync)(Display *display, Bool discard) = NULL; static int load_x11_syms(void); #ifdef X11_DYNAMIC_LOAD #include "loadso.h" enum { X11_LIBRARY_LIBX11, X11_LIBRARY_MAX_, }; #ifndef X11_LIBRARY_LIBX11_PATH # define X11_LIBRARY_LIBX11_PATH NULL #endif /* Libraries table. Currently, we only ever use libX11. */ static struct { const char *path; void *lib; } x11_dltrick_handles_[X11_LIBRARY_MAX_] = { [X11_LIBRARY_LIBX11] = {X11_LIBRARY_LIBX11_PATH, NULL}, }; static void x11_dlend(void) { size_t i; for (i = 0; i < ARRAY_SIZE(x11_dltrick_handles_); i++) { // unload if (x11_dltrick_handles_[i].lib) { loadso_object_unload(x11_dltrick_handles_[i].lib); x11_dltrick_handles_[i].lib = NULL; } } } static int x11_dlinit(void) { size_t i; for (i = 0; i < ARRAY_SIZE(x11_dltrick_handles_); i++) { // do we already have it and does the library exist? if (x11_dltrick_handles_[i].lib || !x11_dltrick_handles_[i].path) return 0; // eh x11_dltrick_handles_[i].lib = loadso_object_load(x11_dltrick_handles_[i].path); } int retval = load_x11_syms(); if (retval < 0) x11_dlend(); return retval; } SCHISM_STATIC_ASSERT(sizeof(void (*)) == sizeof(void *), "dynamic loading code assumes function pointer and void pointer are of equivalent size"); // `library' is the enum above static int x11_load_sym(int library, const char *fn, void *addr) { if (!x11_dltrick_handles_[library].lib) return 0; void *func = loadso_function_load(x11_dltrick_handles_[library].lib, fn); if (!func) return 0; memcpy(addr, &func, sizeof(void *)); return 1; } # define SCHISM_X11_SYM(library, x) \ if (!x11_load_sym(X11_LIBRARY_LIB ## library, #x, &X11_##x)) return -1 #else static int x11_dlinit(void) { load_x11_syms(); return 0; } #define x11_dlend() // nothing # define SCHISM_X11_SYM(library, x) \ X11_##x = x #endif static int load_x11_syms(void) { SCHISM_X11_SYM(X11, XOpenDisplay); SCHISM_X11_SYM(X11, XCloseDisplay); SCHISM_X11_SYM(X11, XInternAtom); SCHISM_X11_SYM(X11, XChangeProperty); SCHISM_X11_SYM(X11, XConvertSelection); SCHISM_X11_SYM(X11, XFree); SCHISM_X11_SYM(X11, XGetSelectionOwner); SCHISM_X11_SYM(X11, XSetSelectionOwner); SCHISM_X11_SYM(X11, XGetWindowProperty); SCHISM_X11_SYM(X11, XSendEvent); SCHISM_X11_SYM(X11, XSync); return 0; } ////////////////////////////////////////////////////////////////////////////// static int roll = 0; // returns non-zero on success or zero on error int x11_init(void) { if (!roll) { if (x11_dlinit()) return 0; // ok, let's try opening the default display. Display *display = X11_XOpenDisplay(NULL); if (!display) return 0; // d'oh! X11_XCloseDisplay(display); } roll++; return 1; } void x11_quit(void) { if (roll > 0) roll--; if (roll == 0) x11_dlend(); } schismtracker-20250313/sys/x11/init.h000066400000000000000000000056531476471630300172040ustar00rootroot00000000000000/* * Schism Tracker - a cross-platform Impulse Tracker clone * copyright (c) 2003-2005 Storlek * copyright (c) 2005-2008 Mrs. Brisby * copyright (c) 2009 Storlek & Mrs. Brisby * copyright (c) 2010-2012 Storlek * URL: http://schismtracker.org/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef SCHISM_SYS_X11_INIT_H_ #define SCHISM_SYS_X11_INIT_H_ #include "headers.h" #include #include int x11_init(void); void x11_quit(void); // sys/x11/clippy.c enum { X11_CLIPBOARD_MIME_TYPE_STRING, X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN, X11_CLIPBOARD_MIME_TYPE_TEXT_PLAIN_UTF8, X11_CLIPBOARD_MIME_TYPE_TEXT, X11_CLIPBOARD_MIME_TYPE_MAX_, }; Atom x11_get_cut_buffer_type(Display *display, int mime_type, Atom selection_type); Atom x11_get_cut_buffer_external_fmt(Display *display, int mime_type); Atom x11_get_cut_buffer_internal_fmt(Display *display, int mime_type); // dynamically loaded symbols. these are ONLY valid after a successful call to x11_init(), // and they should be avoided (even if non-null) if you haven't called that. extern Display *(*X11_XOpenDisplay)(const char *display_name); extern int (*X11_XCloseDisplay)(Display *display); extern Atom (*X11_XInternAtom)(Display *display, const char *atom_name, Bool only_if_exists); extern int (*X11_XChangeProperty)(Display *display, Window w, Atom property, Atom type, int format, int mode, const unsigned char *data, int nelements); extern Window (*X11_XGetSelectionOwner)(Display *display, Atom selection); extern int (*X11_XConvertSelection)(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); extern int (*X11_XFree)(void *ptr); extern int (*X11_XGetWindowProperty)(Display *display, Window w, Atom property, long long_offset, long long_length, Bool delete, Atom req_type, Atom *actual_type_return, int *actual_format_return, unsigned long *nitems_return, unsigned long *bytes_after_return, unsigned char **prop_return); extern int (*X11_XSetSelectionOwner)(Display *display, Atom selection, Window owner, Time time); extern Status (*X11_XSendEvent)(Display *display, Window w, Bool propagate, long event_mask, XEvent *event_send); extern int (*X11_XSync)(Display *display, Bool discard); #endif /* SCHISM_SYS_X11_INIT_H_ */