From 27be5bc72855a0fbbdae230bc144624c9eb85f5e Mon Sep 17 00:00:00 2001 From: Michiel Van Der Kolk Date: Thu, 17 Mar 2005 20:50:03 +0000 Subject: Initial check in dumb 0.9.2 - has a few usages of floating point that should be rewritten to fixed point. seems to compile cleanly for iriver. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@6197 a1c6a512-1295-4272-9138-f99709370657 --- apps/codecs/dumb/Makefile | 311 +++ apps/codecs/dumb/docs/deprec.txt | 281 +++ apps/codecs/dumb/docs/dumb.txt | 1699 ++++++++++++++ apps/codecs/dumb/docs/faq.txt | 263 +++ apps/codecs/dumb/docs/fnptr.txt | 113 + apps/codecs/dumb/docs/howto.txt | 845 +++++++ apps/codecs/dumb/docs/modplug.txt | 137 ++ apps/codecs/dumb/docs/ptr.txt | 129 + apps/codecs/dumb/examples/dumb.ini | 44 + apps/codecs/dumb/examples/dumbout.c | 335 +++ apps/codecs/dumb/examples/dumbplay.c | 238 ++ apps/codecs/dumb/include/aldumb.h | 93 + apps/codecs/dumb/include/dumb.h | 563 +++++ apps/codecs/dumb/include/internal/aldumb.h | 27 + apps/codecs/dumb/include/internal/dumb.h | 58 + apps/codecs/dumb/include/internal/it.h | 710 ++++++ apps/codecs/dumb/licence.txt | 54 + apps/codecs/dumb/make/Makefile.inc | 34 + apps/codecs/dumb/make/config.bat | 33 + apps/codecs/dumb/make/config.sh | 35 + apps/codecs/dumb/make/config.txt | 3 + apps/codecs/dumb/make/djgpp.inc | 28 + apps/codecs/dumb/make/dumbask | Bin 0 -> 12700 bytes apps/codecs/dumb/make/dumbask.c | 30 + apps/codecs/dumb/make/mingw.inc | 28 + apps/codecs/dumb/make/unix.inc | 20 + apps/codecs/dumb/readme.txt | 421 ++++ apps/codecs/dumb/release.txt | 406 ++++ apps/codecs/dumb/src/allegro/alplay.c | 270 +++ apps/codecs/dumb/src/allegro/datduh.c | 60 + apps/codecs/dumb/src/allegro/datit.c | 62 + apps/codecs/dumb/src/allegro/datmod.c | 61 + apps/codecs/dumb/src/allegro/dats3m.c | 61 + apps/codecs/dumb/src/allegro/datunld.c | 31 + apps/codecs/dumb/src/allegro/datxm.c | 62 + apps/codecs/dumb/src/allegro/packfile.c | 98 + apps/codecs/dumb/src/core/atexit.c | 71 + apps/codecs/dumb/src/core/duhlen.c | 34 + apps/codecs/dumb/src/core/dumbfile.c | 401 ++++ apps/codecs/dumb/src/core/loadduh.c | 42 + apps/codecs/dumb/src/core/makeduh.c | 92 + apps/codecs/dumb/src/core/rawsig.c | 44 + apps/codecs/dumb/src/core/readduh.c | 107 + apps/codecs/dumb/src/core/register.c | 104 + apps/codecs/dumb/src/core/rendduh.c | 202 ++ apps/codecs/dumb/src/core/rendsig.c | 299 +++ apps/codecs/dumb/src/core/unload.c | 58 + apps/codecs/dumb/src/helpers/clickrem.c | 270 +++ apps/codecs/dumb/src/helpers/memfile.c | 96 + apps/codecs/dumb/src/helpers/resample.c | 1177 ++++++++++ apps/codecs/dumb/src/helpers/sampbuf.c | 47 + apps/codecs/dumb/src/helpers/silence.c | 29 + apps/codecs/dumb/src/helpers/stdfile.c | 93 + apps/codecs/dumb/src/it/itload.c | 43 + apps/codecs/dumb/src/it/itmisc.c | 175 ++ apps/codecs/dumb/src/it/itorder.c | 63 + apps/codecs/dumb/src/it/itread.c | 1181 ++++++++++ apps/codecs/dumb/src/it/itrender.c | 3512 ++++++++++++++++++++++++++++ apps/codecs/dumb/src/it/itunload.c | 71 + apps/codecs/dumb/src/it/loadmod.c | 42 + apps/codecs/dumb/src/it/loads3m.c | 42 + apps/codecs/dumb/src/it/loadxm.c | 42 + apps/codecs/dumb/src/it/readmod.c | 594 +++++ apps/codecs/dumb/src/it/reads3m.c | 668 ++++++ apps/codecs/dumb/src/it/readxm.c | 998 ++++++++ apps/codecs/dumb/src/it/xmeffect.c | 242 ++ 66 files changed, 18482 insertions(+) create mode 100644 apps/codecs/dumb/Makefile create mode 100644 apps/codecs/dumb/docs/deprec.txt create mode 100644 apps/codecs/dumb/docs/dumb.txt create mode 100644 apps/codecs/dumb/docs/faq.txt create mode 100644 apps/codecs/dumb/docs/fnptr.txt create mode 100644 apps/codecs/dumb/docs/howto.txt create mode 100644 apps/codecs/dumb/docs/modplug.txt create mode 100644 apps/codecs/dumb/docs/ptr.txt create mode 100644 apps/codecs/dumb/examples/dumb.ini create mode 100644 apps/codecs/dumb/examples/dumbout.c create mode 100644 apps/codecs/dumb/examples/dumbplay.c create mode 100644 apps/codecs/dumb/include/aldumb.h create mode 100644 apps/codecs/dumb/include/dumb.h create mode 100644 apps/codecs/dumb/include/internal/aldumb.h create mode 100644 apps/codecs/dumb/include/internal/dumb.h create mode 100644 apps/codecs/dumb/include/internal/it.h create mode 100644 apps/codecs/dumb/licence.txt create mode 100644 apps/codecs/dumb/make/Makefile.inc create mode 100644 apps/codecs/dumb/make/config.bat create mode 100755 apps/codecs/dumb/make/config.sh create mode 100644 apps/codecs/dumb/make/config.txt create mode 100644 apps/codecs/dumb/make/djgpp.inc create mode 100755 apps/codecs/dumb/make/dumbask create mode 100644 apps/codecs/dumb/make/dumbask.c create mode 100644 apps/codecs/dumb/make/mingw.inc create mode 100644 apps/codecs/dumb/make/unix.inc create mode 100644 apps/codecs/dumb/readme.txt create mode 100644 apps/codecs/dumb/release.txt create mode 100644 apps/codecs/dumb/src/allegro/alplay.c create mode 100644 apps/codecs/dumb/src/allegro/datduh.c create mode 100644 apps/codecs/dumb/src/allegro/datit.c create mode 100644 apps/codecs/dumb/src/allegro/datmod.c create mode 100644 apps/codecs/dumb/src/allegro/dats3m.c create mode 100644 apps/codecs/dumb/src/allegro/datunld.c create mode 100644 apps/codecs/dumb/src/allegro/datxm.c create mode 100644 apps/codecs/dumb/src/allegro/packfile.c create mode 100644 apps/codecs/dumb/src/core/atexit.c create mode 100644 apps/codecs/dumb/src/core/duhlen.c create mode 100644 apps/codecs/dumb/src/core/dumbfile.c create mode 100644 apps/codecs/dumb/src/core/loadduh.c create mode 100644 apps/codecs/dumb/src/core/makeduh.c create mode 100644 apps/codecs/dumb/src/core/rawsig.c create mode 100644 apps/codecs/dumb/src/core/readduh.c create mode 100644 apps/codecs/dumb/src/core/register.c create mode 100644 apps/codecs/dumb/src/core/rendduh.c create mode 100644 apps/codecs/dumb/src/core/rendsig.c create mode 100644 apps/codecs/dumb/src/core/unload.c create mode 100644 apps/codecs/dumb/src/helpers/clickrem.c create mode 100644 apps/codecs/dumb/src/helpers/memfile.c create mode 100644 apps/codecs/dumb/src/helpers/resample.c create mode 100644 apps/codecs/dumb/src/helpers/sampbuf.c create mode 100644 apps/codecs/dumb/src/helpers/silence.c create mode 100644 apps/codecs/dumb/src/helpers/stdfile.c create mode 100644 apps/codecs/dumb/src/it/itload.c create mode 100644 apps/codecs/dumb/src/it/itmisc.c create mode 100644 apps/codecs/dumb/src/it/itorder.c create mode 100644 apps/codecs/dumb/src/it/itread.c create mode 100644 apps/codecs/dumb/src/it/itrender.c create mode 100644 apps/codecs/dumb/src/it/itunload.c create mode 100644 apps/codecs/dumb/src/it/loadmod.c create mode 100644 apps/codecs/dumb/src/it/loads3m.c create mode 100644 apps/codecs/dumb/src/it/loadxm.c create mode 100644 apps/codecs/dumb/src/it/readmod.c create mode 100644 apps/codecs/dumb/src/it/reads3m.c create mode 100644 apps/codecs/dumb/src/it/readxm.c create mode 100644 apps/codecs/dumb/src/it/xmeffect.c (limited to 'apps/codecs/dumb') diff --git a/apps/codecs/dumb/Makefile b/apps/codecs/dumb/Makefile new file mode 100644 index 0000000000..3ced51338d --- /dev/null +++ b/apps/codecs/dumb/Makefile @@ -0,0 +1,311 @@ +# Main Makefile for DUMB. + +# In theory, this Makefile can be used without modifications on DOS, Windows, +# Linux, BeOS and Mac OS X. Caveats are as follows: + +# - For DOS and Windows users, COMSPEC (or ComSpec) must be set to point to +# command.com or cmd.exe. If they point to a Unix-style shell, this +# Makefile will die horribly. + +# - Users of other platforms must NOT set COMSPEC or ComSpec. They must be +# undefined. + +# Commands are as follows: + +# make - Build the library (does make config for you first time). +# make install - Install the library and examples into the system. +# make uninstall - Remove the above. +# make config - Do or redo the configuration. +# make clean - Delete all object files; examples and libraries remain. +# make veryclean - Delete examples and libraries too. + +# TODO: consider whether to delete config.txt and/or dumbask(.exe) + + +.PHONY: all install uninstall clean veryclean config config-if-necessary + +PHONY_TARGETS := core allegro core-examples allegro-examples core-headers allegro-headers + +.PHONY: $(PHONY_TARGETS) +.PHONY: $(PHONY_TARGETS:%=install-%) +.PHONY: $(PHONY_TARGETS:%=uninstall-%) + + +COMMA := , + +#CC := gcc +#AR := ar + + +# Configuration. +# The configuration is done by an MS-DOS batch file if COMSPEC is set. +# Otherwise it is done by a Unix shell script. A file called 'config.txt', +# containing variables that control the build process, is created, and +# included by this Makefile. + + +ifeq "$(COMSPEC)" "" +ifdef ComSpec +COMSPEC := $(ComSpec) +endif +endif + + +-include make/config.txt + + +ifeq "$(OSTYPE)" "beos" + +INCLUDE_INSTALL_PATH := /boot/develop/headers +LIB_INSTALL_PATH := /boot/develop/lib/x86 +BIN_INSTALL_PATH := /boot/home/config/bin +# DEFAULT_PREFIX is not set, so config.sh will not prompt for PREFIX. +LINK_MATH := + +else + +ifdef PREFIX +DEFAULT_PREFIX := $(PREFIX) +else +DEFAULT_PREFIX := /usr/local +endif +export DEFAULT_PREFIX +INCLUDE_INSTALL_PATH := $(PREFIX)/include +LIB_INSTALL_PATH := $(PREFIX)/lib +BIN_INSTALL_PATH := $(PREFIX)/bin + +endif + + +all: config-if-necessary + @$(MAKE) --no-print-directory $(ALL_TARGETS) + $(call ECHO,DUMB has been built. Run $(APOST)make install$(APOST) to install it.) + +install: config-if-necessary + @$(MAKE) --no-print-directory $(ALL_TARGETS:%=install-%) + $(call ECHO,DUMB has been installed.) + $(call ECHO,See readme.txt for details on the example programs.) + $(call ECHO,When you$(APOST)re ready to start using DUMB$(COMMA) see docs/howto.txt.) + $(call ECHO,Enjoy!) + +uninstall: config-if-necessary + @$(MAKE) --no-print-directory $(ALL_TARGETS:%=uninstall-%) + $(call ECHO,DUMB has been uninstalled.) + + +ifdef COMSPEC +# Assume DOS or Windows. +SHELL := $(COMSPEC) +CONFIG_COMMAND := make\config.bat +DUMBASK_EXE := make/dumbask.exe +else +# Assume a Unix-compatible system. +CONFIG_COMMAND := make/config.sh +DUMBASK_EXE := make/dumbask +endif + +# This will always configure. +config: $(DUMBASK_EXE) + $(CONFIG_COMMAND) + +# This will only configure if the configuration file is absent. We don't use +# config.txt as the target name, because Make then runs the config initially, +# and again when it sees the 'config' target, so an initial 'make config' +# causes the configuration to be done twice. +ifeq "$(wildcard make/config.txt)" "" +config-if-necessary: config +else +config-if-necessary: +endif + +$(DUMBASK_EXE): make/dumbask.c + $(CC) $< -o $@ + + +ifdef PLATFORM + + +# Build. + + +CORE_MODULES := \ + core/atexit.c \ + core/duhlen.c \ + core/dumbfile.c \ + core/loadduh.c \ + core/makeduh.c \ + core/rawsig.c \ + core/readduh.c \ + core/register.c \ + core/rendduh.c \ + core/rendsig.c \ + core/unload.c \ + helpers/clickrem.c \ + helpers/memfile.c \ + helpers/resample.c \ + helpers/sampbuf.c \ + helpers/silence.c \ + it/itload.c \ + it/itread.c \ + it/itrender.c \ + it/itunload.c \ + it/loads3m.c \ + it/reads3m.c \ + it/loadxm.c \ + it/readxm.c \ + it/loadmod.c \ + it/readmod.c \ + it/xmeffect.c \ + it/itorder.c \ + it/itmisc.c +# helpers/stdfile.c + +ALLEGRO_MODULES := \ + allegro/alplay.c \ + allegro/datduh.c \ + allegro/datit.c \ + allegro/datxm.c \ + allegro/dats3m.c \ + allegro/datmod.c \ + allegro/datunld.c \ + allegro/packfile.c + +CORE_EXAMPLES := examples/dumbout.c +ALLEGRO_EXAMPLES := examples/dumbplay.c + +CORE_HEADERS := include/dumb.h +ALLEGRO_HEADERS := include/aldumb.h + + +LIBDIR := lib/$(PLATFORM) +OBJDIR_BASE := obj/$(PLATFORM) + + +WFLAGS := -Wall -W -Wwrite-strings -Wstrict-prototypes -Wmissing-declarations -DDUMB_DECLARE_DEPRECATED +WFLAGS_ALLEGRO := -Wno-missing-declarations +OFLAGS := -O2 -ffast-math -fomit-frame-pointer +DBGFLAGS := -DDEBUGMODE=1 -g3 + +CFLAGS_RELEASE := -Iinclude $(WFLAGS) $(OFLAGS) +CFLAGS_DEBUG := -Iinclude $(WFLAGS) $(DBGFLAGS) + +LDFLAGS := -s + + +CORE_EXAMPLES_OBJ := $(addprefix examples/, $(notdir $(patsubst %.c, %.o, $(CORE_EXAMPLES)))) +ALLEGRO_EXAMPLES_OBJ := $(addprefix examples/, $(notdir $(patsubst %.c, %.o, $(ALLEGRO_EXAMPLES)))) + +CORE_EXAMPLES_EXE := $(addprefix examples/, $(notdir $(patsubst %.c, %$(EXE_SUFFIX), $(CORE_EXAMPLES)))) +ALLEGRO_EXAMPLES_EXE := $(addprefix examples/, $(notdir $(patsubst %.c, %$(EXE_SUFFIX), $(ALLEGRO_EXAMPLES)))) + + +CORE_LIB_FILE_RELEASE := $(LIBDIR)/libdumb.a +ALLEGRO_LIB_FILE_RELEASE := $(LIBDIR)/libaldmb.a + +CORE_LIB_FILE_DEBUG := $(LIBDIR)/libdumbd.a +ALLEGRO_LIB_FILE_DEBUG := $(LIBDIR)/libaldmd.a + + +core: $(CORE_LIB_FILE_RELEASE) $(CORE_LIB_FILE_DEBUG) +allegro: $(ALLEGRO_LIB_FILE_RELEASE) $(ALLEGRO_LIB_FILE_DEBUG) + +core-examples: $(CORE_EXAMPLES_EXE) +allegro-examples: $(ALLEGRO_EXAMPLES_EXE) + +core-headers: + +allegro-headers: + +install-core: core + $(call COPY,$(CORE_LIB_FILE_RELEASE),$(LIB_INSTALL_PATH)) + $(call COPY,$(CORE_LIB_FILE_DEBUG),$(LIB_INSTALL_PATH)) + +install-allegro: allegro + $(call COPY,$(ALLEGRO_LIB_FILE_RELEASE),$(LIB_INSTALL_PATH)) + $(call COPY,$(ALLEGRO_LIB_FILE_DEBUG),$(LIB_INSTALL_PATH)) + +ifeq "$(COMSPEC)" "" +install-core-examples: core-examples + $(call COPY,$(CORE_EXAMPLES_EXE),$(BIN_INSTALL_PATH)) + +install-allegro-examples: allegro-examples + $(call COPY,$(ALLEGRO_EXAMPLES_EXE),$(BIN_INSTALL_PATH)) +else +# Don't install the examples on a Windows system. +install-core-examples: +install-allegro-examples: +endif + +install-core-headers: + $(call COPY,$(CORE_HEADERS),$(INCLUDE_INSTALL_PATH)) + +install-allegro-headers: + $(call COPY,$(ALLEGRO_HEADERS),$(INCLUDE_INSTALL_PATH)) + + +uninstall-core: + $(call DELETE,$(LIB_INSTALL_PATH)/$(notdir $(CORE_LIB_FILE_RELEASE))) + $(call DELETE,$(LIB_INSTALL_PATH)/$(notdir $(CORE_LIB_FILE_DEBUG))) + +uninstall-allegro: + $(call DELETE,$(LIB_INSTALL_PATH)/$(notdir $(ALLEGRO_LIB_FILE_RELEASE))) + $(call DELETE,$(LIB_INSTALL_PATH)/$(notdir $(ALLEGRO_LIB_FILE_DEBUG))) + +ifeq "$COMSPEC" "" +uninstall-core-examples: + $(call DELETE,$(patsubst %,$(BIN_INSTALL_PATH)/%,$(notdir $(CORE_EXAMPLES_EXE)))) + +uninstall-allegro-examples: + $(call DELETE,$(patsubst %,$(BIN_INSTALL_PATH)/%,$(notdir $(ALLEGRO_EXAMPLES_EXE)))) +else +# The examples wouldn't have been installed on a Windows system. +uninstall-core-examples: +uninstall-allegro-examples: +endif + +uninstall-core-headers: + $(call DELETE,$(patsubst %,$(INCLUDE_INSTALL_PATH)/%,$(notdir $(CORE_HEADERS)))) + +uninstall-allegro-headers: + $(call DELETE,$(patsubst %,$(INCLUDE_INSTALL_PATH)/%,$(notdir $(ALLEGRO_HEADERS)))) + + +OBJDIR := $(OBJDIR_BASE)/release +CFLAGS := $(CFLAGS_RELEASE) +CORE_LIB_FILE := $(LIBDIR)/libdumb.a +ALLEGRO_LIB_FILE := $(LIBDIR)/libaldmb.a +include make/Makefile.inc + +OBJDIR := $(OBJDIR_BASE)/debug +CFLAGS := $(CFLAGS_DEBUG) +CORE_LIB_FILE := $(LIBDIR)/libdumbd.a +ALLEGRO_LIB_FILE := $(LIBDIR)/libaldmd.a +include make/Makefile.inc + + +$(CORE_EXAMPLES_EXE): examples/%$(EXE_SUFFIX): examples/%.o $(CORE_LIB_FILE_RELEASE) + $(CC) $^ -o $@ $(LDFLAGS) $(LINK_MATH) + +$(ALLEGRO_EXAMPLES_EXE): examples/%$(EXE_SUFFIX): examples/%.o $(ALLEGRO_LIB_FILE_RELEASE) $(CORE_LIB_FILE_RELEASE) + $(CC) $^ -o $@ $(LDFLAGS) $(LINK_ALLEGRO) + +$(CORE_EXAMPLES_OBJ): examples/%.o: examples/%.c include/dumb.h + $(CC) -c $< -o $@ $(CFLAGS_RELEASE) + +$(ALLEGRO_EXAMPLES_OBJ): examples/%.o: examples/%.c include/dumb.h include/aldumb.h + $(CC) -c $< -o $@ $(CFLAGS_RELEASE) -Wno-missing-declarations + + +clean: + $(call DELETE,$(call FIX,$(OBJDIR_BASE)/release/*.o)) + $(call DELETE,$(call FIX,$(OBJDIR_BASE)/debug/*.o)) + $(call DELETE,$(call FIX,examples/*.o)) + +veryclean: clean + $(call DELETE,$(call FIX,$(CORE_LIB_FILE))) + $(call DELETE,$(call FIX,$(ALLEGRO_LIB_FILE))) + $(call DELETE,$(call FIX,$(CORE_EXAMPLES_EXE))) + $(call DELETE,$(call FIX,$(ALLEGRO_EXAMPLES_EXE))) + + +endif # ifdef PLATFORM diff --git a/apps/codecs/dumb/docs/deprec.txt b/apps/codecs/dumb/docs/deprec.txt new file mode 100644 index 0000000000..88ca2e9fda --- /dev/null +++ b/apps/codecs/dumb/docs/deprec.txt @@ -0,0 +1,281 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * deprec.txt - Deprecated functions, why they / / \ \ + * were deprecated, and what to do | < / \_ + * instead. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + + +********************************************** +*** How the functions have been deprecated *** +********************************************** + + + GCC 3.1 and later provide a very useful attribute. The following: + + __attribute__((__deprecated__)) + + when written alongside a function prototype, variable declaration or type + definition, will result in a warning from GCC if any such part of the API + is used. The warning will even tell you where the declaration is, and I + have inserted comments by all the deprecated declarations, telling you + what to do. + + Unfortunately, GCC 2.x and 3.0.x and MSVC do not have any means to + deprecate things. The approach I have taken with these compilers is to + avoid prototyping the declared functions. This means you will get + warnings and errors, and they won't be very helpful. If your program + compiles, you may get strange crashes when you run it, since the compiler + needs the declarations in order to make sure function calls are carried + out correctly. + + If you would like the deprecated parts of the API to be declared, you can + compile with the -DDUMB_DECLARE_DEPRECATED switch for GCC, or the + -D"DUMB_DECLARE_DEPRECATED" switch for MSVC. This will be accepted by + GCC 3.x but is unnecessary. Use this switch with other people's projects + if necessary, but please make the effort to update your own projects to + use the new API, as the deprecated parts may be removed in the future. + + The rest of this file explains why some parts of the API were deprecated, + and how to adapt your code. + + +************************************** +*** What happened to DUH_RENDERER? *** +************************************** + + + The DUH_RENDERER struct was designed for rendering audio to an end-user + format - 8-bit or 16-bit, signed or unsigned, with stereo samples + interleaved. In order for it to do this, it was built on top of the + hitherto undocumented DUH_SIGRENDERER struct, which rendered audio in + DUMB's internal 32-bit signed format with channels (left/right) stored + separately. The DUH_RENDERER struct contained a pointer to a + DUH_SIGRENDERER struct, along with some other data like the position and + number of channels. + + There were then some developments in the API. The DUH_SIGRENDERER struct + also stored the position and the number of channels, so I decided to write + functions for returning these. Suddenly there was no need to store them in + the DUH_RENDERER struct. Before long, the DUH_RENDERER struct contained + nothing but a pointer to a DUH_SIGRENDERER. + + I decided it would be a good idea to unify the structs. After all, there + really is no difference between the data stored in each, and it would be + easy to make duh_render(DUH_RENDERER *dr, ...) and + duh_render_signal(DUH_SIGRENDERER *sr, ...) work on the same type of + struct. (Note that duh_render_signal() is now deprecated too; see the next + section.) It took some deliberation, but I decided I didn't want functions + to be #defined (it prevents you from using these names for member + functions in C++ classes), and that meant they had to be defined + somewhere. Defining redundant functions is a source of bloat, inefficiency + and general inelegance. After weighing things up, I decided it was better + to deprecate the redundant functions and have people begin to use the more + efficient versions, and eventually the redundant functions will be able to + be removed. + + So why did I choose to keep the more complicated name, DUH_SIGRENDERER? + The reason has to do with what DUMB will become in the future. Signals are + an inherent part of the DUH struct and how .duh files will be constructed. + It will be possible to have multiple signals in a single DUH struct, and + you will be able to choose which one you want to play (this is the 'sig' + parameter passed to duh_start_sigrenderer()). But don't hold your breath; + we still have a long way to go before .duh files will start to appear... + + +typedef DUH_SIGRENDERER DUH_RENDERER; + + Wherever you are using DUH_RENDERER in your program, simply replace it + with DUH_SIGRENDERER. An automated (case-sensitive!) search and replace + operation should get this done. + + +DUH_RENDERER *duh_start_renderer(DUH *duh, int n_channels, long pos); + + Use duh_start_sigrenderer() instead. It takes an extra parameter, 'sig', + which comes after 'duh' and before 'n_channels'; pass 0 for this. So an + example would be, replace: + + sr = duh_start_renderer(duh, 2, 0); + + with: + + sr = duh_start_sigrenderer(duh, 0, 2, 0); + + +int duh_renderer_get_n_channels(DUH_RENDERER *dr); +long duh_renderer_get_position(DUH_RENDERER *dr); +void duh_end_renderer(DUH_RENDERER *dr); + + These are easy enough to fix; all you have to do is replace 'renderer' + with 'sigrenderer'. So the new functions are: + + int duh_sigrenderer_get_n_channels(DUH_SIGRENDERER *sigrenderer); + long duh_sigrenderer_get_position(DUH_SIGRENDERER *sigrenderer); + void duh_end_sigrenderer(DUH_SIGRENDERER *sigrenderer); + + +Note that duh_render() has NOT been deprecated. It now uses DUH_SIGRENDERER +instead of DUH_RENDERER, but its functionality is unchanged. You do not have +to change calls to this function in any way. + + +DUH_RENDERER *duh_renderer_encapsulate_sigrenderer(DUH_SIGRENDERER *sr); +DUH_SIGRENDERER *duh_renderer_get_sigrenderer(DUH_RENDERER *dr); +DUH_SIGRENDERER *duh_renderer_decompose_to_sigrenderer(DUH_RENDERER *dr); + + These functions did not exist in the last release of DUMB, so you are + probably not using them, but they are included here for completeness. All + you have to do here is unwrap the function, since the structs have been + unified. So, for instance, replace: + + duh_renderer_encapsulate_sigrenderer(my_sigrenderer) + + with: + + my_sigrenderer + + Simple! + + +AL_DUH_PLAYER *al_duh_encapsulate_renderer(DUH_RENDERER *dr, + float volume, long bufsize, int freq); +DUH_RENDERER *al_duh_get_renderer(AL_DUH_PLAYER *dp); +DUH_RENDERER *al_duh_decompose_to_renderer(AL_DUH_PLAYER *dp); + + Again, these functions were not in the last release, so you probably + aren't using them. Nevertheless, the fix is simple as always: simply + replace 'renderer' with 'sigrenderer'. So the new functions are: + + AL_DUH_PLAYER *al_duh_encapsulate_sigrenderer(DUH_SIGRENDERER *sr, + float volume, long bufsize, int freq); + DUH_SIGRENDERER *al_duh_get_sigrenderer(AL_DUH_PLAYER *dp); + DUH_SIGRENDERER *al_duh_decompose_to_sigrenderer(AL_DUH_PLAYER *dp); + + +********************* +*** Miscellaneous *** +********************* + + +long duh_render_signal(DUH_SIGRENDERER *sigrenderer, + float volume, float delta, + long size, sample_t **samples); + + This function used to return samples in DUMB's internal format. This + format consisted of 32-bit integers whose 'normal range' was -0x8000 to + 0x7FFF (any samples outside this range would have to be clipped when sent + to the sound card). + + DUMB's internal format has changed. DUMB still uses 32-bit integers, but + now the normal range is -0x800000 to 0x7FFFFF. The lowest eight bits are + discarded at the final stage by duh_render() when you ask for 16-bit + output. A new function, duh_sigrenderer_get_samples(), will return samples + in DUMB's new internal format. It takes exactly the same parameters, so + all you have to do to the call itself is change the name; however, you + will most likely have to change your code to account for the new + normalised range. + + duh_render_signal() will still be able to give you the samples in DUMB's + old internal format, but it is inefficient. You should change your code as + soon as possible. + + +typedef void (*DUH_SIGRENDERER_CALLBACK)(void *data, sample_t **samples, + int n_channels, long length); + +void duh_sigrenderer_set_callback(DUH_SIGRENDERER *sigrenderer, + DUH_SIGRENDERER_CALLBACK callback, void *data); + + This callback was intended to allow you to analyse the output. It was by + no means intended to let you modify the output. For this reason, the names + have been changed to DUH_SIGRENDERER_ANALYSER_CALLBACK and + duh_sigrenderer_set_analyser_callback, and the 'samples' parameter to your + callback should now be specified as follows: + + const sample_t *const *samples + + The first 'const' indicates that you must not modify the samples. The + second indicates that you must not modify the pointers to each channel. + + There is a second reason why this change was necessary, and it is the one + described further up for duh_render_signal()'s entry: the format in which + the samples themselves are stored has changed. They are 256 times as + large, with a normal range from -0x800000 to 0x7FFFFF. You will most + likely need to change your code to account for this. + + If you try to call the old function, it will print a message to stderr + directing you to this file, and it will not install the callback. You + shouldn't be able to get this far without a compiler warning (or, if you + don't have GCC 3.1 or later, some compiler errors). + + If you wanted to use this callback to apply a DSP effect, don't worry; + there is a better way of doing this. It is undocumented, so contact me + and I shall try to help. Contact details are at the bottom of this file. + + For reference, here are the new definitions: + + typedef void (*DUH_SIGRENDERER_ANALYSER_CALLBACK)(void *data, + const sample_t *const *samples, int n_channels, long length); + + void duh_sigrenderer_set_analyser_callback(DUH_SIGRENDERER *sigrenderer, + DUH_SIGRENDERER_ANALYSER_CALLBACK callback, void *data); + + +int dumb_resampling_quality; + + This variable has changed meaning. It used to hold a value from 0 to 4, + whose meaning was as follows: + + 0 - aliasing + 1,2 - linear interpolation + 3 - quadratic interpolation + 4 - cubic interpolation + + 0,1 - always use a straightforward interpolation algorithm + 2,3,4 - when decimating (increasing the pitch), use a linear average + algorithm designed to reduce frequencies that would otherwise + reflect off the Nyquist + + Now the variable only holds values from 0 to 2, and these values have + preprocessor constants associated with them. The somewhat inappropriate + quadratic interpolation has been removed. The linear average algorithm has + also been removed, and may or may not come back; there are probably more + efficient ways of achieving the same effect, which I shall be + investigating in the future. + + This change will have hardly any noticeable effect on existing programs. + Levels 2, 3 and 4 used considerably more processor time because of the + linear average algorithm. Likewise, Level 2 in the new scheme (cubic) uses + considerably more processor time than Levels 1 and 0, and Levels 3 and 4 + will behave identically to Level 2. + + +****************** +*** Conclusion *** +****************** + + +"I conclude that... DUMB is the bestest music player in the world because... +Complete this sentence in fifteen words or fewer... D'OH!" + +The preceding conclusion formerly appeared in dumb.txt, and is deprecated +because it's lame. + + +Ben Davis +entheh@users.sf.net +IRC EFnet #dumb +See readme.txt for details on using IRC. diff --git a/apps/codecs/dumb/docs/dumb.txt b/apps/codecs/dumb/docs/dumb.txt new file mode 100644 index 0000000000..86b2cc3374 --- /dev/null +++ b/apps/codecs/dumb/docs/dumb.txt @@ -0,0 +1,1699 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * dumb.txt - DUMB library reference. / / \ \ + * | < / \_ + * See readme.txt for general information on | \/ /\ / + * DUMB and how to set it up. \_ / > / + * | \ / / + * If you are new to DUMB, see howto.txt. | ' / + * \__/ + */ + + +*********************************** +*** Include Files and Libraries *** +*********************************** + + +dumb.h + + Include this if you only want the core DUMB library functions. You will + be able to load music files and render them into memory buffers at your + own pace. The core library is completely portable, and as such does not + access hardware; you must relay the sound data to the sound card yourself. + A stdio file input module is available, but you must actively register it + if you wish to use it (see dumb_register_stdfiles()); if you do not + register it, it will not be linked into your executable. You must register + it, or a DUMBFILE module of your own, in order to load stand-alone music + files. + + Optimised: -ldumb or /link dumb.lib + Debugging: -ldumbd or /link dumbd.lib + + +aldumb.h + + Include this if you wish to use DUMB with Allegro. This will provide you + with functions to play DUHs back through Allegro's audio streams and embed + music files in Allegro datafiles. A file input module using Allegro's + packfiles is provided; you have a choice between this and the stdio + module (or provide one of your own). You will be able to load datafiles + containing music files no matter which file input module you register, or + even if you register no file input module. However, you must register a + file input module in order to load stand-alone files. + + Optimised: -laldmb -ldumb -lalleg or /link aldmb.lib alleg.lib dumb.lib + Debugging: -laldmd -ldumbd -lalld or /link aldmd.lib alld.lib dumbd.lib + + aldmb or aldmd must be linked in first, so the symbols can be resolved + when linking in the other two libraries. + + +*************************** +*** Version Information *** +*************************** + + +#define DUMB_MAJOR_VERSION +#define DUMB_MINOR_VERSION +#define DUMB_REVISION_VERSION + + Numeric constants representing this version of DUMB. If this were version + 1.0, DUMB_MAJOR_VERSION would be 1 and DUMB_MINOR_VERSION would be 0. + DUMB_REVISION_VERSION will be 0 on any significant releases, and will be + incremented as releases with bugfixes and minor features are made. + + Typical usage: + + #if DUMB_MAJOR_VERSION < 1 + #error This add-on requires DUMB v1.0 or higher. Please upgrade. + #endif + + +#define DUMB_VERSION + + A numeric constant which appears in the format MMmmrr when displayed in + decimal (M for major, m for minor, r for revision). This is most useful + for comparing version numbers; it has little other practical use. + + Typical usage: + + #if DUMB_VERSION < 801 + #error This game requires DUMB v0.8.1 or higher. Please upgrade. + #endif + + #if DUMB_VERSION < 10002 + #error This game requires DUMB v1.0.2 or higher. Please upgrade. + #endif + + +#define DUMB_VERSION_STR + + String constant representing this version of DUMB. If this were Version + 1.0, DUMB_VERSION_STR would be "1.0". DUMB_REVISION_VERSION will only + appear on the end if it is nonzero; then DUMB_VERSION_STR might be + "1.0.1". + + +#define DUMB_NAME + + A string identifying DUMB and its version. If this were Version 1.0, + DUMB_NAME might be "DUMB v1.0". This constant is suitable for use in your + Credits screen if you wish to acknowledge the use of DUMB there. + + +#define DUMB_YEAR +#define DUMB_MONTH +#define DUMB_DAY + + Numeric constants representing the year, month and day of this release of + DUMB. All four digits are included in the year. Please note that + DUMB_MONTH and DUMB_DAY were inadvertently swapped in the v0.8 release. + + +#define DUMB_YEAR_STR4 +#define DUMB_YEAR_STR2 +#define DUMB_MONTH_STR2 +#define DUMB_MONTH_STR1 +#define DUMB_DAY_STR2 +#define DUMB_DAY_STR1 + + String constants representing the year, month and day of this release of + DUMB. DUMB_MONTH_STR2 and DUMB_DAY_STR2 include a leading zero if the + month or day respectively are less than ten; the STR1 variations do not. + DUMB_YEAR_STR2 contains only the two rightmost digits of the year, while + DUMB_YEAR_STR4 contains all four. I recommend using DUMB_YEAR_STR4, + especially so soon after the turn of the century (indeed the millennium). + However, it is a matter of personal preference which you use. + + Please note that the month and day were inadvertently swapped in the v0.8 + release. + + +#define DUMB_DATE + + A numeric constant that appears in the form yyyymmdd when displayed in + decimal. This is most useful for comparing release dates; it has little + other practical use. + + WARNING: The month and day were inadvertently swapped in the v0.8 release. + Please do not compare this constant against any date in 2002. In + any case, DUMB_VERSION is probably more useful for this purpose. + + +#define DUMB_DATE_STR + + The date as a string. The format is "d.m.yyyy", with dots used as + separators, the day written first, four digits for the year, and no + leading zeros on the day or month. This is my preferred format. If you + don't like it, you can construct your own format using the other + constants. For example, "mm/dd/yy" could be constructed as follows: + + DUMB_MONTH_STR2 "/" DUMB_DAY_STR2 "/" DUMB_YEAR_STR2 + + Please note that the month and day were inadvertently swapped in the v0.8 + release. + + +************************* +*** Basic Sample Type *** +************************* + + +typedef int sample_t; + + DUMB works internally with 32-bit integer samples, with a 'normal range' + from -0x800000 to 0x7FFFFF (as of DUMB v0.9.2; previously they ranged from + -0x8000 to 0x7FFF). Any samples that exceed this range will eventually be + clipped, and could cause integer overflow in extreme cases. + + +*********************************** +*** Library Clean-up Management *** +*********************************** + + +int dumb_atexit(void (*proc)(void)); + + Registers a function to be called at the end of your program. You can + register multiple functions to be called, and the one you register last + will be called first. If you try to register the same function twice, the + second attempt will have no effect. + + See fnptr.txt for help with function pointers. + + You must call dumb_exit() before exiting your program for this to work + properly. The library itself registers functions with dumb_atexit(), so it + is important to call dumb_exit() even if you do not use dumb_atexit() + yourself. + + This function will return zero on success. It will return zero when + trying to install the same function twice. If it fails through lack of + memory, it will return nonzero. Generally you can ignore the return code; + in the worst case some memory will not be freed at the end. If it is + crucial that your function be called (e.g. to shut down some hardware or + save critical data), then you should call your function manually at the + end of the program instead of registering it here - or use the stdlib + function atexit(), guaranteed under ANSI C to succeed for at least 32 + functions. + + +void dumb_exit(void); + + You should call this before exiting your program if you have used any part + of DUMB in the program. Some parts of DUMB will allocate memory, and this + function will free it all up. + + More specifically, this function will call any functions that have been + registered with dumb_atexit(). If a part of DUMB needs shutting down, the + shutdown procedure will have been registered in this way. + + dumb_exit() will, of course, also call any functions you registered with + dumb_atexit() yourself. + + After a call to dumb_exit(), the list of functions is erased. If you are + not ready to exit your program, you can start using DUMB anew as if your + program had just started. (Note that not everything will be reset in + practice - dumb_resampling_quality will retain whatever you set it to, for + example, though you should not assume it will.) + + If you only need to call dumb_exit() once at the end of the program, you + can use the following to register dumb_exit() with stdlib.h atexit(): + + #include + + atexit(&dumb_exit); + + Then dumb_exit() will be called for you when your program exits. This is + the recommended method, since it will ensure clean-up even if your program + aborts. You should only call dumb_exit() manually if you need to shut DUMB + down prematurely, or if atexit() is unavailable for one reason or another. + + +***************************** +*** Sequential File Input *** +***************************** + + + DUMB provides a strictly sequential file input system which uses the + DUMBFILE struct. "Strictly sequential" means you cannot seek backwards. + However, the system will keep track of how many bytes you have read, + enabling you to seek forwards. DUMBFILEs provide a convenient error + detection system, so you do not have to check the return value from every + function call in the way you do with the ANSI C functions. + + Note that DUMBFILEs cannot be used for output, nor can they be used + portably for text files. + + If an error occurs when reading data from a DUMBFILE, the DUMBFILE will + become inoperative. All subsequent activities on the DUMBFILE will return + error codes without attempting to read from the file. The position in the + file will also be forgotten. You can find out if this has happened at any + stage with the dumbfile_error() function. You are still required to close + the DUMBFILE, and the return value from dumbfile_close() will tell you if + an error has occurred. + + This system allows you to input large chunks of your file, neither + checking every return value nor wasting time accessing a file that has + already experienced an error. However, before you allocate an amount of + memory or read in a quantity of data depending on previous input from the + file, you should always check that such input was valid. In particular you + should avoid passing zero or negative numbers to malloc(), and avoid + passing negative numbers to dumbfile_skip() and dumbfile_getnc(). + + DUMBFILEs can be hooked. In other words, you can specify your own + functions to do the work of reading from a file. While DUMB contains two + modules for this purpose, it does not set them up for you automatically. + In most cases you must register one of these modules yourself, or provide + your own module. See register_dumbfile_system(), dumb_register_stdfiles() + and dumb_register_packfiles(). + + +void register_dumbfile_system(DUMBFILE_SYSTEM *dfs); + + Use this function to register a set of functions for use by the DUMBFILEs + (a DUMBFILE system). The DUMBFILE_SYSTEM struct contains the following + fields: + + void *(*open)(const char *filename); + int (*skip)(void *f, long n); + int (*getc)(void *f); + long (*getnc)(char *ptr, long n, void *f); + void (*close)(void *f); + + See fnptr.txt for help with function pointers such as these. + + Your 'open' function should open the file specified and return a pointer + to a struct representing the open file. This pointer will be passed to + your other functions as 'f'. Your 'close' function should close the file + and free all memory pointed to by 'f'. Note that the 'close' operation + should never be able to fail; if you are calling a function with a return + value, you can generally ignore it. + + Your 'getc' function should read one byte from the file and return its + value in the range 0 to 255. If an error occurs, you should return -1. Do + not worry about remembering that an error has occurred; DUMB will do that + for you. + + 'skip' is for skipping parts of the file, and should skip n bytes, + returning 0 on success or any other number on failure. 'getnc' should read + n bytes from the file, store them at 'ptr', and return the number of bytes + read (n on success, fewer on failure). However, these two functions are + optional, and you should only provide them if the operations can be done + more efficiently than with repeated calls to your 'getc' function. If this + is not the case, specify NULL for 'skip', 'getnc' or both, and DUMB will + use your 'getc' function to do the work. + + Once you have written all your functions, you need to create a + DUMBFILE_SYSTEM struct to hold them, and pass its pointer to + register_dumbfile_system(). + + The DUMBFILE_SYSTEM struct must be permanent. In other words, it must be + either global or static, and you should not modify it later. DUMB will not + make its own copy. + + You will most likely create your own struct to represent the open file, + but do not be tempted to specify that struct in the function prototypes + and pacify the compiler warnings by casting your function pointers. There + exist computer systems where a (void *) pointer and a (MY_STRUCT *) + pointer are represented differently in memory, and a cast of such a + pointer causes a tangible conversion to take place. If you cast the + function pointers, the computer cannot know when such a conversion is + necessary. Instead, use the following structure: + + int myskip(void *f, long n) + { + FILE *file = f; + /* Do some stuff with 'file' */ + return something; + } + + If you need examples, have a look at the two existing DUMBFILE systems in + dumb/src/core/stdfile.c and dumb/src/allegro/packfile.c. + + +DUMBFILE *dumbfile_open(const char *filename); + + Open the specified file for input. You must pass the DUMBFILE pointer + whenever you wish to operate on this file. When you have finished with the + file, you must pass it to dumbfile_close(). + + Before you use this function, make sure you have registered a DUMBFILE + system. See register_dumbfile_system(), dumb_register_stdfiles() and + dumb_register_packfiles(). + + You must check the return value from this function. If it is NULL, the + file could not be opened, and you must not pass the DUMBFILE to any other + function. The debugging library will abort if you get this wrong; the + optimised library will act weird. + + +DUMBFILE *dumbfile_open_ex(void *file, DUMBFILE_SYSTEM *dfs); + + This function is provided for more specialised use. You should create a + DUMBFILE_SYSTEM specially for the purpose. Its 'open' field is irrelevant; + for neatness, set it to NULL, unless you are using this DUMBFILE_SYSTEM + with register_dumbfile_system() as well. + + When you have called this function, the DUMBFILE struct it returned can be + used as normal. The specified DUMBFILE_SYSTEM will be used for all input, + with 'file' passed to your 'skip', 'getc' and 'getnc' functions as 'f'. + This can be used, for example, to read from an already open file. + + Note that the position will always be initialised to 0 for this DUMBFILE. + This means for example that offsets in the file do not need adjusting when + embedding data in a larger file. + + There are two ways to use this function. If you want 'file' to persist + after using a DUMBFILE returned by this function, you should make sure the + 'close' field in the DUMBFILE is set to NULL. When the DUMBFILE is closed, + 'file' will be left alone, and you can and should deal with it yourself + when the DUMBFILE has been closed. + + Alternatively, you can provide a 'close' function to get rid of 'file' for + you when the DUMBFILE is closed. If you do this, you should not otherwise + use 'file' after a call to this function. + + If dumbfile_open_ex() has to return NULL, owing to lack of memory, then + your 'close' function will be called if provided. In other words, if you + have provided a 'close' function, then you no longer need to worry about + 'file' whether this function succeeds or not. + + See dumb/src/helpers/stdfile.c and dumb/src/allegro/packfile.c for + examples of how to use this function. Neither provides a 'close' function, + so I hope my explanation here will suffice. If not, please feel free to + contact me so I can make the explanation clearer and help you do what you + want to do. Contact details are at the end of this file. + + +long dumbfile_pos(DUMBFILE *f); + + Returns the number of bytes read from the DUMBFILE (or skipped) since it + was opened, or -1 if an error has occurred while reading. + + +int dumbfile_skip(DUMBFILE *f, long n); + + Skips n bytes of the specified DUMBFILE. Returns zero on success. + + +int dumbfile_getc(DUMBFILE *f); + + Reads one byte from the DUMBFILE and returns it in unsigned format (from 0 + to 255). If an error occurs, or occurred before, this function returns -1. + + +int dumbfile_igetw(DUMBFILE *f); + + Reads two bytes from the DUMBFILE and combines them into a word ranging + from 0 to 65535. The first byte read is the least significant byte, as + with Intel processors. This function returns -1 on error. + + +int dumbfile_mgetw(DUMBFILE *f); + + Reads two bytes from the DUMBFILE and combines them into a word ranging + from 0 to 65535. The first byte read is the most significant byte, as + with the Apple Macintosh. This function returns -1 on error. + + +long dumbfile_igetl(DUMBFILE *f); + + Reads four bytes from the DUMBFILE and combines them into a long integer + ranging from -2147483648 to 2147483647. The first byte read is the least + significant byte, as with Intel processors. This function returns -1 on + error, but -1 is also a valid return value. After a call to this function, + you can use dumbfile_error() to find out if an error occurred. + + +long dumbfile_mgetl(DUMBFILE *f); + + Reads four bytes from the DUMBFILE and combines them into a long integer + ranging from -2147483648 to 2147483647. The first byte read is the most + significant byte, as with the Apple Macintosh. This function returns -1 on + error, but -1 is also a valid return value. After a call to this function, + you can use dumbfile_error() to find out if an error occurred. + + +unsigned long dumbfile_cgetul(DUMBFILE *f); + + Reads an unsigned (nonnegative) integer from the DUMBFILE. The integer is + stored in a condensed format where smaller numbers use less space: + + 0 to 127 1 byte + 128 to 16383 2 bytes + 16384 to 2097151 3 bytes + 2097152 to 268435455 4 bytes + 268435456 to 4294967295 5 bytes + + This format is the same as that used for the times between notes in MIDI + files. + + If an error occurs, this function returns (unsigned long)(-1), but that + may be a valid return value. After a call to this function, you can use + dumbfile_error() to find out if an error occurred. + + +signed long dumbfile_cgetsl(DUMBFILE *f); + + Reads a signed integer from the DUMBFILE. The integer is stored in a + condensed format where numbers closer to zero use less space: + + -64 to 63 1 byte + -8192 to 8191 2 bytes + -1048576 to 1048575 3 bytes + -134217728 to 134217727 4 bytes + -2147483648 to 2147483647 5 bytes + + If an error occurs, this function returns -1, but -1 is also a valid + return value. After a call to this function, you can use dumbfile_error() + to find out if an error occurred. + + +long dumbfile_getnc(char *ptr, long n, DUMBFILE *f); + + Reads n bytes from the DUMBFILE and stores them at 'ptr'. Note that the + pointer is to a series of chars. You may also use this function to read in + a series of signed chars or unsigned chars (which are both officially + distinct types from char), but do not use this to read ints, structs or + any other data type from the file. Integers must be read one at a time + using dumbfile_igetl(), dumbfile_cgetul(), etc. To load a struct in, you + must read each field separately using an appropriate function for each + one. For complicated data types, you can simplify this process by writing + a function for each struct. + + dumbfile_getnc() returns the number of bytes successfully read, which will + be less than n if an error occurs, and may be as low as zero. If + dumbfile_getnc() returns -1, that means an error occurred on this DUMBFILE + earlier, before this function was called. + + +int dumbfile_error(DUMBFILE *f); + + This function returns -1 if an error has occurred with the specified + DUMBFILE, or 0 if all is well. + + +int dumbfile_close(DUMBFILE *f); + + This function closes the DUMBFILE, after which the pointer will be + invalid. dumbfile_close() returns the value that dumbfile_error() would + have returned, which is -1 if an error occurred while reading or 0 + otherwise. Regardless of the return value, the file will always be closed + properly. + + +******************************* +*** stdio File Input Module *** +******************************* + + +void dumb_register_stdfiles(void); + + This function registers the stdio file input module for use by DUMBFILEs. + FILE structs and their corresponding functions, as defined by the ANSI C + header stdio.h, will be used internally for all DUMBFILE input (unless + opened with dumbfile_open_ex()). + + This must be called before dumbfile_open() is used, or else an alternative + system must be registered (see register_dumbfile_system() and + dumb_register_packfiles()). + + +DUMBFILE *dumbfile_open_stdfile(FILE *p); + + If you have a stdio FILE struct representing an open file, you can call + this if you wish to read from it using a DUMBFILE. This is useful when you + need to pass a DUMBFILE struct to a library function, to read an embedded + music file for example. When you close the DUMBFILE, you can continue + using the FILE struct to read what follows the embedded data. + + +******************************** +*** Memory File Input Module *** +******************************** + + +DUMBFILE *dumbfile_open_memory(const char *data, long size); + + This function is useful if you have an image of a music file in memory. + You might have such an image if you use dat2s to encode a datafile + directly into the executable. Pass a pointer to the start of the memory, + and the size of the image to make sure DUMB doesn't overrun the buffer. + The resulting DUMBFILE will feed the contents of the image to you. + + Note that the pointer is of type 'char *'. Files are series of chars, and + interpreting them directly as anything else isn't portable. + + +********************** +*** DUH Management *** +********************** + + +void unload_duh(DUH *duh); + + Removes a DUH from memory. You must call this for all DUHs you load, + making sure they're not playing at the time. + + +long duh_get_length(DUH *duh); + + Returns the length of a DUH; 65536 represents one second. This value is + calculated when the DUH is created, and this function simply lifts it from + the struct. It may not truly correspond to the time for which the DUH will + generate sound. For module files, it will represent the point at which the + module first loops (or, in the case of some XM and MOD files, freezes). + Any add-ons to DUMB will provide their own code for calculating this. + + The algorithm for calculating the length of a module file can be fooled, + but only by very deliberate methods. In the early days, when modules could + only be played by their editors and had to be exported to .wav or similar + in order to be used elsewhere, musicians would sometimes make the player + think it was looping when it wasn't in order to prevent their music from + being exported properly. If the length of a module seems a lot less than + it should be, the module is probably protected in this way. + + Getting around this protection reliably would be extremely difficult, but + after considering it for a while I decided it would be better not to. The + musician has a right to protect his or her music in this way, and I have + no interest in actively breaking that protection. + + (On the other hand, some musicians were just showing off!) + + +*********************************** +*** IT, XM, S3M and MOD Support *** +*********************************** + + +int dumb_it_max_to_mix; + + Specifies the maximum number of samples DUMB will mix at any one time. The + default number is 64. Regardless of this value, all samples will continue + to be processed up to an internal maximum of 256 (roughly speaking; in + fact it will process one sample for each channel plus up to 192 extra + samples that are continuing to play owing to Impulse Tracker's New Note + Actions), and samples that have been cut will sound again as soon as the + congestion clears. Samples are given priority according to their final + volume after all factors affecting the volume of a sample have been + considered. + + If you play two or more modules at once, this value represents the + maximum number of samples for each one. You will have to reduce it further + if your computer cannot keep up. + + Despite the name, this variable controls XM, S3M and MOD files as well as + IT files. + + +DUMB_IT_SIGDATA *duh_get_it_sigdata(DUH *duh); + + This function attempts to retrieve the DUMB_IT_SIGDATA struct from a DUH. + This struct will exist for any IT, XM, S3M or MOD file, and you can use it + to obtain or override module-specific information. If 'duh' is NULL, or if + the DUH you pass contains something other than a music module, then this + function will return NULL (which can safely be passed to any other + function). + + +DUMB_IT_SIGRENDERER *duh_get_it_sigrenderer(DUH_SIGRENDERER *sigrenderer); + + This function attempts to retrieve the DUMB_IT_SIGRENDERER struct from a + DUH_SIGRENDERER. This struct will exist for any currently playing IT, XM, + S3M or MOD file, and you can use it to obtain or override information + specific to module playback. If 'sigrenderer' is NULL, or if the + DUH_SIGRENDERER you pass is rendering something other than a music module, + then this function will return NULL (which can safely be passed to any + other function). + + +DUH_SIGRENDERER *dumb_it_start_at_order + (DUH *duh, int n_channels, int startorder); + + This function, given a DUH containing an IT, XM, S3M or MOD file, will + start playing it at the specified order. If the DUH does not contain a + module, this function will fail and return NULL. + + Note that starting at an arbitrary order may result in missing notes or + other playback oddities. It should be used primarily for modules that + contain multiple songs that start on different orders. If you wish just to + start some music in the middle, consider using duh_start_sigrenderer() or + al_start_duh() with the pos parameter set appropriately. + + +void dumb_it_set_loop_callback(DUMB_IT_SIGRENDERER *sigrenderer, + int (*callback)(void *data), void *data); + + Installs a callback which will be called every time the module loops. You + can pass any data pointer you like, and it will be passed to the callback + for you. DUMB considers a file to loop when it reaches the end, or when a + 'Jump to order' effect (Bxx in both IT/S3M and XM/MOD) jumps to the same + order or a preceding order. This can result in the loop callback being + called when the module isn't really looping, but this only happens if the + module has a very deliberate design. See duh_get_length() for further + musings on this subject. + + If your callback returns nonzero, the music will stop abruptly. Samples + will be cut, and the main program will be notified that the + DUH_SIGRENDERER has ended. + + Alternatively, if you pass the DUMB_IT_SIGRENDERER for 'data', or + otherwise arrange for it to be available to the callback, then you can + call: + + dumb_it_sr_set_speed(sigrenderer, 0); + + from inside the callback, and this will cause the music to freeze but + samples will be able to continue playing. The xm_speed_zero callback will + NOT be called in this case (see below for information on this callback). + Note also that setting the speed in this way will work equally for IT and + S3M files, even though a 'speed zero' effect can only exist in XM and MOD + files. Beware when using this method; samples might not fade at all! + + A helper callback, dumb_it_callback_terminate(), is provided; installing + this will cause the music to terminate when it tries to loop for the first + time. + + Pass NULL to remove the callback function; the module will then loop as + normal. + + +void dumb_it_set_xm_speed_zero_callback(DUMB_IT_SIGRENDERER *sigrenderer, + int (*callback)(void *data), void *data); + + Installs a callback which is in many ways similar to the loop callback + (see dumb_it_set_loop_callback()). This callback will be called whenever + an F00 effect is encountered in a MOD or XM file, setting the speed to + zero. If the callback returns nonzero, the music will terminate. If not, + any currently playing samples will continue to play. You can pass any data + pointer you like to this function, and it will be passed to your callback + for you. + + The helper callback, dumb_it_callback_terminate(), will also work here; + installing it will cause the music to terminate as soon as an F00 effect + is encountered. + + Pass NULL to remove the callback function. + + +void dumb_it_set_midi_callback(DUMB_IT_SIGRENDERER *sigrenderer, + int (*callback)(void *data, int channel, unsigned char byte), + void *data); + + Installs a callback function which will be called whenever MIDI data are + generated by an IT file. (No other module formats are capable of + generating MIDI data, so your callback will never be called.) + + Zxx macros will generate MIDI data. These are most often used to set the + parameters for IT's low-pass resonant filters, and DUMB will handle these + messages by itself by default. See Impulse Tracker's documentation for + the MIDI messages that control filters. However, Zxx macros can be used + to send any kind of MIDI data. + + If you wish to interpret MIDI messages yourself, you can use this + callback. Note that the only MIDI messages generated by DUMB at present + are from Zxx macros; there are no messages for note start, stop, or + anything else. + + If you return 1 from this callback, DUMB will subsequently ignore the byte + of MIDI data. You can use this to prevent Zxx macros from controlling the + filters, useful if they were intended to do something else. Note that this + is NOT an effective way to disable filters, since instruments can have + filter envelopes and initial filter parameters. DUMB provides no means to + disable filters, as any IT file that uses them will sound wrong without + them. If you want lower processor consumption, use a different piece of + music. + + A helper callback, dumb_it_callback_midi_block(), is provided for blocking + all MIDI messages and making Zxx macros do nothing. + + Pass NULL to remove the callback. + + +int dumb_it_callback_terminate(void *data); + + This is a helper callback that can be installed with both + dumb_it_set_loop_callback() and dumb_it_set_xm_speed_zero_callback(). In + each case it will cause the music to terminate abruptly. + + +int dumb_it_callback_midi_block(void *data, int channel, unsigned char byte); + + This helper callback, for use with dumb_it_set_midi_callback(), will + absorb all MIDI messages, returning 1 to prevent DUMB from interpreting + them itself. + + +DUH *dumb_load_it(const char *filename); + + Loads the specified Impulse Tracker file, encapsulating it in a DUH + struct. Once the file is loaded, it can be treated exactly the same as any + other DUH in memory. If this fails it will return NULL, but you can safely + pass this NULL value to DUMB's other functions, so you do not need to + check the return value explicitly. + + +DUH *dumb_read_it(DUMBFILE *f); + + Reads an Impulse Tracker file from an already open DUMBFILE. This leaves + the DUMBFILE open, but the DUMBFILE may not be positioned at the end of + the IT data. If you are embedding an IT in another file, you are advised + to store the size of the IT file and make up for it at the end using + dumbfile_pos(). + + Otherwise, this function is identical to dumb_load_it(). + + WARNING: The behaviour of this function is undefined if you pass a + DUMBFILE from which data have already been read; it is likely not + to work. This oversight will be fixed in future releases. + + +DUH *dumb_load_xm(const char *filename); + + Loads the specified Fast Tracker II file, encapsulating it in a DUH + struct. Once the file is loaded, it can be treated exactly the same as any + other DUH in memory. If this fails it will return NULL, but you can safely + pass this NULL value to DUMB's other functions, so you do not need to + check the return value explicitly. + + +DUH *dumb_read_xm(DUMBFILE *f); + + Reads a Fast Tracker II file from an already open DUMBFILE. This leaves + the DUMBFILE open, but the DUMBFILE may not be positioned at the end of + the XM data. If you are embedding an XM in another file, you are advised + to store the size of the XM file and make up for it at the end using + dumbfile_pos(). + + Otherwise, this function is identical to dumb_load_xm(). + + WARNING: The behaviour of this function is undefined if you pass a + DUMBFILE from which data have already been read; it is likely not + to work. This oversight will be fixed in future releases. + + +DUH *dumb_load_s3m(const char *filename); + + Loads the specified Scream Tracker 3 file, encapsulating it in a DUH + struct. Once the file is loaded, it can be treated exactly the same as any + other DUH in memory. If this fails it will return NULL, but you can safely + pass this NULL value to DUMB's other functions, so you do not need to + check the return value explicitly. + + +DUH *dumb_read_s3m(DUMBFILE *f); + + Reads a Scream Tracker 3 file from an already open DUMBFILE. This leaves + the DUMBFILE open, but the DUMBFILE may not be positioned at the end of + the S3M data. If you are embedding an S3M in another file, you are advised + to store the size of the S3M file and make up for it at the end using + dumbfile_pos(). + + Otherwise, this function is identical to dumb_load_s3m(). + + WARNING: The behaviour of this function is undefined if you pass a + DUMBFILE from which data have already been read; it is likely not + to work. This oversight will be fixed in future releases. + + +DUH *dumb_load_mod(const char *filename); + + Loads the specified Amiga module file, encapsulating it in a DUH struct. + Once the file is loaded, it can be treated exactly the same as any other + DUH in memory. If this fails it will return NULL, but you can safely pass + this NULL value to DUMB's other functions, so you do not need to check the + return value explicitly. + + +DUH *dumb_read_mod(DUMBFILE *f); + + Reads an Amiga module file from an already open DUMBFILE. This leaves the + DUMBFILE open, but the DUMBFILE may not be positioned at the end of the + MOD data. If you are embedding a MOD in another file, you are advised to + store the size of the MOD file and make up for it at the end using + dumbfile_pos(). + + Otherwise, this function is identical to dumb_load_mod(). + + WARNING: The behaviour of this function is undefined if you pass a + DUMBFILE from which data have already been read; it is likely not + to work. This oversight will be fixed in future releases. + + +int dumb_it_sd_get_n_orders(DUMB_IT_SIGDATA *sd); + + This function returns the number of orders in the module. + + +int dumb_it_sd_get_initial_global_volume(DUMB_IT_SIGDATA *sd); +void dumb_it_sd_set_initial_global_volume(DUMB_IT_SIGDATA *sd, int gv); + + These functions obtain and set the initial global volume for the module. + This value ranges from 0 to 128 inclusive. The module can set the global + volume itself during playback, so your change may not last throughout the + playback. + + +int dumb_it_sd_get_mixing_volume(DUMB_IT_SIGDATA *sd); +void dumb_it_sd_set_mixing_volume(DUMB_IT_SIGDATA *sd, int mv); + + These functions obtain and set the mixing volume for the module. This + value ranges from 0 to 128 inclusive, and does not change during playback. + IT files have the mixing volume stored in them; for other formats it is + set to 48 on loading. + + +int dumb_it_sd_get_initial_speed(DUMB_IT_SIGDATA *sd); +void dumb_it_sd_set_initial_speed(DUMB_IT_SIGDATA *sd, int speed); +int dumb_it_sd_get_initial_tempo(DUMB_IT_SIGDATA *sd); +void dumb_it_sd_set_initial_tempo(DUMB_IT_SIGDATA *sd, int tempo); + + These functions obtain and set the initial speed and tempo for the module. + During module playback, everything happens on a tick. If a beat is 24 + ticks, then the tempo is measured in beats per second. The speed is then + the number of ticks per row. With a speed of 6, a beat is then four rows. + + Modules can set these values during playback, so your change may not last + throughout the playback. MOD files have to set the speed and tempo on the + first row if they want anything other than the default 6/125, so your + change may not be noticed at all! + + +int dumb_it_sd_get_initial_channel_volume(DUMB_IT_SIGDATA *sd, int channel); +void dumb_it_sd_set_initial_channel_volume(DUMB_IT_SIGDATA *sd, int channel, + int volume); + + These functions obtain and set the initial volume for the specified + channel. The channel parameter is 0-based (contrary to the display in most + trackers so be careful), and can range from 0 to DUMB_IT_N_CHANNELS - 1, + i.e. from 0 to 63. + + Modules can set their channel volumes during playback, so your changes may + not last throughout the playback. + + +int dumb_it_sr_get_current_order(DUMB_IT_SIGRENDERER *sr); +int dumb_it_sr_get_current_row(DUMB_IT_SIGRENDERER *sr); + + These functions return the current order and row of playback. Both are + 0-based. If the DUMB_IT_SIGRENDERER is invalid, or has been terminated + by a callback (see dumb_it_set_loop_callback() and + dumb_it_set_xm_speed_zero_callback()), these functions will both return + -1. + + +int dumb_it_sr_get_global_volume(DUMB_IT_SIGRENDERER *sr); +void dumb_it_sr_set_global_volume(DUMB_IT_SIGRENDERER *sr, int gv); + + These functions obtain and set the current global volume for the module. + This value ranges from 0 to 128 inclusive. The module can set the global + volume itself during playback, so your change may not last. + + +int dumb_it_sr_get_tempo(DUMB_IT_SIGRENDERER *sr); +void dumb_it_sr_set_tempo(DUMB_IT_SIGRENDERER *sr, int tempo); +int dumb_it_sr_get_speed(DUMB_IT_SIGRENDERER *sr); +void dumb_it_sr_set_speed(DUMB_IT_SIGRENDERER *sr, int speed); + + These functions obtain and set the current speed and tempo of the module. + See the dumb_it_sd_*() equivalents of these functions for details on what + the speed and tempo mean. + + Modules can set these values during playback, so your change may not last. + + +int dumb_it_sr_get_channel_volume(DUMB_IT_SIGRENDERER *sr, int channel); +void dumb_it_sr_set_channel_volume(DUMB_IT_SIGRENDERER *sr, int channel, + int volume); + + These functions obtain and set the current volume for the specified + channel. The channel parameter is 0-based (contrary to the display in most + trackers so be careful), and can range from 0 to DUMB_IT_N_CHANNELS - 1, + i.e. from 0 to 63. + + Modules can set their channel volumes during playback, so your changes may + not last. + + +void dumb_it_sr_get_channel_state(DUMB_IT_SIGRENDERER *sr, int channel, + DUMB_IT_CHANNEL_STATE *state); + + Returns the current playback state of the given channel. If you pass a + channel in the range 0 to DUMB_IT_N_CHANNELS-1 (0 to 63), you will get the + state of the most recently played note on that physical channel, if it is + still playing. For MOD, S3M and XM files, that's all there is to it. + + IT files can have more than one note playing on a single channel, courtesy + of New Note Actions. This function also lets you query all the notes that + have been forced into the background and are still playing. For this, set + 'channel' to a value from DUMB_IT_N_CHANNELS to DUMB_IT_TOTAL_CHANNELS-1. + DUMB_IT_TOTAL_CHANNELS is defined as follows: + + #define DUMB_IT_TOTAL_CHANNELS \ + (DUMB_IT_N_CHANNELS + DUMB_IT_N_NNA_CHANNELS) + + Querying these background channels for MOD, S3M and XM files will not do + any harm; the function will report that these channels are inactive. For + all files, be sure not to query any channel numbers greater than or equal + to DUMB_IT_TOTAL_CHANNELS. + + You must provide a pointer to a preallocated DUMB_IT_CHANNEL_STATE struct. + The easiest way to do this is as follows: + + DUMB_IT_CHANNEL_STATE state; + dumb_it_sr_get_channel_state(sr, channel, &state); + + or: + + DUMB_IT_CHANNEL_STATE state[IT_TOTAL_CHANNELS]; + dumb_it_sr_get_channel_state(sr, channel, &state[channel]); + + This struct contains the following fields: + + int channel; + int sample; + int freq; + float volume; + unsigned char pan; + signed char subpan; + unsigned char filter_cutoff; + unsigned char filter_subcutoff; + unsigned char filter_resonance; + + The first field to check is 'sample'; if this is 0, then the channel is + inactive and the other fields are undefined. Otherwise, it is the index of + the currently playing sample, and is 1-based. + + The channel number is returned, 0-based. This will be the same as the + channel number you passed, unless you are querying a background channel in + which case it will represent the channel the note originated on. + + The freq field is the current playback frequency, taking into account all + phenomena such as slides, vibrato and arpeggio. + + The volume field ranges from 0.0f to 1.0f. In practical terms, it will + rarely reach 1.0f; if it does, the module is probably clipping a lot. This + takes mixing volume into account, along with all the other volume + phenomena in the IT file. The only one it doesn't take into account is the + one you pass to duh_render() or duh_sigrenderer_get_samples(), or the one + you passed to al_start_duh() (these are in fact the same thing). + + The pan field ranges from 0 to 64 for a normally panned sample, but will + be 100 if the sample is playing using IT's surround mode where the right- + hand channel is inverted. If you want a more accurate pan reading, use one + of the following to get one: + + int scaled_pan = ((int)state.pan << 8) + state.subpan; + float float_pan = state.pan + state.subpan / 256.0f; + + The first will give a scaled value ranging (strictly) from 0 to 64*256. + The second will give a floating-point value whose scale corresponds to + that of the pan field. These results will only be valid if surround mode + is off, so you should check that pan <= 64 before using the above + expressions. At the time of writing, pitch-pan separation and panning + envelopes take advantage of the extra accuracy offered by subpan. + + Note that subpan is signed. This means applications that only look at the + pan field will get an unbiased reading. + + The filter cut-off and resonance both range from 0 to 127. If the cut-off + is 127 and the resonance is 0, then no filters are applied. These + parameters only ever change from the default values for IT files. + + While IT allows you to set 127 different filter cut-off levels in the + patterns and as a default value per instrument, it also allows you to + create a filter envelope, which will result in an actual cut-off somewhere + between 0 and the first-mentioned value. By the time this has been + calculated, the actual cut-off may lie in between two levels on the + original scale. If this is the case, filter_subcutoff will be nonzero and + you can combine it with filter_cutoff. Typically you will want to use one + of the following: + + int scaled_cutoff = ((int)state.filter_cutoff << 8) + + state.filter_subcutoff; + + float float_cutoff = state.filter_cutoff + + state.filter_subcutoff / 256.0f; + + The first will give you a scaled value whose maximum is 127*256. The + second will give you a floating-point value whose scale corresponds to the + scale used by filter_cutoff. These match the expressions given further up + for pan and subpan, but in this case, filter_subcutoff is unsigned. + + Note that filter_subcutoff will always be zero if filter_cutoff is 127, so + you need not check it if you simply wish to determine whether filters are + being applied. + + +******************************* +*** DUH Rendering Functions *** +******************************* + + + Use these functions to generate samples from a DUH. First you call + duh_start_sigrenderer() with the DUH, the number of channels you want and + the position at which you want to start. Then you use duh_render() or + duh_sigrenderer_get_samples() to generate the samples. You can call these + functions as many times as you like, and they will generate as many or as + few samples as you require. When you have finished, call + duh_end_sigrenderer(). + + +DUH_SIGRENDERER *duh_start_sigrenderer + (DUH *duh, int sig, int n_channels, long pos); + + Starts a DUH_SIGRENDERER off. This is the struct you can use to get + samples from a DUH. This function does not generate any samples; you must + pass the struct to duh_render() or duh_sigrenderer_get_samples() for that. + When you have finished with it, you must pass it to duh_end_sigrenderer(). + You can use as many DUH_SIGRENDERER structs as you like at the same time. + + Set sig to 0 for now. Currently, n_channels can only be 1 or 2, for + monaural and stereo sound respectively. The debugging library will cause + your program to abort if you pass anything else. Future versions will be + enhanced to support more channels as soon as someone needs them. + + When specifying the position, 0 represents the start of the DUH, and 65536 + represents one second. Unlike most other music systems, DUMB will always + make sure every note is there right from the start (assuming you aren't + using any broken add-ons). In other words, you can start a DUH at a point + halfway through a long note, and you will still hear the long note. + + +void duh_sigrenderer_set_analyser_callback(DUH_SIGRENDERER *sigrenderer, + DUH_SIGRENDERER_ANALYSER_CALLBACK callback, void *data); + + Installs a callback function which will be called every time the given + sigrenderer is used to generate some samples. This can be used to create + an oscilloscope or spectrum analyser. DUH_SIGRENDERER_ANALYSER_CALLBACK is + defined as follows: + + typedef void (*DUH_SIGRENDERER_ANALYSER_CALLBACK)(void *data, + const sample_t *const *samples, int n_channels, long length); + + If the above confuses you, see fnptr.txt. As for the 'samples' parameter, + the first 'const' says that the samples are read-only; the second says + that each channel's sample pointer is also read-only. If you don't + understand this, don't worry about it. + + Beware: your callback function may occasionally be called with + samples == NULL. This means the main program has decided to skip through + the music without generating any data (see duh_sigrenderer_get_samples()). + You should handle this case elegantly, typically by returning immediately, + but you may wish to make a note of the fact that the music is being + skipped, for whatever reason. + + Beware again: if the main program ever calls duh_sigrenderer_get_samples() + on a buffer that isn't all silence, this callback function will be passed + the existing buffer after mixing, and thus it will include the original + data. This will not be an issue if you stick to duh_render(), which always + starts with a buffer filled with silence. + + The samples array is two-dimensional. Refer to it as follows: + + samples[channel_number][sample_position] + + where 0 <= channel_number < n_channels, + and 0 <= sample_position < length. + + In addition you can pass any 'data' pointer you like to + duh_sigrenderer_set_analyser_callback(), and this pointer will be relayed + to your callback function each time. + + To remove the callback function, pass NULL to + duh_sigrenderer_set_analyser_callback(). + + +int duh_sigrenderer_get_n_channels(DUH_SIGRENDERER *sigrenderer); + + Tells you how many channels a DUH_SIGRENDERER is set up to generate, or 0 + if it is invalid (perhaps owing to lack of memory). This will be 1 for + monaural sound or 2 for stereo, in this release. + + +long duh_sigrenderer_get_position(DUH_SIGRENDERER *sigrenderer); + + Tells you what position a DUH_SIGRENDERER is up to, or -1 if it is invalid + (perhaps owing to lack of memory). As usual, 65536 is one second. + + +long duh_sigrenderer_get_samples(DUH_SIGRENDERER *sigrenderer, + float volume, float delta, + long size, sample_t **samples); + + Generates some samples in DUMB's internal 32-bit format (see sample_t; see + also duh_render()). The samples buffer is a two-dimensional array, and can + be allocated with create_sample_buffer(); see + duh_sigrenderer_set_analyser_callback() for details. + duh_sigrenderer_get_samples() mixes sample data with what's already in the + buffer, so you have to call dumb_silence() first. + + The volume is a float. 1.0f is the pseudo-maximum. If you pass 1.0f, any + properly designed DUH will play nice and loud, but will not clip. You can + pass a greater volume if you like, but be prepared for the possibility of + distortion due to integer overflow. Of course you can pass smaller values + to play the DUH more quietly, and this will also resolve clipping issues + in badly designed DUHs. + + Use delta to control the speed of the output signal. If you pass 1.0f, the + resultant signal will be suitable for a 65536-Hz sampling rate (which + isn't a commonly used rate). The most common sampling rates are 11025 Hz, + 22050 Hz, 44100 Hz and 48000 Hz. You can work out the required delta value + as follows: + + delta = 65536.0f / sampling_rate; + + If you then increase this value, the DUH will speed up and increase in + pitch. If you decrease it, the DUH will slow down and decrease in pitch. + + This function will attempt to render 'size' samples. In most cases it will + succeed. However, if the end of the DUH is reached, it may render fewer. + The number of samples rendered will be returned. Therefore, if the return + value is less than the value of 'size' passed, you know the DUH has + finished. It is safe to continue calling duh_sigrenderer_get_samples() if + you wish, and it will continually return 0. + + If the DUH_SIGRENDERER is a null pointer, this function will generate + precisely 0 samples. If you pass NULL for 'samples', the function will + behave exactly the same as if you provided a sample buffer, except the + samples won't be stored anywhere and the function will execute very + quickly. This can be used to skip ahead in the audio. + + +long duh_render(DUH_SIGRENDERER *sigrenderer, + int bits, int unsign, + float volume, float delta, + long size, void *sptr); + + Generates some samples and converts them to an 8-bit or 16-bit format (see + also duh_sigrenderer_get_samples()). Pass the DUH_SIGRENDERER as returned + by duh_start_sigrenderer(). Pass the number of bits, which should be 8 or + 16. If unsign is nonzero, the samples will be unsigned (centred on 0x80 or + 0x8000 for 8 bits and 16 bits respectively). If unsign is zero, the + samples will be signed. + + Allegro's audio streams always take unsigned samples. 8-bit .wav files + always take unsigned samples. 16-bit .wav files always take signed + samples. + + The volume and delta parameters work the same as for + duh_sigrenderer_get_samples(). + + This function will attempt to render 'size' samples. In most cases it will + succeed. However, if the end of the DUH is reached, it may render fewer. + The number of samples rendered will be returned. Therefore, if the return + value is less than the value of 'size' passed, you know the DUH has + finished. It is safe to continue calling duh_render() if you wish, and it + will continually return 0. However, if you wish to do this, you will + probably have to fill the rest of the buffer with silence, which is 0 for + signed, 0x80 for 8-bit unsigned or 0x8000 for 16-bit unsigned. + + The samples will be placed at sptr. Use an array of chars for 8 bits or an + array of shorts for 16 bits. Stereo samples will be interleaved, left + first. Your array should contain at least (size * n_channels) elements of + the appropriate bit resolution. + + From an aesthetic standpoint if nothing else, it is wise to use the C + qualifiers 'signed' or 'unsigned' depending on whether the samples are + signed or unsigned. This is also convenient if you wish to process the + samples further yourself. + + If the DUH_SIGRENDERER is a null pointer, this function will generate + precisely 0 samples. Unlike with duh_sigrenderer_get_samples(), you must + specify a sample buffer. + + +void duh_end_sigrenderer(DUH_SIGRENDERER *dr); + + Terminates a DUH_SIGRENDERER. Be sure to call this when you've finished + with one. You can safely pass a null pointer. + + +******************************** +*** Allegro Packfile Support *** +******************************** + + +void dumb_register_packfiles(void); + + This function registers the Allegro PACKFILE input module for use by + DUMBFILEs. PACKFILE structs and their corresponding functions, as defined + by Allegro's header file allegro.h, will be used internally for all + DUMBFILE input (unless opened with dumbfile_open_ex()). + + This must be called before dumbfile_open() is used, or else an alternative + system must be registered (see register_dumbfile_system() and + dumb_register_stdfiles()). Note that you don't have to call this function + in order to load datafiles that contain music. + + +DUMBFILE *dumbfile_open_packfile(PACKFILE *p); + + If you have an Allegro PACKFILE struct representing an open file, you can + call this if you wish to read from it using a DUMBFILE. This is useful + when you need to pass a DUMBFILE struct to a library function, to read an + embedded music file for example. When you close the DUMBFILE, you can + continue using the PACKFILE struct to read what follows the embedded data. + + +DUMBFILE *dumbfile_from_packfile(PACKFILE *p); + + This function is the same as dumbfile_open_packfile(), except it will + check if p is NULL, and arrange for pack_fclose() to be called on the + PACKFILE when you close the DUMBFILE. It can be seen as a function for + converting a PACKFILE to a DUMBFILE, but it will only work for a PACKFILE + you obtained with pack_fopen(), not pack_fopen_chunk(). If this function + fails, which may happen if memory is short, then the PACKFILE will be + closed immediately, so you need not worry about potential memory leaks or + files being left open when this happens. + + The following is typical usage, and will open the compressed file foo.bin: + + DUMBFILE *f = dumbfile_from_packfile(pack_fopen("foo.bin", + F_READ_PACKED)); + + This differs from calling dumb_register_packfiles() and dumbfile_open() in + that the latter will only read uncompressed files (and is thus a method + suitable for reading music modules). + + +*********************************************** +*** Allegro Datafile Registration Functions *** +*********************************************** + + +void dumb_register_dat_it(long type); + + If you wish to embed an IT file in an Allegro datafile, it is recommended + that you use "IT " for the type. The grabber will have a box for the type + when you insert a new object. The grabber will treat the IT file as binary + data, which means the datafile will contain an exact copy of the IT file + on disk. + + You must then call dumb_register_dat_it(DUMB_DAT_IT) in your program + before you load the datafile. Once you've done this, you'll be able to + access the DUH using the usual datafile[n].dat notation. You do not need + to call unload_duh() on this DUH; unload_datafile() will do that for you. + + If you are using a different type for whatever reason, you can use + Allegro's DAT_ID() macro for encoding it and passing it to this function. + For example: + + dumb_register_dat_it(DAT_ID('B','L','A','H')); + + Assuming you used the recommended type, the following example iterates + through all the ITs in disan.dat: + + DATAFILE *dat; + int n; + + dumb_register_dat_it(); + dat = load_datafile("disan.dat"); + + for (n = 0; dat[n].type != DAT_END; n++) { + if (dat[n].type == DUMB_DAT_IT) { + DUH *duh = dat[n].dat; + /* Insert code here to play 'duh' or whatever you want to do. */ + } + } + + unload_datafile(dat); + + +void dumb_register_dat_xm(long type); + + Inserting an XM file in an Allegro datafile is the same as inserting an IT + file, except that the recommended type is "XM ", the registration + function is dumb_register_dat_xm(), and the macro DUMB_DAT_XM is provided + for the type. The intuitive process of substituting XM for IT in the above + method will work. + + +void dumb_register_dat_s3m(long type); + + Inserting an S3M file in an Allegro datafile is the same as inserting an + IT file, except that the recommended type is "S3M ", the registration + function is dumb_register_dat_s3m(), and the macro DUMB_DAT_S3M is + provided for the type. The intuitive process of substituting S3M for IT in + the above method will work. + + +void dumb_register_dat_mod(long type); + + Inserting a MOD file in an Allegro datafile is the same as inserting an IT + file, except that the recommended type is "MOD ", the registration + function is dumb_register_dat_mod(), and the macro DUMB_DAT_MOD is + provided for the type. The intuitive process of substituting MOD for IT in + the above method will work. + + +**************************************** +*** Sample Buffer Allocation Helpers *** +**************************************** + + + Many parts of DUMB require sample buffers allocated in a special way. A + pointer to one looks like this: + + sample_t **samples; + + and it can be indexed as follows: + + samples[channel_number][sample_position] + + where 0 <= channel_number < n_channels + and 0 <= sample_position < length. + + The following helpers will allocate and deallocate such buffers for you. + They will not initialise them, and DUMB always writes into these buffers + by adding to what's already there, so you will generally have to call + dumb_silence() too. + + +sample_t **create_sample_buffer(int n_channels, long length); + + This will allocate a sample buffer to hold the specified number of samples + for the specified number of channels. Don't forget to check the return + value! + + You will generally have to initialise the buffer by calling + dumb_silence(); the channels will be stored consecutively in memory, so + the following technique is officially supported: + + dumb_silence(samples[0], n_channels * length); + + See dumb_silence() for general information on what this function does. + + +void destroy_sample_buffer(sample_t **samples); + + This function does the obvious: it frees up a sample buffer when you've + finished with it. It is safe to pass a null pointer to this function. + + +************************ +*** Silencing Helper *** +************************ + + +void dumb_silence(sample_t *samples, long length); + + This function simply stores 'length' samples' worth of silence in the + array. It is typically used straight after allocating a sample buffer with + create_sample_buffer(). + + +************************** +*** Resampling Helpers *** +************************** + + + Please forgive the odd section name; it has to do with DUMB's internal + structure and the fact that the resampling algorithm is there not just for + use in rendering module files but for use anywhere that a waveform needs + resampling. Unfortunately DUMB's resampling algorithm is not ready to be + documented and used yet. However, one thing can be documented, and that's + the global variable controlling the resampling quality. + + (Ironically, even this variable has changed! See deprec.txt for + information on what it used to do.) + + +int dumb_resampling_quality; + + Allows you to control the quality of all resampling that takes place. This + may be set to any DUMB_RQ_* constant (except DUMB_RQ_N_LEVELS). Higher + values will sound better, but lower values will use up less processor + time. You may compare any two DUMB_RQ_* constants or values using the + integer inequalities <, <=, > and >=; higher numbers represent higher- + quality algorithms. + + #define DUMB_RQ_ALIASING + + | --___ 'Aliasing' has very noticeable and usually unwanted + |__--- __ overtones. It will occasionally produce acceptable + | ___-- results for noisy (impure) samples (or for cheap + speakers!), but usually you will want to pay for + the extra processor time, which isn't much, and go for linear + interpolation. + + #define DUMB_RQ_LINEAR + + | __ Linear interpolation is a pretty good algorithm in most + | / \ /\ cases. When resampling down a few octaves, however, you + |/ \/ \__ may begin to notice unwanted high frequencies. You can + reduce these by switching to cubic interpolation, but it + will cost you some processor time. + + #define DUMB_RQ_CUBIC + + Cubic interpolation looks like a smooth curve to the eye, and will + produce good results in most cases. At present this is the highest + quality offered by DUMB, and also the default. While this may seem + extravagant, GCC 3.x and an AthlonXP handle it quite well - and the + general trend is for processors to get better! + + #define DUMB_RQ_N_LEVELS + + This represents the number of resampling quality levels DUMB provides. + Values of dumb_resampling_quality from 0 to DUMB_RQ_N_LEVELS - 1 are + valid. You can use this constant if you wish to offer the resampling + quality as an option for the user. + + +************************************* +*** Allegro DUH Playing Functions *** +************************************* + + + The functions in this section allow you to play back a DUH through + Allegro's sound system. You must call Allegro's install_sound() function + before you use them. + + +AL_DUH_PLAYER *al_start_duh(DUH *duh, int n_channels, long pos, + float volume, long bufsize, int freq); + + Starts playing the specified DUH. + + An AL_DUH_PLAYER represents one instance of the DUH playing. If you wish, + you can have two or more AL_DUH_PLAYERs going at the same time, for the + same DUH or for different ones. Each uses one of Allegro's audio streams + and hence one voice. The voice will be given priority 255 initially, so a + build-up of sound effects will not cause your music streams to cut off (as + long as you don't give all your sound effects priority 255!). You can + change the priority of a stream with al_duh_set_priority(). See Allegro's + documentation for more information on how voice priorities work. + + At present, n_channels can either be 1 or 2 for monaural or stereo + respectively. If you use the debugging library, your program will abort if + other values are passed; otherwise weird things will happen. + + The DUH will start playing from position 'pos'. 0 represents the start of + the DUH, and 65536 represents one second. Unlike other music systems, DUMB + will always make sure every note is there right from the start. In other + words, you can start a DUH at a point halfway through a long note, and you + will still hear the long note. + + The volume is a float. 1.0f is the pseudo-maximum. If you pass 1.0f, any + properly designed DUH file will play nice and loud, but will not clip. You + can pass a greater volume if you like, but be prepared for clipping to + occur. Of course you can pass smaller values to play the DUH more quietly, + and this will also resolve clipping issues in badly designed DUH files. + + You will need to pass the AL_DUH_PLAYER to other functions when you need + to stop or pause the DUH, change its volume, or otherwise modify the way + it is playing. You will also need to pass it to al_poll_duh() at regular + intervals; if the sound is choppy, try calling al_poll_duh() more often. + + 'bufsize' is the number of samples that will be rendered at once. 1024 is + a suitable value for most purposes. The greater this is, the less often + you will have to call al_poll_duh() - but when al_poll_duh() decides to + fill the buffer, it will take longer doing so. If your game exhibits + regular brief freezes, try reducing the buffer size. If the sound is + choppy, however, you may have to increase it. + + 'freq' specifies the sampling frequency at which the DUH should be + rendered. At present there is no (official and portable) way of knowing + the frequency at which Allegro is mixing - but if you do know that + frequency, passing it here will give the highest quality sound. If you + reduce it, the DUH will sound less crisp but use less processor time. + + When you have finished, you must pass the AL_DUH_PLAYER to al_stop_duh() + to free up memory. Do not destroy the DUH beforehand. + + There is no real need to check the return value from this function. The + other functions can be called safely with null pointers, so if there is a + problem, your music will simply not play. + + +void al_stop_duh(AL_DUH_PLAYER *dp); + + This will stop an AL_DUH_PLAYER. You must call this when you have finished + with it, before destroying the DUH. The pointer will no longer be valid on + return from this function. + + +void al_pause_duh(AL_DUH_PLAYER *dp); + + This will pause an AL_DUH_PLAYER. Use al_resume_duh() when you want it to + continue. You can safely call al_poll_duh() while the music is paused, and + it will do nothing. + + +void al_resume_duh(AL_DUH_PLAYER *dp); + + Causes a paused AL_DUH_PLAYER to resume playing (see al_pause_duh()). + + +void al_duh_set_priority(AL_DUH_PLAYER *dp, int priority); + + This will set the priority of the audio stream underlying an + AL_DUH_PLAYER. The priority is an integer ranging from 0 to 255. When + too many samples play at the same time, those with lower priorities will + be cut. 128 is the usual default with Allegro, but DUMB overrides the + default for all AL_DUH_PLAYER structs: they will be set up initially with + priority 255, so your music won't be cut (unless you play too many other + streams or samples with priority 255). See Allegro's documentation for + more information on priorities. + + +void al_duh_set_volume(AL_DUH_PLAYER *dp, float volume); + + This will set the volume of an AL_DUH_PLAYER. See al_start_duh() for + details on the volume parameter. + + +int al_poll_duh(AL_DUH_PLAYER *dp); + + An AL_DUH_PLAYER is not interrupt-driven. That means it will not play by + itself. You must keep it alive from your main program. Call this function + at regular intervals. If the sound crackles, try calling it more often. + (There is nothing you can do if Windows decides to play with the hard + disk; that will make your sound crackle no matter what you do.) + + Normally this function will return zero. However, if it returns nonzero, + that means the AL_DUH_PLAYER will not generate any more sound. Indeed the + underlying audio stream and DUH_SIGRENDERER have been destroyed. When this + happens, you can call al_stop_duh() whenever you wish - but you do not + have to. Note that this function will wait two buffers' worth of samples + before taking this action, allowing Allegro to mix the trailing sound + before the audio stream is destroyed. This is an attempt to make sure your + music does not get cut off prematurely, and it should work when using + Allegro's mixer (the only option on DOS, the default on Linux as far as I + know, but not the default on Windows). That said, if you immediately call + Allegro's remove_sound() or exit your program, the music may get cut off. + If you are using another mixer and experience problems, let me know (but I + don't guarantee to be able to come up with an elegant solution, i.e. it + might not get fixed). + + In case you were wondering, it is not safe on all platforms to call + al_poll_duh() from an interrupt context (that means an Allegro timer + handler). Not only is no part of DUMB locked in memory, but many parts of + DUMB allocate and free their memory on a call-by-call basis! Remember that + any disk access that occurs in interrupt context is likely to crash the + machine; this is explained more fully in howto.txt. This limitation only + applies to DOS at present, and is due to the fact that the DOS file access + functions are not re-entrant. + + Multitasking systems are generally safe. If you are sure you don't want to + target DOS, you can call al_poll_duh() from inside a timer handler, but I + recommend including a construction like the following! + + #ifdef ALLEGRO_DOS + #error calling al_poll_duh() from a timer handler will not work in DOS! + #endif + + Furthermore, if you call al_poll_duh() from inside a timer handler, you + must use a semaphore or other threading mechanism to make sure it is not + executing when you call al_stop_duh(). If you don't know what a semaphore + is, for Heaven's sake follow my advice and call al_poll_duh() from your + main loop! + + +long al_duh_get_position(AL_DUH_PLAYER *dp); + + Tells you what position an AL_DUH_PLAYER is up to, or -1 if it is invalid + (perhaps owing to lack of memory). As usual, 65536 is one second. Note + that this is a whole number, whereas a fractional part is stored + internally; the sample will not be continuous if you terminate the + AL_DUH_PLAYER and then reinitiate it with the same position. Furthermore, + note that Allegro will not have mixed in all the sound up to this point; + if you wait for this to reach a certain position and then terminate the + AL_DUH_PLAYER, the sound will cut off too early. Please contact me if you + need to get around this. + + +AL_DUH_PLAYER *al_duh_encapsulate_sigrenderer + (DUH_SIGRENDERER *sigrenderer, float volume, long bufsize, int freq); + + If you have a DUH_SIGRENDERER, and would like to start playing music from + it through an Allegro audio stream, use this function. Beware that it may + return NULL, in which case you will have to call duh_end_sigrenderer() + yourself instead of relying on the encapsulating AL_DUH_PLAYER to do it + for you. + + +DUH_SIGRENDERER *al_duh_get_sigrenderer(AL_DUH_PLAYER *dp); + + This returns the DUH_SIGRENDERER contained in an AL_DUH_PLAYER, useful for + controlling playback, installing callbacks, etc. + + +DUH_SIGRENDERER *al_duh_decompose_to_sigrenderer(AL_DUH_PLAYER *dp); + + This destroys an AL_DUH_PLAYER, but preserves the DUH_SIGRENDERER it + contains, and returns it to you. You can then continue rendering samples + from the DUH_SIGRENDERER and do whatever you like with them. + + +********************* +*** Thread Safety *** +********************* + + +The following points should pretty much sum up the essence of DUMB's thread +safety. If I haven't covered the one thing you'd like to do, please don't +hesitate to ask about it. + +DOs: + +- You may load and use multiple DUHs in separate threads. + +- You may change dumb_resampling_quality and dumb_it_max_to_mix while another + thread is generating samples. + +DON'Ts: + +- You may not generate samples from the same DUH in multiple threads, even if + you are using separate DUH_RENDERERs (separate AL_DUH_PLAYERS). + + +****************** +*** Conclusion *** +****************** + + +"DUMB is the bestest music player in the world because ..." + +Complete this sentence in fifteen words or fewer and receive a free copy of +DUMB! (Your Internet Service Provider may issue charges for your connection, +required for download of the Product. Your electricity supplier may issue +charges for the electricity consumed in writing the Product to a Permanent +Storage Device. You may have been charged for a Permanent Storage Device on +which to store the Product.) + + +Ben Davis +entheh@users.sf.net +IRC EFnet #dumb +See readme.txt for details on using IRC. diff --git a/apps/codecs/dumb/docs/faq.txt b/apps/codecs/dumb/docs/faq.txt new file mode 100644 index 0000000000..48b5ef3fff --- /dev/null +++ b/apps/codecs/dumb/docs/faq.txt @@ -0,0 +1,263 @@ +TO DO: add question regarding set_close_button_callback vs set_window_close_hook + +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * faq.txt - Frequently Asked Questions. / / \ \ + * | < / \_ + * This file covers some of the common problems | \/ /\ / + * and misconceptions people have with DUMB. If \_ / > / + * your problem is not covered here, please | \ / / + * contact me. I'll do my best to help - but | ' / + * don't be offended if I just direct you to the \__/ + * manual! + */ + + +***************************************************************************** +* I get a lot of strange warnings and errors when I compile my projects * +* with this release of DUMB. They work with older versions! What happened? * +***************************************************************************** + + Some parts of DUMB's API have been deprecated. See docs/deprec.txt for + full details, including an explanation as to why your compiler warnings + and errors are so unfriendly, and information on how to fix each warning + or error. + + +***************************************************************************** +* When I try to compile DUMB with Allegro, it complains that it cannot find * +* 'internal/alconfig.h'! What's wrong? * +***************************************************************************** + + In Allegro 4.0.1, and quite likely some other versions of Allegro, the + msvcmake batch file does not install Allegro properly. I believe this was + fixed in Allegro 4.0.2, but don't take my word for it. Some include files + are neglected, including alconfig.h. The fix is quite easy; you need to + copy all of Allegro's include files to your compiler's directory. The + following should do this for you (alter it accordingly depending on where + MSVC and Allegro are installed): + + cd\progra~1\msvc\include + xcopy/s \allegro\include\*.* + + You can safely tell it to overwrite all files. + + +***************************************************************************** +* When I build a project that uses DUMB, I get an error that it doesn't * +* find -laldmbd! What's wrong? * +***************************************************************************** + + See the notes for DUMB v0.8 in release.txt; the existence of libaldmbd.a + in DUMB v0.7 was due to a mistake in the makefiles. It should be + libaldmd.a, in order to maintain DOS compatibility. All subsequent + releases get it right, but you will have to change your project files to + allow for the change. If this is someone else's project, please let them + know that it needs changing. + + +***************************************************************************** +* When I build a project that uses DUMB, I get some linker errors about * +* _free, _malloc, etc. already being defined in LIBC.lib! What's wrong? * +***************************************************************************** + + MSVC offers three different implementations of the standard libraries. + When you link statically with a library, you have to use the same + implementation that the library uses. You need the multithreaded DLL + implementation, which you can select by passing /MD when you compile (not + when you link). See howto.txt for details. + + +***************************************************************************** +* I created an IT file with Impulse Tracker, but DUMB won't play it! Why? * +***************************************************************************** + + You probably created some patterns but didn't give any information on the + order in which they should be played. Impulse Tracker will also fail to + play your music if you press F5. Press F11 and you will have an + opportunity to create an order list, required for playback. + + +***************************************************************************** +* I created an IT file with ModPlug Tracker and I have it fading out at the * +* end. Why won't it loop when I play it with DUMB? * +***************************************************************************** + + It loops at zero volume. This is what Impulse Tracker itself does. Fix the + IT file by setting the global volume explicitly (Vxx in the effects + column), either at the start, or right at the end before looping. Also see + the next two questions. + + +***************************************************************************** +* My module plays too loud and distorts badly with DUMB! What can I do? * +***************************************************************************** + + This problem is most often caused by ModPlug Tracker, which has a complete + lack of regard for the playback volume of the original tracker. See the + next question for DUMB's official position with regard to ModPlug Tracker. + If you wrote your module with ModPlug Tracker, please try loading it with + the original tracker and see if it distorts there too. If it does, reduce + the volume. If not, then it's a problem with DUMB; please let me know. + + If for whatever reason you cannot modify the module file itself, you can + make it sound better by reducing the volume passed to al_start_duh(). + + +***************************************************************************** +* I created a music module with ModPlug Tracker, and DUMB doesn't play it * +* right! * +***************************************************************************** + + DUMB cannot and will not support ModPlug Tracker. Please see + docs/modplug.txt for details. The original trackers, which DUMB is + designed to mimic as closely as possible, are listed in readme.txt. + If you find DUMB plays your module differently from the original tracker, + then please contact me. + + +***************************************************************************** +* My program crashes as soon as I try to load anything with DUMB! * +***************************************************************************** + + Please take my advice and use the debugging build of DUMB, not the + optimised build. Then you'll probably find it aborts instead of crashing. + In this case you probably forgot to register a DUMBFILE system; this is + necessary for loading stand-alone files, though not for loading Allegro + datafiles with embedded music. Follow the instructions in docs/howto.txt + carefully and you shouldn't have this problem. + + If DUMB crashes with a specific music module, please let me know. + + +***************************************************************************** +* I want to use the stdio file access functions to load stand-alone music * +* files, but I also want to load datafiles containing music files. The docs * +* say I shouldn't call both dumb_register_stdfiles() and * +* dumb_register_packfiles(). What shall I do? * +***************************************************************************** + + When you register a DUMBFILE system, it only applies to files opened with + dumbfile_open(), i.e. separate files. When a file is embedded in a + datafile, dumbfile_open_ex() is used to read it, enabling it to use + PACKFILEs regardless of which DUMBFILE system is registered. In short, you + do not need to call dumb_register_packfiles() in order to load datafiles + with embedded music. See the section on "Sequential File Input" in + docs/dumb.txt if you're interested in how all this works. + + +***************************************************************************** +* I want to read a specific object in a datafile using Allegro's * +* "demo.dat#MY_MUSIC" syntax. Why won't it work? * +***************************************************************************** + + Did you call dumb_register_packfiles(), or did you call + dumb_register_stdfiles()? It will only work if you use the former. + + +***************************************************************************** +* My program runs, but no music plays! What am I doing wrong? * +***************************************************************************** + + There are a number of possible causes for this. The most likely reason is + that you aren't calling al_poll_duh(); see docs/howto.txt for further + information. + + Other possible causes are as follows: + + - The speakers are turned down (duh) + - The volume of some system mixer is turned down + - Another program is using the sound card (not a problem for most modern + systems) + - You didn't initialise Allegro's sound system; see install_sound() in + Allegro's docs + - Allegro's drivers don't work on your system and chosen platform + + In order to narrow down the cause, consider the following: + + - Do you get any other sound from your program? + - Do other Allegro+DUMB programs generate sound? + - Do other Allegro programs generate sound? + - Do other non-Allegro programs generate sound? + - Does your program fail only on a specific platform (e.g. DOS but not + Windows)? + + This problem is highly system-specific; please try hard to solve it by + yourself before contacting me. However, if you think this problem could + affect other people, please let me know what the problem is and how you + fixed it, if you did. Be as specific as possible. + + +***************************************************************************** +* The music stutters! What can I do? * +***************************************************************************** + + If you have an older computer, it may not be able to cope with the load. + Try reducing quality options; look up dumb_resampling_quality and + dumb_it_max_to_mix in docs/dumb.txt, and consider changing the frequency + you pass to al_start_duh(). + + Stuttering may not be caused by excessive load. To find out, try + increasing the buffer size passed to al_start_duh(). Beware of making it + too big though; older systems will freeze periodically if it's too big, + because they render larger chunks less frequently. The timing of callbacks + will also be less accurate, if you are using those. + + If you're using the 'dumbplay' example, you can control these parameters + by editing dumb.ini. + + +***************************************************************************** +* Why does DUMB use so much processor time compared with other players? * +***************************************************************************** + + This should be less so in this release than in previous releases; the + resampling and filtering algorithms have been optimised. + + By default, DUMB uses the most expensive resampling quality option. I've + found on an AthlonXP 1800+ and on a Pentium 233 that it typically uses + about twice as much processor time as the least expensive option. + + Try setting dumb_resampling_quality to DUMB_RQ_ALIASING or DUMB_RQ_LINEAR. + See dumb.txt for more information. If you're using the example programs, + you can control this variable by editing dumb.ini. + + DUMB uses 32-bit ints for mixing. Some players use 16-bit ints, and are + therefore marginally faster (not much!) and lower quality. So you can't + expect DUMB to beat these players. Furthermore, DUMB is currently written + entirely in C. GCC does an impressive job on the C code, but that's not to + say some custom-written assembly language couldn't beat it ... + + +***************************************************************************** +* Why does DUMB generate so much background noise? * +***************************************************************************** + + You're probably using the DOS build on a system with bad Sound Blaster + compatibility (most Windows XP systems fall in this category). This would + mean DUMB could only access an 8-bit driver. The Windows build will almost + certainly give better results. Your DOS binary will still give good + results on systems with better compatibility (like my Windows 98 system). + + +***************************************************************************** +* I e-mailed you and you replied with "RTFM"! What does that mean? * +***************************************************************************** + + Read The Manual. If it's a specific problem, I'll probably be kind and + tell you where to look in the manual. However, if I get the impression you + haven't even looked for a solution in the manual, expect no mercy ... + + +Ben Davis +entheh@users.sf.net +IRC EFnet #dumb +See readme.txt for details on using IRC. diff --git a/apps/codecs/dumb/docs/fnptr.txt b/apps/codecs/dumb/docs/fnptr.txt new file mode 100644 index 0000000000..a5fb216822 --- /dev/null +++ b/apps/codecs/dumb/docs/fnptr.txt @@ -0,0 +1,113 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * fnptr.txt - Function pointer explanation. / / \ \ + * | < / \_ + * | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + + +C allows you to create and use function pointers. A function pointer is a +variable that points to a function, and you can use it to call that function. +Why is this useful? + +Function pointers can be passed as parameters. As an example, here's a +function from Allegro: + + void create_light_table(COLOR_MAP *table, const PALETTE pal, int r, g, b, + void (*callback)(int pos)); + +Don't worry about the syntax just yet, but the last parameter, 'callback', is +a pointer to a function that takes an int parameter. create_light_table() can +take some time to complete its work, and you may want to display a progress +indicator. So you write a function to draw the progress indicator, and then, +for 'callback', you specify a pointer to your function. This will enable +create_light_table() to call your function at intervals during its +processing. (If you don't want to use the callback, you can pass NULL, but +this only works because create_light_table() checks actively for NULL. You +can't always specify NULL when you want nothing to happen.) + +There are many other uses. In addition to using function pointers as +parameters, Allegro has some global function pointers you can set to point to +your functions. Function pointers can also be used in structs, and this is +where DUMB makes the most use of them. + +So how are they used? + + void bar(void) { ... } /* Here's a function */ + void (*foo)(void) = &bar; /* Take a pointer */ + (*foo)(); /* Call the function */ + + char *baz(float a) { ... } /* Here's another function */ + char *(*foobarbaz)(float a) = &baz; /* Take a pointer */ + char *rv = (*foobarbaz)(0.1); /* Call the function */ + +In both these cases, note how the statement for calling the pointed-to +function (third line) resembles the definition of the function pointer +(second line). This is true of any variable in C, and can lead to some truly +obfuscated definitions if you are that way inclined. Such definitions can be +clarified with typedefs, but before you use those, it is important you +understand how the above statements work. I speak from experience: function +pointer notation looks random and scary, until you understand why it's the +way it is; then it makes perfect sense. + +(It is actually permissible to omit the & when taking a pointer and to write +e.g. foobarbaz(0.1) instead of (*foobarbaz)(0.1). However, I recommend not +doing this, since the syntax for using the pointer no longer resembles the +definition. Writing e.g. (*foobarbaz)(0.1) also makes a clear distinction +between function pointer calls and ordinary function calls, which makes code +more readable.) + +Note that function pointers have the return value and parameter list +specified. A function pointer can only point to a function with a matching +return value and matching parameters. (You can break this rule by casting the +pointer explicitly, but there is no situation where doing so is portable to +all computers, and I strongly advise against it unless you're writing system +code. If you're not sure whether you're writing system code or not, then +you're not.) + +The parameter names need not match (although the types must). If you wish to +rename a parameter in your function, you do not have to change the function +pointer accordingly. In fact, when you define a function pointer, you don't +even have to specify the names of parameters if you don't want to. I normally +do so for clarity. + +It is possible to typedef a function pointer. In order to typedef a function +pointer, you start by declaring the pointer as a variable: + + void (*myfunc)(void); + +Then you write 'typedef' before it and replace the variable name, which is +myfunc, with the type name (this rule can be applied to any variable when you +want to use typedef): + + typedef void (*MYTYPE)(void); + +Now 'MYTYPE' represents a pointer to a function with no parameters and no +return value. The following two lines are completely equivalent: + + MYTYPE myfunc; + void (*myfunc)(void); + +Note that we use MYTYPE without an asterisk (*), since it is already a +pointer. + +That's it. If you feel anything should be explained better here, or if you +feel something should be added, please don't hesitate to let me know! + + +Ben Davis +entheh@users.sf.net +IRC EFnet #dumb +See readme.txt for details on using IRC. diff --git a/apps/codecs/dumb/docs/howto.txt b/apps/codecs/dumb/docs/howto.txt new file mode 100644 index 0000000000..0e7057da2c --- /dev/null +++ b/apps/codecs/dumb/docs/howto.txt @@ -0,0 +1,845 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * howto.txt - How To Use DUMB. / / \ \ + * | < / \_ + * See readme.txt for general information on | \/ /\ / + * DUMB and how to set it up. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + + +******************** +*** Introduction *** +******************** + + +Welcome to the DUMB How-To! It is assumed here that you have already set DUMB +up on your system, with or without Allegro. If not, please see readme.txt. + + +********************************* +*** Adding music to your game *** +********************************* + + +These instructions will help you add a piece of music to your game, assuming +your music is stored in a stand-alone IT, XM, S3M or MOD file. If you wish to +use a different method (such as putting the music file in an Allegro +datafile), please follow these instructions first, test your program, and +then follow the instructions further down for adapting your code. + + +1. You need to include DUMB's header file. If you have Allegro, add the + following line to the top of your source file (or at the top of each file + where you wish to use DUMB): + + #include + + If you do not have Allegro or do not wish to use it, use dumb.h instead. + + +2. You need to link with DUMB's library file or files. If you are compiling + with GCC from a command line on any platform, you need to add the + following to the command line: + + If you are using Allegro: -laldmd -ldumbd + If you are not using Allegro: -ldumbd + + If you are using MSVC from the command line: + + If you are using Allegro: /link aldmd.lib dumbd.lib + If you are not using Allegro: /link dumbd.lib + + With MSVC, you must also add /MD to the command line when compiling (not + when linking). + + Note that -laldmd or aldmd.lib must PRECEDE alleg.lib, -lalleg_s, + `allegro-config --libs`, or whatever you are already using to link with + Allegro. For MSVC users, the /MD flag selects the multithreaded DLL + implementation of the standard libraries; since DUMB is statically linked, + you have to use the same library DUMB uses. You would also need this flag + to link statically with Allegro; if you already have it, there's no need + to put it twice. + + (If anyone would like to contribute instructions for doing the above using + MSVC's IDE, please contact me. Contact details are at the end of this + file.) + + If you are using RHIDE, go to Options -> Libraries. You will need to type + 'aldmd' and 'dumbd' in two boxes, making sure 'aldmd' comes above whatever + you are using to link with Allegro (or just put 'dumbd' if you are not + using Allegro). Make sure the box next to each of these libraries is + checked. + + The above are the debugging libraries. It is VERY HIGHLY RECOMMENDED that + you use the debugging libraries at first. The reason is as follows. + Although DUMB is supposedly robust against corrupt music files and things + like lack of memory, it will NOT tolerate programmer error. If you write + faulty code, DUMB will probably crash rather than returning an error code + for you. However, the debugging libraries will abort in many cases, + enabling you to find out what the cause is. + + Once your program is up and running reliably, you can replace 'aldmd' with + 'aldmb' and 'dumbd' with 'dumb'. Don't forget to do this, or DUMB will be + a lot slower than it should be! + + +3. As you use DUMB, it may claim system resources (memory in particular). You + will need to arrange for these resources to be freed at the end. Doing so + is very easy. Simply write the following line at the top of your main + function, but below allegro_init() if you are using Allegro: + + atexit(&dumb_exit); + + This arranges for the function dumb_exit() to be called when your program + exits; you do not need to call dumb_exit() yourself. This method is + preferable to calling dumb_exit() manually, as it will free resources even + if your program aborts unexpectedly. + + If you are happy with this, please skip ahead to Step 4. If you are + interested in alternative methods, read on, but read on carefully. + + In fact it mostly doesn't matter where you put the above atexit() line, + provided it gets called only once, and before you do anything with DUMB. + If you are using DUMB with Allegro, it is recommended that you write the + functions in this order: + + allegro_init(); + atexit(&dumb_exit); + + And then you must NOT call allegro_exit() yourself (because it has to be + called after dumb_exit()). Alternatively, if you prefer not to use + atexit() (or you cannot), you will have to do the following before + exiting: + + dumb_exit(); + allegro_exit(); + + +4. DUMB does not automatically do any of its own file input. You have to tell + it how to read files. Don't worry, it's easy. Simply call the following + function near the beginning of your program, after your atexit() call: + + dumb_register_stdfiles(); + + This tells DUMB to use ordinary stdio FILE structs for reading and writing + files. If you are using Allegro and would rather DUMB used PACKFILEs, call + the following function INSTEAD: + + dumb_register_packfiles(); + + In the latter case, DUMB will be affected by any password you set with + packfile_password() in the same way that other PACKFILEs are. + + Note that the procedure for loading datafiles with embedded music is + independent of these two functions; even if you will be loading datafiles, + you can use either of these functions. If you are loading datafiles, your + executable might be slightly smaller if you use dumb_register_packfiles(). + On the other hand, dumb_register_stdfiles() will probably be faster. If + you are only ever going to load datafiles and never stand-alone files, you + can actually leave this step out; but I would recommend you put this in, + test your code with a stand-alone file, then follow the instructions in + the next section in order to adapt your code to use the datafile (you will + be reminded that you can remove the function call). + + +5. If you are using Allegro, you'll have to initialise Allegro's sound + system. In most cases the following line will do the job: + + install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL); + + You may like to initialise a MIDI driver though; see Allegro's docs for + details. Put this line after allegro_init(). + + +6. All pieces of music are stored in memory in DUH structs. To handle these, + you must define pointers to them. Such pointers look like this: + + DUH *myduh; + + You can of course replace 'myduh' with anything you like. If you are + unfamiliar with pointers, please see ptr.txt. It is very important that + you understand these if you wish to use DUMB correctly. + + You do not have direct access to the contents of a DUH struct, so do not + try. DUMB's functions provide everything you need; if you disagree, please + let me know and I shall see what I can do. Contact details are at the end + of this file. + + Given the above definition, you can load a piece of music using one of the + following lines, depending on what file format you want to load: + + myduh = dumb_load_it("a_one.it"); + myduh = dumb_load_xm("a_two.xm"); + myduh = dumb_load_s3m("a_one_two.s3m"); + myduh = dumb_load_mod("three_four.mod"); + + Obviously you can use relative or absolute paths as normal. You should + always use forward slash (/), not backslash (\), when coding in C and + similar languages. + + Every piece of music you load must be unloaded when you've finished with + it. When you type the above line in, it is good practice to type the + following line in at the same time, but put it at the end of the program: + + unload_duh(myduh); + + You will now be able to use the DUH struct anywhere in between the two + lines you just added. There is no need to check the return value; if the + DUH failed to load for one reason or another (this could be due to lack of + memory as well as the file not being there), then DUMB will do nothing - + safely. + + +7. From this step onwards, it will be assumed you're using Allegro. If not, + please read these steps anyway, and then see the section entitled + "Rendering music into a buffer". You will have to write your own playback + code using whatever sound output system is available. Alternatively you + may like to write data to a file (especially if you have a file that + consumes a lot of processor time), but beware that any streaming audio + format is likely to be substantially larger than the module file you + generate it from, and formats like MP3 will be lower quality. You might + not be able to hear the difference between the MP3 and the original, but + many people can and don't like it, so please consider them. I'm one of + them. If you really want to use a lossy compression format, I highly + recommend Ogg Vorbis: + + http://www.vorbis.com/ + + But I digress. + + In order to play the DUH you loaded, you need to define a pointer to an + AL_DUH_PLAYER struct: + + AL_DUH_PLAYER *dp; + + Two of the functions you will need are prototyped as follows: + + AL_DUH_PLAYER *al_start_duh(DUH *duh, int n_channels, long pos, + float volume, long bufsize, int freq); + + void al_stop_duh(AL_DUH_PLAYER *dp); + + As you can see, al_start_duh() returns a pointer to an AL_DUH_PLAYER + struct when you call it. You then pass this pointer to all the other + functions. Again, if it is a NULL pointer for whatever reason (usually + lack of memory), DUMB will safely do nothing. When you call al_stop_duh(), + the pointer becomes invalid and you should not use it again; if there's + any risk of the pointer being used again, it is wise to set it to NULL at + this point. You can reassign the variable with a new call to + al_start_duh() of course. + + Set 'n_channels' to 1 or 2 for mono or stereo respectively. Note that this + parameter has nothing to do with the number of samples that can play at + once in a music module. Set 'pos' to 0 to play from the beginning; each + time you add 65536, you will have advanced one second into the piece. As a + general rule, set the volume to 1.0f and adjust it later if the music is + too loud or too quiet - but see Allegro's set_volume_per_voice() function + first. + + 'bufsize' can generally be set to 4096. If your music stutters, try + increasing it; if your game freezes periodically, try reducing it. Find a + happy medium. Set 'freq' to 48000 for the best quality, though 44100 will + do in most cases. 22050 will be fine for a lot of music, though 11025 may + sound muffled. You can choose any other value, higher, lower or in + between. If your music stutters, and increasing 'bufsize' doesn't fix it, + try reducing this value. + + Once you have put in a call to al_start_duh(), it is good practice to + insert the call to al_stop_duh() at the same time. You must call + al_stop_duh() before the DUH is unloaded (unload_duh(), Step 6 above). + + Don't get impetuous, your program is not ready yet! Proceed to Step 8. + + +8. DUMB does not play music in the background for you; if you were expecting + it to do so, please see the explanation at the end of this step. For your + music to be played, you have to call another function at regular + intervals. Here is its prototype: + + int al_poll_duh(AL_DUH_PLAYER *dp); + + Do NOT call this function from inside a timer function unless you really + know what you are doing. The reasons why this is bad are explained + further down. You should call it from your main program. + + Simply writing the following line will be sufficient in general, if you + have a variable 'dp' that points to your AL_DUH_PLAYER struct. + + al_poll_duh(dp); + + As a general rule, calling this once for each logic update will do the + trick. If, however, you are executing time-consuming algorithms such as + software 3D rendering, you may wish to insert calls to this function in + the middle of those algorithms. You cannot call this function too often + (within reason); if it has nothing to do it will return immediately. + + Exactly how often you need to call the function depends on the values for + 'bufsize' and 'freq' that you passed to al_start_duh(): + + n = freq / bufsize; + + You have to call al_poll_duh() at least n times a second. Do not hesitate + to call it more often for safety; if the sound stutters, you may need to + do just that. (Or you may need to increase the buffer size or reduce the + quality settings; the only way to find out is to try.) + + For now, don't worry about al_poll_duh()'s return value. As soon as you + need it, it will be explained. + + If you are happy, please skip to Step 9. If you were expecting DUMB to + play your music in the background, please read on. + + The natural way to play music in the background on most operating systems + nowadays is to use threads. DOS was not built with multithreading in mind, + and its system operations (notably disk access) assume they will only be + used from a single thread. + + Interrupts are the next best thing to threads. A DOS hardware interrupt + could be triggered at any moment, and a handler function will be called. + This is how Allegro's timer functions work. Unfortunately, what you can do + inside an interrupt handler is very limited. For one thing, all code and + data used by the handler must be locked in memory; if not, it could get + written to disk (virtual memory). If the main program was accessing the + disk when it got interrupted, the system would then die a horrible death. + This precludes the possibility of allocating extra memory inside the + handler, and DUMB does a lot of that in al_poll_duh(). + + Given DUMB's architecture, which cannot change for reasons which will + become apparent in future versions, this renders it impossible to come up + with a portable solution for making DUMB play music in the background. + Having said that, if you wish to write your own wrapper for al_poll_duh() + and use it in a thread, there is nothing stopping you. If you do do this, + you will have to be very careful when stopping the music; see the + description of al_poll_duh() in dumb.txt for more information. + + So why not kill DOS? It is all too common a practice among programmers to + quote the phrase, "DOS is as dead as the dodo." Despite being a decidedly + derisible demonstation of the dreary device of alliteration, it shows a + distinct lack of experience. Many embedded systems still use DOS because + it provides hardware access capabilities and real-time possibilities + unparalleled by any current multitasking operating system. For an argument + closer to home, I used to use RHIDE for DOS before I switched to Linux, + and I have not found a single Freeware Windows IDE that measures up to + RHIDE. I'm sure many people are in the same boat, and really appreciate + DUMB's DOS port. + + We will not be removing DOS support from DUMB. Any blind suggestions to do + so will be met with fiery flames. You have been warned. + + +9. Test your program! + + If you have trouble, check through the above steps to make sure you didn't + miss one out. Refer to faq.txt to see if your problem is addressed there. + If you still have trouble, contact me; details are at the end of this + file. + + +********************************** +*** Controlling music playback *** +********************************** + + +Here I describe some common operations you may wish to perform. The method +for doing so will seem a bit strange sometimes, as will the names of the +structs. However, there is a reason behind everything. If you would like to +do more exotic things, or better understand some of the methods used here, +then see dumb.txt, which covers everything from the ground up. + + +To control playback quality: + + #define DUMB_RQ_ALIASING + #define DUMB_RQ_LINEAR + #define DUMB_RQ_CUBIC + #define DUMB_RQ_N_LEVELS + extern int dumb_resampling_quality; + extern int dumb_it_max_to_mix; + + Please note that dumb_resampling_quality has changed in DUMB v0.9.2. See + deprec.txt for more details on the change. + + dumb_resampling_quality can be set to any of the DUMB_RQ_* constants + (except DUMB_RQ_N_LEVELS; see below). Resampling is the term given to the + process of adjusting a sample's pitch (in this context). + dumb_resampling_quality defaults to DUMB_RQ_CUBIC, which sounds nice but + takes a lot of processor power. Try reducing it if you have an older + computer or if you are trying to mix an insane number of samples (or + both!). See dumb.txt for details on what the different values actually do. + + If you wish to give this option to your user, you can use + DUMB_RQ_N_LEVELS. All the values from 0 to DUMB_RQ_N_LEVELS - 1 will be + valid resampling levels. If a value outside this range is chosen, it is + not the end of the world; DUMB will behave as if you had chosen the value + at whichever extreme you went beyond. + + dumb_it_max_to_mix, defaulting to 64, is the maximum number of samples + DUMB will ever mix together when playing an IT, XM, S3M or MOD file. + Unlike many other music systems, DUMB will still keep track of all samples + (up to a fixed maximum of 256 of them, roughly speaking), and then will + just render as many of them as this variable permits, starting with the + loudest ones. When samples are cut or come back in, the exact timings will + not generally be predictable - but nor will they be important. + + dumb_it_max_to_mix applies to each currently playing module file + independently. So if you set it to 64, but render two modules + simultaneously, DUMB could end up mixing up to 128 samples. + + +To pause and resume playback, set the volume, get the current playback +position, or get the length of time a DUH will play for before either looping +or freezing (effect F00 in XM and MOD files, which means no new notes will be +played but any existing notes will continue): + + void al_pause_duh(AL_DUH_PLAYER *dp); + void al_resume_duh(AL_DUH_PLAYER *dp); + void al_duh_set_volume(AL_DUH_PLAYER *dp, float volume); + long al_duh_get_position(AL_DUH_PLAYER *dp); + + long duh_get_length(DUH *duh); + + These functions are pretty self-explanatory. The volume passed to + al_duh_set_volume() and the position returned by al_duh_get_position() are + in the same units as those you passed to al_start_duh(). The length + returned by duh_get_length() is in the same units as the aforementioned + position; see dumb.txt for more information on this function. Be careful + with al_duh_get_position(); it will return a position slightly ahead of + what you can hear, because the system has to keep ahead slightly to avoid + stuttering. + + +To prevent the music from looping and/or freezing: + + DUH_SIGRENDERER *al_duh_get_sigrenderer(AL_DUH_PLAYER *dp); + DUMB_IT_SIGRENDERER *duh_get_it_sigrenderer(DUH_SIGRENDERER *sigrenderer); + + void dumb_it_set_loop_callback(DUMB_IT_SIGRENDERER *sigrenderer, + int (*callback)(void *data), void *data); + void dumb_it_set_xm_speed_zero_callback(DUMB_IT_SIGRENDERER *sigrenderer, + int (*callback)(void *data), void *data); + + int dumb_it_callback_terminate(void *data); + + If you are unfamiliar with function pointers, please see fnptr.txt. + + Note that these functions apply to IT, XM, S3M and MOD files - not just to + IT files. This holds true throughout DUMB, for all functions with "it" in + the name. The xm_speed_zero event can only occur with XM and MOD files. + + The first two functions will return a pointer to a struct contained by the + struct you pass. This system is necessary to ensure that these operations + are possible when not using Allegro. Typically you would write the + following code: + + { + DUH_SIGRENDERER *sr = al_duh_get_sigrenderer(dp); + DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(sigrenderer); + dumb_it_set_loop_callback(itsr, &dumb_it_callback_terminate, NULL); + dumb_it_set_xm_speed_zero_callback + (itsr, &dumb_it_callback_terminate, NULL); + } + + Once you have done this, the return value of al_poll_duh() becomes + significant. It will be 0 as long as the music is playing. When the music + stops, al_poll_duh() will return nonzero. You can call al_stop_duh() and + do something else as soon as you wish, but calling al_poll_duh() some more + will not do any harm. + + al_poll_duh() will also return 1 if the music could not be loaded, or if + memory was short when trying to play it, or if it was a quirky music file + with no music in it (technically one with an empty order list). This + happens regardless of whether or not you execute the above code to disable + looping. Normally you shouldn't need to worry about this. + + To undo the above and make DUMB loop or freeze again, pass NULL instead of + &dumb_it_callback_terminate. If you would like to fade on looping, or loop + a finite number of times, or display a message when looping, or whatever, + you will have to write your own callback function. In this case, please + see dumb.txt. + + Note that the above code can safely be applied for a DUH that doesn't + contain a music module but contains some other kind of music. + duh_get_it_sigrenderer() will return NULL, and the code will do nothing. + + +To analyse the audio as it's generated: + + typedef int sample_t; + + typedef void (*DUH_SIGRENDERER_ANALYSER_CALLBACK)(void *data, + const sample_t *const *samples, int n_channels, long length); + + void duh_sigrenderer_set_analyser_callback(DUH_SIGRENDERER *sigrenderer, + DUH_SIGRENDERER_ANALYSER_CALLBACK callback, void *data); + + If the above confuses you, see fnptr.txt. These functions, along with + al_duh_get_sigrenderer() from the last section, enable you to register a + callback function. Every time some samples are generated, they will be + passed to this function. This enables you to display an oscilloscope or + spectrum analyser, for example. + + Beware: your callback function may occasionally be called with + samples == NULL. This means the main program has decided to skip through + the music without generating any data. You should handle this case + elegantly, typically by returning immediately, but you may wish to make a + note of the fact that the music is being skipped, for whatever reason. + + Beware again: if the main program ever calls duh_sigrenderer_get_samples() + on a buffer that isn't all silence, this callback function will be passed + the existing buffer after mixing, and thus it will include the original + data. This will not be an issue if you stick to duh_render(), which always + starts with a buffer filled with silence. + + The samples array is two-dimensional. Refer to it as follows: + + samples[channel_number][sample_position] + + where 0 <= channel_number < n_channels, + and 0 <= sample_position < length. + + In addition you can pass any 'data' pointer you like to + duh_sigrenderer_set_analyser_callback(), and this pointer will be relayed + to your callback function each time. + + To remove the callback function, pass NULL to + duh_sigrenderer_set_analyser_callback(). + + +Everything below this point assumes some knowledge of how a music module is +constructed. If you do not have this knowledge, talk to whoever is writing +music for you, or download a tracking program and play with it (see +readme.txt). + + +To start playing an IT, XM, S3M or MOD from an arbitrary order number (the +default being 0, the beginning of the song), use the following: + + DUH_SIGRENDERER *dumb_it_start_at_order + (DUH *duh, int n_channels, int startorder); + AL_DUH_PLAYER *al_duh_encapsulate_sigrenderer + (DUH_SIGRENDERER *sigrenderer, float volume, long bufsize, int freq); + + The usage of these functions is as follows: + + { + DUH_SIGRENDERER *sr = dumb_it_start_at_order + (duh, n_channels, startorder); + dp = al_duh_encapsulate_sigrenderer(sr, volume, bufsize, freq); + } + + Replace 'dp' with whatever your AL_DUH_PLAYER pointer is. You also need + to insert suitable values for n_channels, startorder, volume, bufsize and + freq. These have the same meaning as those passed to al_start_duh(). + + WARNING: after passing a pointer to an "encapsulate" function, do not use + that pointer again. (More specifically, do not use it again if + the function returns NULL, because the function will have + destroyed the pointer if this happens, to help prevent memory + leaks.) There will be a "get" function with which you can obtain + the original pointer if it is still valid, or NULL otherwise. + + The above functions will fail (safely) if you try to use them with a DUH + that contains a different type of music. + + Notice that there is no 'pos' parameter. If you would like to skip through + the music, you can use this function: + + long duh_sigrenderer_get_samples( + DUH_SIGRENDERER *sigrenderer, + float volume, float delta, + long size, sample_t **samples + ); + + Pass 0 for volume and NULL for samples, and this function will skip + through the music nice and quickly. So insert the following between the + two above statements: + + duh_sigrenderer_get_samples(sr, 0, 65536.0f / freq, pos, NULL); + + Substitute for 'freq' and 'pos'. An explanation of the 'delta' parameter + can be found further down in this file. + + Finally, note that duh_get_length() is only meaningful when you start + playing music from order 0. + + +If an IT file contains Zxx effects, DUMB will generate MIDI messages, which +will control the low-pass resonant filters unless the IT file actively +specifies something else. In rare cases this may not be what the Zxx effects +were intended to do; if this is the case, you can block the MIDI messages as +follows. Note that this does NOT mean filters are disabled; if an instrument +specifies initial cut-off and resonance values, or has a filter envelope, +then filters will be applied. It only makes sense to use this procedure at +the beginning of playback. + + void dumb_it_set_midi_callback(DUMB_IT_SIGRENDERER *sigrenderer, + int (*callback)(void *data, int channel, unsigned char byte), + void *data); + + int dumb_it_callback_midi_block(void *data, int channel, + unsigned char byte); + + Using some functions described in the previous section, we arrive at the + following code: + + { + DUH_SIGRENDERER *sr = al_duh_get_sigrenderer(dp); + DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(sigrenderer); + dumb_it_set_midi_callback(itsr, &dumb_it_callback_midi_block, NULL); + } + +DUMB offers no way of disabling filters completely. Disabling filters is not +recommended as a means to reduce processor usage, as it will completely +damage any piece of music that uses the filters. If you want lower processor +consumption, use a piece of music that does not use filters. + + +Finally, DUMB offers a myriad of functions for querying and adjusting +module playback. Those beginning with "dumb_it_sd" operate on the +DUMB_IT_SIGDATA struct, which represents the piece of music before it starts +to play. Those beginning with "dumb_it_sr" operate on the DUMB_IT_SIGRENDERER +struct, which represents a currently playing instance of the music. Note that +duh_get_length(), described above, becomes meaningless after some of these +functions are used. + +The method for getting a DUMB_IT_SIGRENDERER struct has already been given, +but the function prototypes are repeated here for convenience: + + DUH_SIGRENDERER *al_duh_get_sigrenderer(AL_DUH_PLAYER *dp); + DUMB_IT_SIGRENDERER *duh_get_it_sigrenderer(DUH_SIGRENDERER *sigrenderer); + +Getting a DUMB_IT_SIGDATA struct is simpler: + + DUMB_IT_SIGDATA *duh_get_it_sigdata(DUH *duh); + +For a list of dumb_it_sd_*() and dumb_it_sr_*() functions, please see +dumb.txt. These functions are new, and may not provide exactly what you need; +if not, please let me know. + + +************************************************** +*** Embedding music files in Allegro datafiles *** +************************************************** + + +In this section it is assumed you are already reasonably familiar with how +Allegro datafiles are used. If not, please refer to Allegro's documentation. +At the time of writing, the documentation you need is off the beaten track, +so to speak, in allegro/tools/grabber.txt. + +To add a piece of music to a datafile, you need to create an object of type +"IT ", "XM ", "S3M " or "MOD " (note the spaces used as padding, although +you do not need to type these into the grabber). Then grab the piece of music +in. The grabber will treat it as a binary object. Save the datafile as usual. + + +To use a piece of music you added to the datafile, follow these steps: + + +1. Before loading the datafile, call one or more of these functions, + depending on which music format or formats you'd like to support: + + dumb_register_dat_it(DUMB_DAT_IT); + dumb_register_dat_xm(DUMB_DAT_XM); + dumb_register_dat_s3m(DUMB_DAT_S3M); + dumb_register_dat_mod(DUMB_DAT_MOD); + + Remember, do not call multiple functions unless you want to support + multiple formats. Calling more functions will add unused code to your + executable. + + It is important that you make call these before loading the datafile, + since they tell Allegro how to load the respective files straight from + datafiles in the future. They will not help Allegro interpret any module + files that have already been loaded as binary objects (but if you really + need to interpret a module that has been loaded in this fashion, have a + look at dumbfile_open_memory() in dumb.txt). + + If for whatever reason your music objects are identified by a different + type in the datafile, you can tell DUMB what that type is by changing the + parameter to the registration function above. Use Allegro's DAT_ID() + macro, e.g. DAT_ID('B','L','A','H'). This is not really recommended + though, since it would prevent a hypothetical grabber plug-in from being + able to play your music files. Use the above types if possible. + + +2. Whenever you need a pointer to a DUH struct, simply use the 'dat' field. + Do this in the same way you would for a pointer to a BITMAP struct or + anything else. If it makes you feel more comfortable, you can extract the + pointer in advance: + + DATAFILE *dat = load_datafile("smurf.dat"); + if (!dat) abort(); /* There are much nicer ways of handling failure! */ + DUH *myduh = (DUH *)dat[GAME_MUSIC].dat; + + Note that the explicit (DUH *) cast is only necessary for C++, not for C. + However, it does no harm. + + Be sure that you do NOT call unload_duh() for anything stored in the + datafile. These DUHs will be freed when you call unload_datafile(), and + freeing them twice is practically guaranteed to crash your program. + + +3. If you only ever load music as part of a datafile, and you never load any + stand-alone music files, you do not need to register a file input system + for DUMB to use. If you followed the instructions for the first section + you will have one of these two lines in your program: + + dumb_register_stdfiles(); + dumb_register_packfiles(); + + You can safely delete this line - but only if you never load any + stand-alone music files. The debugging library will bale you out if you + delete it when you shouldn't; the optimised library won't. + + +************************************* +*** Rendering music into a buffer *** +************************************* + + +NOTE: much of the API formerly described in this section has been deprecated, + and you will need to alter your code. See deprec.txt for details. If + you are reading this section for the first time, you can ignore this + note. + +Rendering to a buffer is similar to playing using an AL_DUH_PLAYER. However, +you must use a DUH_SIGRENDERER struct instead. Here are the functions: + + DUH_SIGRENDERER *duh_start_sigrenderer + (DUH *duh, int sig, int n_channels, long pos); + + int duh_sigrenderer_get_n_channels(DUH_SIGRENDERER *sigrenderer); + long duh_sigrenderer_get_position(DUH_SIGRENDERER *sigrenderer); + + long duh_sigrenderer_get_samples(DUH_SIGRENDERER *sigrenderer, + float volume, float delta, long size, sample_t **samples); + + long duh_render(DUH_SIGRENDERER *sigrenderer, + int bits, int unsign, float volume, float delta, long size, void *sptr); + + void duh_end_sigrenderer(DUH_SIGRENDERER *sigrenderer); + +The parameters to duh_start_sigrenderer() have the same meanings as those to +al_start_duh(). However, note that the volume is not set at this stage. You +pass the desired volume each time you want to render a block. The 'sig' +parameter should be set to 0 for now. + +Notice that there are two rendering functions. duh_sigrenderer_get_samples() +will generate samples in the internal 32-bit format, with a normal range from +-0x800000 to 0x7FFFFF and with each channel in a separate array; duh_render() +will convert to 8 or 16 bits, signed or unsigned, with stereo samples +interleaved, left first. + +When you call duh_render(), pass 8 or 16 for 'bits'. If you pass 8, 'sptr' is +expected to be an array of chars. If you pass 16, 'sptr' is expected to be an +array of shorts. Endianness therefore depends on the platform, and you should +not try to interpret 16-bit wave data as an array of chars (unless you're +writing highly system-specific code anyway). Because DUMB renders internally +with 32 bits, there is no significant speed increase in rendering an 8-bit +stream. + +If you are rendering in stereo, make sure your 'sptr' array is twice as big! + +If you set 'unsign' to a nonzero value, then the samples generated will be +centred on 0x80 or 0x8000, suitably stored in an array of unsigned chars or +unsigned shorts. If 'unsign' is zero, the samples will be centred on 0, +suitably stored in an array of signed chars or signed shorts. Note that 8-bit +WAV files are unsigned while 16-bit WAV files are signed. This convention was +used by the SoundBlaster 16 when receiving samples to be sent to the +speakers. If you wish to write 16-bit sample data to a WAV file, don't use +fwrite(); instead, take the shorts one at a time, split them up into chars as +follows, and write the chars to the file. + + short sptr[n]; + char lsb = (char)sptr[n]; + char msb = (char)(sptr[n] >> 8); + +For a 16-bit WAV file, write the LSB (less significant byte) first. + +The following applies equally to duh_render() and +duh_sigrenderer_get_samples(), except where otherwise stated. + +If you set 'delta' to 1.0f, the sound generated will be suitable for playback +at 65536 Hz. Increasing 'delta' causes the wave to speed up, given a constant +sampling rate for playback. Supposing you want to vary the playback sampling +rate but keep the pitch constant, here's the equation for 'delta': + + delta = 65536.0f / sampling_rate; + +'size' is the number of samples you want rendered. For duh_render(), they +will be rendered into an array which you pass as 'sptr'. Note that stereo +samples count as one; so if you set n_channels to 2, your array must contain +(2 * size) elements. + +For duh_sigrenderer_get_samples() you will have to use the following +functions: + + sample_t **create_sample_buffer(int n_channels, long length); + void destroy_sample_buffer(sample_t **samples); + + void dumb_silence(sample_t *samples, long length); + +create_sample_buffer() allocates the channels sequentially in memory, so the +following technique is valid: + + sample_t **samples = create_sample_buffer(n_channels, length); + dumb_silence(samples[0], n_channels * length); + +It is necessary to fill the buffer with silence like this because +duh_sigrenderer_get_samples() mixes what it renders with the existing +contents of the buffer. + +The return values from duh_render() and duh_sigrenderer_get_samples() tell +you how many samples were actually generated. In most cases, this will be the +same as the 'size' parameter. However, if you reach the end of the DUH (which +will happen if you disable looping or freezing as described further up), this +function will return less. When that happens, you can assume the stream has +finished. In the case of duh_render(), the remainder of the array will not +have been initialised, so you either have to initialise it yourself or avoid +using it. + +If for whatever reason duh_start_sigrenderer() returns NULL, then +duh_render() and duh_sigrenderer_get_samples() will generate exactly 0 +samples, duh_sigrenderer_get_n_channels() will return 0, +duh_sigrenderer_get_position() will return -1, and duh_end_sigrenderer() will +safely do nothing. + + +********************* +*** Miscellaneous *** +********************* + + +Please see dumb.txt for an API reference and for information on thread safety +with DUMB. The API reference has been stripped down, since some functions and +variables are subject to change. If something does not appear in dumb.txt, +please do not use it. + + +****************** +*** Conclusion *** +****************** + + +If you have any difficulties, or if you use DUMB successfully, please don't +hesitate to contact me (see below). + +Enjoy! + + +Ben Davis +entheh@users.sf.net +IRC EFnet #dumb +See readme.txt for details on using IRC. diff --git a/apps/codecs/dumb/docs/modplug.txt b/apps/codecs/dumb/docs/modplug.txt new file mode 100644 index 0000000000..a02ddd8bdd --- /dev/null +++ b/apps/codecs/dumb/docs/modplug.txt @@ -0,0 +1,137 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * modplug.txt - Our official position regarding / / \ \ + * compatibility with ModPlug | < / \_ + * Tracker. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + + +******************** +*** Introduction *** +******************** + +ModPlug Tracker is a very popular tracker for Windows. Its popularity is due +to the intuitive interface and its many advanced features. The author has +done a good job with this piece of software, but sadly in doing so he has +desecrated the IT file format. + +I am not against ModPlug Tracker being used to write music modules. As +already stated, it has some very advanced and convenient features; I use it +myself. However, I believe its users should be aware of the entire situation +before using it for any serious work. + + ModPlug Tracker - http://www.modplug.com/ + + +************************* +*** Incompatibilities *** +************************* + +There are a few situations in which ModPlug Tracker misinterprets the +original module formats. I shall list the five I am most aware of, from least +to most annoying: + +5. Create a multisample instrument, for example a piano. Play a low note. + Then go up the scale, but in the pattern data, make sure the instrument + column is blank; put in only the notes. Play this with ModPlug Tracker, + and play it with Impulse Tracker. Impulse Tracker changes sample as you go + up the scale; ModPlug Tracker does not. + +4. Arpeggio and Retrigger Note effects behave badly when combined with + Portamento, which can appear in the volume column. While Retrigger Note + isn't too bad, Arpeggio sounds completely wrong. Try it and see what + happens. Then repeat the experiment in Impulse Tracker. + +3. The filter algorithm is incorrect, in more ways than one. When Jeffrey Lim + programmed the low-pass resonant filters into Impulse Tracker, he used a + standard filter algorithm with a slight modification to achieve greater + resonance. ModPlug Tracker does not incorporate this modification. + Furthermore, ModPlug Tracker uses integer arithmetic with nowhere near + enough precision; the wave output is really poor in some cases. I don't + doubt it damages the acoustic properties of the filters in subtle ways. + +2. When looping, ModPlug Tracker resets all variables. The original trackers + do not do this. + +1. Worst of all, ModPlug Tracker has no regard for playback volume, and + generally has a much lower output level than the original trackers. + +Cases 3, 2 and 1 lead people to write IT files that play badly in the +original trackers. If some of these problems could be fixed, I'd be all for +it - but these problems have been reported to the author and he had no +motivation to fix them. ModPlug Tracker has been around long enough that +fixing 3, 2 and 1 would be detrimental to too many people's music. + + +****************** +*** Extensions *** +****************** + +Worse than the incompatibilities are the extensions ModPlug Tracker makes, +mostly to the IT format. DUMB currently supports one of these extensions, +namely stereo samples, but supporting the others is not high on my list of +priorities. + +Other extensions ModPlug Tracker has provided mostly take the form of extra +effects. For instance, S98 and S99 can be used to enable or disable reverb. I +believe the latest versions of ModPlug Tracker offer alternative types of +filter, such as high-pass and band-pass. As soon as an IT file uses any of +these features, it will play incorrectly with Impulse Tracker. + +By far the most evil extension provided by ModPlug Tracker is the effect +plug-ins. These enable IT files to use VST effects. I recently downloaded an +IT file that uses some effects from a collection named "DirectX Media Audio +Effects". When can we expect these effects to be ported to Linux? + + +****************** +*** Conclusion *** +****************** + +ModPlug Tracker is trying to be two things at once. It wants to be an editor +for the existing formats, but at the same time it wants to be proprietary, +with all its own features and extensions. Unfortunately it is succeeding; +there are many IT files out there that only play right in ModPlug Tracker. In +my opinion, ModPlug Tracker should have come out with its own file format, in +which all these extensions would have found a home. + +If you are going to use ModPlug Tracker's extensions, I recommend you +ultimately convert your music to a streamed format such as Ogg Vorbis. (If +you were thinking of using MP3, then don't - consider using Ogg Vorbis +instead.) If you release IT files that use ModPlug Tracker's extensions, +please state prominently that the files are designed to be played with +ModPlug Tracker. Finally, don't ask me to support ModPlug Tracker's +extensions; ModPlug Tracker's playback code is available for use in your +games, so use that instead. + + Ogg Vorbis - http://www.vorbis.com/ + +Despite all the above problems, don't forget that ModPlug Tracker does have a +lot of very useful features for editing files. These include a function for +removing unused patterns, samples and instruments, drag-and-drop sample and +instrument ripping, drop-down menus for selecting the effects by name without +having to memorise the codes or refer to help, and lots of other nice things. +I do recommend it as an editor, provided you make sure you are aware of the +situation and do not use ModPlug Tracker's extensions or incompatibilities +inadvertently. + +Oh, and by the way, save your final version with Impulse Tracker. Then the +samples will be compressed for you! + + +Ben Davis +entheh@users.sf.net +IRC EFnet #dumb +See readme.txt for details on using IRC. diff --git a/apps/codecs/dumb/docs/ptr.txt b/apps/codecs/dumb/docs/ptr.txt new file mode 100644 index 0000000000..0eb42ccf02 --- /dev/null +++ b/apps/codecs/dumb/docs/ptr.txt @@ -0,0 +1,129 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * ptr.txt - Pointer explanation. / / \ \ + * | < / \_ + * | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + + +A pointer is a small variable (often the same size as an int BUT NOT ALWAYS) +that holds the address of something in memory. You create a pointer by adding +a * to a variable, as follows: + + int x, *y; + + x = 5; + y = &x; + +The & means 'address of', so &x gives us a pointer to x. We are storing it in +y. + + (*y)++; + +The * here means 'value at'. It's known as the 'dereferencing' operator. When +written before a pointer, as it is here, it allows you to treat the value +like a normal variable. In this case we are incrementing the value. If we +look at x, we'll find that it now contains 6, not 5. + + y++; + +Here we are incrementing the pointer itself. This is useful for traversing +through an array, but in this particular example it is not much use. + + *y++; + +Beware; this will increment the pointer, not the value stored there. It will +return the value stored at the pointer (before incrementing the pointer), so +you can use this in a bigger expression. This is why we needed brackets in +the first example. + +Note that you will not need these three examples when working with DUMB; they +are simply to help illustrate the idea of pointers. + +Also be aware that when defining pointers you attach the * to the variable, +not to the type. The following example will create a pointer and an int, not +two pointers: + + int *a, b; + +That is why I believe it's a good idea to put a space before the * and not +after it, although programmers are divided on this. + + y = 0; + y = NULL; + +These two statements are equivalent. 0, or NULL, is a special value that is +guaranteed to have a different value from any valid pointer. This is most +often used to indicate that something doesn't point anywhere. DUMB's +functions may return it on occasion. However, in simple usage of DUMB, you +will not actually need to check for it. + +Some of DUMB's functions return pointers to structs. (A struct is an +aggregration of other variables, such as ints, pointers, or other structs. +You can generally treat a struct as a single unit.) Here's an example of such +a function: + + DUH *dumb_load_it(const char *filename); + +You do not know what the DUH struct actually contains; dumb.h and aldumb.h +only give the compiler enough information to deal with pointers to them. DUMB +will take charge of everything that happens inside a DUH struct. + +The above function will create a DUH struct for you. First it allocates +the memory it needs, then it fills the struct with data, then it returns a +pointer. This DUH struct will contain the data necessary to play an IT file. +You can define a suitable variable and store the pointer in it as follows: + + DUH *duh = dumb_load_it("music.it"); + +Or this can be split up: + + DUH *duh; + duh = dumb_load_it("music.it"); + +In order to use this DUH struct later, you must pass its pointer to other +functions. To pass the pointer to a function, simply write 'duh' for the +appropriate parameter. + +When you've finished with a DUH struct (this applies equally to the other +structs DUMB deals with), you must pass it to an appropriate function for +freeing up the memory: + + unload_duh(duh); + +After you've done this, the memory will no longer be allocated, and the +pointer will have no meaning. You may wish to set it to NULL at this point +for safety. Alternatively just be sure not to use the present value of the +pointer any more. You can of course assign a new value to the pointer, e.g. +by calling dumb_load_it() again. + +Note the following: + + DUH *duh2 = duh; + +This only duplicates the pointer, not the DUH itself. You still only have one +copy of the DUH. There is no way of duplicating a DUH, short of loading it +twice. This is not a problem, because DUMB can play it 'twice at the same +time' anyway. + +That should be all you need to know about pointers in order to use DUMB. If +there's anything you feel should be explained better here, or anything else +that should be added, please don't hesitate to let me know! + + +Ben Davis +entheh@users.sf.net +IRC EFnet #dumb +See readme.txt for details on using IRC. diff --git a/apps/codecs/dumb/examples/dumb.ini b/apps/codecs/dumb/examples/dumb.ini new file mode 100644 index 0000000000..6fea2ce50f --- /dev/null +++ b/apps/codecs/dumb/examples/dumb.ini @@ -0,0 +1,44 @@ +# Please edit this file to control the playback quality for 'dumbplay'. Note +# that this does not affect DUMB when you use it in your own programs; you +# need to borrow some code from the example program in order to get that to +# happen. + +# dumb_resampling_quality can be 0 for aliasing, 1 for linear interpolation +# or 2 for cubic interpolation. See docs/dumb.txt for details on what these +# terms mean. + +# dumb_it_max_to_mix is the maximum number of samples DUMB will render at a +# time. See docs/dumb.txt for a more detailed description. + +# Increase buffer_size to combat stuttering. + +# The music module will be rendered at the sampling frequency specified by +# sound_freq. This variable is also used by Allegro for initialising the +# sound hardware. + +# buffer_size and sound_freq are passed directly to al_start_duh(). See this +# function's description in docs/dumb.txt for information about how to use +# these variables. + +# You can ignore the quality variable. Allegro uses it when relaying the +# audio stream to the sound card. Only a masochist would set it lower than 2; +# if your computer is powerful enough to run DUMB, it is powerful enough to +# use this setting with Allegro. + +# For best results, choose a value for sound_freq that your sound card can do +# exactly. See Allegro's docs, "Standard config variables", for details. If +# you do not choose an exact value, Allegro will round it to the nearest +# value it can do; then when DUMB plays the stream at a sampling frequency of +# sound_freq, Allegro will have to resample it. Allegro's 'quality = 2' +# setting is only comparable with DUMB's 'dumb_resampling_quality = 1' +# setting. Therefore, in order to appreciate DUMB's cubic resampler fully, +# you will need to make sure Allegro doesn't do any resampling, by choosing +# an exact value for sound_freq. + +[sound] +dumb_resampling_quality = 2 +dumb_it_max_to_mix = 256 +buffer_size = 4096 +sound_freq = 44100 + +quality = 2 diff --git a/apps/codecs/dumb/examples/dumbout.c b/apps/codecs/dumb/examples/dumbout.c new file mode 100644 index 0000000000..b0ead3dd5e --- /dev/null +++ b/apps/codecs/dumb/examples/dumbout.c @@ -0,0 +1,335 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * dumbout.c - Utility to stream music to a file. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include +#include +#include +#include +#include +#include + + +union { + short s16[8192]; + char s8[16384]; +} buffer; + + +int main(int argc, const char *const *argv) /* I'm const-crazy! */ +{ + DUH *duh; + DUH_SIGRENDERER *sr; + + const char *fn = NULL; + const char *fn_out = NULL; + FILE *outf; + + int depth = 16; + int bigendian = 0; + int unsign = 0; + int freq = 44100; + int n_channels = 2; + float volume = 1.0f; + float delay = 0.0f; + float delta; + int bufsize; + clock_t start, end; + + int i = 1; + + LONG_LONG length; + LONG_LONG done; + int dots; + + while (i < argc) { + const char *arg = argv[i++]; + if (*arg != '-') { + if (fn) { + fprintf(stderr, + "Cannot specify multiple filenames!\n" + "Second filename found: \"%s\"\n", arg); + return 1; + } + fn = arg; + continue; + } + arg++; + while (*arg) { + char *endptr; + switch (*arg++) { + case 'o': + case 'O': + if (i >= argc) { + fprintf(stderr, "Out of arguments; output filename expected!\n"); + return 1; + } + fn_out = argv[i++]; + break; + case 'd': + case 'D': + if (i >= argc) { + fprintf(stderr, "Out of arguments; delay expected!\n"); + return 1; + } + delay = (float)strtod(argv[i++], &endptr); + if (*endptr != 0 || delay < 0.0f || delay > 64.0f) { + fprintf(stderr, "Invalid delay!\n"); + return 1; + } + break; + case 'v': + case 'V': + if (i >= argc) { + fprintf(stderr, "Out of arguments; volume expected!\n"); + return 1; + } + volume = (float)strtod(argv[i++], &endptr); + if (*endptr != 0 || volume < -8.0f || volume > 8.0f) { + fprintf(stderr, "Invalid volume!\n"); + return 1; + } + break; + case 's': + case 'S': + if (i >= argc) { + fprintf(stderr, "Out of arguments; sampling rate expected!\n"); + return 1; + } + freq = strtol(argv[i++], &endptr, 10); + if (*endptr != 0 || freq < 1 || freq > 960000) { + fprintf(stderr, "Invalid sampling rate!\n"); + return 1; + } + break; + case '8': + depth = 8; + break; + case 'b': + case 'B': + bigendian = 1; + break; + case 'm': + case 'M': + n_channels = 1; + break; + case 'u': + case 'U': + unsign = 1; + break; + case 'r': + case 'R': + if (i >= argc) { + fprintf(stderr, "Out of arguments; resampling quality expected!\n"); + return 1; + } + dumb_resampling_quality = strtol(argv[i++], &endptr, 10); + if (*endptr != 0 || dumb_resampling_quality < 0 || dumb_resampling_quality > 2) { + fprintf(stderr, "Invalid resampling quality!\n"); + return 1; + } + break; + default: + fprintf(stderr, "Invalid switch - '%c'!\n", isprint(arg[-1]) ? arg[-1] : '?'); + return 1; + } + } + } + + if (!fn) { + fprintf(stderr, + "Usage: dumbout [options] module [more-options]\n" + "\n" + "The module can be any IT, XM, S3M or MOD file. It will be rendered to a .pcm\n" + "file of the same name, unless you specify otherwise with the -o option.\n" + "\n" + "The valid options are:\n" + "-o specify the output filename (defaults to the input filename with\n" + " the extension replaced with .pcm); use - to write to standard\n" + " output or . to write nowhere (useful for measuring DUMB's\n" + " performance, and DOS and Windows don't have /dev/null!)\n" + "-d set the initial delay, in seconds (default 0.0)\n" + "-v adjust the volume (default 1.0)\n" + "-s set the sampling rate in Hz (default 44100)\n" + "-8 generate 8-bit instead of 16-bit\n" + "-b generate big-endian data instead of little-endian (meaningless when\n" + " using -8)\n" + "-m generate mono output instead of stereo left/right pairs\n" + "-u generated unsigned output instead of signed\n" + "-r specify the resampling quality to use\n"); + return 1; + } + + atexit(&dumb_exit); + dumb_register_stdfiles(); + + dumb_it_max_to_mix = 256; + + duh = load_duh(fn); + if (!duh) { + duh = dumb_load_it(fn); + if (!duh) { + duh = dumb_load_xm(fn); + if (!duh) { + duh = dumb_load_s3m(fn); + if (!duh) { + duh = dumb_load_mod(fn); + if (!duh) { + fprintf(stderr, "Unable to open %s!\n", fn); + return 1; + } + } + } + } + } + + sr = duh_start_sigrenderer(duh, 0, n_channels, 0); + if (!sr) { + unload_duh(duh); + fprintf(stderr, "Unable to play file!\n"); + return 1; + } + + if (fn_out) { + if (fn_out[0] == '-' && fn_out[1] == 0) + outf = stdout; + else if (fn_out[0] == '.' && fn_out[1] == 0) + outf = NULL; + else { + outf = fopen(fn_out, "wb"); + if (!outf) { + fprintf(stderr, "Unable to open %s for writing!\n", fn_out); + duh_end_sigrenderer(sr); + unload_duh(duh); + return 1; + } + } + } else { + char *extptr = NULL, *p; + char *fn_out = malloc(strlen(fn)+5); + if (!fn_out) { + fprintf(stderr, "Out of memory!\n"); + duh_end_sigrenderer(sr); + unload_duh(duh); + return 1; + } + strcpy(fn_out, fn); + for (p = fn_out; *p; p++) + if (*p == '.') extptr = p; + if (!extptr) extptr = p; + strcpy(extptr, ".pcm"); + outf = fopen(fn_out, "wb"); + if (!outf) { + fprintf(stderr, "Unable to open %s for writing!\n", fn_out); + free(fn_out); + duh_end_sigrenderer(sr); + unload_duh(duh); + return 1; + } + free(fn_out); + } + + { + DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(sr); + dumb_it_set_loop_callback(itsr, &dumb_it_callback_terminate, NULL); + dumb_it_set_xm_speed_zero_callback(itsr, &dumb_it_callback_terminate, NULL); + } + + length = (LONG_LONG)duh_get_length(duh) * freq >> 16; + done = 0; + dots = 0; + delta = 65536.0f / freq; + bufsize = depth == 16 ? 8192 : 16384; + bufsize /= n_channels; + + { + long l = (long)floor(delay * freq + 0.5f); + l *= n_channels * (depth >> 3); + if (l) { + if (unsign) { + if (depth == 16) { + if (bigendian) { + for (i = 0; i < 8192; i++) { + buffer.s8[i*2] = 0x80; + buffer.s8[i*2+1] = 0x00; + } + } else { + for (i = 0; i < 8192; i++) { + buffer.s8[i*2] = 0x00; + buffer.s8[i*2+1] = 0x80; + } + } + } else + memset(buffer.s8, 0x80, 16384); + } else + memset(buffer.s8, 0, 16384); + while (l >= 16384) { + if (outf) fwrite(buffer.s8, 1, 16384, outf); + l -= 16384; + } + if (l && outf) fwrite(buffer.s8, 1, l, outf); + } + } + + start = clock(); + + fprintf(stderr, "................................................................\n"); + for (;;) { + int l = duh_render(sr, depth, unsign, volume, delta, bufsize, &buffer); + if (depth == 16) { + if (bigendian) { + for (i = 0; i < l * n_channels; i++) { + short val = buffer.s16[i]; + buffer.s8[i*2] = val >> 8; + buffer.s8[i*2+1] = val; + } + } else { + for (i = 0; i < l * n_channels; i++) { + short val = buffer.s16[i]; + buffer.s8[i*2] = val; + buffer.s8[i*2+1] = val >> 8; + } + } + } + if (outf) fwrite(buffer.s8, 1, l * n_channels * (depth >> 3), outf); + if (l < bufsize) break; + done += l; + l = done * 64 / length; + while (dots < 64 && l > dots) { + fprintf(stderr, "|"); + dots++; + } + } + + while (64 > dots) { + fprintf(stderr, "|"); + dots++; + } + fprintf(stderr, "\n"); + + end = clock(); + + duh_end_sigrenderer(sr); + unload_duh(duh); + if (outf && outf != stdout) fclose(outf); + + fprintf(stderr, "Elapsed time: %f seconds\n", (end - start) / (float)CLOCKS_PER_SEC); + + return 0; +} diff --git a/apps/codecs/dumb/examples/dumbplay.c b/apps/codecs/dumb/examples/dumbplay.c new file mode 100644 index 0000000000..6421cfdf34 --- /dev/null +++ b/apps/codecs/dumb/examples/dumbplay.c @@ -0,0 +1,238 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * dumbplay.c - Not-so-simple program to play / / \ \ + * music. It used to be simpler! | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * IMPORTANT NOTE: This file is not very friendly. | ' / + * I strongly recommend AGAINST using it as a \__/ + * reference for writing your own code. If you would + * like to write a program that uses DUMB, or add DUMB to an existing + * project, please use docs/howto.txt. It will help you a lot more than this + * file can. (If you have difficulty reading documentation, you are lacking + * an important coding skill, and now is as good a time as any to learn.) + */ + +#include +#include + +#ifndef ALLEGRO_DOS +#include +#endif + +/* Note that your own programs should use not "aldumb.h". <> tells + * the compiler to look in the compiler's default header directory, which is + * where DUMB should be installed before you use it (make install does this). + * Use "" when it is your own header file. This example uses "" because DUMB + * might not have been installed yet when the makefile builds it. + */ +#include "aldumb.h" + + + +#ifndef ALLEGRO_DOS +static volatile int closed = 0; +static void closehook(void) { closed = 1; } +#else +#define closed 0 +#endif + +#ifdef ALLEGRO_WINDOWS +#define GFX_DUMB_MODE GFX_GDI +#include +#define YIELD() Sleep(1) +#else +#define GFX_DUMB_MODE GFX_AUTODETECT_WINDOWED +#ifdef ALLEGRO_UNIX +#include +static void YIELD(void) +{ + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 1; + select(0, NULL, NULL, NULL, &tv); +} +#else +#define YIELD() yield_timeslice() +#endif +#endif + + + +#ifdef ALLEGRO_DOS +static int loop_callback(void *data) +{ + (void)data; + printf("Music has looped.\n"); + return 0; +} + +static int xm_speed_zero_callback(void *data) +{ + (void)data; + printf("Music has stopped.\n"); + return 0; +} +#else +static int gfx_half_width; + +static int loop_callback(void *data) +{ + (void)data; + if (gfx_half_width) { + acquire_screen(); + textout_centre(screen, font, "Music has looped.", gfx_half_width, 36, 10); + release_screen(); + } + return 0; +} + +static int xm_speed_zero_callback(void *data) +{ + (void)data; + if (gfx_half_width) { + text_mode(0); /* In case this is overwriting "Music has looped." */ + acquire_screen(); + textout_centre(screen, font, "Music has stopped.", gfx_half_width, 36, 10); + release_screen(); + } + return 0; +} +#endif + + + +static void usage(const char *exename) +{ + allegro_message( +#ifdef ALLEGRO_WINDOWS + "Usage:\n" + " At the command line: %s file\n" + " In Windows Explorer: drag a file on to this program's icon.\n" +#else + "Usage: %s file\n" +#endif + "This will play the music file specified.\n" + "File formats supported: IT XM S3M MOD.\n" + "You can control playback quality by editing dumb.ini.\n", exename); + + exit(1); +} + + + +int main(int argc, const char *const *argv) /* I'm const-crazy! */ +{ + DUH *duh; + AL_DUH_PLAYER *dp; + + if (allegro_init()) + return 1; + + if (argc != 2) + usage(argv[0]); + + set_config_file("dumb.ini"); + + if (install_keyboard()) { + allegro_message("Failed to initialise keyboard driver!\n"); + return 1; + } + + set_volume_per_voice(0); + + if (install_sound(DIGI_AUTODETECT, MIDI_NONE, NULL)) { + allegro_message("Failed to initialise sound driver!\n%s\n", allegro_error); + return 1; + } + + atexit(&dumb_exit); + + dumb_register_packfiles(); + + duh = dumb_load_it(argv[1]); + if (!duh) { + duh = dumb_load_xm(argv[1]); + if (!duh) { + duh = dumb_load_s3m(argv[1]); + if (!duh) { + duh = dumb_load_mod(argv[1]); + if (!duh) { + allegro_message("Failed to load %s!\n", argv[1]); + return 1; + } + } + } + } + + dumb_resampling_quality = get_config_int("sound", "dumb_resampling_quality", 4); + dumb_it_max_to_mix = get_config_int("sound", "dumb_it_max_to_mix", 128); + +#ifndef ALLEGRO_DOS + { + const char *fn = get_filename(argv[1]); + gfx_half_width = strlen(fn); + if (gfx_half_width < 22) gfx_half_width = 22; + gfx_half_width = (gfx_half_width + 2) * 4; + + /* set_window_title() is not const-correct (yet). */ + set_window_title((char *)"DUMB Music Player"); + + if (set_gfx_mode(GFX_DUMB_MODE, gfx_half_width*2, 80, 0, 0) == 0) { + acquire_screen(); + textout_centre(screen, font, fn, gfx_half_width, 20, 14); + textout_centre(screen, font, "Press any key to exit.", gfx_half_width, 52, 11); + release_screen(); + } else + gfx_half_width = 0; + } + +#if ALLEGRO_VERSION*10000 + ALLEGRO_SUB_VERSION*100 + ALLEGRO_WIP_VERSION >= 40105 + set_close_button_callback(&closehook); +#else + set_window_close_hook(&closehook); +#endif + +#endif + + set_display_switch_mode(SWITCH_BACKGROUND); + + dp = al_start_duh(duh, 2, 0, 1.0f, + get_config_int("sound", "buffer_size", 4096), + get_config_int("sound", "sound_freq", 44100)); + + { + DUH_SIGRENDERER *sr = al_duh_get_sigrenderer(dp); + DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(sr); + dumb_it_set_loop_callback(itsr, &loop_callback, NULL); + dumb_it_set_xm_speed_zero_callback(itsr, &xm_speed_zero_callback, NULL); + } + + for (;;) { + if (keypressed()) { + readkey(); + break; + } + + if (al_poll_duh(dp) || closed) + break; + + YIELD(); + } + + al_stop_duh(dp); + + unload_duh(duh); + + return 0; +} +END_OF_MAIN(); diff --git a/apps/codecs/dumb/include/aldumb.h b/apps/codecs/dumb/include/aldumb.h new file mode 100644 index 0000000000..dae2029316 --- /dev/null +++ b/apps/codecs/dumb/include/aldumb.h @@ -0,0 +1,93 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * aldumb.h - The user header file for DUMB with / / \ \ + * Allegro. | < / \_ + * | \/ /\ / + * Include this file if you wish to use DUMB \_ / > / + * with Allegro. It will include dumb.h for you, | \ / / + * and provide extra functionality such as audio | ' / + * stream and datafile integration. \__/ + */ + +#ifndef ALDUMB_H +#define ALDUMB_H + + +#include + +#include "dumb.h" + + +#ifdef __cplusplus + extern "C" { +#endif + + +/* Packfile Support */ + +void dumb_register_packfiles(void); + +DUMBFILE *dumbfile_open_packfile(PACKFILE *p); +DUMBFILE *dumbfile_from_packfile(PACKFILE *p); + + +/* Datafile Registration Functions */ + +#define DUMB_DAT_DUH DAT_ID('D','U','H',' ') +#define DUMB_DAT_IT DAT_ID('I','T',' ',' ') +#define DUMB_DAT_XM DAT_ID('X','M',' ',' ') +#define DUMB_DAT_S3M DAT_ID('S','3','M',' ') +#define DUMB_DAT_MOD DAT_ID('M','O','D',' ') + +void dumb_register_dat_duh(long type); +void dumb_register_dat_it(long type); +void dumb_register_dat_xm(long type); +void dumb_register_dat_s3m(long type); +void dumb_register_dat_mod(long type); + + +/* DUH Playing Functions */ + +typedef struct AL_DUH_PLAYER AL_DUH_PLAYER; + +AL_DUH_PLAYER *al_start_duh(DUH *duh, int n_channels, long pos, float volume, long bufsize, int freq); +void al_stop_duh(AL_DUH_PLAYER *dp); +void al_pause_duh(AL_DUH_PLAYER *dp); +void al_resume_duh(AL_DUH_PLAYER *dp); +void al_duh_set_priority(AL_DUH_PLAYER *dp, int priority); +void al_duh_set_volume(AL_DUH_PLAYER *dp, float volume); +int al_poll_duh(AL_DUH_PLAYER *dp); +long al_duh_get_position(AL_DUH_PLAYER *dp); + +AL_DUH_PLAYER *al_duh_encapsulate_sigrenderer(DUH_SIGRENDERER *sigrenderer, float volume, long bufsize, int freq); +DUH_SIGRENDERER *al_duh_get_sigrenderer(AL_DUH_PLAYER *dp); + +/* IMPORTANT: This function will return NULL if the music has ended. */ +DUH_SIGRENDERER *al_duh_decompose_to_sigrenderer(AL_DUH_PLAYER *dp); + +#ifdef DUMB_DECLARE_DEPRECATED + +AL_DUH_PLAYER *al_duh_encapsulate_renderer(DUH_SIGRENDERER *dr, float volume, long bufsize, int freq) DUMB_DEPRECATED; +DUH_SIGRENDERER *al_duh_get_renderer(AL_DUH_PLAYER *dp) DUMB_DEPRECATED; +DUH_SIGRENDERER *al_duh_decompose_to_renderer(AL_DUH_PLAYER *dp) DUMB_DEPRECATED; +/* Replace 'renderer' with 'sigrenderer' in each case where you called one of + * these functions. + */ + +#endif + + +#ifdef __cplusplus + } +#endif + + +#endif /* ALDUMB_H */ diff --git a/apps/codecs/dumb/include/dumb.h b/apps/codecs/dumb/include/dumb.h new file mode 100644 index 0000000000..3d6d6f940d --- /dev/null +++ b/apps/codecs/dumb/include/dumb.h @@ -0,0 +1,563 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * dumb.h - The user header file for DUMB. / / \ \ + * | < / \_ + * Include this file in any of your files in | \/ /\ / + * which you wish to use the DUMB functions \_ / > / + * and variables. | \ / / + * | ' / + * \__/ + */ + +#ifndef DUMB_H +#define DUMB_H + + +#include +#include + + +#ifdef __cplusplus + extern "C" { +#endif + + +#define DUMB_MAJOR_VERSION 0 +#define DUMB_MINOR_VERSION 9 +#define DUMB_REVISION_VERSION 2 + +#define DUMB_VERSION (DUMB_MAJOR_VERSION*10000 + DUMB_MINOR_VERSION*100 + DUMB_REVISION_VERSION) + +#define DUMB_VERSION_STR "0.9.2" + +#define DUMB_NAME "DUMB v"DUMB_VERSION_STR + +#define DUMB_YEAR 2003 +#define DUMB_MONTH 4 +#define DUMB_DAY 2 + +#define DUMB_YEAR_STR2 "03" +#define DUMB_YEAR_STR4 "2003" +#define DUMB_MONTH_STR1 "4" +#define DUMB_DAY_STR1 "2" + +#if DUMB_MONTH < 10 +#define DUMB_MONTH_STR2 "0"DUMB_MONTH_STR1 +#else +#define DUMB_MONTH_STR2 DUMB_MONTH_STR1 +#endif + +#if DUMB_DAY < 10 +#define DUMB_DAY_STR2 "0"DUMB_DAY_STR1 +#else +#define DUMB_DAY_STR2 DUMB_DAY_STR1 +#endif + + +/* WARNING: The month and day were inadvertently swapped in the v0.8 release. + * Please do not compare this constant against any date in 2002. In + * any case, DUMB_VERSION is probably more useful for this purpose. + */ +#define DUMB_DATE (DUMB_YEAR*10000 + DUMB_MONTH*100 + DUMB_DAY) + +#define DUMB_DATE_STR DUMB_DAY_STR1"."DUMB_MONTH_STR1"."DUMB_YEAR_STR4 + + +#undef MIN +#undef MAX +#undef MID + +#define MIN(x,y) (((x) < (y)) ? (x) : (y)) +#define MAX(x,y) (((x) > (y)) ? (x) : (y)) +#define MID(x,y,z) MAX((x), MIN((y), (z))) + +#undef ABS +#define ABS(x) (((x) >= 0) ? (x) : (-(x))) + + +#ifdef DEBUGMODE + +#ifndef ASSERT +#include +#define ASSERT(n) assert(n) +#endif +#ifndef TRACE +// it would be nice if this did actually trace ... +#define TRACE 1 ? (void)0 : (void)printf +#endif + +#else + +#ifndef ASSERT +#define ASSERT(n) +#endif +#ifndef TRACE +#define TRACE 1 ? (void)0 : (void)printf +#endif + +#endif + + +#define DUMB_ID(a,b,c,d) (((unsigned int)(a) << 24) | \ + ((unsigned int)(b) << 16) | \ + ((unsigned int)(c) << 8) | \ + ((unsigned int)(d) )) + + + +#ifndef LONG_LONG +#ifdef __GNUC__ +#define LONG_LONG long long +#elif defined _MSC_VER +#define LONG_LONG __int64 +#else +#error 64-bit integer type unknown +#endif +#endif + +#if __GNUC__ * 100 + __GNUC_MINOR__ >= 301 /* GCC 3.1+ */ +#ifndef DUMB_DECLARE_DEPRECATED +#define DUMB_DECLARE_DEPRECATED +#endif +#define DUMB_DEPRECATED __attribute__((__deprecated__)) +#else +#define DUMB_DEPRECATED +#endif + + +/* Basic Sample Type. Normal range is -0x800000 to 0x7FFFFF. */ + +typedef int sample_t; + + +/* Library Clean-up Management */ + +int dumb_atexit(void (*proc)(void)); + +void dumb_exit(void); + + +/* File Input Functions */ + +typedef struct DUMBFILE_SYSTEM +{ + void *(*open)(const char *filename); + int (*skip)(void *f, long n); + int (*getc)(void *f); + long (*getnc)(char *ptr, long n, void *f); + void (*close)(void *f); +} +DUMBFILE_SYSTEM; + +typedef struct DUMBFILE DUMBFILE; + +void register_dumbfile_system(DUMBFILE_SYSTEM *dfs); + +DUMBFILE *dumbfile_open(const char *filename); +DUMBFILE *dumbfile_open_ex(void *file, DUMBFILE_SYSTEM *dfs); + +long dumbfile_pos(DUMBFILE *f); +int dumbfile_skip(DUMBFILE *f, long n); + +int dumbfile_getc(DUMBFILE *f); + +int dumbfile_igetw(DUMBFILE *f); +int dumbfile_mgetw(DUMBFILE *f); + +long dumbfile_igetl(DUMBFILE *f); +long dumbfile_mgetl(DUMBFILE *f); + +unsigned long dumbfile_cgetul(DUMBFILE *f); +signed long dumbfile_cgetsl(DUMBFILE *f); + +long dumbfile_getnc(char *ptr, long n, DUMBFILE *f); + +int dumbfile_error(DUMBFILE *f); +int dumbfile_close(DUMBFILE *f); + + +/* stdio File Input Module */ + +/*void dumb_register_stdfiles(void); + +DUMBFILE *dumbfile_open_stdfile(int fd);*/ + + +/* Memory File Input Module */ + +DUMBFILE *dumbfile_open_memory(const char *data, long size); + + +/* DUH Management */ + +typedef struct DUH DUH; + +#define DUH_SIGNATURE DUMB_ID('D','U','H','!') + +void unload_duh(DUH *duh); + +DUH *load_duh(const char *filename); +DUH *read_duh(DUMBFILE *f); + +long duh_get_length(DUH *duh); + + +/* Signal Rendering Functions */ + +typedef struct DUH_SIGRENDERER DUH_SIGRENDERER; + +DUH_SIGRENDERER *duh_start_sigrenderer(DUH *duh, int sig, int n_channels, long pos); + +#ifdef DUMB_DECLARE_DEPRECATED +typedef void (*DUH_SIGRENDERER_CALLBACK)(void *data, sample_t **samples, int n_channels, long length); +/* This is deprecated, but is not marked as such because GCC tends to + * complain spuriously when the typedef is used later. See comments below. + */ + +void duh_sigrenderer_set_callback( + DUH_SIGRENDERER *sigrenderer, + DUH_SIGRENDERER_CALLBACK callback, void *data +) DUMB_DEPRECATED; +/* The 'callback' argument's type has changed for const-correctness. See the + * DUH_SIGRENDERER_CALLBACK definition just above. Also note that the samples + * in the buffer are now 256 times as large; the normal range is -0x800000 to + * 0x7FFFFF. The function has been renamed partly because its functionality + * has changed slightly and partly so that its name is more meaningful. The + * new one is duh_sigrenderer_set_analyser_callback(), and the typedef for + * the function pointer has also changed, from DUH_SIGRENDERER_CALLBACK to + * DUH_SIGRENDERER_ANALYSER_CALLBACK. (If you wanted to use this callback to + * apply a DSP effect, don't worry; there is a better way of doing this. It + * is undocumented, so contact me and I shall try to help. Contact details + * are in readme.txt.) + */ +#endif + +typedef void (*DUH_SIGRENDERER_ANALYSER_CALLBACK)(void *data, const sample_t *const *samples, int n_channels, long length); + +void duh_sigrenderer_set_analyser_callback( + DUH_SIGRENDERER *sigrenderer, + DUH_SIGRENDERER_ANALYSER_CALLBACK callback, void *data +); + +int duh_sigrenderer_get_n_channels(DUH_SIGRENDERER *sigrenderer); +long duh_sigrenderer_get_position(DUH_SIGRENDERER *sigrenderer); + +void duh_sigrenderer_set_sigparam(DUH_SIGRENDERER *sigrenderer, unsigned char id, long value); + +long duh_sigrenderer_get_samples( + DUH_SIGRENDERER *sigrenderer, + float volume, float delta, + long size, sample_t **samples +); + +void duh_sigrenderer_get_current_sample(DUH_SIGRENDERER *sigrenderer, float volume, sample_t *samples); + +void duh_end_sigrenderer(DUH_SIGRENDERER *sigrenderer); + + +/* DUH Rendering Functions */ + +long duh_render( + DUH_SIGRENDERER *sigrenderer, + int bits, int unsign, + float volume, float delta, + long size, void *sptr +); + +#ifdef DUMB_DECLARE_DEPRECATED + +long duh_render_signal( + DUH_SIGRENDERER *sigrenderer, + float volume, float delta, + long size, sample_t **samples +) DUMB_DEPRECATED; +/* Please use duh_sigrenderer_get_samples(). Arguments and functionality are + * identical. + */ + +typedef DUH_SIGRENDERER DUH_RENDERER DUMB_DEPRECATED; +/* Please use DUH_SIGRENDERER instead of DUH_RENDERER. */ + +DUH_SIGRENDERER *duh_start_renderer(DUH *duh, int n_channels, long pos) DUMB_DEPRECATED; +/* Please use duh_start_sigrenderer() instead. Pass 0 for 'sig'. */ + +int duh_renderer_get_n_channels(DUH_SIGRENDERER *dr) DUMB_DEPRECATED; +long duh_renderer_get_position(DUH_SIGRENDERER *dr) DUMB_DEPRECATED; +/* Please use the duh_sigrenderer_*() equivalents of these two functions. */ + +void duh_end_renderer(DUH_SIGRENDERER *dr) DUMB_DEPRECATED; +/* Please use duh_end_sigrenderer() instead. */ + +DUH_SIGRENDERER *duh_renderer_encapsulate_sigrenderer(DUH_SIGRENDERER *sigrenderer) DUMB_DEPRECATED; +DUH_SIGRENDERER *duh_renderer_get_sigrenderer(DUH_SIGRENDERER *dr) DUMB_DEPRECATED; +DUH_SIGRENDERER *duh_renderer_decompose_to_sigrenderer(DUH_SIGRENDERER *dr) DUMB_DEPRECATED; +/* These functions have become no-ops that just return the parameter. + * So, for instance, replace + * duh_renderer_encapsulate_sigrenderer(my_sigrenderer) + * with + * my_sigrenderer + */ + +#endif + + +/* Impulse Tracker Support */ + +extern int dumb_it_max_to_mix; + +typedef struct DUMB_IT_SIGDATA DUMB_IT_SIGDATA; +typedef struct DUMB_IT_SIGRENDERER DUMB_IT_SIGRENDERER; + +DUMB_IT_SIGDATA *duh_get_it_sigdata(DUH *duh); +DUH_SIGRENDERER *duh_encapsulate_it_sigrenderer(DUMB_IT_SIGRENDERER *it_sigrenderer, int n_channels, long pos); +DUMB_IT_SIGRENDERER *duh_get_it_sigrenderer(DUH_SIGRENDERER *sigrenderer); + +DUH_SIGRENDERER *dumb_it_start_at_order(DUH *duh, int n_channels, int startorder); + +void dumb_it_set_loop_callback(DUMB_IT_SIGRENDERER *sigrenderer, int (*callback)(void *data), void *data); +void dumb_it_set_xm_speed_zero_callback(DUMB_IT_SIGRENDERER *sigrenderer, int (*callback)(void *data), void *data); +void dumb_it_set_midi_callback(DUMB_IT_SIGRENDERER *sigrenderer, int (*callback)(void *data, int channel, unsigned char byte), void *data); + +int dumb_it_callback_terminate(void *data); +int dumb_it_callback_midi_block(void *data, int channel, unsigned char byte); + +DUH *dumb_load_it(const char *filename); +DUH *dumb_load_xm(const char *filename); +DUH *dumb_load_s3m(const char *filename); +DUH *dumb_load_mod(const char *filename); + +DUH *dumb_read_it(DUMBFILE *f); +DUH *dumb_read_xm(DUMBFILE *f); +DUH *dumb_read_s3m(DUMBFILE *f); +DUH *dumb_read_mod(DUMBFILE *f); + +int dumb_it_sd_get_n_orders(DUMB_IT_SIGDATA *sd); + +int dumb_it_sd_get_initial_global_volume(DUMB_IT_SIGDATA *sd); +void dumb_it_sd_set_initial_global_volume(DUMB_IT_SIGDATA *sd, int gv); + +int dumb_it_sd_get_mixing_volume(DUMB_IT_SIGDATA *sd); +void dumb_it_sd_set_mixing_volume(DUMB_IT_SIGDATA *sd, int mv); + +int dumb_it_sd_get_initial_speed(DUMB_IT_SIGDATA *sd); +void dumb_it_sd_set_initial_speed(DUMB_IT_SIGDATA *sd, int speed); + +int dumb_it_sd_get_initial_tempo(DUMB_IT_SIGDATA *sd); +void dumb_it_sd_set_initial_tempo(DUMB_IT_SIGDATA *sd, int tempo); + +int dumb_it_sd_get_initial_channel_volume(DUMB_IT_SIGDATA *sd, int channel); +void dumb_it_sd_set_initial_channel_volume(DUMB_IT_SIGDATA *sd, int channel, int volume); + +int dumb_it_sr_get_current_order(DUMB_IT_SIGRENDERER *sr); +int dumb_it_sr_get_current_row(DUMB_IT_SIGRENDERER *sr); + +int dumb_it_sr_get_global_volume(DUMB_IT_SIGRENDERER *sr); +void dumb_it_sr_set_global_volume(DUMB_IT_SIGRENDERER *sr, int gv); + +int dumb_it_sr_get_tempo(DUMB_IT_SIGRENDERER *sr); +void dumb_it_sr_set_tempo(DUMB_IT_SIGRENDERER *sr, int tempo); + +int dumb_it_sr_get_speed(DUMB_IT_SIGRENDERER *sr); +void dumb_it_sr_set_speed(DUMB_IT_SIGRENDERER *sr, int speed); + +#define DUMB_IT_N_CHANNELS 64 +#define DUMB_IT_N_NNA_CHANNELS 192 +#define DUMB_IT_TOTAL_CHANNELS (DUMB_IT_N_CHANNELS + DUMB_IT_N_NNA_CHANNELS) + +/* Channels passed to any of these functions are 0-based */ +int dumb_it_sr_get_channel_volume(DUMB_IT_SIGRENDERER *sr, int channel); +void dumb_it_sr_set_channel_volume(DUMB_IT_SIGRENDERER *sr, int channel, int volume); + +typedef struct DUMB_IT_CHANNEL_STATE DUMB_IT_CHANNEL_STATE; + +struct DUMB_IT_CHANNEL_STATE +{ + int channel; /* 0-based; meaningful for NNA channels */ + int sample; /* 1-based; 0 if nothing playing, then other fields undef */ + int freq; /* in Hz */ + float volume; /* 1.0 maximum; affected by ALL factors, inc. mixing vol */ + unsigned char pan; /* 0-64, 100 for surround */ + signed char subpan; /* use (pan + subpan/256.0f) or ((pan<<8)+subpan) */ + unsigned char filter_cutoff; /* 0-127 cutoff=127 AND resonance=0 */ + unsigned char filter_subcutoff; /* 0-255 -> no filters (subcutoff */ + unsigned char filter_resonance; /* 0-127 always 0 in this case) */ + /* subcutoff only changes from zero if filter envelopes are in use. The + * calculation (filter_cutoff + filter_subcutoff/256.0f) gives a more + * accurate filter cutoff measurement as a float. It would often be more + * useful to use a scaled int such as ((cutoff<<8) + subcutoff). + */ +}; + +/* Values of 64 or more will access NNA channels here. */ +void dumb_it_sr_get_channel_state(DUMB_IT_SIGRENDERER *sr, int channel, DUMB_IT_CHANNEL_STATE *state); + + +/* Signal Design Helper Values */ + +/* Use pow(DUMB_SEMITONE_BASE, n) to get the 'delta' value to transpose up by + * n semitones. To transpose down, use negative n. + */ +#define DUMB_SEMITONE_BASE 1.059463094359295309843105314939748495817 + +/* Use pow(DUMB_QUARTERTONE_BASE, n) to get the 'delta' value to transpose up + * by n quartertones. To transpose down, use negative n. + */ +#define DUMB_QUARTERTONE_BASE 1.029302236643492074463779317738953977823 + +/* Use pow(DUMB_PITCH_BASE, n) to get the 'delta' value to transpose up by n + * units. In this case, 256 units represent one semitone; 3072 units + * represent one octave. These units are used by the sequence signal (SEQU). + */ +#define DUMB_PITCH_BASE 1.000225659305069791926712241547647863626 + + +/* Signal Design Function Types */ + +typedef void sigdata_t; +typedef void sigrenderer_t; + +typedef sigdata_t *(*DUH_LOAD_SIGDATA)(DUH *duh, DUMBFILE *file); + +typedef sigrenderer_t *(*DUH_START_SIGRENDERER)( + DUH *duh, + sigdata_t *sigdata, + int n_channels, + long pos +); + +typedef void (*DUH_SIGRENDERER_SET_SIGPARAM)( + sigrenderer_t *sigrenderer, + unsigned char id, long value +); + +typedef long (*DUH_SIGRENDERER_GET_SAMPLES)( + sigrenderer_t *sigrenderer, + float volume, float delta, + long size, sample_t **samples +); + +typedef void (*DUH_SIGRENDERER_GET_CURRENT_SAMPLE)( + sigrenderer_t *sigrenderer, + float volume, + sample_t *samples +); + +typedef void (*DUH_END_SIGRENDERER)(sigrenderer_t *sigrenderer); + +typedef void (*DUH_UNLOAD_SIGDATA)(sigdata_t *sigdata); + + +/* Signal Design Function Registration */ + +typedef struct DUH_SIGTYPE_DESC +{ + long type; + DUH_LOAD_SIGDATA load_sigdata; + DUH_START_SIGRENDERER start_sigrenderer; + DUH_SIGRENDERER_SET_SIGPARAM sigrenderer_set_sigparam; + DUH_SIGRENDERER_GET_SAMPLES sigrenderer_get_samples; + DUH_SIGRENDERER_GET_CURRENT_SAMPLE sigrenderer_get_current_sample; + DUH_END_SIGRENDERER end_sigrenderer; + DUH_UNLOAD_SIGDATA unload_sigdata; +} +DUH_SIGTYPE_DESC; + +void dumb_register_sigtype(DUH_SIGTYPE_DESC *desc); + + +// Decide where to put these functions; new heading? + +sigdata_t *duh_get_raw_sigdata(DUH *duh, int sig, long type); + +DUH_SIGRENDERER *duh_encapsulate_raw_sigrenderer(sigrenderer_t *vsigrenderer, DUH_SIGTYPE_DESC *desc, int n_channels, long pos); +sigrenderer_t *duh_get_raw_sigrenderer(DUH_SIGRENDERER *sigrenderer, long type); + + +/* Sample Buffer Allocation Helpers */ + +sample_t **create_sample_buffer(int n_channels, long length); +void destroy_sample_buffer(sample_t **samples); + + +/* Silencing Helper */ + +void dumb_silence(sample_t *samples, long length); + + +/* Click Removal Helpers */ + +typedef struct DUMB_CLICK_REMOVER DUMB_CLICK_REMOVER; + +DUMB_CLICK_REMOVER *dumb_create_click_remover(void); +void dumb_record_click(DUMB_CLICK_REMOVER *cr, long pos, sample_t step); +void dumb_remove_clicks(DUMB_CLICK_REMOVER *cr, sample_t *samples, long length, float halflife); +sample_t dumb_click_remover_get_offset(DUMB_CLICK_REMOVER *cr); +void dumb_destroy_click_remover(DUMB_CLICK_REMOVER *cr); + +DUMB_CLICK_REMOVER **dumb_create_click_remover_array(int n); +void dumb_record_click_array(int n, DUMB_CLICK_REMOVER **cr, long pos, sample_t *step); +void dumb_record_click_negative_array(int n, DUMB_CLICK_REMOVER **cr, long pos, sample_t *step); +void dumb_remove_clicks_array(int n, DUMB_CLICK_REMOVER **cr, sample_t **samples, long length, float halflife); +void dumb_click_remover_get_offset_array(int n, DUMB_CLICK_REMOVER **cr, sample_t *offset); +void dumb_destroy_click_remover_array(int n, DUMB_CLICK_REMOVER **cr); + + +/* Resampling Helpers */ + +#define DUMB_RQ_ALIASING 0 +#define DUMB_RQ_LINEAR 1 +#define DUMB_RQ_CUBIC 2 +#define DUMB_RQ_N_LEVELS 3 +extern int dumb_resampling_quality; + +typedef struct DUMB_RESAMPLER DUMB_RESAMPLER; + +typedef void (*DUMB_RESAMPLE_PICKUP)(DUMB_RESAMPLER *resampler, void *data); + +struct DUMB_RESAMPLER +{ + sample_t *src; + long pos; + int subpos; + long start, end; + int dir; + DUMB_RESAMPLE_PICKUP pickup; + void *pickup_data; + int min_quality; + int max_quality; + /* Everything below this point is internal: do not use. */ + sample_t x[3]; + int overshot; +}; + +void dumb_reset_resampler(DUMB_RESAMPLER *resampler, sample_t *src, long pos, long start, long end); +DUMB_RESAMPLER *dumb_start_resampler(sample_t *src, long pos, long start, long end); +long dumb_resample(DUMB_RESAMPLER *resampler, sample_t *dst, long dst_size, float volume, float delta); +sample_t dumb_resample_get_current_sample(DUMB_RESAMPLER *resampler, float volume); +void dumb_end_resampler(DUMB_RESAMPLER *resampler); + + +/* DUH Construction */ + +DUH *make_duh( + long length, + int n_signals, + DUH_SIGTYPE_DESC *desc[], + sigdata_t *sigdata[] +); + + +#ifdef __cplusplus + } +#endif + + +#endif /* DUMB_H */ diff --git a/apps/codecs/dumb/include/internal/aldumb.h b/apps/codecs/dumb/include/internal/aldumb.h new file mode 100644 index 0000000000..a0c6d63c07 --- /dev/null +++ b/apps/codecs/dumb/include/internal/aldumb.h @@ -0,0 +1,27 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * internal/aldumb.h - The internal header file / / \ \ + * for DUMB with Allegro. | < / \_ + * | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#ifndef INTERNAL_ALDUMB_H +#define INTERNAL_ALDUMB_H + + +void _dat_unload_duh(void *duh); + + +#endif /* INTERNAL_DUMB_H */ diff --git a/apps/codecs/dumb/include/internal/dumb.h b/apps/codecs/dumb/include/internal/dumb.h new file mode 100644 index 0000000000..b4cd0ab1c4 --- /dev/null +++ b/apps/codecs/dumb/include/internal/dumb.h @@ -0,0 +1,58 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * internal/dumb.h - DUMB's internal declarations. / / \ \ + * | < / \_ + * This header file provides access to the | \/ /\ / + * internal structure of DUMB, and is liable \_ / > / + * to change, mutate or cease to exist at any | \ / / + * moment. Include it at your own peril. | ' / + * \__/ + * ... + * + * I mean it, people. You don't need access to anything in this file. If you + * disagree, contact the authors. In the unlikely event that you make a good + * case, we'll add what you need to dumb.h. Thanking you kindly. + */ + +#ifndef INTERNAL_DUMB_H +#define INTERNAL_DUMB_H + + +typedef struct DUH_SIGTYPE_DESC_LINK +{ + struct DUH_SIGTYPE_DESC_LINK *next; + DUH_SIGTYPE_DESC *desc; +} +DUH_SIGTYPE_DESC_LINK; + + +typedef struct DUH_SIGNAL +{ + sigdata_t *sigdata; + DUH_SIGTYPE_DESC *desc; +} +DUH_SIGNAL; + + +struct DUH +{ + long length; + + int n_signals; + DUH_SIGNAL **signal; +}; + + +DUH_SIGTYPE_DESC *_dumb_get_sigtype_desc(long type); + + +#endif /* INTERNAL_DUMB_H */ + diff --git a/apps/codecs/dumb/include/internal/it.h b/apps/codecs/dumb/include/internal/it.h new file mode 100644 index 0000000000..7fffed6e6b --- /dev/null +++ b/apps/codecs/dumb/include/internal/it.h @@ -0,0 +1,710 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * internal/it.h - Internal stuff for IT playback / / \ \ + * and MOD/XM/S3M conversion. | < / \_ + * | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#ifndef INTERNAL_IT_H +#define INTERNAL_IT_H + + + +#include + + + +/** TO DO: THINK ABOUT THE FOLLOWING: + +sigdata->flags & IT_COMPATIBLE_GXX + + Bit 5: On = Link Effect G's memory with Effect E/F. Also + Gxx with an instrument present will cause the + envelopes to be retriggered. If you change a + sample on a row with Gxx, it'll adjust the + frequency of the current note according to: + + NewFrequency = OldFrequency * NewC5 / OldC5; +*/ + + + +/* These #defines are TEMPORARY. They are used to write alternative code to + * handle ambiguities in the format specification. The correct code in each + * case will be determined most likely by experimentation. + */ +#define STEREO_SAMPLES_COUNT_AS_TWO +#define INVALID_ORDERS_END_SONG +#define INVALID_NOTES_CAUSE_NOTE_CUT +#define SUSTAIN_LOOP_OVERRIDES_NORMAL_LOOP +#define VOLUME_OUT_OF_RANGE_SETS_MAXIMUM + + + +#define SIGTYPE_IT DUMB_ID('I', 'T', ' ', ' ') + +#define IT_SIGNATURE DUMB_ID('I', 'M', 'P', 'M') +#define IT_INSTRUMENT_SIGNATURE DUMB_ID('I', 'M', 'P', 'I') +#define IT_SAMPLE_SIGNATURE DUMB_ID('I', 'M', 'P', 'S') + + + +/* 1 minute per 4 rows, each row 6 ticks; this is divided by the tempo to get + * the interval between ticks. + */ +#define TICK_TIME_DIVIDEND ((65536 * 60) / (4 * 6)) + + + +/* I'm not going to try to explain this, because I didn't derive it very + * formally ;) + */ +/* #define AMIGA_DIVISOR ((float)(4.0 * 14317056.0)) */ +/* I believe the following one to be more accurate. */ +#define AMIGA_DIVISOR ((float)(8.0 * 7159090.5)) + + + +typedef struct IT_MIDI IT_MIDI; +typedef struct IT_FILTER_STATE IT_FILTER_STATE; +typedef struct IT_ENVELOPE IT_ENVELOPE; +typedef struct IT_INSTRUMENT IT_INSTRUMENT; +typedef struct IT_SAMPLE IT_SAMPLE; +typedef struct IT_ENTRY IT_ENTRY; +typedef struct IT_PATTERN IT_PATTERN; +typedef struct IT_PLAYING_ENVELOPE IT_PLAYING_ENVELOPE; +typedef struct IT_PLAYING IT_PLAYING; +typedef struct IT_CHANNEL IT_CHANNEL; +typedef struct IT_CHECKPOINT IT_CHECKPOINT; +typedef struct IT_CALLBACKS IT_CALLBACKS; + + + +struct IT_MIDI +{ + unsigned char SFmacro[16][16]; // read these from 0x120 + unsigned char SFmacrolen[16]; + unsigned short SFmacroz[16]; /* Bitfield; bit 0 set = z in first position */ + unsigned char Zmacro[128][16]; // read these from 0x320 + unsigned char Zmacrolen[128]; +}; + + + +struct IT_FILTER_STATE +{ + float currsample, prevsample; +}; + + + +#define IT_ENVELOPE_ON 1 +#define IT_ENVELOPE_LOOP_ON 2 +#define IT_ENVELOPE_SUSTAIN_LOOP 4 +#define IT_ENVELOPE_PITCH_IS_FILTER 128 + +struct IT_ENVELOPE +{ + unsigned char flags; + unsigned char n_nodes; + unsigned char loop_start; + unsigned char loop_end; + unsigned char sus_loop_start; + unsigned char sus_loop_end; + signed char node_y[25]; + unsigned short node_t[25]; +}; + + + +#define NNA_NOTE_CUT 0 +#define NNA_NOTE_CONTINUE 1 +#define NNA_NOTE_OFF 2 +#define NNA_NOTE_FADE 3 + +#define DCT_OFF 0 +#define DCT_NOTE 1 +#define DCT_SAMPLE 2 +#define DCT_INSTRUMENT 3 + +#define DCA_NOTE_CUT 0 +#define DCA_NOTE_OFF 1 +#define DCA_NOTE_FADE 2 + +struct IT_INSTRUMENT +{ + int fadeout; + + IT_ENVELOPE volume_envelope; + IT_ENVELOPE pan_envelope; + IT_ENVELOPE pitch_envelope; + + unsigned char new_note_action; + unsigned char dup_check_type; + unsigned char dup_check_action; + unsigned char pp_separation; + unsigned char pp_centre; + unsigned char global_volume; + unsigned char default_pan; + unsigned char random_volume; + unsigned char random_pan; + + unsigned char filter_cutoff; + unsigned char filter_resonance; + + unsigned char map_note[120]; + unsigned short map_sample[120]; +}; + + + +#define IT_SAMPLE_EXISTS 1 +#define IT_SAMPLE_16BIT 2 +#define IT_SAMPLE_STEREO 4 +#define IT_SAMPLE_LOOP 16 +#define IT_SAMPLE_SUS_LOOP 32 +#define IT_SAMPLE_PINGPONG_LOOP 64 +#define IT_SAMPLE_PINGPONG_SUS_LOOP 128 + +#define IT_VIBRATO_SINE 0 +#define IT_VIBRATO_SAWTOOTH 1 /* Ramp down */ +#define IT_VIBRATO_SQUARE 2 +#define IT_VIBRATO_RANDOM 3 + +struct IT_SAMPLE +{ + unsigned char flags; + unsigned char global_volume; + unsigned char default_volume; + unsigned char default_pan; + + long length; + long loop_start; + long loop_end; + long C5_speed; + long sus_loop_start; + long sus_loop_end; + + unsigned char vibrato_speed; + unsigned char vibrato_depth; + unsigned char vibrato_rate; + unsigned char vibrato_waveform; + + sample_t *left; + sample_t *right; +}; + + + +#define IT_ENTRY_NOTE 1 +#define IT_ENTRY_INSTRUMENT 2 +#define IT_ENTRY_VOLPAN 4 +#define IT_ENTRY_EFFECT 8 + +#define IT_SET_END_ROW(entry) ((entry)->channel = 255) +#define IT_IS_END_ROW(entry) ((entry)->channel >= DUMB_IT_N_CHANNELS) + +#define IT_NOTE_OFF 255 +#define IT_NOTE_CUT 254 + +#define IT_ENVELOPE_SHIFT 8 + +#define IT_SURROUND 100 +#define IT_IS_SURROUND(pan) ((pan) > 64) +#define IT_IS_SURROUND_SHIFTED(pan) ((pan) > 64 << IT_ENVELOPE_SHIFT) + +#define IT_SET_SPEED 1 +#define IT_JUMP_TO_ORDER 2 +#define IT_BREAK_TO_ROW 3 +#define IT_VOLUME_SLIDE 4 +#define IT_PORTAMENTO_DOWN 5 +#define IT_PORTAMENTO_UP 6 +#define IT_TONE_PORTAMENTO 7 +#define IT_VIBRATO 8 +#define IT_TREMOR 9 +#define IT_ARPEGGIO 10 +#define IT_VOLSLIDE_VIBRATO 11 +#define IT_VOLSLIDE_TONEPORTA 12 +#define IT_SET_CHANNEL_VOLUME 13 +#define IT_CHANNEL_VOLUME_SLIDE 14 +#define IT_SET_SAMPLE_OFFSET 15 +#define IT_PANNING_SLIDE 16 +#define IT_RETRIGGER_NOTE 17 +#define IT_TREMOLO 18 +#define IT_S 19 +#define IT_SET_SONG_TEMPO 20 +#define IT_FINE_VIBRATO 21 +#define IT_SET_GLOBAL_VOLUME 22 +#define IT_GLOBAL_VOLUME_SLIDE 23 +#define IT_SET_PANNING 24 +#define IT_PANBRELLO 25 +#define IT_MIDI_MACRO 26 //see MIDI.TXT + +/* Some effects needed for XM compatibility */ +#define IT_XM_PORTAMENTO_DOWN 27 +#define IT_XM_PORTAMENTO_UP 28 +#define IT_XM_FINE_VOLSLIDE_DOWN 29 +#define IT_XM_FINE_VOLSLIDE_UP 30 +#define IT_XM_RETRIGGER_NOTE 31 + +#define IT_N_EFFECTS 32 + +/* These represent the top nibble of the command value. */ +#define IT_S_SET_FILTER 0 /* Greyed out in IT... */ +#define IT_S_SET_GLISSANDO_CONTROL 1 /* Greyed out in IT... */ +#define IT_S_FINETUNE 2 /* Greyed out in IT... */ +#define IT_S_SET_VIBRATO_WAVEFORM 3 +#define IT_S_SET_TREMOLO_WAVEFORM 4 +#define IT_S_SET_PANBRELLO_WAVEFORM 5 +#define IT_S_FINE_PATTERN_DELAY 6 +#define IT_S7 7 +#define IT_S_SET_PAN 8 +#define IT_S_SET_SURROUND_SOUND 9 +#define IT_S_SET_HIGH_OFFSET 10 +#define IT_S_PATTERN_LOOP 11 +#define IT_S_DELAYED_NOTE_CUT 12 +#define IT_S_NOTE_DELAY 13 +#define IT_S_PATTERN_DELAY 14 +#define IT_S_SET_MIDI_MACRO 15 + +/* +S0x Set filter +S1x Set glissando control +S2x Set finetune + + +S3x Set vibrato waveform to type x +S4x Set tremelo 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 +*/ + +struct IT_ENTRY +{ + unsigned char channel; /* End of row if channel >= DUMB_IT_N_CHANNELS */ + unsigned char mask; + unsigned char note; + unsigned char instrument; + unsigned char volpan; + unsigned char effect; + unsigned char effectvalue; +}; + + + +struct IT_PATTERN +{ + int n_rows; + int n_entries; + IT_ENTRY *entry; +}; + + + +#define IT_STEREO 1 +#define IT_USE_INSTRUMENTS 4 +#define IT_LINEAR_SLIDES 8 /* If not set, use Amiga slides */ +#define IT_OLD_EFFECTS 16 +#define IT_COMPATIBLE_GXX 32 + +/* Make sure IT_WAS_AN_XM and IT_WAS_A_MOD aren't set accidentally */ +#define IT_REAL_FLAGS 63 + +#define IT_WAS_AN_XM 64 /* Set for both XMs and MODs */ +#define IT_WAS_A_MOD 128 + +#define IT_ORDER_END 255 +#define IT_ORDER_SKIP 254 + +struct DUMB_IT_SIGDATA +{ + int n_orders; + int n_instruments; + int n_samples; + int n_patterns; + + int flags; + + int global_volume; + int mixing_volume; + int speed; + int tempo; + int pan_separation; + + unsigned char channel_pan[DUMB_IT_N_CHANNELS]; + unsigned char channel_volume[DUMB_IT_N_CHANNELS]; + + unsigned char *order; + unsigned char restart_position; /* for XM compatiblity */ + + IT_INSTRUMENT *instrument; + IT_SAMPLE *sample; + IT_PATTERN *pattern; + + IT_MIDI *midi; + + IT_CHECKPOINT *checkpoint; +}; + + + +struct IT_PLAYING_ENVELOPE +{ + int next_node; + int tick; +}; + + + +#define IT_PLAYING_BACKGROUND 1 +#define IT_PLAYING_SUSTAINOFF 2 +#define IT_PLAYING_FADING 4 +#define IT_PLAYING_DEAD 8 + +struct IT_PLAYING +{ + int flags; + + IT_CHANNEL *channel; + IT_SAMPLE *sample; + IT_INSTRUMENT *instrument; + IT_INSTRUMENT *env_instrument; + + unsigned short sampnum; + unsigned char instnum; + + unsigned char channel_volume; + + unsigned char volume; + unsigned short pan; + + unsigned char note; + + unsigned char filter_cutoff; + unsigned char filter_resonance; + + unsigned short true_filter_cutoff; /* These incorporate the filter envelope, and will not */ + unsigned char true_filter_resonance; /* be changed if they would be set to 127<<8 and 0. */ + + unsigned char vibrato_speed; + unsigned char vibrato_depth; + unsigned char vibrato_n; /* May be specified twice: volpan & effect. */ + unsigned char vibrato_time; + + unsigned char tremolo_speed; + unsigned char tremolo_depth; + unsigned char tremolo_time; + + unsigned char sample_vibrato_time; + int sample_vibrato_depth; /* Starts at rate?0:depth, increases by rate */ + + int slide; + float delta; + + IT_PLAYING_ENVELOPE volume_envelope; + IT_PLAYING_ENVELOPE pan_envelope; + IT_PLAYING_ENVELOPE pitch_envelope; + + int fadeoutcount; + + IT_FILTER_STATE filter_state[2]; /* Left and right */ + + DUMB_RESAMPLER resampler[2]; + + /* time_lost is used to emulate Impulse Tracker's sample looping + * characteristics. When time_lost is added to pos, the result represents + * the position in the theoretical version of the sample where all loops + * have been expanded. If this is stored, the resampling helpers will + * safely convert it for use with new loop boundaries. The situation is + * slightly more complicated if dir == -1 when the change takes place; we + * must reflect pos off the loop end point and set dir to 1 before + * proceeding. + */ + long time_lost; +}; + + + +#define IT_CHANNEL_MUTED 1 + +struct IT_CHANNEL +{ + int flags; + + unsigned char volume; + signed char volslide; + signed char xm_volslide; + + /* xm_volslide is used for volume slides done in the volume column in an + * XM file, since it seems the volume column slide is applied first, + * followed by clamping, followed by the effects column slide. IT does + * not exhibit this behaviour, so xm_volslide is maintained at zero. + */ + + unsigned char pan; + unsigned short truepan; + + unsigned char channelvolume; + signed char channelvolslide; + + unsigned char instrument; + unsigned char note; + + unsigned char SFmacro; + + unsigned char filter_cutoff; + unsigned char filter_resonance; + + unsigned char note_cut_count; + unsigned char note_delay_count; + IT_ENTRY *note_delay_entry; + + int arpeggio; + unsigned char retrig; + unsigned char xm_retrig; + int retrig_tick; + + unsigned char tremor; + unsigned char tremor_time; /* Bit 6 set if note on; bit 7 set if tremor active. */ + + int portamento; + int toneporta; + unsigned char destnote; + + /** WARNING - for neatness, should one or both of these be in the IT_PLAYING struct? */ + unsigned short sample; + unsigned char truenote; + + unsigned char midi_state; + + signed char lastvolslide; + unsigned char lastDKL; + unsigned char lastEF; /* Doubles as last portamento up for XM files */ + unsigned char lastG; + unsigned char lastHspeed; + unsigned char lastHdepth; + unsigned char lastRspeed; + unsigned char lastRdepth; + unsigned char lastI; + unsigned char lastJ; /* Doubles as last portamento down for XM files */ + unsigned char lastN; + unsigned char lastO; + unsigned char high_offset; + unsigned char lastQ; + unsigned char lastS; + unsigned char pat_loop_row; + unsigned char pat_loop_count; + unsigned char lastW; + + unsigned char xm_lastE1; + unsigned char xm_lastE2; + unsigned char xm_lastEA; + unsigned char xm_lastEB; + unsigned char xm_lastX1; + unsigned char xm_lastX2; + + IT_PLAYING *playing; +}; + + + +struct DUMB_IT_SIGRENDERER +{ + DUMB_IT_SIGDATA *sigdata; + + int n_channels; + + unsigned char globalvolume; + signed char globalvolslide; + + unsigned char tempo; + signed char temposlide; + + IT_CHANNEL channel[DUMB_IT_N_CHANNELS]; + + IT_PLAYING *playing[DUMB_IT_N_NNA_CHANNELS]; + + int tick; + int speed; + int rowcount; + + int order; /* Set to -1 if the song is terminated by a callback. */ + int row; + int processorder; + int processrow; + int breakrow; + int pat_loop_row; + + int n_rows; + + IT_ENTRY *entry_start; + IT_ENTRY *entry; + IT_ENTRY *entry_end; + + long time_left; /* Time before the next tick is processed */ + int sub_time_left; + + DUMB_CLICK_REMOVER **click_remover; + + IT_CALLBACKS *callbacks; +}; + + + +struct IT_CHECKPOINT +{ + IT_CHECKPOINT *next; + long time; + DUMB_IT_SIGRENDERER *sigrenderer; +}; + + + +struct IT_CALLBACKS +{ + int (*loop)(void *data); + void *loop_data; + /* Return 1 to prevent looping; the music will terminate abruptly. If you + * want to make the music stop but allow samples to fade (beware, as they + * might not fade at all!), use dumb_it_sr_set_speed() and set the speed + * to 0. Note that xm_speed_zero() will not be called if you set the + * speed manually, and also that this will work for IT and S3M files even + * though the music can't stop in this way by itself. + */ + + int (*xm_speed_zero)(void *data); + void *xm_speed_zero_data; + /* Return 1 to terminate the mod, without letting samples fade. */ + + int (*midi)(void *data, int channel, unsigned char byte); + void *midi_data; + /* Return 1 to prevent DUMB from subsequently interpreting the MIDI bytes + * itself. In other words, return 1 if the Zxx macros in an IT file are + * controlling filters and shouldn't be. + */ +}; + + + +void _dumb_it_end_sigrenderer(sigrenderer_t *sigrenderer); +void _dumb_it_unload_sigdata(sigdata_t *vsigdata); + +extern DUH_SIGTYPE_DESC _dumb_sigtype_it; + + + +long _dumb_it_build_checkpoints(DUMB_IT_SIGDATA *sigdata); + + + +#define XM_APPREGIO 0 +#define XM_PORTAMENTO_UP 1 +#define XM_PORTAMENTO_DOWN 2 +#define XM_TONE_PORTAMENTO 3 +#define XM_VIBRATO 4 +#define XM_VOLSLIDE_TONEPORTA 5 +#define XM_VOLSLIDE_VIBRATO 6 +#define XM_TREMOLO 7 +#define XM_SET_PANNING 8 +#define XM_SAMPLE_OFFSET 9 +#define XM_VOLUME_SLIDE 10 /* A */ +#define XM_POSITION_JUMP 11 /* B */ +#define XM_SET_CHANNEL_VOLUME 12 /* C */ +#define XM_PATTERN_BREAK 13 /* D */ +#define XM_E 14 /* E */ +#define XM_SET_TEMPO_BPM 15 /* F */ +#define XM_SET_GLOBAL_VOLUME 16 /* G */ +#define XM_GLOBAL_VOLUME_SLIDE 17 /* H */ +#define XM_KEY_OFF 20 /* K (undocumented) */ +#define XM_SET_ENVELOPE_POSITION 21 /* L */ +#define XM_PANNING_SLIDE 25 /* P */ +#define XM_MULTI_RETRIG 27 /* R */ +#define XM_TREMOR 29 /* T */ +#define XM_X 33 /* X */ +#define XM_N_EFFECTS (10+26) + +#define XM_E_SET_FILTER 0x0 +#define XM_E_FINE_PORTA_UP 0x1 +#define XM_E_FINE_PORTA_DOWN 0x2 +#define XM_E_SET_GLISSANDO_CONTROL 0x3 +#define XM_E_SET_VIBRATO_CONTROL 0x4 +#define XM_E_SET_FINETUNE 0x5 +#define XM_E_SET_LOOP 0x6 +#define XM_E_SET_TREMOLO_CONTROL 0x7 +#define XM_E_RETRIG_NOTE 0x9 +#define XM_E_FINE_VOLSLIDE_UP 0xA +#define XM_E_FINE_VOLSLIDE_DOWN 0xB +#define XM_E_NOTE_CUT 0xC +#define XM_E_NOTE_DELAY 0xD +#define XM_E_PATTERN_DELAY 0xE + +#define XM_X_EXTRAFINE_PORTA_UP 1 +#define XM_X_EXTRAFINE_PORTA_DOWN 2 + +/* To make my life a bit simpler during conversion, effect E:xy is converted + * to effect number EBASE+x:y. The same applies to effect X, and IT's S. That + * way, these effects can be manipulated like regular effects. + */ +#define EBASE (XM_N_EFFECTS) +#define XBASE (EBASE+16) +#define SBASE (IT_N_EFFECTS) + +#define EFFECT_VALUE(x, y) (((x)<<4)|(y)) +#define HIGH(v) ((v)>>4) +#define LOW(v) ((v)&0x0F) +#define SET_HIGH(v, x) v = (((x)<<4)|((v)&0x0F)) +#define SET_LOW(v, y) v = (((v)&0xF0)|(y)) +#define BCD_TO_NORMAL(v) (HIGH(v)*10+LOW(v)) + + + +#if 0 +unsigned char **_dumb_malloc2(int w, int h); +void _dumb_free2(unsigned char **line); +#endif + +void _dumb_it_xm_convert_effect(int effect, int value, IT_ENTRY *entry); +int _dumb_it_fix_invalid_orders(DUMB_IT_SIGDATA *sigdata); + + + +#endif /* INTERNAL_IT_H */ diff --git a/apps/codecs/dumb/licence.txt b/apps/codecs/dumb/licence.txt new file mode 100644 index 0000000000..231bfa7f1d --- /dev/null +++ b/apps/codecs/dumb/licence.txt @@ -0,0 +1,54 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * licence.txt - Conditions for use of DUMB. / / \ \ + * | < / \_ + * If you do not agree to these terms, please | \/ /\ / + * do not use DUMB. \_ / > / + * | \ / / + * Information in [brackets] is provided to aid | ' / + * interpretation of the licence. \__/ + */ + + +Dynamic Universal Music Bibliotheque + +Copyright (C) 2001-2003 Ben Davis, Robert J Ohannessian and Julien Cugniere + +This software is provided 'as-is', without any express or implied warranty. +In no event shall 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, you are requested to acknowledge its use in the product + documentation, along with details on where to get an unmodified version of + this software, but this is not a strict requirement. + + [Note that the above point asks for a link to DUMB, not just a mention. + Googling for DUMB doesn't help much! The URL is "http://dumb.sf.net/".] + + [The only reason why the link is not strictly required is that such a + requirement prevents DUMB from being used in projects with certain other + licences, notably the GPL. See http://www.gnu.org/philosophy/bsd.html .] + +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 from or altered in any source distribution. + +4. If you are using the Program in someone else's bedroom at any Monday + 3:05 PM, you are not allowed to modify the Program for ten minutes. [This + clause provided by Inphernic; every licence should contain at least one + clause, the reasoning behind which is far from obvious.] diff --git a/apps/codecs/dumb/make/Makefile.inc b/apps/codecs/dumb/make/Makefile.inc new file mode 100644 index 0000000000..4137b04804 --- /dev/null +++ b/apps/codecs/dumb/make/Makefile.inc @@ -0,0 +1,34 @@ +# This file contains the main rules for compiling the library. It is included +# twice with different values for CFLAGS and OBJDIR, so the optimised and +# debugging libraries are both built. + +CORE_OBJECTS := $(addprefix $(OBJDIR)/, $(notdir $(patsubst %.c, %.o, $(CORE_MODULES)))) +ALLEGRO_OBJECTS := $(addprefix $(OBJDIR)/, $(notdir $(patsubst %.c, %.o, $(ALLEGRO_MODULES)))) + + +# Pass the current value of CFLAGS through to the commands. Or, more +# accurately, create a local copy of the current CFLAGS variable. This is +# necessary because Make doesn't expand variables in commands until they are +# executed. +$(CORE_LIB_FILE): CFLAGS := $(CFLAGS) +$(ALLEGRO_LIB_FILE): CFLAGS := $(CFLAGS) + + +$(OBJDIR)/%.o: src/core/%.c include/dumb.h include/internal/dumb.h + $(CC) -c -o $@ $< $(CFLAGS) + +$(OBJDIR)/%.o: src/helpers/%.c include/dumb.h + $(CC) -c -o $@ $< $(CFLAGS) + +$(OBJDIR)/%.o: src/it/%.c include/dumb.h include/internal/it.h + $(CC) -c -o $@ $< $(CFLAGS) + +$(OBJDIR)/%.o: src/allegro/%.c include/aldumb.h include/dumb.h \ + include/internal/aldumb.h include/internal/dumb.h + $(CC) -c -o $@ $< $(CFLAGS) $(WFLAGS_ALLEGRO) + +$(CORE_LIB_FILE): $(CORE_OBJECTS) + $(AR) rs $@ $^ + +$(ALLEGRO_LIB_FILE): $(ALLEGRO_OBJECTS) + $(AR) rs $@ $^ diff --git a/apps/codecs/dumb/make/config.bat b/apps/codecs/dumb/make/config.bat new file mode 100644 index 0000000000..2d33a61511 --- /dev/null +++ b/apps/codecs/dumb/make/config.bat @@ -0,0 +1,33 @@ +@ECHO OFF + +REM This file does an interactive configuration for users of DOS and Windows. +REM It creates a config.txt file for inclusion in the Makefile. This batch +REM file should be run indirectly through the 'make config' target (or the +REM 'make' target the first time). + +IF EXIST make\dumbask.exe GOTO dumbaskok +ECHO You should not be running this directly! Use 'make' or 'make config'. +GOTO end +:dumbaskok + +make\dumbask.exe "Would you like to compile DUMB for DJGPP or MinGW (D/M)? " DM +IF ERRORLEVEL 1 GOTO mingw +ECHO include make/djgpp.inc>make\config.tmp +GOTO djgpp +:mingw +ECHO include make/mingw.inc>make\config.tmp +:djgpp + +ECHO ALL_TARGETS := core core-examples core-headers>>make\config.tmp + +make\dumbask.exe "Would you like support for Allegro (Y/N)? " +IF NOT ERRORLEVEL 1 ECHO ALL_TARGETS += allegro allegro-examples allegro-headers>>make\config.tmp + +IF EXIST make\config.txt DEL make\config.txt +REN make\config.tmp config.txt + +ECHO Configuration complete. +ECHO Run 'make config' to change it in the future. +PAUSE + +:end diff --git a/apps/codecs/dumb/make/config.sh b/apps/codecs/dumb/make/config.sh new file mode 100755 index 0000000000..f42e141176 --- /dev/null +++ b/apps/codecs/dumb/make/config.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +# This file does an interactive configuration for users of Unix-like systems. +# It creates a config.txt file for inclusion in the Makefile. This script +# should be run indirectly through the 'make config' target (or the 'make' +# target the first time). + +if [ ! -e make/dumbask ]; then + echo "You should not be running this directly! Use 'make' or 'make config'." + exit +fi + +echo 'include make/unix.inc' > make/config.tmp + +echo 'ALL_TARGETS := core core-examples core-headers' >> make/config.tmp + +if make/dumbask 'Would you like support for Allegro (Y/N)? ' YN; then + echo 'ALL_TARGETS += allegro allegro-examples allegro-headers' >> make/config.tmp +fi + + +if [ ! -z $DEFAULT_PREFIX ]; then +echo "Please specify an installation prefix (default $DEFAULT_PREFIX)." +echo -n '> ' +read PREFIX +if [ -z $PREFIX ]; then PREFIX=$DEFAULT_PREFIX; fi +echo "PREFIX := $PREFIX" >> make/config.tmp +fi + +mv -f make/config.tmp make/config.txt + +echo 'Configuration complete.' +echo "Run 'make config' to change it in the future." +echo -n 'Press Enter to continue ... ' +read diff --git a/apps/codecs/dumb/make/config.txt b/apps/codecs/dumb/make/config.txt new file mode 100644 index 0000000000..abe4397216 --- /dev/null +++ b/apps/codecs/dumb/make/config.txt @@ -0,0 +1,3 @@ +include make/unix.inc +ALL_TARGETS := core +PREFIX := /usr diff --git a/apps/codecs/dumb/make/djgpp.inc b/apps/codecs/dumb/make/djgpp.inc new file mode 100644 index 0000000000..4147ab26a4 --- /dev/null +++ b/apps/codecs/dumb/make/djgpp.inc @@ -0,0 +1,28 @@ +# This file contains DJGPP-specific definitions. It will be included by the +# main Makefile when you compile with DJGPP. + +PLATFORM := djgpp + +APOST := \' + +# Macro for replacing / with \ where necessary. Usage: $(call FIX,path) +FIX = $(subst /,\,$(subst /*,\\\*,$(1))) + +ECHO = @$(COMSPEC) /C ECHO $(1) +# Note: the following two macros only work for single files! +DELETE = $(COMSPEC) /C DEL $(call FIX,$(1)) +COPY = $(COMSPEC) /C COPY $(call FIX,$(1)) $(call FIX,$(2)) + +EXE_SUFFIX := .exe + +LINK_MATH := +LINK_ALLEGRO := -lalleg + +ifndef DJDIR +.PHONY: error +error: + $(call ECHO,Your DJDIR environment variable is not set!) + $(call ECHO,Please refer to DJGPP's documentation and install it properly.) +endif + +PREFIX := $(DJDIR) diff --git a/apps/codecs/dumb/make/dumbask b/apps/codecs/dumb/make/dumbask new file mode 100755 index 0000000000..ee6f5f7ecd Binary files /dev/null and b/apps/codecs/dumb/make/dumbask differ diff --git a/apps/codecs/dumb/make/dumbask.c b/apps/codecs/dumb/make/dumbask.c new file mode 100644 index 0000000000..da89fab97b --- /dev/null +++ b/apps/codecs/dumb/make/dumbask.c @@ -0,0 +1,30 @@ +#include +#include + + +int main(int argc, const char *const argv[]) +{ + const char *message = argv[1]; + const char *options; + + if (!message) { + fprintf(stderr, + "dumbask: asks the user a question.\n" + "Specify a message as the first argument (quoted!).\n" + "You may optionally specify the choices as the second argument.\n" + "Default choices are YN. Exit code is 0 for first, 1 for second, etc.\n"); + return 0; + } + + options = argv[2] ? : "YN"; /* I _had_ to use a GNU Extension _somewhere_! */ + + printf("%s", argv[1]); + + for (;;) { + char c = toupper(getchar()); + int i; + for (i = 0; options[i]; i++) + if (c == toupper(options[i])) + return i; + } +} diff --git a/apps/codecs/dumb/make/mingw.inc b/apps/codecs/dumb/make/mingw.inc new file mode 100644 index 0000000000..e58de58788 --- /dev/null +++ b/apps/codecs/dumb/make/mingw.inc @@ -0,0 +1,28 @@ +# This file contains MinGW-specific definitions. It will be included by the +# main Makefile when you compile with MinGW. + +PLATFORM := mingw + +APOST := \' + +# Macro for replacing / with \ where necessary. Usage: $(call FIX,path) +FIX = $(subst /,\,$(subst /*,\\\*,$(1))) + +ECHO = @$(COMSPEC) /C ECHO $(1) +# Note: the following two macros only work for single files! +DELETE = $(COMSPEC) /C DEL $(call FIX,$(1)) +COPY = $(COMSPEC) /C COPY $(call FIX,$(1)) $(call FIX,$(2)) + +EXE_SUFFIX := .exe + +LINK_MATH := +LINK_ALLEGRO := -lalleg + +ifndef MINGDIR +.PHONY: error +error: + $(call ECHO,Your MINGDIR environment variable is not set!) + $(call ECHO,Please set it to point to the directory containing your MinGW installation.) +endif + +PREFIX := $(MINGDIR) diff --git a/apps/codecs/dumb/make/unix.inc b/apps/codecs/dumb/make/unix.inc new file mode 100644 index 0000000000..89d47c0997 --- /dev/null +++ b/apps/codecs/dumb/make/unix.inc @@ -0,0 +1,20 @@ +# This file contains definitions suitable for Unix-compatible systems. It will +# be included by the main Makefile when you compile on such a system. + +PLATFORM := unix + +APOST := \' + +# Macro that on DOS and Windows would replace / with \. Usage: $(call FIX,path) +FIX = $(1) + +ECHO = @echo $(1) +DELETE = rm -f $(1) +COPY = cp $(1) $(2) + +EXE_SUFFIX := + +LINK_MATH := -lm +LINK_ALLEGRO := `allegro-config --libs` + +# PREFIX is set by config.sh. diff --git a/apps/codecs/dumb/readme.txt b/apps/codecs/dumb/readme.txt new file mode 100644 index 0000000000..45abd1a17a --- /dev/null +++ b/apps/codecs/dumb/readme.txt @@ -0,0 +1,421 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * readme.txt - General information on DUMB. / / \ \ + * | < / \_ + * | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + + +******************** +*** Introduction *** +******************** + + +Thank you for downloading DUMB! You should have the following documentation: + + readme.txt - This file + licence.txt - Conditions for the use of this software + release.txt - Release notes and changes for this and past releases + docs/ + howto.txt - Step-by-step instructions on adding DUMB to your project + faq.txt - Frequently asked questions and answers to them + dumb.txt - DUMB library reference + deprec.txt - Information about deprecated parts of the API + ptr.txt - Quick introduction to pointers for those who need it + fnptr.txt - Explanation of function pointers for those who need it + modplug.txt - Our official position regarding ModPlug Tracker + +This file will help you get DUMB set up. If you have not yet done so, please +read licence.txt and release.txt before proceeding. After you've got DUMB set +up, please refer to the files in the docs/ directory at your convenience. I +recommend you start with howto.txt. + + +**************** +*** Features *** +**************** + + +Here is the statutory feature list: + +- Freeware + +- Supports playback of IT, XM, S3M and MOD files + +- Faithful to the original trackers, especially IT; if it plays your module + wrongly, please tell me so I can fix the bug! (But please don't complain + about differences between DUMB and ModPlug Tracker; see docs/modplug.txt) + +- Accurate support for low-pass resonant filters for IT files + +- Very accurate timing and pitching; completely deterministic playback + +- Click removal + +- Facility to embed music files in other files (e.g. Allegro datafiles) + +- Three resampling quality settings: aliasing, linear interpolation and cubic + interpolation + +- Number of samples playing at once can be limited to reduce processor usage, + but samples will come back in when other louder ones stop + +- All notes will be present and correct even if you start a piece of music in + the middle + +- Fast seeking to any point before the music first loops (seeking time + increases beyond this point) + +- Audio generated can be used in any way; DUMB does not necessarily send it + straight to a sound output system + +- Makefile provided for DJGPP, MinGW, Linux, BeOS and Mac OS X; project file + provided for MSVC 6 (please contact me if you'd like to submit or request + support for a new platform; the code itself should port anywhere that has a + 32-bit C compiler) + +- Can be used with Allegro, can be used without (if you'd like to help make + DUMB more approachable to people who aren't using Allegro, please contact + me) + + +********************* +*** What you need *** +********************* + + +To use DUMB, you need a 32-bit C compiler (GCC and MSVC are fine). If you +have Allegro, DUMB can integrate with its audio streams and datafiles, making +your life easier. If you do not wish to use Allegro, you will have to do some +work to get music playing back. The 'dumbplay' example program requires +Allegro. + + Allegro - http://alleg.sf.net/ + +Neil Walker has kindly uploaded some DUMB binaries at +http://retrospec.sgn.net/allegro/ . They may not always be up to date, so you +should try to compile it yourself first. + + +********************************************** +*** How to set DUMB up with DJGPP or MinGW *** +********************************************** + + +You should have got the .zip version. If for some reason you got the .tar.gz +version instead, you may have to convert make/config.bat to DOS text file +format. WinZip does this automatically by default. Otherwise, loading it into +MS EDIT and saving it again should do the trick. You will have to do the same +for any files you want to view in Windows Notepad. If you have problems, just +go and download the .zip instead. + +Make sure you preserved the directory structure when you extracted DUMB from +the archive. Most unzipping programs will do this by default, but pkunzip +requires you to pass -d. If not, please delete DUMB and extract it again +properly. + +If you are using Windows, open an MS-DOS Prompt or a Windows Command Line. +Change to the directory into which you unzipped DUMB. + +Type the following: + + make + +DUMB will ask you whether you wish to compile for DJGPP or MinGW. Then it +will ask you whether you want support for Allegro. (You have to have made and +installed Allegro's optimised library for this to work.) Finally, it will +compile optimised and debugging builds of DUMB, along with the example +programs. When it has finished, run the following to install the libraries: + + make install + +All done! If you ever need the configuration again (e.g. if you compiled for +DJGPP before and you want to compile for MinGW now), run the following: + + make config + +See the comments in the makefile for other targets. + +Note: the makefile will only work properly if you have COMSPEC or ComSpec set +to point to command.com or cmd.exe. If you set it to point to a Unix-style +shell, the makefile won't work. + +Please let me know if you have any trouble. + +Scroll down for information on the example programs. Refer to docs/howto.txt +when you are ready to start programming with DUMB. If you use DUMB in a game, +let me know - I might decide to place a link to your game on DUMB's website! + + +****************************************************** +*** How to set DUMB up with Microsoft Visual C++ 6 *** +****************************************************** + + +You should have got the .zip version. If for some reason you got the .tar.gz +version instead, you may have to convert some files to DOS text file format. +WinZip does this automatically by default. Otherwise, loading such files into +MS EDIT and saving them again should do the trick. You will have to do this +for any files you want to view in Windows Notepad. If you have problems, just +go and download the .zip instead. + +Make sure you preserved the directory structure when you extracted DUMB from +the archive. Most unzipping programs will do this by default, but pkunzip +requires you to pass -d. If not, please delete DUMB and extract it again +properly. + +DUMB now comes with a project file for Microsoft Visual C++ 6. To add DUMB to +your project: + +1. Open your project in VC++. +2. Select Project|Insert Project into Workspace... +3. Navigate to the dumb\vc6 directory, and select dumb.dsp. +4. Select Build|Set Active Configuration..., and reselect one of your + project's configurations. +5. Select Project|Dependencies... and ensure your project is dependent on + DUMB. +6. Select Project|Settings..., Settings for: All Configurations, C/C++ tab, + Preprocessor category. Add the DUMB include directory to the Additional + Include Directories box. +7. Ensure that for all the projects in the workspace (or more likely just all + the projects in a particular dependency chain) the run-time libraries are + the same. That's in Project|Settings, C/C++ tab, Code generation category, + Use run-time library dropdown. The settings for Release and Debug are + separate, so you'll have to change them one at a time. Exactly which run- + time library you use will depend on what you need; it doesn't appear that + DUMB has any particular requirements, so set it to whatever you're using + now. + +Good thing you only have to do all that once ... + +If you have the Intel compiler installed, it will - well, should - be used to +compile DUMB. The only setting I added is /QxiM. This allows the compiler to +use PPro and MMX instructions, and so when compiling with Intel the resultant +EXE will require a Pentium II or greater. I don't think this is unreasonable. +After all, it is 2003 :) + +If you don't have the Intel compiler, VC will compile DUMB as normal. + +This project file and these instructions were provided by Tom Seddon (I hope +I got his name right; I had to guess it from his e-mail address!). They are +untested by me. If you have problems, check the download page at +http://dumb.sf.net/ to see if they are addressed; failing that, direct +queries to me and I'll try to figure them out. + +When you are ready to start using DUMB, refer to docs/howto.txt. If you use +DUMB in a game, let me know - I might decide to place a link to your game on +DUMB's website! + + +******************************************************************** +*** How to set DUMB up on Linux, BeOS and possibly even Mac OS X *** +******************************************************************** + + +You should have got the .tar.gz version. If for some reason you got the .zip +version instead, you may have to use dtou on some or all of the text files. +If you have problems, just go and download the .tar.gz instead. + +First, run the following command as a normal user: + + make + +You will be asked whether you want Allegro support. Then, unless you are on +BeOS, you will be asked where you'd like DUMB to install its headers, +libraries and examples (which will go in the include/, lib/ and bin/ +subdirectories of the prefix you specify). BeOS has fixed locations for these +files. Once you have specified these pieces of information, the optimised and +debugging builds of DUMB will be compiled, along with the examples. When it +has finished, you can install them with: + + make install + +You may need to be root for this to work. It depends on the prefix you chose. + +Note: the makefile will only work if COMSPEC and ComSpec are both undefined. +If either of these is defined, the makefile will try to build for a Windows +system, and will fail. + +Please let me know if you have any trouble. + +Information on the example programs is just below. Refer to docs/howto.txt +when you are ready to start programming with DUMB. If you use DUMB in a game, +let me know - I might decide to place a link to your game on DUMB's website! + + +**************************** +*** The example programs *** +**************************** + + +Two example programs are provided. On DOS and Windows, you can find them in +the examples subdirectory. On other systems they will be installed system- +wide. + +dumbplay + This program will only be built if you have Allegro. Pass it the filename + of an IT, XM, S3M or MOD file, and it will play it. It's not a polished + player with real-time threading or anything - so don't complain about it + stuttering while you use other programs - but it does show DUMB's fidelity + nicely. You can control the playback quality by editing dumb.ini, which + must be in the current working directory. (This is a flaw for systems + where the program is installed system-wide, but it is non-fatal.) Have a + look at the examples/dumb.ini file for further information. + +dumbout + This program does not need Allegro. You can use it to stream an IT, XM, + S3M or MOD file to raw PCM. This can be used as input to an encoder like + oggenc (with appropriate command-line options), or it can be sent to a + .pcm file which can be read by any respectable waveform editor. No .wav + support yet, sorry. This program is also convenient for timing DUMB. + Compare the time it takes to render a module with the module's playing + time! dumbout doesn't try to read any configuration file; the options are + set on the command line. + + +********************************************* +*** Downloading music or writing your own *** +********************************************* + + +If you would like to compose your own music modules, then first I must offer +a word of warning: not everyone is capable of composing music. Do not assume +you will be able to learn the art. By all means have a go; if you can learn +to play tunes on the computer keyboard, you're well on the way to being a +composer! + +The best programs for the job are the trackers that pioneered the file +formats: + + Impulse Tracker - IT files - http://www.noisemusic.org/it/ + Fast Tracker II - XM files - http://www.gwinternet.com/music/ft2/ + Scream Tracker 3 - S3M files - + http://www.united-trackers.org/resources/software/screamtracker.htm + +MOD files come from the Amiga; I do not know what PC tracker to recommend for +editing these. If you know of one, let me know! In the meantime, I would +recommend using a more advanced file format. However, don't convert your +existing MODs just for the sake of it. + +Note that Fast Tracker II is Shareware. It arguably offers the best +interface, but the IT file format is more powerful and better defined. +Impulse Tracker and Scream Tracker 3 are Freeware. DUMB is likely to be at +its best with IT files. + +These editors are DOS programs. Users of DOS-incapable operating systems may +like to try ModPlug Tracker, but should read docs/modplug.txt before using it +for any serious work. If you use a different operating system, or if you know +of any module editors for Windows that are more faithful to the original +trackers' playback, please give me some links so I can put them here! + + ModPlug Tracker - http://www.modplug.com/ + +BEWARE OF WINAMP! Although it's excellent for MP3s, it is notorious for being +one of the worst module players in existence; very few modules play correctly +with it. There are plug-ins available to improve Winamp's module support, for +example WSP. + + Winamp - http://www.winamp.com/ + WSP - http://www.spytech.cz/index.php?sec=demo + +Samples and instruments are the building blocks of music modules. You can +download samples at: + + http://www.tump.net/ + +If you would like to download module files composed by other people, check +the following sites: + + http://www.modarchive.com/ + http://www.scene.org/ + http://www.tump.net/ + http://www.homemusic.cc/main.php + http://www.modplug.com/ + +Once again, if you know of more sites where samples or module files are +available for download, please let me know. + +If you wish to use someone's music in your game, please respect the +composer's wishes. In general, you should ask the composer. Music that has +been placed in the Public Domain can be used by anyone for anything, but it +wouldn't do any harm to ask anyway if you know who the author is. In most +cases the author will be thrilled, so don't hesitate! + +A note about converting modules from one format to another: don't do it, +unless you are a musician and are prepared to go through the file and make +sure everything sounds the way it should! The module formats are all slightly +different, and converting from one format to another will usually do some +damage. + +Instead, it is recommended that you allow DUMB to interpret the original file +as it sees fit. DUMB may make mistakes (it does a lot of conversion on +loading), but future versions of DUMB will be able to rectify these mistakes. +On the other hand, if you convert the file, the damage is permanent. + + +*********************** +*** Contact details *** +*********************** + + +If you have trouble with DUMB, or want to contact me for any other reason, my +e-mail address is given below. However, I may be able to help more if you +come on to IRC EFnet #dumb. + +IRC stands for Internet Relay Chat, and is a type of chat network. Several +such networks exist, and EFnet is a popular one. In order to connect to an +IRC network, you first need an IRC client. Here are some: + + http://www.xchat.org/ + http://www.visualirc.net/beta.php + http://www.mirc.com/ + +Getting on to IRC can be a steep cliff, but it is not insurmountable, and +it's well worth it. Once you have set up the client software, you need to +connect to a server. Here is a list of EFnet servers I have had success with. +Type "/server" (without quotes), then a space, then the name of a server. + + irc.homelien.no + irc.webgiro.se + efnet.vuurwerk.nl + efnet.demon.co.uk + irc.isdnet.fr + irc.prison.net + +If these servers do not work, visit http://efnet.org/ircdb/servers.php for a +huge list of other EFnet servers to try. + +Once you're connected, type the following: + + /join #dumb + +A window will appear, and you can ask your question. It should be clear +what's going on from this point onwards. I am 'entheh'. Note that unlike many +other nerds I am not always at my computer, so if I don't answer your +question, don't take it personally! I will usually be able to read your +question when I come back. + + +****************** +*** Conclusion *** +****************** + + +This is the conclusion. + + +Ben Davis +entheh@users.sf.net +IRC EFnet #dumb diff --git a/apps/codecs/dumb/release.txt b/apps/codecs/dumb/release.txt new file mode 100644 index 0000000000..eff4b8ded8 --- /dev/null +++ b/apps/codecs/dumb/release.txt @@ -0,0 +1,406 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * release.txt - Release notes for DUMB. / / \ \ + * | < / \_ + * | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + + +****************************************** +*** DUMB v0.9.2, released 2 April 2003 *** +****************************************** + +Yes, there really has been a release. This is not a day-late April fools' +joke. + +DUMB's full name has changed! The old "Dedicated Universal Music +Bastardisation" was rather silly, and not much more than a forced attempt at +finding words beginning with D, U, M and B. I spent weeks and weeks browsing +dictionaries and hopelessly asking others for bright ideas, until the +brilliant Chris "Kitty Cat" Robinson came up with "Dynamic". I decided to +keep the U as Universal, since a DUH struct can hold digital music in any +format. Now all that remained was the B, but it didn't take me long to come +up with Bibliotheque, which, despite looking French, is indeed considered an +English word by Oxford English Dictionary Online, to which my university has +a subscription. So there you have it - the name now makes sense. + +The two most significant additions to the project would have to be the new +thread safety (with an important restriction, detailed in docs/dumb.txt), and +the new build system. The silly 'makeall' and 'makecore' scripts are gone. If +you are a GCC user, all you need do now is run 'make' and 'make install', as +for other projects. You don't even have to run a 'fix' script any more! There +are some caveats, which are covered in readme.txt. If you use Microsoft +Visual C++ 6, you no longer need to obtain GCC and GNU Make - there is a +project file just for you. + +Huge thanks go to Steve Terry for testing on Windows XP - about five times - +and to lillo for testing on BeOS and Mac OS X. Thanks also to X-G for testing +on a Windows system that has consistently posed problems for DUMB's old +makefiles. + +There was a bug whereby al_poll_duh() would sometimes cause the music to +resume playing if you called it after al_pause_duh(). Whether this was DUMB's +fault for misusing Allegro's API, or a bug in Allegro, is unclear, but this +release makes it work. + +In one of my projects, I found that my AL_DUH_PLAYER stopped playing when +there were lots of other sound effects. In order to fix this, I programmed +DUMB to set the priority of the stream's voice to 255, the maximum. I also +added al_duh_set_priority(), so you can set the priority yourself if you need +to. + +The resampling code has undergone a transformation. The bad news is that the +linear average code is no longer in use. The good news is that where DUMB's +resamplers used to require three extra samples' worth of memory to be +allocated and initialised, it now copes with just the sample data. And it +does a very good job at bouncing off loop points and otherwise hurtling +around the sample. The resampling code is considerably more complicated, but +the code that uses the resamplers is considerably simpler - and if you +noticed a slight click in some bidirectionally looping samples, you'll be +pleased to know that that click is gone! + +I have also devoted some effort to optimisation. It seemed hopeless for a +while, but then I actually figured out a way of making it faster AND more +accurate at the same time! DUMB is now quite a bit faster than it was, and it +mixes not with 16-bit precision, but with 24-bit precision. (It used 32-bit +integers all along, but the difference is that it now makes use of 256 times +as much of the integer's range.) + +There have been the usual improvements to playback. The last release occurred +rather too soon after I had fixed the XM effect memories; EAx and EBx, fine +volume ramps, had been neglected. These are now handled properly. + +In previous versions of DUMB, muted channels in IT were actually played with +surround sound panning (where the right-hand channel is inverted). This has +been fixed, so muted channels will really be muted now. + +There were also some subtle problems with the way DUMB handled New Note +Actions for IT files. It turned out that, in all releases of DUMB so far, +pitch, filter and panning envelopes and sample vibrato were not being +processed for any note that was forced into the background by a new note on +the same channel! This only affected IT files. Not only has this been fixed, +but envelope interpolation is much more accurate. Long trailing envelope- +driven fade-outs sound a lot better now! + +Since panning and filter envelopes are more precise, extra fields have been +added to the DUMB_IT_CHANNEL_STATE struct, used by +dumb_it_sr_get_channel_state(). These fields hold the 'decimal' parts of the +pan and filter cut-off. See dumb.txt for details. + +Mxx (set channel volume) now correctly only modifies the last note played on +the channel, not any previous notes that have been forced into the background +by New Note Actions, and filter effect processing is now closer to what +Impulse Tracker does. + +The XM loader was slightly flawed and could crash on files containing samples +with out-of-range loop points. One such file was given to me. This has been +fixed. + +Finally, the legal stuff. Julien Cugniere has been added to the list of +copyright owners. He deserves it, for all the work he did on the XM support! +And the licence has been changed. You are no longer required to include a +link to DUMB in a project that uses DUMB; the reasons for this relaxation are +explained in licence.txt. However, the request is still there ... + +As usual, enjoy! + + +********************************************** +*** DUMB v0.9.1, released 19 December 2002 *** +********************************************** + +Hi again! Lots to say this time, so I shall cut right to the chase. + +DUMB now supports Impulse Tracker's low-pass resonant filters! Huge thanks go +to Jeffrey Lim, author of Impulse Tracker, for giving me what information he +still had regarding the algorithm; to cut a long story short, modifying +ModPlug Tracker's source code (which is in the Public Domain) led to an +algorithm whose output matched Impulse Tracker's perfectly. + +Please note that ModPlug Tracker's filters as they stand do not match Impulse +Tracker's, and I have no interest in supporting ModPlug Tracker's variant +(especially not the integer rounding problems). Please see docs/modplug.txt, +new in this release, for details. + +Thanks also go to Fatso Huuskonen for motivating me to add filter support, +and providing me with several great IT files to test it with! + +The other important feature added for this release is click removal. Up until +now, DUMB has generated clicks when cutting notes, starting samples in the +middle, and so on. This version of DUMB will remove any such clicks. Note +that DUMB does not use volume ramps to accomplish this; the algorithm will +not take the bite out of the music! + +In other news, DUMB now supports sample vibrato for IT files, and instrument +vibrato for XM files. A slight bug in New Note Action handling for IT files +has been fixed; Note Fade will not break the sustain loops of the sample and +envelope, as it did before. Tremor handling (Ixy) had a strange bug in it, +which has been fixed. + +Support for XM files has been greatly enhanced. The XM envelope handling new +in the last release contained a huge bug, resulting in notes seeming not to +stop when they should; this has been fixed. Some XM files crashed DUMB, while +others failed to load; these problems have been solved. Effect memories now +work properly for XM and MOD files, to the best of my knowledge. Some other +differences between IT and XM have been accounted for, most notably the +Retrigger Note effects, Rxy and E9x. + +DUMB's sound quality and accuracy are not the only areas that have been +enhanced. The API has been expanded, at last. You can now detect when a +module loops, or make it play through just once. You can ask DUMB to inform +you every time it generates some samples; this is useful for visualisation. +For IT files, you can intercept the MIDI messages generated by Zxx macros, +enabling you to synchronise your game with the music to some extent. (There +is no such method for XM, S3M or MOD files yet; sorry. Also note that the +function will be called before you actually hear the sound; I cannot improve +this until DUMB has its own sound drivers, which won't be for a while.) You +can query the current order and row. Finally, operations like changing the +speed and tempo are now possible, and you can query the playback state on +each channel. + +Some parts of DUMB's API have been deprecated. Simple programs that use +Allegro will be unaffected, but if you get some compiler warnings or errors, +please review docs/deprec.txt. This file explains why those parts of the API +were deprecated, and tells you how to adapt your code; the changes you need +to make are straightforward. Sorry for the inconvenience. + +For various reasons, I have made DUMB's makefiles use different compiler +flags depending on your GCC version (unless you are using MSVC). There is no +elegant way of getting the makefiles to detect when GCC is upgraded. If you +upgrade GCC, you should execute 'make clean' in order to make DUMB detect the +GCC version again. Otherwise you may get some annoying error messages. (It is +wise to do this in any case, so that all the object files are built with the +same GCC version.) + +DUMB's example players have been unified into a single player called +'dumbplay'. The player has been enhanced to display messages when the music +loops, and when XM and MOD files freeze (effect F00; more information on this +in docs/howto.txt). + +Finally, as noted on DUMB's website, the release notes from the last release +were inaccurate. It has been verified that DUMBOGG v0.5 does still work with +that release, and still works with this release. The esoteric DUMBOGG v0.6 +has not been created yet, since DUMBOGG v0.5 still works. + +Please scroll down and read through the indented paragraphs in the notes for +the last release; they are relevant for this release too. + +That's all folks! Until next time. + + +******************************************* +*** DUMB v0.9, released 16 October 2002 *** +******************************************* + +MOD support is here! DUMB now supports all four of the common module formats. +As usual, there have also been some improvements to the way modules are +played back. Most notably, handling of tone portamento in IT files has been +improved a lot, and XM envelopes are now processed correctly. + +The other major change is that DUMB now does a dummy run through each module +on loading. It stores the playback state at thirty-second intervals. It stops +when the module first loops, and then stores the playback time. This results +in a slightly longer load time and a greater memory overhead, but seeking is +faster (to any point before the module first loops) and the length is +calculated! duh_get_length() will return this and is now documented in +docs/howto.txt and docs/dumb.txt. + +DUMB's build process has been changed to use 'mingw' wherever it used +'mingw32' before; some directories have been renamed, and the 'fix' command +you had to run for MinGW has been changed from 'fix mingw32' to 'fix mingw'. + +Last time, I directed you to scroll down and read the notes from a past +release, but ignore this point, and that point applies to something else, and +so on. Did anyone do so? Well, if you're reading this at all, you probably +did. Nevertheless, this time I shall be much less confusing and restate any +relevant information. So the least you can do is read it! + +- If your program ever aborts with exit code 37 while loading an IT file, + PLEASE LET ME KNOW! The IT file in question has a stereo compressed sample + in it, and the format is unspecified for this case (Impulse Tracker itself + doesn't use stereo samples at all). I will need the IT file in question, + and any information you can give me about how the IT file was created (e.g. + what program). (If you don't get to see an exit code, let me know anyway.) + +- If your program ever outputs a line resembling "Inst 01 Env: 0,64 8,32 + 15,48" to stderr while loading an IT file, PLEASE LET ME KNOW! You have an + old IT file (saved by an Impulse Tracker version older than 2.00), and + support for such files is STILL untested. + +- Not all parts of DUMB's API are documented yet. You will find some + functions in dumb.h which are not listed in docs/dumb.txt; the reason is + that these functions still need work and will probably change. If you + really, really want to use them, talk to me first (IRC EFnet #dumb is a + good place for this; see readme.txt for details on using IRC). I intend to + finalise and document the whole of DUMB's API for Version 1.0. + +There have been some changes to the naming conventions in DUMB's undocumented +API. DUMBOGG v0.5 will not work with this and subsequent releases of DUMB; +please upgrade to DUMBOGG v0.6. These changes should not break anything in +your own code, since you didn't use those parts of the API, did you ;) + +There is still a great deal of work to be done before DUMB's API can be +finalised, and thus it will be a while before DUMB v1.0 comes out. It should +be worth the wait. In the meantime, there will be 0.9.x releases with +additional functionality, improved playback, and possibly support for some +extra file formats. + +Finally I should like to offer an apology; there is a strong possibility that +some of DUMB's official API will change in the near future. There will not be +any drastic changes, and the corresponding changes to your source code will +be simple enough. If I didn't make these changes, DUMB's API would start to +become limited, or messy, or both, so it's for the better. I apologise in +advance for this. + +Now scroll down and read the notes for the first r... oh wait, we already did +that. I guess that's it then. You can stop reading now. + +Right after you've read this. + +And this. + +Off you go. + +Bye. + + +******************************************** +*** DUMB v0.8.1, released 11 August 2002 *** +******************************************** + +This is a minor release that fixes a few bugs. One of these bugs, however, +was pretty serious. dumb_register_dat_xm() was never coded! It was prototyped +in aldumb.h, so code would compile, but there would be an unresolved symbol +at the linking stage. This has been fixed. + +Platforms other than Unix did not have a working 'make veryclean' target; +this has been fixed. In addition, the makefiles now use 'xcopy' instead of +'copy', since on some systems GNU Make seems to have trouble calling commands +built in to the shell. + +Contrary to the errata that was on the DUMB website, the makeall.sh and +makecore.sh scripts actually DID install in /usr. This has now been +corrected, and regardless of whether you use these scripts or call make +directly, the files will now be installed to /usr/local by default. + +The XM loader used to treat stereo samples as mono samples with the data for +the right channel positioned after the data for the left channel. This +generally resulted in an unwanted echo effect. This has been fixed. + +When playing XM files, specifying an invalid instrument would cause an old +note on that channel to come back (roughly speaking). Fast Tracker 2 does not +exhibit this behaviour. This has been fixed. + +The GCC makefiles used -mpentium, which is deprecated in gcc 3.x. This was +generating warnings, and has now been fixed. + +In XM files, the length of a sample is stored in bytes. DUMB was assuming +that the length of a 16-bit sample would be even. I had two XM files where +this was not the case, and DUMB was unable to load them. This has been fixed. + +In order to accommodate the extra part of the version number, +DUMB_REVISION_VERSION has been added. DUMB_VERSION has also been added in +order to facilitate checking if the version of DUMB installed is sufficient. +See docs/dumb.txt for details. + +As a last-minute fix, the XM "Break to row" effect is now loaded properly. It +was necessary to convert from binary-coded decimal to hexadecimal (those who +have experience with Fast Tracker 2 will know what I mean). In short, this +means the effect will now work properly when breaking to row 10 or greater. + +DUMB v0.8 had faulty release date constants; DUMB_MONTH and DUMB_DAY were +swapped! For this reason, DUMB_DATE should not be compared against any date +in 2002. This note has been added to docs/dumb.txt and also to dumb.h. + +Please scroll to the end and read the release notes for the first version, +DUMB v0.7. Most of them apply equally to this release. However, the +non-portable code was rewritten for DUMB v0.8, so that point does not apply. +The point about length not being calculated also applies to XM files. + +Enjoy :) + + +**************************************** +*** DUMB v0.8, released 14 June 2002 *** +**************************************** + +Welcome to the second release of DUMB! + +In addition to these notes, please read below the release notes for the +previous version, DUMB v0.7. Most of them apply equally to this release. +However, the non-portable code has been rewritten; DUMB should now port to +big-endian platforms. + +The main improvement in this release of DUMB is the support for XM files. +Enormous thanks go to Julien Cugniere for working on this while I had to +revise for my exams! + +There was a mistake in the makefiles in the last release. The debugging +Allegro interface library was mistakenly named libaldmbd.a instead of +libaldmd.a, meaning you had to compile with -laldmbd, contrary to what the +docs said. Apologies to everyone who lost sleep trying to work out what was +wrong! The reason for using libaldmd.a is to maintain compatibility with +plain DOS, where filenames are limited to eight characters (plus a three- +letter extension). The makefiles have now been changed to match the +information in the docs, so you may have to alter your project files +accordingly. + +The example programs were faulty, and crashed on Windows if they were unable +to load the file. It was also difficult to work out how to exit them (you had +to click the taskbar button that didn't have a window, then press a key). +They have been improved in both these respects. + +I have now added a docs/faq.txt file (Frequently Asked Questions), which is +based on problems and misconceptions people have had with the first release. +Please refer to it before contacting me with problems. + +Thanks to networm for touching up the Unix makefile and writing the +instructions on using it. + +Incidentally, today (Friday 14 June) is the Robinson College May Ball at +Cambridge Uni. God knows why it's called a May Ball if it's in June. I'm not +going myself (72 GBP, and I'd have to wear a suit, ugh), but with all the +noise outside I shall enjoy pumping up the speakers tonight! + + +**************************************** +*** DUMB v0.7, released 2 March 2002 *** +**************************************** + +This is the first release of DUMB, and parts of the library are not +crystallised. Don't let this put you off! Provided you don't try to use any +features that aren't documented in docs/dumb.txt, the library should be rock +solid and you should be able to upgrade more or less without problems. + +Here are some notes on this release: + +- There is some non-portable code in this release of DUMB. It is likely that + the library will fail to load IT files with compressed samples on + big-endian machines such as the Apple Macintosh. + +- If your program ever aborts with exit code 37 while loading an IT file, + PLEASE LET ME KNOW! The IT file in question has a stereo compressed sample + in it, and the format is unspecified for this case (Impulse Tracker itself + doesn't use stereo samples at all). I will need the IT file in question, + and any information you can give me about how the IT file was created (e.g. + what program). (If you don't get to see an exit code, let me know anyway.) + +- If your program ever outputs a line resembling "Inst 01 Env: 0,64 8,32 + 15,48" to stderr while loading an IT file, PLEASE LET ME KNOW! You have an + old IT file (saved by an Impulse Tracker version older than 2.00), and + support for such files is untested. + +- The length of IT and S3M files is not currently calculated. It is just set + to ten minutes. diff --git a/apps/codecs/dumb/src/allegro/alplay.c b/apps/codecs/dumb/src/allegro/alplay.c new file mode 100644 index 0000000000..983bde105b --- /dev/null +++ b/apps/codecs/dumb/src/allegro/alplay.c @@ -0,0 +1,270 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * alplay.c - Functions to play a DUH through / / \ \ + * an Allegro audio stream. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include + +#include "aldumb.h" + + + +#define ADP_PLAYING 1 + +struct AL_DUH_PLAYER +{ + int flags; + long bufsize; + int freq; + AUDIOSTREAM *stream; + DUH_SIGRENDERER *sigrenderer; /* If this is NULL, stream is invalid. */ + float volume; + int silentcount; +}; + + + +AL_DUH_PLAYER *al_start_duh(DUH *duh, int n_channels, long pos, float volume, long bufsize, int freq) +{ + AL_DUH_PLAYER *dp; + + /* This restriction is imposed by Allegro. */ + ASSERT(n_channels > 0); + ASSERT(n_channels <= 2); + + if (!duh) + return NULL; + + dp = malloc(sizeof(*dp)); + if (!dp) + return NULL; + + dp->flags = ADP_PLAYING; + dp->bufsize = bufsize; + dp->freq = freq; + + dp->stream = play_audio_stream(bufsize, 16, n_channels - 1, freq, 255, 128); + + if (!dp->stream) { + free(dp); + return NULL; + } + + voice_set_priority(dp->stream->voice, 255); + + dp->sigrenderer = duh_start_sigrenderer(duh, 0, n_channels, pos); + + if (!dp->sigrenderer) { + stop_audio_stream(dp->stream); + free(dp); + return NULL; + } + + dp->volume = volume; + dp->silentcount = 0; + + return dp; +} + + + +void al_stop_duh(AL_DUH_PLAYER *dp) +{ + if (dp) { + if (dp->sigrenderer) { + duh_end_sigrenderer(dp->sigrenderer); + stop_audio_stream(dp->stream); + } + free(dp); + } +} + + + +void al_pause_duh(AL_DUH_PLAYER *dp) +{ + if (dp && dp->sigrenderer && (dp->flags & ADP_PLAYING)) { + voice_stop(dp->stream->voice); + dp->flags &= ~ADP_PLAYING; + } +} + + + +void al_resume_duh(AL_DUH_PLAYER *dp) +{ + if (dp && dp->sigrenderer && !(dp->flags & ADP_PLAYING)) { + voice_start(dp->stream->voice); + dp->flags |= ADP_PLAYING; + } +} + + + +void al_duh_set_priority(AL_DUH_PLAYER *dp, int priority) +{ + if (dp && dp->sigrenderer) + voice_set_priority(dp->stream->voice, priority); +} + + + +void al_duh_set_volume(AL_DUH_PLAYER *dp, float volume) +{ + if (dp) + dp->volume = volume; +} + + + +int al_poll_duh(AL_DUH_PLAYER *dp) +{ + unsigned short *sptr; + long n; + long size; + int n_channels; + + if (!dp || !dp->sigrenderer) + return 1; + + if (!(dp->flags & ADP_PLAYING)) + return 0; + + sptr = get_audio_stream_buffer(dp->stream); + + if (!sptr) + return 0; + + n = duh_render(dp->sigrenderer, 16, 1, dp->volume, 65536.0 / dp->freq, dp->bufsize, sptr); + + if (n == 0) { + if (++dp->silentcount >= 2) { + duh_end_sigrenderer(dp->sigrenderer); + free_audio_stream_buffer(dp->stream); + stop_audio_stream(dp->stream); + dp->sigrenderer = NULL; + return 1; + } + } + + n_channels = duh_sigrenderer_get_n_channels(dp->sigrenderer); + n *= n_channels; + size = dp->bufsize * n_channels; + for (; n < size; n++) + sptr[n] = 0x8000; + + free_audio_stream_buffer(dp->stream); + + return 0; +} + + + +long al_duh_get_position(AL_DUH_PLAYER *dp) +{ + return dp ? duh_sigrenderer_get_position(dp->sigrenderer) : -1; +} + + + +AL_DUH_PLAYER *al_duh_encapsulate_sigrenderer(DUH_SIGRENDERER *sigrenderer, float volume, long bufsize, int freq) +{ + AL_DUH_PLAYER *dp; + int n_channels; + + if (!sigrenderer) + return NULL; + + dp = malloc(sizeof(*dp)); + if (!dp) + return NULL; + + n_channels = duh_sigrenderer_get_n_channels(sigrenderer); + + /* This restriction is imposed by Allegro. */ + ASSERT(n_channels > 0); + ASSERT(n_channels <= 2); + + dp->flags = ADP_PLAYING; + dp->bufsize = bufsize; + dp->freq = freq; + + dp->stream = play_audio_stream(bufsize, 16, n_channels - 1, freq, 255, 128); + + if (!dp->stream) { + free(dp); + return NULL; + } + + voice_set_priority(dp->stream->voice, 255); + + dp->sigrenderer = sigrenderer; + + dp->volume = volume; + dp->silentcount = 0; + + return dp; +} + + + +DUH_SIGRENDERER *al_duh_get_sigrenderer(AL_DUH_PLAYER *dp) +{ + return dp ? dp->sigrenderer : NULL; +} + + + +/* IMPORTANT: This function will return NULL if the music has ended. */ +// Should this be changed? User might want to hack the underlying SIGRENDERER +// and resurrect it (e.g. change pattern number), before it gets destroyed... +DUH_SIGRENDERER *al_duh_decompose_to_sigrenderer(AL_DUH_PLAYER *dp) +{ + if (dp) { + DUH_SIGRENDERER *sigrenderer = dp->sigrenderer; + if (sigrenderer) stop_audio_stream(dp->stream); + free(dp); + return sigrenderer; + } + return NULL; +} + + + +/* DEPRECATED */ +AL_DUH_PLAYER *al_duh_encapsulate_renderer(DUH_SIGRENDERER *dr, float volume, long bufsize, int freq) +{ + return al_duh_encapsulate_sigrenderer(dr, volume, bufsize, freq); +} + + + +/* DEPRECATED */ +DUH_SIGRENDERER *al_duh_get_renderer(AL_DUH_PLAYER *dp) +{ + return al_duh_get_sigrenderer(dp); +} + + + +/* DEPRECATED */ +DUH_SIGRENDERER *al_duh_decompose_to_renderer(AL_DUH_PLAYER *dp) +{ + return al_duh_decompose_to_sigrenderer(dp); +} diff --git a/apps/codecs/dumb/src/allegro/datduh.c b/apps/codecs/dumb/src/allegro/datduh.c new file mode 100644 index 0000000000..672e3c820c --- /dev/null +++ b/apps/codecs/dumb/src/allegro/datduh.c @@ -0,0 +1,60 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * datduh.c - Integration with Allegro's / / \ \ + * datafiles. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "aldumb.h" +#include "internal/aldumb.h" + + + +static void *dat_read_duh(PACKFILE *f, long size) +{ + DUMBFILE *df; + DUH *duh; + + (void)size; + + df = dumbfile_open_packfile(f); + + if (!df) + return NULL; + + duh = read_duh(df); + + dumbfile_close(df); + + return duh; +} + + + +/* dumb_register_dat_duh(): tells Allegro about the DUH datafile object. If + * you intend to load a datafile containing a DUH object, you must call this + * function first. It is recommended you pass DAT_DUH, but you may have a + * reason to use a different type (apart from pride, that doesn't count). + */ +void dumb_register_dat_duh(long type) +{ + register_datafile_object( + type, + &dat_read_duh, + &_dat_unload_duh + ); +} diff --git a/apps/codecs/dumb/src/allegro/datit.c b/apps/codecs/dumb/src/allegro/datit.c new file mode 100644 index 0000000000..8f58f14234 --- /dev/null +++ b/apps/codecs/dumb/src/allegro/datit.c @@ -0,0 +1,62 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * datit.c - Integration of IT files with / / \ \ + * Allegro's datafiles. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "aldumb.h" +#include "internal/aldumb.h" + + + +static void *dat_read_it(PACKFILE *f, long size) +{ + DUMBFILE *df; + DUH *duh; + + (void)size; + + df = dumbfile_open_packfile(f); + + if (!df) + return NULL; + + duh = dumb_read_it(df); + + dumbfile_close(df); + + return duh; +} + + + +/* dumb_register_dat_it(): tells Allegro about the IT datafile object. If you + * intend to load a datafile containing an IT object, you must call this + * function first. It is recommended you pass DUMB_DAT_IT, but you may have a + * reason to use a different type (perhaps you already have a datafile with + * IT files in and they use a different type). + */ +void dumb_register_dat_it(long type) +{ + register_datafile_object( + type, + &dat_read_it, + &_dat_unload_duh + ); +} + diff --git a/apps/codecs/dumb/src/allegro/datmod.c b/apps/codecs/dumb/src/allegro/datmod.c new file mode 100644 index 0000000000..850b17b444 --- /dev/null +++ b/apps/codecs/dumb/src/allegro/datmod.c @@ -0,0 +1,61 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * datmod.c - Integration of MOD files with / / \ \ + * Allegro's datafiles. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "aldumb.h" +#include "internal/aldumb.h" + + + +static void *dat_read_mod(PACKFILE *f, long size) +{ + DUMBFILE *df; + DUH *duh; + + (void)size; + + df = dumbfile_open_packfile(f); + + if (!df) + return NULL; + + duh = dumb_read_mod(df); + + dumbfile_close(df); + + return duh; +} + + + +/* dumb_register_dat_mod(): tells Allegro about the MOD datafile object. If + * you intend to load a datafile containing a MOD object, you must call this + * function first. It is recommended you pass DUMB_DAT_MOD, but you may have + * a reason to use a different type (perhaps you already have a datafile with + * MOD files in and they use a different type). + */ +void dumb_register_dat_mod(long type) +{ + register_datafile_object( + type, + &dat_read_mod, + &_dat_unload_duh + ); +} diff --git a/apps/codecs/dumb/src/allegro/dats3m.c b/apps/codecs/dumb/src/allegro/dats3m.c new file mode 100644 index 0000000000..a0fc74420e --- /dev/null +++ b/apps/codecs/dumb/src/allegro/dats3m.c @@ -0,0 +1,61 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * dats3m.c - Integration of S3M files with / / \ \ + * Allegro's datafiles. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "aldumb.h" +#include "internal/aldumb.h" + + + +static void *dat_read_s3m(PACKFILE *f, long size) +{ + DUMBFILE *df; + DUH *duh; + + (void)size; + + df = dumbfile_open_packfile(f); + + if (!df) + return NULL; + + duh = dumb_read_s3m(df); + + dumbfile_close(df); + + return duh; +} + + + +/* dumb_register_dat_s3m(): tells Allegro about the S3M datafile object. If + * you intend to load a datafile containing an S3M object, you must call this + * function first. It is recommended you pass DUMB_DAT_S3M, but you may have + * a reason to use a different type (perhaps you already have a datafile with + * S3M files in and they use a different type). + */ +void dumb_register_dat_s3m(long type) +{ + register_datafile_object( + type, + &dat_read_s3m, + &_dat_unload_duh + ); +} diff --git a/apps/codecs/dumb/src/allegro/datunld.c b/apps/codecs/dumb/src/allegro/datunld.c new file mode 100644 index 0000000000..71906445e0 --- /dev/null +++ b/apps/codecs/dumb/src/allegro/datunld.c @@ -0,0 +1,31 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * datunld.c - Unload function for integration / / \ \ + * with Allegro's datafiles. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "aldumb.h" +#include "internal/aldumb.h" + + + +void _dat_unload_duh(void *duh) +{ + unload_duh(duh); +} + diff --git a/apps/codecs/dumb/src/allegro/datxm.c b/apps/codecs/dumb/src/allegro/datxm.c new file mode 100644 index 0000000000..6cb98d87c1 --- /dev/null +++ b/apps/codecs/dumb/src/allegro/datxm.c @@ -0,0 +1,62 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * datxm.c - Integration of XM files with / / \ \ + * Allegro's datafiles. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "aldumb.h" +#include "internal/aldumb.h" + + + +static void *dat_read_xm(PACKFILE *f, long size) +{ + DUMBFILE *df; + DUH *duh; + + (void)size; + + df = dumbfile_open_packfile(f); + + if (!df) + return NULL; + + duh = dumb_read_xm(df); + + dumbfile_close(df); + + return duh; +} + + + +/* dumb_register_dat_xm(): tells Allegro about the XM datafile object. If you + * intend to load a datafile containing an XM object, you must call this + * function first. It is recommended you pass DUMB_DAT_XM, but you may have a + * reason to use a different type (perhaps you already have a datafile with + * XM files in and they use a different type). + */ +void dumb_register_dat_xm(long type) +{ + register_datafile_object( + type, + &dat_read_xm, + &_dat_unload_duh + ); +} + diff --git a/apps/codecs/dumb/src/allegro/packfile.c b/apps/codecs/dumb/src/allegro/packfile.c new file mode 100644 index 0000000000..525baebd4e --- /dev/null +++ b/apps/codecs/dumb/src/allegro/packfile.c @@ -0,0 +1,98 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * packfile.c - Packfile input module. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * Note that this does not use file compression; | \ / / + * for that you must open the file yourself and | ' / + * then use dumbfile_open_packfile(). \__/ + */ + +#include + +#include "aldumb.h" + + + +static void *dumb_packfile_open(const char *filename) +{ + return pack_fopen(filename, F_READ); +} + + + +static int dumb_packfile_skip(void *f, long n) +{ + return pack_fseek(f, n); +} + + + +static int dumb_packfile_getc(void *f) +{ + return pack_getc(f); +} + + + +static long dumb_packfile_getnc(char *ptr, long n, void *f) +{ + return pack_fread(ptr, n, f); +} + + + +static void dumb_packfile_close(void *f) +{ + pack_fclose(f); +} + + + +static DUMBFILE_SYSTEM packfile_dfs = { + &dumb_packfile_open, + &dumb_packfile_skip, + &dumb_packfile_getc, + &dumb_packfile_getnc, + &dumb_packfile_close +}; + + + +void dumb_register_packfiles(void) +{ + register_dumbfile_system(&packfile_dfs); +} + + + +static DUMBFILE_SYSTEM packfile_dfs_leave_open = { + NULL, + &dumb_packfile_skip, + &dumb_packfile_getc, + &dumb_packfile_getnc, + NULL +}; + + + +DUMBFILE *dumbfile_open_packfile(PACKFILE *p) +{ + return dumbfile_open_ex(p, &packfile_dfs_leave_open); +} + + + +DUMBFILE *dumbfile_from_packfile(PACKFILE *p) +{ + return p ? dumbfile_open_ex(p, &packfile_dfs) : NULL; +} diff --git a/apps/codecs/dumb/src/core/atexit.c b/apps/codecs/dumb/src/core/atexit.c new file mode 100644 index 0000000000..16c6abdb2c --- /dev/null +++ b/apps/codecs/dumb/src/core/atexit.c @@ -0,0 +1,71 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * atexit.c - Library Clean-up Management. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +typedef struct DUMB_ATEXIT_PROC +{ + struct DUMB_ATEXIT_PROC *next; + void (*proc)(void); +} +DUMB_ATEXIT_PROC; + + + +static DUMB_ATEXIT_PROC *dumb_atexit_proc = NULL; + + + +int dumb_atexit(void (*proc)(void)) +{ + DUMB_ATEXIT_PROC *dap = dumb_atexit_proc; + + while (dap) { + if (dap->proc == proc) return 0; + dap = dap->next; + } + + dap = malloc(sizeof(*dap)); + + if (!dap) + return -1; + + dap->next = dumb_atexit_proc; + dap->proc = proc; + dumb_atexit_proc = dap; + + return 0; +} + + + +void dumb_exit(void) +{ + while (dumb_atexit_proc) { + DUMB_ATEXIT_PROC *next = dumb_atexit_proc->next; + (*dumb_atexit_proc->proc)(); + free(dumb_atexit_proc); + dumb_atexit_proc = next; + } +} diff --git a/apps/codecs/dumb/src/core/duhlen.c b/apps/codecs/dumb/src/core/duhlen.c new file mode 100644 index 0000000000..4500d0a50f --- /dev/null +++ b/apps/codecs/dumb/src/core/duhlen.c @@ -0,0 +1,34 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * duhlen.c - Function to return the length of / / \ \ + * a DUH. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * Note that the length of a DUH is a constant | ' / + * stored in the DUH struct and in the DUH disk \__/ + * format. It will be calculated on loading for + * other formats in which the length is not explicitly stored. Also note that + * it does not necessarily correspond to the length of time for which the DUH + * will generate samples. Rather it represents a suitable point for a player + * such as Winamp to stop, and in any good DUH it will allow for any final + * flourish to fade out and be appreciated. + */ + +#include "dumb.h" +#include "internal/dumb.h" + + + +long duh_get_length(DUH *duh) +{ + return duh ? duh->length : 0; +} diff --git a/apps/codecs/dumb/src/core/dumbfile.c b/apps/codecs/dumb/src/core/dumbfile.c new file mode 100644 index 0000000000..71108c0c3b --- /dev/null +++ b/apps/codecs/dumb/src/core/dumbfile.c @@ -0,0 +1,401 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * dumbfile.c - Hookable, strictly sequential / / \ \ + * file input functions. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" + + + +static DUMBFILE_SYSTEM *the_dfs = NULL; + + + +void register_dumbfile_system(DUMBFILE_SYSTEM *dfs) +{ + ASSERT(dfs); + ASSERT(dfs->open); + ASSERT(dfs->getc); + ASSERT(dfs->close); + the_dfs = dfs; +} + + + +struct DUMBFILE +{ + DUMBFILE_SYSTEM *dfs; + void *file; + long pos; +}; + + + +DUMBFILE *dumbfile_open(const char *filename) +{ + DUMBFILE *f; + + ASSERT(the_dfs); + + f = malloc(sizeof(*f)); + + if (!f) + return NULL; + + f->dfs = the_dfs; + + f->file = (*the_dfs->open)(filename); + + if (!f->file) { + free(f); + return NULL; + } + + f->pos = 0; + + return f; +} + + + +DUMBFILE *dumbfile_open_ex(void *file, DUMBFILE_SYSTEM *dfs) +{ + DUMBFILE *f; + + ASSERT(dfs); + ASSERT(dfs->getc); + ASSERT(file); + + f = malloc(sizeof(*f)); + + if (!f) { + if (dfs->close) + (*dfs->close)(file); + return NULL; + } + + f->dfs = dfs; + f->file = file; + + f->pos = 0; + + return f; +} + + + +long dumbfile_pos(DUMBFILE *f) +{ + ASSERT(f); + + return f->pos; +} + + + +int dumbfile_skip(DUMBFILE *f, long n) +{ + int rv; + + ASSERT(f); + ASSERT(n >= 0); + + if (f->pos < 0) + return -1; + + f->pos += n; + + if (f->dfs->skip) { + rv = (*f->dfs->skip)(f->file, n); + if (rv) { + f->pos = -1; + return rv; + } + } else { + while (n) { + rv = (*f->dfs->getc)(f->file); + if (rv < 0) { + f->pos = -1; + return rv; + } + n--; + } + } + + return 0; +} + + + +int dumbfile_getc(DUMBFILE *f) +{ + int rv; + + ASSERT(f); + + if (f->pos < 0) + return -1; + + rv = (*f->dfs->getc)(f->file); + + if (rv < 0) { + f->pos = -1; + return rv; + } + + f->pos++; + + return rv; +} + + + +int dumbfile_igetw(DUMBFILE *f) +{ + int l, h; + + ASSERT(f); + + if (f->pos < 0) + return -1; + + l = (*f->dfs->getc)(f->file); + if (l < 0) { + f->pos = -1; + return l; + } + + h = (*f->dfs->getc)(f->file); + if (h < 0) { + f->pos = -1; + return h; + } + + f->pos += 2; + + return l | (h << 8); +} + + + +int dumbfile_mgetw(DUMBFILE *f) +{ + int l, h; + + ASSERT(f); + + if (f->pos < 0) + return -1; + + h = (*f->dfs->getc)(f->file); + if (h < 0) { + f->pos = -1; + return h; + } + + l = (*f->dfs->getc)(f->file); + if (l < 0) { + f->pos = -1; + return l; + } + + f->pos += 2; + + return l | (h << 8); +} + + + +long dumbfile_igetl(DUMBFILE *f) +{ + unsigned long rv, b; + + ASSERT(f); + + if (f->pos < 0) + return -1; + + rv = (*f->dfs->getc)(f->file); + if ((signed long)rv < 0) { + f->pos = -1; + return rv; + } + + b = (*f->dfs->getc)(f->file); + if ((signed long)b < 0) { + f->pos = -1; + return b; + } + rv |= b << 8; + + b = (*f->dfs->getc)(f->file); + if ((signed long)b < 0) { + f->pos = -1; + return b; + } + rv |= b << 16; + + b = (*f->dfs->getc)(f->file); + if ((signed long)b < 0) { + f->pos = -1; + return b; + } + rv |= b << 24; + + f->pos += 4; + + return rv; +} + + + +long dumbfile_mgetl(DUMBFILE *f) +{ + unsigned long rv, b; + + ASSERT(f); + + if (f->pos < 0) + return -1; + + rv = (*f->dfs->getc)(f->file); + if ((signed long)rv < 0) { + f->pos = -1; + return rv; + } + rv <<= 24; + + b = (*f->dfs->getc)(f->file); + if ((signed long)b < 0) { + f->pos = -1; + return b; + } + rv |= b << 16; + + b = (*f->dfs->getc)(f->file); + if ((signed long)b < 0) { + f->pos = -1; + return b; + } + rv |= b << 8; + + b = (*f->dfs->getc)(f->file); + if ((signed long)b < 0) { + f->pos = -1; + return b; + } + rv |= b; + + f->pos += 4; + + return rv; +} + + + +unsigned long dumbfile_cgetul(DUMBFILE *f) +{ + unsigned long rv = 0; + int v; + + do { + v = dumbfile_getc(f); + + if (v < 0) + return v; + + rv <<= 7; + rv |= v & 0x7F; + } while (v & 0x80); + + return rv; +} + + + +signed long dumbfile_cgetsl(DUMBFILE *f) +{ + unsigned long rv = dumbfile_cgetul(f); + + if (f->pos < 0) + return rv; + + return (rv >> 1) | (rv << 31); +} + + + +long dumbfile_getnc(char *ptr, long n, DUMBFILE *f) +{ + long rv; + + ASSERT(f); + ASSERT(n >= 0); + + if (f->pos < 0) + return -1; + + if (f->dfs->getnc) { + rv = (*f->dfs->getnc)(ptr, n, f->file); + if (rv < n) { + f->pos = -1; + return MAX(rv, 0); + } + } else { + for (rv = 0; rv < n; rv++) { + int c = (*f->dfs->getc)(f->file); + if (c < 0) { + f->pos = -1; + return rv; + } + *ptr++ = c; + } + } + + f->pos += rv; + + return rv; +} + + + +int dumbfile_error(DUMBFILE *f) +{ + ASSERT(f); + + return f->pos < 0; +} + + + +int dumbfile_close(DUMBFILE *f) +{ + int rv; + + ASSERT(f); + + rv = f->pos < 0; + + if (f->dfs->close) + (*f->dfs->close)(f->file); + + free(f); + + return rv; +} diff --git a/apps/codecs/dumb/src/core/loadduh.c b/apps/codecs/dumb/src/core/loadduh.c new file mode 100644 index 0000000000..7dfe5cc100 --- /dev/null +++ b/apps/codecs/dumb/src/core/loadduh.c @@ -0,0 +1,42 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * loadduh.c - Code to read a DUH from a file, / / \ \ + * opening and closing the file for | < / \_ + * you. | \/ /\ / + * \_ / > / + * By entheh. | \ / / + * | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/dumb.h" + + + +/* load_duh(): loads a .duh file, returning a pointer to a DUH struct. + * When you have finished with it, you must pass the pointer to unload_duh() + * so that the memory can be freed. + */ +DUH *load_duh(const char *filename) +{ + DUH *duh; + DUMBFILE *f = dumbfile_open(filename); + + if (!f) + return NULL; + + duh = read_duh(f); + + dumbfile_close(f); + + return duh; +} diff --git a/apps/codecs/dumb/src/core/makeduh.c b/apps/codecs/dumb/src/core/makeduh.c new file mode 100644 index 0000000000..1e422fb502 --- /dev/null +++ b/apps/codecs/dumb/src/core/makeduh.c @@ -0,0 +1,92 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * makeduh.c - Function to construct a DUH from / / \ \ + * its components. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +static DUH_SIGNAL *make_signal(DUH_SIGTYPE_DESC *desc, sigdata_t *sigdata) +{ + DUH_SIGNAL *signal; + + ASSERT((desc->start_sigrenderer && desc->end_sigrenderer) || (!desc->start_sigrenderer && !desc->end_sigrenderer)); + ASSERT(desc->sigrenderer_get_samples && desc->sigrenderer_get_current_sample); + + signal = malloc(sizeof(*signal)); + + if (!signal) { + if (desc->unload_sigdata) + if (sigdata) + (*desc->unload_sigdata)(sigdata); + return NULL; + } + + signal->desc = desc; + signal->sigdata = sigdata; + + return signal; +} + + + +DUH *make_duh(long length, int n_signals, DUH_SIGTYPE_DESC *desc[], sigdata_t *sigdata[]) +{ + DUH *duh = malloc(sizeof(*duh)); + int i; + int fail; + + if (duh) { + duh->n_signals = n_signals; + + duh->signal = malloc(n_signals * sizeof(*duh->signal)); + + if (!duh->signal) { + free(duh); + duh = NULL; + } + } + + if (!duh) { + for (i = 0; i < n_signals; i++) + if (desc[i]->unload_sigdata) + if (sigdata[i]) + (*desc[i]->unload_sigdata)(sigdata[i]); + return NULL; + } + + fail = 0; + + for (i = 0; i < n_signals; i++) { + duh->signal[i] = make_signal(desc[i], sigdata[i]); + if (!duh->signal[i]) + fail = 1; + } + + if (fail) { + unload_duh(duh); + return NULL; + } + + duh->length = length; + + return duh; +} diff --git a/apps/codecs/dumb/src/core/rawsig.c b/apps/codecs/dumb/src/core/rawsig.c new file mode 100644 index 0000000000..926c990655 --- /dev/null +++ b/apps/codecs/dumb/src/core/rawsig.c @@ -0,0 +1,44 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * rawsig.c - Function to retrieve raw signal / / \ \ + * data from a DUH provided you know | < / \_ + * what type of signal it is. | \/ /\ / + * \_ / > / + * By entheh. | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +/* You have to specify the type of sigdata, proving you know what to do with + * the pointer. If you get it wrong, you can expect NULL back. + */ +sigdata_t *duh_get_raw_sigdata(DUH *duh, int sig, long type) +{ + DUH_SIGNAL *signal; + + if (!duh) return NULL; + + if ((unsigned int)sig >= (unsigned int)duh->n_signals) return NULL; + + signal = duh->signal[sig]; + + if (signal && signal->desc->type == type) + return signal->sigdata; + + return NULL; +} diff --git a/apps/codecs/dumb/src/core/readduh.c b/apps/codecs/dumb/src/core/readduh.c new file mode 100644 index 0000000000..514b04a077 --- /dev/null +++ b/apps/codecs/dumb/src/core/readduh.c @@ -0,0 +1,107 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * readduh.c - Code to read a DUH from an open / / \ \ + * file. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +static DUH_SIGNAL *read_signal(DUH *duh, DUMBFILE *f) +{ + DUH_SIGNAL *signal; + long type; + + signal = malloc(sizeof(*signal)); + + if (!signal) + return NULL; + + type = dumbfile_mgetl(f); + if (dumbfile_error(f)) { + free(signal); + return NULL; + } + + signal->desc = _dumb_get_sigtype_desc(type); + if (!signal->desc) { + free(signal); + return NULL; + } + + if (signal->desc->load_sigdata) { + signal->sigdata = (*signal->desc->load_sigdata)(duh, f); + if (!signal->sigdata) { + free(signal); + return NULL; + } + } else + signal->sigdata = NULL; + + return signal; +} + + + +/* read_duh(): reads a DUH from an already open DUMBFILE, and returns its + * pointer, or null on error. The file is not closed. + */ +DUH *read_duh(DUMBFILE *f) +{ + DUH *duh; + int i; + + if (dumbfile_mgetl(f) != DUH_SIGNATURE) + return NULL; + + duh = malloc(sizeof(*duh)); + if (!duh) + return NULL; + + duh->length = dumbfile_igetl(f); + if (dumbfile_error(f) || duh->length <= 0) { + free(duh); + return NULL; + } + + duh->n_signals = dumbfile_igetl(f); + if (dumbfile_error(f) || duh->n_signals <= 0) { + free(duh); + return NULL; + } + + duh->signal = malloc(sizeof(*duh->signal) * duh->n_signals); + if (!duh->signal) { + free(duh); + return NULL; + } + + for (i = 0; i < duh->n_signals; i++) + duh->signal[i] = NULL; + + for (i = 0; i < duh->n_signals; i++) { + if (!(duh->signal[i] = read_signal(duh, f))) { + unload_duh(duh); + return NULL; + } + } + + return duh; +} diff --git a/apps/codecs/dumb/src/core/register.c b/apps/codecs/dumb/src/core/register.c new file mode 100644 index 0000000000..9eed45f796 --- /dev/null +++ b/apps/codecs/dumb/src/core/register.c @@ -0,0 +1,104 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * register.c - Signal type registration. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +static DUH_SIGTYPE_DESC_LINK *sigtype_desc = NULL; +static DUH_SIGTYPE_DESC_LINK **sigtype_desc_tail = &sigtype_desc; + + + +/* destroy_sigtypes(): frees all memory allocated while registering signal + * types. This function is set up to be called by dumb_exit(). + */ +static void destroy_sigtypes(void) +{ + DUH_SIGTYPE_DESC_LINK *desc_link = sigtype_desc, *next; + sigtype_desc = NULL; + sigtype_desc_tail = &sigtype_desc; + + while (desc_link) { + next = desc_link->next; + free(desc_link); + desc_link = next; + } +} + + + +/* dumb_register_sigtype(): registers a new signal type with DUMB. The signal + * type is identified by a four-character string (e.g. "WAVE"), which you can + * encode using the the DUMB_ID() macro (e.g. DUMB_ID('W','A','V','E')). The + * signal's behaviour is defined by four functions, whose pointers you pass + * here. See the documentation for details. + * + * If a DUH tries to use a signal that has not been registered using this + * function, then the library will fail to load the DUH. + */ +void dumb_register_sigtype(DUH_SIGTYPE_DESC *desc) +{ + DUH_SIGTYPE_DESC_LINK *desc_link = sigtype_desc; + + ASSERT((desc->load_sigdata && desc->unload_sigdata) || (!desc->load_sigdata && !desc->unload_sigdata)); + ASSERT((desc->start_sigrenderer && desc->end_sigrenderer) || (!desc->start_sigrenderer && !desc->end_sigrenderer)); + ASSERT(desc->sigrenderer_get_samples && desc->sigrenderer_get_current_sample); + + if (desc_link) { + do { + if (desc_link->desc->type == desc->type) { + desc_link->desc = desc; + return; + } + desc_link = desc_link->next; + } while (desc_link); + } else + dumb_atexit(&destroy_sigtypes); + + desc_link = *sigtype_desc_tail = malloc(sizeof(DUH_SIGTYPE_DESC_LINK)); + + if (!desc_link) + return; + + desc_link->next = NULL; + sigtype_desc_tail = &desc_link->next; + + desc_link->desc = desc; +} + + + +/* _dumb_get_sigtype_desc(): searches the registered functions for a signal + * type matching the parameter. If such a sigtype is found, it returns a + * pointer to a sigtype descriptor containing the necessary functions to + * manage the signal. If none is found, it returns NULL. + */ +DUH_SIGTYPE_DESC *_dumb_get_sigtype_desc(long type) +{ + DUH_SIGTYPE_DESC_LINK *desc_link = sigtype_desc; + + while (desc_link && desc_link->desc->type != type) + desc_link = desc_link->next; + + return desc_link->desc; +} diff --git a/apps/codecs/dumb/src/core/rendduh.c b/apps/codecs/dumb/src/core/rendduh.c new file mode 100644 index 0000000000..39db8ab8a3 --- /dev/null +++ b/apps/codecs/dumb/src/core/rendduh.c @@ -0,0 +1,202 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * rendduh.c - Functions for rendering a DUH into / / \ \ + * an end-user sample format. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +/* On the x86, we can use some tricks to speed stuff up */ +#if (defined _MSC_VER) || (defined __DJGPP__) || (defined __MINGW__) +// Can't we detect Linux and other x86 platforms here? :/ + +#define FAST_MID(var, min, max) { \ + var -= (min); \ + var &= (~var) >> (sizeof(var) * CHAR_BIT - 1); \ + var += (min); \ + var -= (max); \ + var &= var >> (sizeof(var) * CHAR_BIT - 1); \ + var += (max); \ +} + +#define CONVERT8(src, pos, signconv) { \ + signed int f = (src + 0x8000) >> 16; \ + FAST_MID(f, -128, 127); \ + ((char*)sptr)[pos] = (char)f ^ signconv; \ +} + +#define CONVERT16(src, pos, signconv) { \ + signed int f = (src + 0x80) >> 8; \ + FAST_MID(f, -32768, 32767); \ + ((short*)sptr)[pos] = (short)(f ^ signconv); \ +} + +#else + +#define CONVERT8(src, pos, signconv) \ +{ \ + signed int f = (src + 0x8000) >> 16; \ + f = MID(-128, f, 127); \ + ((char *)sptr)[pos] = (char)f ^ signconv; \ +} + + + +#define CONVERT16(src, pos, signconv) \ +{ \ + signed int f = (src + 0x80) >> 8; \ + f = MID(-32768, f, 32767); \ + ((short *)sptr)[pos] = (short)(f ^ signconv); \ +} + +#endif + + + +/* DEPRECATED */ +DUH_SIGRENDERER *duh_start_renderer(DUH *duh, int n_channels, long pos) +{ + return duh_start_sigrenderer(duh, 0, n_channels, pos); +} + + + +long duh_render( + DUH_SIGRENDERER *sigrenderer, + int bits, int unsign, + float volume, float delta, + long size, void *sptr +) +{ + long n; + + sample_t **sampptr; + + int n_channels; + + ASSERT(bits == 8 || bits == 16); + ASSERT(sptr); + + if (!sigrenderer) + return 0; + + n_channels = duh_sigrenderer_get_n_channels(sigrenderer); + + ASSERT(n_channels > 0); + /* This restriction will be removed when need be. At the moment, tightly + * optimised loops exist for exactly one or two channels. + */ + ASSERT(n_channels <= 2); + + sampptr = create_sample_buffer(n_channels, size); + + if (!sampptr) + return 0; + + dumb_silence(sampptr[0], n_channels * size); + + size = duh_sigrenderer_get_samples(sigrenderer, volume, delta, size, sampptr); + + if (bits == 16) { + int signconv = unsign ? 0x8000 : 0x0000; + + if (n_channels == 2) { + for (n = 0; n < size; n++) { + CONVERT16(sampptr[0][n], n << 1, signconv); + } + for (n = 0; n < size; n++) { + CONVERT16(sampptr[1][n], (n << 1) + 1, signconv); + } + } else { + for (n = 0; n < size; n++) { + CONVERT16(sampptr[0][n], n, signconv); + } + } + } else { + char signconv = unsign ? 0x80 : 0x00; + + if (n_channels == 2) { + for (n = 0; n < size; n++) { + CONVERT8(sampptr[0][n], n << 1, signconv); + } + for (n = 0; n < size; n++) { + CONVERT8(sampptr[1][n], (n << 1) + 1, signconv); + } + } else { + for (n = 0; n < size; n++) { + CONVERT8(sampptr[0][n], n, signconv); + } + } + } + + destroy_sample_buffer(sampptr); + + return size; +} + + + +/* DEPRECATED */ +int duh_renderer_get_n_channels(DUH_SIGRENDERER *dr) +{ + return duh_sigrenderer_get_n_channels(dr); +} + + + +/* DEPRECATED */ +long duh_renderer_get_position(DUH_SIGRENDERER *dr) +{ + return duh_sigrenderer_get_position(dr); +} + + + +/* DEPRECATED */ +void duh_end_renderer(DUH_SIGRENDERER *dr) +{ + duh_end_sigrenderer(dr); +} + + + +/* DEPRECATED */ +DUH_SIGRENDERER *duh_renderer_encapsulate_sigrenderer(DUH_SIGRENDERER *sigrenderer) +{ + return sigrenderer; +} + + + +/* DEPRECATED */ +DUH_SIGRENDERER *duh_renderer_get_sigrenderer(DUH_SIGRENDERER *dr) +{ + return dr; +} + + + +/* DEPRECATED */ +DUH_SIGRENDERER *duh_renderer_decompose_to_sigrenderer(DUH_SIGRENDERER *dr) +{ + return dr; +} diff --git a/apps/codecs/dumb/src/core/rendsig.c b/apps/codecs/dumb/src/core/rendsig.c new file mode 100644 index 0000000000..a36ceb41cf --- /dev/null +++ b/apps/codecs/dumb/src/core/rendsig.c @@ -0,0 +1,299 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * rendsig.c - Wrappers to render samples from / / \ \ + * the signals in a DUH. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +struct DUH_SIGRENDERER +{ + DUH_SIGTYPE_DESC *desc; + + sigrenderer_t *sigrenderer; + + int n_channels; + + long pos; + int subpos; + + DUH_SIGRENDERER_ANALYSER_CALLBACK callback; + void *callback_data; +}; + + + +DUH_SIGRENDERER *duh_start_sigrenderer(DUH *duh, int sig, int n_channels, long pos) +{ + DUH_SIGRENDERER *sigrenderer; + + DUH_SIGNAL *signal; + DUH_START_SIGRENDERER proc; + + if ((unsigned int)sig >= (unsigned int)duh->n_signals) + return NULL; + + signal = duh->signal[sig]; + if (!signal) + return NULL; + + sigrenderer = malloc(sizeof(*sigrenderer)); + if (!sigrenderer) + return NULL; + + sigrenderer->desc = signal->desc; + + proc = sigrenderer->desc->start_sigrenderer; + + if (proc) { + duh->signal[sig] = NULL; + sigrenderer->sigrenderer = (*proc)(duh, signal->sigdata, n_channels, pos); + duh->signal[sig] = signal; + + if (!sigrenderer->sigrenderer) { + free(sigrenderer); + return NULL; + } + } else + sigrenderer->sigrenderer = NULL; + + sigrenderer->n_channels = n_channels; + + sigrenderer->pos = pos; + sigrenderer->subpos = 0; + + sigrenderer->callback = NULL; + + return sigrenderer; +} + + + +#include +void duh_sigrenderer_set_callback( + DUH_SIGRENDERER *sigrenderer, + DUH_SIGRENDERER_CALLBACK callback, void *data +) +{ + (void)sigrenderer; + (void)callback; + (void)data; +/* FIXME + fprintf(stderr, + "Call to deprecated function duh_sigrenderer_set_callback(). The callback\n" + "was not installed. See dumb/docs/deprec.txt for how to fix this.\n");*/ +} + + + +void duh_sigrenderer_set_analyser_callback( + DUH_SIGRENDERER *sigrenderer, + DUH_SIGRENDERER_ANALYSER_CALLBACK callback, void *data +) +{ + if (sigrenderer) { + sigrenderer->callback = callback; + sigrenderer->callback_data = data; + } +} + + + +int duh_sigrenderer_get_n_channels(DUH_SIGRENDERER *sigrenderer) +{ + return sigrenderer ? sigrenderer->n_channels : 0; +} + + + +long duh_sigrenderer_get_position(DUH_SIGRENDERER *sigrenderer) +{ + return sigrenderer ? sigrenderer->pos : -1; +} + + + +void duh_sigrenderer_set_sigparam( + DUH_SIGRENDERER *sigrenderer, + unsigned char id, long value +) +{ + DUH_SIGRENDERER_SET_SIGPARAM proc; + + if (!sigrenderer) return; + + proc = sigrenderer->desc->sigrenderer_set_sigparam; + if (proc) + (*proc)(sigrenderer->sigrenderer, id, value); + else + TRACE("Parameter #%d = %ld for signal %c%c%c%c, which does not take parameters.\n", + (int)id, + value, + (int)(sigrenderer->desc->type >> 24), + (int)(sigrenderer->desc->type >> 16), + (int)(sigrenderer->desc->type >> 8), + (int)(sigrenderer->desc->type)); +} + + + +long duh_sigrenderer_get_samples( + DUH_SIGRENDERER *sigrenderer, + float volume, float delta, + long size, sample_t **samples +) +{ + long rendered; + LONG_LONG t; + + if (!sigrenderer) return 0; + + rendered = (*sigrenderer->desc->sigrenderer_get_samples) + (sigrenderer->sigrenderer, volume, delta, size, samples); + + if (rendered) { + if (sigrenderer->callback) + (*sigrenderer->callback)(sigrenderer->callback_data, + (const sample_t *const *)samples, sigrenderer->n_channels, rendered); + + t = sigrenderer->subpos + (LONG_LONG)(delta * 65536.0 + 0.5) * rendered; + + sigrenderer->pos += (long)(t >> 16); + sigrenderer->subpos = (int)t & 65535; + } + + return rendered; +} + + + +/* DEPRECATED */ +long duh_render_signal( + DUH_SIGRENDERER *sigrenderer, + float volume, float delta, + long size, sample_t **samples +) +{ + sample_t **s = create_sample_buffer(sigrenderer->n_channels, size); + long rendered; + long i; + int j; + if (!s) return 0; + rendered = duh_sigrenderer_get_samples(sigrenderer, volume, delta, size, s); + for (j = 0; j < sigrenderer->n_channels; j++) + for (i = 0; i < rendered; i++) + samples[j][i] += s[j][i] >> 8; + destroy_sample_buffer(s); + return rendered; +} + + + +void duh_sigrenderer_get_current_sample(DUH_SIGRENDERER *sigrenderer, float volume, sample_t *samples) +{ + if (sigrenderer) + (*sigrenderer->desc->sigrenderer_get_current_sample)(sigrenderer->sigrenderer, volume, samples); +} + + + +void duh_end_sigrenderer(DUH_SIGRENDERER *sigrenderer) +{ + if (sigrenderer) { + if (sigrenderer->desc->end_sigrenderer) + if (sigrenderer->sigrenderer) + (*sigrenderer->desc->end_sigrenderer)(sigrenderer->sigrenderer); + + free(sigrenderer); + } +} + + + +DUH_SIGRENDERER *duh_encapsulate_raw_sigrenderer(sigrenderer_t *vsigrenderer, DUH_SIGTYPE_DESC *desc, int n_channels, long pos) +{ + DUH_SIGRENDERER *sigrenderer; + + if (desc->start_sigrenderer && !vsigrenderer) return NULL; + + sigrenderer = malloc(sizeof(*sigrenderer)); + if (!sigrenderer) { + if (desc->end_sigrenderer) + if (vsigrenderer) + (*desc->end_sigrenderer)(vsigrenderer); + return NULL; + } + + sigrenderer->desc = desc; + sigrenderer->sigrenderer = vsigrenderer; + + sigrenderer->n_channels = n_channels; + + sigrenderer->pos = pos; + sigrenderer->subpos = 0; + + sigrenderer->callback = NULL; + + return sigrenderer; +} + + + +sigrenderer_t *duh_get_raw_sigrenderer(DUH_SIGRENDERER *sigrenderer, long type) +{ + if (sigrenderer && sigrenderer->desc->type == type) + return sigrenderer->sigrenderer; + + return NULL; +} + + + +#if 0 +// This function is disabled because we don't know whether we want to destroy +// the sigrenderer if the type doesn't match. We don't even know if we need +// the function at all. Who would want to keep an IT_SIGRENDERER (for +// instance) without keeping the DUH_SIGRENDERER? +sigrenderer_t *duh_decompose_to_raw_sigrenderer(DUH_SIGRENDERER *sigrenderer, long type) +{ + if (sigrenderer && sigrenderer->desc->type == type) { + + + + if (sigrenderer) { + if (sigrenderer->desc->end_sigrenderer) + if (sigrenderer->sigrenderer) + (*sigrenderer->desc->end_sigrenderer)(sigrenderer->sigrenderer); + + free(sigrenderer); + } + + + + + + + return sigrenderer->sigrenderer; + } + + return NULL; +} +#endif diff --git a/apps/codecs/dumb/src/core/unload.c b/apps/codecs/dumb/src/core/unload.c new file mode 100644 index 0000000000..3bf0285cd1 --- /dev/null +++ b/apps/codecs/dumb/src/core/unload.c @@ -0,0 +1,58 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * unload.c - Code to free a DUH from memory. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +static void destroy_signal(DUH_SIGNAL *signal) +{ + if (signal) { + if (signal->desc) + if (signal->desc->unload_sigdata) + if (signal->sigdata) + (*signal->desc->unload_sigdata)(signal->sigdata); + + free(signal); + } +} + + + +/* unload_duh(): destroys a DUH struct. You must call this for every DUH + * struct created, when you've finished with it. + */ +void unload_duh(DUH *duh) +{ + int i; + + if (duh) { + if (duh->signal) { + for (i = 0; i < duh->n_signals; i++) + destroy_signal(duh->signal[i]); + + free(duh->signal); + } + + free(duh); + } +} diff --git a/apps/codecs/dumb/src/helpers/clickrem.c b/apps/codecs/dumb/src/helpers/clickrem.c new file mode 100644 index 0000000000..ddc861d931 --- /dev/null +++ b/apps/codecs/dumb/src/helpers/clickrem.c @@ -0,0 +1,270 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * clickrem.c - Click removal helpers. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include +#include "dumb.h" + + + +typedef struct DUMB_CLICK DUMB_CLICK; + + +struct DUMB_CLICK_REMOVER +{ + DUMB_CLICK *click; + int n_clicks; + + int offset; +}; + + +struct DUMB_CLICK +{ + DUMB_CLICK *next; + long pos; + sample_t step; +}; + + + +DUMB_CLICK_REMOVER *dumb_create_click_remover(void) +{ + DUMB_CLICK_REMOVER *cr = malloc(sizeof(*cr)); + if (!cr) return NULL; + + cr->click = NULL; + cr->n_clicks = 0; + + cr->offset = 0; + + return cr; +} + + + +void dumb_record_click(DUMB_CLICK_REMOVER *cr, long pos, sample_t step) +{ + DUMB_CLICK *click; + + ASSERT(pos >= 0); + + if (!cr || !step) return; + + if (pos == 0) { + cr->offset -= step; + return; + } + + click = malloc(sizeof(*click)); + if (!click) return; + + click->pos = pos; + click->step = step; + + click->next = cr->click; + cr->click = click; + cr->n_clicks++; +} + + + +static DUMB_CLICK *dumb_click_mergesort(DUMB_CLICK *click, int n_clicks) +{ + int i; + DUMB_CLICK *c1, *c2, **cp; + + if (n_clicks <= 1) return click; + + /* Split the list into two */ + c1 = click; + cp = &c1; + for (i = 0; i < n_clicks; i += 2) cp = &(*cp)->next; + c2 = *cp; + *cp = NULL; + + /* Sort the sublists */ + c1 = dumb_click_mergesort(c1, (n_clicks + 1) >> 1); + c2 = dumb_click_mergesort(c2, n_clicks >> 1); + + /* Merge them */ + cp = &click; + while (c1 && c2) { + if (c1->pos > c2->pos) { + *cp = c2; + c2 = c2->next; + } else { + *cp = c1; + c1 = c1->next; + } + cp = &(*cp)->next; + } + if (c2) + *cp = c2; + else + *cp = c1; + + return click; +} + + + +void dumb_remove_clicks(DUMB_CLICK_REMOVER *cr, sample_t *samples, long length, float halflife) +{ + DUMB_CLICK *click; + long pos = 0; + int offset; + int factor; + + if (!cr) return; + + factor = (int)floor(pow(0.5, 1.0/halflife) * (1U << 31)); + + click = dumb_click_mergesort(cr->click, cr->n_clicks); + cr->click = NULL; + cr->n_clicks = 0; + + while (click) { + DUMB_CLICK *next = click->next; + ASSERT(click->pos <= length); + offset = cr->offset; + if (offset < 0) { + offset = -offset; + while (pos < click->pos) { + samples[pos++] -= offset; + offset = (int)((LONG_LONG)(offset << 1) * factor >> 32); + } + offset = -offset; + } else { + while (pos < click->pos) { + samples[pos++] += offset; + offset = (int)((LONG_LONG)(offset << 1) * factor >> 32); + } + } + cr->offset = offset - click->step; + free(click); + click = next; + } + + offset = cr->offset; + if (offset < 0) { + offset = -offset; + while (pos < length) { + samples[pos++] -= offset; + offset = (int)((LONG_LONG)(offset << 1) * factor >> 32); + } + offset = -offset; + } else { + while (pos < length) { + samples[pos++] += offset; + offset = (int)((LONG_LONG)(offset << 1) * factor >> 32); + } + } + cr->offset = offset; +} + + + +sample_t dumb_click_remover_get_offset(DUMB_CLICK_REMOVER *cr) +{ + return cr ? cr->offset : 0; +} + + + +void dumb_destroy_click_remover(DUMB_CLICK_REMOVER *cr) +{ + if (cr) { + DUMB_CLICK *click = cr->click; + while (click) { + DUMB_CLICK *next = click->next; + free(click); + click = next; + } + free(cr); + } +} + + + +DUMB_CLICK_REMOVER **dumb_create_click_remover_array(int n) +{ + int i; + DUMB_CLICK_REMOVER **cr; + if (n <= 0) return NULL; + cr = malloc(n * sizeof(*cr)); + if (!cr) return NULL; + for (i = 0; i < n; i++) cr[i] = dumb_create_click_remover(); + return cr; +} + + + +void dumb_record_click_array(int n, DUMB_CLICK_REMOVER **cr, long pos, sample_t *step) +{ + if (cr) { + int i; + for (i = 0; i < n; i++) + dumb_record_click(cr[i], pos, step[i]); + } +} + + + +void dumb_record_click_negative_array(int n, DUMB_CLICK_REMOVER **cr, long pos, sample_t *step) +{ + if (cr) { + int i; + for (i = 0; i < n; i++) + dumb_record_click(cr[i], pos, -step[i]); + } +} + + + +void dumb_remove_clicks_array(int n, DUMB_CLICK_REMOVER **cr, sample_t **samples, long length, float halflife) +{ + if (cr) { + int i; + for (i = 0; i < n; i++) + dumb_remove_clicks(cr[i], samples[i], length, halflife); + } +} + + + +void dumb_click_remover_get_offset_array(int n, DUMB_CLICK_REMOVER **cr, sample_t *offset) +{ + if (cr) { + int i; + for (i = 0; i < n; i++) + if (cr[i]) offset[i] += cr[i]->offset; + } +} + + + +void dumb_destroy_click_remover_array(int n, DUMB_CLICK_REMOVER **cr) +{ + if (cr) { + int i; + for (i = 0; i < n; i++) dumb_destroy_click_remover(cr[i]); + free(cr); + } +} diff --git a/apps/codecs/dumb/src/helpers/memfile.c b/apps/codecs/dumb/src/helpers/memfile.c new file mode 100644 index 0000000000..b65ab5f78d --- /dev/null +++ b/apps/codecs/dumb/src/helpers/memfile.c @@ -0,0 +1,96 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * memfile.c - Module for reading data from / / \ \ + * memory using a DUMBFILE. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include + +#include "dumb.h" + + + +typedef struct MEMFILE MEMFILE; + +struct MEMFILE +{ + const char *ptr; + long left; +}; + + + +static int dumb_memfile_skip(void *f, long n) +{ + MEMFILE *m = f; + if (n > m->left) return -1; + m->ptr += n; + m->left -= n; + return 0; +} + + + +static int dumb_memfile_getc(void *f) +{ + MEMFILE *m = f; + if (m->left <= 0) return -1; + m->left--; + return *(const unsigned char *)m->ptr++; +} + + + +static long dumb_memfile_getnc(char *ptr, long n, void *f) +{ + MEMFILE *m = f; + if (n > m->left) n = m->left; + memcpy(ptr, m->ptr, n); + m->ptr += n; + m->left -= n; + return n; +} + + + +static void dumb_memfile_close(void *f) +{ + free(f); +} + + + +static DUMBFILE_SYSTEM memfile_dfs = { + NULL, + &dumb_memfile_skip, + &dumb_memfile_getc, + &dumb_memfile_getnc, + &dumb_memfile_close +}; + + + +DUMBFILE *dumbfile_open_memory(const char *data, long size) +{ + MEMFILE *m = malloc(sizeof(*m)); + if (!m) return NULL; + + m->ptr = data; + m->left = size; + + return dumbfile_open_ex(m, &memfile_dfs); +} diff --git a/apps/codecs/dumb/src/helpers/resample.c b/apps/codecs/dumb/src/helpers/resample.c new file mode 100644 index 0000000000..d8b238fc71 --- /dev/null +++ b/apps/codecs/dumb/src/helpers/resample.c @@ -0,0 +1,1177 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * resample.c - Resampling helper. / / \ \ + * | < / \_ + * By Bob and entheh. | \/ /\ / + * \_ / > / + * In order to find a good trade-off between | \ / / + * speed and accuracy in this code, some tests | ' / + * were carried out regarding the behaviour of \__/ + * long long ints with gcc. The following code + * was tested: + * + * int a, b, c; + * c = ((long long)a * b) >> 16; + * + * DJGPP GCC Version 3.0.3 generated the following assembly language code for + * the multiplication and scaling, leaving the 32-bit result in EAX. + * + * movl -8(%ebp), %eax ; read one int into EAX + * imull -4(%ebp) ; multiply by the other; result goes in EDX:EAX + * shrdl $16, %edx, %eax ; shift EAX right 16, shifting bits in from EDX + * + * Note that a 32*32->64 multiplication is performed, allowing for high + * accuracy. On the Pentium 2 and above, shrdl takes two cycles (generally), + * so it is a minor concern when four multiplications are being performed + * (the cubic resampler). On the Pentium MMX and earlier, it takes four or + * more cycles, so this method is unsuitable for use in the low-quality + * resamplers. + * + * Since "long long" is a gcc-specific extension, we use LONG_LONG instead, + * defined in dumb.h. We may investigate later what code MSVC generates, but + * if it seems too slow then we suggest you use a good compiler. + * + * FIXME: these comments are somewhat out of date now. + */ + +#include +#include "dumb.h" + + + +/* Compile with -DHEAVYDEBUG if you want to make sure the pick-up function is + * called when it should be. There will be a considerable performance hit, + * since at least one condition has to be tested for every sample generated. + */ +#ifdef HEAVYDEBUG +#define HEAVYASSERT(cond) ASSERT(cond) +#else +#define HEAVYASSERT(cond) +#endif + + + +//#define MULSC(a, b) ((int)((LONG_LONG)(a) * (b) >> 16)) +//#define MULSC(a, b) ((a) * ((b) >> 2) >> 14) +#define MULSC(a, b) ((int)((LONG_LONG)((a) << 4) * ((b) << 12) >> 32)) + + + +/* A global variable for controlling resampling quality wherever a local + * specification doesn't override it. The following values are valid: + * + * 0 - DUMB_RQ_ALIASING - fastest + * 1 - DUMB_RQ_LINEAR + * 2 - DUMB_RQ_CUBIC - nicest + * + * Values outside the range 0-2 will behave the same as the nearest + * value within the range. + */ +int dumb_resampling_quality = 2; + + + +void dumb_reset_resampler(DUMB_RESAMPLER *resampler, sample_t *src, long pos, long start, long end) +{ + resampler->src = src; + resampler->pos = pos; + resampler->subpos = 0; + resampler->start = start; + resampler->end = end; + resampler->dir = 1; + resampler->pickup = NULL; + resampler->pickup_data = NULL; + resampler->min_quality = 0; + resampler->max_quality = DUMB_RQ_N_LEVELS - 1; + resampler->x[2] = resampler->x[1] = resampler->x[0] = 0; + resampler->overshot = -1; +} + + + +DUMB_RESAMPLER *dumb_start_resampler(sample_t *src, long pos, long start, long end) +{ + DUMB_RESAMPLER *resampler = malloc(sizeof(*resampler)); + if (!resampler) return NULL; + dumb_reset_resampler(resampler, src, pos, start, end); + return resampler; +} + + + +/* For convenience, returns nonzero on stop. */ +static int process_pickup(DUMB_RESAMPLER *resampler) +{ + if (resampler->overshot < 0) { + resampler->overshot = 0; + dumb_resample(resampler, NULL, 2, 0, 1.0f); + resampler->x[0] = resampler->x[1]; + } + + for (;;) { + if (resampler->dir < 0) { + if (resampler->overshot >= 3 && resampler->pos+3 >= resampler->start) resampler->x[0] = resampler->src[resampler->pos+3]; + if (resampler->overshot >= 2 && resampler->pos+2 >= resampler->start) resampler->x[1] = resampler->src[resampler->pos+2]; + if (resampler->overshot >= 1 && resampler->pos+1 >= resampler->start) resampler->x[2] = resampler->src[resampler->pos+1]; + resampler->overshot = resampler->start - resampler->pos - 1; + } else { + if (resampler->overshot >= 3 && resampler->pos-3 < resampler->end) resampler->x[0] = resampler->src[resampler->pos-3]; + if (resampler->overshot >= 2 && resampler->pos-2 < resampler->end) resampler->x[1] = resampler->src[resampler->pos-2]; + if (resampler->overshot >= 1 && resampler->pos-1 < resampler->end) resampler->x[2] = resampler->src[resampler->pos-1]; + resampler->overshot = resampler->pos - resampler->end; + } + + if (resampler->overshot < 0) { + resampler->overshot = 0; + return 0; + } + + if (!resampler->pickup) { + resampler->dir = 0; + return 1; + } + (*resampler->pickup)(resampler, resampler->pickup_data); + if (resampler->dir == 0) return 1; + ASSERT(resampler->dir == -1 || resampler->dir == 1); + } +} + + + +/* Executes the content 'iterator' times. + * Clobbers the 'iterator' variable. + * The loop is unrolled by four. + */ +#define LOOP4(iterator, CONTENT) \ +{ \ + if ((iterator) & 2) { \ + CONTENT; \ + CONTENT; \ + } \ + if ((iterator) & 1) { \ + CONTENT; \ + } \ + (iterator) >>= 2; \ + while (iterator) { \ + CONTENT; \ + CONTENT; \ + CONTENT; \ + CONTENT; \ + (iterator)--; \ + } \ +} + + + +long dumb_resample(DUMB_RESAMPLER *resampler, sample_t *dst, long dst_size, float volume, float delta) +{ + int dt; + int vol; + long done; + long todo; + int quality; + + if (!resampler || resampler->dir == 0) return 0; + ASSERT(resampler->dir == -1 || resampler->dir == 1); + + done = 0; + dt = (int)(delta * 65536.0 + 0.5); + vol = (int)floor(volume * 65536.0 + 0.5); + + if (vol == 0) dst = NULL; + + quality = dumb_resampling_quality; + if (quality > resampler->max_quality) quality = resampler->max_quality; + else if (quality < resampler->min_quality) quality = resampler->min_quality; + + while (done < dst_size) { + if (process_pickup(resampler)) return done; + + if ((resampler->dir ^ dt) < 0) + dt = -dt; + + if (resampler->dir < 0) + todo = (long)((((LONG_LONG)(resampler->pos - resampler->start) << 16) + resampler->subpos - dt) / -dt); + else + todo = (long)((((LONG_LONG)(resampler->end - resampler->pos) << 16) - resampler->subpos - 1 + dt) / dt); + + if (todo < 0) + todo = 0; + else if (todo > dst_size - done) + todo = dst_size - done; + + done += todo; + + { + sample_t *src = resampler->src; + long pos = resampler->pos; + int subpos = resampler->subpos; + long diff = pos; + long overshot; + if (resampler->dir < 0) { + if (!dst) { + /* Silence or simulation */ + LONG_LONG new_subpos = subpos + dt * todo; + pos += (long)(new_subpos >> 16); + subpos = (long)new_subpos & 65535; + } else if (quality <= DUMB_RQ_ALIASING) { + /* Aliasing, backwards */ + sample_t xbuf[2]; + sample_t *x = &xbuf[0]; + sample_t *xstart; + xbuf[0] = resampler->x[1]; + xbuf[1] = resampler->x[2]; + while (todo && x < &xbuf[2]) { + HEAVYASSERT(pos >= resampler->start); + *dst++ += MULSC(x[0], vol); + subpos += dt; + pos += subpos >> 16; + x -= subpos >> 16; + subpos &= 65535; + todo--; + } + x = xstart = &src[pos]; + LOOP4(todo, + *dst++ += MULSC(x[2], vol); + subpos += dt; + x += subpos >> 16; + subpos &= 65535; + ); + pos += x - xstart; + } else if (quality <= DUMB_RQ_LINEAR) { + /* Linear interpolation, backwards */ + sample_t xbuf[3]; + sample_t *x = &xbuf[1]; + xbuf[0] = resampler->x[1]; + xbuf[1] = resampler->x[2]; + xbuf[2] = src[pos]; + while (todo && x < &xbuf[3]) { + HEAVYASSERT(pos >= resampler->start); + *dst++ += MULSC(x[0] + MULSC(x[-1] - x[0], subpos), vol); + subpos += dt; + pos += subpos >> 16; + x -= subpos >> 16; + subpos &= 65535; + todo--; + } + x = &src[pos]; + LOOP4(todo, + HEAVYASSERT(pos >= resampler->start); + *dst++ += MULSC(x[1] + MULSC(x[2] - x[1], subpos), vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + ); + } else { + /* Cubic interpolation, backwards */ + sample_t xbuf[6]; + sample_t *x = &xbuf[3]; + sample_t *lastx = NULL; + int a = 0, b = 0, c = 0; + xbuf[0] = resampler->x[0]; + xbuf[1] = resampler->x[1]; + xbuf[2] = resampler->x[2]; + xbuf[3] = src[pos]; + if (pos-1 >= resampler->start) xbuf[4] = src[pos-1]; + if (pos-2 >= resampler->start) xbuf[5] = src[pos-2]; + while (todo && x < &xbuf[6]) { + HEAVYASSERT(pos >= resampler->start); + if (lastx != x) { + lastx = x; + a = (((x[-1] - x[-2]) << 1) + (x[-1] - x[-2]) + (x[-3] - x[0])) >> 1; + b = (x[-2] << 1) + x[0] - ((5 * x[-1] + x[-3]) >> 1); + c = (x[-2] - x[0]) >> 1; + } + *dst++ += MULSC(MULSC(MULSC(MULSC(a, subpos) + b, subpos) + c, subpos) + x[-1], vol); + subpos += dt; + pos += subpos >> 16; + x -= subpos >> 16; + subpos &= 65535; + todo--; + } + x = &src[pos]; + lastx = NULL; + LOOP4(todo, + HEAVYASSERT(pos >= resampler->start); + if (lastx != x) { + lastx = x; + a = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (x[3] - x[0])) >> 1; + b = (x[2] << 1) + x[0] - ((5 * x[1] + x[3]) >> 1); + c = (x[2] - x[0]) >> 1; + } + *dst++ += MULSC(MULSC(MULSC(MULSC(a, subpos) + b, subpos) + c, subpos) + x[1], vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + ); + } + diff = diff - pos; + overshot = resampler->start - pos - 1; + if (diff >= 3) { + resampler->x[0] = overshot >= 3 ? 0 : src[pos+3]; + resampler->x[1] = overshot >= 2 ? 0 : src[pos+2]; + resampler->x[2] = overshot >= 1 ? 0 : src[pos+1]; + } else if (diff >= 2) { + resampler->x[0] = resampler->x[2]; + resampler->x[1] = overshot >= 2 ? 0 : src[pos+2]; + resampler->x[2] = overshot >= 1 ? 0 : src[pos+1]; + } else if (diff >= 1) { + resampler->x[0] = resampler->x[1]; + resampler->x[1] = resampler->x[2]; + resampler->x[2] = overshot >= 1 ? 0 : src[pos+1]; + } + } else { + if (!dst) { + /* Silence or simulation */ + LONG_LONG new_subpos = subpos + dt * todo; + pos += (long)(new_subpos >> 16); + subpos = (long)new_subpos & 65535; + } else if (dumb_resampling_quality <= DUMB_RQ_ALIASING) { + /* Aliasing, forwards */ + sample_t xbuf[2]; + sample_t *x = &xbuf[0]; + sample_t *xstart; + xbuf[0] = resampler->x[1]; + xbuf[1] = resampler->x[2]; + while (todo && x < &xbuf[2]) { + HEAVYASSERT(pos < resampler->end); + *dst++ += MULSC(x[0], vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + todo--; + } + x = xstart = &src[pos]; + LOOP4(todo, + *dst++ += MULSC(x[-2], vol); + subpos += dt; + x += subpos >> 16; + subpos &= 65535; + ); + pos += x - xstart; + } else if (dumb_resampling_quality <= DUMB_RQ_LINEAR) { + /* Linear interpolation, forwards */ + sample_t xbuf[3]; + sample_t *x = &xbuf[1]; + xbuf[0] = resampler->x[1]; + xbuf[1] = resampler->x[2]; + xbuf[2] = src[pos]; + while (todo && x < &xbuf[3]) { + HEAVYASSERT(pos < resampler->end); + *dst++ += MULSC(x[-1] + MULSC(x[0] - x[-1], subpos), vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + todo--; + } + x = &src[pos]; + LOOP4(todo, + HEAVYASSERT(pos < resampler->end); + *dst++ += MULSC(x[-2] + MULSC(x[-1] - x[-2], subpos), vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + ); + } else { + /* Cubic interpolation, forwards */ + sample_t xbuf[6]; + sample_t *x = &xbuf[3]; + sample_t *lastx = NULL; + int a = 0, b = 0, c = 0; + xbuf[0] = resampler->x[0]; + xbuf[1] = resampler->x[1]; + xbuf[2] = resampler->x[2]; + xbuf[3] = src[pos]; + if (pos+1 < resampler->end) xbuf[4] = src[pos+1]; + if (pos+2 < resampler->end) xbuf[5] = src[pos+2]; + while (todo && x < &xbuf[6]) { + HEAVYASSERT(pos < resampler->end); + if (lastx != x) { + lastx = x; + a = (((x[-2] - x[-1]) << 1) + (x[-2] - x[-1]) + (x[0] - x[-3])) >> 1; + b = (x[-1] << 1) + x[-3] - ((5 * x[-2] + x[0]) >> 1); + c = (x[-1] - x[-3]) >> 1; + } + *dst++ += MULSC(MULSC(MULSC(MULSC(a, subpos) + b, subpos) + c, subpos) + x[-2], vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + todo--; + } + x = &src[pos]; + lastx = NULL; + LOOP4(todo, + HEAVYASSERT(pos < resampler->end); + if (lastx != x) { + lastx = x; + a = (((x[-2] - x[-1]) << 1) + (x[-2] - x[-1]) + (x[0] - x[-3])) >> 1; + b = (x[-1] << 1) + x[-3] - ((5 * x[-2] + x[0]) >> 1); + c = (x[-1] - x[-3]) >> 1; + } + *dst++ += MULSC(MULSC(MULSC(MULSC(a, subpos) + b, subpos) + c, subpos) + x[-2], vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + ); + } + diff = pos - diff; + overshot = pos - resampler->end; + if (diff >= 3) { + resampler->x[0] = overshot >= 3 ? 0 : src[pos-3]; + resampler->x[1] = overshot >= 2 ? 0 : src[pos-2]; + resampler->x[2] = overshot >= 1 ? 0 : src[pos-1]; + } else if (diff >= 2) { + resampler->x[0] = resampler->x[2]; + resampler->x[1] = overshot >= 2 ? 0 : src[pos-2]; + resampler->x[2] = overshot >= 1 ? 0 : src[pos-1]; + } else if (diff >= 1) { + resampler->x[0] = resampler->x[1]; + resampler->x[1] = resampler->x[2]; + resampler->x[2] = overshot >= 1 ? 0 : src[pos-1]; + } + } + resampler->pos = pos; + resampler->subpos = subpos; + } + } + + return done; +} + + + +sample_t dumb_resample_get_current_sample(DUMB_RESAMPLER *resampler, float volume) +{ + int vol; + sample_t *src; + long pos; + int subpos; + int quality; + + if (!resampler || resampler->dir == 0) return 0; + ASSERT(resampler->dir == -1 || resampler->dir == 1); + + if (process_pickup(resampler)) return 0; + + vol = (int)floor(volume * 65536.0 + 0.5); + if (vol == 0) return 0; + + quality = dumb_resampling_quality; + if (quality > resampler->max_quality) quality = resampler->max_quality; + else if (quality < resampler->min_quality) quality = resampler->min_quality; + + src = resampler->src; + pos = resampler->pos; + subpos = resampler->subpos; + + if (resampler->dir < 0) { + HEAVYASSERT(pos >= resampler->start); + if (dumb_resampling_quality <= 0) { + /* Aliasing, backwards */ + return MULSC(src[pos], vol); + } else if (quality <= DUMB_RQ_LINEAR) { + /* Linear interpolation, backwards */ + return MULSC(resampler->x[2] + MULSC(resampler->x[1] - resampler->x[2], subpos), vol); + } else { + /* Cubic interpolation, backwards */ + sample_t *x = resampler->x; + int a, b, c; + a = (((x[2] - x[1]) << 1) + (x[2] - x[1]) + (x[0] - src[pos])) >> 1; + b = (x[1] << 1) + src[pos] - ((5 * x[2] + x[0]) >> 1); + c = (x[1] - src[pos]) >> 1; + return MULSC(MULSC(MULSC(MULSC(a, subpos) + b, subpos) + c, subpos) + x[2], vol); + } + } else { + HEAVYASSERT(pos < resampler->end); + if (dumb_resampling_quality <= 0) { + /* Aliasing */ + return MULSC(src[pos], vol); + } else if (dumb_resampling_quality <= DUMB_RQ_LINEAR) { + /* Linear interpolation, forwards */ + return MULSC(resampler->x[1] + MULSC(resampler->x[2] - resampler->x[1], subpos), vol); + } else { + /* Cubic interpolation, forwards */ + sample_t *x = resampler->x; + int a, b, c; + a = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (src[pos] - x[0])) >> 1; + b = (x[2] << 1) + x[0] - ((5 * x[1] + src[pos]) >> 1); + c = (x[2] - x[0]) >> 1; + return MULSC(MULSC(MULSC(MULSC(a, subpos) + b, subpos) + c, subpos) + x[1], vol); + } + } +} + + + +void dumb_end_resampler(DUMB_RESAMPLER *resampler) +{ + if (resampler) + free(resampler); +} + + + +#if 0 +/* The following macro is used to overcome the fact that most C + * compilers (including gcc and MSVC) can't correctly multiply signed + * integers outside the range -32768 to 32767. i86 assembler versions + * don't need to use this method, since the processor does in fact + * have instructions to multiply large numbers correctly - which + * means using assembly language could make a significant difference + * to the speed. + * + * The basic method is as follows. We halve the subposition (how far + * we are between samples), so it never exceeds 32767. We also halve + * the delta, which is the amount to be added to the subposition each + * time. Then we unroll the loop twofold, so that we can add the lost + * one every other time if necessary (since the halving may have + * resulted in rounding down). + * + * This method doesn't incur any cumulative inaccuracies. There is a + * very slight loss of quality, which I challenge anyone to notice - + * but the position will advance at *exactly* the same rate as it + * would if we didn't use this method. This also means the pitch is + * exactly the same, which may even make a difference to trained + * musicians when resampling down a lot :) + * + * Each time this macro is invoked, DO_RESAMPLE(inc) must be defined + * to calculate the samples by the appropriate equation (linear, + * cubic, etc.). See the individual cases for examples of how this is + * done. + */ +#define MAKE_RESAMPLER() \ +{ \ + if (dt & 1) { \ + long todo2; \ + \ + dt >>= 1; \ + \ + if (src_subpos & 1) { \ + src_subpos >>= 1; \ + DO_RESAMPLE(1); \ + todo--; \ + } else \ + src_subpos >>= 1; \ + \ + todo2 = todo >> 1; \ + \ + while (todo2) { \ + DO_RESAMPLE(0); \ + DO_RESAMPLE(1); \ + todo2--; \ + } \ + \ + if (todo & 1) { \ + DO_RESAMPLE(0); \ + src_subpos = (src_subpos << 1) | 1; \ + } else \ + src_subpos <<= 1; \ + \ + todo = 0; \ + dt = (dt << 1) | 1; \ + } else { \ + long subposbit = src_subpos & 1; \ + dt >>= 1; \ + src_subpos >>= 1; \ + \ + if (todo & 1) { \ + DO_RESAMPLE(0); \ + } \ + \ + todo >>= 1; \ + \ + while (todo) { \ + DO_RESAMPLE(0); \ + DO_RESAMPLE(0); \ + todo--; \ + } \ + \ + src_subpos = (src_subpos << 1) | subposbit; \ + dt <<= 1; \ + } \ +} + + + +sample_t dumb_resample_get_current_sample( + sample_t *src, long *_src_pos, int *_src_subpos, + long src_start, long src_end, + float volume, int *_dir, + DUMB_RESAMPLE_PICKUP pickup, void *pickup_data +) +{ + long src_pos = *_src_pos; + int src_subpos = *_src_subpos; + int dir = _dir ? *_dir : 1; + + sample_t value = 0; + + if (dir == 0) + return 0; + + ASSERT(dir == 1 || dir == -1); + + if (dir < 0 ? (src_pos < src_start) : (src_pos >= src_end)) { + + /* If there's no pick-up function, we stop. */ + if (!pickup) { + dir = 0; + goto end; + } + + /* Process the pick-up. It may need invoking more than once. */ + do { + dir = (*pickup)(src, &src_pos, &src_subpos, &src_start, &src_end, dir, pickup_data); + + if (dir == 0) + goto end; + + ASSERT(dir == 1 || dir == -1); + } while (dir < 0 ? (src_pos < src_start) : (src_pos >= src_end)); + } + + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); + + if (dumb_resampling_quality == 0) { + /* Aliasing (coarse) */ + int volume_fact = (int)(volume * 16384.0); + value = (src[src_pos] * volume_fact) >> 14; + } else if (dumb_resampling_quality <= 2) { + /* Linear interpolation */ + int volume_fact = (int)(volume * 16384.0); + int subpos = src_subpos >> 1; + value = ((src[src_pos] + ((((src[src_pos + 1] - src[src_pos]) >> 1) * subpos) >> 14)) * volume_fact) >> 14; + } else if (dumb_resampling_quality == 3) { + /* Quadratic interpolation */ + int volume_fact = (int)(volume * 16384.0); + int a, b; + sample_t *x; + int subpos = src_subpos >> 1; + x = &src[src_pos]; + a = ((x[0] + x[2]) >> 1) - x[1]; + b = ((x[2] - x[0]) >> 1) - (a << 1); + value = (((((((a * subpos) >> 15) + b) * subpos) >> 15) + x[0]) * volume_fact) >> 14; + } else { + /* Cubic interpolation */ + int volume_fact = (int)(volume * 16384.0); + int a, b, c; + sample_t *x; + int subpos = src_subpos >> 1; + x = &src[src_pos]; + a = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (x[3] - x[0])) >> 1; + b = (x[2] << 1) + x[0] - ((5 * x[1] + x[3]) >> 1); + c = (x[2] - x[0]) >> 1; + value = (((int)(((LONG_LONG)((int)(((LONG_LONG)((int)(((LONG_LONG)a * subpos) >> 15) + b) * subpos) >> 15) + c) * subpos) >> 15) + x[1]) * volume_fact) >> 14; + } + + end: + + *_src_pos = src_pos; + *_src_subpos = src_subpos; + if (_dir) *_dir = dir; + + return value; +} + + + +long dumb_resample( + sample_t *src, long *_src_pos, int *_src_subpos, + long src_start, long src_end, + sample_t *dst, long dst_size, + float volume, float delta, int *_dir, + DUMB_RESAMPLE_PICKUP pickup, void *pickup_data +) +{ + int dt = (int)(delta * 65536.0 + 0.5); + long s = 0; /* Current position in the destination buffer */ + + long src_pos = *_src_pos; + int src_subpos = *_src_subpos; + int dir = _dir ? *_dir : 1; + + int linear_average; + + if (dir == 0) + return 0; + + ASSERT(dir == 1 || dir == -1); + + linear_average = dst && dumb_resampling_quality >= 2 && dt > 65536; + + if (dir < 0) dt = -dt; + + if (linear_average) + volume /= delta; + + while (s < dst_size) { + + long todo; + + /* Process pick-ups first, just in case. */ + + if (linear_average) { + + /* For linear average, the pick-up point could split a sum into + * two parts. We handle this by putting the pick-up code inside + * the summing loop. Note that this code is only executed when we + * know that a pick-up is necessary somewhere during this sum + * (although it is always executed once for the first sample). + * We use a separate loop further down when we know we won't have + * to do a pick-up, so the condition does not need testing inside + * the loop. + */ + + float sum; + long i; + int advance; + int x[3]; + + advance = src_subpos + dt; + + /* Make these negative. Then they stay within the necessary + * range for integer multiplication, -32768 to 32767 ;) + */ + x[0] = ~(src_subpos >> 1); /* = -1 - (src_subpos >> 1) */ + x[2] = x[0] ^ 0x7FFF; /* = -32768 + (src_subpos >> 1) */ + + sum = (float)(-((src[src_pos] * (x+1)[dir]) >> 15)); + + i = src_pos + (advance >> 16); + src_pos += dir; + src_subpos = (dir >> 1) & 65535; /* changes 1,-1 to 0,65535 */ + + advance &= 65535; + + /* i is the index of the first sample NOT to sum fully, + * regardless of the direction of resampling. + */ + + while (dir < 0 ? (i < src_start) : (i >= src_end)) { + if (dir < 0) { + while (src_pos >= src_start) + sum += src[src_pos--]; + } else { + while (src_pos < src_end) + sum += src[src_pos++]; + } + + i -= src_pos; + /* i is now the number of samples left to sum fully, except + * it's negative if we're going backwards. + */ + + if (!pickup) { + dir = 0; + goto endsum; + } + + dir = (*pickup)(src, &src_pos, &src_subpos, &src_start, &src_end, dir, pickup_data); + + if (dir == 0) + goto endsum; + + ASSERT(dir == 1 || dir == -1); + + if ((dir ^ dt) < 0) { + dt = -dt; + advance ^= 65535; + i = -i; + } + + i += src_pos; + /* There, i is back to normal. */ + } + + for (; src_pos != i; src_pos += dir) + sum += src[src_pos]; + + src_subpos = advance; + + x[2] = src_subpos >> 1; + x[0] = x[2] ^ 0x7FFF; /* = 32767 - (src_subpos >> 1) */ + + sum += (src[src_pos] * (x+1)[dir]) >> 15; + + endsum: + + sum *= volume; + dst[s] += (int)sum; + + s++; + + if (dir == 0) + break; + + } else if (dir < 0 ? (src_pos < src_start) : (src_pos >= src_end)) { + + /* If there's no pick-up function, we stop. */ + if (!pickup) { + dir = 0; + break; + } + + /* Process the pick-up. It may need invoking more than once. */ + do { + dir = (*pickup)(src, &src_pos, &src_subpos, &src_start, &src_end, dir, pickup_data); + + if (dir == 0) + goto end; + + ASSERT(dir == 1 || dir == -1); + } while (dir < 0 ? (src_pos < src_start) : (src_pos >= src_end)); + + /* Update sign of dt to match that of dir. */ + if ((dir ^ dt) < 0) + dt = -dt; + } + + /* Work out how many contiguous samples we can now render. */ + if (dir < 0) + todo = (long)((((LONG_LONG)(src_pos - src_start) << 16) + src_subpos) / -dt); + else + todo = (long)((((LONG_LONG)(src_end - src_pos) << 16) - src_subpos - 1) / dt); + + /* The above equations work out how many complete dt-sized + * intervals there are between the current position and the loop + * point (provided there is a little fractional extra). The linear + * average function needs complete intervals - but the other + * resamplers only read a sample from the beginning of each interval, + * so they can process one extra sample in their main loops (so we + * increment todo in a moment). + * + * The linear average function makes up the extra sample using the + * specialised pick-up code above. + * + * Note that our above pick-up process should have absolutely ensured + * that the result of this function will be nonnegative. + */ + + ASSERT(todo >= 0); + + if (!linear_average) + todo++; + + /* Of course we don't want to overrun the output buffer! */ + if (todo > dst_size - s) + todo = dst_size - s; + + if (!dst) { + + LONG_LONG t = src_subpos + (LONG_LONG)dt * todo; + src_pos += (long)(t >> 16); + src_subpos = (int)t & 0xFFFFl; + + s += todo; + + } else if (linear_average) { + + float sum; + long i; + int advance; + int x[3]; + + while (todo) { + + advance = src_subpos + dt; + + /* Make these negative. Then they stay within the necessary + * range for integer multiplication, -32768 to 32767 ;) + */ + x[0] = ~(src_subpos >> 1); /* = -1 - (src_subpos >> 1) */ + x[2] = x[0] ^ 0x7FFF; /* = -32768 + (src_subpos >> 1) */ + + sum = (float)(-((src[src_pos] * (x+1)[dir]) >> 15)); + + i = src_pos + (advance >> 16); + src_pos += dir; + src_subpos = (dir >> 1) & 65535; /* changes 1,-1 to 0,65535 */ + + advance &= 65535; + + /* i is the index of the first sample NOT to sum fully, + * regardless of the direction of resampling. + */ + + HEAVYASSERT(dir < 0 ? (i >= src_start) : (i < src_end)); + + for (; src_pos != i; src_pos += dir) + sum += src[src_pos]; + + src_subpos = advance; + + x[2] = src_subpos >> 1; + x[0] = x[2] ^ 0x7FFF; /* = 32767 - (src_subpos >> 1) */ + + sum += (src[src_pos] * (x+1)[dir]) >> 15; + + sum *= volume; + dst[s] += (int)sum; + + s++; + todo--; + } + + } else if (dumb_resampling_quality == 0 || (dumb_resampling_quality == 1 && delta >= 1.0)) { + + /* Aliasing (coarse) */ + int volume_fact = (int)(volume * 16384.0); + + do { + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); + dst[s] += ((src[src_pos] * volume_fact) >> 14); + src_subpos += dt; + src_pos += src_subpos >> 16; + src_subpos &= 0xFFFFl; + s++; + } while (--todo); + + } else if (dumb_resampling_quality <= 2) { + + /* Linear interpolation */ + int volume_fact = (int)(volume * 16384.0); + + #define DO_RESAMPLE(inc) \ + { \ + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); \ + \ + dst[s] += (((src[src_pos] + ((((src[src_pos + 1] - src[src_pos]) >> 1) * src_subpos) >> 14)) * volume_fact) >> 14); \ + \ + src_subpos += dt + inc; \ + src_pos += src_subpos >> 15; \ + src_subpos &= 0x7FFFl; \ + s++; \ + } + + MAKE_RESAMPLER(); + + #undef DO_RESAMPLE + + } else if (dumb_resampling_quality == 3) { + + /* Quadratic interpolation */ + + int volume_fact = (int)(volume * 16384.0); + int a = 0, b = 0; + sample_t *x = NULL; + int last_src_pos = -1; + + /* AIM: no integer multiplicands must transcend the range -32768 to 32767. + * This limitation is imposed by most compilers, including gcc and MSVC. + * + * a = 0.5 * (s0 + s2) - s1 + * b = -1.5 * s0 + 2 * s1 - 0.5 * s2 + * c = s0 + * + * s = (a * t + b) * t + c + * + * In fixed-point: + * + * a = ((s0 + s2) >> 1) - s1 + * b = ((-3 * s0 - s2) >> 1) + (s1 << 1) + * + * s = (((((a * t) >> 16) + b) * t) >> 16) + s0 + * + * With t halved (since t can reach 65535): + * + * s = (((((a * t) >> 15) + b) * t) >> 15) + s0 + * + * a currently reaches 65536 + * b currently reaches 131072 + * + * So we must use aon2 + * + * s = (((((aon2 * t) >> 14) + b) * t) >> 15) + s0 + * + * ((aon2 * t) >> 14) + b is 5 times too big + * so we must divide by 8 + * + * s = (((((aon2 * t) >> 17) + bon8) * t) >> 12) + s0 + * + * aon2 = ((s0 + s2) >> 2) - (s1 >> 1) + * bon8 = ((-3 * s0 - s2) >> 4) + (s1 >> 2) + * or: + * bon8 = ((s2 - s0) >> 4) - (aon2 >> 1) + */ + + /* Unh4x0r3d version: + #define DO_RESAMPLE(inc) \ + { \ + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); \ + \ + if (src_pos != last_src_pos) { \ + last_src_pos = src_pos; \ + x = &src[src_pos]; \ + a = ((x[0] + x[2]) >> 2) - (x[1] >> 1); \ + b = ((x[2] - x[0]) >> 4) - (a >> 1); \ + } \ + \ + dst[s] += ((((((((a * src_subpos) >> 17) + b) * src_subpos) >> 12) + x[0]) * volume_fact) >> 14); \ + \ + src_subpos += dt + inc; \ + src_pos += src_subpos >> 15; \ + src_subpos &= 0x7FFFl; \ + s++; \ + } + */ + + /* H4x0r3d version: */ + #define DO_RESAMPLE(inc) \ + { \ + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); \ + \ + if (src_pos != last_src_pos) { \ + last_src_pos = src_pos; \ + x = &src[src_pos]; \ + a = ((x[0] + x[2]) >> 1) - x[1]; \ + b = ((x[2] - x[0]) >> 1) - (a << 1); \ + } \ + \ + dst[s] += ((((((((a * src_subpos) >> 15) + b) * src_subpos) >> 15) + x[0]) * volume_fact) >> 14); \ + \ + src_subpos += dt + inc; \ + src_pos += src_subpos >> 15; \ + src_subpos &= 0x7FFFl; \ + s++; \ + } + + MAKE_RESAMPLER(); + + #undef DO_RESAMPLE + + } else { + + /* Cubic interpolation */ + + int volume_fact = (int)(volume * 16384.0); + int a = 0, b = 0, c = 0; + sample_t *x = NULL; + int last_src_pos = -1; + + /* AIM: never multiply integers outside the range -32768 to 32767. + * + * a = 1.5f * (x[1] - x[2]) + (x[3] - x[0]) * 0.5f; + * b = 2.0f * x[2] + x[0] - 2.5f * x[1] - x[3] * 0.5f; + * c = (x[2] - x[0]) * 0.5f; + * + * s = ((a * t + b) * t + c) * t + x[1]; + * + * Fixed-point version: + * + * a = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (x[3] - x[0])) >> 1; + * b = (x[2] << 1) + x[0] - ((5 * x[1] + x[3]) >> 1); + * c = (x[2] - x[0]) >> 1; + * + * s = ((((((((a * t) >> 15) + b) * t) >> 15) + c) * t) >> 15) + x[1]; + * (with t already halved, maximum 32767) + * + * a is in (((1+1)*2)+(1+1)+(1+1))/2 = 8 times the required range + * b is in (1*2)+1+((5*1+1)/2) = 6 times + * c is in the required range + * + * We must use aon8 + * + * s = ((((((((aon8 * t) >> 12) + b) * t) >> 15) + c) * t) >> 15) + x[1]; + * + * But ((aon8 * t) >> 12) is in 2^(15+15-12) = 2^18 = 8 times + * b is in 6 times + * so we divide both ((aon8 * t) >> 12) and b by 16 + * + * s = ((((((((aon8 * t) >> 16) + bon16) * t) >> 11) + c) * t) >> 15) + x[1]; + * + * ((... + bon16) * t) >> 11 is 16 times too big + * c is in the correct range + * we must divide both by 32 + * + * s = ((((((((aon8 * t) >> 16) + bon16) * t) >> 16) + con32) * t) >> 10) + x[1]; + * + * aon8 = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (x[3] - x[0])) >> 4; + * bon16 = ((x[2] << 2) + (x[0] << 1) - (5 * x[1] + x[3])) >> 5; + * con32 = (x[2] - x[0]) >> 6; + * + * A lot of accuracy is lost here. It is quite likely that some + * of the above would cancel anyway, so the scaling down wouldn't + * have to be so severe. However, I'm not in the mood to work it + * out now :P + * + * It may also be worth investigating whether doing this stuff + * in floats would be faster. + */ + + /* Unh4x0r3d version: + #define DO_RESAMPLE(inc) \ + { \ + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); \ + \ + if (src_pos != last_src_pos) { \ + last_src_pos = src_pos; \ + x = &src[src_pos]; \ + a = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (x[3] - x[0])) >> 4; \ + b = ((x[2] << 2) + (x[0] << 1) - (5 * x[1] + x[3])) >> 5; \ + c = (x[2] - x[0]) >> 6; \ + } \ + \ + dst[s] += ((((((((((a * src_subpos) >> 16) + b) * src_subpos) >> 16) + c) * src_subpos) >> 10) + x[1]) * volume_fact) >> 14; \ + \ + src_subpos += dt + inc; \ + src_pos += src_subpos >> 15; \ + src_subpos &= 0x7FFFl; \ + s++; \ + } + */ + + /* H4x0r3d version: */ + #define DO_RESAMPLE(inc) \ + { \ + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); \ + \ + if (src_pos != last_src_pos) { \ + last_src_pos = src_pos; \ + x = &src[src_pos]; \ + a = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (x[3] - x[0])) >> 1; \ + b = (x[2] << 1) + x[0] - ((5 * x[1] + x[3]) >> 1); \ + c = (x[2] - x[0]) >> 1; \ + } \ + \ + dst[s] += (((int)(((LONG_LONG)((int)(((LONG_LONG)((int)(((LONG_LONG)a * src_subpos) >> 15) + b) * src_subpos) >> 15) + c) * src_subpos) >> 15) + x[1]) * volume_fact) >> 14; \ + \ + src_subpos += dt + inc; \ + src_pos += src_subpos >> 15; \ + src_subpos &= 0x7FFFl; \ + s++; \ + } + + MAKE_RESAMPLER(); + + #undef DO_RESAMPLE + + } + + } + + end: + + ASSERT(s <= dst_size); + + *_src_pos = src_pos; + *_src_subpos = src_subpos; + if (_dir) *_dir = dir; + + return s; +} +#endif diff --git a/apps/codecs/dumb/src/helpers/sampbuf.c b/apps/codecs/dumb/src/helpers/sampbuf.c new file mode 100644 index 0000000000..75510c729a --- /dev/null +++ b/apps/codecs/dumb/src/helpers/sampbuf.c @@ -0,0 +1,47 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * sampbuf.c - Helper for allocating sample / / \ \ + * buffers. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include "dumb.h" + + + +sample_t **create_sample_buffer(int n_channels, long length) +{ + int i; + sample_t **samples = malloc(n_channels * sizeof(*samples)); + if (!samples) return NULL; + samples[0] = malloc(n_channels * length * sizeof(*samples[0])); + if (!samples[0]) { + free(samples); + return NULL; + } + for (i = 1; i < n_channels; i++) samples[i] = samples[i-1] + length; + return samples; +} + + + +void destroy_sample_buffer(sample_t **samples) +{ + if (samples) { + free(samples[0]); + free(samples); + } +} diff --git a/apps/codecs/dumb/src/helpers/silence.c b/apps/codecs/dumb/src/helpers/silence.c new file mode 100644 index 0000000000..4d5fdcf4da --- /dev/null +++ b/apps/codecs/dumb/src/helpers/silence.c @@ -0,0 +1,29 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * silence.c - Silencing helper. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include "dumb.h" + + + +void dumb_silence(sample_t *samples, long length) +{ + memset(samples, 0, length * sizeof(*samples)); +} + diff --git a/apps/codecs/dumb/src/helpers/stdfile.c b/apps/codecs/dumb/src/helpers/stdfile.c new file mode 100644 index 0000000000..2f02539a92 --- /dev/null +++ b/apps/codecs/dumb/src/helpers/stdfile.c @@ -0,0 +1,93 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * stdfile.c - stdio file input module. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" + + + +static void *dumb_stdfile_open(const char *filename) +{ + return fopen(filename, "rb"); +} + + + +static int dumb_stdfile_skip(void *f, long n) +{ + return fseek(f, n, SEEK_CUR); +} + + + +static int dumb_stdfile_getc(void *f) +{ + return fgetc(f); +} + + + +static long dumb_stdfile_getnc(char *ptr, long n, void *f) +{ + return fread(ptr, 1, n, f); +} + + + +static void dumb_stdfile_close(void *f) +{ + fclose(f); +} + + + +static DUMBFILE_SYSTEM stdfile_dfs = { + &dumb_stdfile_open, + &dumb_stdfile_skip, + &dumb_stdfile_getc, + &dumb_stdfile_getnc, + &dumb_stdfile_close +}; + + + +void dumb_register_stdfiles(void) +{ + register_dumbfile_system(&stdfile_dfs); +} + + + +static DUMBFILE_SYSTEM stdfile_dfs_leave_open = { + NULL, + &dumb_stdfile_skip, + &dumb_stdfile_getc, + &dumb_stdfile_getnc, + NULL +}; + + + +DUMBFILE *dumbfile_open_stdfile(FILE *p) +{ + DUMBFILE *d = dumbfile_open_ex(p, &stdfile_dfs_leave_open); + + return d; +} diff --git a/apps/codecs/dumb/src/it/itload.c b/apps/codecs/dumb/src/it/itload.c new file mode 100644 index 0000000000..a377619a88 --- /dev/null +++ b/apps/codecs/dumb/src/it/itload.c @@ -0,0 +1,43 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * itload.c - Code to read an Impulse Tracker / / \ \ + * file, opening and closing it for | < / \_ + * you. | \/ /\ / + * \_ / > / + * By entheh. Don't worry Bob, you're credited | \ / / + * in itread.c! | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/it.h" + + + +/* dumb_load_it(): loads an IT file into a DUH struct, returning a pointer to + * the DUH struct. When you have finished with it, you must pass the pointer + * to unload_duh() so that the memory can be freed. + */ +DUH *dumb_load_it(const char *filename) +{ + DUH *duh; + DUMBFILE *f = dumbfile_open(filename); + + if (!f) + return NULL; + + duh = dumb_read_it(f); + + dumbfile_close(f); + + return duh; +} + diff --git a/apps/codecs/dumb/src/it/itmisc.c b/apps/codecs/dumb/src/it/itmisc.c new file mode 100644 index 0000000000..b69a64df10 --- /dev/null +++ b/apps/codecs/dumb/src/it/itmisc.c @@ -0,0 +1,175 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * itmisc.c - Miscellaneous functions relating / / \ \ + * to module files. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/it.h" + + + +DUMB_IT_SIGDATA *duh_get_it_sigdata(DUH *duh) +{ + return duh_get_raw_sigdata(duh, 0, SIGTYPE_IT); +} + + + +int dumb_it_sd_get_n_orders(DUMB_IT_SIGDATA *sd) +{ + return sd ? sd->n_orders : 0; +} + + + +int dumb_it_sd_get_initial_global_volume(DUMB_IT_SIGDATA *sd) +{ + return sd ? sd->global_volume : 0; +} + + + +void dumb_it_sd_set_initial_global_volume(DUMB_IT_SIGDATA *sd, int gv) +{ + if (sd) sd->global_volume = gv; +} + + + +int dumb_it_sd_get_mixing_volume(DUMB_IT_SIGDATA *sd) +{ + return sd ? sd->mixing_volume : 0; +} + + + +void dumb_it_sd_set_mixing_volume(DUMB_IT_SIGDATA *sd, int mv) +{ + if (sd) sd->mixing_volume = mv; +} + + + +int dumb_it_sd_get_initial_speed(DUMB_IT_SIGDATA *sd) +{ + return sd ? sd->speed : 0; +} + + + +void dumb_it_sd_set_initial_speed(DUMB_IT_SIGDATA *sd, int speed) +{ + if (sd) sd->speed = speed; +} + + + +int dumb_it_sd_get_initial_tempo(DUMB_IT_SIGDATA *sd) +{ + return sd ? sd->tempo : 0; +} + + + +void dumb_it_sd_set_initial_tempo(DUMB_IT_SIGDATA *sd, int tempo) +{ + if (sd) sd->tempo = tempo; +} + + + +int dumb_it_sd_get_initial_channel_volume(DUMB_IT_SIGDATA *sd, int channel) +{ + ASSERT(channel >= 0 && channel < DUMB_IT_N_CHANNELS); + return sd ? sd->channel_volume[channel] : 0; +} + +void dumb_it_sd_set_initial_channel_volume(DUMB_IT_SIGDATA *sd, int channel, int volume) +{ + ASSERT(channel >= 0 && channel < DUMB_IT_N_CHANNELS); + if (sd) sd->channel_volume[channel] = volume; +} + + + +int dumb_it_sr_get_current_order(DUMB_IT_SIGRENDERER *sr) +{ + return sr ? sr->order : -1; +} + + + +int dumb_it_sr_get_current_row(DUMB_IT_SIGRENDERER *sr) +{ + return sr ? sr->row : -1; +} + + + +int dumb_it_sr_get_global_volume(DUMB_IT_SIGRENDERER *sr) +{ + return sr ? sr->globalvolume : 0; +} + + + +void dumb_it_sr_set_global_volume(DUMB_IT_SIGRENDERER *sr, int gv) +{ + if (sr) sr->globalvolume = gv; +} + + + +int dumb_it_sr_get_tempo(DUMB_IT_SIGRENDERER *sr) +{ + return sr ? sr->tempo : 0; +} + + + +void dumb_it_sr_set_tempo(DUMB_IT_SIGRENDERER *sr, int tempo) +{ + if (sr) sr->tempo = tempo; +} + + + +int dumb_it_sr_get_speed(DUMB_IT_SIGRENDERER *sr) +{ + return sr ? sr->speed : 0; +} + + + +void dumb_it_sr_set_speed(DUMB_IT_SIGRENDERER *sr, int speed) +{ + if (sr) sr->speed = speed; +} + + + +int dumb_it_sr_get_channel_volume(DUMB_IT_SIGRENDERER *sr, int channel) +{ + return sr ? sr->channel[channel].channelvolume : 0; +} + + + +void dumb_it_sr_set_channel_volume(DUMB_IT_SIGRENDERER *sr, int channel, int volume) +{ + if (sr) sr->channel[channel].channelvolume = volume; +} diff --git a/apps/codecs/dumb/src/it/itorder.c b/apps/codecs/dumb/src/it/itorder.c new file mode 100644 index 0000000000..6959f05443 --- /dev/null +++ b/apps/codecs/dumb/src/it/itorder.c @@ -0,0 +1,63 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * itorder.c - Code to fix invalid patterns in / / \ \ + * the pattern table. | < / \_ + * | \/ /\ / + * By Julien Cugniere. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + + + +#include + +#include "dumb.h" +#include "internal/it.h" + + + +/* This function ensures that any pattern mentioned in the order table but + * not present in the pattern table is treated as an empty 64 rows pattern. + * This is done by adding such a dummy pattern at the end of the pattern + * table, and redirect invalid orders to it. + * Patterns 254 and 255 are left untouched, unless the signal is an XM. + */ +int _dumb_it_fix_invalid_orders(DUMB_IT_SIGDATA *sigdata) +{ + int i; + int found_some = 0; + + int first_invalid = sigdata->n_patterns; + int last_invalid = (sigdata->flags & IT_WAS_AN_XM) ? 255 : 253; + + for (i = 0; i < sigdata->n_orders; i++) { + if (sigdata->order[i] >= first_invalid && sigdata->order[i] <= last_invalid) { + sigdata->order[i] = sigdata->n_patterns; + found_some = 1; + } + } + + if (found_some) { + IT_PATTERN *new_pattern = realloc(sigdata->pattern, sizeof(*sigdata->pattern) * (sigdata->n_patterns + 1)); + if (!new_pattern) + return -1; + + new_pattern[sigdata->n_patterns].n_rows = 64; + new_pattern[sigdata->n_patterns].n_entries = 0; + new_pattern[sigdata->n_patterns].entry = NULL; + sigdata->pattern = new_pattern; + sigdata->n_patterns++; + } + + return 0; +} diff --git a/apps/codecs/dumb/src/it/itread.c b/apps/codecs/dumb/src/it/itread.c new file mode 100644 index 0000000000..7384b30a92 --- /dev/null +++ b/apps/codecs/dumb/src/it/itread.c @@ -0,0 +1,1181 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * itread.c - Code to read an Impulse Tracker / / \ \ + * module from an open file. | < / \_ + * | \/ /\ / + * Based on the loader from an IT player by Bob. \_ / > / + * Adapted for DUMB by entheh. | \ / / + * | ' / + * \__/ + */ + +#include +#include //might not be necessary later; required for memset + +#include "dumb.h" +#include "internal/it.h" + + + +#define INVESTIGATE_OLD_INSTRUMENTS + + + +static int it_seek(DUMBFILE *f, long offset) +{ + long pos = dumbfile_pos(f); + + if (pos > offset) + return -1; + + if (pos < offset) + if (dumbfile_skip(f, offset - pos)) + return -1; + + return 0; +} + + + +typedef unsigned char byte; +typedef unsigned short word; +typedef unsigned long dword; + + + +static unsigned char *sourcebuf = NULL; +static unsigned char *sourcepos = NULL; +static unsigned char *sourceend; +static int rembits = 0; + + + +static int readblock(DUMBFILE *f) +{ + long size; + int c; + + size = dumbfile_igetw(f); + if (size < 0) + return size; + + sourcebuf = malloc(size); + if (!sourcebuf) + return -1; + + c = dumbfile_getnc((char *)sourcebuf, size, f); + if (c < size) { + free(sourcebuf); + sourcebuf = NULL; + return -1; + } + + sourcepos = sourcebuf; + sourceend = sourcebuf + size; + rembits = 8; + return 0; +} + + + +static void freeblock(void) +{ + free(sourcebuf); + sourcebuf = NULL; +} + + + +static int readbits(int bitwidth) +{ + int val = 0; + int b = 0; + + if (sourcepos >= sourceend) return val; + + while (bitwidth > rembits) { + val |= *sourcepos++ << b; + if (sourcepos >= sourceend) return val; + b += rembits; + bitwidth -= rembits; + rembits = 8; + } + + val |= (*sourcepos & ((1 << bitwidth) - 1)) << b; + *sourcepos >>= bitwidth; + rembits -= bitwidth; + + return val; +} + + + +/** WARNING - do we even need to pass `right`? */ +/** WARNING - why bother memsetting at all? The whole array is written... */ +// if we do memset, dumb_silence() would be neater... +static int decompress8(DUMBFILE *f, sample_t *left, sample_t *right, int len, int cmwt) +{ + int blocklen, blockpos; + byte bitwidth; + word val; + char d1, d2; + + memset(left, 0, len * sizeof(*left)); + if (right) { + memset(right, 0, len * sizeof(*right)); + len <<= 1; + } + + while (len > 0) { + //Read a block of compressed data: + if (readblock(f)) + return -1; + //Set up a few variables + blocklen = (len < 0x8000) ? len : 0x8000; //Max block length is 0x8000 bytes + blockpos = 0; + bitwidth = 9; + d1 = d2 = 0; + //Start the decompression: + while (blockpos < blocklen) { + //Read a value: + val = (word)readbits(bitwidth); + //Check for bit width change: + + if (bitwidth < 7) { //Method 1: + if (val == (1 << (bitwidth - 1))) { + val = (word)readbits(3) + 1; + bitwidth = (val < bitwidth) ? val : val + 1; + continue; + } + } + else if (bitwidth < 9) { //Method 2 + byte border = (0xFF >> (9 - bitwidth)) - 4; + + if (val > border && val <= (border + 8)) { + val -= border; + bitwidth = (val < bitwidth) ? val : val + 1; + continue; + } + } + else if (bitwidth == 9) { //Method 3 + if (val & 0x100) { + bitwidth = (val + 1) & 0xFF; + continue; + } + } + else { //Illegal width, abort ? + freeblock(); + return -1; + } + + //Expand the value to signed byte: + { + char v; //The sample value: + if (bitwidth < 8) { + byte shift = 8 - bitwidth; + v = (val << shift); + v >>= shift; + } + else + v = (char)val; + + //And integrate the sample value + //(It always has to end with integration doesn't it ? ;-) + d1 += v; + d2 += d1; + } + + //Store ! + /* Version 2.15 was an unofficial version with hacked compression + * code. Yay, better compression :D + */ + if (right && (len & 1)) + *right++ = (int)(signed char)(cmwt == 0x215 ? d2 : d1) << 16; + else + *left++ = (int)(signed char)(cmwt == 0x215 ? d2 : d1) << 16; + len--; + blockpos++; + } + freeblock(); + } + return 0; +} + + + +static int decompress16(DUMBFILE *f, sample_t *left, sample_t *right, int len, int cmwt) +{ + int blocklen, blockpos; + byte bitwidth; + long val; + short d1, d2; + + memset(left, 0, len * sizeof(*left)); + if (right) { + memset(right, 0, len * sizeof(*right)); + len <<= 1; + } + + while (len > 0) { + //Read a block of compressed data: + if (readblock(f)) + return -1; + //Set up a few variables + blocklen = (len < 0x4000) ? len : 0x4000; // Max block length is 0x4000 bytes + blockpos = 0; + bitwidth = 17; + d1 = d2 = 0; + //Start the decompression: + while (blockpos < blocklen) { + val = readbits(bitwidth); + //Check for bit width change: + + if (bitwidth < 7) { //Method 1: + if (val == (1 << (bitwidth - 1))) { + val = readbits(4) + 1; + bitwidth = (val < bitwidth) ? val : val + 1; + continue; + } + } + else if (bitwidth < 17) { //Method 2 + word border = (0xFFFF >> (17 - bitwidth)) - 8; + + if (val > border && val <= (border + 16)) { + val -= border; + bitwidth = val < bitwidth ? val : val + 1; + continue; + } + } + else if (bitwidth == 17) { //Method 3 + if (val & 0x10000) { + bitwidth = (val + 1) & 0xFF; + continue; + } + } + else { //Illegal width, abort ? + freeblock(); + return -1; + } + + //Expand the value to signed byte: + { + short v; //The sample value: + if (bitwidth < 16) { + byte shift = 16 - bitwidth; + v = (short)(val << shift); + v >>= shift; + } + else + v = (short)val; + + //And integrate the sample value + //(It always has to end with integration doesn't it ? ;-) + d1 += v; + d2 += d1; + } + + //Store ! + /* Version 2.15 was an unofficial version with hacked compression + * code. Yay, better compression :D + */ + if (right && (len & 1)) + *right++ = (int)(signed short)(cmwt == 0x215 ? d2 : d1) << 8; + else + *left++ = (int)(signed short)(cmwt == 0x215 ? d2 : d1) << 8; + len--; + blockpos++; + } + freeblock(); + } + return 0; +} + + + +static int it_read_envelope(IT_ENVELOPE *envelope, DUMBFILE *f) +{ + int n; + + envelope->flags = dumbfile_getc(f); + envelope->n_nodes = dumbfile_getc(f); + envelope->loop_start = dumbfile_getc(f); + envelope->loop_end = dumbfile_getc(f); + envelope->sus_loop_start = dumbfile_getc(f); + envelope->sus_loop_end = dumbfile_getc(f); + for (n = 0; n < envelope->n_nodes; n++) { + envelope->node_y[n] = dumbfile_getc(f); + envelope->node_t[n] = dumbfile_igetw(f); + } + dumbfile_skip(f, 75 - envelope->n_nodes * 3 + 1); + + return dumbfile_error(f); +} + + + +static int it_read_old_instrument(IT_INSTRUMENT *instrument, DUMBFILE *f) +{ + int n; + + if (dumbfile_mgetl(f) != IT_INSTRUMENT_SIGNATURE) + return -1; + + /* Skip DOS Filename. */ + dumbfile_skip(f, 13); + + instrument->volume_envelope.flags = dumbfile_getc(f); + instrument->volume_envelope.loop_start = dumbfile_getc(f); + instrument->volume_envelope.loop_end = dumbfile_getc(f); + instrument->volume_envelope.sus_loop_start = dumbfile_getc(f); + instrument->volume_envelope.sus_loop_end = dumbfile_getc(f); + + /* Skip two unused bytes. */ + dumbfile_skip(f, 2); + + /* In the old instrument format, fadeout ranges from 0 to 64, and is + * subtracted at intervals from a value starting at 512. In the new + * format, all these values are doubled. Therefore we double when loading + * from the old instrument format - that way we don't have to think about + * it later. + */ + instrument->fadeout = dumbfile_igetw(f) << 1; + instrument->new_note_action = dumbfile_getc(f); + instrument->dup_check_type = dumbfile_getc(f); + instrument->dup_check_action = DCA_NOTE_CUT; // This might be wrong! + /** WARNING - what is the duplicate check action for old-style instruments? */ + + /* Skip Tracker Version and Number of Samples. These are only used in + * separate instrument files. Also skip unused bytes and Instrument Name. + */ + dumbfile_skip(f, 36); + + instrument->pp_separation = 0; + instrument->pp_centre = 60; + instrument->global_volume = 128; + /** WARNING - should global_volume be 64 or something? */ + instrument->default_pan = 32; + /** WARNING - should default_pan be 128, meaning don`t use? */ + instrument->random_volume = 0; + instrument->random_pan = 0; + + for (n = 0; n < 120; n++) { + instrument->map_note[n] = dumbfile_getc(f); + instrument->map_sample[n] = dumbfile_getc(f); + } + + /* Skip "Volume envelope (200 bytes)". */ + // - need to know better what this is for though. + dumbfile_skip(f, 200); + +#if defined(INVESTIGATE_OLD_INSTRUMENTS) && 0 + fprintf(stderr, "Inst %02d Env:", n); +#endif + + for (n = 0; n < 25; n++) + { + instrument->volume_envelope.node_t[n] = dumbfile_getc(f); + instrument->volume_envelope.node_y[n] = dumbfile_getc(f); + +#if defined(INVESTIGATE_OLD_INSTRUMENTS) && 0 + fprintf(stderr, " %d,%d", + instrument->volume_envelope.node_t[n], + instrument->volume_envelope.node_y[n]); +#endif + + // This loop is unfinished, as we can probably escape from it before + // the end if we want to. Hence the otherwise useless dumbfile_skip() + // call below. + } + dumbfile_skip(f, 50 - (n << 1)); + instrument->volume_envelope.n_nodes = n; + +#if defined(INVESTIGATE_OLD_INSTRUMENTS) && 0 + fprintf(stderr, "\n"); +#endif + + if (dumbfile_error(f)) + return -1; + + instrument->filter_cutoff = 127; + instrument->filter_resonance = 0; + + instrument->pan_envelope.flags = 0; + instrument->pitch_envelope.flags = 0; + + return 0; +} + + + +static int it_read_instrument(IT_INSTRUMENT *instrument, DUMBFILE *f) +{ + int n; + + if (dumbfile_mgetl(f) != IT_INSTRUMENT_SIGNATURE) + return -1; + + /* Skip DOS Filename. */ + dumbfile_skip(f, 13); + + instrument->new_note_action = dumbfile_getc(f); + instrument->dup_check_type = dumbfile_getc(f); + instrument->dup_check_action = dumbfile_getc(f); + instrument->fadeout = dumbfile_igetw(f); + instrument->pp_separation = dumbfile_getc(f); + instrument->pp_centre = dumbfile_getc(f); + instrument->global_volume = dumbfile_getc(f); + instrument->default_pan = dumbfile_getc(f); + instrument->random_volume = dumbfile_getc(f); + instrument->random_pan = dumbfile_getc(f); + + /* Skip Tracker Version and Number of Samples. These are only used in + * separate instrument files. Also skip unused byte and Instrument Name. + */ + dumbfile_skip(f, 30); + + instrument->filter_cutoff = dumbfile_getc(f); + instrument->filter_resonance = dumbfile_getc(f); + + /* Skip MIDI Channel, Program and Bank. */ + dumbfile_skip(f, 4); + + for (n = 0; n < 120; n++) { + instrument->map_note[n] = dumbfile_getc(f); + instrument->map_sample[n] = dumbfile_getc(f); + } + + if (dumbfile_error(f)) + return -1; + + if (it_read_envelope(&instrument->volume_envelope, f)) return -1; + if (it_read_envelope(&instrument->pan_envelope, f)) return -1; + if (it_read_envelope(&instrument->pitch_envelope, f)) return -1; + + return 0; +} + + + +static int it_read_sample_header(IT_SAMPLE *sample, unsigned char *convert, long *offset, DUMBFILE *f) +{ + if (dumbfile_mgetl(f) != IT_SAMPLE_SIGNATURE) + return -1; + + /* Skip DOS Filename. */ + dumbfile_skip(f, 13); + + sample->global_volume = dumbfile_getc(f); + sample->flags = dumbfile_getc(f); + sample->default_volume = dumbfile_getc(f); + + /* Skip Sample Name. */ + dumbfile_skip(f, 26); + + *convert = dumbfile_getc(f); + sample->default_pan = dumbfile_getc(f); + sample->length = dumbfile_igetl(f); + sample->loop_start = dumbfile_igetl(f); + sample->loop_end = dumbfile_igetl(f); + sample->C5_speed = dumbfile_igetl(f); + sample->sus_loop_start = dumbfile_igetl(f); + sample->sus_loop_end = dumbfile_igetl(f); + +#ifdef STEREO_SAMPLES_COUNT_AS_TWO + if (sample->flags & IT_SAMPLE_STEREO) { + sample->length >>= 1; + sample->loop_start >>= 1; + sample->loop_end >>= 1; + sample->C5_speed >>= 1; + sample->sus_loop_start >>= 1; + sample->sus_loop_end >>= 1; + } +#endif + + if (sample->flags & IT_SAMPLE_EXISTS) { + if (sample->length <= 0) + sample->flags &= ~IT_SAMPLE_EXISTS; + else { + if ((unsigned int)sample->loop_end > (unsigned int)sample->length) + sample->flags &= ~IT_SAMPLE_LOOP; + else if ((unsigned int)sample->loop_start >= (unsigned int)sample->loop_end) + sample->flags &= ~IT_SAMPLE_LOOP; + + if ((unsigned int)sample->sus_loop_end > (unsigned int)sample->length) + sample->flags &= ~IT_SAMPLE_SUS_LOOP; + else if ((unsigned int)sample->sus_loop_start >= (unsigned int)sample->sus_loop_end) + sample->flags &= ~IT_SAMPLE_SUS_LOOP; + + /* We may be able to truncate the sample to save memory. */ + if (sample->flags & IT_SAMPLE_LOOP) { + if ((sample->flags & IT_SAMPLE_SUS_LOOP) && sample->sus_loop_end >= sample->loop_end) + sample->length = sample->sus_loop_end; + else + sample->length = sample->loop_end; + } + } + } + + *offset = dumbfile_igetl(f); + + sample->vibrato_speed = dumbfile_getc(f); + sample->vibrato_depth = dumbfile_getc(f); + sample->vibrato_rate = dumbfile_getc(f); + sample->vibrato_waveform = dumbfile_getc(f); + + return dumbfile_error(f); +} + + + +static long it_read_sample_data(int cmwt, IT_SAMPLE *sample, unsigned char convert, DUMBFILE *f) +{ + long n; + + sample->left = malloc(sample->length * sizeof(*sample->left)); + if (!sample->left) + return -1; + + if (sample->flags & IT_SAMPLE_STEREO) { + sample->right = malloc(sample->length * sizeof(*sample->right)); + if (!sample->right) + return -1; + } + + if (sample->flags & 8) { + /* If the sample is packed, then we must unpack it. */ + + /** WARNING - unresolved business here... test with ModPlug? */ + + if (sample->flags & IT_SAMPLE_STEREO) + exit(37); + +/* +//#ifndef STEREO_SAMPLES_COUNT_AS_TWO + ASSERT(!(sample->flags & IT_SAMPLE_STEREO)); +//#endif +*/ + if (sample->flags & IT_SAMPLE_16BIT) + decompress16(f, sample->left, sample->right, sample->length, cmwt); + else + decompress8(f, sample->left, sample->right, sample->length, cmwt); + } else if (sample->flags & IT_SAMPLE_STEREO) { + if (sample->flags & IT_SAMPLE_16BIT) { + if (convert & 2) { + for (n = 0; n < sample->length; n++) { + sample->left[n] = (int)(signed short)dumbfile_mgetw(f) << 8; + sample->right[n] = (int)(signed short)dumbfile_mgetw(f) << 8; + } + } else { + for (n = 0; n < sample->length; n++) { + sample->left[n] = (int)(signed short)dumbfile_igetw(f) << 8; + sample->right[n] = (int)(signed short)dumbfile_igetw(f) << 8; + } + } + } else { + for (n = 0; n < sample->length; n++) { + sample->left[n] = (int)(signed char)dumbfile_getc(f) << 16; + sample->right[n] = (int)(signed char)dumbfile_getc(f) << 16; + } + } + } else if (sample->flags & IT_SAMPLE_16BIT) { + if (convert & 2) + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed short)dumbfile_mgetw(f) << 8; + else + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed short)dumbfile_igetw(f) << 8; + } else + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed char)dumbfile_getc(f) << 16; + + if (dumbfile_error(f)) + return -1; + + if (!(convert & 1)) { + /* Convert to signed. */ + for (n = 0; n < sample->length; n++) + sample->left[n] ^= 0xFF800000; + + if (sample->right) + for (n = 0; n < sample->length; n++) + sample->right[n] ^= 0xFF800000; + } + + /* NOT SUPPORTED: + * + * convert & 4 - Samples stored as delta values + * convert & 16 - Samples stored as TX-Wave 12-bit values + * convert & 32 - Left/Right/All Stereo prompt + */ + + return 0; +} + + + +#define DETECT_DUPLICATE_CHANNELS +#ifdef DETECT_DUPLICATE_CHANNELS +#include +#endif +static int it_read_pattern(IT_PATTERN *pattern, DUMBFILE *f, unsigned char *buffer) +{ + unsigned char cmask[DUMB_IT_N_CHANNELS]; + unsigned char cnote[DUMB_IT_N_CHANNELS]; + unsigned char cinstrument[DUMB_IT_N_CHANNELS]; + unsigned char cvolpan[DUMB_IT_N_CHANNELS]; + unsigned char ceffect[DUMB_IT_N_CHANNELS]; + unsigned char ceffectvalue[DUMB_IT_N_CHANNELS]; +#ifdef DETECT_DUPLICATE_CHANNELS + IT_ENTRY *dupentry[DUMB_IT_N_CHANNELS]; +#endif + + int n_entries = 0; + int buflen; + int bufpos = 0; + + IT_ENTRY *entry; + + unsigned char channel; + unsigned char mask; + + memset(cmask, 0, sizeof(cmask)); + memset(cnote, 0, sizeof(cnote)); + memset(cinstrument, 0, sizeof(cinstrument)); + memset(cvolpan, 0, sizeof(cvolpan)); + memset(ceffect, 0, sizeof(ceffect)); + memset(ceffectvalue, 0, sizeof(ceffectvalue)); +#ifdef DETECT_DUPLICATE_CHANNELS + { + int i; + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) dupentry[i] = NULL; + } +#endif + + buflen = dumbfile_igetw(f); + pattern->n_rows = dumbfile_igetw(f); + + /* Skip four unused bytes. */ + dumbfile_skip(f, 4); + + if (dumbfile_error(f)) + return -1; + + /* Read in the pattern data. */ + dumbfile_getnc(buffer, buflen, f); + + if (dumbfile_error(f)) + return -1; + + /* Scan the pattern data, and work out how many entries we need room for. */ + while (bufpos < buflen) { + unsigned char b = buffer[bufpos++]; + + if (b == 0) { + /* End of row */ + n_entries++; + continue; + } + + channel = (b - 1) & 63; + + if (b & 128) + cmask[channel] = mask = buffer[bufpos++]; + else + mask = cmask[channel]; + + { + static const unsigned char used[16] = {0, 1, 1, 2, 1, 2, 2, 3, 2, 3, 3, 4, 3, 4, 4, 5}; + n_entries += (mask != 0); + bufpos += used[mask & 15]; + } + } + + pattern->n_entries = n_entries; + + pattern->entry = malloc(n_entries * sizeof(*pattern->entry)); + + if (!pattern->entry) + return -1; + + bufpos = 0; + memset(cmask, 0, sizeof(cmask)); + + entry = pattern->entry; + + while (bufpos < buflen) { + unsigned char b = buffer[bufpos++]; + + if (b == 0) { + /* End of row */ + IT_SET_END_ROW(entry); + entry++; +#ifdef DETECT_DUPLICATE_CHANNELS + { + int i; + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) dupentry[i] = NULL; + } +#endif + continue; + } + + channel = (b - 1) & 63; + + if (b & 128) + cmask[channel] = mask = buffer[bufpos++]; + else + mask = cmask[channel]; + + if (mask) { + entry->mask = (mask & 15) | (mask >> 4); + entry->channel = channel; + + if (mask & IT_ENTRY_NOTE) + cnote[channel] = entry->note = buffer[bufpos++]; + else if (mask & (IT_ENTRY_NOTE << 4)) + entry->note = cnote[channel]; + + if (mask & IT_ENTRY_INSTRUMENT) + cinstrument[channel] = entry->instrument = buffer[bufpos++]; + else if (mask & (IT_ENTRY_INSTRUMENT << 4)) + entry->instrument = cinstrument[channel]; + + if (mask & IT_ENTRY_VOLPAN) + cvolpan[channel] = entry->volpan = buffer[bufpos++]; + else if (mask & (IT_ENTRY_VOLPAN << 4)) + entry->volpan = cvolpan[channel]; + + if (mask & IT_ENTRY_EFFECT) { + ceffect[channel] = entry->effect = buffer[bufpos++]; + ceffectvalue[channel] = entry->effectvalue = buffer[bufpos++]; + } else { + entry->effect = ceffect[channel]; + entry->effectvalue = ceffectvalue[channel]; + } + +#if defined( DETECT_DUPLICATE_CHANNELS) && 0 + if (dupentry[channel]) { + FILE *f = fopen("dupentry.txt", "a"); + if (!f) abort(); + fprintf(f, "Two events on channel %d:", channel); + fprintf(f, " Event #1:"); + if (dupentry[channel]->mask & IT_ENTRY_NOTE ) fprintf(f, " %03d", dupentry[channel]->note ); else fprintf(f, " ..."); + if (dupentry[channel]->mask & IT_ENTRY_INSTRUMENT) fprintf(f, " %03d", dupentry[channel]->instrument); else fprintf(f, " ..."); + if (dupentry[channel]->mask & IT_ENTRY_VOLPAN ) fprintf(f, " %03d", dupentry[channel]->volpan ); else fprintf(f, " ..."); + if (dupentry[channel]->mask & IT_ENTRY_EFFECT) fprintf(f, " %c%02X\n", 'A' - 1 + dupentry[channel]->effect, dupentry[channel]->effectvalue); else fprintf(f, " ...\n"); + fprintf(f, " Event #2:"); + if (entry->mask & IT_ENTRY_NOTE ) fprintf(f, " %03d", entry->note ); else fprintf(f, " ..."); + if (entry->mask & IT_ENTRY_INSTRUMENT) fprintf(f, " %03d", entry->instrument); else fprintf(f, " ..."); + if (entry->mask & IT_ENTRY_VOLPAN ) fprintf(f, " %03d", entry->volpan ); else fprintf(f, " ..."); + if (entry->mask & IT_ENTRY_EFFECT) fprintf(f, " %c%02X\n", 'A' - 1 + entry->effect, entry->effectvalue); else fprintf(f, " ...\n"); + fclose(f); + } + dupentry[channel] = entry; +#endif + + entry++; + } + } + + ASSERT(entry == pattern->entry + n_entries); + + return 0; +} + + + +/* Currently we assume the sample data are stored after the sample headers in + * module files. This assumption may be unjustified; let me know if you have + * trouble. + */ + +#define IT_COMPONENT_INSTRUMENT 1 +#define IT_COMPONENT_PATTERN 2 +#define IT_COMPONENT_SAMPLE 3 + +typedef struct IT_COMPONENT +{ + unsigned char type; + unsigned char n; + long offset; + short sampfirst; /* component[sampfirst] = first sample data after this */ + short sampnext; /* sampnext is used to create linked lists of sample data */ +} +IT_COMPONENT; + + + +static int it_component_compare(const void *e1, const void *e2) +{ + return ((const IT_COMPONENT *)e1)->offset - + ((const IT_COMPONENT *)e2)->offset; +} + + + +static sigdata_t *it_load_sigdata(DUMBFILE *f) +{ + DUMB_IT_SIGDATA *sigdata; + + int cwt, cmwt; + int special; + + IT_COMPONENT *component; + int n_components = 0; + + unsigned char sample_convert[256]; + + int n; + + unsigned char *buffer; + + if (dumbfile_mgetl(f) != IT_SIGNATURE) + return NULL; + + sigdata = malloc(sizeof(*sigdata)); + + if (!sigdata) + return NULL; + + sigdata->order = NULL; + sigdata->instrument = NULL; + sigdata->sample = NULL; + sigdata->pattern = NULL; + sigdata->midi = NULL; + sigdata->checkpoint = NULL; + + /* Skip song name and pattern row highlight info. */ + dumbfile_skip(f, 28); + + sigdata->n_orders = dumbfile_igetw(f); + sigdata->n_instruments = dumbfile_igetw(f); + sigdata->n_samples = dumbfile_igetw(f); + sigdata->n_patterns = dumbfile_igetw(f); + + cwt = dumbfile_igetw(f); + cmwt = dumbfile_igetw(f); + + sigdata->flags = dumbfile_igetw(f); + special = dumbfile_igetw(f); + + sigdata->global_volume = dumbfile_getc(f); + sigdata->mixing_volume = dumbfile_getc(f); + sigdata->speed = dumbfile_getc(f); + if (sigdata->speed == 0) sigdata->speed = 6; // Should we? What about tempo? + sigdata->tempo = dumbfile_getc(f); + sigdata->pan_separation = dumbfile_getc(f); /** WARNING: use this */ + + /* Skip Pitch Wheel Depth, Message Length, Message Offset and Reserved. */ + dumbfile_skip(f, 11); + + dumbfile_getnc(sigdata->channel_pan, DUMB_IT_N_CHANNELS, f); + dumbfile_getnc(sigdata->channel_volume, DUMB_IT_N_CHANNELS, f); + + if (dumbfile_error(f) || sigdata->n_orders <= 0 || sigdata->n_instruments > 256 || sigdata->n_samples > 256 || sigdata->n_patterns > 256) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + sigdata->order = malloc(sigdata->n_orders); + if (!sigdata->order) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (sigdata->n_instruments) { + sigdata->instrument = malloc(sigdata->n_instruments * sizeof(*sigdata->instrument)); + if (!sigdata->instrument) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + } + + if (sigdata->n_samples) { + sigdata->sample = malloc(sigdata->n_samples * sizeof(*sigdata->sample)); + if (!sigdata->sample) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (n = 0; n < sigdata->n_samples; n++) + sigdata->sample[n].right = sigdata->sample[n].left = NULL; + } + + if (sigdata->n_patterns) { + sigdata->pattern = malloc(sigdata->n_patterns * sizeof(*sigdata->pattern)); + if (!sigdata->pattern) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (n = 0; n < sigdata->n_patterns; n++) + sigdata->pattern[n].entry = NULL; + } + + dumbfile_getnc(sigdata->order, sigdata->n_orders, f); + sigdata->restart_position = 0; + + component = malloc(768 * sizeof(*component)); + if (!component) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + for (n = 0; n < sigdata->n_instruments; n++) { + component[n_components].type = IT_COMPONENT_INSTRUMENT; + component[n_components].n = n; + component[n_components].offset = dumbfile_igetl(f); + component[n_components].sampfirst = -1; + n_components++; + } + + for (n = 0; n < sigdata->n_samples; n++) { + component[n_components].type = IT_COMPONENT_SAMPLE; + component[n_components].n = n; + component[n_components].offset = dumbfile_igetl(f); + component[n_components].sampfirst = -1; + n_components++; + } + + for (n = 0; n < sigdata->n_patterns; n++) { + long offset = dumbfile_igetl(f); + if (offset) { + component[n_components].type = IT_COMPONENT_PATTERN; + component[n_components].n = n; + component[n_components].offset = offset; + component[n_components].sampfirst = -1; + n_components++; + } else { + /* Empty 64-row pattern */ + sigdata->pattern[n].n_rows = 64; + sigdata->pattern[n].n_entries = 0; + } + } + + if (dumbfile_error(f)) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (!(sigdata->flags & 128) != !(special & 8)) { +#if 0 + fprintf(stderr, "Flags Bit 7 (\"Request embedded MIDI configuration\"): %s\n", sigdata->flags & 128 ? "=SET=" : "clear"); + fprintf(stderr, "Special Bit 3 (\"MIDI configuration embedded\") : %s\n", special & 8 ? "=SET=" : "clear"); + fprintf(stderr, "entheh would like to investigate this IT file.\n"); + fprintf(stderr, "Please contact him! entheh@users.sf.net\n"); +#endif + } + + if (special & 8) { + /* MIDI configuration is embedded. */ + unsigned char mididata[32]; + int i; + sigdata->midi = malloc(sizeof(*sigdata->midi)); + if (!sigdata->midi) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + // Should we be happy with this outcome in some situations? + } + // What are we skipping? + i = dumbfile_igetw(f); + if (dumbfile_error(f) || dumbfile_skip(f, 8*i)) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + /* Read embedded MIDI configuration */ + // What are the first 9 commands for? + if (dumbfile_skip(f, 32*9)) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (i = 0; i < 16; i++) { + unsigned char len = 0; + int j, leftdigit = -1; + if (dumbfile_getnc(mididata, 32, f) < 32) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + sigdata->midi->SFmacroz[i] = 0; + for (j = 0; j < 32; j++) { + if (leftdigit >= 0) { + if (mididata[j] == 0) { + sigdata->midi->SFmacro[i][len++] = leftdigit; + break; + } else if (mididata[j] == ' ') + sigdata->midi->SFmacro[i][len++] = leftdigit; + else if (mididata[j] >= '0' && mididata[j] <= '9') + sigdata->midi->SFmacro[i][len++] = (leftdigit << 4) | (mididata[j] - '0'); + else if (mididata[j] >= 'A' && mididata[j] <= 'F') + sigdata->midi->SFmacro[i][len++] = (leftdigit << 4) | (mididata[j] - 'A' + 0xA); + leftdigit = -1; + } else if (mididata[j] == 0) + break; + else if (mididata[j] == 'z') + sigdata->midi->SFmacroz[i] |= 1 << len++; + else if (mididata[j] >= '0' && mididata[j] <= '9') + leftdigit = mididata[j] - '0'; + else if (mididata[j] >= 'A' && mididata[j] <= 'F') + leftdigit = mididata[j] - 'A' + 0xA; + } + sigdata->midi->SFmacrolen[i] = len; + } + for (i = 0; i < 128; i++) { + unsigned char len = 0; + int j, leftdigit = -1; + dumbfile_getnc(mididata, 32, f); + for (j = 0; j < 32; j++) { + if (leftdigit >= 0) { + if (mididata[j] == 0) { + sigdata->midi->Zmacro[i][len++] = leftdigit; + break; + } else if (mididata[j] == ' ') + sigdata->midi->Zmacro[i][len++] = leftdigit; + else if (mididata[j] >= '0' && mididata[j] <= '9') + sigdata->midi->Zmacro[i][len++] = (leftdigit << 4) | (mididata[j] - '0'); + else if (mididata[j] >= 'A' && mididata[j] <= 'F') + sigdata->midi->Zmacro[i][len++] = (leftdigit << 4) | (mididata[j] - 'A' + 0xA); + leftdigit = -1; + } else if (mididata[j] == 0) + break; + else if (mididata[j] >= '0' && mididata[j] <= '9') + leftdigit = mididata[j] - '0'; + else if (mididata[j] >= 'A' && mididata[j] <= 'F') + leftdigit = mididata[j] - 'A' + 0xA; + } + sigdata->midi->Zmacrolen[i] = len; + } + } + + sigdata->flags &= IT_REAL_FLAGS; + + qsort(component, n_components, sizeof(IT_COMPONENT), &it_component_compare); + + buffer = malloc(65536); + if (!buffer) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + for (n = 0; n < n_components; n++) { + long offset; + int m; + + if (it_seek(f, component[n].offset)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + switch (component[n].type) { + + case IT_COMPONENT_INSTRUMENT: + if (cmwt < 0x200) + m = it_read_old_instrument(&sigdata->instrument[component[n].n], f); + else + m = it_read_instrument(&sigdata->instrument[component[n].n], f); + + if (m) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + break; + + case IT_COMPONENT_PATTERN: + if (it_read_pattern(&sigdata->pattern[component[n].n], f, buffer)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + break; + + case IT_COMPONENT_SAMPLE: + if (it_read_sample_header(&sigdata->sample[component[n].n], &sample_convert[component[n].n], &offset, f)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (sigdata->sample[component[n].n].flags & IT_SAMPLE_EXISTS) { + short *sample; + + for (m = n + 1; m < n_components; m++) + if (component[m].offset > offset) + break; + m--; + + sample = &component[m].sampfirst; + + while (*sample >= 0 && component[*sample].offset <= offset) + sample = &component[*sample].sampnext; + + component[n].sampnext = *sample; + *sample = n; + + component[n].offset = offset; + } + } + + m = component[n].sampfirst; + + while (m >= 0) { + if (it_seek(f, component[m].offset)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (it_read_sample_data(cmwt, &sigdata->sample[component[m].n], sample_convert[component[m].n], f)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + m = component[m].sampnext; + } + } + + free(buffer); + free(component); + + _dumb_it_fix_invalid_orders(sigdata); + + return sigdata; +} + + + +DUH *dumb_read_it(DUMBFILE *f) +{ + sigdata_t *sigdata; + long length; + + DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it; + + sigdata = it_load_sigdata(f); + + if (!sigdata) + return NULL; + + length = _dumb_it_build_checkpoints(sigdata); + + return make_duh(length, 1, &descptr, &sigdata); +} + diff --git a/apps/codecs/dumb/src/it/itrender.c b/apps/codecs/dumb/src/it/itrender.c new file mode 100644 index 0000000000..103654fd77 --- /dev/null +++ b/apps/codecs/dumb/src/it/itrender.c @@ -0,0 +1,3512 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * itrender.c - Code to render an Impulse Tracker / / \ \ + * module. | < / \_ + * | \/ /\ / + * Written - painstakingly - by entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include + +#include "dumb.h" +#include "internal/it.h" + + + +static IT_PLAYING *dup_playing(IT_PLAYING *src, IT_CHANNEL *dstchannel, IT_CHANNEL *srcchannel) +{ + IT_PLAYING *dst; + + if (!src) return NULL; + + dst = malloc(sizeof(*dst)); + if (!dst) return NULL; + + dst->flags = src->flags; + + ASSERT(src->channel); + dst->channel = &dstchannel[src->channel - srcchannel]; + dst->sample = src->sample; + dst->instrument = src->instrument; + dst->env_instrument = src->env_instrument; + + dst->sampnum = src->sampnum; + dst->instnum = src->instnum; + + dst->channel_volume = src->channel_volume; + + dst->volume = src->volume; + dst->pan = src->pan; + + dst->note = src->note; + + dst->filter_cutoff = src->filter_cutoff; + dst->filter_resonance = src->filter_resonance; + + dst->true_filter_cutoff = src->true_filter_cutoff; + dst->true_filter_resonance = src->true_filter_resonance; + + dst->vibrato_speed = src->vibrato_speed; + dst->vibrato_depth = src->vibrato_depth; + dst->vibrato_n = src->vibrato_n; + dst->vibrato_time = src->vibrato_time; + + dst->tremolo_speed = src->tremolo_speed; + dst->tremolo_depth = src->tremolo_depth; + dst->tremolo_time = src->tremolo_time; + + dst->sample_vibrato_time = src->sample_vibrato_time; + dst->sample_vibrato_depth = src->sample_vibrato_depth; + + dst->slide = src->slide; + dst->delta = src->delta; + + dst->volume_envelope = src->volume_envelope; + dst->pan_envelope = src->pan_envelope; + dst->pitch_envelope = src->pitch_envelope; + + dst->fadeoutcount = src->fadeoutcount; + + dst->filter_state[0] = src->filter_state[0]; + dst->filter_state[1] = src->filter_state[1]; + + dst->resampler[0] = src->resampler[0]; + dst->resampler[1] = src->resampler[1]; + dst->resampler[1].pickup_data = dst->resampler[0].pickup_data = dst; + dst->time_lost = src->time_lost; + + return dst; +} + + + +static void dup_channel(IT_CHANNEL *dst, IT_CHANNEL *src) +{ + dst->flags = src->flags; + + dst->volume = src->volume; + dst->volslide = src->volslide; + dst->xm_volslide = src->xm_volslide; + + dst->pan = src->pan; + dst->truepan = src->truepan; + + dst->channelvolume = src->channelvolume; + dst->channelvolslide = src->channelvolslide; + + dst->instrument = src->instrument; + dst->note = src->note; + + dst->SFmacro = src->SFmacro; + + dst->filter_cutoff = src->filter_cutoff; + dst->filter_resonance = src->filter_resonance; + + dst->note_cut_count = src->note_cut_count; + dst->note_delay_count = src->note_delay_count; + dst->note_delay_entry = src->note_delay_entry; + + dst->arpeggio = src->arpeggio; + dst->retrig = src->retrig; + dst->xm_retrig = src->xm_retrig; + dst->retrig_tick = src->retrig_tick; + + dst->tremor_time = src->tremor_time; + + dst->portamento = src->portamento; + dst->toneporta = src->toneporta; + dst->destnote = src->destnote; + + dst->sample = src->sample; + dst->truenote = src->truenote; + + dst->midi_state = src->midi_state; + + dst->lastvolslide = src->lastvolslide; + dst->lastDKL = src->lastDKL; + dst->lastEF = src->lastEF; + dst->lastG = src->lastG; + dst->lastHspeed = src->lastHspeed; + dst->lastHdepth = src->lastHdepth; + dst->lastRspeed = src->lastRspeed; + dst->lastRdepth = src->lastRdepth; + dst->lastI = src->lastI; + dst->lastJ = src->lastJ; + dst->lastN = src->lastN; + dst->lastO = src->lastO; + dst->high_offset = src->high_offset; + dst->lastQ = src->lastQ; + dst->lastS = src->lastS; + dst->pat_loop_row = src->pat_loop_row; + dst->pat_loop_count = src->pat_loop_count; + dst->lastW = src->lastW; + + dst->xm_lastE1 = src->xm_lastE1; + dst->xm_lastE2 = src->xm_lastE2; + dst->xm_lastEA = src->xm_lastEA; + dst->xm_lastEB = src->xm_lastEB; + dst->xm_lastX1 = src->xm_lastX1; + dst->xm_lastX2 = src->xm_lastX2; + + dst->playing = dup_playing(src->playing, dst, src); +} + + + +/* Allocate the new callbacks first, then pass them to this function! + * It will free them on failure. + */ +static DUMB_IT_SIGRENDERER *dup_sigrenderer(DUMB_IT_SIGRENDERER *src, int n_channels, IT_CALLBACKS *callbacks) +{ + DUMB_IT_SIGRENDERER *dst; + int i; + + if (!src) { + if (callbacks) free(callbacks); + return NULL; + } + + dst = malloc(sizeof(*dst)); + if (!dst) { + if (callbacks) free(callbacks); + return NULL; + } + + dst->sigdata = src->sigdata; + + dst->n_channels = n_channels; + + dst->globalvolume = src->globalvolume; + dst->globalvolslide = src->globalvolslide; + + dst->tempo = src->tempo; + dst->temposlide = src->temposlide; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) + dup_channel(&dst->channel[i], &src->channel[i]); + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) + dst->playing[i] = dup_playing(src->playing[i], dst->channel, src->channel); + + dst->tick = src->tick; + dst->speed = src->speed; + dst->rowcount = src->rowcount; + + dst->order = src->order; + dst->row = src->row; + dst->processorder = src->processorder; + dst->processrow = src->processrow; + dst->breakrow = src->breakrow; + dst->pat_loop_row = src->pat_loop_row; + + dst->n_rows = src->n_rows; + + dst->entry_start = src->entry_start; + dst->entry = src->entry; + dst->entry_end = src->entry_end; + + dst->time_left = src->time_left; + dst->sub_time_left = src->sub_time_left; + + dst->click_remover = NULL; + + dst->callbacks = callbacks; + + return dst; +} + + + +static IT_MIDI default_midi = { + /* unsigned char SFmacro[16][16]; */ + { + {0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + }, + /* unsigned char SFmacrolen[16]; */ + {4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + /* unsigned short SFmacroz[16]; */ + /* Bitfield; bit 0 set = z in first position */ + { + 0x0008, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 + }, + /* unsigned char Zmacro[128][16]; */ + { + {0xF0, 0xF0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + }, + /* unsigned char Zmacrolen[128]; */ + { + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + } +}; + + + +static void it_reset_filter_state(IT_FILTER_STATE *state) +{ + state->currsample = 0; + state->prevsample = 0; +} + + + +#define LOG10 2.30258509299 + +/* IMPORTANT: This function expects one extra sample in 'src' so it can apply + * click removal. It reads size samples, starting from src[0], and writes its + * output starting at dst[pos]. The pos parameter is required for getting + * click removal right. + */ +static void it_filter(DUMB_CLICK_REMOVER *cr, IT_FILTER_STATE *state, sample_t *dst, long pos, sample_t *src, long size, int sampfreq, int cutoff, int resonance) +{ + float currsample = state->currsample; + float prevsample = state->prevsample; + + float a, b, c; + + { + float inv_angle = (float)(sampfreq * pow(0.5, 0.25 + cutoff*(1.0/(24< 2.0f) d = 2.0f; + d = (loss - d) * inv_angle; + e = inv_angle * inv_angle; + a = 1.0f / (1.0f + d + e); + c = -e * a; + b = 1.0f - a - c; +#else + a = 1.0f / (inv_angle*inv_angle + inv_angle*loss + loss); + c = -(inv_angle*inv_angle) * a; + b = 1.0f - a - c; +#endif + } + + dst += pos; + + if (cr) { + float startstep = src[0]*a + currsample*b + prevsample*c; + dumb_record_click(cr, pos, (sample_t)startstep); + } + +#define INT_FILTERS +#ifdef INT_FILTERS +#define MULSCA(a, b) ((int)((LONG_LONG)((a) << 4) * (b) >> 32)) +#define SCALEB 12 + { + int ai = (int)(a * (1 << (16+SCALEB))); + int bi = (int)(b * (1 << (16+SCALEB))); + int ci = (int)(c * (1 << (16+SCALEB))); + sample_t csi = (sample_t)currsample; + sample_t psi = (sample_t)prevsample; + sample_t *dst_end = dst + size; + while (dst < dst_end) { + { + sample_t nsi = MULSCA(*src++, ai) + MULSCA(csi, bi) + MULSCA(psi, ci); + psi = csi; + csi = nsi; + } + *dst++ += csi; + } + currsample = csi; + prevsample = psi; + } +#else + { + int i = size % 3; + while (i > 0) { + { + float newsample = *src++*a + currsample*b + prevsample*c; + prevsample = currsample; + currsample = newsample; + } + *dst++ += (sample_t)currsample; + i--; + } + i = size / 3; + while (i > 0) { + float newsample; + /* Gotta love unrolled loops! */ + *dst++ += (sample_t)(newsample = *src++*a + currsample*b + prevsample*c); + *dst++ += (sample_t)(prevsample = *src++*a + newsample*b + currsample*c); + *dst++ += (sample_t)(currsample = *src++*a + prevsample*b + newsample*c); + i--; + } + } +#endif + + if (cr) { + float endstep = *src*a + currsample*b + prevsample*c; + dumb_record_click(cr, pos + size, -(sample_t)endstep); + } + + state->currsample = currsample; + state->prevsample = prevsample; +} + +#undef LOG10 + + + +static signed char it_sine[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 +}; + + + +#if 0 +/** WARNING: use these! */ +/** JULIEN: Plus for XM compatibility it could be interesting to rename + * it_sawtooth[] to it_rampdown[], and add an it_rampup[]. + * Also, still for XM compat', twood be good if it was possible to tell the + * the player not to retrig' the waveform on a new instrument. + * Both of these are only for completness though, as I don't think it would + * be very noticeable ;) + */ +/** ENTHEH: IT also has the 'don't retrig' thingy :) */ + +static signed char it_sawtooth[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 +}; + + + +static signed char it_squarewave[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 +}; + +#endif + + + +static void reset_tick_counts(DUMB_IT_SIGRENDERER *sigrenderer) +{ + int i; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; + channel->note_cut_count = 0; + channel->note_delay_count = 0; + } +} + + + +static void reset_effects(DUMB_IT_SIGRENDERER *sigrenderer) +{ + int i; + + sigrenderer->globalvolslide = 0; + sigrenderer->temposlide = 0; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; + channel->volslide = 0; + channel->xm_volslide = 0; + channel->channelvolslide = 0; + channel->arpeggio = 0; + channel->retrig = 0; + if (channel->xm_retrig) { + channel->xm_retrig = 0; + channel->retrig_tick = 0; + } + channel->tremor_time &= 127; + channel->portamento = 0; + channel->toneporta = 0; + if (channel->playing) { + channel->playing->vibrato_n = 0; + channel->playing->tremolo_speed = 0; + channel->playing->tremolo_depth = 0; + } + } +} + + + +static void update_tremor(IT_CHANNEL *channel) +{ + if ((channel->tremor_time & 128) && channel->playing) { + if (channel->tremor_time == 128) + channel->tremor_time = (channel->lastI >> 4) | 192; + else if (channel->tremor_time == 192) + channel->tremor_time = (channel->lastI & 15) | 128; + else + channel->tremor_time--; + } +} + + + +static void it_pickup_loop(DUMB_RESAMPLER *resampler, void *data) +{ + resampler->pos -= resampler->end - resampler->start; + ((IT_PLAYING *)data)->time_lost += resampler->end - resampler->start; +} + + + +static void it_pickup_pingpong_loop(DUMB_RESAMPLER *resampler, void *data) +{ + if (resampler->dir < 0) { + resampler->pos = (resampler->start << 1) - 1 - resampler->pos; + resampler->subpos ^= 65535; + resampler->dir = 1; + ((IT_PLAYING *)data)->time_lost += (resampler->end - resampler->start) << 1; + } else { + resampler->pos = (resampler->end << 1) - 1 - resampler->pos; + resampler->subpos ^= 65535; + resampler->dir = -1; + } +} + + + +static void it_pickup_stop_at_end(DUMB_RESAMPLER *resampler, void *data) +{ + (void)data; + + if (resampler->dir < 0) { + resampler->pos = (resampler->start << 1) - 1 - resampler->pos; + resampler->subpos ^= 65535; + /* By rights, time_lost would be updated here. However, there is no + * need at this point; it will not be used. + * + * ((IT_PLAYING *)data)->time_lost += (resampler->src_end - resampler->src_start) << 1; + */ + resampler->dir = 1; + } else + resampler->dir = 0; +} + + + +static void it_playing_update_resamplers(IT_PLAYING *playing) +{ + if ((playing->sample->flags & IT_SAMPLE_SUS_LOOP) && !(playing->flags & IT_PLAYING_SUSTAINOFF)) { + playing->resampler[0].start = playing->sample->sus_loop_start; + playing->resampler[0].end = playing->sample->sus_loop_end; + if (playing->sample->flags & IT_SAMPLE_PINGPONG_SUS_LOOP) + playing->resampler[0].pickup = &it_pickup_pingpong_loop; + else + playing->resampler[0].pickup = &it_pickup_loop; + } else if (playing->sample->flags & IT_SAMPLE_LOOP) { + playing->resampler[0].start = playing->sample->loop_start; + playing->resampler[0].end = playing->sample->loop_end; + if (playing->sample->flags & IT_SAMPLE_PINGPONG_LOOP) + playing->resampler[0].pickup = &it_pickup_pingpong_loop; + else + playing->resampler[0].pickup = &it_pickup_loop; + } else { + if (playing->sample->flags & IT_SAMPLE_SUS_LOOP) + playing->resampler[0].start = playing->sample->sus_loop_start; + else + playing->resampler[0].start = 0; + playing->resampler[0].end = playing->sample->length; + playing->resampler[0].pickup = &it_pickup_stop_at_end; + } + playing->resampler[1].start = playing->resampler[0].start; + playing->resampler[1].end = playing->resampler[0].end; + playing->resampler[1].pickup = playing->resampler[0].pickup; + ASSERT(playing->resampler[0].pickup_data == playing); + ASSERT(playing->resampler[1].pickup_data == playing); +} + + + +/* This should be called whenever the sample or sample position changes. */ +static void it_playing_reset_resamplers(IT_PLAYING *playing, long pos) +{ + dumb_reset_resampler(&playing->resampler[0], playing->sample->left, pos, 0, 0); + dumb_reset_resampler(&playing->resampler[1], playing->sample->right, pos, 0, 0); + playing->resampler[1].pickup_data = playing->resampler[0].pickup_data = playing; + playing->time_lost = 0; + playing->flags &= ~IT_PLAYING_DEAD; + it_playing_update_resamplers(playing); +} + + + +static void update_retrig(IT_CHANNEL *channel) +{ + if (channel->xm_retrig) { + channel->retrig_tick--; + if (channel->retrig_tick <= 0) { + if (channel->playing) it_playing_reset_resamplers(channel->playing, 0); + channel->retrig_tick = channel->xm_retrig; + } + } else if (channel->retrig & 0x0F) { + channel->retrig_tick--; + if (channel->retrig_tick <= 0) { + if (channel->retrig < 0x10) { + } else if (channel->retrig < 0x20) { + channel->volume--; + if (channel->volume > 64) channel->volume = 0; + } else if (channel->retrig < 0x30) { + channel->volume -= 2; + if (channel->volume > 64) channel->volume = 0; + } else if (channel->retrig < 0x40) { + channel->volume -= 4; + if (channel->volume > 64) channel->volume = 0; + } else if (channel->retrig < 0x50) { + channel->volume -= 8; + if (channel->volume > 64) channel->volume = 0; + } else if (channel->retrig < 0x60) { + channel->volume -= 16; + if (channel->volume > 64) channel->volume = 0; + } else if (channel->retrig < 0x70) { + channel->volume <<= 1; + channel->volume /= 3; + } else if (channel->retrig < 0x80) { + channel->volume >>= 1; + } else if (channel->retrig < 0x90) { + } else if (channel->retrig < 0xA0) { + channel->volume++; + if (channel->volume > 64) channel->volume = 64; + } else if (channel->retrig < 0xB0) { + channel->volume += 2; + if (channel->volume > 64) channel->volume = 64; + } else if (channel->retrig < 0xC0) { + channel->volume += 4; + if (channel->volume > 64) channel->volume = 64; + } else if (channel->retrig < 0xD0) { + channel->volume += 8; + if (channel->volume > 64) channel->volume = 64; + } else if (channel->retrig < 0xE0) { + channel->volume += 16; + if (channel->volume > 64) channel->volume = 64; + } else if (channel->retrig < 0xF0) { + channel->volume *= 3; + channel->volume >>= 1; + if (channel->volume > 64) channel->volume = 64; + } else { + channel->volume <<= 1; + if (channel->volume > 64) channel->volume = 64; + } + if (channel->playing) it_playing_reset_resamplers(channel->playing, 0); + channel->retrig_tick = channel->retrig & 0x0F; + } + } +} + + + +static void update_smooth_effects(DUMB_IT_SIGRENDERER *sigrenderer) +{ + int i; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; + IT_PLAYING *playing = channel->playing; + + if (playing) { + playing->vibrato_time += playing->vibrato_n * + (playing->vibrato_speed << 2); + playing->tremolo_time += playing->tremolo_speed << 2; + } + } +} + + + +static void update_effects(DUMB_IT_SIGRENDERER *sigrenderer) +{ + int i; + + if (sigrenderer->globalvolslide) { + sigrenderer->globalvolume += sigrenderer->globalvolslide; + if (sigrenderer->globalvolume > 128) { + if (sigrenderer->globalvolslide >= 0) + sigrenderer->globalvolume = 128; + else + sigrenderer->globalvolume = 0; + } + } + + if (sigrenderer->temposlide) { + sigrenderer->tempo += sigrenderer->temposlide; + if (sigrenderer->tempo < 32) { + if (sigrenderer->temposlide >= 0) + sigrenderer->tempo = 255; + else + sigrenderer->tempo = 32; + } + } + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; + IT_PLAYING *playing = channel->playing; + + if (channel->xm_volslide) { + channel->volume += channel->xm_volslide; + if (channel->volume > 64) { + if (channel->xm_volslide >= 0) + channel->volume = 64; + else + channel->volume = 0; + } + } + + if (channel->volslide) { + channel->volume += channel->volslide; + if (channel->volume > 64) { + if (channel->volslide >= 0) + channel->volume = 64; + else + channel->volume = 0; + } + } + + if (channel->channelvolslide) { + channel->channelvolume += channel->channelvolslide; + if (channel->channelvolume > 64) { + if (channel->channelvolslide >= 0) + channel->channelvolume = 64; + else + channel->channelvolume = 0; + } + if (channel->playing) + channel->playing->channel_volume = channel->channelvolume; + } + + update_tremor(channel); + + channel->arpeggio = (channel->arpeggio << 4) | (channel->arpeggio >> 8); + channel->arpeggio &= 0xFFF; + + update_retrig(channel); + + if (playing) { + playing->slide += channel->portamento; + + if (sigrenderer->sigdata->flags & IT_LINEAR_SLIDES) { + if (channel->toneporta && channel->destnote < 120) { + int currpitch = ((playing->note - 60) << 8) + playing->slide; + int destpitch = (channel->destnote - 60) << 8; + if (currpitch > destpitch) { + currpitch -= channel->toneporta; + if (currpitch < destpitch) { + currpitch = destpitch; + channel->destnote = IT_NOTE_OFF; + } + } else if (currpitch < destpitch) { + currpitch += channel->toneporta; + if (currpitch > destpitch) { + currpitch = destpitch; + channel->destnote = IT_NOTE_OFF; + } + } + playing->slide = currpitch - ((playing->note - 60) << 8); + } + } else { + if (channel->toneporta && channel->destnote < 120) { + float amiga_multiplier = playing->sample->C5_speed * (1.0f / AMIGA_DIVISOR); + + float deltanote = (float)pow(DUMB_SEMITONE_BASE, 60 - playing->note); + /* deltanote is 1.0 for C-5, 0.5 for C-6, etc. */ + + float deltaslid = deltanote - playing->slide * amiga_multiplier; + + float destdelta = (float)pow(DUMB_SEMITONE_BASE, 60 - channel->destnote); + if (deltaslid < destdelta) { + playing->slide -= channel->toneporta; + deltaslid = deltanote - playing->slide * amiga_multiplier; + if (deltaslid > destdelta) { + playing->note = channel->destnote; + playing->slide = 0; + channel->destnote = IT_NOTE_OFF; + } + } else { + playing->slide += channel->toneporta; + deltaslid = deltanote - playing->slide * amiga_multiplier; + if (deltaslid < destdelta) { + playing->note = channel->destnote; + playing->slide = 0; + channel->destnote = IT_NOTE_OFF; + } + } + } + } + } + } + + update_smooth_effects(sigrenderer); +} + + + +static void update_pattern_variables(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; + + if (entry->mask & IT_ENTRY_EFFECT) { + if (entry->effect == IT_S) { + unsigned char effectvalue = entry->effectvalue; + if (effectvalue == 0) + effectvalue = channel->lastS; + channel->lastS = effectvalue; + switch (effectvalue >> 4) { + //case IT_S7: + case IT_S_PATTERN_LOOP: + { + unsigned char v = effectvalue & 15; + if (v == 0) + channel->pat_loop_row = sigrenderer->processrow; + else { + if (channel->pat_loop_count == 0) { + channel->pat_loop_count = v; + sigrenderer->pat_loop_row = channel->pat_loop_row; + } else { + if (--channel->pat_loop_count) + sigrenderer->pat_loop_row = channel->pat_loop_row; + else if (!(sigrenderer->sigdata->flags & IT_WAS_AN_XM)) + channel->pat_loop_row = sigrenderer->processrow + 1; + } + } + } + break; + case IT_S_PATTERN_DELAY: + sigrenderer->rowcount = 1 + (effectvalue & 15); + break; + } + } + } +} + + + +/* This function guarantees that channel->sample will always be valid if it + * is nonzero. In other words, to check if it is valid, simply check if it is + * nonzero. + */ +static void instrument_to_sample(DUMB_IT_SIGDATA *sigdata, IT_CHANNEL *channel) +{ + if (sigdata->flags & IT_USE_INSTRUMENTS) { + if (channel->instrument >= 1 && channel->instrument <= sigdata->n_instruments) { + if (channel->note < 120) { + channel->sample = sigdata->instrument[channel->instrument-1].map_sample[channel->note]; + channel->truenote = sigdata->instrument[channel->instrument-1].map_note[channel->note]; + } else + channel->sample = 0; + } else + channel->sample = 0; + } else { + channel->sample = channel->instrument; + channel->truenote = channel->note; + } + if (!(channel->sample >= 1 && channel->sample <= sigdata->n_samples && (sigdata->sample[channel->sample-1].flags & IT_SAMPLE_EXISTS))) + channel->sample = 0; +} + + + +static void fix_sample_looping(IT_PLAYING *playing) +{ + if ((playing->sample->flags & (IT_SAMPLE_LOOP | IT_SAMPLE_SUS_LOOP)) == + (IT_SAMPLE_LOOP | IT_SAMPLE_SUS_LOOP)) { + if (playing->resampler[0].dir < 0) { + playing->resampler[1].pos = playing->resampler[0].pos = (playing->sample->sus_loop_end << 1) - 1 - playing->resampler[0].pos; + playing->resampler[1].subpos = playing->resampler[0].subpos ^= 65535; + playing->resampler[1].dir = playing->resampler[0].dir = 1; + } + + playing->resampler[1].pos = playing->resampler[0].pos += playing->time_lost; + } +} + + + +static void retrigger_it_envelopes(DUMB_IT_SIGDATA *sigdata, IT_CHANNEL *channel) +{ + channel->playing->volume_envelope.next_node = 0; + channel->playing->volume_envelope.tick = -1; + channel->playing->pan_envelope.next_node = 0; + channel->playing->pan_envelope.tick = -1; + channel->playing->pitch_envelope.next_node = 0; + channel->playing->pitch_envelope.tick = -1; + channel->playing->fadeoutcount = 1024; + // Should we remove IT_PLAYING_BACKGROUND? Test with sample with sustain loop... + channel->playing->flags &= ~(IT_PLAYING_BACKGROUND | IT_PLAYING_SUSTAINOFF | IT_PLAYING_FADING | IT_PLAYING_DEAD); + it_playing_update_resamplers(channel->playing); + + if (channel->sample) + if (sigdata->flags & IT_USE_INSTRUMENTS) + channel->playing->env_instrument = &sigdata->instrument[channel->instrument-1]; +} + + + +static void it_retrigger_note(DUMB_IT_SIGRENDERER *sigrenderer, IT_CHANNEL *channel) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + unsigned char nna; + int i; + + if (channel->playing) { +#ifdef INVALID_NOTES_CAUSE_NOTE_CUT + if (channel->note == IT_NOTE_OFF) + nna = NNA_NOTE_OFF; + else if (channel->note >= 120 || !channel->playing->instrument || (channel->playing->flags & IT_PLAYING_DEAD)) + nna = NNA_NOTE_CUT; + else + nna = channel->playing->instrument->new_note_action; +#else + if (channel->note == IT_NOTE_CUT) + nna = NNA_NOTE_CUT; + if (channel->note >= 120) + nna = NNA_NOTE_OFF; + else if (!channel->playing->instrument || (channel->playing->flags & IT_PLAYING_DEAD)) + nna = NNA_NOTE_CUT; + else + nna = channel->playing->instrument->new_note_action; +#endif + + switch (nna) { + case NNA_NOTE_CUT: + free(channel->playing); + channel->playing = NULL; + break; + case NNA_NOTE_OFF: + channel->playing->flags |= IT_PLAYING_BACKGROUND | IT_PLAYING_SUSTAINOFF; + fix_sample_looping(channel->playing); + it_playing_update_resamplers(channel->playing); + if (channel->playing->instrument) + if ((channel->playing->instrument->volume_envelope.flags & (IT_ENVELOPE_ON | IT_ENVELOPE_LOOP_ON)) != IT_ENVELOPE_ON) + channel->playing->flags |= IT_PLAYING_FADING; + break; + case NNA_NOTE_FADE: + channel->playing->flags |= IT_PLAYING_BACKGROUND | IT_PLAYING_FADING; + break; + } + } + + if (channel->sample == 0 || channel->note >= 120) + return; + + channel->destnote = IT_NOTE_OFF; + + if (channel->playing) { + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (!sigrenderer->playing[i]) { + sigrenderer->playing[i] = channel->playing; + channel->playing = NULL; + break; + } + } +/** WARNING - come up with some more heuristics for replacing old notes */ +#if 0 + if (channel->playing) { + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (sigrenderer->playing[i]->flags & IT_PLAYING_BACKGROUND) { + write_seqtime(); + sequence_c(SEQUENCE_STOP_SIGNAL); + sequence_c(i); + channel->VChannel = &module->VChannel[i]; + break; + } + } + } +#endif + } + + if (channel->playing) + free(channel->playing); + + channel->playing = malloc(sizeof(*channel->playing)); + + if (!channel->playing) + return; + + channel->playing->flags = 0; + channel->playing->channel = channel; + channel->playing->sample = &sigdata->sample[channel->sample-1]; + if (sigdata->flags & IT_USE_INSTRUMENTS) + channel->playing->instrument = &sigdata->instrument[channel->instrument-1]; + else + channel->playing->instrument = NULL; + channel->playing->env_instrument = channel->playing->instrument; + channel->playing->sampnum = channel->sample; + channel->playing->instnum = channel->instrument; + channel->playing->channel_volume = channel->channelvolume; + channel->playing->note = channel->truenote; + channel->playing->filter_cutoff = 127; + channel->playing->filter_resonance = 0; + channel->playing->true_filter_cutoff = 127 << 8; + channel->playing->true_filter_resonance = 0; + channel->playing->vibrato_speed = 0; + channel->playing->vibrato_depth = 0; + channel->playing->vibrato_n = 0; + channel->playing->vibrato_time = 0; + channel->playing->tremolo_speed = 0; + channel->playing->tremolo_depth = 0; + channel->playing->tremolo_time = 0; + channel->playing->sample_vibrato_time = 0; + channel->playing->sample_vibrato_depth = 0; + channel->playing->slide = 0; + channel->playing->volume_envelope.next_node = 0; + channel->playing->volume_envelope.tick = -1; + channel->playing->pan_envelope.next_node = 0; + channel->playing->pan_envelope.tick = -1; + channel->playing->pitch_envelope.next_node = 0; + channel->playing->pitch_envelope.tick = -1; + channel->playing->fadeoutcount = 1024; + it_reset_filter_state(&channel->playing->filter_state[0]); + it_reset_filter_state(&channel->playing->filter_state[1]); + it_playing_reset_resamplers(channel->playing, 0); + + /** WARNING - is everything initialised? */ +} + + + +static void get_default_volpan(DUMB_IT_SIGDATA *sigdata, IT_CHANNEL *channel) +{ + if (channel->sample == 0) + return; + + channel->volume = sigdata->sample[channel->sample-1].default_volume; + + { + int pan = sigdata->sample[channel->sample-1].default_pan; + if (pan >= 128 && pan <= 192) { + channel->pan = pan - 128; + return; + } + } + + if (sigdata->flags & IT_USE_INSTRUMENTS) { + IT_INSTRUMENT *instrument = &sigdata->instrument[channel->instrument-1]; + if (instrument->default_pan <= 64) + channel->pan = instrument->default_pan; + if (instrument->filter_cutoff >= 128) + channel->filter_cutoff = instrument->filter_cutoff - 128; + if (instrument->filter_resonance >= 128) + channel->filter_resonance = instrument->filter_resonance - 128; + } +} + + + +static void get_true_pan(DUMB_IT_SIGDATA *sigdata, IT_CHANNEL *channel) +{ + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + + if (!IT_IS_SURROUND_SHIFTED(channel->truepan) && (sigdata->flags & IT_USE_INSTRUMENTS)) { + IT_INSTRUMENT *instrument = &sigdata->instrument[channel->instrument-1]; + int truepan = channel->truepan; + truepan += (channel->note - instrument->pp_centre) * instrument->pp_separation << (IT_ENVELOPE_SHIFT - 3); + channel->truepan = MID(0, truepan, 64 << IT_ENVELOPE_SHIFT); + } +} + + + +static void post_process_it_volpan(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; + + if (entry->mask & IT_ENTRY_VOLPAN) { + if (entry->volpan <= 84) { + /* Volume */ + /* Fine volume slide up */ + /* Fine volume slide down */ + } else if (entry->volpan <= 94) { + /* Volume slide up */ + unsigned char v = entry->volpan - 85; + if (v == 0) + v = channel->lastvolslide; + channel->lastvolslide = v; + /* = effect Dx0 where x == entry->volpan - 85 */ + channel->volslide = v; + } else if (entry->volpan <= 104) { + /* Volume slide down */ + unsigned char v = entry->volpan - 95; + if (v == 0) + v = channel->lastvolslide; + channel->lastvolslide = v; + /* = effect D0x where x == entry->volpan - 95 */ + channel->volslide = -v; + } else if (entry->volpan <= 114) { + /* Portamento down */ + unsigned char v = (entry->volpan - 105) << 2; + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + channel->portamento -= v << 4; + } else if (entry->volpan <= 124) { + /* Portamento up */ + unsigned char v = (entry->volpan - 115) << 2; + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + channel->portamento += v << 4; + } else if (entry->volpan <= 202) { + /* Pan */ + /* Tone Portamento */ + } else if (entry->volpan <= 212) { + /* Vibrato */ + unsigned char v = entry->volpan - 203; + if (v == 0) + v = channel->lastHdepth; + else { + v <<= 2; + channel->lastHdepth = v; + } + if (channel->playing) { + channel->playing->vibrato_speed = channel->lastHspeed; + channel->playing->vibrato_depth = v; + channel->playing->vibrato_n++; + } + } + } +} + + + +static void it_send_midi(DUMB_IT_SIGRENDERER *sigrenderer, IT_CHANNEL *channel, unsigned char byte) +{ + if (sigrenderer->callbacks->midi) + if ((*sigrenderer->callbacks->midi)(sigrenderer->callbacks->midi_data, channel - sigrenderer->channel, byte)) + return; + + switch (channel->midi_state) { + case 4: /* Ready to receive resonance parameter */ + if (byte < 0x80) channel->filter_resonance = byte; + channel->midi_state = 0; + break; + case 3: /* Ready to receive cutoff parameter */ + if (byte < 0x80) channel->filter_cutoff = byte; + channel->midi_state = 0; + break; + case 2: /* Ready for byte specifying which parameter will follow */ + if (byte == 0) /* Cutoff */ + channel->midi_state = 3; + else if (byte == 1) /* Resonance */ + channel->midi_state = 4; + else + channel->midi_state = 0; + break; + default: /* Counting initial F0 bytes */ + switch (byte) { + case 0xF0: + channel->midi_state++; + break; + case 0xFA: + case 0xFC: + case 0xFF: + /* Reset filter parameters for all channels */ + { + int i; + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + sigrenderer->channel[i].filter_cutoff = 127; + sigrenderer->channel[i].filter_resonance = 0; + //// should we be resetting channel[i].playing->filter_* here? + } + } + /* Fall through */ + default: + channel->midi_state = 0; + break; + } + } +} + + + +/* Returns 1 if a callback caused termination of playback. */ +static int process_effects(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + + IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; + + if (entry->mask & IT_ENTRY_EFFECT) { + switch (entry->effect) { +/* +Notes about effects (as compared to other module formats) + +C This is now in *HEX*. (Used to be in decimal in ST3) +E/F/G/H/U You need to check whether the song uses Amiga/Linear slides. +H/U Vibrato in Impulse Tracker is two times finer than in + any other tracker and is updated EVERY tick. + If "Old Effects" is *ON*, then the vibrato is played in the + normal manner (every non-row tick and normal depth) +E/F/G These commands ALL share the same memory. +Oxx Offsets to samples are to the 'xx00th' SAMPLE. (ie. for + 16 bit samples, the offset is xx00h*2) + Oxx past the sample end will be ignored, unless "Old Effects" + is ON, in which case the Oxx will play from the end of the + sample. +Yxy This uses a table 4 times larger (hence 4 times slower) than + vibrato or tremelo. If the waveform is set to random, then + the 'speed' part of the command is interpreted as a delay. +*/ + case IT_SET_SPEED: + if (entry->effectvalue) + sigrenderer->tick = sigrenderer->speed = entry->effectvalue; + else if (sigdata->flags & IT_WAS_AN_XM) { + sigrenderer->speed = 0; + if (sigrenderer->callbacks->xm_speed_zero && (*sigrenderer->callbacks->xm_speed_zero)(sigrenderer->callbacks->xm_speed_zero_data)) + return 1; + } + break; + + case IT_JUMP_TO_ORDER: sigrenderer->processorder = entry->effectvalue - 1; sigrenderer->processrow = 0xFFFE; break; + case IT_BREAK_TO_ROW: sigrenderer->breakrow = entry->effectvalue; sigrenderer->processrow = 0xFFFE; break; + + case IT_VOLSLIDE_VIBRATO: + if (channel->playing) { + channel->playing->vibrato_speed = channel->lastHspeed; + channel->playing->vibrato_depth = channel->lastHdepth; + channel->playing->vibrato_n++; + } + /* Fall through and process volume slide. */ + case IT_VOLUME_SLIDE: + case IT_VOLSLIDE_TONEPORTA: + /* The tone portamento component is handled elsewhere. */ + { + unsigned char v = entry->effectvalue; + if (!(sigdata->flags & IT_WAS_A_MOD)) { + if (v == 0) + v = channel->lastDKL; + channel->lastDKL = v; + } + if ((v & 0x0F) == 0) { /* Dx0 */ + channel->volslide = v >> 4; + if (channel->volslide == 15 && !(sigdata->flags & IT_WAS_AN_XM)) { + channel->volume += 15; + if (channel->volume > 64) channel->volume = 64; + } + } else if ((v & 0xF0) == 0) { /* D0x */ + channel->volslide = -v; + if (channel->volslide == -15 && !(sigdata->flags & IT_WAS_AN_XM)) { + channel->volume -= 15; + if (channel->volume > 64) channel->volume = 0; + } + } else if ((v & 0x0F) == 0x0F) { /* DxF */ + channel->volume += v >> 4; + if (channel->volume > 64) channel->volume = 64; + } else if ((v & 0xF0) == 0xF0) { /* DFx */ + channel->volume -= v & 15; + if (channel->volume > 64) channel->volume = 0; + } + } + break; + case IT_XM_FINE_VOLSLIDE_DOWN: + { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->xm_lastEB; + channel->xm_lastEB = v; + channel->volume -= v; + if (channel->volume > 64) channel->volume = 0; + } + break; + case IT_XM_FINE_VOLSLIDE_UP: + { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->xm_lastEA; + channel->xm_lastEA = v; + channel->volume += v; + if (channel->volume > 64) channel->volume = 64; + } + break; + case IT_PORTAMENTO_DOWN: + { + unsigned char v = entry->effectvalue; + if (sigdata->flags & IT_WAS_AN_XM) { + if (!(sigdata->flags & IT_WAS_A_MOD)) { + if (v == 0xF0) + v |= channel->xm_lastE2; + else if (v >= 0xF0) + channel->xm_lastE2 = v & 15; + else if (v == 0xE0) + v |= channel->xm_lastX2; + else + channel->xm_lastX2 = v & 15; + } + } else { + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + } + if (channel->playing) { + if ((v & 0xF0) == 0xF0) + channel->playing->slide -= (v & 15) << 4; + else if ((v & 0xF0) == 0xE0) + channel->playing->slide -= (v & 15) << 2; + else + channel->portamento -= v << 4; + } + } + break; + case IT_PORTAMENTO_UP: + { + unsigned char v = entry->effectvalue; + if (sigdata->flags & IT_WAS_AN_XM) { + if (!(sigdata->flags & IT_WAS_A_MOD)) { + if (v == 0xF0) + v |= channel->xm_lastE1; + else if (v >= 0xF0) + channel->xm_lastE1 = v & 15; + else if (v == 0xE0) + v |= channel->xm_lastX1; + else + channel->xm_lastX1 = v & 15; + } + } else { + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + } + if (channel->playing) { + if ((v & 0xF0) == 0xF0) + channel->playing->slide += (v & 15) << 4; + else if ((v & 0xF0) == 0xE0) + channel->playing->slide += (v & 15) << 2; + else + channel->portamento += v << 4; + } + } + break; + case IT_XM_PORTAMENTO_DOWN: + { + unsigned char v = entry->effectvalue; + if (!(sigdata->flags & IT_WAS_A_MOD)) { + if (v == 0) + v = channel->lastJ; + channel->lastJ = v; + } + if (channel->playing) + channel->portamento -= v << 4; + } + break; + case IT_XM_PORTAMENTO_UP: + { + unsigned char v = entry->effectvalue; + if (!(sigdata->flags & IT_WAS_A_MOD)) { + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + } + if (channel->playing) + channel->portamento += v << 4; + } + break; + case IT_VIBRATO: + { + unsigned char speed = entry->effectvalue >> 4; + unsigned char depth = entry->effectvalue & 15; + if (speed == 0) + speed = channel->lastHspeed; + channel->lastHspeed = speed; + if (depth == 0) + depth = channel->lastHdepth; + else { + if (sigdata->flags & IT_OLD_EFFECTS) + depth <<= 3; + else + depth <<= 2; + channel->lastHdepth = depth; + } + if (channel->playing) { + channel->playing->vibrato_speed = speed; + channel->playing->vibrato_depth = depth; + channel->playing->vibrato_n++; + } + } + break; + case IT_TREMOR: + { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->lastI; + else if (!(sigdata->flags & IT_OLD_EFFECTS)) { + if (v & 0xF0) v -= 0x10; + if (v & 0x0F) v -= 0x01; + } + channel->lastI = v; + channel->tremor_time |= 128; + } + update_tremor(channel); + break; + case IT_ARPEGGIO: + { + unsigned char v = entry->effectvalue; + /* XM files have no memory for arpeggio (000 = no effect) + * and we use lastJ for portamento down instead. + */ + if (!(sigdata->flags & IT_WAS_AN_XM)) { + if (v == 0) + v = channel->lastJ; + channel->lastJ = v; + } + channel->arpeggio = v; + } + break; + case IT_SET_CHANNEL_VOLUME: + if (sigdata->flags & IT_WAS_AN_XM) + channel->volume = MIN(entry->effectvalue, 64); + else if (entry->effectvalue <= 64) + channel->channelvolume = entry->effectvalue; +#ifdef VOLUME_OUT_OF_RANGE_SETS_MAXIMUM + else + channel->channelvolume = 64; +#endif + if (channel->playing) + channel->playing->channel_volume = channel->channelvolume; + break; + case IT_CHANNEL_VOLUME_SLIDE: + { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->lastN; + channel->lastN = v; + if ((v & 0x0F) == 0) { /* Nx0 */ + channel->channelvolslide = v >> 4; + } else if ((v & 0xF0) == 0) { /* N0x */ + channel->channelvolslide = -v; + } else { + if ((v & 0x0F) == 0x0F) { /* NxF */ + channel->channelvolume += v >> 4; + if (channel->channelvolume > 64) channel->channelvolume = 64; + } else if ((v & 0xF0) == 0xF0) { /* NFx */ + channel->channelvolume -= v & 15; + if (channel->channelvolume > 64) channel->channelvolume = 0; + } else + break; + if (channel->playing) + channel->playing->channel_volume = channel->channelvolume; + } + } + break; + case IT_SET_SAMPLE_OFFSET: + { + unsigned char v = entry->effectvalue; + if (sigdata->flags & IT_WAS_A_MOD) { + if (v == 0) break; + } else { + if (v == 0) + v = channel->lastO; + channel->lastO = v; + } + /* Note: we set the offset even if tone portamento is + * specified. Impulse Tracker does the same. + */ + if (entry->mask & IT_ENTRY_NOTE) { + if (channel->playing) { + int offset = ((int)channel->high_offset << 16) | ((int)v << 8); + IT_PLAYING *playing = channel->playing; + IT_SAMPLE *sample = playing->sample; + int end; + if ((sample->flags & IT_SAMPLE_SUS_LOOP) && !(playing->flags & IT_PLAYING_SUSTAINOFF)) + end = sample->sus_loop_end; + else if (sample->flags & IT_SAMPLE_LOOP) + end = sample->loop_end; + else + end = sample->length; + if (offset < end) + it_playing_reset_resamplers(playing, offset); + else if (sigdata->flags & IT_OLD_EFFECTS) + it_playing_reset_resamplers(playing, end); + } + } + } + break; + //case IT_PANNING_SLIDE: + /** JULIEN: guess what? the docs are wrong! (how unusual ;) + * Pxy seems to memorize its previous value... and there + * might be other mistakes like that... (sigh!) + */ + /** ENTHEH: umm... but... the docs say that Pxy memorises its + * value... don't they? :o + */ + case IT_RETRIGGER_NOTE: + { + unsigned char v = entry->effectvalue; + if (sigdata->flags & IT_WAS_AN_XM) { + if ((v & 0x0F) == 0) v |= channel->lastQ & 0x0F; + if ((v & 0xF0) == 0) v |= channel->lastQ & 0xF0; + } else { + if (v == 0) + v = channel->lastQ; + } + channel->lastQ = v; + if ((v & 0x0F) == 0) v |= 0x01; + channel->retrig = v; + if (entry->mask & IT_ENTRY_NOTE) { + channel->retrig_tick = v & 0x0F; + /* Emulate a bug */ + if (sigdata->flags & IT_WAS_AN_XM) + update_retrig(channel); + } else + update_retrig(channel); + } + break; + case IT_XM_RETRIGGER_NOTE: + channel->retrig_tick = channel->xm_retrig = entry->effectvalue; + if (entry->effectvalue == 0) + if (channel->playing) it_playing_reset_resamplers(channel->playing, 0); + break; + case IT_TREMOLO: + { + unsigned char speed = entry->effectvalue >> 4; + unsigned char depth = entry->effectvalue & 15; + if (speed == 0) + speed = channel->lastRspeed; + channel->lastRspeed = speed; + if (depth == 0) + depth = channel->lastRdepth; + channel->lastRdepth = depth; + if (channel->playing) { + channel->playing->tremolo_speed = speed; + channel->playing->tremolo_depth = depth; + } + } + break; + case IT_S: + { + /* channel->lastS was set in update_pattern_variables(). */ + unsigned char effectvalue = channel->lastS; + switch (effectvalue >> 4) { + //case IT_S_SET_FILTER: + //case IT_S_SET_GLISSANDO_CONTROL: + //case IT_S_FINETUNE: + //case IT_S_SET_VIBRATO_WAVEFORM: + //case IT_S_SET_TREMOLO_WAVEFORM: + //case IT_S_SET_PANBRELLO_WAVEFORM: + /* Waveforms for commands S3x, S4x and S5x: + * 0: Sine wave + * 1: Ramp down + * 2: Square wave + * 3: Random wave + */ + case IT_S_FINE_PATTERN_DELAY: + sigrenderer->tick += effectvalue & 15; + break; + //case IT_S7: + case IT_S_SET_PAN: + channel->pan = + ((effectvalue & 15) << 2) | + ((effectvalue & 15) >> 2); + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + break; + case IT_S_SET_SURROUND_SOUND: + if ((effectvalue & 15) == 1) + channel->pan = IT_SURROUND; + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + break; + case IT_S_SET_HIGH_OFFSET: + channel->high_offset = effectvalue & 15; + break; + //case IT_S_PATTERN_LOOP: + case IT_S_DELAYED_NOTE_CUT: + channel->note_cut_count = effectvalue & 15; + if (!channel->note_cut_count) { + if (sigdata->flags & IT_WAS_AN_XM) + channel->volume = 0; + else + channel->note_cut_count = 1; + } + break; + case IT_S_SET_MIDI_MACRO: + channel->SFmacro = effectvalue & 15; + break; + } + } + break; + case IT_SET_SONG_TEMPO: + { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->lastW; + channel->lastW = v; + if (v < 0x10) + sigrenderer->temposlide = -v; + else if (v < 0x20) + sigrenderer->temposlide = v & 15; + else + sigrenderer->tempo = v; + } + break; + case IT_FINE_VIBRATO: + { + unsigned char speed = entry->effectvalue >> 4; + unsigned char depth = entry->effectvalue & 15; + if (speed == 0) + speed = channel->lastHspeed; + channel->lastHspeed = speed; + if (depth == 0) + depth = channel->lastHdepth; + else { + if (sigdata->flags & IT_OLD_EFFECTS) + depth <<= 1; + channel->lastHdepth = depth; + } + if (channel->playing) { + channel->playing->vibrato_speed = speed; + channel->playing->vibrato_depth = depth; + channel->playing->vibrato_n++; + } + } + break; + case IT_SET_GLOBAL_VOLUME: + if (entry->effectvalue <= 128) + sigrenderer->globalvolume = entry->effectvalue; +#ifdef VOLUME_OUT_OF_RANGE_SETS_MAXIMUM + else + sigrenderer->globalvolume = 128; +#endif + break; + case IT_GLOBAL_VOLUME_SLIDE: + { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->lastW; + channel->lastW = v; + if ((v & 0x0F) == 0) { /* Wx0 */ + sigrenderer->globalvolslide = + (sigdata->flags & IT_WAS_AN_XM) ? (v >> 4)*2 : (v >> 4); + } else if ((v & 0xF0) == 0) { /* W0x */ + sigrenderer->globalvolslide = + (sigdata->flags & IT_WAS_AN_XM) ? (-v)*2 : (-v); + } else if ((v & 0x0F) == 0x0F) { /* WxF */ + sigrenderer->globalvolume += v >> 4; + if (sigrenderer->globalvolume > 128) sigrenderer->globalvolume = 128; + } else if ((v & 0xF0) == 0xF0) { /* WFx */ + sigrenderer->globalvolume -= v & 15; + if (sigrenderer->globalvolume > 128) sigrenderer->globalvolume = 0; + } + } + break; + case IT_SET_PANNING: + channel->pan = (entry->effectvalue + 2) >> 2; + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + break; + //case IT_PANBRELLO: + case IT_MIDI_MACRO: + { + IT_MIDI *midi = sigdata->midi ? sigdata->midi : &default_midi; + if (entry->effectvalue >= 0x80) { + int n = midi->Zmacrolen[entry->effectvalue-0x80]; + int i; + for (i = 0; i < n; i++) + it_send_midi(sigrenderer, channel, midi->Zmacro[entry->effectvalue-0x80][i]); + } else { + int n = midi->SFmacrolen[channel->SFmacro]; + int i, j; + for (i = 0, j = 1; i < n; i++, j <<= 1) + it_send_midi(sigrenderer, channel, + midi->SFmacroz[channel->SFmacro] & j ? + entry->effectvalue : midi->SFmacro[channel->SFmacro][i]); + } + } + break; + } + } + + if (!(sigdata->flags & IT_WAS_AN_XM)) + post_process_it_volpan(sigrenderer, entry); + + return 0; +} + + + +static int process_it_note_data(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; + + // When tone portamento and instrument are specified: + // If Gxx is off: + // - same sample, do nothing but portamento + // - diff sample, retrigger all but keep current note+slide + do porta + // - if instrument is invalid, nothing; if sample is invalid, cut + // If Gxx is on: + // - same sample or new sample invalid, retrigger envelopes + // - diff sample/inst, start using new envelopes + // When tone portamento is specified alone, sample won't change. + // TODO: consider what happens with instrument alone after all this... + + if (entry->mask & (IT_ENTRY_NOTE | IT_ENTRY_INSTRUMENT)) { + if (entry->mask & IT_ENTRY_INSTRUMENT) + channel->instrument = entry->instrument; + instrument_to_sample(sigdata, channel); + if (channel->note < 120) { + if ((sigdata->flags & IT_USE_INSTRUMENTS) && channel->sample == 0) + return 1; + if (entry->mask & IT_ENTRY_INSTRUMENT) + get_default_volpan(sigdata, channel); + } else + it_retrigger_note(sigrenderer, channel); + } + + /** WARNING: This is not ideal, since channel->playing might not get allocated owing to lack of memory... */ + if (channel->playing && + (((entry->mask & IT_ENTRY_VOLPAN) && entry->volpan >= 193 && entry->volpan <= 202) || + ((entry->mask & IT_ENTRY_EFFECT) && (entry->effect == IT_TONE_PORTAMENTO || entry->effect == IT_VOLSLIDE_TONEPORTA)))) + { + if (entry->mask & IT_ENTRY_INSTRUMENT) { + if (sigdata->flags & IT_COMPATIBLE_GXX) + retrigger_it_envelopes(sigdata, channel); + else if ((!(sigdata->flags & IT_USE_INSTRUMENTS) || + (channel->instrument >= 1 && channel->instrument <= sigdata->n_instruments)) && + channel->sample != channel->playing->sampnum) + { + unsigned char note = channel->playing->note; + int slide = channel->playing->slide; + it_retrigger_note(sigrenderer, channel); + if (channel->playing) { + channel->playing->note = note; + channel->playing->slide = slide; + // Should we be preserving sample_vibrato_time? depth? + } + } + } + + if ((entry->mask & IT_ENTRY_VOLPAN) && entry->volpan >= 193 && entry->volpan <= 202) { + /* Tone Portamento in the volume column */ + static const unsigned char slidetable[] = {0, 1, 4, 8, 16, 32, 64, 96, 128, 255}; + unsigned char v = slidetable[entry->volpan - 193]; + if (sigdata->flags & IT_COMPATIBLE_GXX) { + if (v == 0) + v = channel->lastG; + channel->lastG = v; + } else { + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + } + if (entry->mask & IT_ENTRY_NOTE) + if (channel->sample) + channel->destnote = channel->truenote; + channel->toneporta = v << 4; + } else { + /* Tone Portamento in the effect column */ + unsigned char v; + if (entry->effect == IT_TONE_PORTAMENTO) + v = entry->effectvalue; + else + v = 0; + if (sigdata->flags & IT_COMPATIBLE_GXX) { + if (v == 0) + v = channel->lastG; + channel->lastG = v; + } else { + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + } + if (entry->mask & IT_ENTRY_NOTE) + if (channel->sample) + channel->destnote = channel->truenote; + channel->toneporta = v << 4; + } + } else if ((entry->mask & IT_ENTRY_NOTE) || + ((entry->mask & IT_ENTRY_INSTRUMENT) && (!channel->playing || entry->instrument != channel->playing->instnum))) + { + if (channel->note < 120) { + get_true_pan(sigdata, channel); + it_retrigger_note(sigrenderer, channel); + } + } + + if (entry->mask & IT_ENTRY_VOLPAN) { + if (entry->volpan <= 64) { + /* Volume */ + channel->volume = entry->volpan; + } else if (entry->volpan <= 74) { + /* Fine volume slide up */ + unsigned char v = entry->volpan - 65; + if (v == 0) + v = channel->lastvolslide; + channel->lastvolslide = v; + /* = effect DxF where x == entry->volpan - 65 */ + channel->volume += v; + if (channel->volume > 64) channel->volume = 64; + } else if (entry->volpan <= 84) { + /* Fine volume slide down */ + unsigned char v = entry->volpan - 75; + if (v == 0) + v = channel->lastvolslide; + channel->lastvolslide = v; + /* = effect DFx where x == entry->volpan - 75 */ + channel->volume -= v; + if (channel->volume > 64) channel->volume = 0; + } else if (entry->volpan < 128) { + /* Volume slide up */ + /* Volume slide down */ + /* Portamento down */ + /* Portamento up */ + } else if (entry->volpan <= 192) { + /* Pan */ + channel->pan = entry->volpan - 128; + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + } + /* else */ + /* Tone Portamento */ + /* Vibrato */ + } + return 0; +} + + + +static void retrigger_xm_envelopes(IT_PLAYING *playing) +{ + playing->volume_envelope.next_node = 0; + playing->volume_envelope.tick = -1; + playing->pan_envelope.next_node = 0; + playing->pan_envelope.tick = -1; + playing->fadeoutcount = 1024; +} + + + +static void process_xm_note_data(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; + + if (entry->mask & IT_ENTRY_INSTRUMENT) { + channel->instrument = entry->instrument; + instrument_to_sample(sigdata, channel); + if (channel->playing) { + /* Retrigger vol/pan envelopes if enabled, and cancel fadeout. + * Also reset vol/pan to that of _original_ instrument. + */ + channel->playing->flags &= ~(IT_PLAYING_SUSTAINOFF | IT_PLAYING_FADING); + it_playing_update_resamplers(channel->playing); + + channel->volume = channel->playing->sample->default_volume; + if (channel->pan >= 128 && channel->pan <= 192) + channel->pan = channel->playing->sample->default_pan - 128; + + retrigger_xm_envelopes(channel->playing); + } + } + + if (entry->mask & IT_ENTRY_NOTE) { + if (!(entry->mask & IT_ENTRY_INSTRUMENT)) + instrument_to_sample(sigdata, channel); + + if (channel->note >= 120) { + if (channel->playing) { + if (!(sigdata->instrument[channel->instrument-1].volume_envelope.flags & IT_ENVELOPE_ON)) + if (!(entry->mask & IT_ENTRY_INSTRUMENT)) + channel->volume = 0; + channel->playing->flags |= IT_PLAYING_SUSTAINOFF | IT_PLAYING_FADING; + it_playing_update_resamplers(channel->playing); + } + } else if (channel->sample == 0) { + /** If we get here, one of the following is the case: + ** 1. The instrument has never been specified on this channel. + ** 2. The specified instrument is invalid. + ** 3. The instrument has no sample mapped to the selected note. + ** What should happen? + ** + ** Experimentation shows that any existing note stops and cannot + ** be brought back. A subsequent instrument change fixes that. + **/ + if (channel->playing) { + free(channel->playing); + channel->playing = NULL; + } + return; + } else if (channel->playing && (entry->mask & IT_ENTRY_VOLPAN) && ((entry->volpan>>4) == 0xF)) { + /* Don't retrigger note; portamento in the volume column. */ + } else if (channel->playing && + (entry->mask & IT_ENTRY_EFFECT) && + (entry->effect == IT_TONE_PORTAMENTO || + entry->effect == IT_VOLSLIDE_TONEPORTA)) { + /* Don't retrigger note; portamento in the effects column. */ + } else { + channel->destnote = IT_NOTE_OFF; + + if (!channel->playing) { + channel->playing = malloc(sizeof(*channel->playing)); + if (!channel->playing) + return; + // Adding the following seems to do the trick for the case where a piece starts with an instrument alone and then some notes alone. + retrigger_xm_envelopes(channel->playing); + } + + channel->playing->flags = 0; + channel->playing->channel = channel; + channel->playing->sample = &sigdata->sample[channel->sample-1]; + if (sigdata->flags & IT_USE_INSTRUMENTS) + channel->playing->instrument = &sigdata->instrument[channel->instrument-1]; + else + channel->playing->instrument = NULL; + channel->playing->env_instrument = channel->playing->instrument; + channel->playing->sampnum = channel->sample; + channel->playing->instnum = channel->instrument; + channel->playing->channel_volume = channel->channelvolume; + channel->playing->note = channel->truenote; + channel->playing->filter_cutoff = 127; + channel->playing->filter_resonance = 0; + channel->playing->true_filter_cutoff = 127 << 8; + channel->playing->true_filter_resonance = 0; + channel->playing->vibrato_speed = 0; + channel->playing->vibrato_depth = 0; + channel->playing->vibrato_n = 0; + channel->playing->vibrato_time = 0; + channel->playing->tremolo_speed = 0; + channel->playing->tremolo_depth = 0; + channel->playing->tremolo_time = 0; + channel->playing->sample_vibrato_time = 0; + channel->playing->sample_vibrato_depth = 0; + channel->playing->slide = 0; + it_reset_filter_state(&channel->playing->filter_state[0]); // Are these + it_reset_filter_state(&channel->playing->filter_state[1]); // necessary? + it_playing_reset_resamplers(channel->playing, 0); + + /** WARNING - is everything initialised? */ + } + } + + if ((entry->mask & (IT_ENTRY_NOTE | IT_ENTRY_INSTRUMENT)) == (IT_ENTRY_NOTE | IT_ENTRY_INSTRUMENT)) { + if (channel->playing) retrigger_xm_envelopes(channel->playing); + get_default_volpan(sigdata, channel); + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + } + + if ((entry->mask & IT_ENTRY_VOLPAN) && ((entry->volpan>>4) == 0xF)) { + /* Tone Portamento */ + unsigned char v = (entry->volpan & 15) << 4; + if (v == 0) + v = channel->lastG; + channel->lastG = v; + if (entry->mask & IT_ENTRY_NOTE) + if (channel->sample) + channel->destnote = channel->truenote; + channel->toneporta = v << 4; + } else if ((entry->mask & IT_ENTRY_EFFECT) && + (entry->effect == IT_TONE_PORTAMENTO || + entry->effect == IT_VOLSLIDE_TONEPORTA)) { + unsigned char v; + if (entry->effect == IT_TONE_PORTAMENTO) + v = entry->effectvalue; + else + v = 0; + if (v == 0) + v = channel->lastG; + channel->lastG = v; + if (entry->mask & IT_ENTRY_NOTE) + if (channel->sample) + channel->destnote = channel->truenote; + channel->toneporta = v << 4; + } + + if (entry->mask & IT_ENTRY_VOLPAN) { + int effect = entry->volpan >> 4; + int value = entry->volpan & 15; + switch (effect) { + case 0x6: /* Volume slide down */ + channel->xm_volslide = -value; + break; + case 0x7: /* Volume slide up */ + channel->xm_volslide = value; + break; + case 0x8: /* Fine volume slide down */ + channel->volume -= value; + if (channel->volume > 64) channel->volume = 0; + break; + case 0x9: /* Fine volume slide up */ + channel->volume += value; + if (channel->volume > 64) channel->volume = 64; + break; + case 0xA: /* Set vibrato speed */ + if (value) + channel->lastHspeed = value; + if (channel->playing) + channel->playing->vibrato_speed = channel->lastHspeed; + break; + case 0xB: /* Vibrato */ + if (value) + channel->lastHdepth = value << 2; /** WARNING: correct ? */ + if (channel->playing) { + channel->playing->vibrato_depth = channel->lastHdepth; + channel->playing->vibrato_speed = channel->lastHspeed; + channel->playing->vibrato_n++; + } + break; + case 0xC: /* Set panning */ + channel->pan = (value*64)/15; + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + break; + case 0xD: /* Pan slide left */ + // TODO + // channel->xm_panslide = -value; + break; + case 0xE: /* Pan slide Right */ + // TODO + // channel->xm_panslide = value; + break; + case 0xF: /* Tone porta */ + break; + default: /* Volume */ + channel->volume = entry->volpan - 0x10; + break; + } + } +} + + + +/* This function assumes !IT_IS_END_ROW(entry). */ +static int process_note_data(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + + if (sigdata->flags & IT_WAS_AN_XM) + process_xm_note_data(sigrenderer, entry); + else + if (process_it_note_data(sigrenderer, entry)) return 0; + + return process_effects(sigrenderer, entry); +} + + + +static int process_entry(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; + + if (entry->mask & IT_ENTRY_NOTE) + channel->note = entry->note; + + if ((entry->mask & IT_ENTRY_EFFECT) && entry->effect == IT_S) { + /* channel->lastS was set in update_pattern_variables(). */ + unsigned char effectvalue = channel->lastS; + if (effectvalue >> 4 == IT_S_NOTE_DELAY) { + channel->note_delay_count = effectvalue & 15; + if (channel->note_delay_count == 0) + channel->note_delay_count = 1; + channel->note_delay_entry = entry; + return 0; + } + } + + return process_note_data(sigrenderer, entry); +} + + + +static void update_tick_counts(DUMB_IT_SIGRENDERER *sigrenderer) +{ + int i; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; + + if (channel->note_cut_count) { + channel->note_cut_count--; + if (channel->note_cut_count == 0) { + if (sigrenderer->sigdata->flags & IT_WAS_AN_XM) + channel->volume = 0; + else if (channel->playing) { + free(channel->playing); + channel->playing = NULL; + } + } + } else if (channel->note_delay_count) { + channel->note_delay_count--; + if (channel->note_delay_count == 0) + process_note_data(sigrenderer, channel->note_delay_entry); + /* Don't bother checking the return value; if the note + * was delayed, there can't have been a speed=0. + */ + } + } +} + + + +static int envelope_get_y(IT_ENVELOPE *envelope, IT_PLAYING_ENVELOPE *pe) +{ + int ys, ye; + int ts, te; + int t; + + if (pe->next_node <= 0) + return envelope->node_y[0] << IT_ENVELOPE_SHIFT; + + if (pe->next_node >= envelope->n_nodes) + return envelope->node_y[envelope->n_nodes-1] << IT_ENVELOPE_SHIFT; + + ys = envelope->node_y[pe->next_node-1] << IT_ENVELOPE_SHIFT; + ts = envelope->node_t[pe->next_node-1]; + te = envelope->node_t[pe->next_node]; + + if (ts == te) + return ys; + + ye = envelope->node_y[pe->next_node] << IT_ENVELOPE_SHIFT; + + t = pe->tick; + + return ys + (ye - ys) * (t - ts) / (te - ts); +} + + + +static int it_envelope_end(IT_PLAYING *playing, IT_ENVELOPE *envelope, IT_PLAYING_ENVELOPE *pe) +{ + if (pe->next_node >= envelope->n_nodes) + return 1; + + if (pe->tick < envelope->node_t[pe->next_node]) return 0; + + if ((envelope->flags & IT_ENVELOPE_LOOP_ON) && + envelope->loop_end >= pe->next_node && + envelope->node_t[envelope->loop_end] <= pe->tick) return 0; + + if ((envelope->flags & IT_ENVELOPE_SUSTAIN_LOOP) && + !(playing->flags & IT_PLAYING_SUSTAINOFF) && + envelope->sus_loop_end >= pe->next_node && + envelope->node_t[envelope->sus_loop_end] <= pe->tick) return 0; + + if (envelope->node_t[envelope->n_nodes-1] <= pe->tick) return 1; + + return 0; +} + + + +/* This returns 1 if the envelope finishes. */ +static int update_it_envelope(IT_PLAYING *playing, IT_ENVELOPE *envelope, IT_PLAYING_ENVELOPE *pe) +{ + if (!(envelope->flags & IT_ENVELOPE_ON)) + return 0; + + if (pe->next_node >= envelope->n_nodes) + return 1; + + while (pe->tick >= envelope->node_t[pe->next_node]) { + if ((envelope->flags & IT_ENVELOPE_LOOP_ON) && pe->next_node == envelope->loop_end) { + pe->next_node = envelope->loop_start; + pe->tick = envelope->node_t[envelope->loop_start]; + return it_envelope_end(playing, envelope, pe); + } + if ((envelope->flags & IT_ENVELOPE_SUSTAIN_LOOP) && !(playing->flags & IT_PLAYING_SUSTAINOFF) && pe->next_node == envelope->sus_loop_end) { + pe->next_node = envelope->sus_loop_start; + pe->tick = envelope->node_t[envelope->sus_loop_start]; + return it_envelope_end(playing, envelope, pe); + } + + pe->next_node++; + + if (pe->next_node >= envelope->n_nodes) + return 1; + } + + pe->tick++; + + return it_envelope_end(playing, envelope, pe); +} + + + +static void update_it_envelopes(IT_PLAYING *playing) +{ + IT_ENVELOPE *envelope = &playing->env_instrument->volume_envelope; + + if (update_it_envelope(playing, envelope, &playing->volume_envelope)) { + playing->flags |= IT_PLAYING_FADING; + if (envelope->n_nodes && envelope->node_y[envelope->n_nodes-1] == 0) + playing->flags |= IT_PLAYING_DEAD; + } + + update_it_envelope(playing, &playing->env_instrument->pan_envelope, &playing->pan_envelope); + update_it_envelope(playing, &playing->env_instrument->pitch_envelope, &playing->pitch_envelope); +} + + + +static int xm_envelope_is_sustaining(IT_PLAYING *playing, IT_ENVELOPE *envelope, IT_PLAYING_ENVELOPE *pe) +{ + if ((envelope->flags & IT_ENVELOPE_SUSTAIN_LOOP) && !(playing->flags & IT_PLAYING_SUSTAINOFF)) + if (envelope->sus_loop_start < envelope->n_nodes) + if (pe->tick == envelope->node_t[envelope->sus_loop_start]) + return 1; + return 0; +} + + + +static void update_xm_envelope(IT_PLAYING *playing, IT_ENVELOPE *envelope, IT_PLAYING_ENVELOPE *pe) +{ + if (!(envelope->flags & IT_ENVELOPE_ON)) + return; + + if (xm_envelope_is_sustaining(playing, envelope, pe)) + return; + + if (pe->tick >= envelope->node_t[envelope->n_nodes-1]) + return; + + pe->tick++; + + /* pe->next_node must be kept up to date for envelope_get_y(). */ + while (pe->tick > envelope->node_t[pe->next_node]) + pe->next_node++; + + if ((envelope->flags & IT_ENVELOPE_LOOP_ON) && envelope->loop_end < envelope->n_nodes) { + if (pe->tick == envelope->node_t[envelope->loop_end]) { + pe->next_node = MID(0, envelope->loop_start, envelope->n_nodes - 1); + pe->tick = envelope->node_t[pe->next_node]; + } + } + + if (xm_envelope_is_sustaining(playing, envelope, pe)) + return; + + if (pe->tick >= envelope->node_t[envelope->n_nodes-1]) + return; +} + + + +static void update_xm_envelopes(IT_PLAYING *playing) +{ + update_xm_envelope(playing, &playing->env_instrument->volume_envelope, &playing->volume_envelope); + update_xm_envelope(playing, &playing->env_instrument->pan_envelope, &playing->pan_envelope); +} + + + +static void update_fadeout(DUMB_IT_SIGDATA *sigdata, IT_PLAYING *playing) +{ + if (playing->flags & IT_PLAYING_FADING) { + playing->fadeoutcount -= playing->env_instrument->fadeout; + if (playing->fadeoutcount <= 0) { + playing->fadeoutcount = 0; + if (!(sigdata->flags & IT_WAS_AN_XM)) + playing->flags |= IT_PLAYING_DEAD; + } + } +} + + + +static void process_playing(DUMB_IT_SIGDATA *sigdata, IT_PLAYING *playing) +{ + if (playing->instrument) { + if (sigdata->flags & IT_WAS_AN_XM) + update_xm_envelopes(playing); + else + update_it_envelopes(playing); + update_fadeout(sigdata, playing); + } + + //Calculate final volume if required + //Calculate final pan if required + + if (sigdata->flags & IT_WAS_AN_XM) { + /* 'depth' is used to store the tick number for XM files. */ + if (playing->sample_vibrato_depth < playing->sample->vibrato_rate) + playing->sample_vibrato_depth++; + } else { + playing->sample_vibrato_depth += playing->sample->vibrato_rate; + if (playing->sample_vibrato_depth > playing->sample->vibrato_depth << 8) + playing->sample_vibrato_depth = playing->sample->vibrato_depth << 8; + } + + playing->sample_vibrato_time += playing->sample->vibrato_speed; +} + + + +static void process_all_playing(DUMB_IT_SIGRENDERER *sigrenderer) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + int i; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; + IT_PLAYING *playing = channel->playing; + + if (playing) { + int vibrato_shift = it_sine[playing->vibrato_time]; + vibrato_shift *= playing->vibrato_n; + vibrato_shift *= playing->vibrato_depth; + vibrato_shift >>= 4; + + if (sigdata->flags & IT_OLD_EFFECTS) + vibrato_shift = -vibrato_shift; + + playing->volume = channel->volume; + playing->pan = channel->truepan; + + if (sigdata->flags & IT_LINEAR_SLIDES) { + int currpitch = ((playing->note - 60) << 8) + playing->slide + + vibrato_shift; + + /* We add a feature here, which is that of keeping the pitch + * within range. Otherwise it crashes. Trust me. It happened. + * The limit 32768 gives almost 11 octaves either way. + */ + if (currpitch < -32768) + currpitch = -32768; + else if (currpitch > 32767) + currpitch = 32767; + + playing->delta = (float)pow(DUMB_PITCH_BASE, currpitch); + playing->delta *= playing->sample->C5_speed / 65536.0f; + } else { + int slide = playing->slide + vibrato_shift; + + playing->delta = (float)pow(DUMB_SEMITONE_BASE, 60 - playing->note); + /* playing->delta is 1.0 for C-5, 0.5 for C-6, etc. */ + + playing->delta *= 1.0f / playing->sample->C5_speed; + + playing->delta -= slide / AMIGA_DIVISOR; + + if (playing->delta < (1.0f / 65536.0f) / 32768.0f) { + // Should XM notes die if Amiga slides go out of range? + playing->flags |= IT_PLAYING_DEAD; + continue; + } + + playing->delta = (1.0f / 65536.0f) / playing->delta; + } + + playing->delta *= (float)pow(DUMB_SEMITONE_BASE, channel->arpeggio >> 8); + + playing->filter_cutoff = channel->filter_cutoff; + playing->filter_resonance = channel->filter_resonance; + } + } + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + if (sigrenderer->channel[i].playing) { + process_playing(sigdata, sigrenderer->channel[i].playing); + if (!(sigdata->flags & IT_WAS_AN_XM)) { + if ((sigrenderer->channel[i].playing->flags & (IT_PLAYING_BACKGROUND | IT_PLAYING_DEAD)) == (IT_PLAYING_BACKGROUND | IT_PLAYING_DEAD)) { + free(sigrenderer->channel[i].playing); + sigrenderer->channel[i].playing = NULL; + } + } + } + } + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (sigrenderer->playing[i]) { + process_playing(sigdata, sigrenderer->playing[i]); + if (sigrenderer->playing[i]->flags & IT_PLAYING_DEAD) { + free(sigrenderer->playing[i]); + sigrenderer->playing[i] = NULL; + } + } + } +} + + + +static int process_tick(DUMB_IT_SIGRENDERER *sigrenderer) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + + // Set note vol/freq to vol/freq set for each channel + + if (sigrenderer->speed && --sigrenderer->tick == 0) { + reset_tick_counts(sigrenderer); + sigrenderer->tick = sigrenderer->speed; + sigrenderer->rowcount--; + if (sigrenderer->rowcount == 0) { + sigrenderer->rowcount = 1; + if (sigrenderer->pat_loop_row >= 0) { + int n = sigrenderer->pat_loop_row - 1; + sigrenderer->row = sigrenderer->processrow = n; + sigrenderer->pat_loop_row = -1; + if (n < 0) + sigrenderer->entry = NULL; + else { + sigrenderer->entry = sigrenderer->entry_start; + while (n) { + while (sigrenderer->entry < sigrenderer->entry_end) { + if (IT_IS_END_ROW(sigrenderer->entry)) { + sigrenderer->entry++; + break; + } + sigrenderer->entry++; + } + n--; + } + } + } + + sigrenderer->processrow++; + + if (sigrenderer->processrow >= sigrenderer->n_rows) { + IT_PATTERN *pattern; + int n; + + sigrenderer->processrow = sigrenderer->breakrow; + sigrenderer->breakrow = 0; + + for (;;) { + sigrenderer->processorder++; + + if (sigrenderer->processorder >= sigdata->n_orders) { + sigrenderer->processorder = sigdata->restart_position; + if (sigrenderer->processorder >= sigdata->n_orders) { + /* Restarting beyond end. We'll loop for now. */ + sigrenderer->processorder = -1; + continue; + } + } + + n = sigdata->order[sigrenderer->processorder]; + + if (n < sigdata->n_patterns) + break; + +#ifdef INVALID_ORDERS_END_SONG + if (n != IT_ORDER_SKIP) + sigrenderer->processorder = -1; +#else + if (n == IT_ORDER_END) + sigrenderer->processorder = -1; +#endif + } + + pattern = &sigdata->pattern[n]; + + sigrenderer->n_rows = pattern->n_rows; + + if (sigrenderer->processrow >= sigrenderer->n_rows) + sigrenderer->processrow = 0; + +/** WARNING - everything pertaining to a new pattern initialised? */ + + sigrenderer->entry = sigrenderer->entry_start = pattern->entry; + sigrenderer->entry_end = sigrenderer->entry + pattern->n_entries; + + if (sigrenderer->order >= sigrenderer->processorder) { + if (sigrenderer->callbacks->loop) { + if ((*sigrenderer->callbacks->loop)(sigrenderer->callbacks->loop_data)) + return 1; + if (sigrenderer->speed == 0) + goto speed0; /* I love goto */ + } + } + sigrenderer->order = sigrenderer->processorder; + + n = sigrenderer->processrow; + while (n) { + while (sigrenderer->entry < sigrenderer->entry_end) { + if (IT_IS_END_ROW(sigrenderer->entry)) { + sigrenderer->entry++; + break; + } + sigrenderer->entry++; + } + n--; + } + sigrenderer->row = sigrenderer->processrow; + } else { + if (sigrenderer->entry) { + while (sigrenderer->entry < sigrenderer->entry_end) { + if (IT_IS_END_ROW(sigrenderer->entry)) { + sigrenderer->entry++; + break; + } + sigrenderer->entry++; + } + sigrenderer->row++; + } else { + sigrenderer->entry = sigrenderer->entry_start; + sigrenderer->row = 0; + } + } + + reset_effects(sigrenderer); + + { + IT_ENTRY *entry = sigrenderer->entry; + + while (entry < sigrenderer->entry_end && !IT_IS_END_ROW(entry)) + update_pattern_variables(sigrenderer, entry++); + } + + { + IT_ENTRY *entry = sigrenderer->entry; + + while (entry < sigrenderer->entry_end && !IT_IS_END_ROW(entry)) + if (process_entry(sigrenderer, entry++)) + return 1; + } + + if (!(sigdata->flags & IT_OLD_EFFECTS)) + update_smooth_effects(sigrenderer); + } else { + { + IT_ENTRY *entry = sigrenderer->entry; + + while (entry < sigrenderer->entry_end && !IT_IS_END_ROW(entry)) + process_effects(sigrenderer, entry++); + /* Don't bother checking the return value; if there + * was a pattern delay, there can't be a speed=0. + */ + } + + update_effects(sigrenderer); + } + } else { + speed0: + update_effects(sigrenderer); + update_tick_counts(sigrenderer); + } + + process_all_playing(sigrenderer); + + sigrenderer->time_left += TICK_TIME_DIVIDEND / sigrenderer->tempo; + + return 0; +} + + + +int dumb_it_max_to_mix = 64; + + + +static float calculate_volume(DUMB_IT_SIGRENDERER *sigrenderer, IT_PLAYING *playing, float volume) +{ + if (volume != 0) { + int vol; + + if (playing->channel->flags & IT_CHANNEL_MUTED) + return 0; + + if ((playing->channel->tremor_time & 192) == 128) + return 0; + + vol = it_sine[playing->tremolo_time]; + vol *= playing->tremolo_depth; + + vol = (playing->volume << 5) + vol; + + if (vol <= 0) + return 0; + + if (vol > 64 << 5) + vol = 64 << 5; + + volume *= vol; /* 64 << 5 */ + volume *= playing->sample->global_volume; /* 64 */ + volume *= playing->channel_volume; /* 64 */ + volume *= sigrenderer->globalvolume; /* 128 */ + volume *= sigrenderer->sigdata->mixing_volume; /* 128 */ + volume *= 1.0f / ((64 << 5) * 64.0f * 64.0f * 128.0f * 128.0f); + + if (volume && playing->instrument) { + if (playing->env_instrument->volume_envelope.flags & IT_ENVELOPE_ON) { + volume *= envelope_get_y(&playing->env_instrument->volume_envelope, &playing->volume_envelope); + volume *= 1.0f / (64 << IT_ENVELOPE_SHIFT); + } + volume *= playing->instrument->global_volume; /* 128 */ + volume *= playing->fadeoutcount; /* 1024 */ + volume *= 1.0f / (128.0f * 1024.0f); + } + } + + return volume; +} + + + +static int apply_pan_envelope(IT_PLAYING *playing) +{ + int pan = playing->pan; + if (pan <= 64 << IT_ENVELOPE_SHIFT && playing->env_instrument && (playing->env_instrument->pan_envelope.flags & IT_ENVELOPE_ON)) { + int p = envelope_get_y(&playing->env_instrument->pan_envelope, &playing->pan_envelope); + if (pan > 32 << IT_ENVELOPE_SHIFT) + p *= (64 << IT_ENVELOPE_SHIFT) - pan; + else + p *= pan; + pan += p >> (5 + IT_ENVELOPE_SHIFT); + } + return pan; +} + + + +/* Note: if a click remover is provided, and store_end_sample is set, then + * the end point will be computed twice. This situation should not arise. + */ +static long render_playing(DUMB_IT_SIGRENDERER *sigrenderer, IT_PLAYING *playing, float volume, float delta, long pos, long size, sample_t **samples, int store_end_sample, int *left_to_mix) +{ + int pan; + + long size_rendered; + + if (playing->flags & IT_PLAYING_DEAD) + return 0; + + if (*left_to_mix <= 0) + volume = 0; + + pan = apply_pan_envelope(playing); + +#define RESAMPLERV(rv, resampler, dst, volume) \ +{ \ + rv = dumb_resample(resampler, dst, size, volume, delta); \ + if (store_end_sample) \ + (dst)[rv] = RESAMPLE_VALUE(resampler, volume); \ +} + +#define RESAMPLE(resampler, dst, volume) \ +{ \ + int i; \ + RESAMPLERV(i, resampler, dst, volume); \ +} + +#define RESAMPLE_VALUE(resampler, volume) \ + dumb_resample_get_current_sample(resampler, volume) + + if (volume == 0) { + size_rendered = dumb_resample(&playing->resampler[0], NULL, size, 0, delta); + if (playing->sample->flags & IT_SAMPLE_STEREO) + dumb_resample(&playing->resampler[1], NULL, size, 0, delta); + } else { + if (sigrenderer->n_channels == 2) { + float vol = volume; + DUMB_RESAMPLER start = playing->resampler[0]; + if (!IT_IS_SURROUND_SHIFTED(pan)) vol *= 2.0f - pan * (1.0f / (32 << IT_ENVELOPE_SHIFT)); + if (sigrenderer->click_remover && sigrenderer->click_remover[0]) + dumb_record_click(sigrenderer->click_remover[0], pos, RESAMPLE_VALUE(&playing->resampler[0], vol)); + RESAMPLERV(size_rendered, &playing->resampler[0], samples[0] + pos, vol); + if (sigrenderer->click_remover && sigrenderer->click_remover[0]) + dumb_record_click(sigrenderer->click_remover[0], pos + size_rendered, -RESAMPLE_VALUE(&playing->resampler[0], vol)); + vol = -vol; + if (!IT_IS_SURROUND_SHIFTED(pan)) vol += 2.0f * volume; + if (playing->sample->flags & IT_SAMPLE_STEREO) { + if (sigrenderer->click_remover && sigrenderer->click_remover[1]) + dumb_record_click(sigrenderer->click_remover[1], pos, RESAMPLE_VALUE(&playing->resampler[1], vol)); + RESAMPLE(&playing->resampler[1], samples[1] + pos, vol); + if (sigrenderer->click_remover && sigrenderer->click_remover[1]) + dumb_record_click(sigrenderer->click_remover[1], pos + size_rendered, -RESAMPLE_VALUE(&playing->resampler[1], vol)); + } else { + playing->resampler[0] = start; + if (sigrenderer->click_remover && sigrenderer->click_remover[1]) + dumb_record_click(sigrenderer->click_remover[1], pos, RESAMPLE_VALUE(&playing->resampler[0], vol)); + RESAMPLE(&playing->resampler[0], samples[1] + pos, vol); + if (sigrenderer->click_remover && sigrenderer->click_remover[1]) + dumb_record_click(sigrenderer->click_remover[1], pos + size_rendered, -RESAMPLE_VALUE(&playing->resampler[0], vol)); + } + } else { + if (playing->sample->flags & IT_SAMPLE_STEREO) { + float vol = 0.5f * volume; + if (!IT_IS_SURROUND_SHIFTED(pan)) vol *= 2.0f - pan * (1.0f / (32 << IT_ENVELOPE_SHIFT)); + if (sigrenderer->click_remover && sigrenderer->click_remover[0]) { + sample_t startstep, endstep; + startstep = RESAMPLE_VALUE(&playing->resampler[0], vol); + RESAMPLE(&playing->resampler[0], samples[0] + pos, vol); + endstep = RESAMPLE_VALUE(&playing->resampler[0], vol); + if (!IT_IS_SURROUND_SHIFTED(pan)) vol = 2.0f * volume - vol; + startstep += RESAMPLE_VALUE(&playing->resampler[1], vol); + RESAMPLERV(size_rendered, &playing->resampler[1], samples[0] + pos, vol); + endstep += RESAMPLE_VALUE(&playing->resampler[1], vol); + dumb_record_click(sigrenderer->click_remover[0], pos, startstep); + dumb_record_click(sigrenderer->click_remover[0], pos + size_rendered, -endstep); + } else { + RESAMPLE(&playing->resampler[0], samples[0] + pos, vol); + if (!IT_IS_SURROUND_SHIFTED(pan)) vol = 2.0f * volume - vol; + RESAMPLERV(size_rendered, &playing->resampler[1], samples[0] + pos, vol); + } + } else { + if (sigrenderer->click_remover && sigrenderer->click_remover[0]) + dumb_record_click(sigrenderer->click_remover[0], pos, RESAMPLE_VALUE(&playing->resampler[0], volume)); + RESAMPLERV(size_rendered, &playing->resampler[0], samples[0] + pos, volume); + if (sigrenderer->click_remover && sigrenderer->click_remover[0]) + dumb_record_click(sigrenderer->click_remover[0], pos + size_rendered, -RESAMPLE_VALUE(&playing->resampler[0], volume)); + } + } + (*left_to_mix)--; + } + + if (playing->resampler[0].dir == 0) + playing->flags |= IT_PLAYING_DEAD; + + return size_rendered; +} + + + +typedef struct IT_TO_MIX +{ + IT_PLAYING *playing; + float volume; +} +IT_TO_MIX; + + + +static int it_to_mix_compare(const void *e1, const void *e2) +{ + if (((const IT_TO_MIX *)e1)->volume > ((const IT_TO_MIX *)e2)->volume) + return -1; + + if (((const IT_TO_MIX *)e1)->volume < ((const IT_TO_MIX *)e2)->volume) + return 1; + + return 0; +} + + + +static void apply_pitch_modifications(DUMB_IT_SIGDATA *sigdata, IT_PLAYING *playing, float *delta, int *cutoff) +{ + { + int sample_vibrato_shift = it_sine[playing->sample_vibrato_time]; + + if (sigdata->flags & IT_WAS_AN_XM) { + int depth = playing->sample->vibrato_depth; /* True depth */ + if (playing->sample->vibrato_rate) { + depth *= playing->sample_vibrato_depth; /* Tick number */ + depth /= playing->sample->vibrato_rate; /* XM sweep */ + } + sample_vibrato_shift *= depth; + } else + sample_vibrato_shift *= playing->sample_vibrato_depth >> 8; + + sample_vibrato_shift >>= 4; + + *delta *= (float)pow(DUMB_PITCH_BASE, sample_vibrato_shift); + } + + if (playing->env_instrument && + (playing->env_instrument->pitch_envelope.flags & IT_ENVELOPE_ON)) + { + int p = envelope_get_y(&playing->env_instrument->pitch_envelope, &playing->pitch_envelope); + if (playing->env_instrument->pitch_envelope.flags & IT_ENVELOPE_PITCH_IS_FILTER) + *cutoff = (*cutoff * (p+(32<> (6 + IT_ENVELOPE_SHIFT); + else + *delta *= (float)pow(DUMB_PITCH_BASE, p >> (IT_ENVELOPE_SHIFT - 7)); + } +} + + + +static void render(DUMB_IT_SIGRENDERER *sigrenderer, float volume, float delta, long pos, long size, sample_t **samples) +{ + int i; + + int n_to_mix = 0; + IT_TO_MIX to_mix[DUMB_IT_TOTAL_CHANNELS]; + int left_to_mix = dumb_it_max_to_mix; + + sample_t **samples_to_filter = NULL; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + if (sigrenderer->channel[i].playing && !(sigrenderer->channel[i].playing->flags & IT_PLAYING_DEAD)) { + to_mix[n_to_mix].playing = sigrenderer->channel[i].playing; + to_mix[n_to_mix].volume = volume == 0 ? 0 : calculate_volume(sigrenderer, sigrenderer->channel[i].playing, volume); + n_to_mix++; + } + } + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (sigrenderer->playing[i]) { /* Won't be dead; it would have been freed. */ + to_mix[n_to_mix].playing = sigrenderer->playing[i]; + to_mix[n_to_mix].volume = volume == 0 ? 0 : calculate_volume(sigrenderer, sigrenderer->playing[i], volume); + n_to_mix++; + } + } + + if (volume != 0) + qsort(to_mix, n_to_mix, sizeof(IT_TO_MIX), &it_to_mix_compare); + + for (i = 0; i < n_to_mix; i++) { + IT_PLAYING *playing = to_mix[i].playing; + float note_delta = delta * playing->delta; + int cutoff = playing->filter_cutoff << IT_ENVELOPE_SHIFT; + + apply_pitch_modifications(sigrenderer->sigdata, playing, ¬e_delta, &cutoff); + + if (cutoff != 127 << IT_ENVELOPE_SHIFT || playing->filter_resonance != 0) { + playing->true_filter_cutoff = cutoff; + playing->true_filter_resonance = playing->filter_resonance; + } + + if (to_mix[i].volume && (playing->true_filter_cutoff != 127 << IT_ENVELOPE_SHIFT || playing->true_filter_resonance != 0)) { + if (!samples_to_filter) { + samples_to_filter = create_sample_buffer(sigrenderer->n_channels, size + 1); + if (!samples_to_filter) { + render_playing(sigrenderer, playing, 0, note_delta, pos, size, NULL, 0, &left_to_mix); + continue; + } + } + { + long size_rendered; + DUMB_CLICK_REMOVER **cr = sigrenderer->click_remover; + dumb_silence(samples_to_filter[0], sigrenderer->n_channels * (size + 1)); + sigrenderer->click_remover = NULL; + size_rendered = render_playing(sigrenderer, playing, to_mix[i].volume, note_delta, 0, size, samples_to_filter, 1, &left_to_mix); + sigrenderer->click_remover = cr; + it_filter(cr ? cr[0] : NULL, &playing->filter_state[0], samples[0], pos, samples_to_filter[0], size_rendered, + 65536.0f/delta, playing->true_filter_cutoff, playing->true_filter_resonance); + if (sigrenderer->n_channels == 2) + it_filter(cr ? cr[1] : NULL, &playing->filter_state[1], samples[1], pos, samples_to_filter[1], size_rendered, + 65536.0f/delta, playing->true_filter_cutoff, playing->true_filter_resonance); + // warning: filtering is not prevented by low left_to_mix! + } + } else { + it_reset_filter_state(&playing->filter_state[0]); + it_reset_filter_state(&playing->filter_state[1]); + render_playing(sigrenderer, playing, to_mix[i].volume, note_delta, pos, size, samples, 0, &left_to_mix); + } + } + + destroy_sample_buffer(samples_to_filter); + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + if (sigrenderer->channel[i].playing) { + if ((sigrenderer->channel[i].playing->flags & (IT_PLAYING_BACKGROUND | IT_PLAYING_DEAD)) == (IT_PLAYING_BACKGROUND | IT_PLAYING_DEAD)) { + free(sigrenderer->channel[i].playing); + sigrenderer->channel[i].playing = NULL; + } + } + } + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (sigrenderer->playing[i]) { + if (sigrenderer->playing[i]->flags & IT_PLAYING_DEAD) { + free(sigrenderer->playing[i]); + sigrenderer->playing[i] = NULL; + } + } + } +} + + + +static DUMB_IT_SIGRENDERER *init_sigrenderer(DUMB_IT_SIGDATA *sigdata, int n_channels, int startorder, IT_CALLBACKS *callbacks, DUMB_CLICK_REMOVER **cr) +{ + DUMB_IT_SIGRENDERER *sigrenderer; + int i; + + if (startorder > sigdata->n_orders) { + free(callbacks); + dumb_destroy_click_remover_array(n_channels, cr); + return NULL; + } + + sigrenderer = malloc(sizeof(*sigrenderer)); + if (!sigrenderer) { + free(callbacks); + dumb_destroy_click_remover_array(n_channels, cr); + return NULL; + } + + sigrenderer->callbacks = callbacks; + sigrenderer->click_remover = cr; + + sigrenderer->sigdata = sigdata; + sigrenderer->n_channels = n_channels; + sigrenderer->globalvolume = sigdata->global_volume; + sigrenderer->tempo = sigdata->tempo; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; +#if IT_CHANNEL_MUTED != 1 +#error this is wrong +#endif + channel->flags = sigdata->channel_pan[i] >> 7; + channel->volume = (sigdata->flags & IT_WAS_AN_XM) ? 0 : 64; + channel->pan = sigdata->channel_pan[i] & 0x7F; + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + channel->channelvolume = sigdata->channel_volume[i]; + channel->instrument = 0; + channel->note = 0; + channel->SFmacro = 0; + channel->filter_cutoff = 127; + channel->filter_resonance = 0; + channel->xm_retrig = 0; + channel->retrig_tick = 0; + channel->tremor_time = 0; + channel->midi_state = 0; + channel->lastvolslide = 0; + channel->lastDKL = 0; + channel->lastEF = 0; + channel->lastG = 0; + channel->lastHspeed = 0; + channel->lastHdepth = 0; + channel->lastRspeed = 0; + channel->lastRdepth = 0; + channel->lastI = 0; + channel->lastJ = 0; + channel->lastN = 0; + channel->lastO = 0; + channel->high_offset = 0; + channel->lastQ = 0; + channel->lastS = 0; + channel->pat_loop_row = 0; + channel->pat_loop_count = 0; + channel->lastW = 0; + channel->xm_lastE1 = 0; + channel->xm_lastE2 = 0; + channel->xm_lastEA = 0; + channel->xm_lastEB = 0; + channel->xm_lastX1 = 0; + channel->xm_lastX2 = 0; + channel->playing = NULL; + } + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) + sigrenderer->playing[i] = NULL; + + sigrenderer->speed = sigdata->speed; + + sigrenderer->processrow = 0; + sigrenderer->breakrow = 0; + sigrenderer->pat_loop_row = -1; + sigrenderer->rowcount = 1; + + reset_tick_counts(sigrenderer); + + sigrenderer->tick = sigrenderer->speed; + + { + IT_PATTERN *pattern; + int n; + + sigrenderer->processorder = startorder; + for (;;) { + n = sigdata->order[sigrenderer->processorder]; + + if (n < sigdata->n_patterns) + break; + +#ifdef INVALID_ORDERS_END_SONG + if (n != IT_ORDER_SKIP) +#else + if (n == IT_ORDER_END) +#endif + { + _dumb_it_end_sigrenderer(sigrenderer); + return NULL; + } + + sigrenderer->processorder++; + if (sigrenderer->processorder >= sigdata->n_orders) + sigrenderer->processorder = 0; + if (sigrenderer->processorder == startorder) { + _dumb_it_end_sigrenderer(sigrenderer); + return NULL; + } + } + + pattern = &sigdata->pattern[n]; + + sigrenderer->n_rows = pattern->n_rows; + +/** WARNING - everything pertaining to a new pattern initialised? */ + + sigrenderer->entry = sigrenderer->entry_start = pattern->entry; + sigrenderer->entry_end = sigrenderer->entry + pattern->n_entries; + + sigrenderer->order = sigrenderer->processorder; + sigrenderer->row = 0; + } + + reset_effects(sigrenderer); + + { + IT_ENTRY *entry = sigrenderer->entry; + + while (entry < sigrenderer->entry_end && !IT_IS_END_ROW(entry)) + update_pattern_variables(sigrenderer, entry++); + } + + { + IT_ENTRY *entry = sigrenderer->entry; + + while (entry < sigrenderer->entry_end && !IT_IS_END_ROW(entry)) { + if (process_entry(sigrenderer, entry++)) { + /* Oops, that song ended quickly! */ + _dumb_it_end_sigrenderer(sigrenderer); + return NULL; + } + } + } + + if (!(sigdata->flags & IT_OLD_EFFECTS)) + update_smooth_effects(sigrenderer); + + process_all_playing(sigrenderer); + + sigrenderer->time_left = TICK_TIME_DIVIDEND / sigrenderer->tempo; + sigrenderer->sub_time_left = 0; + + return sigrenderer; +} + + + +void dumb_it_set_loop_callback(DUMB_IT_SIGRENDERER *sigrenderer, int (*callback)(void *data), void *data) +{ + if (sigrenderer) { + sigrenderer->callbacks->loop = callback; + sigrenderer->callbacks->loop_data = data; + } +} + + + +void dumb_it_set_xm_speed_zero_callback(DUMB_IT_SIGRENDERER *sigrenderer, int (*callback)(void *data), void *data) +{ + if (sigrenderer) { + sigrenderer->callbacks->xm_speed_zero = callback; + sigrenderer->callbacks->xm_speed_zero_data = data; + } +} + + + +void dumb_it_set_midi_callback(DUMB_IT_SIGRENDERER *sigrenderer, int (*callback)(void *data, int channel, unsigned char byte), void *data) +{ + if (sigrenderer) { + sigrenderer->callbacks->midi = callback; + sigrenderer->callbacks->midi_data = data; + } +} + + + +static IT_CALLBACKS *create_callbacks(void) +{ + IT_CALLBACKS *callbacks = malloc(sizeof(*callbacks)); + if (!callbacks) return NULL; + callbacks->loop = NULL; + callbacks->xm_speed_zero = NULL; + callbacks->midi = NULL; + return callbacks; +} + + + +static DUMB_IT_SIGRENDERER *dumb_it_init_sigrenderer(DUMB_IT_SIGDATA *sigdata, int n_channels, int startorder) +{ + IT_CALLBACKS *callbacks; + + if (!sigdata) return NULL; + + callbacks = create_callbacks(); + if (!callbacks) return NULL; + + return init_sigrenderer(sigdata, n_channels, startorder, callbacks, + dumb_create_click_remover_array(n_channels)); +} + + + +DUH_SIGRENDERER *dumb_it_start_at_order(DUH *duh, int n_channels, int startorder) +{ + DUMB_IT_SIGDATA *itsd = duh_get_it_sigdata(duh); + DUMB_IT_SIGRENDERER *itsr = dumb_it_init_sigrenderer(itsd, n_channels, startorder); + return duh_encapsulate_it_sigrenderer(itsr, n_channels, 0); +} + + + +static sigrenderer_t *it_start_sigrenderer(DUH *duh, sigdata_t *vsigdata, int n_channels, long pos) +{ + DUMB_IT_SIGDATA *sigdata = vsigdata; + DUMB_IT_SIGRENDERER *sigrenderer; + + (void)duh; + + { + IT_CALLBACKS *callbacks = create_callbacks(); + if (!callbacks) return NULL; + + if (sigdata->checkpoint) { + IT_CHECKPOINT *checkpoint = sigdata->checkpoint; + while (checkpoint->next && checkpoint->next->time < pos) + checkpoint = checkpoint->next; + sigrenderer = dup_sigrenderer(checkpoint->sigrenderer, n_channels, callbacks); + if (!sigrenderer) return NULL; + sigrenderer->click_remover = dumb_create_click_remover_array(n_channels); + pos -= checkpoint->time; + } else { + sigrenderer = init_sigrenderer(sigdata, n_channels, 0, callbacks, + dumb_create_click_remover_array(n_channels)); + if (!sigrenderer) return NULL; + } + } + + for (;;) { + if (sigrenderer->time_left >= pos) + break; + + render(sigrenderer, 0, 1.0f, 0, sigrenderer->time_left, NULL); + + pos -= sigrenderer->time_left; + sigrenderer->time_left = 0; + + if (process_tick(sigrenderer)) { + _dumb_it_end_sigrenderer(sigrenderer); + return NULL; + } + } + + render(sigrenderer, 0, 1.0f, 0, pos, NULL); + sigrenderer->time_left -= pos; + + /** WARNING - everything initialised? */ + + return sigrenderer; +} + + + +static long it_sigrenderer_get_samples( + sigrenderer_t *vsigrenderer, + float volume, float delta, + long size, sample_t **samples +) +{ + DUMB_IT_SIGRENDERER *sigrenderer = vsigrenderer; + long pos; + int dt; + long todo; + LONG_LONG t; + + if (sigrenderer->order < 0) return 0; + + pos = 0; + dt = (int)(delta * 65536.0f + 0.5f); + + /* When samples is finally used in render_playing(), it won't be used if + * volume is 0. + */ + if (!samples) volume = 0; + + for (;;) { + todo = (long)((((LONG_LONG)sigrenderer->time_left << 16) | sigrenderer->sub_time_left) / dt); + + if (todo >= size) + break; + + render(sigrenderer, volume, delta, pos, todo, samples); + + pos += todo; + size -= todo; + + t = sigrenderer->sub_time_left - (LONG_LONG)todo * dt; + sigrenderer->sub_time_left = (long)t & 65535; + sigrenderer->time_left += (long)(t >> 16); + + if (process_tick(sigrenderer)) { + sigrenderer->order = -1; + sigrenderer->row = -1; + return pos; + } + } + + render(sigrenderer, volume, delta, pos, size, samples); + + pos += size; + + t = sigrenderer->sub_time_left - (LONG_LONG)size * dt; + sigrenderer->sub_time_left = (long)t & 65535; + sigrenderer->time_left += (long)(t >> 16); + + dumb_remove_clicks_array(sigrenderer->n_channels, sigrenderer->click_remover, samples, pos, 512.0f / delta); + + return pos; +} + + + +static void it_sigrenderer_get_current_sample(sigrenderer_t *vsigrenderer, float volume, sample_t *samples) +{ + DUMB_IT_SIGRENDERER *sigrenderer = vsigrenderer; + (void)volume; // for consideration: in any of these such functions, is 'volume' going to be required? + dumb_click_remover_get_offset_array(sigrenderer->n_channels, sigrenderer->click_remover, samples); +} + + + +void _dumb_it_end_sigrenderer(sigrenderer_t *vsigrenderer) +{ + DUMB_IT_SIGRENDERER *sigrenderer = vsigrenderer; + + int i; + + if (sigrenderer) { + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) + if (sigrenderer->channel[i].playing) + free(sigrenderer->channel[i].playing); + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) + if (sigrenderer->playing[i]) + free(sigrenderer->playing[i]); + + dumb_destroy_click_remover_array(sigrenderer->n_channels, sigrenderer->click_remover); + + if (sigrenderer->callbacks) + free(sigrenderer->callbacks); + + free(vsigrenderer); + } +} + + + +DUH_SIGTYPE_DESC _dumb_sigtype_it = { + SIGTYPE_IT, + NULL, + &it_start_sigrenderer, + NULL, + &it_sigrenderer_get_samples, + &it_sigrenderer_get_current_sample, + &_dumb_it_end_sigrenderer, + &_dumb_it_unload_sigdata +}; + + + +DUH_SIGRENDERER *duh_encapsulate_it_sigrenderer(DUMB_IT_SIGRENDERER *it_sigrenderer, int n_channels, long pos) +{ + return duh_encapsulate_raw_sigrenderer(it_sigrenderer, &_dumb_sigtype_it, n_channels, pos); +} + + + +DUMB_IT_SIGRENDERER *duh_get_it_sigrenderer(DUH_SIGRENDERER *sigrenderer) +{ + return duh_get_raw_sigrenderer(sigrenderer, SIGTYPE_IT); +} + + + +/* Values of 64 or more will access NNA channels here. */ +void dumb_it_sr_get_channel_state(DUMB_IT_SIGRENDERER *sr, int channel, DUMB_IT_CHANNEL_STATE *state) +{ + IT_PLAYING *playing; + int t; /* temporary var for holding accurate pan and filter cutoff */ + float delta; + ASSERT(channel < DUMB_IT_TOTAL_CHANNELS); + if (!sr) { state->sample = 0; return; } + if (channel >= DUMB_IT_N_CHANNELS) { + playing = sr->playing[channel - DUMB_IT_N_CHANNELS]; + if (!playing) { state->sample = 0; return; } + } else { + playing = sr->channel[channel].playing; + if (!playing) { state->sample = 0; return; } + } + + if (playing->flags & IT_PLAYING_DEAD) { state->sample = 0; return; } + + state->channel = playing->channel - sr->channel; + state->sample = playing->sampnum; + state->volume = calculate_volume(sr, playing, 1.0f); + + t = apply_pan_envelope(playing); + state->pan = (unsigned char)((t + 128) >> IT_ENVELOPE_SHIFT); + state->subpan = (signed char)t; + + delta = playing->delta * 65536.0f; + t = playing->filter_cutoff << IT_ENVELOPE_SHIFT; + apply_pitch_modifications(sr->sigdata, playing, &delta, &t); + state->freq = (int)delta; + if (t == 127 << IT_ENVELOPE_SHIFT && playing->filter_resonance == 0) { + state->filter_resonance = playing->true_filter_resonance; + t = playing->true_filter_cutoff; + } else + state->filter_resonance = playing->filter_resonance; + state->filter_cutoff = (unsigned char)(t >> 8); + state->filter_subcutoff = (unsigned char)t; +} + + + +int dumb_it_callback_terminate(void *data) +{ + (void)data; + return 1; +} + + + +int dumb_it_callback_midi_block(void *data, int channel, unsigned char byte) +{ + (void)data; + (void)channel; + (void)byte; + return 1; +} + + + +#define IT_CHECKPOINT_INTERVAL (30 * 65536) /* Half a minute */ + + + +/* Returns the length of the module, up until it first loops. */ +long _dumb_it_build_checkpoints(DUMB_IT_SIGDATA *sigdata) +{ + IT_CHECKPOINT *checkpoint = malloc(sizeof(*checkpoint)); + if (!checkpoint) return 0; + checkpoint->time = 0; + checkpoint->sigrenderer = dumb_it_init_sigrenderer(sigdata, 0, 0); + if (!checkpoint->sigrenderer) { + free(checkpoint); + return 0; + } + checkpoint->sigrenderer->callbacks->loop = &dumb_it_callback_terminate; + checkpoint->sigrenderer->callbacks->xm_speed_zero = &dumb_it_callback_terminate; + sigdata->checkpoint = checkpoint; + + for (;;) { + long l; + DUMB_IT_SIGRENDERER *sigrenderer = dup_sigrenderer(checkpoint->sigrenderer, 0, checkpoint->sigrenderer->callbacks); + checkpoint->sigrenderer->callbacks = NULL; + if (!sigrenderer) { + checkpoint->next = NULL; + return checkpoint->time; + } + + l = it_sigrenderer_get_samples(sigrenderer, 0, 1.0f, IT_CHECKPOINT_INTERVAL, NULL); + if (l < IT_CHECKPOINT_INTERVAL) { + _dumb_it_end_sigrenderer(sigrenderer); + checkpoint->next = NULL; + return checkpoint->time + l; + } + + checkpoint->next = malloc(sizeof(*checkpoint->next)); + if (!checkpoint->next) { + _dumb_it_end_sigrenderer(sigrenderer); + return checkpoint->time + IT_CHECKPOINT_INTERVAL; + } + + checkpoint->next->time = checkpoint->time + IT_CHECKPOINT_INTERVAL; + checkpoint = checkpoint->next; + checkpoint->sigrenderer = sigrenderer; + } +} diff --git a/apps/codecs/dumb/src/it/itunload.c b/apps/codecs/dumb/src/it/itunload.c new file mode 100644 index 0000000000..8f282bea2e --- /dev/null +++ b/apps/codecs/dumb/src/it/itunload.c @@ -0,0 +1,71 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * itunload.c - Code to free an Impulse Tracker / / \ \ + * module from memory. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/it.h" + + + +void _dumb_it_unload_sigdata(sigdata_t *vsigdata) +{ + if (vsigdata) { + DUMB_IT_SIGDATA *sigdata = vsigdata; + int n; + + if (sigdata->order) + free(sigdata->order); + + if (sigdata->instrument) + free(sigdata->instrument); + + if (sigdata->sample) { + for (n = 0; n < sigdata->n_samples; n++) { + if (sigdata->sample[n].left) + free(sigdata->sample[n].left); + if (sigdata->sample[n].right) + free(sigdata->sample[n].right); + } + free(sigdata->sample); + } + + if (sigdata->pattern) { + for (n = 0; n < sigdata->n_patterns; n++) + if (sigdata->pattern[n].entry) + free(sigdata->pattern[n].entry); + free(sigdata->pattern); + } + + if (sigdata->midi) + free(sigdata->midi); + + { + IT_CHECKPOINT *checkpoint = sigdata->checkpoint; + while (checkpoint) { + IT_CHECKPOINT *next = checkpoint->next; + _dumb_it_end_sigrenderer(checkpoint->sigrenderer); + free(checkpoint); + checkpoint = next; + } + } + + free(vsigdata); + } +} diff --git a/apps/codecs/dumb/src/it/loadmod.c b/apps/codecs/dumb/src/it/loadmod.c new file mode 100644 index 0000000000..f7bbcb50e6 --- /dev/null +++ b/apps/codecs/dumb/src/it/loadmod.c @@ -0,0 +1,42 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * loadmod.c - Code to read a good old-fashioned / / \ \ + * Amiga module file, opening and | < / \_ + * closing it for you. | \/ /\ / + * \_ / > / + * By entheh. | \ / / + * | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/it.h" + + + +/* dumb_load_mod(): loads a MOD file into a DUH struct, returning a pointer + * to the DUH struct. When you have finished with it, you must pass the + * pointer to unload_duh() so that the memory can be freed. + */ +DUH *dumb_load_mod(const char *filename) +{ + DUH *duh; + DUMBFILE *f = dumbfile_open(filename); + + if (!f) + return NULL; + + duh = dumb_read_mod(f); + + dumbfile_close(f); + + return duh; +} diff --git a/apps/codecs/dumb/src/it/loads3m.c b/apps/codecs/dumb/src/it/loads3m.c new file mode 100644 index 0000000000..dfe6a9268e --- /dev/null +++ b/apps/codecs/dumb/src/it/loads3m.c @@ -0,0 +1,42 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * loads3m.c - Code to read a ScreamTracker 3 / / \ \ + * file, opening and closing it for | < / \_ + * you. | \/ /\ / + * \_ / > / + * By entheh. | \ / / + * | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/it.h" + + + +/* dumb_load_s3m(): loads an S3M file into a DUH struct, returning a pointer + * to the DUH struct. When you have finished with it, you must pass the + * pointer to unload_duh() so that the memory can be freed. + */ +DUH *dumb_load_s3m(const char *filename) +{ + DUH *duh; + DUMBFILE *f = dumbfile_open(filename); + + if (!f) + return NULL; + + duh = dumb_read_s3m(f); + + dumbfile_close(f); + + return duh; +} diff --git a/apps/codecs/dumb/src/it/loadxm.c b/apps/codecs/dumb/src/it/loadxm.c new file mode 100644 index 0000000000..9cee06d6ee --- /dev/null +++ b/apps/codecs/dumb/src/it/loadxm.c @@ -0,0 +1,42 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * loadxm.c - Code to read a Fast Tracker II / / \ \ + * file, opening and closing it for | < / \_ + * you. | \/ /\ / + * \_ / > / + * By entheh. | \ / / + * | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/it.h" + + + +/* dumb_load_xm(): loads an XM file into a DUH struct, returning a pointer + * to the DUH struct. When you have finished with it, you must pass the + * pointer to unload_duh() so that the memory can be freed. + */ +DUH *dumb_load_xm(const char *filename) +{ + DUH *duh; + DUMBFILE *f = dumbfile_open(filename); + + if (!f) + return NULL; + + duh = dumb_read_xm(f); + + dumbfile_close(f); + + return duh; +} diff --git a/apps/codecs/dumb/src/it/readmod.c b/apps/codecs/dumb/src/it/readmod.c new file mode 100644 index 0000000000..452c8900a4 --- /dev/null +++ b/apps/codecs/dumb/src/it/readmod.c @@ -0,0 +1,594 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * readmod.c - Code to read a good old-fashioned / / \ \ + * Amiga module from an open file. | < / \_ + * | \/ /\ / + * By Ben Davis. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include +#include + +#include "dumb.h" +#include "internal/it.h" + + + +static int it_mod_read_pattern(IT_PATTERN *pattern, DUMBFILE *f, int n_channels, unsigned char *buffer) +{ + int pos; + int channel; + int row; + IT_ENTRY *entry; + + pattern->n_rows = 64; + + if (n_channels == 0) { + /* Read the first four channels, leaving gaps for the rest. */ + for (pos = 0; pos < 64*8*4; pos += 8*4) + dumbfile_getnc(buffer + pos, 4*4, f); + /* Read the other channels into the gaps we left. */ + for (pos = 4*4; pos < 64*8*4; pos += 8*4) + dumbfile_getnc(buffer + pos, 4*4, f); + + n_channels = 8; + } else + dumbfile_getnc(buffer, 64 * n_channels * 4, f); + + if (dumbfile_error(f)) + return -1; + + /* compute number of entries */ + pattern->n_entries = 64; /* Account for the row end markers */ + pos = 0; + for (row = 0; row < 64; row++) { + for (channel = 0; channel < n_channels; channel++) { + if (buffer[pos+0] | buffer[pos+1] | buffer[pos+2] | buffer[pos+3]) + pattern->n_entries++; + pos += 4; + } + } + + pattern->entry = malloc(pattern->n_entries * sizeof(*pattern->entry)); + if (!pattern->entry) + return -1; + + entry = pattern->entry; + pos = 0; + for (row = 0; row < 64; row++) { + for (channel = 0; channel < n_channels; channel++) { + if (buffer[pos+0] | buffer[pos+1] | buffer[pos+2] | buffer[pos+3]) { + unsigned char sample = (buffer[pos+0] & 0xF0) | (buffer[pos+2] >> 4); + int period = ((int)(buffer[pos+0] & 0x0F) << 8) | buffer[pos+1]; + + entry->channel = channel; + entry->mask = 0; + + if (period) { + int note; + entry->mask |= IT_ENTRY_NOTE; + + /* frequency = (AMIGA_DIVISOR / 8) / (period * 2) + * C-1: period = 214 -> frequency = 16726 + * so, set C5_speed to 16726 + * and period = 214 should translate to C5 aka 60 + * halve the period, go up an octive + * + * period = 214 / pow(DUMB_SEMITONE_BASE, note - 60) + * pow(DUMB_SEMITONE_BASE, note - 60) = 214 / period + * note - 60 = log(214/period) / log(DUMB_SEMITONE_BASE) + */ + note = (int)floor(log(214.0/period) / log(DUMB_SEMITONE_BASE) + 60.5); + entry->note = MID(0, note, 119); + // or should we preserve the period? + //entry->note = buffer[pos+0] & 0x0F; /* High nibble */ + //entry->volpan = buffer[pos+1]; /* Low byte */ + // and what about finetune? + } + + if (sample) { + entry->mask |= IT_ENTRY_INSTRUMENT; + entry->instrument = sample; + } + + _dumb_it_xm_convert_effect(buffer[pos+2] & 0x0F, buffer[pos+3], entry); + + entry++; + } + pos += 4; + } + IT_SET_END_ROW(entry); + entry++; + } + + return 0; +} + + + +/* This function does not skip the name (22 bytes); it is assumed the caller + * has already done that. + */ +static int it_mod_read_sample_header(IT_SAMPLE *sample, DUMBFILE *f) +{ + int finetune; + +/** + 21 22 Chars Sample 1 name. If the name is not a full + 22 chars in length, it will be null + terminated. + +If +the sample name begins with a '#' character (ASCII $23 (35)) then this is +assumed not to be an instrument name, and is probably a message. +*/ + sample->length = dumbfile_mgetw(f) << 1; + finetune = (signed char)(dumbfile_getc(f) << 4) >> 4; /* signed nibble */ +/** Each finetune step changes the note 1/8th of a semitone. */ + sample->global_volume = 64; + sample->default_volume = dumbfile_getc(f); // Should we be setting global_volume to this instead? + sample->loop_start = dumbfile_mgetw(f) << 1; + sample->loop_end = sample->loop_start + (dumbfile_mgetw(f) << 1); +/** +Once this sample has been played completely from beginning +to end, if the repeat length (next field) is greater than two bytes it +will loop back to this position in the sample and continue playing. Once +it has played for the repeat length, it continues to loop back to the +repeat start offset. This means the sample continues playing until it is +told to stop. +*/ + + if (sample->length <= 0) { + sample->flags = 0; + return 0; + } + + sample->flags = IT_SAMPLE_EXISTS; + + sample->default_pan = 0; + sample->C5_speed = (long)(16726.0*pow(DUMB_PITCH_BASE, finetune*32)); + // the above line might be wrong + + if (sample->loop_end > sample->length) + sample->loop_end = sample->length; + + if (sample->loop_end - sample->loop_start > 2) + sample->flags |= IT_SAMPLE_LOOP; + + sample->vibrato_speed = 0; + sample->vibrato_depth = 0; + sample->vibrato_rate = 0; + sample->vibrato_waveform = 0; // do we have to set _all_ these? + + return dumbfile_error(f); +} + + + +static int it_mod_read_sample_data(IT_SAMPLE *sample, DUMBFILE *f) +{ + long i; + long truncated_size; + + /* let's get rid of the sample data coming after the end of the loop */ + if ((sample->flags & IT_SAMPLE_LOOP) && sample->loop_end < sample->length) { + truncated_size = sample->length - sample->loop_end; + sample->length = sample->loop_end; + } else { + truncated_size = 0; + } + + sample->left = malloc(sample->length * sizeof(*sample->left)); + + if (!sample->left) + return -1; + + /* Sample data are stored in "8-bit two's compliment format" (sic). */ + for (i = 0; i < sample->length; i++) + sample->left[i] = (int)(signed char)dumbfile_getc(f) << 16; + + /* skip truncated data */ + dumbfile_skip(f, truncated_size); + // Should we be truncating it? + + if (dumbfile_error(f)) + return -1; + + return 0; +} + + + +typedef struct BUFFERED_MOD BUFFERED_MOD; + +struct BUFFERED_MOD +{ + unsigned char *buffered; + long ptr, len; + DUMBFILE *remaining; +}; + + + +static int buffer_mod_skip(void *f, long n) +{ + BUFFERED_MOD *bm = f; + if (bm->buffered) { + bm->ptr += n; + if (bm->ptr >= bm->len) { + free(bm->buffered); + bm->buffered = NULL; + return dumbfile_skip(bm->remaining, bm->ptr - bm->len); + } + return 0; + } + return dumbfile_skip(bm->remaining, n); +} + + + +static int buffer_mod_getc(void *f) +{ + BUFFERED_MOD *bm = f; + if (bm->buffered) { + int rv = bm->buffered[bm->ptr++]; + if (bm->ptr >= bm->len) { + free(bm->buffered); + bm->buffered = NULL; + } + return rv; + } + return dumbfile_getc(bm->remaining); +} + + + +static long buffer_mod_getnc(char *ptr, long n, void *f) +{ + BUFFERED_MOD *bm = f; + if (bm->buffered) { + int left = bm->len - bm->ptr; + if (n >= left) { + int rv; + memcpy(ptr, bm->buffered + bm->ptr, left); + free(bm->buffered); + bm->buffered = NULL; + rv = dumbfile_getnc(ptr + left, n - left, bm->remaining); + return left + MAX(rv, 0); + } + memcpy(ptr, bm->buffered + bm->ptr, n); + bm->ptr += n; + return n; + } + return dumbfile_getnc(ptr, n, bm->remaining); +} + + + +static void buffer_mod_close(void *f) +{ + BUFFERED_MOD *bm = f; + if (bm->buffered) free(bm->buffered); + /* Do NOT close bm->remaining */ + free(f); +} + + + +DUMBFILE_SYSTEM buffer_mod_dfs = { + NULL, + &buffer_mod_skip, + &buffer_mod_getc, + &buffer_mod_getnc, + &buffer_mod_close +}; + + + +#define MOD_FFT_OFFSET (20 + 31*(22+2+1+1+2+2) + 1 + 1 + 128) + +static DUMBFILE *dumbfile_buffer_mod(DUMBFILE *f, unsigned long *fft) +{ + BUFFERED_MOD *bm = malloc(sizeof(*bm)); + if (!bm) return NULL; + + bm->buffered = malloc(MOD_FFT_OFFSET + 4); + if (!bm->buffered) { + free(bm); + return NULL; + } + + bm->len = dumbfile_getnc(bm->buffered, MOD_FFT_OFFSET + 4, f); + + if (bm->len > 0) { + if (bm->len >= MOD_FFT_OFFSET + 4) + *fft = (unsigned long)bm->buffered[MOD_FFT_OFFSET ] << 24 + | (unsigned long)bm->buffered[MOD_FFT_OFFSET+1] << 16 + | (unsigned long)bm->buffered[MOD_FFT_OFFSET+2] << 8 + | (unsigned long)bm->buffered[MOD_FFT_OFFSET+3]; + else + *fft = 0; + bm->ptr = 0; + } else { + free(bm->buffered); + bm->buffered = NULL; + } + + bm->remaining = f; + + return dumbfile_open_ex(bm, &buffer_mod_dfs); +} + + + +static DUMB_IT_SIGDATA *it_mod_load_sigdata(DUMBFILE *f) +{ + DUMB_IT_SIGDATA *sigdata; + int n_channels; + int i; + unsigned long fft; + + f = dumbfile_buffer_mod(f, &fft); + if (!f) + return NULL; + + /** + 1 20 Chars Title of the song. If the title is not a + full 20 chars in length, it will be null- + terminated. + */ + if (dumbfile_skip(f, 20)) { + dumbfile_close(f); + return NULL; + } + + sigdata = malloc(sizeof(*sigdata)); + if (!sigdata) { + dumbfile_close(f); + return NULL; + } + + sigdata->n_samples = 31; + + switch (fft) { + case DUMB_ID('M','.','K','.'): + case DUMB_ID('M','!','K','!'): + case DUMB_ID('M','&','K','!'): + case DUMB_ID('N','.','T','.'): + case DUMB_ID('F','L','T','4'): + n_channels = 4; + break; + case DUMB_ID('F','L','T','8'): + n_channels = 0; + /* 0 indicates a special case; two four-channel patterns must be + * combined into one eight-channel pattern. Pattern indexes must + * be halved. Why oh why do they obfuscate so? + */ + for (i = 0; i < 128; i++) + sigdata->order[i] >>= 1; + break; + case DUMB_ID('C','D','8','1'): + case DUMB_ID('O','C','T','A'): + case DUMB_ID('O','K','T','A'): + n_channels = 8; + break; + case DUMB_ID('1','6','C','N'): + n_channels = 16; + break; + case DUMB_ID('3','2','C','N'): + n_channels = 32; + break; + default: + /* If we get an illegal tag, assume 4 channels 15 samples. */ + if ((fft & 0x0000FFFFL) == DUMB_ID(0,0,'C','H')) { + if (fft >= '1' << 24 && fft < '4' << 24) { + n_channels = ((fft & 0x00FF0000L) >> 16) - '0'; + if ((unsigned int)n_channels >= 10) { + /* Rightmost character wasn't a digit. */ + n_channels = 4; + sigdata->n_samples = 15; + } else { + n_channels += (((fft & 0xFF000000L) >> 24) - '0') * 10; + /* MODs should really only go up to 32 channels, but we're lenient. */ + if ((unsigned int)(n_channels - 1) >= DUMB_IT_N_CHANNELS - 1) { + /* No channels or too many? Can't be right... */ + n_channels = 4; + sigdata->n_samples = 15; + } + } + } else { + n_channels = 4; + sigdata->n_samples = 15; + } + } else if ((fft & 0x00FFFFFFL) == DUMB_ID(0,'C','H','N')) { + n_channels = (fft >> 24) - '0'; + if ((unsigned int)(n_channels - 1) >= 9) { + /* Character was '0' or it wasn't a digit */ + n_channels = 4; + sigdata->n_samples = 15; + } + } else if ((fft & 0xFFFFFF00L) == DUMB_ID('T','D','Z',0)) { + n_channels = (fft & 0x000000FFL) - '0'; + if ((unsigned int)(n_channels - 1) >= 9) { + /* We've been very lenient, given that it should have + * been 1, 2 or 3, but this MOD has been very naughty and + * must be punished. + */ + n_channels = 4; + sigdata->n_samples = 15; + } + } else { + n_channels = 4; + sigdata->n_samples = 15; + } + } + + sigdata->sample = malloc(sigdata->n_samples * sizeof(*sigdata->sample)); + if (!sigdata->sample) { + free(sigdata); + dumbfile_close(f); + return NULL; + } + + sigdata->order = NULL; + sigdata->instrument = NULL; + sigdata->pattern = NULL; + sigdata->midi = NULL; + sigdata->checkpoint = NULL; + + for (i = 0; i < sigdata->n_samples; i++) + sigdata->sample[i].right = sigdata->sample[i].left = NULL; + + for (i = 0; i < sigdata->n_samples; i++) { + if (dumbfile_skip(f, 22) || + it_mod_read_sample_header(&sigdata->sample[i], f)) + { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + } + + sigdata->n_orders = dumbfile_getc(f); + sigdata->restart_position = dumbfile_getc(f); + // what if this is >= 127? what about with Fast Tracker II? + + if (sigdata->n_orders <= 0 || sigdata->n_orders > 128) { // is this right? + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + + //if (sigdata->restart_position >= sigdata->n_orders) + //sigdata->restart_position = 0; + + sigdata->order = malloc(128); /* We may need to scan the extra ones! */ + if (!sigdata->order) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + if (dumbfile_getnc(sigdata->order, 128, f) < 128) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + + /* "The old NST format contains only 15 samples (instead of 31). Further + * it doesn't contain a file format tag (id). So Pattern data offset is + * at 20+15*30+1+1+128." + * - Then I shall assume the File Format Tag never exists if there are + * only 15 samples. I hope this isn't a faulty assumption... + */ + if (sigdata->n_samples == 31) + dumbfile_skip(f, 4); + + /* Work out how many patterns there are. */ + sigdata->n_patterns = -1; + for (i = 0; i < 128; i++) + if (sigdata->n_patterns < sigdata->order[i]) + sigdata->n_patterns = sigdata->order[i]; + sigdata->n_patterns++; + + /* May as well try to save a tiny bit of memory. */ + if (sigdata->n_orders < 128) { + unsigned char *order = realloc(sigdata->order, sigdata->n_orders); + if (order) sigdata->order = order; + } + + sigdata->pattern = malloc(sigdata->n_patterns * sizeof(*sigdata->pattern)); + if (!sigdata->pattern) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + for (i = 0; i < sigdata->n_patterns; i++) + sigdata->pattern[i].entry = NULL; + + /* Read in the patterns */ + { + unsigned char *buffer = malloc(256 * n_channels); /* 64 rows * 4 bytes */ + if (!buffer) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + for (i = 0; i < sigdata->n_patterns; i++) { + if (it_mod_read_pattern(&sigdata->pattern[i], f, n_channels, buffer) != 0) { + free(buffer); + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + } + free(buffer); + } + + /* And finally, the sample data */ + for (i = 0; i < sigdata->n_samples; i++) { + if (it_mod_read_sample_data(&sigdata->sample[i], f)) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + } + + dumbfile_close(f); /* Destroy the BUFFERED_MOD DUMBFILE we were using. */ + /* The DUMBFILE originally passed to our function is intact. */ + + /* Now let's initialise the remaining variables, and we're done! */ + sigdata->flags = IT_WAS_AN_XM | IT_WAS_A_MOD | IT_OLD_EFFECTS | IT_COMPATIBLE_GXX | IT_STEREO; + + sigdata->global_volume = 128; + sigdata->mixing_volume = 48; + /* We want 50 ticks per second; 50/6 row advances per second; + * 50*10=500 row advances per minute; 500/4=125 beats per minute. + */ + sigdata->speed = 6; + sigdata->tempo = 125; + sigdata->pan_separation = 128; + + memset(sigdata->channel_volume, 64, DUMB_IT_N_CHANNELS); + + for (i = 0; i < DUMB_IT_N_CHANNELS; i += 4) { + sigdata->channel_pan[i+0] = 16; + sigdata->channel_pan[i+1] = 48; + sigdata->channel_pan[i+2] = 48; + sigdata->channel_pan[i+3] = 16; + } + + _dumb_it_fix_invalid_orders(sigdata); + + return sigdata; +} + + + +DUH *dumb_read_mod(DUMBFILE *f) +{ + sigdata_t *sigdata; + long length; + + DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it; + + sigdata = it_mod_load_sigdata(f); + + if (!sigdata) + return NULL; + + length = _dumb_it_build_checkpoints(sigdata); + + return make_duh(length, 1, &descptr, &sigdata); +} diff --git a/apps/codecs/dumb/src/it/reads3m.c b/apps/codecs/dumb/src/it/reads3m.c new file mode 100644 index 0000000000..6a8b84dd55 --- /dev/null +++ b/apps/codecs/dumb/src/it/reads3m.c @@ -0,0 +1,668 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * reads3m.c - Code to read a ScreamTracker 3 / / \ \ + * module from an open file. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +// IT_STEREO... :o +#include +#include + +#include "dumb.h" +#include "internal/it.h" + + + +/** WARNING: this is duplicated in itread.c */ +static int it_seek(DUMBFILE *f, long offset) +{ + long pos = dumbfile_pos(f); + + if (pos > offset) + return -1; + + if (pos < offset) + if (dumbfile_skip(f, offset - pos)) + return -1; + + return 0; +} + + + +static int it_s3m_read_sample_header(IT_SAMPLE *sample, long *offset, DUMBFILE *f) +{ + unsigned char type; + int flags; + + type = dumbfile_getc(f); + + if (type > 1) { + /** WARNING: no adlib support */ + } + + /* Skip DOS Filename */ + dumbfile_skip(f, 13); + + *offset = dumbfile_igetw(f) << 4; + + sample->length = dumbfile_igetl(f); + sample->loop_start = dumbfile_igetl(f); + sample->loop_end = dumbfile_igetl(f); + + sample->default_volume = dumbfile_getc(f); + + dumbfile_skip(f, 1); + + if (dumbfile_getc(f) != 0) + /* Sample is packed apparently (or error reading from file). We don't + * know how to read packed samples. + */ + return -1; + + flags = dumbfile_getc(f); + + sample->C5_speed = dumbfile_igetl(f) << 1; + + /* Skip four unused bytes, three internal variables and sample name. */ + dumbfile_skip(f, 4+2+2+4+28); + + if (type == 0) { + /* Looks like no-existy. Anyway, there's for sure no 'SCRS'... */ + sample->flags &= ~IT_SAMPLE_EXISTS; + return dumbfile_error(f); + } + + if (dumbfile_mgetl(f) != DUMB_ID('S','C','R','S')) + return -1; + + sample->global_volume = 64; + + sample->flags = IT_SAMPLE_EXISTS; + if (flags & 1) sample->flags |= IT_SAMPLE_LOOP; + if (flags & 2) sample->flags |= IT_SAMPLE_STEREO; + if (flags & 4) sample->flags |= IT_SAMPLE_16BIT; + + sample->default_pan = 0; // 0 = don't use, or 160 = centre? + + if (sample->length <= 0) + sample->flags &= ~IT_SAMPLE_EXISTS; + else if (sample->flags & IT_SAMPLE_LOOP) { + if ((unsigned int)sample->loop_end > (unsigned int)sample->length) + sample->flags &= ~IT_SAMPLE_LOOP; + else if ((unsigned int)sample->loop_start >= (unsigned int)sample->loop_end) + sample->flags &= ~IT_SAMPLE_LOOP; + else + /* ScreamTracker seems not to save what comes after the loop end + * point, but rather to assume it is a duplicate of what comes at + * the loop start point. I am not completely sure of this though. + * It is easy to evade; simply truncate the sample. + */ + sample->length = sample->loop_end; + } + + + //Do we need to set all these? + sample->vibrato_speed = 0; + sample->vibrato_depth = 0; + sample->vibrato_rate = 0; + sample->vibrato_waveform = IT_VIBRATO_SINE; + + return dumbfile_error(f); +} + + + +static int it_s3m_read_sample_data(IT_SAMPLE *sample, int ffi, DUMBFILE *f) +{ + long n; + + sample->left = malloc(sample->length * sizeof(*sample->left)); + if (!sample->left) + return -1; + + if (sample->flags & IT_SAMPLE_STEREO) { + sample->right = malloc(sample->length * sizeof(*sample->right)); + if (!sample->right) + return -1; + } + + if (sample->flags & IT_SAMPLE_STEREO) { + if (sample->flags & IT_SAMPLE_16BIT) { + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed short)dumbfile_igetw(f) << 8; + for (n = 0; n < sample->length; n++) + sample->right[n] = (int)(signed short)dumbfile_igetw(f) << 8; + } else { + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed char)dumbfile_getc(f) << 16; + for (n = 0; n < sample->length; n++) + sample->right[n] = (int)(signed char)dumbfile_getc(f) << 16; + } + } else if (sample->flags & IT_SAMPLE_16BIT) + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed short)dumbfile_igetw(f) << 8; + else + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed char)dumbfile_getc(f) << 16; + + if (dumbfile_error(f)) + return -1; + + if (ffi != 1) { + /* Convert to signed. */ + for (n = 0; n < sample->length; n++) + sample->left[n] ^= 0xFF800000; + + if (sample->right) + for (n = 0; n < sample->length; n++) + sample->right[n] ^= 0xFF800000; + } + + return 0; +} + + + +static int it_s3m_read_pattern(IT_PATTERN *pattern, DUMBFILE *f, unsigned char *buffer) +{ + int buflen = 0; + int bufpos = 0; + + IT_ENTRY *entry; + + unsigned char channel; + + /* Haha, this is hilarious! + * + * Well, after some experimentation, it seems that different S3M writers + * define the format in different ways. The S3M docs say that the first + * two bytes hold the "length of [the] packed pattern", and the packed + * pattern data follow. Judging by the contents of ARMANI.S3M, packaged + * with ScreamTracker itself, the measure of length _includes_ the two + * bytes used to store the length; in other words, we should read + * (length - 2) more bytes. However, aryx.s3m, packaged with ModPlug + * Tracker, excludes these two bytes, so (length) more bytes must be + * read. + * + * Call me crazy, but I just find it insanely funny that the format was + * misunderstood in this way :D + * + * Now we can't just risk reading two extra bytes, because then we + * overshoot, and DUMBFILEs don't support backward seeking (for a good + * reason). Luckily, there is a way. We can read the data little by + * little, and stop when we have 64 rows in memory. Provided we protect + * against buffer overflow, this method should work with all sensibly + * written S3M files. If you find one for which it does not work, please + * let me know at entheh@users.sf.net so I can look at it. + */ + + /* Discard the length. */ + dumbfile_skip(f, 2); + + if (dumbfile_error(f)) + return -1; + + pattern->n_rows = 0; + pattern->n_entries = 0; + + /* Read in the pattern data, little by little, and work out how many + * entries we need room for. Sorry, but this is just so funny... + */ + for (;;) { + unsigned char b = buffer[buflen++] = dumbfile_getc(f); + +#if 1 + static const unsigned char used[8] = {0, 2, 1, 3, 2, 4, 3, 5}; + channel = b & 31; + b >>= 5; + pattern->n_entries++; + if (b) { + if (buflen + used[b] >= 65536) return -1; + dumbfile_getnc(buffer + buflen, used[b], f); + buflen += used[b]; + } else { + /* End of row */ + if (++pattern->n_rows == 64) break; + if (buflen >= 65536) return -1; + } +#else + if (b == 0) { + /* End of row */ + pattern->n_entries++; + if (++pattern->n_rows == 64) break; + if (buflen >= 65536) return -1; + } else { + static const unsigned char used[8] = {0, 2, 1, 3, 2, 4, 3, 5}; + channel = b & 31; + b >>= 5; + if (b) { + pattern->n_entries++; + if (buflen + used[b] >= 65536) return -1; + dumbfile_getnc(buffer + buflen, used[b], f); + buflen += used[b]; + } + } +#endif + + /* We have ensured that buflen < 65536 at this point, so it is safe + * to iterate and read at least one more byte without checking. + * However, now would be a good time to check for errors reading from + * the file. + */ + + if (dumbfile_error(f)) + return -1; + } + + pattern->entry = malloc(pattern->n_entries * sizeof(*pattern->entry)); + + if (!pattern->entry) + return -1; + + entry = pattern->entry; + + while (bufpos < buflen) { + unsigned char b = buffer[bufpos++]; + +#if 1 + if (!(b & ~31)) +#else + if (b == 0) +#endif + { + /* End of row */ + IT_SET_END_ROW(entry); + entry++; + continue; + } + + channel = b & 31; + + if (b & 224) { + entry->mask = 0; + entry->channel = channel; + + if (b & 32) { + unsigned char n = buffer[bufpos++]; + if (n != 255) { + if (n == 254) + entry->note = IT_NOTE_CUT; + else + entry->note = (n >> 4) * 12 + (n & 15); + entry->mask |= IT_ENTRY_NOTE; + } + + entry->instrument = buffer[bufpos++]; + if (entry->instrument) + entry->mask |= IT_ENTRY_INSTRUMENT; + } + + if (b & 64) { + entry->volpan = buffer[bufpos++]; + if (entry->volpan != 255) + entry->mask |= IT_ENTRY_VOLPAN; + } + + if (b & 128) { + entry->effect = buffer[bufpos++]; + entry->effectvalue = buffer[bufpos++]; + if (entry->effect != 255) { + entry->mask |= IT_ENTRY_EFFECT; + if (entry->effect == IT_BREAK_TO_ROW) + entry->effectvalue -= (entry->effectvalue >> 4) * 6; + } + /** WARNING: ARGH! CONVERT TEH EFFECTS!@~ */ + } + + entry++; + } + } + + ASSERT(entry == pattern->entry + pattern->n_entries); + + return 0; +} + + + +/** WARNING: this is duplicated in itread.c - also bad practice to use the same struct name unless they are unified in a header */ +/* Currently we assume the sample data are stored after the sample headers in + * module files. This assumption may be unjustified; let me know if you have + * trouble. + */ + +#define IT_COMPONENT_INSTRUMENT 1 +#define IT_COMPONENT_PATTERN 2 +#define IT_COMPONENT_SAMPLE 3 + +typedef struct IT_COMPONENT +{ + unsigned char type; + unsigned char n; + long offset; + short sampfirst; /* component[sampfirst] = first sample data after this */ + short sampnext; /* sampnext is used to create linked lists of sample data */ +} +IT_COMPONENT; + + + +static int it_component_compare(const void *e1, const void *e2) +{ + return ((const IT_COMPONENT *)e1)->offset - + ((const IT_COMPONENT *)e2)->offset; +} + + + +static DUMB_IT_SIGDATA *it_s3m_load_sigdata(DUMBFILE *f) +{ + DUMB_IT_SIGDATA *sigdata; + + int flags, cwtv, ffi; + int default_pan_present; + + IT_COMPONENT *component; + int n_components = 0; + + int n; + + unsigned char *buffer; + + /* Skip song name. */ + if (dumbfile_skip(f, 28)) return NULL; + + if (dumbfile_getc(f) != 0x1A) return NULL; + if (dumbfile_getc(f) != 16) return NULL; + + if (dumbfile_skip(f, 2)) return NULL; + + sigdata = malloc(sizeof(*sigdata)); + + if (!sigdata) + return NULL; + + sigdata->order = NULL; + sigdata->instrument = NULL; + sigdata->sample = NULL; + sigdata->pattern = NULL; + sigdata->midi = NULL; + sigdata->checkpoint = NULL; + + sigdata->n_orders = dumbfile_igetw(f); + sigdata->n_instruments = 0; + sigdata->n_samples = dumbfile_igetw(f); + sigdata->n_patterns = dumbfile_igetw(f); + + if (dumbfile_error(f) || sigdata->n_orders <= 0 || sigdata->n_samples > 256 || sigdata->n_patterns > 256) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + sigdata->order = malloc(sigdata->n_orders); + if (!sigdata->order) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (sigdata->n_samples) { + sigdata->sample = malloc(sigdata->n_samples * sizeof(*sigdata->sample)); + if (!sigdata->sample) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (n = 0; n < sigdata->n_samples; n++) + sigdata->sample[n].right = sigdata->sample[n].left = NULL; + } + + if (sigdata->n_patterns) { + sigdata->pattern = malloc(sigdata->n_patterns * sizeof(*sigdata->pattern)); + if (!sigdata->pattern) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (n = 0; n < sigdata->n_patterns; n++) + sigdata->pattern[n].entry = NULL; + } + + flags = dumbfile_igetw(f); + + cwtv = dumbfile_igetw(f); + + if (cwtv == 0x1300) { + /** WARNING: volume slides on every frame */ + } + + ffi = dumbfile_igetw(f); + + /** WARNING: which ones? */ + sigdata->flags = IT_STEREO | IT_OLD_EFFECTS | IT_COMPATIBLE_GXX; + + if (dumbfile_mgetl(f) != DUMB_ID('S','C','R','M')) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + sigdata->global_volume = dumbfile_getc(f) << 1; + sigdata->speed = dumbfile_getc(f); + if (sigdata->speed == 0) sigdata->speed = 6; // Should we? What about tempo? + sigdata->tempo = dumbfile_getc(f); + /*master_volume = */dumbfile_getc(f); // 7 bits; +128 for stereo + //what do we do with master_volume? it's not the same as mixing volume... + sigdata->mixing_volume = 48; + + /* Skip GUS Ultra Click Removal byte. */ + dumbfile_getc(f); + + default_pan_present = dumbfile_getc(f); + + dumbfile_skip(f, 8); + + /* Skip Special Custom Data Pointer. */ + /** WARNING: investigate this? */ + dumbfile_igetw(f); + + /* Channel settings for 32 channels, 255=unused, +128=disabled */ + { + int i; + for (i = 0; i < 32; i++) { + int c = dumbfile_getc(f); + if (!(c & (128 | 16))) { /* +128=disabled, +16=Adlib */ + sigdata->channel_volume[i] = 64; + sigdata->channel_pan[i] = c & 8 ? 12 : 3; + /** WARNING: ah, but it should be 7 for mono... */ + } else { + /** WARNING: this could be improved if we support channel muting... */ + sigdata->channel_volume[i] = 0; + sigdata->channel_pan[i] = 7; + } + } + } + + /* Orders, byte each, length = sigdata->n_orders (should be even) */ + dumbfile_getnc(sigdata->order, sigdata->n_orders, f); + sigdata->restart_position = 0; + + component = malloc(768*sizeof(*component)); + if (!component) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + for (n = 0; n < sigdata->n_samples; n++) { + component[n_components].type = IT_COMPONENT_SAMPLE; + component[n_components].n = n; + component[n_components].offset = dumbfile_igetw(f) << 4; + component[n_components].sampfirst = -1; + n_components++; + } + + for (n = 0; n < sigdata->n_patterns; n++) { + long offset = dumbfile_igetw(f) << 4; + if (offset) { + component[n_components].type = IT_COMPONENT_PATTERN; + component[n_components].n = n; + component[n_components].offset = offset; + component[n_components].sampfirst = -1; + n_components++; + } else { + /** WARNING: Empty 64-row pattern ... ? (this does happen!) */ + sigdata->pattern[n].n_rows = 64; + sigdata->pattern[n].n_entries = 0; + } + } + + qsort(component, n_components, sizeof(IT_COMPONENT), &it_component_compare); + + /* I found a really dumb S3M file that claimed to contain default pan + * data but didn't contain any. Programs would load it by reading part of + * the first instrument header, assuming the data to be default pan + * positions, and then rereading the instrument module. We cannot do this + * without obfuscating the file input model, so we insert an extra check + * here that we won't overrun the start of the first component. + */ + if (default_pan_present == 252 && component[0].offset >= dumbfile_pos(f) + 32) { + /* Channel default pan positions */ + int i; + for (i = 0; i < 32; i++) { + int c = dumbfile_getc(f); + if (c & 32) + sigdata->channel_pan[i] = c & 15; + } + } + + { + int i; + for (i = 0; i < 32; i++) { + sigdata->channel_pan[i] -= (sigdata->channel_pan[i] & 8) >> 3; + sigdata->channel_pan[i] = ((int)sigdata->channel_pan[i] << 5) / 7; + } + } + + /** WARNING: is this right? */ + sigdata->pan_separation = 64; + + if (dumbfile_error(f)) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + buffer = malloc(65536); + if (!buffer) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + for (n = 0; n < n_components; n++) { + long offset; + int m; + + if (it_seek(f, component[n].offset)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + switch (component[n].type) { + + case IT_COMPONENT_PATTERN: + if (it_s3m_read_pattern(&sigdata->pattern[component[n].n], f, buffer)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + break; + + case IT_COMPONENT_SAMPLE: + if (it_s3m_read_sample_header(&sigdata->sample[component[n].n], &offset, f)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (sigdata->sample[component[n].n].flags & IT_SAMPLE_EXISTS) { + short *sample; + + for (m = n + 1; m < n_components; m++) + if (component[m].offset > offset) + break; + m--; + + sample = &component[m].sampfirst; + + while (*sample >= 0 && component[*sample].offset <= offset) + sample = &component[*sample].sampnext; + + component[n].sampnext = *sample; + *sample = n; + + component[n].offset = offset; + } + } + + m = component[n].sampfirst; + + while (m >= 0) { + if (it_seek(f, component[m].offset)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (it_s3m_read_sample_data(&sigdata->sample[component[m].n], ffi, f)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + m = component[m].sampnext; + } + } + + free(buffer); + free(component); + + _dumb_it_fix_invalid_orders(sigdata); + + return sigdata; +} + + + +DUH *dumb_read_s3m(DUMBFILE *f) +{ + sigdata_t *sigdata; + long length; + + DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it; + + sigdata = it_s3m_load_sigdata(f); + + if (!sigdata) + return NULL; + + length = _dumb_it_build_checkpoints(sigdata); + + return make_duh(length, 1, &descptr, &sigdata); +} diff --git a/apps/codecs/dumb/src/it/readxm.c b/apps/codecs/dumb/src/it/readxm.c new file mode 100644 index 0000000000..2aeda1fa67 --- /dev/null +++ b/apps/codecs/dumb/src/it/readxm.c @@ -0,0 +1,998 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * readxm.c - Code to read a Fast Tracker II / / \ \ + * module from an open file. | < / \_ + * | \/ /\ / + * By Julien Cugniere. Some bits of code taken \_ / > / + * from reads3m.c. | \ / / + * | ' / + * \__/ + */ + +#include +#include +#include + +#include "dumb.h" +#include "internal/it.h" + + + +/** TODO: + + * XM_TREMOLO doesn't sound quite right... + * XM_E_SET_FINETUNE todo. + * XM_SET_ENVELOPE_POSITION todo. + + * VIBRATO conversion needs to be checked (sample/effect/volume). Plus check + that effect memory is correct when using XM_VOLSLIDE_VIBRATO. + - sample vibrato (instrument vibrato) is now handled correctly. - entheh + + * XM_E_SET_VIBRATO/TREMOLO_CONTROL: effectvalue&4 -> don't retrig wave when + a new instrument is played. In retrigger_note()?. Is it worth implementing? + + * Lossy fadeout approximation. 0..31 converted to 0 --> won't fade at all. + + * Replace DUMB's sawtooth by ramp_down/ramp_up. Update XM loader. + + * A lot of things need to be reset when the end of the song is reached. + + * It seems that IT and XM don't behave the same way when dealing with + mixed loops. When IT encounters multiple SBx (x>0) commands on the same + row, it decrements the loop count for all, but only execute the loop of + the last one (highest channel). FT2 only decrements the loop count of the + last one. Not that I know of any modules using so convoluted combinations! + + * Maybe we could remove patterns that don't appear in the order table ? Or + provide a function to "optimize" a DUMB_IT_SIGDATA ? + +*/ + + + +#define XM_LINEAR_FREQUENCY 1 /* otherwise, use amiga slides */ + +#define XM_ENTRY_PACKED 128 +#define XM_ENTRY_NOTE 1 +#define XM_ENTRY_INSTRUMENT 2 +#define XM_ENTRY_VOLUME 4 +#define XM_ENTRY_EFFECT 8 +#define XM_ENTRY_EFFECTVALUE 16 + +#define XM_NOTE_OFF 97 + +#define XM_ENVELOPE_ON 1 +#define XM_ENVELOPE_SUSTAIN 2 +#define XM_ENVELOPE_LOOP 4 + +#define XM_SAMPLE_NO_LOOP 0 +#define XM_SAMPLE_FORWARD_LOOP 1 +#define XM_SAMPLE_PINGPONG_LOOP 2 +#define XM_SAMPLE_16BIT 16 +#define XM_SAMPLE_STEREO 32 + +#define XM_VIBRATO_SINE 0 +#define XM_VIBRATO_SQUARE 1 +#define XM_VIBRATO_RAMP_DOWN 2 +#define XM_VIBRATO_RAMP_UP 3 + + + +/* Probably useless :) */ +static const char xm_convert_vibrato[] = { + IT_VIBRATO_SINE, + IT_VIBRATO_SQUARE, + IT_VIBRATO_SAWTOOTH, + IT_VIBRATO_SAWTOOTH +}; + + + +#define XM_MAX_SAMPLES_PER_INSTRUMENT 16 + + + +/* Extra data that doesn't fit inside IT_INSTRUMENT */ +typedef struct XM_INSTRUMENT_EXTRA +{ + int n_samples; + int vibrato_type; + int vibrato_sweep; /* 0-0xFF */ + int vibrato_depth; /* 0-0x0F */ + int vibrato_speed; /* 0-0x3F */ +} +XM_INSTRUMENT_EXTRA; + + + +/* Frees the original block if it can't resize it or if size is 0, and acts + * as malloc if ptr is NULL. + */ +static void *safe_realloc(void *ptr, size_t size) +{ + if (ptr == NULL) + return malloc(size); + + if (size == 0) { + free(ptr); + return NULL; + } else { + void *new_block = realloc(ptr, size); + if (!new_block) + free(ptr); + return new_block; + } +} + + + +/* The interpretation of the XM volume column is left to the player. Here, we + * just filter bad values. + */ +// This function is so tiny now, should we inline it? +static void it_xm_convert_volume(int volume, IT_ENTRY *entry) +{ + entry->mask |= IT_ENTRY_VOLPAN; + entry->volpan = volume; + + switch (volume >> 4) { + case 0xA: /* set vibrato speed */ + case 0xB: /* vibrato */ + case 0xF: /* tone porta */ + case 0x6: /* vol slide up */ + case 0x7: /* vol slide down */ + case 0x8: /* fine vol slide up */ + case 0x9: /* fine vol slide down */ + case 0xC: /* set panning */ + case 0xD: /* pan slide left */ + case 0xE: /* pan slide right */ + case 0x1: /* set volume */ + case 0x2: /* set volume */ + case 0x3: /* set volume */ + case 0x4: /* set volume */ + break; + + case 0x5: + if (volume == 0x50) + break; /* set volume */ + /* else fall through */ + + default: + entry->mask &= ~IT_ENTRY_VOLPAN; + break; + } +} + + + +static int it_xm_read_pattern(IT_PATTERN *pattern, DUMBFILE *f, int n_channels, unsigned char *buffer) +{ + int size; + int pos; + int channel; + int row; + int effect, effectvalue; + IT_ENTRY *entry; + + /* pattern header size */ + if (dumbfile_igetl(f) != 0x09) { + TRACE("XM error: unexpected pattern header size\n"); + return -1; + } + + /* pattern data packing type */ + if (dumbfile_getc(f) != 0) { + TRACE("XM error: unexpected pattern packing type\n"); + return -1; + } + + pattern->n_rows = dumbfile_igetw(f); /* 1..256 */ + size = dumbfile_igetw(f); + pattern->n_entries = 0; + + if (dumbfile_error(f)) + return -1; + + if (size == 0) + return 0; + + if (size > 1280 * n_channels) { + TRACE("XM error: pattern data size > %d bytes\n", 1280 * n_channels); + return -1; + } + + if (dumbfile_getnc(buffer, size, f) < size) + return -1; + + /* compute number of entries */ + pattern->n_entries = 0; + pos = channel = row = 0; + while (pos < size) { + if (!(buffer[pos] & XM_ENTRY_PACKED) || (buffer[pos] & 31)) + pattern->n_entries++; + + channel++; + if (channel >= n_channels) { + channel = 0; + row++; + pattern->n_entries++; + } + + if (buffer[pos] & XM_ENTRY_PACKED) { + static const char offset[] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5 }; + pos += 1 + offset[buffer[pos] & 31]; + } else { + pos += 5; + } + } + + if (row != pattern->n_rows) { + TRACE("XM error: wrong number of rows in pattern data\n"); + return -1; + } + + pattern->entry = malloc(pattern->n_entries * sizeof(*pattern->entry)); + if (!pattern->entry) + return -1; + + /* read the entries */ + entry = pattern->entry; + pos = channel = row = 0; + while (pos < size) { + unsigned char mask; + + if (buffer[pos] & XM_ENTRY_PACKED) + mask = buffer[pos++] & 31; + else + mask = 31; + + if (mask) { + ASSERT(entry < pattern->entry + pattern->n_entries); + + entry->channel = channel; + entry->mask = 0; + + if (mask & XM_ENTRY_NOTE) { + int note = buffer[pos++]; /* 1-96 <=> C0-B7 */ + entry->note = (note == XM_NOTE_OFF) ? (IT_NOTE_OFF) : (note-1); + entry->mask |= IT_ENTRY_NOTE; + } + + if (mask & XM_ENTRY_INSTRUMENT) { + entry->instrument = buffer[pos++]; /* 1-128 */ + entry->mask |= IT_ENTRY_INSTRUMENT; + } + + if (mask & XM_ENTRY_VOLUME) + it_xm_convert_volume(buffer[pos++], entry); + + effect = effectvalue = 0; + if (mask & XM_ENTRY_EFFECT) effect = buffer[pos++]; + if (mask & XM_ENTRY_EFFECTVALUE) effectvalue = buffer[pos++]; + _dumb_it_xm_convert_effect(effect, effectvalue, entry); + + entry++; + } + + channel++; + if (channel >= n_channels) { + channel = 0; + row++; + IT_SET_END_ROW(entry); + entry++; + } + } + + return 0; +} + + + +static int it_xm_make_envelope(IT_ENVELOPE *envelope, const unsigned short *data, int y_offset) +{ + int i, pos; + + if (envelope->n_nodes > 12) { + TRACE("XM error: wrong number of envelope nodes (%d)\n", envelope->n_nodes); + envelope->n_nodes = 0; + return -1; + } + + pos = 0; + for (i = 0; i < envelope->n_nodes; i++) { + envelope->node_t[i] = data[pos++]; + if (data[pos] > 64) { + TRACE("XM error: out-of-range envelope node (node_y[%d]=%d)\n", i, data[pos]); + envelope->n_nodes = 0; + return -1; + } + envelope->node_y[i] = (signed char)(data[pos++] + y_offset); + } + + return 0; +} + + + +static int it_xm_read_instrument(IT_INSTRUMENT *instrument, XM_INSTRUMENT_EXTRA *extra, DUMBFILE *f) +{ + unsigned long size, bytes_read; + unsigned short vol_points[24]; + unsigned short pan_points[24]; + int i, type; + + /* Header size. Tends to be more than the actual size of the structure. + * So unread bytes must be skipped before reading the first sample + * header. + */ + size = dumbfile_igetl(f); + + //memset(instrument, 0, sizeof(*instrument)); + dumbfile_skip(f, 22); /* Instrument name */ + dumbfile_skip(f, 1); /* Instrument type. Should be 0, but seems random. */ + extra->n_samples = dumbfile_igetw(f); + + if (dumbfile_error(f) || (unsigned int)extra->n_samples > XM_MAX_SAMPLES_PER_INSTRUMENT) + return -1; + + bytes_read = 4 + 22 + 1 + 2; + + if (extra->n_samples) { + /* sample header size */ + if (dumbfile_igetl(f) != 0x28) { + TRACE("XM error: unexpected sample header size\n"); + return -1; + } + + /* sample map */ + for (i = 0; i < 96; i++) { + instrument->map_sample[i] = dumbfile_getc(f) + 1; + instrument->map_note[i] = i; + } + + if (dumbfile_error(f)) + return 1; + + /* volume/panning envelopes */ + for (i = 0; i < 24; i++) + vol_points[i] = dumbfile_igetw(f); + for (i = 0; i < 24; i++) + pan_points[i] = dumbfile_igetw(f); + + instrument->volume_envelope.n_nodes = dumbfile_getc(f); + instrument->pan_envelope.n_nodes = dumbfile_getc(f); + + if (dumbfile_error(f)) + return -1; + + instrument->volume_envelope.sus_loop_start = dumbfile_getc(f); + instrument->volume_envelope.loop_start = dumbfile_getc(f); + instrument->volume_envelope.loop_end = dumbfile_getc(f); + + instrument->pan_envelope.sus_loop_start = dumbfile_getc(f); + instrument->pan_envelope.loop_start = dumbfile_getc(f); + instrument->pan_envelope.loop_end = dumbfile_getc(f); + + /* The envelope handler for XM files won't use sus_loop_end. */ + + type = dumbfile_getc(f); + instrument->volume_envelope.flags = 0; + if ((type & XM_ENVELOPE_ON) && instrument->volume_envelope.n_nodes) + instrument->volume_envelope.flags |= IT_ENVELOPE_ON; + if (type & XM_ENVELOPE_LOOP) instrument->volume_envelope.flags |= IT_ENVELOPE_LOOP_ON; +#if 1 + if (type & XM_ENVELOPE_SUSTAIN) instrument->volume_envelope.flags |= IT_ENVELOPE_SUSTAIN_LOOP; +#else // This is now handled in itrender.c + /* let's avoid fading out when reaching the last envelope node */ + if (!(type & XM_ENVELOPE_LOOP)) { + instrument->volume_envelope.loop_start = instrument->volume_envelope.n_nodes-1; + instrument->volume_envelope.loop_end = instrument->volume_envelope.n_nodes-1; + } + instrument->volume_envelope.flags |= IT_ENVELOPE_LOOP_ON; +#endif + + type = dumbfile_getc(f); + instrument->pan_envelope.flags = 0; + if ((type & XM_ENVELOPE_ON) && instrument->pan_envelope.n_nodes) + instrument->pan_envelope.flags |= IT_ENVELOPE_ON; + if (type & XM_ENVELOPE_LOOP) instrument->pan_envelope.flags |= IT_ENVELOPE_LOOP_ON; // should this be here? + if (type & XM_ENVELOPE_SUSTAIN) instrument->pan_envelope.flags |= IT_ENVELOPE_SUSTAIN_LOOP; + + if (it_xm_make_envelope(&instrument->volume_envelope, vol_points, 0) != 0) { + TRACE("XM error: volume envelope\n"); + if (instrument->volume_envelope.flags & IT_ENVELOPE_ON) return -1; + } + + if (it_xm_make_envelope(&instrument->pan_envelope, pan_points, -32) != 0) { + TRACE("XM error: pan envelope\n"); + if (instrument->pan_envelope.flags & IT_ENVELOPE_ON) return -1; + } + + instrument->pitch_envelope.flags = 0; + + extra->vibrato_type = dumbfile_getc(f); + extra->vibrato_sweep = dumbfile_getc(f); + extra->vibrato_depth = dumbfile_getc(f); + extra->vibrato_speed = dumbfile_getc(f); + + if (dumbfile_error(f) || extra->vibrato_type >= 4) + return -1; + + /** WARNING: lossy approximation */ + instrument->fadeout = (dumbfile_igetw(f)*128 + 64)/0xFFF; + + dumbfile_skip(f, 2); /* reserved */ + + bytes_read += 4 + 96 + 48 + 48 + 14*1 + 2 + 2; + } + + if (dumbfile_skip(f, size - bytes_read)) + return -1; + + instrument->new_note_action = NNA_NOTE_CUT; + instrument->dup_check_type = DCT_OFF; + instrument->dup_check_action = DCA_NOTE_CUT; + instrument->pp_separation = 0; + instrument->pp_centre = 60; /* C-5 */ + instrument->global_volume = 128; + instrument->default_pan = 32; + instrument->random_volume = 0; + instrument->random_pan = 0; + instrument->filter_cutoff = 0; + instrument->filter_resonance = 0; + + return 0; +} + + + +/* I (entheh) have two XM files saved by a very naughty program. After a + * 16-bit sample, it saved a rogue byte. The length of the sample was indeed + * an odd number, incremented to include the rogue byte. + * + * In this function we are converting sample lengths and loop points so they + * are measured in samples. This means we forget about the extra bytes, and + * they don't get skipped. So we fail trying to read the next instrument. + * + * To get around this, this function returns the number of rogue bytes that + * won't be accounted for by reading sample->length samples. It returns a + * negative number on failure. + */ +static int it_xm_read_sample_header(IT_SAMPLE *sample, DUMBFILE *f) +{ + int type; + int relative_note_number; /* relative to C4 */ + int finetune; + int roguebytes; + int roguebytesmask; + + sample->length = dumbfile_igetl(f); + sample->loop_start = dumbfile_igetl(f); + sample->loop_end = sample->loop_start + dumbfile_igetl(f); + sample->global_volume = 64; + sample->default_volume = dumbfile_getc(f); + finetune = (signed char)dumbfile_getc(f); /* -128..127 <=> -1 semitone .. +127/128 of a semitone */ + type = dumbfile_getc(f); + sample->default_pan = (dumbfile_getc(f)*64)/255 | 128; /* 0-255 */ + relative_note_number = (signed char)dumbfile_getc(f); + + dumbfile_skip(f, 1); /* reserved */ + dumbfile_skip(f, 22); /* sample name */ + + if (dumbfile_error(f)) + return -1; + + sample->C5_speed = (long)(16726.0*pow(DUMB_SEMITONE_BASE, relative_note_number)*pow(DUMB_PITCH_BASE, finetune*2)); + + sample->flags = IT_SAMPLE_EXISTS; + + roguebytes = (int)sample->length; + roguebytesmask = 3; + + if (type & XM_SAMPLE_16BIT) { + sample->flags |= IT_SAMPLE_16BIT; + sample->length >>= 1; + sample->loop_start >>= 1; + sample->loop_end >>= 1; + } else + roguebytesmask >>= 1; + + if (type & XM_SAMPLE_STEREO) { + sample->flags |= IT_SAMPLE_STEREO; + sample->length >>= 1; + sample->loop_start >>= 1; + sample->loop_end >>= 1; + } else + roguebytesmask >>= 1; + + roguebytes &= roguebytesmask; + + if ((unsigned int)sample->loop_start < (unsigned int)sample->loop_end) { + if (type & XM_SAMPLE_FORWARD_LOOP) sample->flags |= IT_SAMPLE_LOOP; + if (type & XM_SAMPLE_PINGPONG_LOOP) sample->flags |= IT_SAMPLE_LOOP | IT_SAMPLE_PINGPONG_LOOP; + } + + if (sample->length <= 0) + sample->flags &= ~IT_SAMPLE_EXISTS; + else if ((unsigned int)sample->loop_end > (unsigned int)sample->length) + sample->flags &= ~IT_SAMPLE_LOOP; + else if ((unsigned int)sample->loop_start >= (unsigned int)sample->loop_end) + sample->flags &= ~IT_SAMPLE_LOOP; + + return roguebytes; +} + + + +static int it_xm_read_sample_data(IT_SAMPLE *sample, unsigned char roguebytes, DUMBFILE *f) +{ + int old; + long i; + long truncated_size; + + if (!(sample->flags & IT_SAMPLE_EXISTS)) + return dumbfile_skip(f, roguebytes); + + /* let's get rid of the sample data coming after the end of the loop */ + if ((sample->flags & IT_SAMPLE_LOOP) && sample->loop_end < sample->length) { + truncated_size = sample->length - sample->loop_end; + sample->length = sample->loop_end; + } else { + truncated_size = 0; + } + + sample->left = malloc(sample->length * sizeof(*sample->left)); + if (!sample->left) + return -1; + + if (sample->flags & IT_SAMPLE_STEREO) { + sample->right = malloc(sample->length * sizeof(*sample->right)); + if (!sample->right) + return -1; + } + + /* sample data is stored as signed delta values */ + old = 0; + if (sample->flags & IT_SAMPLE_16BIT) { + for (i = 0; i < sample->length; i++) { + old = sample->left[i] = (int)(signed short)(old + dumbfile_igetw(f)); + sample->left[i] <<= 8; + } + } else { + for (i = 0; i < sample->length; i++) { + old = sample->left[i] = (int)(signed char)(old + dumbfile_getc(f)); + sample->left[i] <<= 16; + } + } + + /* skip truncated data */ + dumbfile_skip(f, (sample->flags & IT_SAMPLE_16BIT) ? (2*truncated_size) : (truncated_size)); + + if (sample->flags & IT_SAMPLE_STEREO) { + old = 0; + if (sample->flags & IT_SAMPLE_16BIT) { + for (i = 0; i < sample->length; i++) { + old = sample->right[i] = (int)(signed short)(old + dumbfile_igetw(f)); + sample->right[i] <<= 8; + } + } else { + for (i = 0; i < sample->length; i++) { + old = sample->right[i] = (int)(signed char)(old + dumbfile_getc(f)); + sample->right[i] <<= 16; + } + } + /* skip truncated data */ + dumbfile_skip(f, (sample->flags & IT_SAMPLE_16BIT) ? (2*truncated_size) : (truncated_size)); + } + + dumbfile_skip(f, roguebytes); + + if (dumbfile_error(f)) + return -1; + + return 0; +} + + + +/* "Real programmers don't document. If it was hard to write, + * it should be hard to understand." + * + * (Never trust the documentation provided with a tracker. + * Real files are the only truth...) + */ +static DUMB_IT_SIGDATA *it_xm_load_sigdata(DUMBFILE *f) +{ + DUMB_IT_SIGDATA *sigdata; + char id_text[18]; + + int flags; + int n_channels; + int total_samples; + int i, j; + + /* check ID text */ + if (dumbfile_getnc(id_text, 17, f) < 17) + return NULL; + id_text[17] = 0; + if (strcmp(id_text, "Extended Module: ") != 0) { + TRACE("XM error: Not an Extended Module\n"); + return NULL; + } + + /* song name */ + if (dumbfile_skip(f, 20)) + return NULL; + + if (dumbfile_getc(f) != 0x1A) { + TRACE("XM error: 0x1A not found\n"); + return NULL; + } + + /* tracker name */ + if (dumbfile_skip(f, 20)) + return NULL; + + /* version number */ + if (dumbfile_igetw(f) != 0x0104) { + TRACE("XM error: wrong format version\n"); + return NULL; + } + + /* + ------------------ + --- Header --- + ------------------ + */ + + /* header size */ + if (dumbfile_igetl(f) != 0x0114) { + TRACE("XM error: unexpected header size\n"); + return NULL; + } + + sigdata = malloc(sizeof(*sigdata)); + if (!sigdata) + return NULL; + + sigdata->order = NULL; + sigdata->instrument = NULL; + sigdata->sample = NULL; + sigdata->pattern = NULL; + sigdata->midi = NULL; + sigdata->checkpoint = NULL; + + sigdata->n_samples = 0; + sigdata->n_orders = dumbfile_igetw(f); + sigdata->restart_position = dumbfile_igetw(f); + n_channels = dumbfile_igetw(f); /* max 32 but we'll be lenient */ + sigdata->n_patterns = dumbfile_igetw(f); + sigdata->n_instruments = dumbfile_igetw(f); /* max 128 */ + flags = dumbfile_igetw(f); + sigdata->speed = dumbfile_igetw(f); + sigdata->tempo = dumbfile_igetw(f); + + /* sanity checks */ + if (dumbfile_error(f) || sigdata->n_orders <= 0 || sigdata->n_orders > 256 || sigdata->n_patterns > 256 || sigdata->n_instruments > 128 || n_channels > DUMB_IT_N_CHANNELS) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + //if (sigdata->restart_position >= sigdata->n_orders) + //sigdata->restart_position = 0; + + /* order table */ + sigdata->order = malloc(sigdata->n_orders*sizeof(*sigdata->order)); + if (!sigdata->order) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + dumbfile_getnc(sigdata->order, sigdata->n_orders, f); + dumbfile_skip(f, 256 - sigdata->n_orders); + + if (dumbfile_error(f)) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + /* + -------------------- + --- Patterns --- + -------------------- + */ + + sigdata->pattern = malloc(sigdata->n_patterns * sizeof(*sigdata->pattern)); + if (!sigdata->pattern) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (i = 0; i < sigdata->n_patterns; i++) + sigdata->pattern[i].entry = NULL; + + { + unsigned char *buffer = malloc(1280 * n_channels); /* 256 rows * 5 bytes */ + if (!buffer) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (i = 0; i < sigdata->n_patterns; i++) { + if (it_xm_read_pattern(&sigdata->pattern[i], f, n_channels, buffer) != 0) { + free(buffer); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + } + free(buffer); + } + + /* + ----------------------------------- + --- Instruments and Samples --- + ----------------------------------- + */ + + sigdata->instrument = malloc(sigdata->n_instruments * sizeof(*sigdata->instrument)); + if (!sigdata->instrument) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + /* With XM, samples are not global, they're part of an instrument. In a + * file, each instrument is stored with its samples. Because of this, I + * don't know how to find how many samples are present in the file. Thus + * I have to do n_instruments reallocation on sigdata->sample. + * Looking at FT2, it doesn't seem possible to have more than 16 samples + * per instrument (even though n_samples is stored as 2 bytes). So maybe + * we could allocate a 128*16 array of samples, and shrink it back to the + * correct size when we know it? + * Alternatively, I could allocate samples by blocks of N (still O(n)), + * or double the number of allocated samples when I need more (O(log n)). + */ + total_samples = 0; + sigdata->sample = NULL; + + for (i = 0; i < sigdata->n_instruments; i++) { + XM_INSTRUMENT_EXTRA extra; + + if (it_xm_read_instrument(&sigdata->instrument[i], &extra, f) < 0) { + TRACE("XM error: instrument %d\n", i+1); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (extra.n_samples) { + unsigned char roguebytes[XM_MAX_SAMPLES_PER_INSTRUMENT]; + + /* adjust instrument sample map (make indices absolute) */ + for (j = 0; j < 96; j++) + sigdata->instrument[i].map_sample[j] += total_samples; + + sigdata->sample = safe_realloc(sigdata->sample, sizeof(*sigdata->sample)*(total_samples+extra.n_samples)); + if (!sigdata->sample) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (j = total_samples; j < total_samples+extra.n_samples; j++) + sigdata->sample[j].right = sigdata->sample[j].left = NULL; + + /* read instrument's samples */ + for (j = 0; j < extra.n_samples; j++) { + IT_SAMPLE *sample = &sigdata->sample[total_samples+j]; + int b = it_xm_read_sample_header(sample, f); + if (b < 0) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + roguebytes[j] = b; + // Any reason why these can't be set inside it_xm_read_sample_header()? + sample->vibrato_speed = extra.vibrato_speed; + sample->vibrato_depth = extra.vibrato_depth; + sample->vibrato_rate = extra.vibrato_sweep; + /* Rate and sweep don't match, but the difference is + * accounted for in itrender.c. + */ + sample->vibrato_waveform = xm_convert_vibrato[extra.vibrato_type]; + } + for (j = 0; j < extra.n_samples; j++) { + if (it_xm_read_sample_data(&sigdata->sample[total_samples+j], roguebytes[j], f) != 0) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + } + total_samples += extra.n_samples; + } + } + + sigdata->n_samples = total_samples; + + sigdata->flags = IT_WAS_AN_XM | IT_OLD_EFFECTS | IT_COMPATIBLE_GXX | IT_STEREO | IT_USE_INSTRUMENTS; + // Are we OK with IT_COMPATIBLE_GXX off? + // + // When specifying note + instr + tone portamento, and an old note is still playing (even after note off): + // - If Compatible Gxx is on, the new note will be triggered only if the instrument _changes_. + // - If Compatible Gxx is off, the new note will always be triggered, provided the instrument is specified. + // - FT2 seems to do the latter (unconfirmed). + + // Err, wait. XM playback has its own code. The change made to the IT + // playbackc code didn't affect XM playback. Forget this then. There's + // still a bug in XM playback though, and it'll need some investigation... + // tomorrow... + + // UPDATE: IT_COMPATIBLE_GXX is required to be on, so that tone porta has + // separate memory from portamento. + + if (flags & XM_LINEAR_FREQUENCY) + sigdata->flags |= IT_LINEAR_SLIDES; + + sigdata->global_volume = 128; + sigdata->mixing_volume = 48; + sigdata->pan_separation = 128; + + memset(sigdata->channel_volume, 64, DUMB_IT_N_CHANNELS); + memset(sigdata->channel_pan, 32, DUMB_IT_N_CHANNELS); + + _dumb_it_fix_invalid_orders(sigdata); + + return sigdata; +} + + + +#if 0 // no fucking way, dude! + +/* The length returned is the time required to play from the beginning of the + * file to the last row of the last order (which is when the player will + * loop). Depending on the song, the sound might stop sooner. + * Due to fixed point roundoffs, I think this is only reliable to the second. + * Full precision could be achieved by using a double during the computation, + * or maybe a LONG_LONG. + */ +long it_compute_length(const DUMB_IT_SIGDATA *sigdata) +{ + IT_PATTERN *pattern; + int tempo, speed; + int loop_start[IT_N_CHANNELS]; + char loop_count[IT_N_CHANNELS]; + int order, entry; + int row_first_entry = 0; + int jump, jump_dest; + int delay, fine_delay; + int i; + long t; + + if (!sigdata) + return 0; + + tempo = sigdata->tempo; + speed = sigdata->speed; + order = entry = 0; + jump = jump_dest = 0; + t = 0; + + /* for each PATTERN */ + for (order = 0; order < sigdata->n_orders; order++) { + + if (sigdata->order[order] == IT_ORDER_END) break; + if (sigdata->order[order] == IT_ORDER_SKIP) continue; + + for (i = 0; i < IT_N_CHANNELS; i++) + loop_count[i] = -1; + + pattern = &sigdata->pattern[ sigdata->order[order] ]; + entry = 0; + if (jump == IT_BREAK_TO_ROW) { + int row = 0; + while (row < jump_dest) + if (pattern->entry[entry++].channel >= IT_N_CHANNELS) + row++; + } + + /* for each ROW */ + while (entry < pattern->n_entries) { + row_first_entry = entry; + delay = fine_delay = 0; + jump = 0; + + /* for each note NOTE */ + while (entry < pattern->n_entries && pattern->entry[entry].channel < IT_N_CHANNELS) { + int value = pattern->entry[entry].effectvalue; + int channel = pattern->entry[entry].channel; + + switch (pattern->entry[entry].effect) { + + case IT_SET_SPEED: speed = value; break; + + case IT_JUMP_TO_ORDER: + if (value <= order) /* infinite loop */ + return 0; + jump = IT_JUMP_TO_ORDER; + jump_dest = value; + break; + + case IT_BREAK_TO_ROW: + jump = IT_BREAK_TO_ROW; + jump_dest = value; + break; + + case IT_S: + switch (HIGH(value)) { + case IT_S_PATTERN_DELAY: delay = LOW(value); break; + case IT_S_FINE_PATTERN_DELAY: fine_delay = LOW(value); break; + case IT_S_PATTERN_LOOP: + if (LOW(value) == 0) { + loop_start[channel] = row_first_entry; + } else { + if (loop_count[channel] == -1) + loop_count[channel] = LOW(value); + + if (loop_count[channel]) { + jump = IT_S_PATTERN_LOOP; + jump_dest = loop_start[channel]; + } + loop_count[channel]--; + } + break; + } + break; + + case IT_SET_SONG_TEMPO: + switch (HIGH(value)) { /* slides happen every non-row frames */ + case 0: tempo = tempo - LOW(value)*(speed-1); break; + case 1: tempo = tempo + LOW(value)*(speed-1); break; + default: tempo = value; + } + tempo = MID(32, tempo, 255); + break; + } + + entry++; + } + + /* end of ROW */ + entry++; + t += TICK_TIME_DIVIDEND * (speed*(1+delay) + fine_delay) / tempo; + + if (jump == IT_JUMP_TO_ORDER) { + order = jump_dest - 1; + break; + } else if (jump == IT_BREAK_TO_ROW) + break; + else if (jump == IT_S_PATTERN_LOOP) + entry = jump_dest - 1; + } + + /* end of PATTERN */ + } + + return t; +} + +#endif /* 0 */ + + + +DUH *dumb_read_xm(DUMBFILE *f) +{ + sigdata_t *sigdata; + long length; + + DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it; + + sigdata = it_xm_load_sigdata(f); + + if (!sigdata) + return NULL; + + length = _dumb_it_build_checkpoints(sigdata); + + return make_duh(length, 1, &descptr, &sigdata); +} diff --git a/apps/codecs/dumb/src/it/xmeffect.c b/apps/codecs/dumb/src/it/xmeffect.c new file mode 100644 index 0000000000..51995f3bb8 --- /dev/null +++ b/apps/codecs/dumb/src/it/xmeffect.c @@ -0,0 +1,242 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * xmeffect.c - Code for converting MOD/XM / / \ \ + * effects to IT effects. | < / \_ + * | \/ /\ / + * By Julien Cugniere. Ripped out of readxm.c \_ / > / + * by entheh. | \ / / + * | ' / + * \__/ + */ + + + +#include +#include + +#include "dumb.h" +#include "internal/it.h" + + + +#if 0 +unsigned char **_dumb_malloc2(int w, int h) +{ + unsigned char **line = malloc(h * sizeof(*line)); + int i; + if (!line) return NULL; + + line[0] = malloc(w * h * sizeof(*line[0])); + if (!line[0]) { + free(line); + return NULL; + } + + for (i = 1; i < h; i++) + line[i] = line[i-1] + w; + + memset(line[0], 0, w*h); + + return line; +} + + + +void _dumb_free2(unsigned char **line) +{ + if (line) { + if (line[0]) + free(line[0]); + free(line); + } +} + + + +/* Effects having a memory. 2 means that the two parts of the effectvalue + * should be handled separately. + */ +static const char xm_has_memory[] = { +/* 0 1 2 3 4 5 6 7 8 9 A B C D (E) F G H K L P R T (X) */ + 0, 1, 1, 1, 2, 1, 1, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + +/* E0 E1 E2 E3 E4 E5 E6 E7 E9 EA EB EC ED EE X1 X2 */ + 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +#endif + + + +/* Effects marked with 'special' are handled specifically in itrender.c */ +void _dumb_it_xm_convert_effect(int effect, int value, IT_ENTRY *entry) +{ +const int log = 0; + + if ((!effect && !value) || (effect >= XM_N_EFFECTS)) + return; + +if (log) printf("%c%02X", (effect<10)?('0'+effect):('A'+effect-10), value); + + /* Linearisation of the effect number... */ + if (effect == XM_E) { + effect = EBASE + HIGH(value); + value = LOW(value); + } else if (effect == XM_X) { + effect = XBASE + HIGH(value); + value = LOW(value); + } + +if (log) printf(" - %2d %02X", effect, value); + +#if 0 // This should be handled in itrender.c! + /* update effect memory */ + switch (xm_has_memory[effect]) { + case 1: + if (!value) + value = memory[entry->channel][effect]; + else + memory[entry->channel][effect] = value; + break; + + case 2: + if (!HIGH(value)) + SET_HIGH(value, HIGH(memory[entry->channel][effect])); + else + SET_HIGH(memory[entry->channel][effect], HIGH(value)); + + if (!LOW(value)) + SET_LOW(value, LOW(memory[entry->channel][effect])); + else + SET_LOW(memory[entry->channel][effect], LOW(value)); + break; + } +#endif + + /* convert effect */ + entry->mask |= IT_ENTRY_EFFECT; + switch (effect) { + + case XM_APPREGIO: effect = IT_ARPEGGIO; break; + case XM_VIBRATO: effect = IT_VIBRATO; break; + case XM_TONE_PORTAMENTO: effect = IT_TONE_PORTAMENTO; break; /** TODO: glissando control */ + case XM_TREMOLO: effect = IT_TREMOLO; break; + case XM_SET_PANNING: effect = IT_SET_PANNING; break; + case XM_SAMPLE_OFFSET: effect = IT_SET_SAMPLE_OFFSET; break; + case XM_POSITION_JUMP: effect = IT_JUMP_TO_ORDER; break; + case XM_MULTI_RETRIG: effect = IT_RETRIGGER_NOTE; break; + case XM_TREMOR: effect = IT_TREMOR; break; + case XM_PORTAMENTO_UP: effect = IT_XM_PORTAMENTO_UP; break; + case XM_PORTAMENTO_DOWN: effect = IT_XM_PORTAMENTO_DOWN; break; + case XM_SET_CHANNEL_VOLUME: effect = IT_SET_CHANNEL_VOLUME; break; /* special */ + case XM_VOLSLIDE_TONEPORTA: effect = IT_VOLSLIDE_TONEPORTA; break; /* special */ + case XM_VOLSLIDE_VIBRATO: effect = IT_VOLSLIDE_VIBRATO; break; /* special */ + + case XM_PATTERN_BREAK: + effect = IT_BREAK_TO_ROW; + value = BCD_TO_NORMAL(value); + break; + + case XM_VOLUME_SLIDE: /* special */ + effect = IT_VOLUME_SLIDE; + value = HIGH(value) ? EFFECT_VALUE(HIGH(value), 0) : EFFECT_VALUE(0, LOW(value)); + break; + + case XM_PANNING_SLIDE: + effect = IT_PANNING_SLIDE; + value = HIGH(value) ? EFFECT_VALUE(0, HIGH(value)) : EFFECT_VALUE(LOW(value), 0); + break; + + case XM_GLOBAL_VOLUME_SLIDE: /* special */ + effect = IT_GLOBAL_VOLUME_SLIDE; + value = HIGH(value) ? EFFECT_VALUE(HIGH(value), 0) : EFFECT_VALUE(0, LOW(value)); + break; + + case XM_SET_TEMPO_BPM: + effect = (value < 0x20) ? (IT_SET_SPEED) : (IT_SET_SONG_TEMPO); + break; + + case XM_SET_GLOBAL_VOLUME: + effect = IT_SET_GLOBAL_VOLUME; + value *= 2; + break; + + case XM_KEY_OFF: + /** WARNING: In FT2, the value seems to do something... Oh well, + * this is undocumented anyway! + */ + entry->mask &= ~IT_ENTRY_EFFECT; + entry->mask |= IT_ENTRY_NOTE; + entry->note = IT_NOTE_OFF; + break; + + case EBASE+XM_E_SET_FILTER: effect = SBASE+IT_S_SET_FILTER; break; + case EBASE+XM_E_SET_GLISSANDO_CONTROL: effect = SBASE+IT_S_SET_GLISSANDO_CONTROL; break; /** TODO */ + case EBASE+XM_E_SET_FINETUNE: effect = SBASE+IT_S_FINETUNE; break; /** TODO */ + case EBASE+XM_E_SET_LOOP: effect = SBASE+IT_S_PATTERN_LOOP; break; + case EBASE+XM_E_NOTE_CUT: effect = SBASE+IT_S_DELAYED_NOTE_CUT; break; + case EBASE+XM_E_NOTE_DELAY: effect = SBASE+IT_S_NOTE_DELAY; break; + case EBASE+XM_E_PATTERN_DELAY: effect = SBASE+IT_S_PATTERN_DELAY; break; + case EBASE+XM_E_FINE_VOLSLIDE_UP: effect = IT_XM_FINE_VOLSLIDE_UP; break; + case EBASE+XM_E_FINE_VOLSLIDE_DOWN: effect = IT_XM_FINE_VOLSLIDE_DOWN; break; + + case EBASE + XM_E_FINE_PORTA_UP: + effect = IT_PORTAMENTO_UP; + value = EFFECT_VALUE(0xF, value); + break; + + case EBASE + XM_E_FINE_PORTA_DOWN: + effect = IT_PORTAMENTO_DOWN; + value = EFFECT_VALUE(0xF, value); + break; + + case EBASE + XM_E_RETRIG_NOTE: + effect = IT_XM_RETRIGGER_NOTE; + value = EFFECT_VALUE(0, value); + break; + + case EBASE + XM_E_SET_VIBRATO_CONTROL: + effect = SBASE+IT_S_SET_VIBRATO_WAVEFORM; + value &= ~4; /** TODO: value&4 -> don't retrig wave */ + break; + + case EBASE + XM_E_SET_TREMOLO_CONTROL: + effect = SBASE+IT_S_SET_TREMOLO_WAVEFORM; + value &= ~4; /** TODO: value&4 -> don't retrig wave */ + break; + + case XBASE + XM_X_EXTRAFINE_PORTA_UP: + effect = IT_PORTAMENTO_UP; + value = EFFECT_VALUE(0xE, value); + break; + + case XBASE + XM_X_EXTRAFINE_PORTA_DOWN: + effect = IT_PORTAMENTO_DOWN; + value = EFFECT_VALUE(0xE, value); + break; + + default: + /* user effect (often used in demos for synchronisation) */ + entry->mask &= ~IT_ENTRY_EFFECT; + } + +if (log) printf(" - %2d %02X", effect, value); + + /* Inverse linearisation... */ + if (effect >= SBASE && effect < SBASE+16) { + value = EFFECT_VALUE(effect-SBASE, value); + effect = IT_S; + } + +if (log) printf(" - %c%02X\n", 'A'+effect-1, value); + + entry->effect = effect; + entry->effectvalue = value; +} -- cgit v1.2.3