Initial commit

Import existing source tree; original VCS history is no longer available.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ville Lindholm
2026-05-28 16:35:31 +03:00
co-authored by Claude
commit dbc223eb84
308 changed files with 81555 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
noinst_LIBRARIES = libsound.a
INCLUDES = -I../eel -I../graphics @LOCALCFLAGS@
libsound_a_SOURCES = audio.c a_globals.c a_struct.c \
audiodev.c a_midi.c sfifo.c a_pitch.c a_midisock.c \
a_wave.c a_patch.c a_channel.c a_voice.c a_bus.c \
a_commands.c a_control.c a_midicon.c a_midifile.c \
a_plugin.c a_delay.c a_limiter.c a_filters.c \
a_wca.c a_agw.c a_sequencer.c a_events.c
EXTRA_DIST = *.h
+459
View File
@@ -0,0 +1,459 @@
# Makefile.in generated by automake 1.9.6 from Makefile.am.
# @configure_input@
# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
# 2003, 2004, 2005 Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
@SET_MAKE@
srcdir = @srcdir@
top_srcdir = @top_srcdir@
VPATH = @srcdir@
pkgdatadir = $(datadir)/@PACKAGE@
pkglibdir = $(libdir)/@PACKAGE@
pkgincludedir = $(includedir)/@PACKAGE@
top_builddir = ..
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
INSTALL = @INSTALL@
install_sh_DATA = $(install_sh) -c -m 644
install_sh_PROGRAM = $(install_sh) -c
install_sh_SCRIPT = $(install_sh) -c
INSTALL_HEADER = $(INSTALL_DATA)
transform = $(program_transform_name)
NORMAL_INSTALL = :
PRE_INSTALL = :
POST_INSTALL = :
NORMAL_UNINSTALL = :
PRE_UNINSTALL = :
POST_UNINSTALL = :
build_triplet = @build@
host_triplet = @host@
target_triplet = @target@
subdir = sound
DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/configure.in
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
$(ACLOCAL_M4)
mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
CONFIG_HEADER = $(top_builddir)/aconfig.h
CONFIG_CLEAN_FILES =
LIBRARIES = $(noinst_LIBRARIES)
AR = ar
ARFLAGS = cru
libsound_a_AR = $(AR) $(ARFLAGS)
libsound_a_LIBADD =
am_libsound_a_OBJECTS = audio.$(OBJEXT) a_globals.$(OBJEXT) \
a_struct.$(OBJEXT) audiodev.$(OBJEXT) a_midi.$(OBJEXT) \
sfifo.$(OBJEXT) a_pitch.$(OBJEXT) a_midisock.$(OBJEXT) \
a_wave.$(OBJEXT) a_patch.$(OBJEXT) a_channel.$(OBJEXT) \
a_voice.$(OBJEXT) a_bus.$(OBJEXT) a_commands.$(OBJEXT) \
a_control.$(OBJEXT) a_midicon.$(OBJEXT) a_midifile.$(OBJEXT) \
a_plugin.$(OBJEXT) a_delay.$(OBJEXT) a_limiter.$(OBJEXT) \
a_filters.$(OBJEXT) a_wca.$(OBJEXT) a_agw.$(OBJEXT) \
a_sequencer.$(OBJEXT) a_events.$(OBJEXT)
libsound_a_OBJECTS = $(am_libsound_a_OBJECTS)
DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir)
depcomp = $(SHELL) $(top_srcdir)/depcomp
am__depfiles_maybe = depfiles
COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
CCLD = $(CC)
LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
SOURCES = $(libsound_a_SOURCES)
DIST_SOURCES = $(libsound_a_SOURCES)
ETAGS = etags
CTAGS = ctags
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
ACLOCAL = @ACLOCAL@
AMDEP_FALSE = @AMDEP_FALSE@
AMDEP_TRUE = @AMDEP_TRUE@
AMTAR = @AMTAR@
AUDIO_LIBS = @AUDIO_LIBS@
AUTOCONF = @AUTOCONF@
AUTOHEADER = @AUTOHEADER@
AUTOMAKE = @AUTOMAKE@
AWK = @AWK@
BUILD_MACOSX_BUNDLE_FALSE = @BUILD_MACOSX_BUNDLE_FALSE@
BUILD_MACOSX_BUNDLE_TRUE = @BUILD_MACOSX_BUNDLE_TRUE@
BUILD_SIMPLE_BUNDLE_FALSE = @BUILD_SIMPLE_BUNDLE_FALSE@
BUILD_SIMPLE_BUNDLE_TRUE = @BUILD_SIMPLE_BUNDLE_TRUE@
CC = @CC@
CCDEPMODE = @CCDEPMODE@
CFLAGS = @CFLAGS@
CONFIGDIR = @CONFIGDIR@
CONFIGFILE = @CONFIGFILE@
CPP = @CPP@
CPPFLAGS = @CPPFLAGS@
CXX = @CXX@
CXXDEPMODE = @CXXDEPMODE@
CXXFLAGS = @CXXFLAGS@
CYGPATH_W = @CYGPATH_W@
DATADIR = @DATADIR@
DEFS = @DEFS@
DEPDIR = @DEPDIR@
ECHO_C = @ECHO_C@
ECHO_N = @ECHO_N@
ECHO_T = @ECHO_T@
EGREP = @EGREP@
EXEEXT = @EXEEXT@
EXEFILE = @EXEFILE@
GREP = @GREP@
INSTALL_DATA = @INSTALL_DATA@
INSTALL_PROGRAM = @INSTALL_PROGRAM@
INSTALL_SCRIPT = @INSTALL_SCRIPT@
INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
LDFLAGS = @LDFLAGS@
LIBOBJS = @LIBOBJS@
LIBS = @LIBS@
LOCALCFLAGS = @LOCALCFLAGS@
LTLIBOBJS = @LTLIBOBJS@
MAKEINFO = @MAKEINFO@
MATHLIB = @MATHLIB@
OBJEXT = @OBJEXT@
PACKAGE = @PACKAGE@
PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
PACKAGE_NAME = @PACKAGE_NAME@
PACKAGE_STRING = @PACKAGE_STRING@
PACKAGE_TARNAME = @PACKAGE_TARNAME@
PACKAGE_VERSION = @PACKAGE_VERSION@
PATH_SEPARATOR = @PATH_SEPARATOR@
POW_LIB = @POW_LIB@
RANLIB = @RANLIB@
SCOREDIR = @SCOREDIR@
SDL_CFLAGS = @SDL_CFLAGS@
SDL_CONFIG = @SDL_CONFIG@
SDL_LIBS = @SDL_LIBS@
SET_MAKE = @SET_MAKE@
SHELL = @SHELL@
STRIP = @STRIP@
UNIX_SCOREDIR_FALSE = @UNIX_SCOREDIR_FALSE@
UNIX_SCOREDIR_TRUE = @UNIX_SCOREDIR_TRUE@
VERSION = @VERSION@
XMKMF = @XMKMF@
X_CFLAGS = @X_CFLAGS@
X_EXTRA_LIBS = @X_EXTRA_LIBS@
X_LIBS = @X_LIBS@
X_PRE_LIBS = @X_PRE_LIBS@
ac_ct_CC = @ac_ct_CC@
ac_ct_CXX = @ac_ct_CXX@
am__fastdepCC_FALSE = @am__fastdepCC_FALSE@
am__fastdepCC_TRUE = @am__fastdepCC_TRUE@
am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@
am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@
am__include = @am__include@
am__leading_dot = @am__leading_dot@
am__quote = @am__quote@
am__tar = @am__tar@
am__untar = @am__untar@
bindir = @bindir@
build = @build@
build_alias = @build_alias@
build_cpu = @build_cpu@
build_os = @build_os@
build_vendor = @build_vendor@
datadir = @datadir@
datarootdir = @datarootdir@
docdir = @docdir@
dvidir = @dvidir@
exec_prefix = @exec_prefix@
host = @host@
host_alias = @host_alias@
host_cpu = @host_cpu@
host_os = @host_os@
host_vendor = @host_vendor@
htmldir = @htmldir@
includedir = @includedir@
infodir = @infodir@
install_sh = @install_sh@
libdir = @libdir@
libexecdir = @libexecdir@
localedir = @localedir@
localstatedir = @localstatedir@
mandir = @mandir@
mkdir_p = @mkdir_p@
oldincludedir = @oldincludedir@
pdfdir = @pdfdir@
prefix = @prefix@
program_transform_name = @program_transform_name@
psdir = @psdir@
sbindir = @sbindir@
sharedstatedir = @sharedstatedir@
sysconfdir = @sysconfdir@
target = @target@
target_alias = @target_alias@
target_cpu = @target_cpu@
target_os = @target_os@
target_vendor = @target_vendor@
noinst_LIBRARIES = libsound.a
INCLUDES = -I../eel -I../graphics @LOCALCFLAGS@
libsound_a_SOURCES = audio.c a_globals.c a_struct.c \
audiodev.c a_midi.c sfifo.c a_pitch.c a_midisock.c \
a_wave.c a_patch.c a_channel.c a_voice.c a_bus.c \
a_commands.c a_control.c a_midicon.c a_midifile.c \
a_plugin.c a_delay.c a_limiter.c a_filters.c \
a_wca.c a_agw.c a_sequencer.c a_events.c
EXTRA_DIST = *.h
all: all-am
.SUFFIXES:
.SUFFIXES: .c .o .obj
$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
@for dep in $?; do \
case '$(am__configure_deps)' in \
*$$dep*) \
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
&& exit 0; \
exit 1;; \
esac; \
done; \
echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign sound/Makefile'; \
cd $(top_srcdir) && \
$(AUTOMAKE) --foreign sound/Makefile
.PRECIOUS: Makefile
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
@case '$?' in \
*config.status*) \
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
*) \
echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
esac;
$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
$(top_srcdir)/configure: $(am__configure_deps)
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
$(ACLOCAL_M4): $(am__aclocal_m4_deps)
cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
clean-noinstLIBRARIES:
-test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
libsound.a: $(libsound_a_OBJECTS) $(libsound_a_DEPENDENCIES)
-rm -f libsound.a
$(libsound_a_AR) libsound.a $(libsound_a_OBJECTS) $(libsound_a_LIBADD)
$(RANLIB) libsound.a
mostlyclean-compile:
-rm -f *.$(OBJEXT)
distclean-compile:
-rm -f *.tab.c
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_agw.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_bus.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_channel.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_commands.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_control.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_delay.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_events.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_filters.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_globals.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_limiter.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_midi.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_midicon.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_midifile.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_midisock.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_patch.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_pitch.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_plugin.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_sequencer.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_struct.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_voice.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_wave.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/a_wca.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/audio.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/audiodev.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sfifo.Po@am__quote@
.c.o:
@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \
@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(COMPILE) -c $<
.c.obj:
@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \
@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
uninstall-info-am:
ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
unique=`for i in $$list; do \
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
done | \
$(AWK) ' { files[$$0] = 1; } \
END { for (i in files) print i; }'`; \
mkid -fID $$unique
tags: TAGS
TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
$(TAGS_FILES) $(LISP)
tags=; \
here=`pwd`; \
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
unique=`for i in $$list; do \
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
done | \
$(AWK) ' { files[$$0] = 1; } \
END { for (i in files) print i; }'`; \
if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
test -n "$$unique" || unique=$$empty_fix; \
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
$$tags $$unique; \
fi
ctags: CTAGS
CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
$(TAGS_FILES) $(LISP)
tags=; \
here=`pwd`; \
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
unique=`for i in $$list; do \
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
done | \
$(AWK) ' { files[$$0] = 1; } \
END { for (i in files) print i; }'`; \
test -z "$(CTAGS_ARGS)$$tags$$unique" \
|| $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
$$tags $$unique
GTAGS:
here=`$(am__cd) $(top_builddir) && pwd` \
&& cd $(top_srcdir) \
&& gtags -i $(GTAGS_ARGS) $$here
distclean-tags:
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
distdir: $(DISTFILES)
@srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \
list='$(DISTFILES)'; for file in $$list; do \
case $$file in \
$(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \
$(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \
esac; \
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \
if test "$$dir" != "$$file" && test "$$dir" != "."; then \
dir="/$$dir"; \
$(mkdir_p) "$(distdir)$$dir"; \
else \
dir=''; \
fi; \
if test -d $$d/$$file; then \
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
fi; \
cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
else \
test -f $(distdir)/$$file \
|| cp -p $$d/$$file $(distdir)/$$file \
|| exit 1; \
fi; \
done
check-am: all-am
check: check-am
all-am: Makefile $(LIBRARIES)
installdirs:
install: install-am
install-exec: install-exec-am
install-data: install-data-am
uninstall: uninstall-am
install-am: all-am
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
installcheck: installcheck-am
install-strip:
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
`test -z '$(STRIP)' || \
echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
mostlyclean-generic:
clean-generic:
distclean-generic:
-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
maintainer-clean-generic:
@echo "This command is intended for maintainers to use"
@echo "it deletes files that may require special tools to rebuild."
clean: clean-am
clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
distclean: distclean-am
-rm -rf ./$(DEPDIR)
-rm -f Makefile
distclean-am: clean-am distclean-compile distclean-generic \
distclean-tags
dvi: dvi-am
dvi-am:
html: html-am
info: info-am
info-am:
install-data-am:
install-exec-am:
install-info: install-info-am
install-man:
installcheck-am:
maintainer-clean: maintainer-clean-am
-rm -rf ./$(DEPDIR)
-rm -f Makefile
maintainer-clean-am: distclean-am maintainer-clean-generic
mostlyclean: mostlyclean-am
mostlyclean-am: mostlyclean-compile mostlyclean-generic
pdf: pdf-am
pdf-am:
ps: ps-am
ps-am:
uninstall-am: uninstall-info-am
.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
clean-noinstLIBRARIES ctags distclean distclean-compile \
distclean-generic distclean-tags distdir dvi dvi-am html \
html-am info info-am install install-am install-data \
install-data-am install-exec install-exec-am install-info \
install-info-am install-man install-strip installcheck \
installcheck-am installdirs maintainer-clean \
maintainer-clean-generic mostlyclean mostlyclean-compile \
mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
uninstall-am uninstall-info-am
# Tell versions [3.59,3.63) of GNU make to not export all variables.
# Otherwise a system limit (for SysV at least) may be exceeded.
.NOEXPORT:
+528
View File
@@ -0,0 +1,528 @@
/*(LGPL)
---------------------------------------------------------------------------
a_agw.c - "Algorithmically Generated Waveform" file support
---------------------------------------------------------------------------
* Copyright (C) 2002, 2003, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#define DBG(x)
#include <stdio.h>
#include "kobolog.h"
#include "config.h"
#include "eel.h"
#include "a_types.h"
#include "a_wca.h"
#include "a_control.h"
/* Enum class handles */
static int enum_aformat, enum_amodtarget, enum_awaveform, enum_afiltertype;
static int enum_amixmode, enum_aresample;
static int enum_pparam, enum_pdriver;
/*----------------------------------------------------------
AGW Command Callbacks
----------------------------------------------------------*/
static int op_w_reset(int argc, struct eel_data_t *argv)
{
DBG(log_printf(D2LOG, "w_reset;\n");)
wca_reset();
return 1;
}
static int op_w_format(int argc, struct eel_data_t *argv)
{
int target;
double fs;
int format = enum_aformat;
#if 0
/* Alternative, more verbose method. */
eel_symbol_t *fmt;
if(eel_get_args("iEr", &target, &fmt, &fs) != 3)
return -1;
if(fmt->token != enum_aformat)
{
eel_error("Wrong enum class!");
return -1;
}
DBG(log_printf(D2LOG, "w_format %d, %s, %f;\n", target, eel_s_stringrep(fmt), fs);)
format = fmt->data.value.i;
#else
if(eel_get_args("ier", &target, &format, &fs) != 3)
return -1;
#endif
DBG(log_printf(D2LOG, "w_format %d, %d, %f;\n", target, format, fs);)
audio_wave_format(target, format, (int)fs);
return 1;
}
static int op_w_gain(int argc, struct eel_data_t *argv)
{
int target;
if(eel_get_args("i", &target) != 1)
return -1;
DBG(log_printf(D2LOG, "w_gain %d;\n", target);)
wca_gain(target);
return 1;
}
static int op_w_convert(int argc, struct eel_data_t *argv)
{
int source, target;
double fs;
int format = enum_aformat;
int interpol = enum_aresample;
argc = eel_get_args("iie[re]",
&source, &target, &format,
&fs, &interpol);
if(argc < 3)
return -1;
if(argc < 4)
fs = 0;
if(argc < 5)
interpol = AR_BEST;
DBG(log_printf(D2LOG, "w_convert %d, %d, %d, %d, %d;\n", source, target,
format, (int)fs, interpol);)
audio_wave_convert(source, target, format, (int)fs, interpol);
return 1;
}
static int op_w_enhance(int argc, struct eel_data_t *argv)
{
int target;
double f, level;
if(eel_get_args("irr", &target, &f, &level) != 3)
return -1;
DBG(log_printf(D2LOG, "w_enhance %d, %d, %f;\n", target, (int)f, level);)
wca_enhance(target, (int)f, level);
return 1;
}
static int op_w_gate(int argc, struct eel_data_t *argv)
{
int target;
double f, min, thres, att;
if(eel_get_args("irrrr",
&target, &f, &min, &thres, &att) != 5)
return -1;
DBG(log_printf(D2LOG, "w_gate %d, %d, %f, %f, %f;\n",
target, (int)f, min, thres, att);)
wca_gate(target, (int)f, min, thres, att);
return 1;
}
static int op_w_blank(int argc, struct eel_data_t *argv)
{
int target, loop;
unsigned samples;
argc = eel_get_args("ii[i]", &target, &samples, &loop);
if(argc < 2)
return -1;
if(argc < 3)
loop = 0;
DBG(log_printf(D2LOG, "w_blank %d, %d, %d;\n", target, samples, loop);)
audio_wave_blank(target, samples, loop);
return 1;
}
static int op_w_load(int argc, struct eel_data_t *argv)
{
int target, loop;
const char *fn;
argc = eel_get_args("is[i]", &target, &fn, &loop);
if(argc < 2)
return -1;
if(argc < 3)
loop = 0;
DBG(log_printf(D2LOG, "w_load %d, \"%s\", %d;\n", target, fn, loop);)
if(audio_wave_load(target, fn, loop) < 0)
return -1;
return 1;
}
static int op_w_save(int argc, struct eel_data_t *argv)
{
int target;
const char *fn;
if(eel_get_args("is", &target, &fn) != 2)
return -1;
DBG(log_printf(D2LOG, "w_save %d, \"%s\";\n", target, fn);)
if(audio_wave_save(target, fn) < 0)
return -1;
return 1;
}
static int op_w_prepare(int argc, struct eel_data_t *argv)
{
int target;
if(eel_get_args("i", &target) < 1)
return -1;
DBG(log_printf(D2LOG, "w_prepare %d;\n", target);)
audio_wave_prepare(target);
return 1;
}
static int op_w_env(int argc, struct eel_data_t *argv)
{
int i;
int modtarget = enum_amodtarget;
double dur[WCA_MAX_ENV_STEPS];
double val[WCA_MAX_ENV_STEPS];
if(argc > 1 + 2*WCA_MAX_ENV_STEPS)
{
eel_error("Too many envelope steps!");
return -1;
}
if(eel_get_args("e[<rr>]", &modtarget, &dur, &val) != argc)
return -1;
DBG(log_printf(D2LOG, "w_env %d", modtarget);)
wca_mod_reset(modtarget);
if(2 == argc)
{
/*
* Note that dur[0] actually becomes a *value*
* rather than a time in this case!
*/
DBG(log_printf(D2LOG, ", %f;\n", dur[0]);)
wca_val(modtarget, dur[0]);
return 1;
}
for(i = 1; i < argc; i += 2)
{
int sec = (i-1)/2;
DBG(log_printf(D2LOG, ", <%f", dur[sec]);)
if(i+1 >= argc)
{
eel_error("<duration, value> argruments must"
" come in pairs!");
return -1;
}
DBG(else
log_printf(D2LOG, ", %f", val[sec]);)
DBG(log_printf(D2LOG, ">");)
wca_env(modtarget, dur[sec], val[sec]);
}
DBG(log_printf(D2LOG, ";\n");)
return 1;
}
static int op_w_mod(int argc, struct eel_data_t *argv)
{
int mt;
double f, a, d;
mt = enum_amodtarget;
if(eel_get_args("errr", &mt, &f, &a, &d) != 4)
return -1;
DBG(log_printf(D2LOG, "w_mod %d, %f, %f, %f;\n", mt, f, a, d);)
wca_mod(mt, f, a, d);
return 1;
}
static int op_w_osc(int argc, struct eel_data_t *argv)
{
int target, wf, mm;
wf = enum_awaveform;
mm = enum_amixmode;
switch(eel_get_args("ie[e]", &target, &wf, &mm))
{
case 2:
mm = WCA_ADD;
case 3:
break;
default:
return -1;
}
DBG(log_printf(D2LOG, "w_osc %d, %d, %d;\n", target, wf, mm);)
wca_osc(target, wf, mm);
return 1;
}
static int op_w_filter(int argc, struct eel_data_t *argv)
{
int target, ft;
ft = enum_afiltertype;
if(eel_get_args("ie", &target, &ft) != 2)
return -1;
DBG(log_printf(D2LOG, "w_filter %d, %d;\n", target, ft);)
wca_filter(target, ft);
return 1;
}
/*----------------------------------------------------------
Patch Command Callbacks
----------------------------------------------------------*/
static int op_p_param(int argc, struct eel_data_t *argv)
{
int patch, param;
double value;
param = enum_pparam;
if(eel_get_args("ier", &patch, &param, &value) != 3)
return -1;
switch(param)
{
case APP_DRIVER:
case APP_WAVE:
case APP_ENV_SKIP:
case APP_LFO_SHAPE:
audio_patch_param(patch, param, (int)value);
break;
default:
audio_patch_param(patch, param, (int)(value * 65536.0));
break;
}
DBG(log_printf(D2LOG, "p_param %d, %d, %f;\n", patch, param, value);)
return 1;
}
static int op_p_driver(int argc, struct eel_data_t *argv)
{
int patch;
eel_symbol_t *callback;
if(eel_get_args("if", &patch, &callback) != 2)
return -1;
DBG(log_printf(D2LOG, "p_driver %d, %s;\n", patch, eel_s_stringrep(callback));)
return 1;
}
/*----------------------------------------------------------
Init code
----------------------------------------------------------*/
static void load_keywords(void)
{
/* WCA calls */
eel_register_operator("w_reset", op_w_reset, 100, 0 ,0);
eel_register_operator("w_format", op_w_format, 100, 0 ,0);
eel_register_operator("w_blank", op_w_blank, 100, 0 ,0);
eel_register_operator("w_load", op_w_load, 100, 0 ,0);
eel_register_operator("w_save", op_w_save, 100, 0 ,0);
eel_register_operator("w_prepare", op_w_prepare, 100, 0 ,0);
eel_register_operator("w_env", op_w_env, 100, 0 ,0);
eel_register_operator("w_mod", op_w_mod, 100, 0 ,0);
eel_register_operator("w_osc", op_w_osc, 100, 0 ,0);
eel_register_operator("w_filter", op_w_filter, 100, 0 ,0);
eel_register_operator("w_gain", op_w_gain, 100, 0 ,0);
eel_register_operator("w_convert", op_w_convert, 100, 0 ,0);
eel_register_operator("w_enhance", op_w_enhance, 100, 0 ,0);
eel_register_operator("w_gate", op_w_gate, 100, 0 ,0);
/* w_convert resampling modes */
enum_aresample = eel_register_enum_class();
eel_register_enum(enum_aresample, "NEAREST", AR_NEAREST);
eel_register_enum(enum_aresample, "NEAREST4X", AR_NEAREST_4X);
eel_register_enum(enum_aresample, "LINEAR", AR_LINEAR);
eel_register_enum(enum_aresample, "LINEAR2X", AR_LINEAR_2X_R);
eel_register_enum(enum_aresample, "LINEAR4X", AR_LINEAR_4X_R);
eel_register_enum(enum_aresample, "LINEAR8X", AR_LINEAR_8X_R);
eel_register_enum(enum_aresample, "LINEAR16X", AR_LINEAR_16X_R);
eel_register_enum(enum_aresample, "CUBIC", AR_CUBIC_R);
eel_register_enum(enum_aresample, "WORST", AR_WORST);
eel_register_enum(enum_aresample, "MEDIUM", AR_MEDIUM);
eel_register_enum(enum_aresample, "BEST", AR_BEST);
/* Sample formats */
enum_aformat = eel_register_enum_class();
eel_register_enum(enum_aformat, "MONO8", AF_MONO8);
eel_register_enum(enum_aformat, "STEREO8", AF_STEREO8);
eel_register_enum(enum_aformat, "MONO16", AF_MONO16);
eel_register_enum(enum_aformat, "STEREO16", AF_STEREO16);
eel_register_enum(enum_aformat, "MONO32", AF_MONO32);
eel_register_enum(enum_aformat, "STEREO32", AF_STEREO32);
/* Modulation targets */
enum_amodtarget = eel_register_enum_class();
eel_register_enum(enum_amodtarget, "AMPLITUDE", WCA_AMPLITUDE);
eel_register_enum(enum_amodtarget, "BALANCE", WCA_BALANCE);
eel_register_enum(enum_amodtarget, "FREQUENCY", WCA_FREQUENCY);
eel_register_enum(enum_amodtarget, "LIMIT", WCA_LIMIT);
eel_register_enum(enum_amodtarget, "MOD1", WCA_MOD1);
eel_register_enum(enum_amodtarget, "MOD2", WCA_MOD2);
eel_register_enum(enum_amodtarget, "MOD3", WCA_MOD3);
/* Oscillator output modes */
enum_amixmode = eel_register_enum_class();
eel_register_enum(enum_amixmode, "ADD", WCA_ADD);
eel_register_enum(enum_amixmode, "MUL", WCA_MUL);
eel_register_enum(enum_amixmode, "FM", WCA_FM);
eel_register_enum(enum_amixmode, "FM_ADD", WCA_FM_ADD);
eel_register_enum(enum_amixmode, "SYNC", WCA_SYNC);
eel_register_enum(enum_amixmode, "SYNC_ADD", WCA_SYNC_ADD);
/* Oscillator waveforms */
enum_awaveform = eel_register_enum_class();
eel_register_enum(enum_awaveform, "DC", WCA_DC);
eel_register_enum(enum_awaveform, "SINE", WCA_SINE);
eel_register_enum(enum_awaveform, "HALFSINE", WCA_HALFSINE);
eel_register_enum(enum_awaveform, "RECTSINE", WCA_RECTSINE);
eel_register_enum(enum_awaveform, "SINEMORPH", WCA_SINEMORPH);
eel_register_enum(enum_awaveform, "BLMORPH", WCA_BLMORPH);
eel_register_enum(enum_awaveform, "BLCROSS", WCA_BLCROSS);
eel_register_enum(enum_awaveform, "PULSE", WCA_PULSE);
eel_register_enum(enum_awaveform, "TRIANGLE", WCA_TRIANGLE);
eel_register_enum(enum_awaveform, "NOISE", WCA_NOISE);
eel_register_enum(enum_awaveform, "SPECTRUM", WCA_SPECTRUM);
eel_register_enum(enum_awaveform, "ASPECTRUM", WCA_ASPECTRUM);
eel_register_enum(enum_awaveform, "HSPECTRUM", WCA_HSPECTRUM);
eel_register_enum(enum_awaveform, "AHSPECTRUM", WCA_AHSPECTRUM);
/* Filter types */
enum_afiltertype = eel_register_enum_class();
eel_register_enum(enum_afiltertype, "ALLPASS", WCA_ALLPASS);
eel_register_enum(enum_afiltertype, "LOWPASS_6", WCA_LOWPASS_6DB);
eel_register_enum(enum_afiltertype, "HIGHPASS_6", WCA_HIGHPASS_6DB);
eel_register_enum(enum_afiltertype, "LOWPASS_12", WCA_LOWPASS_12DB);
eel_register_enum(enum_afiltertype, "HIGHPASS_12", WCA_HIGHPASS_12DB);
eel_register_enum(enum_afiltertype, "BANDPASS_12", WCA_BANDPASS_12DB);
eel_register_enum(enum_afiltertype, "NOTCH_12", WCA_NOTCH_12DB);
eel_register_enum(enum_afiltertype, "PEAK_12", WCA_PEAK_12DB);
/* Patch calls */
eel_register_operator("p_param", op_p_param, 100, 0 ,0);
eel_register_operator("p_driver", op_p_driver, 100, 0 ,0);
/* Patch Parameters */
enum_pparam = eel_register_enum_class();
eel_register_enum(enum_pparam, "DRIVER", APP_DRIVER);
eel_register_enum(enum_pparam, "WAVE", APP_WAVE);
eel_register_enum(enum_pparam, "RANDPITCH", APP_RANDPITCH);
eel_register_enum(enum_pparam, "RANDVOL", APP_RANDVOL);
eel_register_enum(enum_pparam, "ENV_SKIP", APP_ENV_SKIP);
eel_register_enum(enum_pparam, "ENV_L0", APP_ENV_L0);
eel_register_enum(enum_pparam, "ENV_DELAY", APP_ENV_DELAY);
eel_register_enum(enum_pparam, "ENV_T1", APP_ENV_T1);
eel_register_enum(enum_pparam, "ENV_L1", APP_ENV_L1);
eel_register_enum(enum_pparam, "ENV_HOLD", APP_ENV_HOLD);
eel_register_enum(enum_pparam, "ENV_T2", APP_ENV_T2);
eel_register_enum(enum_pparam, "ENV_L2", APP_ENV_L2);
eel_register_enum(enum_pparam, "ENV_T3", APP_ENV_T3);
eel_register_enum(enum_pparam, "ENV_T4", APP_ENV_T4);
/* Patch Drivers */
enum_pdriver = eel_register_enum_class();
eel_register_enum(enum_pdriver, "MONO", PD_MONO);
eel_register_enum(enum_pdriver, "POLY", PD_POLY);
eel_register_enum(enum_pdriver, "MIDI", PD_MIDI);
eel_register_enum(enum_pdriver, "EEL", PD_EEL);
}
/*----------------------------------------------------------
API code
----------------------------------------------------------*/
static int _agw_initialized = 0;
int agw_open(void)
{
if(_agw_initialized)
return 0;
eel_open();
eel_push_scope(); /* begin AGW extensions scope */
load_keywords();
/* Built-in variables */
(void)eel_set_integer("target", -1);
_agw_initialized = 1;
return 0;
}
void agw_close(void)
{
if(!_agw_initialized)
return;
eel_pop_scope(); /* end AGW extensions scope */
eel_close();
_agw_initialized = 0;
}
#define CHECKINIT if(!_agw_initialized) agw_open();
int agw_load(int wid, const char *name)
{
int script, res;
CHECKINIT
if(wid < 0)
{
wid = audio_wave_alloc(wid);
if(wid < 0)
return wid;
}
script = eel_load(name);
if(script < 0)
return -1;
eel_push_scope(); /* begin script scope */
/* Initialize "argument" variables */
(void)eel_set_integer("target", wid);
/* Reset the waveform construction engine */
wca_reset();
res = eel_run(script);
eel_free(script);
eel_pop_scope(); /* end script scope */
if(res < 0)
return -1;
audio_wave_prepare(wid);
return wid;
}
+359
View File
@@ -0,0 +1,359 @@
/*(LGPL)
---------------------------------------------------------------------------
a_agw.h - "Algorithmically Generated Waveform" file support
---------------------------------------------------------------------------
* Copyright (C) 2002, 2003, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* The AGW v0.1 file format:
*
* This extension to the EEL language makes it possible
* to use EEL scripts to construct waveforms using the
* "Waveform Construction API" of the engine.
*
* Why!?
* Well, pages of hard-coded calls to the Waveform
* Construction API and recompiling for every turn
* while creating sounds got boring, so I decided to
* hack up something simple to get cleaner code and
* faster sound editing.
*
* Over a few weeks, the file format turned into an
* interpreted language, which I eventually cleaned up
* and separated from the engine. This language is
* called "EEL" - Extensible Embeddable Language, and
* is not tied to the audio engine.
*
* Built-in commands:
* //
* C++ style comment.
*
*/ /* ... */
/* C style comment.
*
* w_reset;
* Resets the AGW engine state to the default.
* This is done automatically before a script
* is executed after being loaded with w_load().
* See below for default settings for w_env etc.
*
* w_format <wid>, <sformat>, <samplerate>;
* Set format of <wid> to <sformat>;
* <samplerate> samples per second.
*
* Supported sample formats:
* MONO8 STEREO8
* MONO16 STEREO16
*
* w_blank <wid>, <samples>, <loop>;
* Create <samples> samples of silent data
* for waveform <wid>. <loop> can be either 0
* or 1, and if it's 1, the waveform will be
* set up for looping playback.
*
* w_env <mod_target>[, <time>, <level>[, <time>, <level>...]];
* w_env <mod_target>, <level>;
* Program the envelope generator modulating
* <mod_target> with the specified points.
* Times are in seconds, and levels are in Hz
* for frequency envelopes, while for other
* envelopes, 1.0 corresponds to 0 dB, "no
* change" etc.
*
* Calling w_env with two arguments sets up
* modulator 'mod_target' to the fixed level of
* the second argument - which obviously is *not*
* a duration in this case.
*
* Calling w_env with no other arguments than
* 'mod_target' resets the envelope generator
* for that destination to the default.
*
* Available modulation targets:
* (Defaults are constant levels.)
*
* Name Controls Default
* ---------------------------------------------
* AMPLITUDE Amplitude 1.0
* BALANCE Mixing control 0.0
* FREQUENCY f0/cut-off 100.0
* LIMIT Max overtone freq. 100000.0
* MOD1 PW ratio/FM depth/... 0.0
* MOD2 Various stuff... 0.0
* MOD3 Various stuff... 0.0
*
TODO:
* w_env_taper <mod_target>, <x_coeff>[, <x2_coeff>[, <x3_coeff>]];
* Set transformation of the envelope for target
* 'mod_target' to the function:
*
* out = in * x_coeff +
* in*in * x2_coeff +
* in*in*in * x3_coeff;
*
* Examples:
* w_env_taper FREQUENCY, 1;
* w_env_taper AMPLITUDE, .5, .25, .25;
*
* w_mod <destination>, <frequency>, <amplitude>, <depth>;
* If f(t) is the envelope as defined with
* audio_wave_env(), and w(t) is the modulator phase,
* the final result will be
*
* sin(w(t)) * amplitude + f(t) * (1.0 + sin(w(t)) * depth)
*
* That is, the modulation will add a sine with
* 'amplitude' to the envelope, and it will also add
* a sine with an amplitude of 'depth', scaled by
* the envelope. (Or if you will, the second component
* offset by +1.0 will modulate the envelope.)
*
* w_osc <wid>, <waveform>, <mode>;
* Apply a sound using <mode> to waveform <wid>,
* generated using a <waveform> oscillator.
* Various parameters of the oscillator may
* be modulated.
*
* Available modes:
* WCA_ADD Add oscillator output to buffer
* WCA_MUL AM or ring modulate output with buffer
* ==> BALANCE: 0 ==> no effect
* 0.5 ==> AM
* 1.0 ==> Ring modulation
*
* WCA_FM FM oscillator with buffer, then overwrite
* the buffer with the result. Note that
* it's *not* the buffer that is modulated!
* ==> BALANCE controls FM depth.
*
* WCA_FM_ADD Like AMM_FM, but adds the result to the
* buffer instead of overwriting it.
* ==> BALANCE controls FM depth.
*
* WCA_SYNC Reset oscillator phase when a + -> -
* zero crossing is detected in the
* buffer. Overwrites the buffer with the
* result.
*
* WCA_SYNC_ADD Like AMM_SYNC, but adds the result to
* the buffer instead of overwriting it.
*
* Available waveforms:
* DC Add DC offset
*
* SINE
* ==> MOD1 sets 1.0f FM depth
*
* HALFSINE Half wave rectified sine
* ==> MOD1 sets zero clip level
*
* RECTSINE Full wave rectified sine
* ==> MOD1 sets zero limit level
*
* PULSE Oversampled PWM pulse oscillator.
* ==> MOD1 sets pulse width; 0.5 <==> square
*
* TRIANGLE
* ==> MOD1 ==> ramp up...tri...ramp down
*
* SINEMORPH Sine/square/sawtooth morph, using
* recursive FM. If the sum of MOD1 and
* MOD2 exceeds 1.0, they are balanced
* and scaled to restrict the total to
* 1.0 while preserving the saw/square
* ratio. MOD1 and MOD2 are also scaled
* according to the FREQUENCY/LIMIT
* ratio, to approximate bandlimiting.
* ==> MOD1 sets saw "amount"
* ==> MOD2 sets square "amount"
*
* BLMORPH Bandlimited sine/saw/square/triangle
* oscillator, based on additive synthesis
* using a bank of sine oscillators.
* The harmonics for the respective
* waveforms are scaled by MOD1, MOD2 and
* MOD3, and then are summed. The output
* is bandlimited to the frequency set by
* LIMIT, with a roll-off giving a
* harmonic at LIMIT a 12 dB attenuation.
* ==> MOD1 modulates sawtooth harmonics
* ==> MOD2 modulates square harmonics
* ==> MOD3 modulates triangle harmonics
*
* BLCROSS Bandlimited sine/saw/square/triangle
* oscillator, based on additive synthesis
* using a bank of sine oscillators.
* The harmonics for the respective
* waveforms are bandlimited by the
* respective MODn mapped from [0, 1] to
* [FREQUENCY, LIMIT], with a roll-off
* towards the high end, giving the
* highest harmonic of each waveform a
* 12 dB attenuation.
* ==> MOD1 controls sawtooth cut-off
* ==> MOD2 controls square cut-off
* ==> MOD3 controls triangle cut-off
*
* NOISE "White" noise (6581/SID style :-)
* The noise is generated by a pseudo
* random number generator that is polled
* for a new value at a rate of 2 * f, to
* achieve a minimum period corresponding
* to FREQUENCY. The pseudo random number
* generator is reset at the start of the
* waveform, to guarantee consistent
* results. (Obviously, this means that
* applying noise more than once is rather
* pointless, as it would just increase
* the amplitude!)
* ==> MOD1 sets *non-bandlimited* LP cutoff
*
* SPECTRUM Multiplying frequency spectrum.
* Overtone frequencies are calculated by
* recursively multiplying with MOD1.
* ==> f[n] = f[n-1] * MOD1
* ==> a[n] = a[n-1] * MOD2
*
* ASPECTRUM Adding frequency spectrum. MOD1 is
* the distance in Hz between adjacent
* overtones. As you might expect, an
* ASPECTRUM usually *will* hit the
* maximum # of overtones limit, unless
* you track it with LIMIT properly!
* ==> f[n] = f[n-1] + MOD1
* ==> a[n] = a[n-1] * MOD2
*
* HSPECTRUM Harmonic frequency spectrum with
* separate modulation of even and odd
* harmonics.
* ==> f[n] = f[1] * n
* ==> (Even n) a[n] = a[n-1] * MOD1
* ==> (Odd n) a[n] = a[n-1] * MOD2
*
* PHSPECTRUM Pseudo-Harmonic frequency spectrum.
* Basically a HSPECTRUM, where you
* redefine "harmonic" using MOD1. :-)
* MOD2 and MOD3 control even and odd
* harmonics respectively.
* ==> f[n] = f[1] * n * MOD1
* ==> (Even n) a[n] = a[n-1] * MOD2
* ==> (Odd n) a[n] = a[n-1] * MOD3
*
* w_filter <wid>, <type>;
* Apply filter of <type> to waveform <wid>.
*
* Supported filter types:
* ALLPASS (Currently a NOP)
* LOWPASS_6
* HIGHPASS_6
* LOWPASS_12
* HIGHPASS_12
* BANDPASS_12
* NOTCH_12
* PEAK_12
*
* w_gain <wid>;
* Scale the amplitude of waveform <wid> using the
* AMPLITUDE envelope.
*
*
* w_load <wid>, <filename>, <looped>;
*
* WARNING: This command is recursive!
*
* Load waveform, or load and execute AGW file
* <filename>, and put the result in waveform
* <wid>. It's recommended to pass either
* 'target', something calculated from 'target'
* (when loading "banks" of sounds), or 'tempN'
* for <wid>, although it's possible (but usually
* very nasty!) to specify your own waveform IDs.
FIXME:
The 'looped' argument is a remainder of the
old engine code, and should be replaced with
something sensible.
*
* Note that in the current implementation, there
* is no notion of "scope" - "loading" an AGW
* script from within another AGW script treats
* all variables as globals in all respects.
*
* w_save <wid>, <filename>;
* Save the rendered waveform 'wid' as 'filename'.
*
* w_convert <from_wid>, <to_wid>, <format>[, <rate>[, <resamp>]];
* Convert 'from_wid' into 'format' and 'rate'
* using resampling method 'resamp' and render the
* result into 'to_wid'. Leaving out the rate, or
* setting it to 0 keeps the original rate. Leaving
* out 'inter' uses the generally best interpolation
* method available.
*
* Supported resampling methods:
* NEAREST Nearest sample ("useless")
* NEAREST4X Nearest with 4x oversampling
* LINEAR Linear Interpolation
* LINEAR2X LI w/ 2x oversampling
* LINEAR4X LI w/ 4x oversampling
* LINEAR8X LI w/ 8x oversampling
* LINEAR16X LI w/ 16x oversampling
* CUBIC Cubic interpolation
*
* WORST Worst available method
* MEDIUM Good performance/quality
* BEST Best available method
*
* w_enhance <wid>, <from_f>, <level>;
*
* w_gate <wid>, <cutoff>, <min>, <threshold>, <attenuation>;
*
* (See eel/eel.h for commands that are part of EEL.)
*
* w_mix <from_wid>, <to_wid>;
* Mix waveform <from_wid> into waveform <to_wid>.
* The amplitude envelope controls the mixing
* level, and the frequency envelope controls the
* playback frequency of the source waveform.
*
*/
#ifndef _A_AGW_H_
#define _A_AGW_H_
#ifdef __cplusplus
extern "C" {
#endif
int agw_open(void);
void agw_close(void);
/*
* Load and parse AGW script 'name',
* suggesting that the result be put
* in waveform 'wid'.
*
* Returns the actual id of the generated
* waveform, or -1 upon failure.
*/
int agw_load(int wid, const char *name);
#ifdef __cplusplus
};
#endif
#endif /* _A_AGW_H_ */
+297
View File
@@ -0,0 +1,297 @@
/*(LGPL)
---------------------------------------------------------------------------
a_bus.c - Audio Engine bus
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include <string.h>
#include "a_bus.h"
#include "a_struct.h"
#include "a_delay.h"
#include "a_tools.h"
#define BDBG(x)
static inline int __bus_run_fx(int bus, int slot, int *busses[], unsigned frames)
{
audio_bus_t *b = &bustab[bus];
--slot; /* No IFX on slot 0! */
switch(b->insert[slot].current_state)
{
case FX_STATE_RUNNING:
case FX_STATE_SILENT:
case FX_STATE_RESTING:
if(b->in_use) /* Do we have input? */
b->insert[slot].process(&b->insert[slot],
busses[bus], frames);
else
{
b->insert[slot].process_r(&b->insert[slot],
NULL, busses[bus], frames);
b->in_use = 1;
}
/* Check if the plugin actually produced valid output! */
return (FX_STATE_RUNNING == b->insert[slot].current_state);
default:
/* No plugin, or plugin not running. */
return 0;
}
}
static inline void __bus_process(int bus, int *busses[], int *master, unsigned frames)
{
audio_bus_t *b = &bustab[bus];
int i, s;
/* Tail detection hack for the DC supression filter... */
if(!b->in_use)
if(!dcf6s_silent(&b->dcfilter))
{
b->in_use = 1;
s32clear(busses[bus], frames);
}
if(b->in_use) /* Only if we actually have input! */
{
/* DC suppression filter */
dcf6s_process(&b->dcfilter, busses[bus], frames);
/* Mix pre-insert sends */
s32mix(busses[bus], master, b->bctl[0][ABC_SEND_MASTER], frames);
for(i = bus + 1; i < AUDIO_MAX_BUSSES; ++i)
if(s32mix(busses[bus], busses[i],
b->bctl[0][ABC_SEND_BUS_0 + i],
frames))
bustab[i].in_use = 1;
}
/* Mix post-insert sends */
for(s = 1; s <= AUDIO_MAX_INSERTS; ++s)
{
/* Run the insert */
if(!__bus_run_fx(bus, s, busses, frames))
continue; /* No output, no sends! */
s32mix(busses[bus], master, b->bctl[s][ABC_SEND_MASTER], frames);
for(i = bus + 1; i < AUDIO_MAX_BUSSES; ++i)
if(s32mix(busses[bus], busses[i],
b->bctl[s][ABC_SEND_BUS_0 + i],
frames))
bustab[i].in_use = 1;
}
/* Prepare for the next engine cycle */
if(b->in_use)
{
s32clear(busses[bus], frames);
b->in_use = 0;
}
}
void bus_process_all(int *bufs[], int *master, unsigned frames)
{
int i;
for(i = 0; i < AUDIO_MAX_BUSSES; ++i)
__bus_process(i, bufs, master, frames);
}
static void __remove_insert(unsigned bus, unsigned insert)
{
audio_bus_t *b = &bustab[bus];
audio_plugin_close(&b->insert[insert]);
}
static void __change_insert(unsigned bus, unsigned insert, int fx)
{
int reopen = 0;
audio_bus_t *b = &bustab[bus];
audio_plugin_t *ins;
if(bus < 1)
return;
__remove_insert(bus, insert);
ins = &b->insert[insert-1];
switch(fx)
{
case AFX_NONE:
break;
case AFX_DELAY:
break;
case AFX_ECHO:
break;
case AFX_REVERB:
delay_init(ins);
reopen = 1;
break;
case AFX_CHORUS:
break;
case AFX_FLANGER:
break;
case AFX_FILTER:
break;
case AFX_DIST:
break;
case AFX_WAH:
break;
case AFX_EQ:
break;
case AFX_ENHANCER:
break;
case AFX_COMPRESSOR:
break;
case AFX_MAXIMIZER:
break;
}
if(!reopen)
return;
if(audio_plugin_open(ins, a_settings.buffersize,
a_settings.samplerate, a_settings.quality) < 0)
return;
/* FIXME: This should be done in a butler thread... */
if(audio_plugin_state(ins, FX_STATE_RUNNING) < 0)
{
audio_plugin_close(ins);
return;
}
/* /FIXME */
/*
FIXME: We rely on plugins loading their defaults. Plugins should
FIXME: probably be able to export their default settings instead.
*/
#if 0
for(i = 0; i < 5; ++i)
ins->control(ins, FXC_PARAM_1 + i,
b->bctl[insert][ABC_FX1_PARAM_1 + i]);
#endif
b->bctl[insert][ABC_FX_TYPE] = fx;
}
void bus_ctl_set(unsigned bus, unsigned slot, unsigned ctl, int arg)
{
if(0 BDBG(+ 1))
{
const char *ctls[] = {
"ABC_FX_TYPE",
"ABC_FX_PARAM_1",
"ABC_FX_PARAM_2",
"ABC_FX_PARAM_3",
"ABC_FX_PARAM_4",
"ABC_FX_PARAM_5",
"ABC_FX_PARAM_6",
"ABC_SEND_MASTER",
"ABC_SEND_BUS_0",
"ABC_SEND_BUS_1",
"ABC_SEND_BUS_2",
"ABC_SEND_BUS_3",
"ABC_SEND_BUS_4",
"ABC_SEND_BUS_5",
"ABC_SEND_BUS_6",
"ABC_SEND_BUS_7",
"<ILLEGAL!>"
};
unsigned c = ctl;
if(ctl < ABC_FIRST || ctl > ABC_LAST)
c = (unsigned)(ABC_LAST + 1);
log_printf(DLOG, "Bus %u, slot %u %s set to %d\n", bus, slot, ctls[c], arg);
}
switch(ctl)
{
case ABC_FX_TYPE:
if(arg != bustab[bus].bctl[slot][ctl])
__change_insert(bus, slot, arg);
break;
case ABC_FX_PARAM_1:
case ABC_FX_PARAM_2:
case ABC_FX_PARAM_3:
case ABC_FX_PARAM_4:
case ABC_FX_PARAM_5:
case ABC_FX_PARAM_6:
if(slot < 1)
break;
if(bustab[bus].insert[slot].current_state < FX_STATE_READY)
break;
bustab[bus].insert[slot].control(&bustab[bus].insert[slot],
ctl - ABC_FX_PARAM_1 + FXC_PARAM_1,
arg);
bustab[bus].bctl[slot][ctl] = arg;
break;
default:
if(slot >= AUDIO_MAX_INSERTS)
break;
if(ctl > ABC_LAST)
break;
bustab[bus].bctl[slot][ctl] = arg;
break;
}
/*
* This is where the "connection table" would be
* updated in a more optimized implementation.
*/
}
static int _is_open = 0;
void audio_bus_open(void)
{
int i, s;
if(_is_open)
return;
memset(bustab, 0, sizeof(bustab));
for(i = 0; i < AUDIO_MAX_BUSSES; ++i)
{
for(s = 0; s < AUDIO_MAX_INSERTS; ++s)
memcpy(bustab[i].bctl[s], a_bus_def_ctl,
sizeof(a_bus_def_ctl));
dcf6s_init(&bustab[i].dcfilter, a_settings.samplerate);
dcf6s_set_f(&bustab[i].dcfilter, 10);
bustab[i].in_use = 0;
}
_is_open = 1;
}
void audio_bus_close(void)
{
unsigned i, j;
if(!_is_open)
return;
for(i = 0; i < AUDIO_MAX_BUSSES; ++i)
for(j = 0; j < AUDIO_MAX_INSERTS; ++j)
__change_insert(i, j, AFX_NONE);
memset(bustab, 0, sizeof(bustab));
_is_open = 0;
}
+50
View File
@@ -0,0 +1,50 @@
/*(LGPL)
---------------------------------------------------------------------------
a_bus.h - Audio Engine bus
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_BUS_H_
#define _A_BUS_H_
#include "a_globals.h"
#include "a_types.h"
#include "a_plugin.h"
#include "a_filters.h"
typedef struct
{
/* Control */
abcbank_t bctl[AUDIO_MAX_INSERTS+1];
/* DC filter (applied before all sends and inserts) */
dcf6s_t dcfilter;
int in_use; /* Set by whoever sends to the bus */
/* Insert FX plugins */
audio_plugin_t insert[AUDIO_MAX_INSERTS];
} audio_bus_t;
void audio_bus_open(void);
void audio_bus_close(void);
void bus_process_all(int *bufs[], int *master, unsigned frames);
void bus_ctl_set(unsigned bus, unsigned slot, unsigned ctl, int arg);
#endif /*_A_BUS_H_*/
+250
View File
@@ -0,0 +1,250 @@
/*(LGPL)
---------------------------------------------------------------------------
a_channel.c - Audio Engine high level control abstraction
---------------------------------------------------------------------------
* Copyright (C) 2001, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include "kobolog.h"
#include "a_globals.h"
#include "a_struct.h"
#include "a_control.h"
#include "a_sequencer.h"
#include "a_tools.h"
#define SDBG(x)
void channel_stop_all(void)
{
int i;
for(i = 0; i < AUDIO_MAX_VOICES; ++i)
voice_kill(voicetab + i);
for(i = 0; i < AUDIO_MAX_CHANNELS; ++i)
channeltab[i].playing = 0; /* Reset "voice" count */
sequencer_stop(-1);
}
/*
FIXME: What if there are sounds playing when an ACC_PATCH comes in!?
FIXME: Maybe patch plugins should have a "detach + release" feature?
FIXME:
FIXME: Another alternative would be some way of forwarding events to
FIXME: "unknown" tags (easy now that the controller must provide the
FIXME: tags!) on to whatever patch they were started by. That is, an
FIXME: actively managed, per channel "patch history". "Affect all"
FIXME: events (-1 tags) would have to be processed *and* forwarded.
FIXME:
FIXME: Patch plugins could just pass events for unknown tags to some
FIXME: "forward to previous patch" event port, that the engine checks
FIXME: after running a patch plugin. Of course, this would require
FIXME: that all patches actually remember tags, even if they don't
FIXME: really care about them... In fact, it would be a problem even
FIXME: for patches that *do* use tags, as those will generally
FIXME: "remember" only tags that actually get the required resources.
FIXME: (That is, if a poly patch runs out of voices, it'll start
FIXME: forgetting tags...)
FIXME:
FIXME: A much simpler solution would be to just keep patches running
FIXME: until they say they have no more "contexts" active. Events
FIXME: would have to be duplicated - or we could have all plugins
FIXME: just pass events on to a "garbage port", that actually becomes
FIXME: a forwarding port when needed. In the normal case, the engine
FIXME: would just flush this port when appropriate.
*/
/*
FIXME: Maybe we actually want to pass ACC_PATCH change events
FIXME: on to patch drivers? If so, should they be sent to
FIXME: the old or new patch, or both? That question alone
FIXME: seems to suggest that ACC_PATCH should die here, but
FIXME: then again, some drivers might need these events...
*/
static inline void channel_process(audio_channel_t *c, unsigned frames)
{
aev_event_t *first_ev = c->port.first;
unsigned split_frames = frames;
/*
* This one's rather ugly... Better ideas, anyone?
*/
aev_timestamp_t timer_save = aev_timer;
while(1)
{
aev_event_t *ev = c->port.first;
aev_event_t *prev_ev = NULL;
aev_event_t *rerun_ev = NULL;
while(ev && (AEV_TIME(ev->frame, 0) < split_frames))
{
if(ev->type != CE_CONTROL)
{
prev_ev = ev;
ev = ev->next;
continue;
}
c->rctl[ev->index] = ev->arg2;
switch(ev->index)
{
/* case_ACC_NOTRANSFORM:*/
case ACC_GROUP:
case ACC_PRIORITY:
break;
case ACC_PATCH:
{
aev_event_t *del_ev = ev;
SDBG(log_printf(D3LOG, "(patch) ");)
if(c->ctl[ACC_PATCH] == c->rctl[ACC_PATCH])
{
SDBG(log_printf(D3LOG, "[IGNORE]\n");)
ev = ev->next;
if(prev_ev)
prev_ev->next = ev;
else
c->port.first = ev;
}
else if(ev == first_ev)
{
SDBG(log_printf(D3LOG, "[FIRST]\n");)
c->ctl[ACC_PATCH] = c->rctl[ACC_PATCH];
ev = ev->next;
c->port.first = ev;
first_ev = NULL;
}
else
{
SDBG(log_printf(D3LOG, "[SPLIT]\n");)
split_frames = AEV_TIME(ev->frame, 0);
rerun_ev = ev->next;
/*
* Note that we really must break the list!
* Going by the timestams is insufficient,
* as there may be more events for the same
* frame.
*/
if(prev_ev)
prev_ev->next = NULL;
else
c->port.first = NULL;
ev = NULL;
}
aev_free(del_ev);
continue;
}
case ACC_PRIM_BUS:
case ACC_SEND_BUS:
break;
case_ACC_ADDTRANSFORM:
{
audio_group_t *g;
int min, max;
g = grouptab + c->rctl[ACC_GROUP];
min = addtrans_min[ev->index -
ACC_ADDTRANSFORM_FIRST];
max = addtrans_max[ev->index -
ACC_ADDTRANSFORM_FIRST];
ev->arg2 += g->ctl[ev->index];
ev->arg2 -= addtrans_bias[ev->index -
ACC_ADDTRANSFORM_FIRST];
if(ev->arg2 < min)
ev->arg2 = min;
else if(ev->arg2 > max)
ev->arg2 = max;
break;
}
case_ACC_MULTRANSFORM:
{
audio_group_t *g;
g = grouptab + c->rctl[ACC_GROUP];
ev->arg2 = fixmul(ev->arg2, g->ctl[ev->index]);
break;
}
}
prev_ev = ev;
ev = ev->next;
}
/* Run the patch plugin! */
/* if(c->ctl[ACC_PATCH] < AUDIO_MAX_PATCHES)*/
{
audio_patch_t *p = patchtab + c->ctl[ACC_PATCH];
p->process(p, c, split_frames);
}
/* Apply last patch change, if any */
c->ctl[ACC_PATCH] = c->rctl[ACC_PATCH];
frames -= split_frames;
if(!frames)
break; /* Done! */
SDBG(log_printf(D3LOG, "split!\n");)
/* Set up event port and timing for next fragment */
aev_advance_timer(split_frames);
split_frames = frames;
c->port.first = rerun_ev;
}
aev_timer = timer_save;
}
void channel_process_all(unsigned frames)
{
int i;
for(i = 0; i < AUDIO_MAX_CHANNELS; ++i)
channel_process(channeltab + i, frames);
}
static int _wasinit = 0;
void audio_channel_open(void)
{
int i;
if(_wasinit)
return;
memset(channeltab, 0, sizeof(channeltab));
for(i = 0; i < AUDIO_MAX_CHANNELS; ++i)
{
char *buf = malloc(64);
snprintf(buf, 64, "Audio Channel %d", i);
aev_port_init(&channeltab[i].port, buf);
acc_copya(&channeltab[i].rctl, &a_channel_def_ctl);
acc_copya(&channeltab[i].ctl, &a_channel_def_ctl);
channeltab[i].voices = NULL;
}
_wasinit = 1;
}
void audio_channel_close(void)
{
int i;
if(!_wasinit)
return;
for(i = 0; i < AUDIO_MAX_CHANNELS; ++i)
{
aev_flush(&channeltab[i].port);
free((char *)channeltab[i].port.name);
}
memset(channeltab, 0, sizeof(channeltab));
_wasinit = 0;
}
+251
View File
@@ -0,0 +1,251 @@
/*(LGPL)
---------------------------------------------------------------------------
a_channel.h - Audio Engine high level control abstraction
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_CHANNEL_H_
#define _A_CHANNEL_H_
#include "a_globals.h"
#include "a_types.h"
#include "a_voice.h"
/*----------------------------------------------------------
Audio Group (Abstraction for group control)
----------------------------------------------------------*/
typedef struct audio_group_t
{
int changed;
accbank_t ctl;
} audio_group_t;
/*----------------------------------------------------------
Audio Channel (Abstraction for channel control)
----------------------------------------------------------*/
typedef struct audio_channel_t
{
audio_voice_t *voices; /* For "real" voices only */
int playing; /* # of "voices" playing */
accbank_t rctl; /* Raw controls */
accbank_t ctl; /* Transformed controls */
/* Event Control */
aev_port_t port; /* Event input port */
patch_closure_t closure; /* Per-channel data for patch */
} audio_channel_t;
/*----------------------------------------------------------
Audio Channel Event Interface
------------------------------------------------------------
* These events are passed through the channel management
* code of the engine, where they are transformed through
* the groups. Then they're passed on directly to the patch
* plugins for custom processing.
*
* Notes:
* * The rctl array (raw controls) is kept up to date
* by the engine.
*
* * The ctl array (transformed controls) must be
* updated by the patch plugin as it receives the
* corresponding events.
*
* * For things to work properly, patch plugins must
* remove all events for each frame processed.
*
* * Tags passed to CE_CONTROL[2] can be any positive
* value including 0. Negative values are reserved
* for special uses: -1 means "all voices", whereas
* -2 means "new voices only"; ie -2 will cause no
* other action than updating the channel control
* values.
*/
typedef enum channel_events_t
{
CE_START = 0, /* arg1 = tag, arg2 = pitch, arg3 = velocity */
CE_STOP, /* arg1 = tag, arg2 = velocity */
CE_CONTROL /* arg1 = tag, index = ctl, arg2 = value */
} channel_events_t;
/*
* Inline interface for sending the above events...
*/
static inline aev_event_t *ce_start_i(audio_channel_t *c, Uint16 delay,
int tag, int pitch, int velocity)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = CE_START;
ev->frame = delay + aev_timer;
ev->arg1 = tag;
ev->arg2 = pitch;
ev->arg3 = velocity;
aev_insert(&c->port, ev);
return ev;
}
static inline aev_event_t *ce_stop_i(audio_channel_t *c, Uint16 delay,
int tag, int velocity)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = CE_STOP;
ev->frame = delay + aev_timer;
ev->arg1 = tag;
ev->arg2 = velocity;
aev_insert(&c->port, ev);
return ev;
}
static inline aev_event_t *ce_control_i(audio_channel_t *c, Uint16 delay,
int tag, Uint8 ctl, int value)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = CE_CONTROL;
ev->frame = delay + aev_timer;
ev->arg1 = tag;
ev->index = ctl;
ev->arg2 = value;
aev_insert(&c->port, ev);
return ev;
}
/*
* Fast, non-sorting versions.
*/
static inline aev_event_t *ce_start(audio_channel_t *c, Uint16 delay,
int tag, int pitch, int velocity)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = CE_START;
ev->frame = delay + aev_timer;
ev->arg1 = tag;
ev->arg2 = pitch;
ev->arg3 = velocity;
aev_write(&c->port, ev);
return ev;
}
static inline aev_event_t *ce_stop(audio_channel_t *c, Uint16 delay,
int tag, int velocity)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = CE_STOP;
ev->frame = delay + aev_timer;
ev->arg1 = tag;
ev->arg2 = velocity;
aev_write(&c->port, ev);
return ev;
}
static inline aev_event_t *ce_control(audio_channel_t *c, Uint16 delay,
int tag, Uint8 ctl, int value)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = CE_CONTROL;
ev->frame = delay + aev_timer;
ev->arg1 = tag;
ev->index = ctl;
ev->arg2 = value;
aev_write(&c->port, ev);
return ev;
}
/*
* Brutally stop all voices on all channels.
*
* (Obviously, any other method is better - short of
* crashing the engine, perhaps... ;-)
*/
void channel_stop_all(void);
/*
* Process events and patch plugins for all channels.
*/
void channel_process_all(unsigned frames);
void audio_channel_open(void);
void audio_channel_close(void);
/*----------------------------------------------------------
Voice allocation stuff
----------------------------------------------------------*/
/* Link voice 'v' to channel 'c'. */
static inline void chan_link_voice(audio_channel_t *c, audio_voice_t *v)
{
v->next = c->voices;
if(v->next)
v->next->prev = v;
v->prev = NULL;
v->channel = c;
c->voices = v;
}
/* Unlink voice 'v' from channel 'c'. */
static inline void chan_unlink_voice(audio_voice_t *v)
{
if(v->prev)
{
v->prev->next = v->next;
if(v->next)
v->next->prev = v->prev;
}
else
{
v->channel->voices = v->next;
if(v->next)
v->next->prev = NULL;
}
v->channel = NULL;
}
static inline audio_voice_t *chan_get_first_voice(audio_channel_t *c)
{
return c->voices;
}
static inline audio_voice_t *chan_get_next_voice(audio_voice_t *v)
{
return v->next;
}
#endif /*_A_CHANNEL_H_*/
+226
View File
@@ -0,0 +1,226 @@
/*(LGPL)
---------------------------------------------------------------------------
a_commands.c - Asynchronous Command Interface for the audio engine
---------------------------------------------------------------------------
* Copyright (C) 2001-2003, 2007 David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include "kobolog.h"
#include "a_globals.h"
/*
* These really shouldn't be here, but are
* required for some shortcuts around here...
*/
#include "a_struct.h"
#include "a_sequencer.h"
#include "a_control.h"
#undef DBG2D
sfifo_t commands;
static inline void __push_command(command_t *cmd)
{
if(sfifo_space(&commands) < sizeof(command_t))
{
if(_audio_running)
log_printf(WLOG, "Audio command FIFO overflow!\n");
return;
}
sfifo_write(&commands, cmd, (unsigned)sizeof(command_t));
}
/*----------------------------------------------------------
Mixer Control
----------------------------------------------------------*/
void audio_bus_controlf(unsigned bus, unsigned slot, unsigned ctl, float arg)
{
audio_bus_control(bus, slot, ctl, (int)(arg * 65536.0));
}
void audio_bus_control(unsigned bus, unsigned slot, unsigned ctl, int arg)
{
command_t cmd;
#ifdef AUDIO_SAFE
if(bus >= AUDIO_MAX_BUSSES)
{
log_printf(ELOG, "audio_bus_control(): Bus out of range!\n");
return;
}
if(ctl >= ABC_COUNT)
{
log_printf(ELOG, "audio_bus_control(): Control out of range!\n");
return;
}
#endif
cmd.action = CMD_MCONTROL;
cmd.cid = (signed char)bus;
cmd.index = ctl;
cmd.arg1 = (int)slot;
cmd.arg2 = arg;
__push_command(&cmd);
}
/*----------------------------------------------------------
Group Control
----------------------------------------------------------*/
void audio_group_stop(unsigned gid)
{
int i;
#ifdef AUDIO_SAFE
if(gid >= AUDIO_MAX_GROUPS)
{
log_printf(ELOG, "audio_group_stop(): Group out of range!\n");
return;
}
#endif
/*
FIXME: This isn't exactly thread safe...
*/
for(i = 0; i < AUDIO_MAX_VOICES; i++)
{
audio_channel_t *c;
audio_voice_t *v = voicetab + i;
if(VS_STOPPED == v->state)
continue;
c = v->channel;
if((unsigned)c->ctl[ACC_GROUP] == gid)
audio_channel_stop(i, -1);
}
}
void audio_group_controlf(unsigned gid, unsigned ctl, float arg)
{
audio_group_control(gid, ctl, (int)(arg * 65536.0));
}
void audio_group_control(unsigned gid, unsigned ctl, int arg)
{
command_t cmd;
#ifdef AUDIO_SAFE
if(gid >= AUDIO_MAX_GROUPS)
{
log_printf(ELOG, "audio_group_control(): Group out of range!\n");
return;
}
if(ctl >= ACC_COUNT)
{
log_printf(ELOG, "audio_group_control(): Control out of range!\n");
return;
}
#endif
cmd.action = CMD_GCONTROL;
cmd.cid = (char)gid;
cmd.index = ctl;
cmd.arg1 = arg;
__push_command(&cmd);
}
/*----------------------------------------------------------
Channel Control
----------------------------------------------------------*/
void audio_channel_play(int cid, int tag, int pitch, int velocity)
{
command_t cmd;
#ifdef AUDIO_SAFE
if(cid < 0 || cid >= AUDIO_MAX_CHANNELS)
{
log_printf(ELOG, "audio_play(): Channel out of range!\n");
return;
}
#endif
cmd.action = CMD_PLAY;
cmd.cid = cid;
cmd.tag = tag;
cmd.arg1 = pitch;
cmd.arg2 = velocity;
__push_command(&cmd);
}
void audio_channel_controlf(int cid, int tag, int ctl, float arg)
{
if(ACC_IS_FIXEDPOINT(ctl))
arg *= 65536.0;
audio_channel_control(cid, tag, ctl, (int)arg);
}
void audio_channel_control(int cid, int tag, int ctl, int arg)
{
command_t cmd;
#ifdef AUDIO_SAFE
if(cid < 0 || cid >= AUDIO_MAX_CHANNELS)
{
log_printf(ELOG, "audio_channel_control(): Channel out of range!\n");
return;
}
if(ctl < 0 || ctl >= ACC_COUNT)
{
log_printf(ELOG, "audio_channel_control(): Control out of range!\n");
return;
}
#endif
cmd.action = CMD_CCONTROL;
cmd.cid = cid;
cmd.tag = tag;
cmd.index = (unsigned char)ctl;
cmd.arg1 = arg;
__push_command(&cmd);
}
void audio_channel_stop(int cid, int tag)
{
command_t cmd;
#ifdef AUDIO_SAFE
if(cid >= AUDIO_MAX_CHANNELS)
{
log_printf(ELOG, "audio_stop(): Channel out of range!\n");
return;
}
#endif
if(cid >= 0)
cmd.action = CMD_STOP;
else
cmd.action = CMD_STOP_ALL;
cmd.cid = cid;
cmd.tag = tag;
__push_command(&cmd);
}
int audio_channel_playing(int cid)
{
return channeltab[cid].playing;
}
void audio_bump(unsigned ms)
{
command_t cmd;
cmd.action = CMD_WAIT;
cmd.arg1 = (int)ms;
__push_command(&cmd);
}
+98
View File
@@ -0,0 +1,98 @@
/*(LGPL)
---------------------------------------------------------------------------
asynccmd.h - Asynchronous Command Interface for the audio engine
---------------------------------------------------------------------------
* Copyright (C) 2001-2003, 2007 David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _ASYNCCMD_H_
#define _ASYNCCMD_H_
#include "sfifo.h"
#ifdef __cplusplus
extern "C" {
#endif
#define MAX_COMMANDS 128
/*----------------------------------------------------------
Asynchronous command interface stuff
----------------------------------------------------------*/
typedef struct
{
enum
{
CMD_STOP = 0,
CMD_STOP_ALL,
CMD_PLAY,
CMD_CCONTROL, /* Channel Control */
CMD_GCONTROL, /* Group Control */
CMD_MCONTROL, /* Mixer Control */
CMD_WAIT /* Advance Audio Time */
} action;
signed char cid;
unsigned char index;
int tag;
int arg1;
int arg2;
} command_t;
extern sfifo_t commands;
/*----------------------------------------------------------
Mixer Control
----------------------------------------------------------*/
void audio_bus_controlf(unsigned bus, unsigned slot, unsigned ctl, float arg);
void audio_bus_control(unsigned bus, unsigned slot, unsigned ctl, int arg);
/*----------------------------------------------------------
Group Control
----------------------------------------------------------*/
//void audio_group_default(unsigned gid, unsigned ctl, int arg);
void audio_group_stop(unsigned gid);
void audio_group_controlf(unsigned gid, unsigned ctl, float arg);
void audio_group_control(unsigned gid, unsigned ctl, int arg);
/*----------------------------------------------------------
Channel Control
----------------------------------------------------------*/
void audio_channel_play(int cid, int tag, int pitch, int velocity);
void audio_channel_controlf(int cid, int tag, int ctl, float arg);
void audio_channel_control(int cid, int tag, int ctl, int arg);
void audio_channel_stop(int cid, int tag); /* -1 to stop all */
/*
FIXME: Broken API. Broken implementation. It just "seems" to work.
*/
int audio_channel_playing(int cid);
/*
* Set audio time for subsequent calls to 'ms'.
*/
void audio_bump(unsigned ms);
#ifdef __cplusplus
};
#endif
#endif /*_ASYNCCMD_H_*/
+113
View File
@@ -0,0 +1,113 @@
/*(LGPL)
---------------------------------------------------------------------------
a_control.c - Group/Channel/Voice Control Implementation
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* The control routing model:
*
* Group
* ^ |
* | V
* MIDI -> Channel -> Patch ---> Voice
* | |-> Voice
* V ... ...
* Bus Control '-> Voice
*
* Groups are used by Channels, to transform some of the control
* data sent to them, before passing it on to the current Patch.
* That is, they would effectively fit in in between Channels
* and Patches, from the data flow POV.
*
* Note that while Groups act as passive data containers most of
* the time, they *can* become active, and force Channels to
* refresh some Controls. This can happen when changing the
* "music volume" in an application, for example.
FIXME: What about busses with non-linear insert effects? Looks like
FIXME: at least *some* Group Controls should not be applied on the
FIXME: Channel or Patch level, but rather on the Busses... Maybe
FIXME: Busses should have 'group' fields? That way, one can still
FIXME: use them as with the current design, although for example
FIXME: ACC_VOLUME would be sent off to the bus send that acts as
FIXME: master volume for all Voices of the Group.
FIXME: Then again, this may result in general confusion! It's
FIXME: theoretically possible to select busses on a *per-Voice
FIXME: basis* (via Patches), and quite easy to select busses
FIXME: directly using MIDI CCs. Good bye to application control
FIXME: over the mixing...!
FIXME: The only simple solution I can see is to prevent sending
FIXME: between busses across group boundaries.
*/
#include "a_globals.h"
#include "a_control.h"
#include "a_tools.h"
const int addtrans_min[2] = { -65536, 0 };
const int addtrans_max[2] = { 65536, 128<<16 };
const int addtrans_bias[2] = { 0, 60<<16 };
/*----------------------------------------------------------
Group Control
----------------------------------------------------------*/
void acc_group_set(unsigned gid, unsigned ctl, int arg)
{
int i;
audio_group_t *g = grouptab + gid;
g->ctl[ctl] = arg;
if(!_audio_running)
return;
/* Update all channels of the group */
for(i = 0; i < AUDIO_MAX_CHANNELS; ++i)
{
audio_channel_t *c = channeltab + i;
if((unsigned)c->ctl[ACC_GROUP] != gid)
continue;
(void)ce_control_i(c, 0, -1, ctl, c->rctl[ctl]);
}
}
static int _is_open = 0;
void audio_group_open(void)
{
int i;
if(_is_open)
return;
memset(grouptab, 0, sizeof(grouptab));
for(i = 0; i < AUDIO_MAX_GROUPS; ++i)
acc_copya(&grouptab[i].ctl, &a_group_def_ctl);
_is_open = 1;
}
void audio_group_close(void)
{
if(!_is_open)
return;
memset(grouptab, 0, sizeof(grouptab));
_is_open = 0;
}
+91
View File
@@ -0,0 +1,91 @@
/*(LGPL)
---------------------------------------------------------------------------
a_control.h - Group/Channel/Voice Control Implementation
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_CONTROL_H_
#define _A_CONTROL_H_
#include <string.h>
#include "a_struct.h"
/*----------------------------------------------------------
Tools
----------------------------------------------------------*/
/* Controller bank copy macros */
static inline void acc_copy(accbank_t *to, accbank_t *from,
unsigned first, unsigned last)
{
memcpy(to + first, from + first, (last - first + 1) * sizeof(int));
}
static inline void acc_copya(accbank_t *to, accbank_t *from)
{
memcpy(to, from, sizeof(accbank_t));
}
#define ACC_IS_FIXEDPOINT(x) (((x) >= ACC_PAN) && ((x) <= ACC_Z))
#define ACC_NOTRANSFORM_FIRST ACC_GROUP
#define ACC_NOTRANSFORM_LAST ACC_SEND_BUS
#define case_ACC_NOTRANSFORM \
case ACC_GROUP: \
case ACC_PRIORITY: \
case ACC_PATCH: \
case ACC_PRIM_BUS: \
case ACC_SEND_BUS
#define for_ACC_NOTRANSFORM(x) \
for(x = ACC_NOTRANSFORM_FIRST; x <= ACC_NOTRANSFORM_LAST; ++x)
#define ACC_ADDTRANSFORM_FIRST ACC_PAN
#define ACC_ADDTRANSFORM_LAST ACC_PITCH
#define case_ACC_ADDTRANSFORM \
case ACC_PAN: \
case ACC_PITCH
#define for_ACC_ADDTRANSFORM(x) \
for(x = ACC_ADDTRANSFORM_FIRST; x <= ACC_ADDTRANSFORM_LAST; ++x)
extern const int addtrans_min[2];
extern const int addtrans_max[2];
extern const int addtrans_bias[2];
#define ACC_MULTRANSFORM_FIRST ACC_VOLUME
#define ACC_MULTRANSFORM_LAST ACC_SEND
#define case_ACC_MULTRANSFORM \
case ACC_VOLUME: \
case ACC_SEND
#define for_ACC_MULTRANSFORM(x) \
for(x = ACC_MULTRANSFORM_FIRST; x <= ACC_MULTRANSFORM_LAST; ++x)
/*----------------------------------------------------------
Group Control
----------------------------------------------------------*/
/*
* Set a group control, and update all channels that belong
* to the group. (Expensive!)
*/
void acc_group_set(unsigned gid, unsigned ctl, int arg);
void audio_group_open(void);
void audio_group_close(void);
#endif /*_A_CONTROL_H_*/
+594
View File
@@ -0,0 +1,594 @@
/*(LGPL)
---------------------------------------------------------------------------
a_delay.c - Feedback delay w/ LP filter
---------------------------------------------------------------------------
* Copyright (C) 2001, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include "a_globals.h"
#include "a_delay.h"
#include "a_tools.h"
#define DELAY_BUFSIZE 65536
#define DELAY_MAX_TAPS 16
#define DELAY_MAX_TAIL_TAPS 8
typedef struct dtap_t
{
unsigned delay;
int shift;
} dtap_t;
typedef struct delay_t
{
int *delaybuf;
int inspos;
int cl, cr, lpf; /* LPF */
int level;
unsigned taps, tailtaps;
int ttimer;
float tlevel;
dtap_t tap[DELAY_MAX_TAPS];
dtap_t tailtap[DELAY_MAX_TAIL_TAPS];
} delay_t;
#define DELAY_MASK (DELAY_BUFSIZE-1)
#define INTERNAL_BITS 8
static int _init(delay_t *d)
{
d->inspos = 0;
d->cl = 0;
d->cr = 0;
d->delaybuf = calloc(1, sizeof(int)*DELAY_BUFSIZE);
if(!d->delaybuf)
return -2;
return 0;
}
#define DB(x) delaybuf[(inspos-(x)) & DELAY_MASK]
#define TAP(n) (DB(d->tap[n].delay) >> d->tap[n].shift)
#define TTAP(n) (DB(d->tailtap[n].delay) >> d->tailtap[n].shift)
#if 0
/* Stereo in, stereo out */
static void o_delay_process_mix_s(delay_t *d, int *in, int *out, unsigned frames)
{
int *delaybuf = d->delaybuf;
int inspos = d->inspos;
int cl = d->cl;
int cr = d->cr;
unsigned s = frames;
int outl, outr;
int vm = d->level >> 8;
int i;
while(s--)
{
/* Feedback with LP filters */
cl += (TTAP(0) + TTAP(3) + TTAP(4) - cl) >> 3;
cr += (TTAP(1) + TTAP(2) + TTAP(5) - cr) >> 3;
DB(0) = cl;
DB(1) = cr;
/* "Tap 0" */
outl = cl;
outr = cr;
/* Taps */
for(i = 0; i < d->taps; i += 2)
{
outl += TAP(i);
outr += TAP(i+1);
}
/* Input */
DB(0) += in[0];
DB(1) += in[1];
/* Output */
out[0] += (outl*vm)>>8;
out[1] += (outr*vm)>>8;
inspos += 2;
in += 2;
out += 2;
}
d->cl = cl;
d->cr = cr;
d->inspos = inspos;
}
#endif
/*
* Stereo, replacing, without level control.
*/
static void o_delay_process_r_s(delay_t *d, int *in, int *out, unsigned frames)
{
int *delaybuf = d->delaybuf;
int inspos = d->inspos;
int cl = d->cl;
int cr = d->cr;
unsigned s = frames;
int outl, outr;
unsigned i;
while(s--)
{
/* Tail taps */
outl = outr = 0;
for(i = 0; i < d->tailtaps; i += 2)
{
outl += TTAP(i);
outr += TTAP(i+1);
}
/* LPF */
// cl += (outl - (cl>>INTERNAL_BITS)) * d->lpf;
// cr += (outr - (cr>>INTERNAL_BITS)) * d->lpf;
cl += (outl - cl) * d->lpf >> INTERNAL_BITS;
cr += (outr - cr) * d->lpf >> INTERNAL_BITS;
/* Feedback */
DB(0) = cl;// >> INTERNAL_BITS;
DB(1) = cr;// >> INTERNAL_BITS;
/* "Tap 0" */
outl = cl;
outr = cr;
/* Taps */
for(i = 0; i < d->taps; i += 2)
{
outl += TAP(i);
outr += TAP(i+1);
}
/* Input */
DB(0) += in[0] << INTERNAL_BITS;
DB(1) += in[1] << INTERNAL_BITS;
/* Output */
out[0] = outl >> INTERNAL_BITS;
out[1] = outr >> INTERNAL_BITS;
inspos += 2;
in += 2;
out += 2;
}
d->cl = cl;
d->cr = cr;
d->inspos = inspos;
}
/*
* Stereo, replacing in-place, without output level control
* (for inserts)
*/
static void o_delay_process_s(delay_t *d, int *buf, unsigned frames)
{
int *delaybuf = d->delaybuf;
int inspos = d->inspos;
int cl = d->cl;
int cr = d->cr;
int outl, outr;
unsigned i, s;
frames <<= 1;
for(s = 0; s < frames; s += 2)
{
/* Tail taps */
outl = outr = 0;
for(i = 0; i < d->tailtaps; i += 2)
{
outl += TTAP(i);
outr += TTAP(i+1);
}
/* LP filters */
// cl += (outl - (cl>>INTERNAL_BITS)) * d->lpf;
// cr += (outr - (cr>>INTERNAL_BITS)) * d->lpf;
cl += (outl - cl) * d->lpf >> INTERNAL_BITS;
cr += (outr - cr) * d->lpf >> INTERNAL_BITS;
/* Input + Feedback */
DB(0) = cl + (buf[s] << INTERNAL_BITS);
DB(1) = cr + (buf[s+1] << INTERNAL_BITS);
/* "Tap 0" - the Feedback Signal */
outl = cl;
outr = cr;
/* Early Reflection Taps */
for(i = 0; i < d->taps; i += 2)
{
outl += TAP(i);
outr += TAP(i+1);
}
/* Output */
buf[s] = outl >> INTERNAL_BITS;
buf[s+1] = outr >> INTERNAL_BITS;
inspos += 2;
}
d->cl = cl;
d->cr = cr;
d->inspos = inspos;
}
/*
* Stereo, replacing, with silent input and no level control.
* Returns the approximate peak level of the generated output.
*/
static int o_delay_process_tail_s(delay_t *d, int *out, unsigned frames)
{
int level = 0;
int *delaybuf = d->delaybuf;
int inspos = d->inspos;
int cl = d->cl;
int cr = d->cr;
int outl, outr;
unsigned i, s;
frames <<= 1;
for(s = 0; s < frames; s += 2)
{
/* Tail taps */
outl = outr = 0;
for(i = 0; i < d->tailtaps; i += 2)
{
outl += TTAP(i);
outr += TTAP(i+1);
}
/* LP filters */
// cl += (outl - (cl>>INTERNAL_BITS)) * d->lpf;
// cr += (outr - (cr>>INTERNAL_BITS)) * d->lpf;
cl += (outl - cl) * d->lpf >> INTERNAL_BITS;
cr += (outr - cr) * d->lpf >> INTERNAL_BITS;
/* Feedback (No input!) */
DB(0) = cl;
DB(1) = cr;
/* "Tap 0" - the Feedback Signal */
outl = cl;
outr = cr;
/* Early Reflection Taps */
for(i = 0; i < d->taps; i += 2)
{
outl += TAP(i);
outr += TAP(i+1);
}
/* Output */
out[s] = outl >> INTERNAL_BITS;
out[s+1] = outr >> INTERNAL_BITS;
/* Level meter */
level |= labs(outl) | labs(outr);
inspos += 2;
}
d->cl = cl;
d->cr = cr;
d->inspos = inspos;
return level >> INTERNAL_BITS;
}
#if 0
/* UNTESTED: Mono in, stereo out. Obviously not in-place capable! */
void o_delay_process_mix_ms(delay_t *d, int *in, int *out, unsigned frames)
{
int *delaybuf = d->delaybuf;
int inspos = d->inspos;
int cl = d->cl;
int cr = d->cr;
int s = frames;
int outl, outr;
int vm = d->level >> 8;
int i;
while(s--)
{
/* Feedback with LP filters */
cl += (TTAP(0) + TTAP(3) + TTAP(4) - cl) >> 3;
cr += (TTAP(1) + TTAP(2) + TTAP(5) - cr) >> 3;
DB(0) = cl;
DB(1) = cr;
/* "Tap 0" */
outl = cl;
outr = cr;
/* Taps */
for(i = 0; i < d->taps; i += 2)
{
outl += TAP(i);
outr += TAP(i+1);
}
/* Input */
DB(0) += in[0];
DB(1) += in[0];
/* Output */
out[0] += (outl*vm)>>8;
out[1] += (outr*vm)>>8;
inspos += 2;
in += 1;
out += 2;
}
d->cl = cl;
d->cr = cr;
d->inspos = inspos;
}
#endif
#undef DB
#undef TAP
/*
* New API
======================================================================
*/
static void _load_taps(struct audio_plugin_t *p, const float *taps)
{
int i;
p->ctl[DC_EARLY_TIME] = (int)(1.0 * 65536.0);
p->ctl[DC_TAIL_TIME] = (int)(1.0 * 65536.0);
p->ctl[DC_EARLY_LEVEL] = (int)(1.0 * 65536.0);
p->ctl[DC_FEEDBACK] = (int)(1.0 * 65536.0);
p->ctl[DC_LP_FILTER] = (int)(6500.0 * 65536.0);
for(i = 0; i < DELAY_MAX_TAPS + DELAY_MAX_TAIL_TAPS; ++i)
{
p->ctl[DC_EARLY_TAP_1_TIME + i*2] = (int)(*taps++ * 65536.0);
p->ctl[DC_EARLY_TAP_1_LEVEL + i*2] = (int)(*taps++ * 65536.0);
}
}
static void _scale_early_taps(struct audio_plugin_t *p)
{
delay_t *d = (delay_t *)p->user;
unsigned i, t, scale;
/* samples per ms */
float s_ms = (float)p->ctl[FXC_SAMPLERATE] * 0.001;
s_ms *= (float)p->ctl[DC_EARLY_TIME]; /* scale */
s_ms *= 1.0/1024.0; /* Throw away 10 of the 16 fraction bits */
scale = (unsigned)s_ms;
for(t = 0, i = 0; i < DELAY_MAX_TAPS; ++i)
{
unsigned delay;
int shift;
if(!p->ctl[DC_EARLY_TAP_1_LEVEL + i*2])
continue;
shift = fixp2shift(p->ctl[DC_EARLY_TAP_1_LEVEL + i*2]);
delay = (unsigned)p->ctl[DC_EARLY_TAP_1_TIME + i*2] >> 8;
delay *= scale;
delay >>= 14;
if(delay > DELAY_BUFSIZE-2)
delay = DELAY_BUFSIZE-2;
else if(delay < 0)
delay = 0;
delay &= (DELAY_MASK-1);
delay |= t & 1; /* L->L, R->R, L->L, R->R,... */
d->tap[t].delay = delay;
d->tap[t].shift = shift;
++t;
}
d->taps = t;
}
static void _scale_tail_taps(struct audio_plugin_t *p)
{
delay_t *d = (delay_t *)p->user;
unsigned i, t, scale;
/* samples per ms */
float s_ms = (float)p->ctl[FXC_SAMPLERATE] * 0.001;
s_ms *= (float)p->ctl[DC_TAIL_TIME]; /* scale */
s_ms *= 1.0/1024.0; /* Throw away 10 of the 16 fraction bits */
scale = (unsigned)s_ms;
for(t = 0, i = 0; i < DELAY_MAX_TAIL_TAPS; ++i)
{
unsigned delay;
int shift;
if(!p->ctl[DC_TAIL_TAP_1_LEVEL + i*2])
continue;
shift = fixp2shift(p->ctl[DC_TAIL_TAP_1_LEVEL + i*2]);
delay = (unsigned)p->ctl[DC_TAIL_TAP_1_TIME + i*2] >> 8;
delay *= scale;
delay >>= 14;
if(delay > DELAY_BUFSIZE-2)
delay = DELAY_BUFSIZE-2;
delay &= (DELAY_MASK-1);
delay |= (t>>1) & 1; /* L->L, L->R, R->L, R->R,... */
d->tailtap[t].delay = delay;
d->tailtap[t].shift = shift;
++t;
}
d->tailtaps = t;
}
static inline void __calc_filters(audio_plugin_t *p)
{
delay_t *d = (delay_t *)p->user;
d->lpf = p->ctl[DC_LP_FILTER] /
p->ctl[FXC_SAMPLERATE] >> (16-INTERNAL_BITS);
if(d->lpf > (1<<(16-INTERNAL_BITS)))
d->lpf = (1<<(16-INTERNAL_BITS));
}
static const float default_taps[(DELAY_MAX_TAPS + DELAY_MAX_TAIL_TAPS)*2] = {
/* Early Reflection Taps */
17, 1.0/4, 31, 1.0/4,
87, 1.0/4, 179, 1.0/4,
379, 1.0/16, 246, 1.0/16,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
/* Tail Feedback Taps */
552, 1.0/4, 642, 1.0/4,
883, 1.0/4, 851, 1.0/4,
1204, 1.0/4, 1176, 1.0/4,
0, 0, 0, 0
};
static int delay_state(struct audio_plugin_t *p, audio_fxstates_t new_state)
{
delay_t *d;
if(new_state > p->current_state)
switch(new_state)
{
case FX_STATE_CLOSED: /* Can't happen. */
case FX_STATE_OPEN:
if(!audio_plugin_alloc_ctls(p, DC_COUNT))
return -1;
d = calloc(1, sizeof(delay_t));
if(!d)
return -2;
p->user = d;
/*_load_taps(d, default_taps, p->ctl[FXC_SAMPLERATE]);*/
_load_taps(p, default_taps);
break;
case FX_STATE_READY:
d = (delay_t *)p->user;
if(_init(d) < 0)
return -2;
_scale_early_taps(p);
_scale_tail_taps(p);
__calc_filters(p);
break;
case FX_STATE_PAUSED:
case FX_STATE_RUNNING:
case FX_STATE_SILENT:
case FX_STATE_RESTING:
p->current_state = FX_STATE_RESTING;
break;
}
else
switch(new_state)
{
case FX_STATE_CLOSED:
free(p->user);
p->user = NULL;
break;
case FX_STATE_OPEN:
d = (delay_t *)p->user;
free(d->delaybuf);
d->delaybuf = NULL;
break;
case FX_STATE_READY:
break;
case FX_STATE_PAUSED:
case FX_STATE_RUNNING: /* Can't happen. */
case FX_STATE_SILENT: /* Can't happen. */
case FX_STATE_RESTING: /* Can't happen. */
break;
}
return 0;
}
static void delay_control(struct audio_plugin_t *p, unsigned ctl, int arg)
{
/* 1: Early reflections duration
* 2: Tail duration
* 3: Early reflections level
* 4: Feedback Level
* 5: Feedback LPF Cutoff
* 6: (Unused)
*/
p->ctl[ctl] = arg;
switch(ctl)
{
case DC_EARLY_TIME:
_scale_early_taps(p);
break;
case DC_TAIL_TIME:
_scale_tail_taps(p);
break;
case DC_EARLY_LEVEL:
case DC_FEEDBACK:
break;
case DC_LP_FILTER:
__calc_filters(p);
break;
}
}
static void delay_process(struct audio_plugin_t *p, int *buf, unsigned frames)
{
delay_t *d = (delay_t *)p->user;
d->tlevel = 1000;
d->ttimer = 0;
p->current_state = FX_STATE_RUNNING;
o_delay_process_s(d, buf, frames);
}
static void delay_process_r(struct audio_plugin_t *p, int *in, int *out, unsigned frames)
{
delay_t *d = (delay_t *)p->user;
if(in)
{
d->tlevel = 1000;
d->ttimer = 0;
p->current_state = FX_STATE_RUNNING;
o_delay_process_r_s(d, in, out, frames);
}
else
{
int level;
if(FX_STATE_RESTING == p->current_state)
return;
level = o_delay_process_tail_s(d, out, frames);
d->tlevel += (float)((level - d->tlevel) * frames) /
(float)(p->ctl[FXC_SAMPLERATE] * 0.1);
d->ttimer += frames;
if(d->ttimer < p->ctl[FXC_SAMPLERATE] * 2)
return;
if(d->tlevel < 5.0)
{
d->tlevel = 1000;
p->current_state = FX_STATE_RESTING;
return;
}
}
}
void delay_init(struct audio_plugin_t *p)
{
p->state = delay_state;
p->control = delay_control;
p->process = delay_process;
p->process_r = delay_process_r;
}
+122
View File
@@ -0,0 +1,122 @@
/*(LGPL)
---------------------------------------------------------------------------
a_delay.h - Feedback delay w/ LP filter
---------------------------------------------------------------------------
* Copyright (C) 2001, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_DELAY_H_
#define _A_DELAY_H_
#include "a_plugin.h"
typedef enum
{
/*
* Uniform parameters.
*
*
*/
DC_EARLY_TIME = FXC_PARAM_1, /* Early Reflections Time Scale */
DC_TAIL_TIME = FXC_PARAM_2, /* Tail Time Scale */
/*TODO:*/ DC_EARLY_LEVEL = FXC_PARAM_3, /* Early Reflections Level */
/*TODO:*/ DC_FEEDBACK = FXC_PARAM_4, /* Tail Feedback Level */
DC_LP_FILTER = FXC_PARAM_5, /* Feedback LP Filter Cutoff */
/*
* Early reflection taps.
*
* Times are in ms, 16:16 fixp, and levels are in the
* usual 16:16 fixp format, where 1.0 [65536] is unity
* gain.
*
* Note that there is an implicit "tap 0" that always
* taps at full level directly from the tail feedback
* signal as it's sent back into the delay buffer. That
* is, using only tail feedback taps still results in
* an effect - more specifically, a multi tap feedback
* delay.
*
* (Tech note: In lower quality modes, taps can be
* grouped according to level, or even scaled down to
* the nearest power-of-two values for speed.)
*/
DC_EARLY_TAP_1_TIME = FXC_USER,
DC_EARLY_TAP_1_LEVEL,
DC_EARLY_TAP_2_TIME,
DC_EARLY_TAP_2_LEVEL,
DC_EARLY_TAP_3_TIME,
DC_EARLY_TAP_3_LEVEL,
DC_EARLY_TAP_4_TIME,
DC_EARLY_TAP_4_LEVEL,
DC_EARLY_TAP_5_TIME,
DC_EARLY_TAP_5_LEVEL,
DC_EARLY_TAP_6_TIME,
DC_EARLY_TAP_6_LEVEL,
DC_EARLY_TAP_7_TIME,
DC_EARLY_TAP_7_LEVEL,
DC_EARLY_TAP_8_TIME,
DC_EARLY_TAP_8_LEVEL,
DC_EARLY_TAP_9_TIME,
DC_EARLY_TAP_9_LEVEL,
DC_EARLY_TAP_10_TIME,
DC_EARLY_TAP_10_LEVEL,
DC_EARLY_TAP_11_TIME,
DC_EARLY_TAP_11_LEVEL,
DC_EARLY_TAP_12_TIME,
DC_EARLY_TAP_12_LEVEL,
DC_EARLY_TAP_13_TIME,
DC_EARLY_TAP_13_LEVEL,
DC_EARLY_TAP_14_TIME,
DC_EARLY_TAP_14_LEVEL,
DC_EARLY_TAP_15_TIME,
DC_EARLY_TAP_15_LEVEL,
DC_EARLY_TAP_16_TIME,
DC_EARLY_TAP_16_LEVEL,
/*
* Tail feedback taps.
*
* These work like the early reflection taps, but the
* resulting signal is LP filtered and sent back into
* the start of the delay buffer, together with the
* input signal.
*/
DC_TAIL_TAP_1_TIME,
DC_TAIL_TAP_1_LEVEL,
DC_TAIL_TAP_2_TIME,
DC_TAIL_TAP_2_LEVEL,
DC_TAIL_TAP_3_TIME,
DC_TAIL_TAP_3_LEVEL,
DC_TAIL_TAP_4_TIME,
DC_TAIL_TAP_4_LEVEL,
DC_TAIL_TAP_5_TIME,
DC_TAIL_TAP_5_LEVEL,
DC_TAIL_TAP_6_TIME,
DC_TAIL_TAP_6_LEVEL,
DC_TAIL_TAP_7_TIME,
DC_TAIL_TAP_7_LEVEL,
DC_TAIL_TAP_8_TIME,
DC_TAIL_TAP_8_LEVEL,
DC_COUNT
} delay_controls_t;
void delay_init(struct audio_plugin_t *p);
#endif /*_A_DELAY_H_*/
+242
View File
@@ -0,0 +1,242 @@
/*(LGPL)
---------------------------------------------------------------------------
a_events.c - Internal Event System
---------------------------------------------------------------------------
* Copyright (C) 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#define EVDBG(x)
#include <stdlib.h>
#include "kobolog.h"
#include "a_events.h"
#define EVENTS_PER_BLOCK 256
/*----------------------------------------------------------
The global event timestamp time base
----------------------------------------------------------*/
Uint16 aev_timer = 0;
/*----------------------------------------------------------
Event pool management
----------------------------------------------------------*/
aev_event_t *aev_event_pool = NULL;
#ifdef AEV_TRACKING
int aev_event_counter = 0;
int aev_event_counter_max = 0;
const char *aev_current_client = "Unknown";
#endif
typedef struct evblock_t
{
struct evblock_t *next;
aev_event_t events[EVENTS_PER_BLOCK];
} evblock_t;
static evblock_t *blocks = NULL;
int _aev_refill_pool(void)
{
/*
* We can't refill in real time context on some
* platforms - and it would be a bad idea anyway...
*/
log_printf(ELOG, "Audio event pool exhausted!\n");
return -1;
}
/*
* A job for a future butler thread. For now, we only use
* this during initialization.
*/
static int refill_pool(void)
{
int i;
evblock_t *eb = calloc(1, sizeof(evblock_t));
if(!eb)
return -1;
/* Add to block list */
eb->next = blocks;
blocks = eb;
/* Add the events to the pool */
for(i = 0; i < EVENTS_PER_BLOCK; ++i)
{
eb->events[i].type = AEV_ET_FREE;
aev_free(&eb->events[i]);
}
return 0;
}
/*----------------------------------------------------------
Event Input Port
----------------------------------------------------------*/
void aev_insert(aev_port_t *evp, aev_event_t *ev)
{
EVDBG(log_printf(D2LOG, "aev_insert:\n");)
if(!evp->first)
{
EVDBG(log_printf(D2LOG, " empty list\n");)
ev->next = NULL;
evp->first = evp->last = ev;
}
else if((Sint16)(evp->last->frame - ev->frame) <= 0)
{
EVDBG(log_printf(D2LOG, " last\n");)
ev->next = NULL;
evp->last->next = ev;
evp->last = ev;
}
else
{
aev_event_t *e_prev = NULL;
aev_event_t *e = evp->first;
EVDBG(log_printf(D2LOG, " scanning...\n");)
while((Sint16)(e->frame - ev->frame) <= 0)
{
EVDBG(log_printf(D2LOG, "e->frame = %d, ev->frame = %d\n", e->frame, ev->frame);)
e_prev = e;
e = e->next;
}
ev->next = e;
if(e_prev)
{
EVDBG(log_printf(D2LOG, " insert\n");)
e_prev->next = ev;
}
else
{
EVDBG(log_printf(D2LOG, " first\n");)
evp->first = ev;
}
}
#ifdef AEV_TRACKING
ev->port = evp;
#endif
}
/*----------------------------------------------------------
Open/Close
----------------------------------------------------------*/
int aev_open(int pool_size)
{
while(pool_size > 0)
{
if(refill_pool() < 0)
{
aev_close();
return -1; /* OOM! */
}
pool_size -= EVENTS_PER_BLOCK;
}
#if 0
{
aev_evport_t port;
int i;
aev_evport_init(&port, "testport");
aev_timer = 0;
aev_event_client(42);
for(i = 0; i < 5; ++i)
aev_put0(&port, 20+i*20, 42);
for(i = 0; i < 5; ++i)
aev_put0(&port, 100-i*20, 43);
i = 0;
aev_timer = 0;
while(i < 200)
{
unsigned int frames;
log_printf(D2LOG, "aev_timer = %d\n", aev_timer);
while(!(frames = aev_next(&port, 0)))
{
aev_event_t *ev = aev_read(&port);
log_printf(D2LOG, " event.frame = %d, type = %d\n",
ev->frame, ev->type);
aev_free(ev);
}
if(frames > 5)
frames = 5;
aev_timer += frames;
i += frames;
}
}
#endif
#ifdef AEV_TRACKING
aev_event_counter = 0;
aev_event_counter_max = 0;
#endif
return 0;
}
void aev_close(void)
{
log_printf(DLOG, "aev_close(): max events used: %d\n",
aev_event_counter_max);
#ifdef AEV_TRACKING
if(aev_event_counter)
{
evblock_t *eb = blocks;
log_printf(ELOG, "INTERNAL ERROR: Closing audio event system"
" system with %d events in use!"
" (Memory leaked.)\n",
aev_event_counter);
while(eb)
{
int i;
for(i = 0; i < EVENTS_PER_BLOCK; ++i)
{
if(eb->events[i].type == AEV_ET_FREE)
continue;
log_printf(ELOG, " Event %p, "
"sent from client \"%s\", "
"to port \"%s\".\n",
eb->events + i,
eb->events[i].client,
eb->events[i].port->name );
}
eb = eb->next;
}
}
else
#endif
while(blocks)
{
evblock_t *eb = blocks;
blocks = eb->next;
free(eb);
}
blocks = NULL;
aev_event_pool = NULL;
#ifdef AEV_TRACKING
aev_event_counter = 0;
aev_event_counter_max = 0;
#endif
}
+411
View File
@@ -0,0 +1,411 @@
/*(LGPL)
---------------------------------------------------------------------------
a_events.h - Internal Event System
---------------------------------------------------------------------------
* Copyright (C) 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_EVENTS_H_
#define _A_EVENTS_H_
#define AEV_TRACKING
#include "a_globals.h"
#include "a_types.h"
/*----------------------------------------------------------
Open/Close
----------------------------------------------------------*/
int aev_open(int pool_size);
void aev_close(void);
/*----------------------------------------------------------
The global event timestamp time base
----------------------------------------------------------*/
#define AEV_TIMESTAMP_MASK 0xffff
typedef Uint16 aev_timestamp_t;
extern aev_timestamp_t aev_timer;
static inline void aev_reset_timer(void)
{
aev_timer = 0;
}
static inline void aev_advance_timer(unsigned frames)
{
aev_timer += (aev_timestamp_t)frames;
}
/*----------------------------------------------------------
Event
----------------------------------------------------------*/
#define AEV_ET_FREE 0xff
#define AEV_ET_ALLOCATED 0xfe
struct aev_port_t;
typedef struct aev_event_t
{
struct aev_event_t *next;
aev_timestamp_t frame; /* When */
Uint8 type; /* What */
Uint8 index; /* Qualifier */
Sint32 arg1; /* Argument 1 */
Sint32 arg2; /* Argument 2 */
Sint32 arg3; /* Argument 3 */
#ifdef AEV_TRACKING
const char *client; /* Client name */
struct aev_port_t *port; /* Receiver port */
#endif
} aev_event_t;
/* Macro for interpreting event timestamps. */
#define AEV_TIME(frame, offset) \
((unsigned)(frame - aev_timer - offset) & \
AEV_TIMESTAMP_MASK)
/* The global event pool. */
extern aev_event_t *aev_event_pool;
#ifdef AEV_TRACKING
extern int aev_event_counter; //Current number of events in use
extern int aev_event_counter_max; //Peak use tracker
extern const char *aev_current_client; //Name of current client
#endif
#ifdef AEV_TRACKING
#define aev_client(x) aev_current_client = x;
#else
#define aev_client(x)
#endif
/* Refill event pool. */
int _aev_refill_pool(void);
/*
* Allocate an event.
*
* Will try to refill the event pool if required.
* Returns the event, or NULL, if the pool was empty
* and could not be refilled.
*/
static inline aev_event_t *aev_new(void)
{
aev_event_t *ev;
if(!aev_event_pool)
if(_aev_refill_pool() < 0)
return NULL;
ev = aev_event_pool;
aev_event_pool = ev->next;
#ifdef AEV_TRACKING
ev->type = AEV_ET_ALLOCATED;
ev->client = aev_current_client;
++aev_event_counter;
if(aev_event_counter > aev_event_counter_max)
aev_event_counter_max = aev_event_counter;
#endif
return ev;
}
/* Free an event. */
static inline void aev_free(aev_event_t *ev)
{
ev->next = aev_event_pool;
aev_event_pool = ev;
#ifdef AEV_TRACKING
ev->type = AEV_ET_FREE;
--aev_event_counter;
#endif
}
/*----------------------------------------------------------
Event Input Port
----------------------------------------------------------*/
typedef struct aev_port_t
{
/*
* Note that when 'first' is NULL, 'last' is undefined!
* (If there's no first event, there can't be a last
* event either - makes sense, eh? :-)
*/
aev_event_t *first;
aev_event_t *last;
const char *name;
} aev_port_t;
/*
* Initialize a port.
*****************************************
* DO NOT FORGET TO USE THIS!
*****************************************
*/
static inline void aev_port_init(aev_port_t *evp, const char *name)
{
evp->first = evp->last = NULL;
evp->name = name;
}
/*
* Write one event to a port. The event will belong to the
* owner of the port, who is responsible for freeing or
* reusing the event after reading it.
*
* NOTE: Events must be queued in increasing timestamp order!
*/
static inline void aev_write(aev_port_t *evp, aev_event_t *ev)
{
ev->next = NULL;
if(!evp->first)
evp->first = evp->last = ev; /* Oops, empty port! */
else
{
evp->last->next = ev;
evp->last = ev;
}
#ifdef AEV_TRACKING
ev->port = evp;
#endif
}
/*
* Insert an event at the right position in the queue of
* the specified port. The event will be placed *after*
* any other events with the same timestamp.
*
* The event will belong to the owner of the port, who is
* responsible for freeing or reusing the event after
* reading it.
*/
void aev_insert(aev_port_t *evp, aev_event_t *ev);
/*
* Read one event from a port. The event will be removed
* from the port, and is effectively detached from the
* event system until you reuse it by sending it somewhere,
* or free it.
*
* WARNING: Events *MUST* be either freed or reused!
*
* Event leaks will result in severe timing issues, as the
* pool will need to be refilled regularly, which is *not*
* a real time compatible operation.
*
* Returns the event, or NULL, if no event was queued.
*/
static inline aev_event_t *aev_read(aev_port_t *evp)
{
aev_event_t *ev = evp->first;
if(!ev)
return NULL;
evp->first = ev->next;
return ev;
}
/*
* Return number of frames from the start of the current
* buffer (current value of 'aev_timer') + 'offset' to
* the next event queued on this port.
*
* If there's no event enqueued, the maximum possible
* number of frames is returned.
*/
static inline unsigned aev_next(aev_port_t *evp, unsigned offset)
{
aev_event_t *ev = evp->first;
if(ev)
return AEV_TIME(ev->frame, offset);
else
return AEV_TIMESTAMP_MASK;
}
/*
* Remove and free all events on the specified port.
*/
static inline void aev_flush(aev_port_t *evp)
{
#ifdef AEV_TRACKING
while(1)
{
aev_event_t *ev = evp->first;
if(!ev)
return;
evp->first = ev->next;
aev_free(ev);
}
#else
/*
* Silly me! I've pulled this simple trick
* before, but just forgot somehow... :-)
*/
if(!evp->first)
return;
evp->last->next = aev_event_pool;
aev_event_pool = evp->first;
evp->first = NULL;
#endif
}
/*
* Short and handy "send" functions.
*
* Allocates an event, fills it in, and sends it to the
* specified port. The address of the event used is
* returned - which means that you can detect an error
* by checking that the return is not NULL.
*/
static inline aev_event_t *aev_send0(aev_port_t *evp,
aev_timestamp_t delay, Uint8 type)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = type;
ev->frame = delay + aev_timer;
aev_write(evp, ev);
return ev;
}
static inline aev_event_t *aev_send1(aev_port_t *evp,
aev_timestamp_t delay, Uint8 type, Sint32 arg1)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = type;
ev->frame = delay + aev_timer;
ev->arg1 = arg1;
aev_write(evp, ev);
return ev;
}
static inline aev_event_t *aev_send2(aev_port_t *evp,
aev_timestamp_t delay, Uint8 type, Sint32 arg1, Sint32 arg2)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = type;
ev->frame = delay + aev_timer;
ev->arg1 = arg1;
ev->arg2 = arg2;
aev_write(evp, ev);
return ev;
}
static inline aev_event_t *aev_sendi0(aev_port_t *evp,
aev_timestamp_t delay, Uint8 type, Uint8 index)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = type;
ev->index = index;
ev->frame = delay + aev_timer;
aev_write(evp, ev);
return ev;
}
static inline aev_event_t *aev_sendi1(aev_port_t *evp,
aev_timestamp_t delay, Uint8 type,
Uint8 index, Sint32 arg1)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = type;
ev->index = index;
ev->frame = delay + aev_timer;
ev->arg1 = arg1;
aev_write(evp, ev);
return ev;
}
static inline aev_event_t *aev_sendi2(aev_port_t *evp,
aev_timestamp_t delay, Uint8 type,
Uint8 index, Sint32 arg1, Sint32 arg2)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = type;
ev->index = index;
ev->frame = delay + aev_timer;
ev->arg1 = arg1;
ev->arg2 = arg2;
aev_write(evp, ev);
return ev;
}
/*
* Like the above, but using aev_insert().
*/
static inline aev_event_t *aev_insert0(aev_port_t *evp,
aev_timestamp_t delay, Uint8 type)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = type;
ev->frame = delay + aev_timer;
aev_insert(evp, ev);
return ev;
}
static inline aev_event_t *aev_insert1(aev_port_t *evp,
aev_timestamp_t delay, Uint8 type, Sint32 arg1)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = type;
ev->frame = delay + aev_timer;
ev->arg1 = arg1;
aev_insert(evp, ev);
return ev;
}
static inline aev_event_t *aev_insert2(aev_port_t *evp,
aev_timestamp_t delay, Uint8 type,
Sint32 arg1, Sint32 arg2)
{
aev_event_t *ev = aev_new();
if(!ev)
return NULL;
ev->type = type;
ev->frame = delay + aev_timer;
ev->arg1 = arg1;
ev->arg2 = arg2;
aev_insert(evp, ev);
return ev;
}
#endif /*_A_EVENTS_H_*/
+353
View File
@@ -0,0 +1,353 @@
/*(LGPL)
---------------------------------------------------------------------------
a_filters.c - Various Audio Signal Filters
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include "a_math.h"
#include "a_tools.h"
#include "a_filters.h"
/*--------------------------------------------------------------------
f6_t - Simple mono 6 dB/oct state variable filter
--------------------------------------------------------------------*/
void f6_process_lp(f6_t *fil, int *buf, unsigned frames)
{
int f = fil->f;
int d = fil->d;
unsigned s;
for(s = 0; s < frames; ++s)
{
d += (buf[s] - (d>>12)) * f;
buf[s] = d>>12;
}
fil->d = d;
}
void f6_process_hp(f6_t *fil, int *buf, unsigned frames)
{
int f = fil->f;
int d = fil->d;
unsigned s;
for(s = 0; s < frames; ++s)
{
d += (buf[s] - (d>>12)) * f;
buf[s] -= d>>12;
}
fil->d = d;
}
void f6_set_f(f6_t *fil, int f)
{
fil->f = (f<<13) / fil->rate;
if(fil->f > 1<<13)
fil->f = 1<<13;
}
void f6_init(f6_t *fil, int fs)
{
fil->rate = fs;
fil->d = 0;
f6_set_f(fil, 400);
}
/*--------------------------------------------------------------------
f6s_t - Simple stereo 6 dB/oct state variable filter
--------------------------------------------------------------------*/
void f6s_process_lp(f6s_t *fil, int *buf, unsigned frames)
{
int f = fil->f;
int dl = fil->dl;
int dr = fil->dr;
unsigned s;
frames <<= 1;
for(s = 0; s < frames; s += 2)
{
dl += (buf[s] - (dl>>12)) * f;
dr += (buf[s+1] - (dr>>12)) * f;
buf[s] = dl>>12;
buf[s+1] = dr>>12;
}
fil->dl = dl;
fil->dr = dr;
}
void f6s_process_hp(f6s_t *fil, int *buf, unsigned frames)
{
int f = fil->f;
int dl = fil->dl;
int dr = fil->dr;
unsigned s;
frames <<= 1;
for(s = 0; s < frames; s += 2)
{
dl += (buf[s] - (dl>>12)) * f;
dr += (buf[s+1] - (dr>>12)) * f;
buf[s] -= dl>>12;
buf[s+1] -= dr>>12;
}
fil->dl = dl;
fil->dr = dr;
}
void f6s_set_f(f6s_t *fil, int f)
{
fil->f = (f<<13) / fil->rate;
if(fil->f > 1<<13)
fil->f = 1<<13;
}
void f6s_init(f6s_t *fil, int fs)
{
fil->rate = fs;
fil->dl = fil->dr = 0;
f6s_set_f(fil, 400);
}
/*--------------------------------------------------------------------
dcf6s_t - Fast stereo 6 dB/oct HPF for DC suppression
--------------------------------------------------------------------*/
void dcf6s_process(dcf6s_t *fil, int *buf, unsigned frames)
{
int f = fil->f;
int dl = fil->dl;
int dr = fil->dr;
unsigned s;
frames <<= 1;
for(s = 0; s < frames; s += 2)
{
dl += (buf[s] - (dl>>12)) >> f;
dr += (buf[s+1] - (dr>>12)) >> f;
buf[s] -= dl>>12;
buf[s+1] -= dr>>12;
}
fil->dl = dl;
fil->dr = dr;
}
int dcf6s_silent(dcf6s_t *fil)
{
if(labs(fil->dl) > 4096)
return 0;
if(labs(fil->dr) > 4096)
return 0;
return 1;
}
void dcf6s_set_f(dcf6s_t *fil, int f)
{
fil->f = int2shift(fil->rate / f) - 13;
if(fil->f < 0)
fil->f = 0;
}
void dcf6s_init(dcf6s_t *fil, int fs)
{
fil->rate = fs;
fil->dl = fil->dr = 0;
dcf6s_set_f(fil, 10);
}
/*--------------------------------------------------------------------
resof12_t - Resonant mono 12 dB/oct state variable filter
--------------------------------------------------------------------*/
/* In-place LP */
void resof12_process_lp(resof12_t *rf, int *buf, unsigned frames)
{
int d1 = rf->d1;
int d2 = rf->d2;
int f = rf->f;
int q = rf->q;
int l, b, h;
unsigned s;
for(s = 0; s < frames; ++s)
{
l = d2 + (f*d1 >> 8);
h = (buf[s] << 4) - l - (q*d1 >> 8);
b = (f*h >> 8) + d1;
buf[s] = l >> 4;
d1 = b;
d2 = l;
}
rf->d1 = d1;
rf->d2 = d2;
}
/* In-place HP */
void resof12_process_hp(resof12_t *rf, int *buf, unsigned frames)
{
int d1 = rf->d1;
int d2 = rf->d2;
int f = rf->f;
int q = rf->q;
int l, b, h;
unsigned s;
for(s = 0; s < frames; ++s)
{
l = d2 + (f*d1 >> 8);
h = (buf[s] << 4) - l - (q*d1 >> 8);
b = (f*h >> 8) + d1;
buf[s] = h >> 4;
d1 = b;
d2 = l;
}
rf->d1 = d1;
rf->d2 = d2;
}
/* In-place BP */
void resof12_process_bp(resof12_t *rf, int *buf, unsigned frames)
{
int d1 = rf->d1;
int d2 = rf->d2;
int f = rf->f;
int q = rf->q;
int l, b, h;
unsigned s;
for(s = 0; s < frames; ++s)
{
l = d2 + (f*d1 >> 8);
h = (buf[s] << 4) - l - (q*d1 >> 8);
b = (f*h >> 8) + d1;
buf[s] = b >> 4;
d1 = b;
d2 = l;
}
rf->d1 = d1;
rf->d2 = d2;
}
/*
* LP filter the 'buf' buffer, while separating off
* the HP part into buffer 'out'.
*/
void resof12_process_split(resof12_t *rf, int *buf, int *out, unsigned frames)
{
int d1 = rf->d1;
int d2 = rf->d2;
int f = rf->f;
int q = rf->q;
int l, b, h;
unsigned s;
for(s = 0; s < frames; ++s)
{
l = d2 + (f*d1 >> 8);
h = (buf[s] << 4) - l - (q*d1 >> 8);
b = (f*h >> 8) + d1;
buf[s] = l >> 4;
out[s] = h >> 4;
d1 = b;
d2 = l;
}
rf->d1 = d1;
rf->d2 = d2;
}
void resof12_set_f(resof12_t *rf, int f)
{
rf->f = (int)(512.0 * sin(M_PI * (float)f / rf->rate));
if(rf->f > 256)
rf->f = 256;
}
void resof12_set_q(resof12_t *rf, int q)
{
if(q < 1)
rf->q = 256;
else
rf->q = 256 / q;
}
void resof12_init(resof12_t *rf, int fs)
{
rf->rate = fs;
rf->d1 = rf->d2 = 0;
resof12_set_f(rf, 400);
resof12_set_q(rf, 10);
}
/*--------------------------------------------------------------------
resof12s_t - Resonant stereo 12 dB/oct state variable filter
--------------------------------------------------------------------*/
/* In-place LP */
void resof12s_process_lp(resof12s_t *rf, int *buf, unsigned frames)
{
int d1l = rf->d1l;
int d1r = rf->d1r;
int d2l = rf->d2l;
int d2r = rf->d2r;
int f = rf->f;
int q = rf->q;
int ll, bl, hl;
int lr, br, hr;
unsigned s;
frames <<= 1;
for(s = 0; s < frames; s += 2)
{
ll = d2l + (f*d1l >> 8);
lr = d2r + (f*d1r >> 8);
hl = (buf[s] << 4) - ll - (q*d1l >> 8);
hr = (buf[s+1] << 4) - lr - (q*d1r >> 8);
bl = (f*hl >> 8) + d1l;
br = (f*hr >> 8) + d1r;
buf[s] = ll >> 4;
buf[s+1] = lr >> 4;
d1l = bl;
d1r = br;
d2l = ll;
d2r = lr;
}
rf->d1l = d1l;
rf->d1r = d1r;
rf->d2l = d2l;
rf->d2r = d2r;
}
void resof12s_set_f(resof12s_t *rf, int f)
{
rf->f = (int)(512.0 * sin(M_PI * (float)f / rf->rate));
if(rf->f > 256)
rf->f = 256;
}
void resof12s_set_q(resof12s_t *rf, int q)
{
if(q < 1)
rf->q = 256;
else
rf->q = 256 / q;
}
void resof12s_init(resof12s_t *rf, int fs)
{
rf->rate = fs;
rf->d1l = rf->d2l = 0;
rf->d1r = rf->d2r = 0;
resof12s_set_f(rf, 400);
resof12s_set_q(rf, 10);
}
+110
View File
@@ -0,0 +1,110 @@
/*(LGPL)
---------------------------------------------------------------------------
a_filters.h - Various Audio Signal Filters
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_FILTERS_H_
#define _A_FILTERS_H_
/*--------------------------------------------------------------------
f6_t - Simple mono 6 dB/oct state variable filter
--------------------------------------------------------------------*/
typedef struct f6_t
{
int rate;
int d;
int f;
} f6_t;
void f6_init(f6_t *fil, int fs);
void f6_set_f(f6_t *fil, int f);
void f6_process_lp(f6_t *fil, int *buf, unsigned frames);
void f6_process_hp(f6_t *fil, int *buf, unsigned frames);
/*--------------------------------------------------------------------
f6s_t - Simple stereo 6 dB/oct state variable filter
--------------------------------------------------------------------*/
typedef struct f6s_t
{
int rate;
int f;
int dl, dr;
} f6s_t;
void f6s_init(f6s_t *fil, int fs);
void f6s_set_f(f6s_t *fil, int f);
void f6s_process_lp(f6s_t *fil, int *buf, unsigned frames);
void f6s_process_hp(f6s_t *fil, int *buf, unsigned frames);
/*--------------------------------------------------------------------
dcf6s_t - Fast stereo 6 dB/oct HPF for DC suppression
--------------------------------------------------------------------*/
typedef struct dcf6s_t
{
int rate;
int f;
int dl, dr;
} dcf6s_t;
void dcf6s_init(dcf6s_t *fil, int fs);
void dcf6s_set_f(dcf6s_t *fil, int f);
void dcf6s_process(dcf6s_t *fil, int *buf, unsigned frames);
int dcf6s_silent(dcf6s_t *fil);
/*--------------------------------------------------------------------
resof12_t - Resonant mono 12 dB/oct state variable filter
--------------------------------------------------------------------*/
typedef struct resof12_t
{
int rate;
int d1, d2;
int f;
int q;
} resof12_t;
void resof12_init(resof12_t *rf, int fs);
void resof12_set_f(resof12_t *rf, int f);
void resof12_set_q(resof12_t *rf, int q);
void resof12_process_lp(resof12_t *rf, int *buf, unsigned frames); /*In-place LP*/
void resof12_process_hp(resof12_t *rf, int *buf, unsigned frames); /*In-place HP*/
void resof12_process_bp(resof12_t *rf, int *buf, unsigned frames); /*In-place BP*/
/*
* LP filter the 'buf' buffer, while separating off
* the HP part into buffer 'out'.
*/
void resof12_process_split(resof12_t *rf, int *buf, int *out, unsigned frames);
/*--------------------------------------------------------------------
resof12s_t - Resonant stereo 12 dB/oct state variable filter
--------------------------------------------------------------------*/
typedef struct resof12s_t
{
int rate;
int d1l, d2l;
int d1r, d2r;
int f;
int q;
} resof12s_t;
void resof12s_init(resof12s_t *rf, int fs);
void resof12s_set_f(resof12s_t *rf, int f);
void resof12s_set_q(resof12s_t *rf, int q);
void resof12s_process_lp(resof12s_t *rf, int *buf, unsigned frames); /*In-place LP*/
#endif /*_A_FILTERS_H_*/
+113
View File
@@ -0,0 +1,113 @@
/*(GPL)
---------------------------------------------------------------------------
audio_p.c - Private global audio engine stuff
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2.1 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "a_globals.h"
/* For debug oscilloscope */
#ifdef DEBUG
int *oscbufl = NULL;
int *oscbufr = NULL;
int oscframes = 0;
int oscpos = 0;
#endif
int _audio_running = 0;
/* Global engine settings: defaults */
struct settings_t a_settings = {
44100, /* samplerate */
256, /* output_buffersize */
32, /* buffersize */
AQ_HIGH /* quality */
};
/*
* Default control values
*/
accbank_t a_channel_def_ctl = {
0, /* ACC_GROUP */
1000, /* ACC_PRIORITY */
0, /* ACC_PATCH */
0, /* ACC_PRIM_BUS */
-1, /* ACC_SEND_BUS [Off] */
0, /* ACC_PAN [0.0] */
60<<16, /* ACC_PITCH [1.0 * sample_rate] */
65536, /* ACC_VOLUME [1.0] */
0, /* ACC_SEND [0.0] */
0, /* ACC_MOD1 [0.0] */
0, /* ACC_MOD2 [0.0] */
0, /* ACC_MOD3 [0.0] */
0, /* ACC_X [0.0] */
0, /* ACC_Y [0.0] */
0 /* ACC_Z [0.0] */
};
accbank_t a_group_def_ctl = {
0, /* ACC_GROUP */
1000, /* ACC_PRIORITY */
0, /* ACC_PATCH */
0, /* ACC_PRIM_BUS */
-1, /* ACC_SEND_BUS [Off] */
0, /* ACC_PAN [0.0] */
60<<16, /* ACC_PITCH [1.0 * sample_rate] */
65536, /* ACC_VOLUME [1.0] */
65536, /* ACC_SEND [1.0] */
0, /* ACC_MOD1 [0.0] */
0, /* ACC_MOD2 [0.0] */
0, /* ACC_MOD3 [0.0] */
0, /* ACC_X [0.0] */
0, /* ACC_Y [0.0] */
0 /* ACC_Z [0.0] */
};
abcbank_t a_bus_def_ctl = {
0, /* ABC_FX_TYPE[None - allpass] */
0, /* ABC_FX_PARAM_1 */
0, /* ABC_FX_PARAM_2 */
0, /* ABC_FX_PARAM_3 */
0, /* ABC_FX_PARAM_4 */
0, /* ABC_FX_PARAM_5 */
0, /* ABC_FX_PARAM_6 */
0, /* ABC_SEND_MASTER */
0, /* ABC_SEND_BUS_0 */
0, /* ABC_SEND_BUS_1 */
0, /* ABC_SEND_BUS_2 */
0, /* ABC_SEND_BUS_3 */
0, /* ABC_SEND_BUS_4 */
0, /* ABC_SEND_BUS_5 */
0, /* ABC_SEND_BUS_6 */
0 /* ABC_SEND_BUS_7 */
};
+136
View File
@@ -0,0 +1,136 @@
/*(GPL)
---------------------------------------------------------------------------
a_globals.h - Private global audio engine stuff
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2.1 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_GLOBALS_H_
#define _A_GLOBALS_H_
#ifndef DBG
# define DBG(x) x
# define DBG2(x)
# define DBG3(x)
#endif
#include "config.h"
#include "audio.h"
#include "a_pitch.h"
#include "a_commands.h"
#ifdef DEBUG
#define PROFILE_AUDIO
#endif
#define AUDIO_USE_VU
#undef AUDIO_LOCKING
/*
* Various private types, defaults and limits...
*/
/* Voices; virtually no per-unit overhead. */
#define AUDIO_MAX_VOICES 32
/* Busses; some overhead, but only for busses that are actually used. */
#define AUDIO_MAX_BUSSES 8
/* Bus inserts; minimal per-unit overhead. */
#define AUDIO_MAX_INSERTS 2
#define DEFAULT_VOL 65536
#define DEFAULT_RVB 65536
#define DEFAULT_LIM_RELEASE (65536 * 1 / 44100)
#define DEFAULT_LIM_THRESHOLD 32768
#define SDL_FRAGMENTS 2
#define OSS_FRAGMENTS 3
#define FREQ_BITS 20
#define RAMP_BITS 15
#define VOL_BITS 15
/*
* Restriction caused by the limited fixed point
* range in the resampling voice mixers.
*/
#define MAX_FRAGMENT_SPAN (1<<(32 - FREQ_BITS))
/*
* Maximum size of internal mixing buffers, in frames.
*
* This is a cache thrashing limiter! Tune
* to best trade-off between cache footprint
* and looping overhead.
*/
#define MAX_BUFFER_SIZE 128
/*
* Minimum *output* buffer size, in frames.
* This effectively sets a minimum latency limit.
*/
#define MIN_BUFFER_SIZE 16
/*
* Safety limits to prevent locking up the engine with silly input.
*/
#define MAX_STEP ((AUDIO_MAX_MIX_RATE / AUDIO_MIN_OUTPUT_RATE) << FREQ_BITS)
#define MIN_LOOP ((AUDIO_MAX_MIX_RATE / AUDIO_MIN_OUTPUT_RATE) >> 1)
/* Global engine settings */
struct settings_t
{
int samplerate;
unsigned output_buffersize;
unsigned buffersize;
audio_quality_t quality;
};
extern struct settings_t a_settings;
/* Defaults... */
extern accbank_t a_channel_def_ctl;
extern accbank_t a_group_def_ctl;
extern abcbank_t a_bus_def_ctl;
/* For debug oscilloscope */
#ifdef DEBUG
# define OSCFRAMES 240
# include "a_limiter.h"
extern int *oscbufl;
extern int *oscbufr;
extern int oscframes;
extern limiter_t limiter;
extern int oscpos;
void audio_print_info(void);
#endif
/* For audio engine profiling */
#ifdef PROFILE_AUDIO
# define AUDIO_CPU_FUNCTIONS 10
extern int audio_cpu_ticks;
extern float audio_cpu_total;
extern float audio_cpu_function[AUDIO_CPU_FUNCTIONS];
extern char audio_cpu_funcname[AUDIO_CPU_FUNCTIONS][20];
#endif
extern int _audio_running;
#endif /*_A_GLOBALS_H_*/
+140
View File
@@ -0,0 +1,140 @@
/*(LGPL)
---------------------------------------------------------------------------
a_limiter.h - Simple limiter
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include "a_limiter.h"
#include "a_types.h"
int lim_open(limiter_t *lim, int samplerate)
{
lim->samplerate = samplerate;
lim_control(lim, LIM_THRESHOLD, 32768);
lim_control(lim, LIM_RELEASE, 300000);
lim->peak = (unsigned)(32768<<8);
lim->attenuation = 0;
return 0;
}
void lim_close(limiter_t *lim)
{
/* Nothing to destruct */
}
void lim_control(limiter_t *lim, int param, int value)
{
switch(param)
{
case LIM_THRESHOLD:
lim->threshold = (unsigned)(value << 8);
if(lim->threshold < 256)
lim->threshold = 256;
break;
case LIM_RELEASE:
if(lim->samplerate < 1)
lim->samplerate = 44100;
lim->release = (value << 8) / lim->samplerate;
break;
}
}
void lims_process(limiter_t *lim, int *in, int *out, unsigned frames)
{
unsigned i;
frames <<= 1;
for(i = 0; i < frames; i += 2)
{
int gain;
int lp = abs(in[i]) << 8;
int rp = abs(in[i+1]) << 8;
unsigned maxp = (unsigned)(lp > rp ? lp : rp);
if(maxp > lim->peak)
{
lim->peak = maxp;
gain = (32767<<16) / ((lim->peak+255)>>8);
}
else
{
gain = (32767<<16) / ((lim->peak+255)>>8);
lim->peak -= lim->release;
if(lim->peak < lim->threshold)
lim->peak = lim->threshold;
}
#ifdef A_USE_INT64
out[i] = (Sint64)in[i] * (Sint64)gain >> 16;
out[i+1] = (Sint64)in[i+1] * (Sint64)gain >> 16;
#else
if(gain < 8192)
{
out[i] = in[i] * gain >> 16;
out[i+1] = in[i+1] * gain >> 16;
}
else
{
out[i] = in[i] * (gain>>4) >> 12;
out[i+1] = in[i+1] * (gain>>4) >> 12;
}
#endif
}
lim->attenuation = (lim->peak - lim->threshold) >> 8;
}
void limss_process(limiter_t *lim, int *in, int *out, unsigned frames)
{
unsigned i;
frames <<= 1;
for(i = 0; i < frames; i += 2)
{
int gain;
int lp = abs(in[i]);
int rp = abs(in[i+1]);
unsigned maxp = (unsigned)(lp > rp ? lp : rp);
unsigned p = maxp << 8;
p += (maxp - abs(lp - rp)) << 7;
if(p > lim->peak)
{
lim->peak = p;
gain = (32767<<16) / ((lim->peak+255)>>8);
}
else
{
gain = (32767<<16) / ((lim->peak+255)>>8);
lim->peak -= lim->release;
if(lim->peak < lim->threshold)
lim->peak = lim->threshold;
}
#ifdef A_USE_INT64
out[i] = (Sint64)in[i] * (Sint64)gain >> 16;
out[i+1] = (Sint64)in[i+1] * (Sint64)gain >> 16;
#else
if(gain < 8192)
{
out[i] = in[i] * gain >> 16;
out[i+1] = in[i+1] * gain >> 16;
}
else
{
out[i] = in[i] * (gain>>4) >> 12;
out[i+1] = in[i+1] * (gain>>4) >> 12;
}
#endif
}
lim->attenuation = (lim->peak - lim->threshold) >> 8;
}
+70
View File
@@ -0,0 +1,70 @@
/*(LGPL)
---------------------------------------------------------------------------
a_limiter.h - Simple limiter
---------------------------------------------------------------------------
* Copyright (C) 2001. 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_LIMITER_H_
#define _A_LIMITER_H_
typedef struct limiter_t
{
int samplerate;
unsigned threshold; /* Reaction threshold */
int release; /* Release "speed" */
unsigned peak; /* Filtered peak value */
unsigned attenuation; /* Current output attenuation */
} limiter_t;
enum lim_params_t
{
LIM_THRESHOLD = 0, /* 16 bit sample units */
LIM_RELEASE /* Units per second */
};
int lim_open(limiter_t *lim, int samplerate);
void lim_close(limiter_t *lim);
void lim_control(limiter_t *lim, int param, int value);
/*
* Limiter stereo process function.
* Supports in-place processing.
*/
void lims_process(limiter_t *lim, int *in, int *out, unsigned frames);
/*
* Smart Stereo version.
*
* This algorithm takes both channels in account in a way
* that reduces the effect of the center appearing to have
* more power after compression of signals with unbalanced
* stereo images.
*
* A dead center signal can only get 3 dB louder than the
* same signal in one channel only, as opposed to the
* normal 6 dB of a limiter that only looks at max(L, R).
*
* Meanwhile, with "normal" material (ie most power
* relatively centered), this limiter gets an extra 3 dB
* compared to a limiter that checks (L+R).
*/
void limss_process(limiter_t *lim, int *in, int *out, unsigned frames);
#endif /*_A_LIMITER_H_*/
+32
View File
@@ -0,0 +1,32 @@
/*(LGPL)
---------------------------------------------------------------------------
a_math.h - Currently just to make sure we have M_PI...
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_MATH_H_
#define _A_MATH_H_
#include <math.h>
#ifndef M_PI
# define M_PI 3.14159265358979323846 /* pi */
#endif
#endif /*_A_MATH_H_*/
+267
View File
@@ -0,0 +1,267 @@
/*(LGPL)
---------------------------------------------------------------------------
a_midi.c - midi_socket_t MIDI Input Driver for OSS or ALSA
---------------------------------------------------------------------------
* Copyright (C) 2001, 2003, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* NOTE: This code will build for ALSA *or* OSS, in that order of preference.
*/
#include "config.h"
#include <stdio.h>
#include <string.h>
#include "kobolog.h"
#include "a_midi.h"
#ifdef HAVE_ALSA
# include <sys/asoundlib.h>
# define USE_MIDI
#elif defined(HAVE_OSS)
# include <fcntl.h>
# include <unistd.h>
# define USE_MIDI
#else
# undef USE_MIDI
#endif
#define MDBG(x)
#define MDBG2(x)
#ifdef USE_MIDI
/* MIDI open/close */
#ifdef HAVE_ALSA
static snd_rawmidi_t *m_in_fd = NULL;
static snd_rawmidi_t *m_out_fd = NULL;
#elif defined(HAVE_OSS)
static int m_fd = 0;
#endif
/* MIDI I/O */
#ifdef HAVE_ALSA
# define midi_read(b,s) snd_rawmidi_read(m_in_fd, b, s)
# define midi_write(b,s) snd_rawmidi_write(m_out_fd, b, s)
#elif defined(HAVE_OSS)
# define midi_read(b,s) read(m_fd, b, s)
# define midi_write(b,s) write(m_fd, b, s)
#endif
/*
* Basic real time MIDI parsing
*/
static midisock_t *ms;
static midisock_t *mon;
/* Global state stuff - *not* per channel! */
static int m_channel = 0; /* current channel */
static int m_datacount = 0; /* current data byte */
static char m_status = 0; /* current (last seen) status */
static char m_buf[3]; /* space for data bytes */
static int m_expect = -1; /* expected message data length */
static inline void decode_midi_message(void)
{
/* Seems like we've got a complete message! */
int val;
switch(m_status & 0xf0)
{
case 0x80: /* Note off */
MDBG2(mon->note_off(m_channel, m_buf[0], m_buf[1]);)
ms->note_off(m_channel, m_buf[0], m_buf[1]);
break;
case 0x90: /* Note on */
MDBG2(mon->note_on(m_channel, m_buf[0], m_buf[1]);)
ms->note_on(m_channel, m_buf[0], m_buf[1]);
break;
case 0xa0: /* Poly pressure */
MDBG2(mon->poly_pressure(m_channel, m_buf[0], m_buf[1]);)
ms->poly_pressure(m_channel, m_buf[0], m_buf[1]);
break;
case 0xb0: /* Control change */
MDBG2(mon->control_change(m_channel, m_buf[0], m_buf[1]);)
ms->control_change(m_channel, m_buf[0], m_buf[1]);
break;
case 0xc0: /* Program change */
MDBG2(mon->program_change(m_channel, m_buf[0]);)
ms->program_change(m_channel, m_buf[0]);
break;
case 0xd0: /* Channel pressure */
MDBG2(mon->channel_pressure(m_channel, m_buf[0]);)
ms->channel_pressure(m_channel, m_buf[0]);
break;
case 0xe0: /* Pitch bend */
val = ((m_buf[1] << 7) | m_buf[0]) - 8192;
MDBG2(mon->pitch_bend(m_channel, val);)
ms->pitch_bend(m_channel, val);
break;
case 0xf0: /* System message */
break;
}
m_datacount = 0;
}
static const int full_xtab[8] = {
2, /* 0x80/0: Note off, */
2, /* 0x90/1: Note on */
2, /* 0xa0/2: Poly pressure */
2, /* 0xb0/3: Control change */
1, /* 0xc0/4: Program change */
1, /* 0xd0/5: Channel pressure */
2, /* 0xe0/6: Pitch bend */
-1 /* 0xf0/7: System message (unsupported) */
};
static int xtab[8];
static inline void decode_midi_byte(unsigned char b)
{
if(b & 0x80) /* New status! */
{
MDBG(log_printf(D3LOG, "\n%2.2X", b & 0xff));
m_status = b;
m_datacount = 0;
m_channel = b & 0x0f;
m_expect = xtab[(m_status >> 4) & 0x07];
return;
}
MDBG(log_printf(D3LOG, " %2.2X", b));
if(m_expect < 0)
return; /* Ignore --> */
m_buf[m_datacount++] = b;
if(m_datacount >= m_expect)
decode_midi_message();
}
#endif /*USE_MIDI*/
int midi_open(midisock_t *output)
{
#ifdef USE_MIDI
if(!output)
output = &dummy_midisock;
ms = output;
/*
* Set up the message length table to ignore messages
* that aren't supported by the output midisock.
*/
memset(xtab, -1, sizeof(xtab));
if(output->note_off)
xtab[0] = full_xtab[0];
if(output->note_on)
xtab[1] = full_xtab[1];
if(output->poly_pressure)
xtab[2] = full_xtab[2];
if(output->control_change)
xtab[3] = full_xtab[3];
if(output->program_change)
xtab[4] = full_xtab[4];
if(output->channel_pressure)
xtab[5] = full_xtab[5];
if(output->pitch_bend)
xtab[6] = full_xtab[6];
mon = &monitor_midisock;
#ifdef HAVE_ALSA
if(snd_rawmidi_open(&m_in_fd, 0, 0, SND_RAWMIDI_OPEN_INPUT |
SND_RAWMIDI_OPEN_NONBLOCK))
{
log_printf(ELOG, "Failed to open MIDI input device!\n");
return -1;
}
# ifdef MIDI_THRU
if(snd_rawmidi_open(&m_out_fd, 0, 0, SND_RAWMIDI_OPEN_OUTPUT))
{
log_printf(ELOG, "Failed to open MIDI output device!\n");
return -2;
}
# endif
return 0;
#elif defined(HAVE_OSS)
{
# ifdef MIDI_THRU
int flags = O_RDWR | O_NONBLOCK;
# else
int flags = O_RDONLY | O_NONBLOCK;
# endif
m_fd = open("/dev/midi00", flags);
if(m_fd < 0)
{
log_printf(ELOG, "Failed to open MIDI device!\n");
return -1;
}
return 0;
}
#endif /*HAVE_OSS*/
#else /*USE_MIDI*/
log_printf(ELOG, "MIDI support not compiled in!\n");
return -3;
#endif /*USE_MIDI*/
}
void midi_close(void)
{
#ifdef USE_MIDI
#ifdef HAVE_ALSA
if(m_in_fd)
{
snd_rawmidi_input_flush(m_in_fd);
snd_rawmidi_close(m_in_fd);
}
if(m_out_fd)
{
snd_rawmidi_output_flush(m_out_fd);
snd_rawmidi_close(m_out_fd);
}
m_in_fd = m_out_fd = NULL;
#elif defined(HAVE_OSS)
if(m_fd)
close(m_fd);
m_fd = 0;
#endif
#endif /*USE_MIDI*/
}
void midi_process(void)
{
#ifdef USE_MIDI
int bytes;
static char buf[MIDIMAXBYTES];
if( (bytes = midi_read(buf, MIDIMAXBYTES)) > 0 )
{
char *bufp = buf;
#ifdef MIDI_THRU
midi_write(buf, bytes); /* MIDI Thru */
#endif
while(bytes--)
decode_midi_byte(*bufp++);
}
#endif /*USE_MIDI*/
}
+41
View File
@@ -0,0 +1,41 @@
/*(LGPL)
---------------------------------------------------------------------------
a_midi.h - midi_socket_t MIDI Input Driver for OSS or ALSA
---------------------------------------------------------------------------
* Copyright (C) 2001, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_MIDI_H_
#define _A_MIDI_H_
#include "a_midisock.h"
/*
* Define if you'd like to layer with
* an external synth or something...
*/
#undef MIDI_THRU
#define MIDIMAXBYTES 1024
int midi_open(midisock_t *output);
void midi_close(void);
void midi_process(void);
#endif /*_A_MIDI_H_*/
+351
View File
@@ -0,0 +1,351 @@
/*(LGPL)
---------------------------------------------------------------------------
a_midicon.c - Engine MIDI Control Implementation
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "kobolog.h"
#include "a_midicon.h"
#include "a_control.h"
#include "a_math.h"
#include "a_sequencer.h"
static int first_channel = 0;
#define MIDI_MAP_CH(x) ((x)+first_channel)
#define CH_MAP_MIDI(x) ((x)-first_channel)
static float fs = 44100;
/* Exponential table which maps [0, 127] ==> [0, 65535] */
static unsigned short explut[128];
typedef struct midi_channel_t
{
/* Volume & velocity */
int volume; /* Linear [0, 65535] */
int pan; /* Linear [-65536, 65535] */
int velocity[128]; /* For each key, linear [0, 65535] */
/* Send control */
int send; /* Linear [0, 65535+] */
/* Pitch */
int bend_depth;
int bend;
int f_pitch;
/* Key Management */
int last;
signed char next[128]; /* Index-linked list :-) */
signed char prev[128]; /* Index-linked list :-) */
/* Voice Management */
int poly;
int tag[128]; /* To keep track of individual notes */
/* Modulation */
unsigned mod;
/* Bus Control */
unsigned ifx_slot;
} midi_channel_t;
static midi_channel_t m[16];
static void __init(void)
{
int i, j;
for(i = 0; i < 16; ++i)
{
m[i].volume = 100*512;
for(j = 0; j < 128; ++j)
m[i].velocity[j] = 0;
m[i].bend_depth = 12;
m[i].bend = 0;
m[i].f_pitch = 0;
m[i].last = -1;
memset(m[i].next, -1, sizeof(m[i].next));
memset(m[i].prev, -1, sizeof(m[i].prev));
m[i].poly = 1;
memset(m[i].tag, -1, sizeof(m[i].tag));
m[i].mod = 0;
}
for(i = 0; i < 128; ++i)
{
float linear = (float)(i+1) / 128;
explut[i] = (unsigned short)((0.25*linear + 0.5*linear*linear +
0.25*linear*linear*linear) * 65535.0);
}
#if 0
for(i = 0; i < 128; ++i)
{
int j;
for(j=0; j < (explut[i]>>9); ++j)
log_printf(D3LOG, "#");
log_printf(D3LOG, "\n");
}
for(i = 0; i < 128; ++i)
log_printf(D3LOG, "%d ==> %f\n", i, (float)explut[i]/65536.0);
#endif
}
static inline void __poly(unsigned ch, unsigned on)
{
m[ch].poly = (int)on;
/*
FIXME: Maybe reimplement this somehow...?
FIXME: acc_set(MIDI_MAP_CH(ch), ACC_DETACH, on ? ADM_POLY : ADM_MONO);
*/
}
static inline void __press(unsigned ch, unsigned key)
{
m[ch].next[key] = -1;
m[ch].prev[key] = m[ch].last;
m[ch].next[m[ch].last] = (char)key;
m[ch].last = (char)key;
}
/*
* Returns the new last key (-1 if none),
* or -2 if there's no change.
*/
static inline int __release(unsigned ch, unsigned key)
{
if(m[ch].prev[key] != -1)
m[ch].next[m[ch].prev[key]] = m[ch].next[key];
if(m[ch].next[key] != -1)
{
m[ch].prev[m[ch].next[key]] = m[ch].prev[key];
m[ch].next[key] = m[ch].prev[key] = -1;
return -2;
}
else
{
m[ch].last = m[ch].prev[key];
m[ch].next[key] = m[ch].prev[key] = -1;
return m[ch].last;
}
}
/* Returns 1 if the specified key on the specified channel is down. */
static inline int __is_down(unsigned ch, int key)
{
return (m[ch].tag[key] != -1);
}
static void midicon_note_off(unsigned ch, unsigned pitch, unsigned vel)
{
__release(ch, pitch);
(void)ce_stop(channeltab + MIDI_MAP_CH(ch), 0,
(int)pitch, (int)explut[vel]);
}
static void midicon_note_on(unsigned ch, unsigned pitch, unsigned vel)
{
if(!vel)
{
midicon_note_off(ch, pitch, 64);
return;
}
m[ch].velocity[pitch] = (int)explut[vel];
(void)ce_start(channeltab + MIDI_MAP_CH(ch), 0, (int)pitch,
(int)pitch << 16, m[ch].velocity[pitch]);
__press(ch, pitch);
}
static void midicon_pitch_bend(unsigned ch, int bend)
{
m[ch].bend = bend << 3;
m[ch].bend *= m[ch].bend_depth;
(void)ce_control(channeltab + MIDI_MAP_CH(ch), 0,
-1, ACC_PITCH, m[ch].bend + (60<<16));
}
static void midicon_control_change(unsigned ch, unsigned ctrl, unsigned amt)
{
audio_channel_t *c = channeltab + MIDI_MAP_CH(ch);
switch (ctrl)
{
case 1:
m[ch].mod = amt;
break;
case 7:
m[ch].volume = (int)explut[amt];
(void)ce_control(c, 0, -1, ACC_VOLUME, m[ch].volume);
break;
case 10:
m[ch].pan = (amt << 10) - 65536;
(void)ce_control(c, 0, -1, ACC_PAN, m[ch].pan);
break;
/* Bus Control */
case 39: /* Select IFX Slot */
m[ch].ifx_slot = (unsigned)amt;
break;
case 40: /* IFX Type */
if(ch >= AUDIO_MAX_BUSSES)
break;
if(m[ch].ifx_slot > AUDIO_MAX_INSERTS-1)
break;
bus_ctl_set(ch, m[ch].ifx_slot, ABC_FX_TYPE, (int)amt);
break;
case 41: /* IFX P1 (Time 1) */
case 42: /* IFX P2 (Time 2) */
case 43: /* IFX P3 (Depth 1) */
case 44: /* IFX P4 (Depth 2)*/
case 45: /* IFX P5 (Rate) */
case 46: /* IFX P6 (Mode/Special) */
if(ch >= AUDIO_MAX_BUSSES)
break;
if(m[ch].ifx_slot > AUDIO_MAX_INSERTS-1)
break;
if(ctrl != 46)
amt = (unsigned)(explut[amt]) << 1;
bus_ctl_set(ch, m[ch].ifx_slot, ctrl - 41 + ABC_FX_PARAM_1,
(int)amt);
break;
case 47: /* IFX Wet -> master */
if(ch >= AUDIO_MAX_BUSSES)
break;
if(m[ch].ifx_slot > AUDIO_MAX_INSERTS-1)
break;
bus_ctl_set(ch, m[ch].ifx_slot, ABC_SEND_MASTER,
explut[amt] << 1);
break;
case 48: /* Sends to other busses */
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
if(ch >= AUDIO_MAX_BUSSES)
break;
if(m[ch].ifx_slot > AUDIO_MAX_INSERTS-1)
break;
bus_ctl_set(ch, m[ch].ifx_slot, ABC_SEND_BUS_0 + ctrl-48,
explut[amt] << 1);
break;
case 88: /* Primary output bus */
(void)ce_control(c, 0, -1, ACC_PRIM_BUS, amt-1);
break;
case 89: /* Send bus */
(void)ce_control(c, 0, -1, ACC_SEND_BUS, amt-1);
break;
case 91: /* Send Level ("Reverb") */
m[ch].send = (int)explut[amt] << 1;
(void)ce_control(c, 0, -1, ACC_SEND, m[ch].send);
break;
case 120: /* All Sound Off */
case 123: /* All Notes Off */
if(m[ch].poly)
{
int note = m[ch].last;
while(note >= 0)
{
int n = note;
note = m[ch].prev[note];
midicon_note_off(ch, (unsigned)n, 64);
}
}
else
midicon_note_off(ch, (unsigned)m[ch].last, 64);
break;
case 121: /* Reset All Controllers */
__poly(ch, 1);
m[ch].mod = 0;
m[ch].volume = 100*512;
m[ch].send = 0;
m[ch].pan = 0;
(void)ce_control(c, 0, -1, ACC_VOLUME, m[ch].volume);
(void)ce_control(c, 0, -1, ACC_SEND, m[ch].send);
(void)ce_control(c, 0, -1, ACC_PAN, m[ch].pan);
midicon_pitch_bend(ch, 0);
break;
case 126: /* Mono */
__poly(ch, 0);
break;
case 127: /* Poly */
__poly(ch, 1);
break;
default:
break;
}
}
static void midicon_program_change(unsigned ch, unsigned prog)
{
(void)ce_control(channeltab + MIDI_MAP_CH(ch), 0, -2, ACC_PATCH, (int)prog);
}
midisock_t midicon_midisock = {
midicon_note_off,
midicon_note_on,
NULL, /* poly_pressure */
midicon_control_change,
midicon_program_change,
NULL, /* channel_pressure */
midicon_pitch_bend
};
int midicon_open(float framerate, int first_ch)
{
unsigned i;
aev_client("midicon_open()");
fs = framerate;
first_channel = first_ch;
__init();
for(i = 0; i < 16; ++i)
__poly(i, 1);
return 0;
}
void midicon_close(void)
{
aev_client("midicon_close()");
}
void midicon_process(unsigned frames)
{
aev_client("midicon_process()");
/*
* This is where MIDI clock and stuff goes.
*/
}
+35
View File
@@ -0,0 +1,35 @@
/*(LGPL)
---------------------------------------------------------------------------
a_midicon.h - Engine MIDI Control Implementation
---------------------------------------------------------------------------
* Copyright (C) 2001. 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_MIDICON_H_
#define _A_MIDICON_H_
#include "a_midisock.h"
int midicon_open(float framerate, int first_ch);
void midicon_close(void);
void midicon_process(unsigned frames);
/* Send MIDI messages here. */
extern midisock_t midicon_midisock;
#endif /*_A_MIDICON_H_*/
+806
View File
@@ -0,0 +1,806 @@
/*(LGPL)
------------------------------------------------------------
a_midifile.c - MIDI file loader and player
------------------------------------------------------------
* Stolen from the AdPlug player, stripped down,
* translated into C, and adapted for the Audio Engine
* by David Olofson, 2002.
*
* Below is the original copyright. Of course, the LGPL
* license still applies.
------------------------------------------------------------
* Adplug - Replayer for many OPL2/OPL3 audio file formats.
* Copyright (C) 1999, 2000, 2001 Simon Peter, <dn.tlp@gmx.net>, et al.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* MIDI & MIDI-like file player - Last Update: 8/16/2000
* by Phil Hassey - www.imitationpickles.org
* philhassey@hotmail.com
*
* Can play the following
* .LAA - a raw save of a Lucas Arts Adlib music
* or
* a raw save of a LucasFilm Adlib music
* .MID - a "midi" save of a Lucas Arts Adlib music
* - or general MIDI files
* .CMF - Creative Music Format
* .SCI - the sierra "midi" format.
* Files must be in the form
* xxxNAME.sci
* So that the loader can load the right patch file:
* xxxPATCH.003 (patch.003 must be saved from the
* sierra resource from each game.)
*
* 6/2/2000: v1.0 relased by phil hassey
* Status: LAA is almost perfect
* - some volumes are a bit off (intrument too quiet)
* MID is fine (who wants to listen to MIDI vid adlib anyway)
* CMF is okay (still needs the adlib rythm mode implemented
* for real)
* 6/6/2000:
* Status: SCI: there are two SCI formats, orginal and advanced.
* original: (Found in SCI/EGA Sierra Adventures)
* played almost perfectly, I believe
* there is one mistake in the instrument
* loader that causes some sounds to
* not be quite right. Most sounds are fine.
* advanced: (Found in SCI/VGA Sierra Adventures)
* These are multi-track files. (Thus the
* player had to be modified to work with
* them.) This works fine.
* There are also multiple tunes in each file.
* I think some of them are supposed to be
* played at the same time, but I'm not sure
* when.
* 8/16/200:
* Status: LAA: now EGA and VGA lucas games work pretty well
*
* Other acknowledgements:
* Allegro - for the midi instruments and the midi volume table
* SCUMM Revisited - for getting the .LAA / .MIDs out of those
* LucasArts files.
* FreeSCI - for some information on the sci music files
* SD - the SCI Decoder (to get all .sci out of the Sierra files)
*/
/*
TODO:
* Number of subsongs and other stuff should be checked
in mf_open(), rather than in mp_rewind().
*/
#undef TESTING
#undef TESTING2
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include "kobolog.h"
#include "a_midifile.h"
#ifdef TESTING
#define midiprintf log_printf
#else
static inline void midiprintf(int level, const char *format, ...)
{
}
#endif
#ifdef TESTING2
#define midiprintf2 log_printf
#else
static inline void midiprintf2(int level, const char *format, ...)
{
}
#endif
static inline unsigned char datalook(midi_player_t *mp, unsigned pos)
{
if(pos < 0 || pos > mp->mf->flen)
return 0;
return mp->mf->data[pos];
}
static inline unsigned getnext(midi_player_t *mp, unsigned num)
{
unsigned v = 0;
unsigned i;
for(i = 0; i < num; i++)
{
v <<= 8;
v += datalook(mp, mp->pos);
mp->pos++;
}
return v;
}
static inline unsigned getval(midi_player_t *mp)
{
unsigned v = 0;
unsigned char b;
b = (unsigned char)getnext(mp, 1);
v = b & 0x7f;
while((b & 0x80) != 0)
{
b = (unsigned char)getnext(mp, 1);
v = (v << 7) + (b & 0x7F);
}
return v;
}
static inline void set_tempo(midi_player_t *mp, unsigned ppqn, unsigned usqtr)
{
mp->c.ppqn = ppqn;
mp->c.usqtr = usqtr;
mp->c.spulse = (float)usqtr / (float)ppqn / 1000000.0;
midiprintf(D2LOG, "ppqn = %u ", ppqn);
midiprintf(D2LOG, "µsqtr = %u ", usqtr);
midiprintf(D2LOG, "spulse = %f ", mp->c.spulse);
}
static int matchnext(midi_player_t *mp, const char *s, unsigned len)
{
while(len--)
if(getnext(mp, 1) != (unsigned)(((int)*s++) & 0xff))
return 0;
return 1;
}
static int readheader(midi_player_t *mp, const char *s)
{
if(!matchnext(mp, s, 4))
return -1;
return (int)getnext(mp, 4);
}
static int printnext(midi_player_t *mp, unsigned len)
{
#ifdef TESTING
midiprintf(D2LOG, "\"");
while(len--)
{
unsigned int ch = getnext(mp, 1);
if(ch < 32)
midiprintf(D2LOG, ".");
else
midiprintf(D2LOG, "%c", (char)ch);
}
midiprintf(D2LOG, "\"");
#else
while(len--)
getnext(mp, 1);
#endif
return 1;
}
/* FIXME: This won't work if there are signature changes... */
static void mp_print_time(midi_player_t *mp, unsigned pulses)
{
#ifdef TESTING
unsigned beatval, beat, tick, bar;
if(mp->c.timesig.value)
beatval = mp->c.ppqn * 4 / mp->c.timesig.value;
else
beatval = 120;
beat = pulses / beatval;
tick = pulses % beatval;
if(mp->c.timesig.beats)
{
bar = beat / mp->c.timesig.beats;
beat = beat % mp->c.timesig.beats;
}
else
{
bar = beat / 4;
beat = beat % 4;
}
midiprintf(D2LOG, "(%u:%u:%u)", bar+1, beat+1, tick);
#endif
}
static long filelength(FILE *f)
{
long buf, size;
buf = ftell(f);
if(buf < 0)
return buf;
size = fseek(f, 0, SEEK_END);
if(size < 0)
return size;
size = ftell(f);
if(size < 0)
return size;
fseek(f, buf, SEEK_SET);
return size;
}
midi_file_t *mf_open(const char *name)
{
FILE *f;
unsigned char s[6];
midi_file_t *mf = calloc(1, sizeof(midi_file_t));
if(!mf)
return NULL;
mf->title = mf->author = "<Unknown>";
mf->remarks = "<None>";
f = fopen(name, "rb");
if(!f)
{
free(mf);
return NULL;
}
if(fread(s, 6, 1, f) != 1)
{
fclose(f);
free(mf);
return NULL;
}
if(!memcmp(s, "MThd", 4))
mf->subsongs = 1;
else
{
mf->subsongs = 0;
fclose(f);
free(mf);
return NULL;
}
fseek(f, 0, SEEK_SET);
mf->flen = (unsigned)filelength(f);
mf->data = malloc(mf->flen);
if(!mf->data)
{
fclose(f);
free(mf);
return NULL;
}
if(fread(mf->data, mf->flen, 1, f) != 1)
{
fclose(f);
free(mf->data);
free(mf);
return NULL;
}
fclose(f);
return mf;
}
void mf_close(midi_file_t *mf)
{
if(!mf)
return;
free(mf->data);
free(mf);
}
int mp_select(midi_player_t *mp, midi_file_t *midifile)
{
mp_stop(mp);
mp->mf = midifile;
mp_rewind(mp, 0);
return 0;
}
static void mp_mark_loop(midi_player_t *mp)
{
int i;
for(i = 0; i < 16; i++)
if(mp->track[i].enabled)
mp->track[i].loop_start = mp->track[i].c;
mp->loop_start = mp->c;
mp_print_time(mp, mp->c.time);
midiprintf(D2LOG, ": mp_mark_loop\n");
}
static void mp_loop(midi_player_t *mp)
{
int i;
for(i = 0; i < 16; i++)
if(mp->track[i].enabled)
mp->track[i].c = mp->track[i].loop_start;
mp_print_time(mp, mp->c.time);
midiprintf(D2LOG, ": mp_loop\n");
mp->c = mp->loop_start;
}
static int mp_parse_command(midi_player_t *mp, unsigned len)
{
char buf[128];
unsigned i;
for(i = 0; i < len; i++)
{
if(i < 127)
buf[i] = (char)datalook(mp, mp->pos);
mp->pos++;
}
buf[len < 127 ? len : 127] = 0;
if('#' != buf[0])
{
midiprintf(D2LOG, "Marker: %s", buf);
return 0;
}
if(!memcmp(buf + 1, "LOOP", 4))
{
if(!memcmp(buf + 5, "_START", 6))
mp_mark_loop(mp);
else if(!memcmp(buf + 5, "_END", 4))
{
mp_loop(mp);
return 1;
}
}
return 0;
}
static void mp_read_time_sig(midi_player_t *mp)
{
mp_timesig_t *ts = &mp->c.timesig;
ts->beats = getnext(mp, 1);
ts->value = 1 << getnext(mp, 1);
ts->clock = getnext(mp, 1);
ts->qn = getnext(mp, 1);
midiprintf(D2LOG, "Time Sig: %d/%d;", ts->beats, ts->value);
midiprintf(D2LOG, " %d clock %d qn", ts->clock, ts->qn);
}
#define FOR_TRACKS_ON for(trk = 0; trk < 16; trk++) \
if(mp->track[trk].c.on)
int mp_update(midi_player_t *mp)
{
unsigned note, vel, ctrl, x, l;
unsigned i, j, c;
int trk, ret;
if(mp->doing == 1)
{
/* just get the first wait and ignore it :> */
midiprintf(D2LOG, "Initial wait: ");
FOR_TRACKS_ON
{
mp_track_t *mt = mp->track + trk;
mp->pos = mt->c.pos;
mt->c.iwait += getval(mp);
midiprintf(D2LOG, "%d:%u ", trk, mt->c.iwait);
mt->c.pos = mp->pos;
}
mp->doing = 0;
midiprintf(D2LOG, "\n\n");
}
do
{
ret = 0;
FOR_TRACKS_ON
{
mp_track_t *mt = mp->track + trk;
if(mt->c.iwait)
continue;
if(mt->c.pos >= mt->tend)
{
midiprintf(D2LOG, "(%d) End Of Track\n", trk);
mt->c.on = 0;
continue;
}
ret = 1;
midiprintf2(D2LOG, "<%u>: ", mt->c.pos);
mp->pos = mt->c.pos;
x = getnext(mp, 1);
/* This is for MIDI "running status" */
if(x < 0x80)
{
x = mt->c.pv;
mp->pos--;
}
mt->c.pv = (unsigned char)x;
c = x & 0x0f;
midiprintf2(D2LOG, "(%d)[%2X]", trk, x);
switch (x & 0xf0)
{
case 0x80: /* note off */
note = getnext(mp, 1) + (mp->pitch >> 16);
if(note > 127)
note = 127;
else if(note < 0)
note = 0;
vel = getnext(mp, 1);
if(mp->sock->note_off)
mp->sock->note_off(c, note, vel);
break;
case 0x90: /* note on */
/* doing=0; */
note = getnext(mp, 1) + (mp->pitch >> 16);
if(note > 127)
note = 127;
else if(note < 0)
note = 0;
vel = getnext(mp, 1);
if(mp->sock->note_on)
mp->sock->note_on(c, note, vel);
break;
case 0xa0: /* key aftertouch */
note = getnext(mp, 1) + (mp->pitch >> 16);
if(note > 127)
note = 127;
else if(note < 0)
note = 0;
vel = getnext(mp, 1);
if(mp->sock->poly_pressure)
mp->sock->poly_pressure(c, note,
vel);
break;
case 0xb0: /* control change */
ctrl = getnext(mp, 1);
vel = getnext(mp, 1);
if(mp->sock->control_change)
mp->sock->control_change(c, ctrl,
vel);
break;
case 0xc0: /* patch change */
x = getnext(mp, 1);
if(mp->sock->program_change)
mp->sock->program_change(c, x);
break;
case 0xd0: /* channel aftertouch */
x = getnext(mp, 1);
if(mp->sock->channel_pressure)
mp->sock->channel_pressure(c, x);
break;
case 0xe0: /* pitch wheel */
x = getnext(mp, 1);
x |= getnext(mp, 1) << 7;
if(mp->sock->pitch_bend)
mp->sock->pitch_bend(c, (int)x - 8192);
break;
case 0xf0:
switch (x)
{
case 0xf0:
case 0xf7: /* sysex */
l = getval(mp);
i = (datalook(mp, mp->pos + l) ==
0xf7);
midiprintf2(D2LOG, "{%ld}", l);
midiprintf2(D2LOG, "\n");
for(j = 0; j < l; j++)
midiprintf2(D2LOG, "%2lX ", getnext(mp, 1));
midiprintf2(D2LOG, "\n");
if(i)
getnext(mp, 1);
break;
case 0xf1:
break;
case 0xf2:
getnext(mp, 2);
break;
case 0xf3:
getnext(mp, 1);
break;
case 0xf4:
break;
case 0xf5:
break;
case 0xf6: /* something */
case 0xf8:
case 0xfa:
case 0xfb:
case 0xfc:
break;
case 0xfe:
break;
case 0xfd:
break;
case 0xff:
x = getnext(mp, 1);
l = getval(mp);
midiprintf(D2LOG, " {%X (%X): ", x, l);
switch(x)
{
case 1:
midiprintf(D2LOG, "Text: ");
printnext(mp, l);
break;
case 2:
midiprintf(D2LOG, "Copyright: ");
printnext(mp, l);
break;
case 3:
midiprintf(D2LOG, "Track Name: ");
printnext(mp, l);
break;
case 4:
midiprintf(D2LOG, "Instrument: ");
printnext(mp, l);
break;
case 5:
midiprintf(D2LOG, "Lyric: ");
printnext(mp, l);
break;
case 6:
if(mp_parse_command(mp, l))
{
trk = -1;
continue;
}
break;
case 7:
midiprintf(D2LOG, "Cue Point: ");
printnext(mp, l);
break;
case 8:
case 9:
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
midiprintf(D2LOG, "Text event: ");
printnext(mp, l);
break;
case 0x21: /* MIDI Port # */
x = getnext(mp, 1);
midiprintf(D2LOG, "Port %u", x);
break;
case 0x2f: /* End Of Track */
mt->c.on = 0;
midiprintf(D2LOG, "End Of Track");
break;
case 0x51: /* Set Tempo */
x = getnext(mp, l);
set_tempo(mp, mp->c.ppqn, x);
midiprintf(D2LOG, "(µsqtr:%u ==> BPM:%f)",
mp->c.usqtr,
60.0*1000000.0 /
(float)mp->c.usqtr);
break;
case 0x58: /* Time signature */
mp_read_time_sig(mp);
break;
default:
for(i = 0; i < l; i++)
midiprintf2(D2LOG, "%2lX ", getnext(mp, 1));
break;
}
midiprintf(D2LOG, "}\n");
break;
}
break;
default:
/* if we get down here, an error occurred */
log_printf(ELOG, "a_midifile.c: INTERNAL ERROR;"
" illegal status byte!\n");
break;
}
if(mt->c.on)
mt->c.iwait = getval(mp);
mt->c.pos = mp->pos;
midiprintf2(D2LOG, "\n");
}
} while(ret);
/* Find first track to play */
mp->c.iwait = 1000000000; /* Bigger than any wait can be! */
FOR_TRACKS_ON
if(mp->track[trk].c.iwait < mp->c.iwait)
mp->c.iwait = mp->track[trk].c.iwait;
if(1000000000 == mp->c.iwait)
{
/* No tracks playing! */
mp->c.iwait = 0;
mp->c.fwait = 1000;
return 0;
}
/* Calculate time to next event */
mp->c.fwait = mp->c.iwait * mp->c.spulse;
midiprintf2(D2LOG, "fwait = %f\n", mp->c.fwait);
/* Update "track timers" and check if any tracks are playing */
ret = 0;
midiprintf2(D2LOG, "Wait: ");
FOR_TRACKS_ON
{
ret = 1;
mp->track[trk].c.iwait -= mp->c.iwait;
midiprintf2(D2LOG, "%d:%ld ", trk, mp->track[trk].c.iwait);
}
midiprintf2(D2LOG, "\n");
/* Update song time */
mp->c.time += mp->c.iwait;
midiprintf2(D2LOG, "----------------------------\n");
return ret;
}
#undef FOR_TRACKS_ON
int mp_play(midi_player_t *mp, float dt)
{
if(!mp->mf)
return 0;
while(dt > 0.0)
{
if(dt < mp->c.fwait)
{
mp->c.fwait -= dt;
return 1;
}
dt -= mp->c.fwait;
if(!mp_update(mp))
return 0;
}
return 1;
}
void mp_stop(midi_player_t *mp)
{
unsigned c;
if(!mp)
return;
if(!mp->sock->control_change)
return;
for(c = 0; c < 16; ++c)
{
mp->sock->control_change(c, 120, 1);
mp->sock->control_change(c, 121, 1);
mp->sock->control_change(c, 123, 1);
}
mp->mf = NULL;
}
void mp_rewind(midi_player_t *mp, unsigned subsong)
{
unsigned i;
int len;
if(!mp->mf)
return;
mp->pos = 0;
set_tempo(mp, 250, (unsigned)(1000.0 / (120.0/60.0)));
mp->c.time = 0;
mp->c.fwait = 0;
mp->c.iwait = 0;
mp->mf->subsongs = 1;
for(i = 0; i < 16; i++)
{
mp->track[i].tend = 0;
mp->track[i].spos = 0;
mp->track[i].c.pos = 0;
mp->track[i].c.iwait = 0;
mp->track[i].c.on = 0;
mp->track[i].c.pv = 0;
}
len = readheader(mp, "MThd");
if(len < 0)
{
log_printf(ELOG, "mp_rewind(): Is this a MIDI file...?\n");
mp->mf = NULL;
return;
}
midiprintf(D2LOG, "header length:%d\n", len);
if(len >= 6)
{
mp->mf->format = getnext(mp, 2);
mp->mf->tracks = getnext(mp, 2);
set_tempo(mp, getnext(mp, 2), mp->c.usqtr);
midiprintf(D2LOG, "format:%u\n", mp->mf->format);
midiprintf(D2LOG, "tracks:%u\n", mp->mf->tracks);
midiprintf(D2LOG, " ppqn:%u\n", mp->c.ppqn);
if(0 == mp->mf->format)
mp->mf->tracks = 1;
}
else
{
log_printf(ELOG, "mp_rewind(): WARNING: Short header!\n");
mp->mf->format = 0;
mp->mf->tracks = 1;
}
if(len > 6)
getnext(mp, (unsigned)(len - 6)); /* Skip rest of header */
if(mp->mf->tracks > 16)
{
log_printf(ELOG, "mp_rewind(): WARNING: Too many tracks!\n");
mp->mf->tracks = 16;
}
for(i = 0; i < mp->mf->tracks; ++i)
{
len = readheader(mp, "MTrk");
if(len < 0)
{
log_printf(ELOG, "mp_rewind(): Bad MIDI file!\n");
mp->mf = NULL;
return;
}
mp->track[i].enabled = 1;
mp->track[i].c.on = 1;
mp->track[i].spos = mp->pos;
mp->track[i].tend = mp->pos + len;
midiprintf(D2LOG, "track %u; start:%u length:%d\n",
i, mp->track[i].spos, len);
getnext(mp, (unsigned)len);
}
for(i = 0; i < 16; i++)
if(mp->track[i].enabled)
{
mp->track[i].c.pos = mp->track[i].spos;
mp->track[i].c.pv = 0;
mp->track[i].c.iwait = 0;
mp->track[i].loop_start = mp->track[i].c;
}
mp->doing = 1;
}
midi_player_t *mp_open(midisock_t *ms)
{
midi_player_t *mp = calloc(1, sizeof(midi_player_t));
if(!mp)
return NULL;
mp->sock = ms;
mp->mf = NULL;
return mp;
}
void mp_close(midi_player_t *mp)
{
mp_stop(mp);
free(mp);
}
+144
View File
@@ -0,0 +1,144 @@
/*(LGPL)
------------------------------------------------------------
a_midifile.c - MIDI file loader and player
------------------------------------------------------------
* Stolen from the AdPlug player, stripped down,
* translated into C, and adapted for the Audio Engine
* by David Olofson, 2002.
*
* 20020219: Separated data from player.
*
* 20020613: Removed code for all special formats
* and AdLib hardware specific stuff. (Can't
* use the data. Can't maintain the code.)
*
* 20020615: * Removed all 'long' types. If this engine
* is supposed to work in 16 bit environments,
* there's a *lot* more than a few longs to it.
* * Cleaned up type namespace.
* * Fixed various stuff that Splint whines about.
* * Added musical time printout function.
*
* Below is the original copyright. Of course, the LGPL
* license still applies.
------------------------------------------------------------
* Adplug - Replayer for many OPL2/OPL3 audio file formats.
* Copyright (C) 1999, 2000, 2001 Simon Peter, <dn.tlp@gmx.net>, et al.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* mid.h - LAA & MID & CMF Player by Philip Hassey (philhassey@hotmail.com)
*/
#ifndef _A_MIDIFILE_H_
#define _A_MIDIFILE_H_
#include "a_midisock.h"
/*----------------------------------------------------------
midi_file_t
----------------------------------------------------------*/
typedef struct midi_file_t
{
const char *author;
const char *title;
const char *remarks;
unsigned flen;
unsigned subsongs;
unsigned format;
unsigned tracks;
unsigned char *data;
} midi_file_t;
midi_file_t *mf_open(const char *name);
void mf_close(midi_file_t *mf);
/*----------------------------------------------------------
midi_player_t
----------------------------------------------------------*/
typedef struct mp_track_context_t
{
int on;
unsigned pos;
unsigned iwait;
unsigned char pv;
} mp_track_context_t;
typedef struct mp_track_t
{
int enabled;
unsigned tend;
unsigned spos;
mp_track_context_t c;
mp_track_context_t loop_start;
} mp_track_t;
typedef struct mp_timesig_t
{
unsigned char beats;
unsigned char value;
unsigned char clock;
unsigned char qn;
} mp_timesig_t;
typedef struct mp_context_t
{
unsigned ppqn; /* Pulses Per Quarter Note */
unsigned usqtr; /* microseconds per quarter note */
float spulse; /* seconds per pulse */
mp_timesig_t timesig;
unsigned time; /* Current position in pulses */
unsigned iwait; /* Pulses to next event */
float fwait; /* Seconds to next event */
} mp_context_t;
typedef struct midi_player_t
{
midisock_t *sock;
midi_file_t *mf;
unsigned pos;
mp_context_t c;
mp_context_t loop_start;
mp_track_t track[16];
int doing;
int pitch;
} midi_player_t;
midi_player_t *mp_open(midisock_t *ms);
void mp_close(midi_player_t *mp);
/* Select a previously loaded MIDI file for playback */
int mp_select(midi_player_t *mp, midi_file_t *midifile);
/* Play all events until the next delay */
int mp_update(midi_player_t *mp);
/* Play all events until current_position + dt seconds */
int mp_play(midi_player_t *mp, float dt);
/* Rewind to start of sub song 'subsong' */
void mp_rewind(midi_player_t *mp, unsigned subsong);
void mp_stop(midi_player_t *mp);
#endif /*_A_MIDIFILE_H_*/
+234
View File
@@ -0,0 +1,234 @@
/*(LGPL)
---------------------------------------------------------------------------
a_midisock.c - Generic Interface for Modular MIDI Management
---------------------------------------------------------------------------
* Copyright (C) 2001, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdio.h>
#include "a_midisock.h"
#include "kobolog.h"
/*----------------------------------------------------------
Dummy MIDI Socket implementation
----------------------------------------------------------*/
static void dummy_note_off(unsigned ch, unsigned pitch, unsigned vel) {}
static void dummy_note_on(unsigned ch, unsigned pitch, unsigned vel) {}
static void dummy_poly_pressure(unsigned ch, unsigned pitch, unsigned press) {}
static void dummy_control_change(unsigned ch, unsigned ctrl, unsigned amt) {}
static void dummy_program_change(unsigned ch, unsigned prog) {}
static void dummy_channel_pressure(unsigned ch, unsigned press) {}
static void dummy_pitch_bend(unsigned ch, int amt) {}
midisock_t dummy_midisock = {
dummy_note_off,
dummy_note_on,
dummy_poly_pressure,
dummy_control_change,
dummy_program_change,
dummy_channel_pressure,
dummy_pitch_bend
};
/*----------------------------------------------------------
Monitor MIDI Socket implementation
----------------------------------------------------------*/
static const char notenames[12][4] = {
"C-", "C#", "D-", "D#", "E-",
"F-", "F#", "G-", "G#", "A-", "A#", "B-"
};
static const char *controlnames[128] = {
/*0..7*/
"Bank Select MSB",
"Modulation",
"Breath Controller",
NULL,
"Foot Controller",
"Portamento Time",
"Data Entry MSB",
"Volume",
/*8..15*/
"Balance",
NULL,
"Pan",
"Expression",
"FX Control 1",
"FX Control 2",
NULL,
NULL,
/*16..23*/
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
/*24..31*/
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
/*32..39*/
"Bank Select LSB",
NULL,
NULL,
NULL,
NULL,
NULL,
"Data Entry LSB",
"BC: Select Insert Slot",
/*40..47*/
"BC: IFX Type",
"BC: IFX Param 1 (Time 1)",
"BC: IFX Param 2 (Time 2)",
"BC: IFX Param 3 (Depth 1)",
"BC: IFX Param 4 (Depth 2)",
"BC: IFX Param 5 (Rate)",
"BC: IFX Param 6 (Mode)",
"BC: Send to Master",
/*48..55*/
"BC: Send to Bus 1",
"BC: Send to Bus 2",
"BC: Send to Bus 3",
"BC: Send to Bus 4",
"BC: Send to Bus 5",
"BC: Send to Bus 6",
"BC: Send to Bus 7",
"BC: Send to Bus 8",
/*56..63*/
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
/*64..71*/
"Hold 1",
"Portamento",
"Sostenuto",
"Soft Pedal",
"Legato",
"Hold 2",
"SC 1",
"SC 2: Harmonic Content",
/*72..79*/
"SC 3: Release Time",
"SC 4: Attack Time",
"SC 5: Brightness",
NULL,
NULL,
NULL,
NULL,
NULL,
/*80..87*/
NULL,
NULL,
NULL,
NULL,
"Portamento Control",
NULL,
NULL,
NULL,
/*88..95*/
"BC: Primary Bus Select",
"BC: Send Bus Select",
NULL,
"FX 1 Depth: Reverb",
"FX 2 Depth",
"FX 3 Depth: Chorus",
"FX 4 Depth: Variation",
"FX 5 Depth",
/*96..103*/
"RPN Increment",
"RPN Decrement",
"NRPN LSB",
"NRPN MSB",
"RPN LSB",
"RPN MSB",
NULL,
NULL,
/*104..111*/
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
/*112..119*/
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
/*120..127*/
"All Sound Off",
"Reset All Controllers",
NULL,
"All Notes Off",
"Omni Off",
"Omni On",
"Mono",
"Poly"
};
#if 0
#define KNOWN_RPNS 5
static const char RPNnames[KNOWN_RPNS][24] = {
"Pitch Bend Sensitivity",
"Fine Tuning",
"Coarse Tuning",
"Tuning Program Select",
"Tuning Bank Select"
};
#endif
static void monitor_note_off(unsigned ch, unsigned pitch, unsigned vel)
{
log_printf(DLOG, "NoteOff(%u, %s%u(%u), %u)\n", ch,
notenames[pitch % 12], pitch / 12,
pitch, vel);
}
static void monitor_note_on(unsigned ch, unsigned pitch, unsigned vel)
{
log_printf(DLOG, "NoteOn(%u, %s%u(%u), %u)\n", ch,
notenames[pitch % 12], pitch / 12,
pitch, vel);
}
static void monitor_poly_pressure(unsigned ch, unsigned pitch, unsigned press)
{
log_printf(DLOG, "PolyPress(%u, %s%u(%u), %u)\n", ch,
notenames[pitch % 12], pitch / 12,
pitch, press);
}
static void monitor_control_change(unsigned ch, unsigned ctrl, unsigned amt)
{
const char *cn = controlnames[ctrl];
if(!cn)
cn = "<unknown>";
log_printf(DLOG, "ControlChg(%u, %s(%u), %u)\n", ch,
controlnames[ctrl], ctrl,
amt);
}
static void monitor_program_change(unsigned ch, unsigned prog)
{
log_printf(DLOG, "ProgramChg(%u, %u)\n", ch, prog);
}
static void monitor_channel_pressure(unsigned ch, unsigned press)
{
log_printf(DLOG, "ChannelPress(%u, %u)\n", ch, press);
}
static void monitor_pitch_bend(unsigned ch, int amt)
{
log_printf(DLOG, "PitchBend(%u, %d)\n", ch, amt);
}
midisock_t monitor_midisock = {
monitor_note_off,
monitor_note_on,
monitor_poly_pressure,
monitor_control_change,
monitor_program_change,
monitor_channel_pressure,
monitor_pitch_bend
};
+45
View File
@@ -0,0 +1,45 @@
/*(LGPL)
---------------------------------------------------------------------------
a_midisock.h - Generic Interface for Modular MIDI Management
---------------------------------------------------------------------------
* Copyright (C) 2001, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* TODO: System messages.
*/
#ifndef _A_MIDISOCK_H_
#define _A_MIDISOCK_H_
/* Note: NULL pointers are used for callbacks not desired. */
typedef struct midisock_t
{
void (*note_off)(unsigned ch, unsigned pitch, unsigned vel);
void (*note_on)(unsigned ch, unsigned pitch, unsigned vel);
void (*poly_pressure)(unsigned ch, unsigned pitch, unsigned press);
void (*control_change)(unsigned ch, unsigned ctrl, unsigned amt);
void (*program_change)(unsigned ch, unsigned prog);
void (*channel_pressure)(unsigned ch, unsigned press);
void (*pitch_bend)(unsigned ch, int amt);
} midisock_t;
/* These two can be assumed to have all callbacks defined - no NULLs. */
extern midisock_t dummy_midisock; /* NOP */
extern midisock_t monitor_midisock; /* printf()s MIDI messages as text */
#endif /*_A_MIDISOCK_H_*/
+359
View File
@@ -0,0 +1,359 @@
/*(LGPL)
---------------------------------------------------------------------------
a_mixers.h - Mixer Core Code
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, 2007 David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* This is to be included multiple times, with various definitions
* of some macros - that's why it looks so weird for a "header"...
*/
#define __FOR_SAMPLES frames <<= 1; for(s = 0; s < frames; s += 2)
#ifdef __STEREO
/* Paste the stereo-only lines */
# define __ST(x) x
/* Index unit: two per sample */
# define __INDEX ((sp >> (FREQ_BITS - 1)) & 0xfffffffe)
/* Right output from right input */
# define __INDINC 2
# define __R r
#else
/* Don't paste stereo stuff */
# define __ST(x)
/* Index unit: one per sample */
# define __INDEX (sp >> FREQ_BITS)
# define __INDINC 1
/* Right output from mono input */
# define __R l
#endif
#ifdef __16BIT
# define __NORMALIZE VOL_BITS
#else
# define __NORMALIZE (VOL_BITS-8)
#endif
#if defined(__16BIT) && (FREQ_BITS > 15)
# define __FRAC (int)((sp >> (FREQ_BITS - 15)) & 0x7fff)
# define __IFRAC(x) (0x8000 - x)
# define __FRACBITS 15
#else
# define __FRAC (int)(sp & ((1 << FREQ_BITS) - 1))
# define __IFRAC(x) ((1 << FREQ_BITS) - x)
# define __FRACBITS FREQ_BITS
#endif
#ifdef __SEND
# define __SND(x) x
#else
# define __SND(x)
#endif
#define LVOL (v->ic[VIC_LVOL].v >> RAMP_BITS)
#define RVOL (v->ic[VIC_RVOL].v >> RAMP_BITS)
#define LSEND (v->ic[VIC_LSEND].v >> RAMP_BITS)
#define RSEND (v->ic[VIC_RSEND].v >> RAMP_BITS)
#define CSEND ((v->ic[VIC_LSEND].v + v->ic[VIC_RSEND].v) >> (RAMP_BITS + 1))
#define __OUTPUT \
out[s] += l * LVOL >> __NORMALIZE; \
out[s + 1] += __R * RVOL >> __NORMALIZE; \
__SND( sout[s] += l * LSEND >> __NORMALIZE; \
sout[s + 1] += __R * RSEND >> __NORMALIZE;)
#define __RAMP \
v->ic[VIC_LVOL].v += v->ic[VIC_LVOL].dv; \
v->ic[VIC_RVOL].v += v->ic[VIC_RVOL].dv; \
__SND( v->ic[VIC_LSEND].v += v->ic[VIC_LSEND].dv; \
v->ic[VIC_RSEND].v += v->ic[VIC_RSEND].dv;)
#ifdef AUDIO_USE_VU
int vu;
#endif
unsigned s;
unsigned int sp = ((v->position << FREQ_BITS) & 0x7fffffff) +
(v->position_frac >> (32 - FREQ_BITS));
#ifdef __16BIT
Sint16 *in = (Sint16 *)(wavetab[v->c[VC_WAVE]].data.si16) +
#else
Sint8 *in = (Sint8 *)(wavetab[v->c[VC_WAVE]].data.si8) +
#endif
/*
* Adjust pointer according to bits 31+ of the position.
* Note that this overlap in bit 31 is important; it
* allows windows to cross "block" boundaries!
*/
#ifdef __STEREO
2 *
#endif
(v->position & ~((1<<(31-FREQ_BITS)) - 1));
#ifdef AUDIO_USE_VU
#ifdef __STEREO
vu = in[__INDEX] + in[__INDEX+1];
#else
vu = in[__INDEX] << 1;
#endif
#ifndef __16BIT
vu <<= 8;
#endif
if(vu > v->vu)
v->vu = vu;
else if(-vu > v->vu)
v->vu = -vu;
#endif /*AUDIO_USE_VU*/
switch(v->c[VC_RESAMPLE])
{
default:
case AR_NEAREST:
{
int lvol = LVOL;
int rvol = RVOL;
__SND(int csend = CSEND;)
v->ic[VIC_LVOL].v += v->ic[VIC_LVOL].dv * frames;
v->ic[VIC_RVOL].v += v->ic[VIC_RVOL].dv * frames;
__SND( v->ic[VIC_LSEND].v += v->ic[VIC_LSEND].dv * frames;
v->ic[VIC_RSEND].v += v->ic[VIC_RSEND].dv * frames;)
__FOR_SAMPLES
{
unsigned ind = __INDEX;
int l = in[ind];
__ST(int r = in[ind + 1];)
__SND(int ss;)
out[s] += l * lvol >> __NORMALIZE;
out[s + 1] += __R * rvol >> __NORMALIZE;
__SND(ss = (l + __R) * csend >> __NORMALIZE;)
__SND(sout[s] += ss;)
__SND(sout[s + 1] += ss;)
sp += v->step;
}
break;
}
case AR_NEAREST_4X:
{
/*
* Should be cheaper than AR_LINEAR on machines
* with slow multiplications. Doesn't sound much
* better than NEAREST most of the time, though...
*/
int lvol = LVOL;
int rvol = RVOL;
unsigned step = v->step >> 2;
__SND(int csend = CSEND;)
v->ic[VIC_LVOL].v += v->ic[VIC_LVOL].dv * frames;
v->ic[VIC_RVOL].v += v->ic[VIC_RVOL].dv * frames;
__SND( v->ic[VIC_LSEND].v += v->ic[VIC_LSEND].dv * frames;
v->ic[VIC_RSEND].v += v->ic[VIC_RSEND].dv * frames;)
__FOR_SAMPLES
{
unsigned ind;
int l;
__ST(int r;)
__SND(int ss;)
ind = __INDEX; l = in[ind]; __ST(r = in[ind + 1];) sp += step;
ind = __INDEX; l += in[ind]; __ST(r += in[ind + 1];) sp += step;
ind = __INDEX; l += in[ind]; __ST(r += in[ind + 1];) sp += step;
ind = __INDEX; l += in[ind]; __ST(r += in[ind + 1];) sp += step;
l >>= 2;
__ST(r >>= 2);
out[s] += l * lvol >> __NORMALIZE ;
out[s + 1] += __R * rvol >> __NORMALIZE;
__SND(ss = (l + __R) * csend >> __NORMALIZE;)
__SND(sout[s] += ss;)
__SND(sout[s + 1] += ss;)
}
break;
}
case AR_LINEAR:
{
int lvol = LVOL;
int rvol = RVOL;
__SND( int lsend = LSEND;
int rsend = RSEND;)
v->ic[VIC_LVOL].v += v->ic[VIC_LVOL].dv * frames;
v->ic[VIC_RVOL].v += v->ic[VIC_RVOL].dv * frames;
__SND( v->ic[VIC_LSEND].v += v->ic[VIC_LSEND].dv * frames;
v->ic[VIC_RSEND].v += v->ic[VIC_RSEND].dv * frames;)
__FOR_SAMPLES
{
int l;
__ST(int r;)
unsigned ind = __INDEX;
int frac = __FRAC;
int ifrac = __IFRAC(frac);
l = in[ind + __INDINC] * frac;
__ST(r = in[ind + 3] * frac;)
l += in[ind] * ifrac;
__ST(r += in[ind + 1] * ifrac;)
l >>= __FRACBITS;
__ST(r >>= __FRACBITS;)
out[s] += l * lvol >> __NORMALIZE;
out[s + 1] += __R * rvol >> __NORMALIZE;
__SND(sout[s] += l * lsend >> __NORMALIZE;)
__SND(sout[s + 1] += __R * rsend >> __NORMALIZE;)
sp += v->step;
}
break;
}
case AR_LINEAR_2X_R:
{
unsigned step1 = v->step >> 1;
unsigned step2 = (v->step + 1) >> 1;
__FOR_SAMPLES
{
int l, l2;
__ST(int r;)
__ST(int r2;)
unsigned ind = __INDEX;
int frac = __FRAC;
int ifrac = __IFRAC(frac);
l = in[ind + __INDINC] * frac;
__ST(r = in[ind + 3] * frac;)
l += in[ind] * ifrac;
__ST(r += in[ind + 1] * ifrac;)
sp += step1;
ind = __INDEX;
frac = __FRAC;
ifrac = __IFRAC(frac);
l2 = in[ind + __INDINC] * frac;
__ST(r2 = in[ind + 3] * frac;)
l2 += in[ind] * ifrac;
__ST(r2 += in[ind + 1] * ifrac;)
sp += step2;
l = ((l >> 1) + (l2 >> 1)) >> __FRACBITS;
__ST(r = ((r >> 1) + (r2 >> 1)) >> __FRACBITS;)
__OUTPUT
__RAMP
}
break;
}
case AR_LINEAR_4X_R:
case AR_LINEAR_8X_R:
case AR_LINEAR_16X_R:
{
int over_shift = v->c[VC_RESAMPLE] - AR_LINEAR_4X_R + 2;
int over = 1 << over_shift;
unsigned step = v->step >> over_shift;
__FOR_SAMPLES
{
int i, l = 0;
__ST(int r = 0;)
for(i = 0; i < over; ++i)
{
int ll;
__ST(int rr;)
unsigned ind = __INDEX;
int frac = __FRAC;
int ifrac = __IFRAC(frac);
ll = in[ind + __INDINC] * frac;
__ST(rr = in[ind + 3] * frac;)
ll += in[ind] * ifrac;
__ST(rr += in[ind + 1] * ifrac;)
l += ll >> over_shift;
__ST(r += rr >> over_shift;)
sp += step;
}
l >>= __FRACBITS;
__ST(r >>= __FRACBITS;)
__OUTPUT
__RAMP
}
break;
}
case AR_CUBIC_R:
{
/*
* Interpolation formulae by Olli Niemitalo.
* Converted to integer arithmetics by David Olofson.
*/
__FOR_SAMPLES
{
int a, b, c, lm1, l, l1, l2;
#ifdef __STEREO
int rm1, r, r1, r2;
#endif
int ind = __INDEX;
int frac = __FRAC;
lm1 = in[ind];
l = in[ind + __INDINC];
l1 = in[ind + __INDINC*2];
l2 = in[ind + __INDINC*3];
a = (3 * (l-l1) - lm1 + l2) >> 1;
b = (l1 << 1) + lm1 - ((5*l + l2) >> 1);
c = (l1 - lm1) >> 1;
a = a * frac >> __FRACBITS;
a = (a + b) * frac >> __FRACBITS;
l += (a + c) * frac >> __FRACBITS;
sp += v->step;
#ifdef __STEREO
rm1 = in[ind + 1];
r = in[ind + 3];
r1 = in[ind + 5];
r2 = in[ind + 7];
a = (3 * (r-r1) - rm1 + r2) >> 1;
b = (r1 << 1) + rm1 - ((5*r + r2) >> 1);
c = (r1 - rm1) >> 1;
a = a * frac >> __FRACBITS;
a = (a + b) * frac >> __FRACBITS;
r += (a + c) * frac >> __FRACBITS;
#endif
__OUTPUT
__RAMP
}
break;
}
}
/*
* Put the position back, *adding* to the integer part,
* to handle the bit 31 overlap properly. (The block
* hack requires us to start with bit 31 == 0, so it can
* serve as a "carry flag" as an extra bonus.)
*/
v->position &= ~(0x7fffffff >> FREQ_BITS);
v->position += sp >> FREQ_BITS;
/*
* For the fractional part, we just leave the bits
* below our resolution unaffected.
*/
v->position_frac &= (1<<(32-FREQ_BITS))-1;
v->position_frac |= sp << (32-FREQ_BITS);
#undef __FOR_SAMPLES
#undef __NORMALIZE
#undef __SND
#undef __ST
#undef __INDEX
#undef __INDINC
#undef __R
#undef __FRAC
#undef __IFRAC
#undef __FRACBITS
+753
View File
@@ -0,0 +1,753 @@
/*(LGPL)
---------------------------------------------------------------------------
a_patch.c - Audio Engine "instrument" definitions
---------------------------------------------------------------------------
* Copyright (C) 2001-2003, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <string.h>
#include "kobolog.h"
#include "a_struct.h"
#include "a_patch.h"
#include "a_sequencer.h"
#define EDBG(x)
#define EGDBG(x)
static int _is_open = 0;
#define CHECKINIT if(!_is_open) audio_patch_open();
/*
* Common tools for mono and poly...
*/
static inline int __calc_randpitch(audio_patch_t *p, int pitch)
{
static int rnd = 16576;
int rp = p->param[APP_RANDPITCH];
if(rp)
{
rnd *= 1566083941UL;
rnd++;
rnd &= 0xffffffffUL;
pitch += (rnd % rp) - rp/2;
}
return pitch;
}
static inline int __get_wave(audio_patch_t *p)
{
int wave = p->param[APP_WAVE];
if((wave < 0) || (wave >= AUDIO_MAX_WAVES))
return -1;
if(AF_MIDI == wavetab[wave].format)
return -1;
return wave;
}
/* Update base vol/send levels after control change. */
static inline void __env_control_recalc(audio_patch_t *p, audio_channel_t *c,
audio_voice_t *v)
{
patch_closure_t *clos = &v->closure;
clos->lvol = clos->rvol = clos->velvol;
clos->lvol *= 64 - (c->ctl[ACC_PAN] >> 10);
clos->rvol *= 64 + (c->ctl[ACC_PAN] >> 10);
clos->lvol >>= 6;
clos->rvol >>= 6;
clos->lsend = c->ctl[ACC_SEND] >> (16-VOL_BITS);
clos->lsend *= clos->velvol;
clos->lsend >>= 16;
clos->rsend = clos->lsend;
clos->lsend *= 64 - (c->ctl[ACC_PAN] >> 10);
clos->rsend *= 64 + (c->ctl[ACC_PAN] >> 10);
clos->lsend >>= 6;
clos->rsend >>= 6;
}
/* Initialize envelope to PES_START. */
static inline void __env_start(audio_patch_t *p, audio_channel_t *c,
audio_voice_t *v)
{
patch_closure_t *clos = &v->closure;
clos->env_state = PES_START;
clos->env_next = aev_timer;
}
static inline void __env_stop(audio_patch_t *p, audio_channel_t *c,
audio_voice_t *v)
{
patch_closure_t *clos = &v->closure;
if(p->param[APP_ENV_T3] >= 0)
return;
EGDBG(log_printf(D3LOG, "stop!\n");)
switch (clos->env_state)
{
case PES_START:
case PES_START2:
case PES_DELAY:
case PES_ATTACK:
case PES_HOLD:
case PES_DECAY:
clos->queued_stop = 1;
if(p->param[APP_ENV_SKIP])
{
clos->env_next = aev_timer;
clos->env_state = PES_SUSTAIN;
}
break;
case PES_SUSTAIN: /* No interactive sustain! */
case PES_RELEASE: /* Release already active... */
case PES_DEAD:
break;
case PES_WAIT:
clos->env_next = aev_timer;
clos->env_state = PES_SUSTAIN;
break;
}
}
/*
FIXME: Support longer times than 65535 timestamp units.
FIXME: When that's done, this will need fixing as well...
*/
#define S2S(x) ((((x)>>2) * a_settings.samplerate) >> 14)
static inline void __env_run(audio_patch_t *p, audio_channel_t *c,
audio_voice_t *v, unsigned frames)
{
patch_closure_t *clos = &v->closure;
int lvol, rvol, lsend, rsend;
aev_timestamp_t t;
while((t = (unsigned)((clos->env_next - aev_timer) &
AEV_TIMESTAMP_MASK)) < frames)
{
int duration;
int target = 0;
aev_timestamp_t timestamp = (aev_timestamp_t)
(clos->env_next - aev_timer) &
AEV_TIMESTAMP_MASK;
EGDBG(log_printf(D3LOG, "%d: ", clos->env_next);)
switch(clos->env_state)
{
case PES_START:
duration = 0;
target = p->param[APP_ENV_L0];
clos->env_state = PES_START2;
clos->queued_stop = 0;
break;
case PES_START2:
duration = S2S(p->param[APP_ENV_DELAY]);
target = p->param[APP_ENV_L0];
clos->env_state = PES_DELAY;
clos->queued_stop = 0;
break;
case PES_DELAY:
duration = S2S(p->param[APP_ENV_T1]);
target = p->param[APP_ENV_L1];
clos->env_state = PES_ATTACK;
break;
case PES_ATTACK:
duration = S2S(p->param[APP_ENV_HOLD]);
target = p->param[APP_ENV_L1];
clos->env_state = PES_HOLD;
break;
case PES_HOLD:
duration = S2S(p->param[APP_ENV_T2]);
target = p->param[APP_ENV_L2];
clos->env_state = PES_DECAY;
break;
case PES_DECAY:
if(p->param[APP_ENV_T3] >= 0)
{
/* Timed sustain or no sustain */
target = p->param[APP_ENV_L2];
duration = S2S(p->param[APP_ENV_T3]);
clos->env_state = PES_SUSTAIN;
break;
}
else if(!clos->queued_stop)
{
/* Interactive sustain */
target = p->param[APP_ENV_L2];
duration = 30000;
clos->env_state = PES_WAIT;
break;
}
/* Fall though if interactive and stopped! */
case PES_SUSTAIN:
duration = S2S(p->param[APP_ENV_T4]);
target = 0;
clos->env_state = PES_RELEASE;
break;
case PES_WAIT:
/* Interactive sustain */
target = p->param[APP_ENV_L2];
duration = 30000;
break;
case PES_RELEASE:
(void)aev_send0(&v->port, timestamp, VE_STOP);
--c->playing;
case PES_DEAD:
clos->env_next += 30000;
default: /* Warning supressor */
continue;
}
EGDBG(log_printf(D3LOG, " (duration: %d, target: %d)\n", duration, target);)
lvol = (clos->lvol >> 1) * target >> 15;
rvol = (clos->rvol >> 1) * target >> 15;
lsend = (clos->lsend >> 1) * target >> 15;
rsend = (clos->rsend >> 1) * target >> 15;
(void)aev_sendi2(&v->port, t, VE_IRAMP,
VIC_LVOL, lvol, duration);
(void)aev_sendi2(&v->port, t, VE_IRAMP,
VIC_RVOL, rvol, duration);
if(v->use_double)
{
(void)aev_sendi2(&v->port, t, VE_IRAMP,
VIC_LSEND, lsend, duration);
(void)aev_sendi2(&v->port, t, VE_IRAMP,
VIC_RSEND, rsend, duration);
}
clos->env_next += duration;
}
}
static void _env_run_all(audio_patch_t *p, audio_channel_t *c, unsigned frames)
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
__env_run(p, c, v, frames);
v = chan_get_next_voice(v);
}
}
static inline void __start_voice(audio_patch_t *p, audio_channel_t *c,
audio_voice_t *v, int wave, int pitch, int velocity)
{
patch_closure_t *clos = &v->closure;
/* Apply random pitch */
pitch = __calc_randpitch(p, pitch);
/* Store base pitch and vel to closure */
clos->pitch = pitch;
clos->velocity = velocity;
/* Transform to get actual pitch */
pitch += c->ctl[ACC_PITCH];
pitch -= 60<<16;
/* Start voice! */
(void)aev_sendi1(&v->port, 0, VE_SET, VC_PRIM_BUS,
c->ctl[ACC_PRIM_BUS]);
(void)aev_sendi1(&v->port, 0, VE_SET, VC_SEND_BUS,
c->ctl[ACC_SEND_BUS]);
(void)aev_send1(&v->port, 0, VE_START, wave);
(void)aev_sendi1(&v->port, 0, VE_SET, VC_PITCH, pitch);
/* Calculate "base volume" for envelope */
clos->velvol = c->ctl[ACC_VOLUME] >> (16-VOL_BITS);
clos->velvol *= velocity;
clos->velvol >>= 16;
__env_control_recalc(p, c, v);
/* Initialize and start envelope */
__env_start(p, c, v);
++c->playing;
}
/*----------------------------------------------------------
Default patches for polyphonic wave playback
----------------------------------------------------------*/
static inline void poly_start(audio_patch_t *p, audio_channel_t *c, int tag,
int pitch, int velocity)
{
int voice;
int wave = __get_wave(p);
if(wave < 0)
return;
voice = voice_alloc(c);
if(voice < 0)
return;
__start_voice(p, c, voicetab + voice, wave, pitch, velocity);
voicetab[voice].tag = tag;
}
static inline void poly_stop(audio_patch_t *p, audio_channel_t *c, int tag, int velocity)
{
if(tag < 0)
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
__env_stop(p, c, v);
v = chan_get_next_voice(v);
}
}
else
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
if(v->tag == tag)
{
__env_stop(p, c, v);
return;
}
v = chan_get_next_voice(v);
}
}
}
static inline void poly_control(audio_patch_t *p, audio_channel_t *c, int tag,
int ctl, int arg)
{
int mask;
if(AVT_ALL == tag)
mask = tag = 0;
else
mask = -1;
switch(ctl)
{
case ACC_GROUP:
case ACC_PRIORITY:
case ACC_PATCH:
break;
case ACC_PRIM_BUS:
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
if((v->tag & mask) == tag)
v->c[VC_PRIM_BUS] = arg;
v = chan_get_next_voice(v);
}
break;
}
case ACC_SEND_BUS:
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
if((v->tag & mask) == tag)
v->c[VC_SEND_BUS] = arg;
v = chan_get_next_voice(v);
}
break;
}
case ACC_PITCH:
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
int pitch;
if((v->tag & mask) == tag)
{
pitch = c->ctl[ACC_PITCH];
pitch += v->closure.pitch;
pitch -= 60<<16;
(void)aev_sendi1(&v->port, 0, VE_SET,
VC_PITCH, pitch);
}
v = chan_get_next_voice(v);
}
break;
}
case ACC_PAN:
case ACC_VOLUME:
case ACC_SEND:
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
if((v->tag & mask) == tag)
{
/*
TODO: Ramping...
*/
__env_control_recalc(p, c, v);
}
v = chan_get_next_voice(v);
}
break;
}
}
}
static void poly_process(audio_patch_t *p, audio_channel_t *c, unsigned frames)
{
unsigned eframes = frames ? frames : 1;
while(aev_next(&c->port, 0) < eframes)
{
aev_event_t *ev = aev_read(&c->port);
switch(ev->type)
{
case CE_START:
EDBG(log_printf(DLOG, "poly: CE_START %d\n", p->param[APP_WAVE]);)
poly_start(p, c, ev->arg1, ev->arg2, ev->arg3);
break;
case CE_STOP:
EDBG(log_printf(DLOG, "poly: CE_STOP\n");)
poly_stop(p, c, ev->arg1, ev->arg2);
break;
case CE_CONTROL:
EDBG(log_printf(DLOG, "poly: CE_CONTROL %d = %d\n", ev->index, ev->arg2);)
c->ctl[ev->index] = ev->arg2;
if(AVT_FUTURE != ev->arg1)
poly_control(p, c, ev->arg1, ev->index, ev->arg2);
break;
}
aev_free(ev);
}
/*
FIXME: We need to split buffers or something for control
FIXME: events that affect the envelope generators!
*/
if(frames)
_env_run_all(p, c, frames);
}
/*----------------------------------------------------------
Default patches for monophonic wave playback
----------------------------------------------------------*/
static void mono_start(audio_patch_t *p, audio_channel_t *c, int tag,
int pitch, int velocity)
{
int wave = __get_wave(p);
if(wave < 0)
return;
if(voice_alloc(c) < 0)
return;
__start_voice(p, c, c->voices, wave, pitch, velocity);
c->voices->tag = tag;
c->playing = 1;
}
static void mono_stop(audio_patch_t *p, audio_channel_t *c, int tag, int velocity)
{
if(!c->voices)
return;
if(c->voices->channel != c)
return;
__env_stop(p, c, c->voices);
c->playing = 0;
}
static void mono_control(audio_patch_t * p, audio_channel_t *c, int tag,
int ctl, int arg)
{
audio_voice_t *v = c->voices;
if(!v)
return;
if(v->channel != c)
return;
switch (ctl)
{
case ACC_GROUP: /* We don't care. */
case ACC_PRIORITY: /* Only for *new* voices. */
case ACC_PATCH: /* We just got selected. And? :-) */
break;
case ACC_PRIM_BUS:
(void)aev_sendi1(&v->port, 0, VE_SET, VC_PRIM_BUS, arg);
break;
case ACC_SEND_BUS:
(void)aev_sendi1(&v->port, 0, VE_SET, VC_SEND_BUS, arg);
break;
case ACC_PITCH:
(void)aev_sendi1(&v->port, 0, VE_SET, VC_PITCH, arg);
break;
case ACC_PAN:
case ACC_VOLUME:
case ACC_SEND:
__env_control_recalc(p, c, v);
break;
}
}
static void mono_process(audio_patch_t *p, audio_channel_t *c, unsigned frames)
{
unsigned eframes = frames ? frames : 1;
while(aev_next(&c->port, 0) < eframes)
{
aev_event_t *ev = aev_read(&c->port);
switch(ev->type)
{
case CE_START:
EDBG(log_printf(DLOG, "mono: CE_START %d\n", p->param[APP_WAVE]);)
mono_start(p, c, ev->arg1, ev->arg2, ev->arg3);
break;
case CE_STOP:
EDBG(log_printf(DLOG, "mono: CE_STOP\n");)
mono_stop(p, c, ev->arg1, ev->arg2);
break;
case CE_CONTROL:
EDBG(log_printf(DLOG, "mono: CE_CONTROL %d = %d\n", ev->index, ev->arg2);)
c->ctl[ev->index] = ev->arg2;
if(ACC_PATCH == ev->index)
p = patchtab + c->ctl[ACC_PATCH];
if(AVT_FUTURE != ev->arg1)
mono_control(p, c, ev->arg1, ev->index, ev->arg2);
break;
}
aev_free(ev);
}
if(frames)
_env_run_all(p, c, frames);
}
/*----------------------------------------------------------
Default patch for MIDI playback
----------------------------------------------------------*/
static void midi_start(audio_patch_t *p, audio_channel_t *c, int tag,
int pitch, int velocity)
{
int wave = p->param[APP_WAVE];
if((wave < 0) || (wave >= AUDIO_MAX_WAVES))
return;
if(AF_MIDI != wavetab[wave].format)
return; /* Can't play this! */
pitch = __calc_randpitch(p, pitch);
pitch += c->ctl[ACC_PITCH];
pitch -= 60<<16;
if(pitch < 0)
pitch = 0;
else if(pitch > 128<<16)
pitch = 128<<16;
if(sequencer_play(wavetab[wave].data.midi, tag, pitch, velocity) < 0)
return;
++c->playing;
}
static void midi_process(audio_patch_t *p, audio_channel_t *c, unsigned frames)
{
unsigned eframes = frames ? frames : 1;
while(aev_next(&c->port, 0) < eframes)
{
aev_event_t *ev = aev_read(&c->port);
switch(ev->type)
{
case CE_START:
EDBG(log_printf(DLOG, "midi: CE_START %d\n", p->param[APP_WAVE]);)
midi_start(p, c, ev->arg1, ev->arg2, ev->arg3);
break;
case CE_STOP:
EDBG(log_printf(DLOG, "midi: CE_STOP\n");)
/*
FIXME: Handle cid == -1 correctly!
*/
c->playing = 0;
sequencer_stop(ev->arg1);
break;
case CE_CONTROL:
EDBG(log_printf(DLOG, "midi: CE_CONTROL %d = %d\n", ev->index, ev->arg2);)
c->ctl[ev->index] = ev->arg2;
break;
}
aev_free(ev);
}
/* This is where to run the sequencer. */
}
/*----------------------------------------------------------
User EEL Patch Driver
----------------------------------------------------------*/
static void eel_process(audio_patch_t *p, audio_channel_t *c, unsigned frames)
{
unsigned eframes = frames ? frames : 1;
while(aev_next(&c->port, 0) < eframes)
{
aev_event_t *ev = aev_read(&c->port);
switch(ev->type)
{
case CE_START:
EDBG(log_printf(DLOG, "eel: CE_START %d\n", p->param[APP_WAVE]);)
break;
case CE_STOP:
EDBG(log_printf(DLOG, "eel: CE_STOP\n");)
break;
case CE_CONTROL:
EDBG(log_printf(DLOG, "eel: CE_CONTROL %d = %d\n", ev->index, ev->arg2);)
c->ctl[ev->index] = ev->arg2;
break;
}
aev_free(ev);
}
/* Run timer driven EEL code here. */
}
/*----------------------------------------------------------
Dummy Patch Driver
----------------------------------------------------------*/
static void dummy_process(audio_patch_t *p, audio_channel_t *c, unsigned frames)
{
unsigned eframes = frames ? frames : 1;
while(aev_next(&c->port, 0) < eframes)
{
aev_event_t *ev = aev_read(&c->port);
switch(ev->type)
{
case CE_START:
EDBG(log_printf(DLOG, "dummy: CE_START %d\n", p->param[APP_WAVE]);)
break;
case CE_STOP:
EDBG(log_printf(DLOG, "dummy: CE_STOP\n");)
break;
case CE_CONTROL:
EDBG(log_printf(DLOG, "dummy: CE_CONTROL %d = %d\n", ev->index, ev->arg2);)
c->ctl[ev->index] = ev->arg2;
break;
}
aev_free(ev);
}
}
/*----------------------------------------------------------
Patch programming API
----------------------------------------------------------*/
/* Set parameter for a patch */
void audio_patch_param(int pid, int pparam, int value)
{
CHECKINIT
if(pid < 0)
return;
if(pid >= AUDIO_MAX_PATCHES)
return;
if(pparam < 0)
return;
if(pparam > APP_LAST)
return;
if(APP_DRIVER == pparam)
{
switch(value)
{
case PD_MONO:
patchtab[pid].process = mono_process;
break;
case PD_POLY:
patchtab[pid].process = poly_process;
break;
case PD_MIDI:
patchtab[pid].process = midi_process;
break;
case PD_EEL:
patchtab[pid].process = eel_process;
break;
default:
patchtab[pid].process = dummy_process;
log_printf(ELOG, "a_patch.c: Illegal patch"
" driver selected!\n");
break;
}
}
patchtab[pid].param[pparam] = value;
}
/*----------------------------------------------------------
Global init
----------------------------------------------------------*/
void audio_patch_open(void)
{
int i;
if(_is_open)
return;
memset(patchtab, 0, sizeof(patchtab));
for(i = 0; i < AUDIO_MAX_PATCHES; ++i)
{
/*
patchtab[i].param[APP_DRIVER] = PD_NONE;
patchtab[i].process = dummy_process;
*/
/*
FIXME: This hack is just until the EEL<->patch binding is sorted out.
*/
patchtab[i].param[APP_DRIVER] = PD_POLY;
patchtab[i].process = poly_process;
patchtab[i].param[APP_WAVE] = i;
patchtab[i].param[APP_ENV_L0] = 1<<16;
patchtab[i].param[APP_ENV_L1] = 1<<16;
patchtab[i].param[APP_ENV_L2] = 1<<16;
patchtab[i].param[APP_ENV_T3] = -1;
patchtab[i].param[APP_ENV_T4] = (1<<16) / 100;
patchtab[i].param[APP_ENV2VOL] = 1<<16;
}
_is_open = 1;
}
void audio_patch_close(void)
{
if(!_is_open)
return;
memset(patchtab, 0, sizeof(patchtab));
_is_open = 0;
}
+99
View File
@@ -0,0 +1,99 @@
/*(LGPL)
---------------------------------------------------------------------------
a_patch.h - Audio Engine "instrument" definitions
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_PATCH_H_
#define _A_PATCH_H_
#include "a_globals.h"
#include "a_types.h"
#include "a_events.h"
#include "eel.h"
/*----------------------------------------------------------
Audio Patch
----------------------------------------------------------*/
struct audio_channel_t;
typedef struct audio_patch_t
{
appbank_t param;
/*
* Process input events, update internal state and
* generate output events for 'frames' frames.
*
* IMPORTANT!
* The engine may occasionally call this
* function with a 0 'frames' argument. This
* doesn't mean "do nothing", but rather
* "process all events available for the
* first frame". (It's used whenever the
* engine needs to split for an ACC_PATCH
* while there are more events for the
* same frame.)
*/
void (*process)(struct audio_patch_t *p,
struct audio_channel_t *c, unsigned frames);
eel_symbol_t *eel_process;
} audio_patch_t;
typedef enum
{
PES_START = 0,
PES_START2,
PES_DELAY,
PES_ATTACK,
PES_HOLD,
PES_DECAY,
PES_SUSTAIN,
PES_WAIT,
PES_RELEASE,
PES_DEAD
} A_patch_env_states;
/*
* Temporary kludge. Something more flexible will probably
* be needed, but I'm using this for now, to avoid type
* casting void pointers all over the place. (Not an issue
* with EEL based patches.)
*/
typedef struct patch_closure_t
{
/* Arguments from CE_START */
int velocity;
int pitch; /* w/ RANDPITCH, but w/o channel PITCH */
int velvol; /* For envelope */
/* Envelope generator state */
A_patch_env_states env_state;
int queued_stop;
aev_timestamp_t env_next;
int lvol, rvol;
int lsend, rsend;
} patch_closure_t;
void audio_patch_open(void);
void audio_patch_close(void);
#endif /*_A_PATCH_H_*/
+60
View File
@@ -0,0 +1,60 @@
/*
---------------------------------------------------------------------------
a_pitch.c - Simple fixed point linear pitch table.
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include "a_globals.h"
#include "a_pitch.h"
/*
* Start pitch, based on the assumption that 1.0 (original
* sample playback speed) gives you a middle C (MIDI #60).
*/
#define FIVE_OCTAVES (1 << 5)
/* 1 + twelfth root of two */
#define SEMITONE_MULTIPLIER 1.0594630943592953
int *__pitchtab = NULL;
int ptab_init(int middle_c)
{
int p;
double pitch;
free(__pitchtab);
__pitchtab = malloc(129 * sizeof(int));
if(!__pitchtab)
return -1;
pitch = (double)middle_c / FIVE_OCTAVES;
for(p = 0; p < 129; ++p)
{
__pitchtab[p] = (int)pitch;
pitch *= SEMITONE_MULTIPLIER;
}
return 0;
}
void ptab_close(void)
{
free(__pitchtab);
__pitchtab = NULL;
}
+107
View File
@@ -0,0 +1,107 @@
/*(LGPL)
---------------------------------------------------------------------------
a_pitch.h - Simple fixed point linear pitch table.
---------------------------------------------------------------------------
* Copyright (C) 2001. 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _PITCHTAB_H_
#define _PITCHTAB_H_
#ifdef __cplusplus
extern "C" {
#endif
extern int *__pitchtab;
/*
* 'middle_c' is the output value you want with an input of
* 60; MIDI standard middle C. The table will start 5 octaves
* down from there, and will contain 129 semitone steps. (The
* last step is for sub-semitone interpolation.)
*/
int ptab_init(int middle_c);
void ptab_close(void);
/*
* Interpolated linear frequency -> frequency factor lookup.
*
* 'pith' is a 16:16 fixed point linear frequency value.
*
* Returns engine pitch as 16:16 fixed point.
*/
static inline int ptab_convert(int pitch)
{
int bfrac;
int btone = pitch >> 16;
if(btone < 0)
return __pitchtab[0];
else if(btone > 127)
return __pitchtab[128];
bfrac = (pitch & 0xffff) >> 8;
return (__pitchtab[btone+1] * bfrac +
__pitchtab[btone] * (256-bfrac)) >> 8;
}
/*
* Interpolated linear frequency -> frequency factor lookup,
* MIDI style.
*
* 'pith' is a MIDI pitch code (0..127; 60 <==> middle C,
* which maps to the original sample rate of the current
* waveform).
*
* 'bend' is a 16:16 fixed point value in the same unit.
* That is, the integer part of bend is added to pitch,
* whereas the fraction part is used for fractional tone
* interpolation.
*
* Returns engine pitch as 16:16 fixed point.
*/
#define ptab_convert_midi(p, b) ptab_convert((b) + ((p)<<16))
#if 0
static inline int ptab_convert_midi(int pitch, int bend)
{
int bfrac;
int btone = pitch + (bend >> 16);
if(btone < 0)
return __pitchtab[0];
else if(btone > 127)
return __pitchtab[128];
bfrac = (bend & 0xffff) >> 8;
return (__pitchtab[btone+1] * bfrac +
__pitchtab[btone] * (256-bfrac)) >> 8;
}
#endif
/*
* Simple semitone resolution lookup - nonchecking.
*/
#define _ptab_lookup(p) (__pitchtab[p])
/*
* ...and with checking.
*/
#define ptab_lookup(p) (__pitchtab ? _ptab_lookup(p) : 65536)
#ifdef __cplusplus
};
#endif
#endif /*_PITCHTAB_H_*/
+292
View File
@@ -0,0 +1,292 @@
/*(LGPL)
---------------------------------------------------------------------------
a_plugin.c - Audio Engine Plugin API
---------------------------------------------------------------------------
* Copyright (C) 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include "a_plugin.h"
#include "a_tools.h"
static int usecount = 0;
#define EMULBUF_FRAMES 256
static int *emulbuf = NULL;
static int init_emulbuf(void)
{
if(!emulbuf)
emulbuf = malloc(EMULBUF_FRAMES * sizeof(int) * 2);
if(!emulbuf)
return -1;
return 0;
}
int audio_plugin_state(audio_plugin_t *p, audio_fxstates_t new_state)
{
int res, s = p->current_state;
/* We don't care about SILENT and RESTING here. */
if(s > FX_STATE_RUNNING)
s = FX_STATE_RUNNING;
/* Nor do we allow forced switches to SILENT or RESTING! */
if(new_state > FX_STATE_RUNNING)
return -1;
while(new_state > s)
{
++s;
res = p->state(p, s);
if(res < 0)
return res;
p->current_state = s;
}
while(new_state < s)
{
--s;
res = p->state(p, s);
if(res < 0)
return res;
p->current_state = s;
switch(new_state)
{
case FX_STATE_CLOSED:
free(p->ctl);
p->ctl = NULL;
--usecount;
if(usecount <= 0)
{
free(emulbuf);
emulbuf = NULL;
usecount = 0;
}
break;
case FX_STATE_OPEN:
case FX_STATE_READY:
case FX_STATE_PAUSED:
case FX_STATE_RUNNING: /* Can't happen. */
case FX_STATE_SILENT: /* Can't happen. */
case FX_STATE_RESTING: /* Can't happen. */
break;
}
}
return 0;
}
int default_state(struct audio_plugin_t *p, audio_fxstates_t new_state)
{
if(new_state > p->current_state)
switch(new_state)
{
case FX_STATE_CLOSED: /* Can't happen. */
case FX_STATE_OPEN:
if(!audio_plugin_alloc_ctls(p, FXC_COUNT))
return -1;
break;
case FX_STATE_READY:
case FX_STATE_PAUSED:
case FX_STATE_RUNNING:
case FX_STATE_SILENT: /* Can't happen. */
case FX_STATE_RESTING: /* Can't happen. */
break;
}
else
switch(new_state)
{
case FX_STATE_CLOSED:
case FX_STATE_OPEN:
case FX_STATE_READY:
case FX_STATE_PAUSED:
case FX_STATE_RUNNING:
case FX_STATE_SILENT:
case FX_STATE_RESTING: /* Can't happen. */
break;
}
return 0;
}
#define SPLIT_BUFFER \
unsigned s, f; \
for(s = 0; f = (unsigned)(frames > EMULBUF_FRAMES ? \
EMULBUF_FRAMES : frames), \
s < frames; s += f)
void default_control(struct audio_plugin_t *p, unsigned ctl, int arg)
{
if((ctl < p->controls) && p->ctl)
p->ctl[ctl] = arg;
}
static void process_with_process_r(struct audio_plugin_t *p,
int *buf, unsigned frames)
{
p->process_r(p, buf, buf, frames);
}
static void process_with_process_m(struct audio_plugin_t *p,
int *buf, unsigned frames)
{
SPLIT_BUFFER
{
s32clear(emulbuf + s, f);
p->process_r(p, buf + s, emulbuf + s, f);
s32copy(emulbuf + s, buf + s, f);
}
}
static void process_r_with_process(struct audio_plugin_t *p,
int *in, int *out, unsigned frames)
{
if(in)
s32copy(in, out, frames);
else
s32clear(out, frames);
p->process(p, out, frames);
}
static void process_r_with_process_m(struct audio_plugin_t *p,
int *in, int *out, unsigned frames)
{
s32clear(out, frames);
p->process_m(p, in, out, frames);
}
static void process_m_with_process(struct audio_plugin_t *p,
int *in, int *out, unsigned frames)
{
SPLIT_BUFFER
{
if(in)
s32copy(in + s, emulbuf + s, f);
else
s32clear(emulbuf + s, f);
p->process(p, emulbuf + s, f);
s32add(emulbuf + s, out + s, f);
}
}
static void process_m_with_process_r(struct audio_plugin_t *p,
int *in, int *out, unsigned frames)
{
SPLIT_BUFFER
{
if(in)
p->process_r(p, in + s, emulbuf + s, f);
else
p->process_r(p, NULL, emulbuf + s, f);
s32add(emulbuf + s, out + s, f);
}
}
int audio_plugin_init(audio_plugin_t *p)
{
int uses_emulbuf = 0;
/*
* This may change, but right now, a plugin cannot
* generate *any* useful form of output without at
* least one process*() callback, so...
*/
if(!p->process && !p->process_r && !p->process_m)
return -1; /* Must have at least one! */
if(!p->state)
p->state = default_state;
if(!p->control)
p->control = default_control;
if(!p->process)
{
if(p->process_r)
p->process = process_with_process_r;
else if(p->process_m)
{
p->process = process_with_process_m;
uses_emulbuf = 1;
}
}
if(!p->process_r)
{
if(p->process)
p->process_r = process_r_with_process;
else if(p->process_m)
p->process_r = process_r_with_process_m;
}
if(!p->process_m)
{
if(p->process)
{
p->process_m = process_m_with_process;
uses_emulbuf = 1;
}
else if(p->process_r)
{
p->process_m = process_m_with_process_r;
uses_emulbuf = 1;
}
}
if(uses_emulbuf)
return init_emulbuf();
return 0;
}
int audio_plugin_open(audio_plugin_t *p, unsigned max_frames, int fs, int quality)
{
int res = audio_plugin_init(p);
if(res < 0)
return res;
res = audio_plugin_state(p, FX_STATE_OPEN);
if(res < 0)
return res;
p->control(p, FXC_MAX_FRAMES, (int)max_frames);
p->control(p, FXC_SAMPLERATE, fs);
p->control(p, FXC_QUALITY, quality);
++usecount;
return 0;
}
void audio_plugin_close(audio_plugin_t *p)
{
audio_plugin_state(p, FX_STATE_CLOSED);
}
/*----------------------------------------------------------
Tools for plugins (NOT for hosts!)
----------------------------------------------------------*/
int *audio_plugin_alloc_ctls(audio_plugin_t *p, unsigned count)
{
p->controls = count;
p->ctl = calloc(p->controls, sizeof(int));
return p->ctl;
}
+386
View File
@@ -0,0 +1,386 @@
/*(LGPL)
---------------------------------------------------------------------------
a_plugin.h - Audio Engine Plugin API
---------------------------------------------------------------------------
* Copyright (C) 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_PLUGIN_H_
#define _A_PLUGIN_H_
#include "a_types.h"
/*
* Plugin States
* Hosts are allowed to inc/dec the FXC_STATE control
* by only 1 unit at a time. If you want to "leap", you
* must work through the the states in between, to allow
* the plugin to do the required actions for each
* transition. The toolkit function audio_plugin_state()
* handles this automatically.
*
* This arrangement makes it easier to implement plugins;
* just check whether the incoming FXC_STATE argument is
* higher or lower than the current value, to select one
* of two switch() statements; one with code for entering
* states, and another for leaving states.
*
* What To Do in the case of a transition from...
* CLOSED to OPEN:
* Create private instance user data, and set
* p->user to point at it.
*
* OPEN to READY:
* Allocate larger buffers, and buffers that
* depend on system parameter settings.
*
* READY to PAUSED:
* Here you're *not* supposed to do anything
* that couldn't be done in "interrupt context"
* or similar, as this change may be made from
* within the actual real time engine core.
* Preferably, any time consuming buffer
* clearing and stuff should be done in the
* OPEN -> READY and PAUSED -> READY
* transitions instead of here.
*
* PAUSED to RUNNING:
* RUNNING to PAUSED:
* In most cases, you should do absolutely
* nothing here. If your plugin cares about the
* world outside, these transitions can be used
* to manage the real world/engine time slip
* when pausing.
*
* PAUSED to READY:
* Now you may kill reverb tails and that sort
* of things.
*
* READY to OPEN:
* Free any system parameter dependent buffers.
* Basically, free everything except what you
* need to store any incoming system parameter
* values.
*
* OPEN to CLOSED:
* Delete any private instance data, and set
* p->user to NULL.
*
* The SILENT and RESTING states:
* This is just a special case of the RUNNING
* state (see below). It's *not* possible to
* force a plugin to this state - plugins may,
* but do not have to, switch between the
* RUNNING and SILENT states by themselves.
*
* What To Expect, and How To Act when in the...
* CLOSED state:
* Well, basically, you don't exist yet! Expect
* someone to switch you into the OPEN state,
* passing a fresh audio_plugin_t with a NULL
* 'user' field for you to fill in.
*
* OPEN state:
* You must accept calls to control() for setting
* system parameters, but you should only store
* the values (or whatever you calculate from
* them) for later use. Other parameter changes
* are not allowed.
*
* READY state:
* If system parameters are changed while in the
* READY state, they are expected to take effect
* immediately. Preferably, work as quickly as
* possible, but assume that no one is stupid
* enough to tell you to reallocate you buffers
* from within a real time thread.
*
* PAUSED state:
* In this state, there will be no calls to
* process() or control(). (After a thread has
* put a plugin in this state, it's safe to
* operate the plugin from another thread, and
* then have that thread change the state back
* to RUNNING when done.)
*
* RUNNING state:
* Same as for the PAUSED state, but of course,
* frequent calls to process() are to be expected.
*
* RUNNING, SILENT and RESTING are the *only*
* states in which process() may be called.
*
* IMPORTANT:
* Note that process*() may get NULL
* input buffer pointers, which is a way
* for the host to say that you have no
* input.
*
* The easiest way to handle that is just grabbing
* the ever present audio_silent_buffer (provided
* by the host) and go on.
*
* The in-place process() call will never get
* NULL input buffer pointers, as it has no
* separate output buffer pointer. This requires
* a special case: the process() callback must
* *always* do it's work, thus effectively
* ignoring the SILENT and RESTING states.
*
* However, the idea is that you should consider
* this as a useful hint! Most DSP algorithms,
* when given silent input, will eventually
* produce silent output. When that point is
* reached, a plugin should spontaneously switch
* to the SILENT state, to tell everyone that
* there will be no output for a while, and thus,
* that there's no point in calling process().
*
* SILENT state:
* Just handle control() calls. process() will
* be called as usual, but you're not required to
* do anything with the output buffers. (Or
* rather, you *shouldn't*, as it would just be
* a waste of CPU cycles.)
*
* As soon as you feel like producing output
* again, switch back to the RUNNING state and
* do so.
*
* If you know that you will not produce output
* again, until you get fresh input, switch to
* the RESTING state instead!
*
* Host designers should note that a plugin in
* the SILENT state *is* actually still running,
* and also that it's most probably *not*
* producing valid output! It's also important
* that output buffer pointers passed to
* process() are valid at all times, as a SILENT
* plugin may switch to the RUNNING state at any
* time, for no obvious reason whatsoever. Input
* is *not* required.)
*
* That is, it will need process() to be called
* as usual, *even* if only to pass NULL input
* buffers. This is to allow plugins to keep
* track of time, and of course, to detect any
* input that might - instantaneously or after
* some time - make the plugin switch back to
* RUNNING mode.
*
* RESTING state:
* Do nothing (except the usual handling of
* control() calls) until you start getting
* input buffers again. You'r *not* allowed to
* switch from this state unless you get
* non-NULL input buffer pointers.
*
* This state exists to tell the host that a
* plugin is *really* done with any tails and
* stuff, and that there will be no more output
* without fresh input buffers. Hosts may use
* this to figure out when to stop recording
* and things like that.
*
* IMPORTANT: You should *NEVER* return from
* process() in the SILENT or RESTING
* states unless the buffer you just
* generated (or should have generated)
* actually *is* garbage, or silent!
*
* ANY OUTPUT WILL BE IGNORED.
*
* General Rules for Hosts:
* * System Parameter changes must *never* be done from
* real time context, period.
*
* * Plugins should *not* be expected to be thread safe.
* That is, never use a plugin from more than one thread
* at a time, without making sure only one callback is
* running at any time.
*/
typedef enum
{
FX_STATE_CLOSED = 0,
FX_STATE_OPEN,
FX_STATE_READY,
FX_STATE_PAUSED,
FX_STATE_RUNNING,
FX_STATE_SILENT,
FX_STATE_RESTING
} audio_fxstates_t;
typedef enum
{
/*
* System Parameters.
* Will be set before the state is changed.
*/
FXC_MAX_FRAMES = 0, /* ...per process() call. */
FXC_SAMPLERATE, /* Buffer sample rate (Hz) */
FXC_QUALITY, /* Audio quality setting */
/* Uniform Parameters */
FXC_PARAM_1, /* (fixp) Time/f/Amount 1 */
FXC_PARAM_2, /* (fixp) Time/f/Amount 2 */
FXC_PARAM_3, /* (fixp) Depth/Level 1 */
FXC_PARAM_4, /* (fixp) Depth/Level 2 */
FXC_PARAM_5, /* (fixp) Rate/Level */
FXC_PARAM_6, /* (int) Mode/Special */
FXC_USER, /* First user defined control */
FXC_COUNT = FXC_USER
/* Higher indices are free to use for custom controls. */
} audio_fxcontrols_t;
#ifdef TODO
typedef enum
{
/* Timing and dependencies (flags) */
FXCAP_TIMING_ = 0x0000000f,
FXCAP_TIMING_SYSCALLS = 0x00000001, /* Uses malloc() etc... */
FXCAP_TIMING_SLOW = 0x00000002, /* in relation to process() */
/* Data type (enumeration) */
FXCAP_TYPE_ = 0x000000f0,
FXCAP_TYPE_STROBE = 0x00000000, /* Event; value ignored */
FXCAP_TYPE_BOOLEAN = 0x00000010, /* 0 = false, !=0 = true */
FXCAP_TYPE_INTEGER = 0x00000020,
FXCAP_TYPE_FIXED_8 = 0x00000030, /* 8 fraction bits */
FXCAP_TYPE_FIXED_16 = 0x00000040, /* 16 fraction bits */
FXCAP_TYPE_FIXED_24 = 0x00000050, /* 24 fraction bits */
FXCAP_TYPE_FLOAT = 0x00000060, /* IEEE 32 bit float */
/* Access rules (flags) */
FXCAP_AXS_ = 0x00000f00,
FXCAP_AXS_READ = 0x00000100, /* May be read */
FXCAP_AXS_WRITE = 0x00000200, /* May be written */
FXCAP_AXS_READ_CALL = 0x00000400, /* Must use read_control()! */
FXCAP_AXS_WRITE_CALL = 0x00000800, /* Must use control()! */
/* Lifetime info - "When can I access this?" (flags)
* Note that there's no flag for PAUSED, as it's *never* legal
* to use any callback but state() in the PAUSED state.
*/
FXCAP_LIFE_ = 0x0000f000,
FXCAP_LIFE_OPEN = 0x00001000,
FXCAP_LIFE_READY = 0x00002000,
FXCAP_LIFE_RUNNING = 0x00004000
} audio_fxcaps_t;
#endif
typedef struct audio_plugin_t
{
void *user; /* Custom instance data */
int current_state; /* DO NOT write in callbacks! */
unsigned controls; /* Number of controls */
int *ctl; /* Array of parameter values */
#ifdef TODO
audio_fxcaps_t *control_caps;
#endif
/* Plugin state management */
int (*state)(struct audio_plugin_t *p, audio_fxstates_t new_state);
/* Parameter Control */
void (*control)(struct audio_plugin_t *p, unsigned ctl, int arg);
/* Running the plugin */
void (*process)(struct audio_plugin_t *p,
int *buf, unsigned frames);
void (*process_r)(struct audio_plugin_t *p,
int *in, int *out, unsigned frames);
void (*process_m)(struct audio_plugin_t *p,
int *in, int *out, unsigned frames);
} audio_plugin_t;
/*----------------------------------------------------------
Handy wrappers & tools
----------------------------------------------------------*/
/*
* Bring a plugin to the specified state.
*
* If anything goes wrong, this call stops and returns
* the error code from the plugin's state() callback.
*/
int audio_plugin_state(audio_plugin_t *p, audio_fxstates_t new_state);
/*
* Fill in missing callbacks with standard wrappers where
* possible.
*
* IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT!
* Note that these wrappers are *not* thread safe
* as of this version! Some of them use a single
* global mixing buffer when emulating missing
* process callback variants. (The alternative
* would be to use one buffer per plugin instance,
* which would result in more cache thrashing.)
*
* Returns a negative error code if it isn't possible to
* turn the passed struct into a valid plugin.
*/
int audio_plugin_init(audio_plugin_t *p);
/*
* Initialize a plugin (audio_plugin_init()), bring it
* plugin into the OPEN state and set up the system
* parameters.
*
* Returns any error code from the plugin.
*/
int audio_plugin_open(audio_plugin_t *p, unsigned max_frames,
int fs, int quality);
/*
* Bring a plugin to the CLOSED state.
*/
void audio_plugin_close(audio_plugin_t *p);
/*----------------------------------------------------------
Tools for plugins (NOT for hosts!)
----------------------------------------------------------*/
/*
* Set number of controls and allocate a ctl array with
* sufficient space for them.
*
* Note that freeing the array is done by the host. (Using
* audio_plugin_close() or audio_plugin_state() to close a
* plugin does that automatically, so only hosts that
* implement state changing "to the metal" have to worry
* about this.)
*
* Returns the address of the allocated ctl array.
*/
int *audio_plugin_alloc_ctls(audio_plugin_t *p, unsigned count);
/*----------------------------------------------------------
Host Provided Services
----------------------------------------------------------*/
/* The Silent Buffer */
extern int *audio_silent_buffer;
#endif /*_A_PLUGIN_H_*/
+116
View File
@@ -0,0 +1,116 @@
/*(LGPL)
---------------------------------------------------------------------------
a_sequencer.c - MIDI Sequencer
---------------------------------------------------------------------------
* Copyright (C) 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "a_globals.h"
#include "a_struct.h"
#include "a_sequencer.h"
#include "a_midisock.h"
/*
* Temporary MIDI playback hack.
*
* Only one sequence at a time, and it uses the
* same midisock as the external MIDI input.
*/
midi_player_t *midiplayer = 0;
static float fs = 44100;
int sequencer_open(midisock_t *ms, float framerate)
{
aev_client("sequencer_open()");
fs = framerate;
#if 1
midiplayer = mp_open(ms);
#else
midiplayer = mp_open(&monitor_midisock);
#endif
return (midiplayer != 0);
}
void sequencer_close(void)
{
aev_client("sequencer_close()");
if(!midiplayer)
return;
mp_close(midiplayer);
midiplayer = 0;
}
int sequencer_play(midi_file_t *mf, int tag, int pitch, int volume)
{
aev_client("sequencer_play()");
if(!midiplayer)
return -1;
midiplayer->pitch = pitch - (60 << 16);
mp_select(midiplayer, mf);
return 0;
}
void sequencer_stop(int tag)
{
aev_client("sequencer_stop()");
if(!midiplayer)
return;
mp_stop(midiplayer);
}
int sequencer_playing(int tag)
{
if(!midiplayer)
return 0;
return (midiplayer->mf != 0);
}
void sequencer_process(unsigned frames)
{
int i;
aev_client("sequencer_process()");
if(!midiplayer)
return;
if(mp_play(midiplayer, (float)frames / fs))
return;
for(i = 0; i < AUDIO_MAX_CHANNELS; ++i)
{
int wave;
if(!channeltab[i].playing)
continue;
wave = patchtab[channeltab[i].ctl[ACC_PATCH]].param[APP_WAVE];
if((wave < 0) || (wave >= AUDIO_MAX_WAVES))
continue;
if(AF_MIDI != wavetab[wave].format)
continue;
channeltab[i].playing = 0;
}
}
+51
View File
@@ -0,0 +1,51 @@
/*(LGPL)
---------------------------------------------------------------------------
a_sequencer.h - MIDI Sequencer
---------------------------------------------------------------------------
* Copyright (C) 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_SEQUENCER_H_
#define _A_SEQUENCER_H_
#include "a_midisock.h"
#include "a_midifile.h"
int sequencer_open(midisock_t *ms, float framerate);
void sequencer_close(void);
/*
* Starts playing 'mf' at 'pitch' (0 ==> normal) and 'volume'.
* The sequencer context is marked with the positive value 'tag'
* for later reference. 'pitch' and 'volume' are 16:16 fixed
* point values, to match the engine control data type.
* Returns a negative value if an error occurred.
*/
int sequencer_play(midi_file_t *mf, int tag, int pitch, int volume);
/*
* Stop the sequence marked with 'tag'.
* Pass -1 for 'tag' to stop all playing sequences.
*/
void sequencer_stop(int tag);
/* Returns 1 if the sequence marked 'tag' is still playing. */
int sequencer_playing(int tag);
void sequencer_process(unsigned frames);
#endif /*_A_SEQUENCER_H_*/
+36
View File
@@ -0,0 +1,36 @@
/*(LGPL)
---------------------------------------------------------------------------
a_struct.c - Audio Engine internal structures
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <string.h>
#include "a_globals.h"
#include "a_struct.h"
#include "a_control.h"
/*----------------------------------------------------------
Engine Global Data
----------------------------------------------------------*/
audio_wave_t wavetab[AUDIO_MAX_WAVES];
audio_patch_t patchtab[AUDIO_MAX_PATCHES];
audio_voice_t voicetab[AUDIO_MAX_VOICES];
audio_group_t grouptab[AUDIO_MAX_GROUPS];
audio_channel_t channeltab[AUDIO_MAX_CHANNELS];
audio_bus_t bustab[AUDIO_MAX_BUSSES];
+43
View File
@@ -0,0 +1,43 @@
/*(LGPL)
---------------------------------------------------------------------------
a_struct.h - Audio Engine internal structures
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_STRUCT_H_
#define _A_STRUCT_H_
#include "a_wave.h"
#include "a_patch.h"
#include "a_channel.h"
#include "a_voice.h"
#include "a_bus.h"
/*----------------------------------------------------------
Engine Global Data
----------------------------------------------------------*/
extern audio_wave_t wavetab[AUDIO_MAX_WAVES];
extern audio_patch_t patchtab[AUDIO_MAX_PATCHES];
extern audio_group_t grouptab[AUDIO_MAX_GROUPS];
extern audio_channel_t channeltab[AUDIO_MAX_CHANNELS];
extern audio_voice_t voicetab[AUDIO_MAX_VOICES];
extern audio_bus_t bustab[AUDIO_MAX_BUSSES];
void _audio_init(void);
#endif /*_A_STRUCT_H_*/
+266
View File
@@ -0,0 +1,266 @@
/*(LGPL)
---------------------------------------------------------------------------
a_tools.h - Various useful audio macros and inlines
---------------------------------------------------------------------------
* Copyright (C) 2001 - 2003, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_TOOLS_H_
#define _A_TOOLS_H_
#include "glSDL.h"
#include <string.h>
#include "kobolog.h"
#include "a_types.h"
/*----------------------------------------------------------
Integer Arithmetics
----------------------------------------------------------*/
/*
* This operation can be implemented very nicely in ML
* on virtually any CPU - but this is C... *heh*
*/
#ifdef A_USE_INT64
/* Ok, but suboptimal - unless the compiler is sufficiently smart. */
#define fixmul(a, b) ((int)((Sint64)(a) * (Sint64)(b) >> 16))
#else
/* Slooooow, but portable... :-/ */
#define fixmul(a, b) ((int)((float)(a) * (float)(b) * (1.0/65536.0)))
#endif
/*
* Given an integer value 'value', and
*
* x = y * value
*
* find the 's' that best approximates that function using
* the (on practically any CPU) faster function
*
* x = y << s
*
* The approximated x will always be equal to, or smaller
* than the exact x.
*/
static inline int int2shift(int value)
{
int res;
if(value >> 16)
res = 16;
else
res = 0;
if(value >> (res + 8))
res += 8;
if(value >> (res + 4))
res += 4;
if(value >> (res + 2))
res += 2;
if(value >> (res + 1))
res += 1;
return res;
}
/*
* Given a 16:16 fixp value 'value', and
*
* x = y * value
*
* find the 's' that best approximates that function using
* the (on practically any CPU) faster function
*
* x = y >> s
*
* The approximated x will always be equal to, or smaller
* than the exact x.
*/
static inline int fixp2shift(int value)
{
int res;
if(value >> 16)
res = 16;
else
res = 0;
if(value >> (res + 8))
res += 8;
if(value >> (res + 4))
res += 4;
if(value >> (res + 2))
res += 2;
if(value >> (res + 1))
res += 1;
return 16 - res;
}
/*
* Like fixp2shift(), but here, 'value' is clamped to the
* [4, 1/64] range (8 bits of dynamic range - hence the
* suffix) for speed.
*/
static inline int fixp2shift8(int value)
{
int res;
value >>= 10;
if(value >> 4)
res = 4;
else
res = 0;
if(value >> (res + 2))
res += 2;
if(value >> (res + 1))
res += 1;
return 6 - res;
}
/*----------------------------------------------------------
Basic Audio Processing
------------------------------------------------------------
* WARNING:
* Most of these macros and inlines will crash if
* told to process 0 samples! In DEBUG mode, this is
* checked and will cause error messages to be printed.
*
* Naming conventions:
* m16 16 bit mono
* s16 16 bit stereo
* m32 32 bit mono
* s32 32 bit stereo
* All samples are expected to be signed integers, wherever
* sample format matters.
*/
/* Clear 'frames' samples starting at 'buf'. */
#define m16clear(buf, frames) memset((buf), 0, (frames) << 1)
#define s16clear(buf, frames) memset((buf), 0, (frames) << 2)
#define m32clear(buf, frames) memset((buf), 0, (frames) << 2)
#define s32clear(buf, frames) memset((buf), 0, (frames) << 3)
/* Copy 'frames' samples from 'in' to 'out'. */
#define m16copy(in, out, frames) memcpy((out), (in), (frames) << 1)
#define s16copy(in, out, frames) memcpy((out), (in), (frames) << 2)
#define m32copy(in, out, frames) memcpy((out), (in), (frames) << 2)
#define s32copy(in, out, frames) memcpy((out), (in), (frames) << 3)
/* Add the contents of 'from' into buffer 'to' */
#define m16add(from, to, frames) _add16(from, to, frames)
#define s16add(from, to, frames) _add16(from, to, (frames)<<1)
static inline void _add16(Sint16 *from, Sint16 *to, unsigned samples)
{
unsigned s;
for(s = samples & 3; s; --s)
*to++ += *from++;
samples >>= 2;
s = 0;
while(samples--)
{
to[s] += from[s];
to[s+1] += from[s+1];
to[s+2] += from[s+2];
to[s+3] += from[s+3];
s += 4;
}
}
#define m32add(from, to, frames) _add32(from, to, frames)
#define s32add(from, to, frames) _add32(from, to, (frames)<<1)
static inline void _add32(Sint32 *from, Sint32 *to, unsigned samples)
{
unsigned s = -((4 - samples) & 3);
#ifdef DEBUG
if(!samples)
{
log_printf(ELOG, "a_tools.h: _add32(); zero samples!\n");
return;
}
#endif
switch(samples & 3) do
{
case 0:
to[s] += from[s];
case 3:
to[s+1] += from[s+1];
case 2:
to[s+2] += from[s+2];
case 1:
to[s+3] += from[s+3];
s += 4;
} while(s < samples);
}
/*
* Returns 1 if mixing was done, 0 if the volume
* is too low for actual output to be produced.
*/
#define m32mix(from, to, vol, frames) _mix32(from, to, vol, frames)
#define s32mix(from, to, vol, frames) _mix32(from, to, vol, (frames)<<1)
static inline int _mix32(Sint32 *from, Sint32 *to, int vol, unsigned samples)
{
unsigned s = -((4 - samples) & 3);
#ifdef DEBUG
if(!samples)
{
log_printf(ELOG, "a_tools.h: _mix32(); zero samples!\n");
return 0;
}
#endif
vol >>= 8;
if(!vol)
return 0;
switch(samples & 3) do
{
case 0:
to[s] += from[s] * vol >> 8;
case 3:
to[s+1] += from[s+1] * vol >> 8;
case 2:
to[s+2] += from[s+2] * vol >> 8;
case 1:
to[s+3] += from[s+3] * vol >> 8;
s += 4;
} while(s < samples);
return 1;
}
#define m32fastmix(f, t, vol, frames) _fastmix32(f, t, vol, frames)
#define s32fastmix(f, t, vol, frames) _fastmix32(f, t, vol, (frames)<<1)
static inline void _fastmix32(Sint32 *from, Sint32 *to, int vol, unsigned samples)
{
unsigned s = -((4 - samples) & 3);
#ifdef DEBUG
if(!samples)
{
log_printf(ELOG, "a_tools.h: _fastmix32(); zero samples!\n");
return;
}
#endif
vol = fixp2shift(vol);
switch(samples & 3) do
{
case 0:
to[s] += from[s] >> vol;
case 3:
to[s+1] += from[s+1] >> vol;
case 2:
to[s+2] += from[s+2] >> vol;
case 1:
to[s+3] += from[s+3] >> vol;
s += 4;
} while(s < samples);
}
#endif /*_A_TOOLS_H_*/
+444
View File
@@ -0,0 +1,444 @@
/*(LGPL)
---------------------------------------------------------------------------
a_types.h - Commonly used types in the engine
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_TYPES_H_
#define _A_TYPES_H_
#include "glSDL.h"
/* Builtin patch drivers */
typedef enum
{
PD_NONE = -1, /* No driver! */
PD_MONO = 0, /* Basic monophonic */
PD_POLY, /* Basic polyphonic */
PD_MIDI, /* Basic MIDI player */
PD_EEL /* User EEL code driver */
} patch_drivers_t;
/* Voice tag special codes */
typedef enum
{
AVT_FUTURE = -2,
AVT_ALL = -1
} audio_voice_tags_t;
/*
* DAHDSR envelope generator parameters:
*
* Level
* ^
* | __L1__
* | /| |\
* | / | | \
* |__L0__/ | | \__L2__
* | | | | | |\
* | D | A | H | D | S |R\
* |_____|___|____|___|____|__\____\
* | | | | | | | / Time
* DELAY T1 HOLD T2 T3 T4
*
* State by state description:
*
* D: Set to L0, and hold for DELAY seconds.
* A: Ramp to L1 over T1 seconds.
* H: Hold at L1 for HOLD seconds.
* D: Ramp to L2 over T2 seconds.
* S: if T3 < 0
* Hold at L2 until CE_STOP is received.
* else
* Hold at L2 for T3 seconds.
* R: Ramp to 0 over T3 seconds.
*
* If T3 is set to a negative value, sustain lasts until
* a CE_STOP event is received. If T3 is 0 of higher,
* sustain lasts for T3 seconds.
*
* The EG will *always* load L0, but from then on, if a
* phase has a zero duration, that phase is skipped.
* Note that skipping any of the A, D and R phases will
* produce clicks, unless the adjacent levels are
* identical.
*
* In some cases, it's desirable that the EG immediately
* skips to the release phase when a CE_STOP event is
* received, whereas in other cases, this would result
* in problems with very short notes. Therefore, there is
* a boolean control 'ENV_SKIP' that make it possible to
* specify which behavior is desired.
* '1' will make the EG skip to release upon receiving
* CE_STOP. '0' will result in the EG taking no action
* on CE_STOP until the sustain phase is reached the
* normal way.
* 'ENV_SKIP' has no effect if "timed sustain" is used;
* T3 must be < 0, or CE_STOP is entirely ignored.
*
TODO:
* Turn this into a generic EG, with "unlimited"
number of phases. The current way of marking
the sustain hold point would still work,
although one would have to replace the ramp/
wait mechanism with a test for change in sc.
* Implement "quadratic taper" or similar non-
linear transform of the output. The EG will
have to generate a reasonably dense stream
of voice control events, since voice ramps
are still linear.
*/
/* Audio Patch Parameters */
typedef enum
{
APP_FIRST = 0,
/* Patch Driver Plugin */
APP_DRIVER = 0, /* (int) Patch driver index */
/* Oscillator */
APP_WAVE, /* (int) Waveform index */
/* "Special" features */
APP_RANDPITCH, /* (fixp) Random pitch depth */
APP_RANDVOL, /* (fixp) Random volume depth */
/* Envelope Generator */
APP_ENV_SKIP, /* (bool) skip to sustain on CE_STOP */
APP_ENV_L0, /* (fixp) envelope level 0 (initial) */
APP_ENV_DELAY, /* (fixp) envelope delay-to-attack */
APP_ENV_T1, /* (fixp) envelope attack time */
APP_ENV_L1, /* (fixp) envelope hold level */
APP_ENV_HOLD, /* (fixp) envelope hold time */
APP_ENV_T2, /* (fixp) envelope decay time */
APP_ENV_L2, /* (fixp) envelope sustain level */
APP_ENV_T3, /* (fixp) envelope sustain time */
APP_ENV_T4, /* (fixp) envelope release time */
/* LFO Generator */
APP_LFO_DELAY, /* (fixp) LFO delay-to-start */
APP_LFO_ATTACK, /* (fixp) LFO attack time-to-full */
APP_LFO_DECAY, /* (fixp) LFO decay time-to-zero */
APP_LFO_RATE, /* (fixp) LFO frequency */
APP_LFO_SHAPE, /* (int) LFO modulation waveform */
/* Modulation Matrix */
APP_ENV2PITCH, /* (fixp) */
APP_ENV2VOL, /* (fixp) */
APP_LFO2PITCH, /* (fixp) */
APP_LFO2VOL, /* (fixp) */
#if 0
APP_ENV2CUT, /* (fixp) */
APP_LFO2CUT, /* (fixp) */
TODO:
/*
* Controls for the resonant filter
* (Setting CUT to 0 disables the filter.)
*/
APP_FILT_CUT, /* (fixp) Filter cutoff */
APP_FILT_RES, /* (fixp) Filter resonance */
/*
* Distortion control
*/
APP_DIST_GAIN, /* (fixp) Pre-limiter gain */
APP_DIST_CLIP, /* (fixp) Limiter clip threshold */
APP_DIST_DRIVE, /* (fixp) Feedback from the clipped peaks */
#endif
APP_COUNT,
APP_LAST = APP_COUNT - 1
} audio_pparams_t;
typedef int appbank_t[APP_COUNT];
/*
* The voice structure:
* OSC -> VOL/PAN/SEND
* | |
* | : '-> o-> FX BUS 0
* '-----> o-> FX BUS 1
* : : ... ...
* : : o-> FX BUS N-1
*
* That's it! No filters, and just one oscillator. The
* interesting part is the output section.
*
* Every voice has two output bus selectors, each on
* referencing a stereo bus, on which further
* processing may be applied. By setting either bus
* selector to -1, or by pointing both at the same bus,
* only the Primary Output will be used, and some some
* CPU power is saved during mixing. :-)
*
* As opposed to most MIDI synths, this engine allows
* you to select one or two output busses for each
* voice, using one as the Primary Output (level set
* by CC7: Volume) and the other as the Secondary
* Output (level set by CC91: Reverb Depth, multiplied
* by CC7.)
*
* The MIDI implementation uses CC92 and CC94 to select
* Primary and Secondary Output busses, respectively.
*/
/*
* Audio Channel & Group Controls
FIXME: Channels use all, groups use a subset - which sucks!
FIXME: I've considered using the OpenGL approach with a single,
FIXME: global enum - but that can only provide runtime "enum
FIXME: type checking". Seems like only languages other than the
FIXME: native language of this engine can do it right...
FIXME: (Another reason to use EEL wherever possible.)
*
* The Priority System:
* Set ACC_PRIORITY to 0 to have a channel grab a
* voice, and have it excluded from the dynamic
* voice allocation system.
*
* Other values specify channel priority for voice
* stealing - higher values mean lower priority.
* Higher priority channels win when fighting for
* voices. Priority 0 voices cannot be stolen.
*/
typedef enum
{
ACC_FIRST = 0,
/*--- Controls that are NOT transformed ---*/
ACC_GROUP = 0, /* mixer group */
ACC_PRIORITY, /* Voice Allocation Priority */
ACC_PATCH, /* (int) Patch index */
ACC_PRIM_BUS, /* (int) target bus for ACC_VOLUME */
ACC_SEND_BUS, /* (int) target bus for ACC_SEND */
/*--- Controls that are transformed by adding ---*/
ACC_PAN, /* (fixp[-1, 1]) pre-send pan */
ACC_PITCH, /* (fixp) linear frequency; int <==> MIDI */
/*--- Controls that are transformed by multiplying ---*/
ACC_VOLUME, /* (fixp) primary "send" level */
ACC_SEND, /* (fixp) secondary send level */
/*--- Patch Controls (Not transformed) ---*/
ACC_MOD1, /* (fixp) Modulation 1 depth */
ACC_MOD2, /* (fixp) Modulation 2 depth */
ACC_MOD3, /* (fixp) Modulation 3 depth */
ACC_X, /* (fixp) X position */
ACC_Y, /* (fixp) Y position */
ACC_Z, /* (fixp) Z position */
ACC_COUNT,
ACC_LAST = ACC_COUNT - 1
} audio_ctls_t;
typedef int accbank_t[ACC_COUNT];
/*
* Bus Control:
* The first 8 MIDI channels can be used for controlling
* the signal processing done between the busses, and
* between the busses and the output buffer.
*
* A set of non-standard MIDI CCs are used for this:
* CC 39: Select Insert Slot for subsequent CCs.
*
* Slot 0 is used to access the dry/pre insert send level
* controls. Thus, CCs 40..45 are ignored for slot 0.
*
* The FX parameter CC range is mapped to according to
* [0, 128] ==> [0.0, 2.0]. (That is, you can't reach the
* exact value of 2.0, as the highest CC value is 127.)
* CC 40: Insert Effect Type
* CC 41: Insert Effect Parameter 1 (Time 1)
* CC 42: Insert Effect Parameter 2 (Time 2)
* CC 43: Insert Effect Parameter 3 (Depth 1)
* CC 44: Insert Effect Parameter 4 (Depth 2)
* CC 45: Insert Effect Parameter 5 (Rate)
*
* This one is not transformed at all - MIDI CCs 0..127
* are used directly. Obviously, it's preferred that
* plugins map integer parameters that fit in that range
* to Parameter 6.
* CC 46: Insert Effect Parameter 6 (Mode/Special)
*
* The following CC doubles as "dry send to master", when
* used on slot 0.
* CC 47: Post insert send to master ("Wet")
*
* The next set of CC are IMHO, a bit dodgy in the current
* implementation. Indeed, they work, but you have to
* accept that the engine processes the busses in a fixed
* order, meaning that you can only send from lower number
* busses to higher number busses - not the other way
* around. (In fact, the engine won't even consider the
* controls for the "broken" connections when mixing.)
*
* Note that when used on slot 0, these act as *dry* send
* levels, as there cannot be an insert in slot 0.
* CC 48: Post insert send to bus 1
* CC 49: Post insert send to bus 2
* CC 50: Post insert send to bus 3
* CC 51: Post insert send to bus 4
* CC 52: Post insert send to bus 5
* CC 53: Post insert send to bus 6
* CC 54: Post insert send to bus 7
* CC 55: Post insert send to bus 8
*
* Note that the post insert sends won't be considered
* if no effect is enabled. (They would just double the
* functionality of the pre insert sends, for extra cost
* and no use.)
*/
/* Audio Bus Controls */
typedef enum
{
ABC_FIRST = 0,
/* NOTE: The ABC_FX_* controls of slot 0 are ignored! */
ABC_FX_TYPE = 0, /* (int) */
ABC_FX_PARAM_1, /* (fixp) Time/f/Amount 1 */
ABC_FX_PARAM_2, /* (fixp) Time/f/Amount 2 */
ABC_FX_PARAM_3, /* (fixp) Depth/Level 1 */
ABC_FX_PARAM_4, /* (fixp) Depth/Level 2 */
ABC_FX_PARAM_5, /* (fixp) Rate/Level */
ABC_FX_PARAM_6, /* (int) Mode/Special */
ABC_SEND_MASTER, /* (fixp) Post FX send to master */
ABC_SEND_BUS_0, /* (fixp) Post FX send to bus 0 */
ABC_SEND_BUS_1, /* (fixp) Post FX send to bus 1 */
ABC_SEND_BUS_2, /* (fixp) Post FX send to bus 2 */
ABC_SEND_BUS_3, /* (fixp) Post FX send to bus 3 */
ABC_SEND_BUS_4, /* (fixp) Post FX send to bus 4 */
ABC_SEND_BUS_5, /* (fixp) Post FX send to bus 5 */
ABC_SEND_BUS_6, /* (fixp) Post FX send to bus 6 */
ABC_SEND_BUS_7, /* (fixp) Post FX send to bus 7 */
ABC_COUNT,
ABC_LAST = ABC_COUNT -1
} audio_busctls_t;
typedef int abcbank_t[ABC_COUNT];
/*
* Generic Effect API - for busses and inserts.
*/
typedef enum
{
AFX_NONE = 0,
AFX_DELAY, /* 1: Tap 1 2: Tap 2
* 3: Gain 1 4: Gain 2
* 5: Feedback Level
*/
AFX_ECHO, /* 1: Ldelay 2: Rdelay
* 3: Lgain 4: Rgain
* 5: Feedback Delay
*/
AFX_REVERB, /* 1: Early reflections duration
* 2: Tail duration
* 3: Early reflections level
* 4: Tail Feedback Level
* 5: Feedback LP Cutoff
*/
AFX_CHORUS, /* 1: Left Vibrato Speed
* 2: Right Vibrato Speed
* 3: Left Vibrato Depth
* 4: Right Vibrato Depth
* 5: Feedback Level
*/
AFX_FLANGER, /* Same as chorus; only different scale. */
AFX_FILTER, /* 1: Left Cut 2: Right Cut
* 3: Left Reso 4: Right Resonance
* 5: <unused>
*/
AFX_DIST, /* 1: Bass 2: Treble
* 3: Pre Gain 4: Post Gain
* 4: Drive
*/
AFX_WAH, /* 1: Low Limit 2: High Limit
* 3: Sensitivity 4: Gain
* 5: Resonance
*/
AFX_EQ, /* 1: Bass f 2: Treble f
* 3: Bass Gain 4: Treble Gain
* 4: Mid Gain
*/
AFX_ENHANCER, /* 1: Mid f 2: High f
* 3: Mid Sens. 4: High Sensitivity
* 5: Artificial Treble Strength
*/
AFX_COMPRESSOR, /* 1: Attack 2: Release
* 3: Gain 4: Threshold
* 5: Knee Softness
*/
AFX_MAXIMIZER /* 1: Low Split 2: High Split
* 3: Low Boost 4: High Boost
* 5: Mid Boost
*/
} audio_fxtypes_t;
typedef enum
{
AR_WORST = -3,
AR_MEDIUM = -2,
AR_BEST = -1,
AR_NEAREST = 0, /* Nearest sample ("useless") */
AR_NEAREST_4X, /* Nearest with 4x oversampling */
AR_LINEAR, /* Linear Interpolation */
AR_LINEAR_2X_R, /* LI w/ 2x oversampling + ramp smoothing */
AR_LINEAR_4X_R, /* LI w/ 4x oversampling + ramp smoothing */
AR_LINEAR_8X_R, /* LI w/ 8x oversampling + ramp smoothing */
AR_LINEAR_16X_R,/* LI w/ 16x oversampling + ramp smoothing */
AR_CUBIC_R /* Cubic interpolation + ramp smoothing */
} audio_resample_t;
typedef enum
{
AF_MONO8 = 0, /* Mono Sint8 */
AF_STEREO8, /* Stereo Sint8 */
AF_MONO16, /* Mono Sint16 */
AF_STEREO16, /* Stereo Sint16 */
AF_MONO32, /* Mono float32 */
AF_STEREO32, /* Stereo float32 */
AF_MIDI /* Midi sequence */
} audio_formats_t;
typedef enum
{
AQ_VERY_LOW, /* Nearest, no oversampling */
AQ_LOW, /* Nearest, 4x oversampling */
AQ_NORMAL, /* Linear interpolation */
AQ_HIGH, /* LI with adaptive oversampling 2x..8x */
AQ_VERY_HIGH /* LI with fixed 16x oversampling */
} audio_quality_t;
#undef A_USE_INT64
#ifdef SDL_HAS_64BIT_TYPE
# define A_USE_INT64
#endif
#endif /*_A_TYPES_H_*/
+711
View File
@@ -0,0 +1,711 @@
/*(LGPL)
---------------------------------------------------------------------------
a_voice.c - Audio Engine low level mixer voices
---------------------------------------------------------------------------
* Copyright (C) 2001-2003, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "kobolog.h"
#include "a_voice.h"
#include "a_struct.h"
#include "a_globals.h"
#include "a_tools.h"
#include "a_control.h"
#define LDBG(x)
#define EVDBG(x)
#define CHECKPOINTS
/* Random number generator state for randtrig etc */
static int rnd = 16576;
#define UPDATE_RND rnd *= 1566083941UL; rnd++; rnd &= 0x7fffffffUL;
/* Last allocated voice (good starting point!) */
static int last_voice = 0;
void voice_kill(audio_voice_t *v)
{
v->vu = 65535; /* Newly allocated voices are harder to steal */
aev_flush(&v->port);
if(v->channel)
{
--v->channel->playing;
chan_unlink_voice(v);
}
v->state = VS_STOPPED;
}
int voice_alloc(audio_channel_t *c)
{
int lv = 0;
int i, pri, v, vol;
/* Pass 1: Look for an unused voice. */
for(v = 0; v < AUDIO_MAX_VOICES; ++v)
{
if(voicetab[v].state != VS_STOPPED)
continue; /* Not interesting here... */
last_voice = v;
chan_link_voice(c, &voicetab[v]);
voicetab[v].priority = c->ctl[ACC_PRIORITY];
voicetab[v].state = VS_RESERVED;
return v;
}
/*
* Pass 2: Look for the most silent voice with
* same or lower priority.
*/
lv = last_voice;
vol = 2000000000;
v = -1;
for(i = 0; i < AUDIO_MAX_VOICES; ++i)
{
int vu;
if(++lv >= AUDIO_MAX_VOICES)
lv = 0;
if(voicetab[lv].priority < c->ctl[ACC_PRIORITY])
continue;
#ifdef AUDIO_USE_VU
vu = voicetab[lv].vu;
vu *= (voicetab[lv].ic[VIC_LVOL].v +
voicetab[lv].ic[VIC_RVOL].v +
voicetab[lv].ic[VIC_LSEND].v +
voicetab[lv].ic[VIC_RSEND].v) >> (RAMP_BITS + 2);
vu >>= VOL_BITS;
#else
vu = (voicetab[lv].ic[VIC_LVOL].v +
voicetab[lv].ic[VIC_RVOL].v +
voicetab[lv].ic[VIC_LSEND].v +
voicetab[lv].ic[VIC_RSEND].v) >> (RAMP_BITS + 2);
#endif
if(vu < vol)
{
vol = vu;
v = lv;
}
}
if(v >= 0)
{
voice_kill(&voicetab[v]);
chan_link_voice(c, &voicetab[v]);
last_voice = v;
voicetab[v].priority = c->ctl[ACC_PRIORITY];
voicetab[v].state = VS_RESERVED;
return v;
}
/* Pass 3: Grab voice with lowest priority. */
lv = last_voice;
pri = c->ctl[ACC_PRIORITY];
v = -1;
for(i = 0; i < AUDIO_MAX_VOICES; ++i)
{
if(++lv >= AUDIO_MAX_VOICES)
lv = 0;
if(voicetab[lv].priority > pri)
{
pri = voicetab[lv].priority;
v = lv;
}
}
if(v >= 0)
{
voice_kill(&voicetab[v]);
chan_link_voice(c, &voicetab[v]);
last_voice = v;
voicetab[v].priority = c->ctl[ACC_PRIORITY];
voicetab[v].state = VS_RESERVED;
return v;
}
return -1;
}
void voice_start(audio_voice_t *v, int wid)
{
int retrig, randtrig;
v->wave = v->c[VC_WAVE] = wid;
v->c[VC_LOOP] = wavetab[wid].looped;
v->position = 0;
v->position_frac = 0;
if(!wavetab[wid].data.si8)
{
voice_kill(v);
return;
}
/* Set up retrig and looping */
randtrig = (int)v->c[VC_RANDTRIG];
retrig = (int)v->c[VC_RETRIG];
if(randtrig)
{
UPDATE_RND
randtrig = rnd % (randtrig<<1) - randtrig;
randtrig = retrig * randtrig >> 16;
retrig += randtrig;
}
if(retrig > 0)
{
if((unsigned)retrig > wavetab[wid].play_samples)
v->section_end = wavetab[wid].play_samples;
else
v->section_end = (unsigned)retrig;
}
else
v->section_end = wavetab[wid].play_samples;
/* Start voice! */
v->state = VS_PLAYING;
}
static inline int __handle_looping(audio_voice_t *v)
{
unsigned int samples = wavetab[v->c[VC_WAVE]].play_samples;
int randtrig = v->c[VC_RANDTRIG];
int retrig = v->c[VC_RETRIG];
/* Latch (new) waveform index */
v->wave = v->c[VC_WAVE];
if(randtrig)
{
UPDATE_RND
randtrig = rnd % (randtrig<<1) - randtrig;
randtrig = retrig * randtrig >> 16;
retrig += randtrig;
}
if(retrig > 0)
{
unsigned int old_se = v->section_end;
if((unsigned)retrig > samples)
v->section_end = samples;
else
v->section_end = (unsigned)retrig;
if(old_se > v->position)
{
/* Force instant initial retrig */
v->position = 0;
v->position_frac = 0;
}
else
{
/* Wrap loop */
if(v->position >= old_se)
v->position = 0;
else
v->position -= old_se;
}
}
else
{
if(v->c[VC_LOOP])
{
v->position -= v->section_end;
v->section_end = samples;
}
else
return 0; /* Stop playing! */
}
return 1;
}
void voice_check_retrig(audio_voice_t *v)
{
if(wavetab[v->wave].data.si8)
{
int retrig_max = v->c[VC_RETRIG] * v->c[VC_RANDTRIG] >> 16;
retrig_max += v->c[VC_RETRIG];
if(v->position > (unsigned)retrig_max)
__handle_looping(v);
}
}
/*
* Macro Mayhem! Create all the mixer variants...
*/
static inline void __mix_m8(audio_voice_t *v, int *out, unsigned frames)
{
#undef __SEND
#undef __STEREO
#undef __16BIT
#include "a_mixers.h"
}
static inline void __mix_s8(audio_voice_t *v, int *out, unsigned frames)
{
#undef __SEND
#define __STEREO
#undef __16BIT
#include "a_mixers.h"
}
static inline void __mix_m16(audio_voice_t *v, int *out, unsigned frames)
{
#undef __SEND
#undef __STEREO
#define __16BIT
#include "a_mixers.h"
}
static inline void __mix_s16(audio_voice_t *v, int *out, unsigned frames)
{
#undef __SEND
#define __STEREO
#define __16BIT
#include "a_mixers.h"
}
static inline void __mix_m8d(audio_voice_t *v, int *out, int *sout, unsigned frames)
{
#define __SEND
#undef __STEREO
#undef __16BIT
#include "a_mixers.h"
}
static inline void __mix_s8d(audio_voice_t *v, int *out, int *sout, unsigned frames)
{
#define __SEND
#define __STEREO
#undef __16BIT
#include "a_mixers.h"
}
static inline void __mix_m16d(audio_voice_t *v, int *out, int *sout, unsigned frames)
{
#define __SEND
#undef __STEREO
#define __16BIT
#include "a_mixers.h"
}
static inline void __mix_s16d(audio_voice_t *v, int *out, int *sout, unsigned frames)
{
#define __SEND
#define __STEREO
#define __16BIT
#include "a_mixers.h"
}
#undef __SEND
#undef __STEREO
#undef __16BIT
/*
* Calculates resampling input "step", and selects resampling mode.
*/
static inline unsigned int __calc_step(audio_voice_t *v)
{
audio_resample_t mode = AR_LINEAR;
/* Resampling factor */
int pitch = v->c[VC_PITCH];
unsigned step = (unsigned)fixmul(ptab_convert(pitch),
wavetab[v->wave].speed);
#if (FREQ_BITS < 16)
step >>= 16 - FREQ_BITS;
#elif (FREQ_BITS > 16)
step <<= FREQ_BITS - 16;
#endif
/*
* We must prevent high pithes from locking the mixer in
* an infinite loop with short looped waveforms...
*/
if(step > MAX_STEP)
{
#ifdef DEBUG
log_printf(ELOG, "Too high pitch!\n");
#endif
while(step > MAX_STEP)
step >>= 1;
}
switch(a_settings.quality)
{
case AQ_VERY_LOW:
mode = AR_NEAREST;
break;
case AQ_LOW:
mode = AR_NEAREST_4X;
break;
case AQ_NORMAL:
mode = AR_LINEAR;
break;
case AQ_HIGH:
/* Select resampling method based on in/out ratio */
if(step > (unsigned)(6 << FREQ_BITS))
mode = AR_LINEAR_8X_R; /* Above 6:1 */
else if(step > (unsigned)(3 << FREQ_BITS))
mode = AR_LINEAR_4X_R; /* Above 3:1 */
else
mode = AR_LINEAR_2X_R; /* Below 2:1 */
break;
case AQ_VERY_HIGH:
/* Select resampling method based on in/out ratio */
if(step > (unsigned)(4 << FREQ_BITS))
mode = AR_LINEAR_16X_R; /* Above 4:1 */
else if(step > (unsigned)(3 << FREQ_BITS))
mode = AR_LINEAR_8X_R; /* Above 3:1 */
else if(step > (unsigned)(2 << FREQ_BITS))
mode = AR_LINEAR_4X_R; /* Above 2:1 */
else if(step > (unsigned)(3 << (FREQ_BITS-1)))
mode = AR_LINEAR_2X_R; /* Above 1.5:1 */
else
mode = AR_CUBIC_R; /* Below 1.5:1 */
break;
}
v->c[VC_RESAMPLE] = mode;
return step;
}
/*
* Calculates # of output frames to the nearest of 'frames',
* end of segment and the "fragment span limit".
*/
static inline unsigned int __endframes(audio_voice_t *v, unsigned int frames)
{
#ifdef A_USE_INT64
Uint64 n, n2;
#else
double n, n2;
#endif
if(!v->step)
return frames;
#ifdef A_USE_INT64
n = ((Uint64)(v->position) << 32) | (Uint64)(v->position_frac);
n >>= 32 - FREQ_BITS;
n = ((Uint64)(v->section_end) << FREQ_BITS) - n + v->step - 1;
n /= v->step;
#else
n = (double)(v->position) + (double)(v->position_frac) / 4294967296.0;
n = (double)(v->section_end) - n;
n /= (double)v->step / (double)(1<<FREQ_BITS);
n = ceil(n);
#endif
if(n > 0xffffffff)
n = 0xffffffff;
#ifdef A_USE_INT64
if(n > (Uint64)frames)
n = (Uint64)frames;
#else
if(n > (double)frames)
n = (double)frames;
#endif
/*
* Restrict fragment size to prevent read position overflows.
*
* (In order to maximize pitch accuracy, voice mixers can only
* handle a very limited number of input samples without
* recalculating their "base pointers".)
*/
#ifdef A_USE_INT64
n2 = (Uint64)MAX_FRAGMENT_SPAN << FREQ_BITS;
n2 /= (Uint64)v->step * (Uint64)frames;
#else
n2 = (double)MAX_FRAGMENT_SPAN * (1 << FREQ_BITS);
n2 /= (double)v->step * (double)frames;
#endif
if(n > n2)
n = n2;
#ifdef CHECKPOINTS
if(!frames)
{
voice_kill(v);
log_printf(ELOG, "Voice locked up! (Too high pitch "
"resulted in zero fragment size.)\n");
}
#endif
return (unsigned int)n;
}
static inline void __fragment_single(audio_voice_t *v, int *out,
unsigned int frames)
{
switch(wavetab[v->wave].format)
{
case AF_MONO8:
__mix_m8(v, out, frames);
break;
case AF_STEREO8:
__mix_s8(v, out, frames);
break;
case AF_MONO16:
__mix_m16(v, out, frames);
break;
case AF_STEREO16:
__mix_s16(v, out, frames);
break;
case AF_MONO32:
/*__mix_m32(v, out, frames);*/
break;
case AF_STEREO32:
/*__mix_s32(v, out, frames);*/
case AF_MIDI: /* warning eliminator */
break;
}
}
static inline void __fragment_double(audio_voice_t * v, int *out, int *sout,
unsigned int frames)
{
switch(wavetab[v->wave].format)
{
case AF_MONO8:
__mix_m8d(v, out, sout, frames);
break;
case AF_STEREO8:
__mix_s8d(v, out, sout, frames);
break;
case AF_MONO16:
__mix_m16d(v, out, sout, frames);
break;
case AF_STEREO16:
__mix_s16d(v, out, sout, frames);
break;
case AF_MONO32:
/*__mix_m32d(v, out, sout, frames);*/
break;
case AF_STEREO32:
/*__mix_s32d(v, out, sout, frames);*/
case AF_MIDI: /* warning eliminator */
break;
}
}
/*
* Figure out if we should use the "double output" mixers,
* and where to connect the output(s).
*/
static inline int __setup_output(audio_voice_t *v)
{
int prim, send;
/*
FIXME: This "automatic routing optimization" isn't needed,
FIXME: and causes trouble elsewhere. Simplify.
*/
v->fx1 = v->c[VC_PRIM_BUS];
v->fx2 = v->c[VC_SEND_BUS];
prim = (v->fx1 >= 0) && (v->fx1 < AUDIO_MAX_BUSSES);
send = (v->fx2 >= 0) && (v->fx2 < AUDIO_MAX_BUSSES);
if(!prim && !send)
return -1; /* No busses selected! --> */
if(prim && send)
v->use_double = (v->fx1 != v->fx2);
else
{
if(send)
v->fx1 = v->fx2;
else
v->fx2 = v->fx1;
v->use_double = 0;
}
return 0;
}
void voice_process_mix(audio_voice_t *v, int *busses[], unsigned frames)
{
unsigned s, frag_s;
if((VS_STOPPED == v->state) && (aev_next(&v->port, 0) > frames))
return; /* Stopped, and no events for this buffer --> */
/* Loop until buffer is full, or the voice is "dead". */
s = 0;
while(frames)
{
unsigned frag_frames;
while( !(frag_frames = aev_next(&v->port, s)) )
{
aev_event_t *ev = aev_read(&v->port);
switch(ev->type)
{
case VE_START:
voice_start(v, ev->arg1);
if(VS_STOPPED == v->state)
{
aev_free(ev);
return; /* Error! --> */
}
/*
* NOTE:
* This being checked here means that
* it's not possible to change routing
* during playback. Who would, anyway?
*/
if(__setup_output(v) < 0)
{
voice_kill(v);
aev_free(ev);
return; /* No sends! --> */
}
break;
case VE_STOP:
voice_kill(v);
aev_free(ev);
return; /* Back in the voice pool! --> */
case VE_SET:
#ifdef CHECKPOINTS
if(ev->index >= VC_COUNT)
{
log_printf(ELOG, "BUG! VC index out of range!");
break;
}
#endif
v->c[ev->index] = ev->arg1;
if(VC_PITCH == ev->index)
v->step = __calc_step(v);
break;
case VE_IRAMP:
#ifdef CHECKPOINTS
if(ev->index >= VIC_COUNT)
{
log_printf(ELOG, "BUG! VIC index out of range!");
break;
}
#endif
if(ev->arg2)
{
v->ic[ev->index].dv = ev->arg1 << RAMP_BITS;
v->ic[ev->index].dv -= v->ic[ev->index].v;
v->ic[ev->index].dv /= ev->arg2 + 1;
}
else
v->ic[ev->index].v = ev->arg1 << RAMP_BITS;
break;
}
aev_free(ev);
}
if(frag_frames > frames)
frag_frames = frames;
/* Handle fragmentation, end-of-waveform and looping */
frag_s = (VS_PLAYING == v->state) ? 0 : frag_frames;
while(frag_s < frag_frames)
{
unsigned offs = (s + frag_s) << 1;
unsigned do_frames = __endframes(v, frag_frames - frag_s);
if(do_frames)
{
#ifdef CHECKPOINTS
if(v->position >= v->section_end)
{
log_printf(ELOG, "BUG! position = %u while section_end = %u.",
v->position, v->section_end);
log_printf(ELOG, " (step = %u)\n", v->step >> FREQ_BITS);
v->position = 0;
}
#endif
bustab[v->fx1].in_use = 1;
if(v->use_double)
{
bustab[v->fx2].in_use = 1;
__fragment_double(v, busses[v->fx1] + offs,
busses[v->fx2] + offs,
do_frames);
}
else
__fragment_single(v, busses[v->fx1] + offs,
do_frames);
frag_s += do_frames;
// This is just for that damn oversampling...
if(v->position >= v->section_end)
do_frames = 0;
}
if(!do_frames && !__handle_looping(v))
{
voice_kill(v);
return;
}
}
s += frag_frames;
frames -= frag_frames;
}
}
void voice_process_all(int *bufs[], unsigned frames)
{
int i;
for(i = 0; i < AUDIO_MAX_VOICES; ++i)
voice_process_mix(voicetab + i, bufs, frames);
}
static int _is_open = 0;
void audio_voice_open(void)
{
int i;
if(_is_open)
return;
memset(voicetab, 0, sizeof(voicetab));
for(i = 0; i < AUDIO_MAX_VOICES; ++i)
{
char *buf = malloc(64);
snprintf(buf, 64, "Audio Voice %d", i);
aev_port_init(&voicetab[i].port, buf);
}
_is_open = 1;
}
void audio_voice_close(void)
{
int i;
if(!_is_open)
return;
for(i = 0; i < AUDIO_MAX_VOICES; ++i)
{
aev_flush(&voicetab[i].port);
free((char *)voicetab[i].port.name);
}
memset(voicetab, 0, sizeof(voicetab));
_is_open = 0;
}
+199
View File
@@ -0,0 +1,199 @@
/*(LGPL)
---------------------------------------------------------------------------
a_voice.h - Audio Engine low level mixer voices
---------------------------------------------------------------------------
* Copyright (C) 2001, 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* How To Keep Track Of Your Voices (for patch programmers):
* Before doing anything with a Voice, first check that it
* still actually *is* your voice, and hasn't been stolen
* by some one else. The 'channel' field will indicate this,
* of course, but that's actually only half a solution!
*
* Why? Well, if you're doing somethingn polyphonic, the
* Voice allocator may steal *your* voices, just as any other
* voices, which is why it's quite possible to lose voices to
* your own channel.
*
* To deal with this, each Voice has a 'tag' field that you
* may use in any way you see fit. The idea is to write a
* "locally unique" code into the 'tag' field whenever you
* get a "new" voice from the allocator. That way, you can
* just check that value later on, to see whether or not the
* channel is still doing what you told it to, or if you or
* someone else has started a new job on it.
*
* As an example, MIDI code may store MIDI pitch in the
* 'tag' field upon NoteOn, as there can usually only be one
* "voice context" (one or more voices) active for each MIDI
* key of each MIDI channel. When receiving other messages,
* just check for each voice before you touch, that 1) it's
* still owned by the engine Channel the MIDI channel is
* mapped to. When you eventually get the NoteOff, 2) check
* that the 'tag' fields of voices match the MIDI pitch of
* the NoteOff message.
*
* Note that as long as everyone sticks with their own
* Channels, it's ok to use the 'tag' fields in different
* ways in the same engine, at the same time. Checking
* 'channel' fields *first* will avoid any confusion.
*/
#ifndef _A_VOICE_H_
#define _A_VOICE_H_
#include "a_globals.h"
#include "a_types.h"
#include "a_events.h"
#include "a_patch.h"
typedef enum voice_states_t
{
VS_STOPPED = 0, /* "Detached" from the mixer */
VS_RESERVED, /* Just allocated; pending events */
VS_PLAYING /* Playing! */
} voice_states_t;
typedef enum voice_events_t
{
VE_START = 0, /* Start playing waveform <arg1>. */
VE_STOP, /* Stop and free voice, flush events.
* This *will* click unless you fade out first!
* NOTE: The voice disappears in a puff of
* violet smoke after receiving this
* event! Any subsequent events will
* be deleted and IGNORED.
*/
VE_SET, /* Set control <index> to <arg1>. */
// VE_ISET, /* Set interpolated control <index> to <arg1>. */
VE_IRAMP /* Ramp interpolated control <index> to <arg1>
* over <arg2> frames.
*/
} voice_events_t;
/* Voice Controls that are *not* interpolated. */
typedef enum voice_controls_t
{
VC_WAVE = 0, /* (int) Waveform index (latched on loop) */
VC_LOOP, /* (boolean) */
VC_PITCH, /* (fixp) linear frequency; int <==> MIDI */
VC_RETRIG, /* (int) retrig after N samples */
VC_RANDTRIG, /* (fixp[0, 1]) random mod. to RETRIG */
VC_PRIM_BUS, /* (int) target bus for ACC_VOLUME */
VC_SEND_BUS, /* (int) target bus for ACC_SEND */
VC_RESAMPLE, /* (enum) Resampling mode */
VC_COUNT
} voice_controls_t;
/* Voice Controls that *are* interpolated. */
typedef enum voice_icontrols_t
{
VIC_LVOL = 0, /* (fixp) Left volume */
VIC_RVOL, /* (fixp) Right volume */
VIC_LSEND, /* (fixp) Left send */
VIC_RSEND, /* (fixp) Right send */
VIC_COUNT
} voice_icontrols_t;
/* Voice control interpolator */
typedef struct voice_ci_t
{
int v;
int dv;
} voice_ci_t;
struct audio_channel_t;
typedef struct audio_voice_t
{
/* Voice Management */
struct audio_voice_t *next; /* For channel voice list */
struct audio_voice_t *prev;
struct audio_channel_t *channel;
int tag; /* tag from patch */
int priority; /* Stealing priority (from owner) */
/* Non-interpolated Controls */
int c[VC_COUNT];
/* Interpolated Controls */
voice_ci_t ic[VIC_COUNT];
/* Event Control */
aev_port_t port; /* Event input port */
/* Oscillator */
voice_states_t state;
int wave;
unsigned int section_end; /* index of section end point */
unsigned int position; /* current pos, integer part */
unsigned int position_frac; /* current pos, fractional part */
unsigned int step; /* position inc per output frame */
#ifdef AUDIO_USE_VU
int vu; /* Pre-volume, linear peak, [0, 65535].
* Reset or fade after reading!
*/
#endif
/* Output routing control */
int use_double; /* Use double output mixers */
int fx1, fx2; /* FX send bus indices */
patch_closure_t closure; /* Per-voice data for patch */
} audio_voice_t;
/* Get a free voice, or steal one if necessary. */
int voice_alloc(struct audio_channel_t *c);
/*
* The *real*, brutal stop action.
* No mercy. No click elimination.
* Use only in case of emergency.
*/
void voice_kill(audio_voice_t *v);
/* Dirty crap function used to "wake a voice up" if needed... */
void voice_check_retrig(audio_voice_t *v);
/*
* Mix 'frames' stereo samples into one or more of the 'busses',
* as specified by the voice's output controls.
*
* (Can be driven directly "from outside" the engine. The Waveform
* Construction API does this for resampling/conversion, for example.)
*/
void voice_process_mix(audio_voice_t *v, int *busses[], unsigned frames);
/* Process all voices. Simple, eh? */
void voice_process_all(int *busses[], unsigned frames);
void audio_voice_open(void);
void audio_voice_close(void);
#endif /*_A_VOICE_H_*/
+987
View File
@@ -0,0 +1,987 @@
/*(LGPL)
---------------------------------------------------------------------------
a_wave.c - Wava Data Manager
---------------------------------------------------------------------------
* Copyright (C) 2001-2003, 2007 David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "kobolog.h"
#include "a_globals.h"
#include "a_wave.h"
#include "a_struct.h"
#include "a_math.h"
#include "a_tools.h"
#include "a_agw.h"
#include "eel.h"
static int _was_init = 0;
void audio_wave_open(void)
{
if(_was_init)
return;
memset(wavetab, 0, sizeof(wavetab));
_was_init = 1;
}
void audio_wave_close(void)
{
if(!_was_init)
return;
audio_wave_free(-1);
_was_init = 0;
}
#define CHECKINIT if(!_was_init) audio_wave_open();
/*----------------------------------------------------------
Internal Tools
----------------------------------------------------------*/
/*
* Fill in the loop/interpolation extension zone with the right
* data, depending on whether the waveform is looped or not.
*
* (This should probably be replaced with smarter mixers. Cheap
* sample players like the EMU8000 chips, have the same problem
* with interpolation over loop wraps, which causes various
* problems with anything but plain "loop forever" samples.)
*/
static void _render_extension(int wid)
{
Sint8 *eos;
if(AF_MIDI == wavetab[wid].format)
return;
eos = wavetab[wid].data.si8 + wavetab[wid].size;
if(wavetab[wid].looped)
{
unsigned s, from = 0;
for(s = 0; s < wavetab[wid].xsize; ++s)
{
eos[s] = wavetab[wid].data.si8[from++];
if(from >= wavetab[wid].size)
from = 0;
}
}
else
memset(eos, 0, wavetab[wid].xsize);
}
static void _calc_info(int wid)
{
wavetab[wid].speed = (unsigned)((double)wavetab[wid].rate * 65536.0 /
(double)a_settings.samplerate);
switch(wavetab[wid].format)
{
case AF_MONO8:
wavetab[wid].samples = wavetab[wid].size;
break;
case AF_STEREO8:
case AF_MONO16:
wavetab[wid].samples = wavetab[wid].size / 2;
break;
case AF_STEREO16:
case AF_MONO32:
wavetab[wid].samples = wavetab[wid].size / 4;
break;
case AF_STEREO32:
wavetab[wid].samples = wavetab[wid].size / 8;
break;
case AF_MIDI:
wavetab[wid].samples = 1;
break;
}
}
void audio_wave_prepare(int wid)
{
int w, first, last;
if(wid < 0)
{
first = 0;
last = AUDIO_MAX_WAVES - 1;
}
else
first = last = wid;
for(w = first; w <= last; ++w)
{
if(!wavetab[w].allocated)
continue;
_calc_info(w);
_render_extension(w);
}
}
static int _get_free_wid(void)
{
int w;
for(w = 0; w < AUDIO_MAX_WAVES; ++w)
if(!wavetab[w].allocated)
return w;
return -1;
}
/*
* Flip back and forth between big and little endian,
* unless we're on big endian hardware.
*/
static void flip_endian(Uint8 *data, Uint32 size, int format)
{
#if SDL_BYTEORDER == SDL_LITTLE_ENDIAN
int i, s;
switch(format)
{
case AF_MIDI:
case AF_MONO8:
case AF_STEREO8:
break;
case AF_MONO16:
case AF_STEREO16:
for(i = 0; i < size; i += 2)
{
s = data[i];
data[i] = data[i + 1];
data[i + 1] = s;
}
break;
case AF_MONO32:
case AF_STEREO32:
for(i = 0; i < size; i += 4)
{
s = data[i];
data[i] = data[i + 3];
data[i + 3] = s;
s = data[i + 1];
data[i + 1] = data[i + 2];
data[i + 2] = s;
}
break;
}
#endif
}
static int LoadRAW(const char *name, Uint8 ** data, Uint32 * size,
int *format, int *rate, int *loop)
{
unsigned char header[8];
int startpos = 0;
FILE *f = fopen(name, "rb");
if(!f)
return -1;
if(fread(header, sizeof(header), 1, f) == 1)
{
if(strncmp("RAW", (char *)header, 3) == 0)
{
const char *fmt;
*format = (int)header[3] & 0x0f;
*loop = (header[3] & 0x80) != 0;
startpos = sizeof(header);
*rate = header[4];
*rate |= header[5] << 8;
*rate |= header[6] << 16;
*rate |= header[7] << 24;
switch (*format)
{
case AF_MONO8:
fmt = "MONO8";
break;
case AF_STEREO8:
fmt = "STEREO8";
break;
case AF_MONO16:
fmt = "MONO16";
break;
case AF_STEREO16:
fmt = "STEREO16";
break;
case AF_MONO32:
fmt = "MONO32";
break;
case AF_STEREO32:
fmt = "STEREO32";
break;
case AF_MIDI:
fmt = "MIDI (Huh!?)";
break;
default:
fmt = "Unknown";
break;
}
log_printf(DLOG, "LoadRAW: %s, %d Hz", fmt, *rate);
if(*loop)
log_printf(DLOG, ", looped\n");
else
log_printf(DLOG, ", one-shot\n");
}
else
fseek(f, 0, SEEK_SET);
}
if(fseek(f, 0, SEEK_END) == 0)
{
int s = (int)ftell(f) - startpos;
if(s <= 0)
{
fclose(f);
return -2;
}
*size = (Uint32) s;
if(fseek(f, startpos, SEEK_SET) == 0)
{
*data = malloc(*size);
if(*data)
{
if(fread(*data, *size, 1, f) == 1)
{
fclose(f);
flip_endian(*data, *size, *format);
return 0;
}
free(*data);
*data = NULL;
}
}
}
fclose(f);
return -3;
}
static int SaveRAW(const char *name, void *data, Uint32 size,
int format, int rate, int loop)
{
unsigned char header[8] = "RAW\0rate";
int result;
FILE *f = fopen(name, "wb");
if(!f)
return -1;
header[3] = (char)format;
if(loop)
header[3] |= 0x80;
header[4] = rate & 0xff;
header[5] = (rate >> 8) & 0xff;
header[6] = (rate >> 16) & 0xff;
header[7] = (rate >> 24) & 0xff;
if(fwrite(header, sizeof(header), 1, f) != 1)
return -2;
flip_endian(data, size, format);
result = fwrite(data, size, 1, f);
flip_endian(data, size, format);
if(result != 1)
return -3;
fclose(f);
return 0;
}
/*
* Calculate waveform memory needed, including the extra bytes
* needed for proper end-of-waveform handling. Will set the
* 'xsize' and 'play_samples' field.
*
* Must know sample format and original size!
*
* Also note that this is heavily dependent on the voice
* mixer - that's where the extra samples are needed.
FIXME: ...which means that this code probably belongs there,
FIXME: or that the voice mixer should set the parameters.
*
* Returns the total size required in bytes.
*/
static unsigned _calc_xsize(int wid)
{
unsigned samples, fsamples;
unsigned bytes_sample = 1;
unsigned ssize = wavetab[wid].size;
switch(wavetab[wid].format)
{
case AF_MONO8:
bytes_sample = 1;
break;
case AF_STEREO8:
case AF_MONO16:
bytes_sample = 2;
ssize >>= 1;
break;
case AF_STEREO16:
case AF_MONO32:
bytes_sample = 4;
ssize >>= 2;
break;
case AF_STEREO32:
bytes_sample = 8;
ssize >>= 3;
break;
case AF_MIDI:
wavetab[wid].xsize = 0;
return 0;
}
/* Fixed part: */
/* Looping. */
samples = ssize;
while(samples < MIN_LOOP)
samples <<= 1;
wavetab[wid].play_samples = samples;
samples -= ssize;
/* Interpolation. */
samples += 3;
/* Freq. ratio dependent part: oversampling and looping */
fsamples = AUDIO_MAX_MIX_RATE / AUDIO_MIN_OUTPUT_RATE;
fsamples *= AUDIO_MAX_OVERSAMPLING;
++fsamples;
wavetab[wid].xsize = bytes_sample * (samples + fsamples);
return wavetab[wid].size + wavetab[wid].xsize;
}
/*----------------------------------------------------------
Basic Wave API
----------------------------------------------------------*/
int audio_wave_alloc(int wid)
{
CHECKINIT
if(wid >= AUDIO_MAX_WAVES)
return -1;
if(wid < 0)
wid = _get_free_wid();
if(wid < 0)
return -2;
audio_wave_free(wid);
wavetab[wid].allocated = 1;
return wid;
}
int audio_wave_alloc_range(int first_wid, int last_wid)
{
int w, res, last_done;
if(last_wid < first_wid)
return -1;
last_done = -1;
res = 0;
for(w = first_wid; w <= last_wid; ++w)
{
res = audio_wave_alloc(w);
if(res < 0)
break;
last_done = w;
}
if(res < 0)
{
if(last_done >= 0)
for(w = first_wid; w <= last_done; ++w)
audio_wave_free(w);
return -2;
}
else
return first_wid;
}
audio_wave_t *audio_wave_get(int wid)
{
if(wid < 0)
return NULL;
if(wid >= AUDIO_MAX_WAVES)
return NULL;
return &wavetab[wid];
}
int audio_wave_format(int wid, audio_formats_t fmt, int fs)
{
unsigned old_xsize, new_size;
wid = audio_wave_alloc(wid);
if(wid < 0)
return wid;
old_xsize = wavetab[wid].xsize;
wavetab[wid].format = fmt;
wavetab[wid].rate = fs;
if(wavetab[wid].data.si8)
{
new_size = _calc_xsize(wid);
if(wavetab[wid].xsize != old_xsize)
{
void *ndata = realloc(wavetab[wid].data.si8, new_size);
if(!ndata)
return -3;
wavetab[wid].data.si8 = ndata;
_calc_info(wid);
}
}
return wid;
}
int audio_wave_load_mem(int wid, void *data, unsigned size, int looped)
{
wid = audio_wave_alloc(wid);
if(wid < 0)
return wid;
wavetab[wid].size = size;
wavetab[wid].looped = looped;
wavetab[wid].data.si8 = (Sint8 *)malloc(_calc_xsize(wid));
wavetab[wid].howtofree = HTF_FREE;
if(!wavetab[wid].data.si8)
{
audio_wave_free(wid);
return -1;
}
if(data)
memcpy(wavetab[wid].data.si8, data, size);
else
memset(wavetab[wid].data.si8, 0, size);
#ifdef xDEBUG
{
int i;
int peak = 0;
double avg = 0;
double power = 0;
int iavg, ipower;
for(i = 0; i < size; ++i)
{
int s;
s = wavetab[wid].data.si8[i];
avg += s;
power += labs(s);
if(labs(s) > peak)
peak = labs(s);
}
avg /= size;
power /= size;
iavg = (int)avg;
ipower = (int)power;
log_printf(D3LOG, "audio_wave_load_mem(id=%d): size=%d peak=%d"
" average=%d power=%d\n",
wid, size, peak, iavg, ipower);
}
#endif
_calc_info(wid);
return wid;
}
int audio_wave_blank(int wid, unsigned samples, int looped)
{
int bps = 0;
wid = audio_wave_alloc(wid);
if(wid < 0)
return wid;
switch(wavetab[wid].format)
{
case AF_MONO8:
bps = 1;
break;
case AF_STEREO8:
case AF_MONO16:
bps = 2;
break;
case AF_STEREO16:
case AF_MONO32:
bps = 4;
break;
case AF_STEREO32:
bps = 8;
break;
case AF_MIDI:
return wid;
}
return audio_wave_load_mem(wid, NULL, samples * bps, looped);
}
int audio_wave_convert(int wid, int new_wid, audio_formats_t fmt,
int fs, audio_resample_t resamp)
{
int inplace, private_pool = 0, i;
audio_voice_t resampler;
audio_quality_t old_quality;
int *bus;
Sint8 *out8;
Sint16 *out16;
float *out32;
unsigned newlen, j;
/* We need this to run the voice mixer... */
if(ptab_init(65536) < 0)
{
log_printf(ELOG, "audio_wave_convert(): ptab_init() failed!\n");
return -20;
}
if(AF_MIDI == fmt)
{
log_printf(ELOG, "audio_wave_convert(): Cannot convert to MIDI!\n");
return -10;
}
if(wid >= AUDIO_MAX_WAVES)
return -1;
if(wid < 0)
return -2;
if(AF_MIDI == wavetab[wid].format)
{
log_printf(ELOG, "audio_wave_convert(): Cannot convert from MIDI!\n");
return -11;
}
if(new_wid == wid)
{
inplace = 1;
new_wid = audio_wave_alloc(-1);
if(new_wid < 0)
return new_wid;
}
else
{
inplace = 0;
new_wid = audio_wave_alloc(new_wid);
if(new_wid < 0)
return new_wid;
}
audio_wave_format(new_wid, fmt, fs);
newlen = (unsigned)ceil((float)wavetab[wid].samples * (float)fs /
(float)wavetab[wid].rate);
audio_wave_blank(new_wid, newlen, wavetab[wid].looped);
/* Must prepare, as we're gonna use the wave mixer! */
audio_wave_prepare(wid);
memset(&resampler, 0, sizeof(resampler));
/* We need to tweak the 'speed' to get the output rate right! */
wavetab[wid].speed = (unsigned)((double)wavetab[wid].rate * 65536.0 /
(double)wavetab[new_wid].rate);
old_quality = a_settings.quality;
a_settings.quality = AQ_VERY_HIGH;
if(!aev_event_pool)
{
private_pool = 1;
aev_open(20);
}
switch(resamp)
{
case AR_WORST:
resamp = AR_NEAREST;
break;
case AR_MEDIUM:
resamp = AR_LINEAR_2X_R;
break;
case AR_BEST:
resamp = AR_CUBIC_R;
break;
default:
break;
}
aev_timer = 0;
(void)aev_send1(&resampler.port, 0, VE_START, wid);
(void)aev_sendi1(&resampler.port, 0, VE_SET, VC_PITCH, 60<<16);
(void)aev_sendi1(&resampler.port, 0, VE_SET, VC_RESAMPLE, resamp);
(void)aev_sendi1(&resampler.port, 0, VE_SET, VC_SEND_BUS, -1);
(void)aev_sendi2(&resampler.port, 0, VE_IRAMP, VIC_LVOL,
65536 >> (16-VOL_BITS), 0);
(void)aev_sendi2(&resampler.port, 0, VE_IRAMP, VIC_RVOL,
65536 >> (16-VOL_BITS), 0);
bus = malloc(256 * sizeof(int) * 2);
out8 = wavetab[new_wid].data.si8;
out16 = wavetab[new_wid].data.si16;
out32 = wavetab[new_wid].data.f32;
for(i = (int)wavetab[new_wid].samples; i > 0; i -= 256)
{
unsigned frames;
if(i > 256)
frames = 256;
else
frames = (unsigned)i;
s32clear(bus, frames);
voice_process_mix(&resampler, &bus, frames);
switch(wavetab[new_wid].format)
{
case AF_MONO8:
for(j = 0; j < frames; ++j)
*out8++ = (bus[j<<1] + bus[(j<<1)+1]) >> 9;
break;
case AF_STEREO8:
for(j = 0; j < frames; ++j)
{
*out8++ = bus[j<<1] >> 8;
*out8++ = bus[(j<<1)+1] >> 8;
}
break;
case AF_MONO16:
for(j = 0; j < frames; ++j)
*out16++ = (Sint16)(bus[j<<1] +
bus[(j<<1)+1]) >> 1;
break;
case AF_STEREO16:
for(j = 0; j < frames; ++j)
{
*out16++ = (Sint16)(bus[j<<1]);
*out16++ = (Sint16)(bus[(j<<1)+1]);
}
break;
case AF_MONO32:
for(j = 0; j < frames; ++j)
*out32++ = (float)(bus[j<<1] +
bus[(j<<1)+1]) * 0.5;
break;
case AF_STEREO32:
for(j = 0; j < frames; ++j)
{
*out32++ = (float)bus[j<<1];
*out32++ = (float)bus[(j<<1)+1];
}
break;
case AF_MIDI: /* whinestopper... */
break;
}
}
free(bus);
_calc_info(new_wid);
voice_kill(&resampler);
a_settings.quality = old_quality;
if(private_pool)
aev_close();
if(inplace)
{
audio_wave_free(wid);
memcpy(wavetab + wid, wavetab + new_wid, sizeof(audio_wave_t));
memset(wavetab + new_wid, 0, sizeof(audio_wave_t));
return wid;
}
else
{
_calc_info(wid); /* Restore after our tweaking */
return new_wid;
}
}
int audio_wave_clone(int wid, int new_wid)
{
if(wid >= AUDIO_MAX_WAVES)
return -1;
if(wid < 0)
return -2;
new_wid = audio_wave_format(new_wid, wavetab[wid].format,
wavetab[wid].rate);
if(new_wid < 0)
return new_wid;
return audio_wave_load_mem(new_wid, wavetab[wid].data.si8,
wavetab[wid].size, wavetab[wid].looped);
}
/* We're simply using the same path for everything. */
void audio_set_path(const char *path)
{
eel_set_path(path);
}
const char *audio_path(void)
{
return eel_path();
}
static int load_midi(int wid, const char *name)
{
midi_file_t *mf;
wid = audio_wave_alloc(wid);
if(wid < 0)
return wid;
mf = mf_open(name);
if(!mf)
{
log_printf(ELOG, "load_midi(): Failed to load file"
" \"%s\"! (Path = \"%s\")\n", name,
eel_path());
audio_wave_free(wid);
return -1;
}
wavetab[wid].data.midi = mf;
wavetab[wid].size = 1; /* Duration in ms or something? */
wavetab[wid].xsize = 0; /* N/A */
wavetab[wid].howtofree = HTF_FREE;
wavetab[wid].format = AF_MIDI;
wavetab[wid].rate = 120; /* PPQN? */
wavetab[wid].looped = 0; /* Not yet implemented */
wavetab[wid].speed = 120; /* ? */
wavetab[wid].samples = 1; /* Number of events? */
log_printf(DLOG, ".------------------------------------------------------\n");
log_printf(DLOG, "| MIDI File: %s\n", name);
log_printf(DLOG, "| Format: %u\n", mf->format);
log_printf(DLOG, "| Title: %s\n", mf->title);
log_printf(DLOG, "| Author: %s\n", mf->author);
log_printf(DLOG, "| Remarks: %s\n", mf->remarks);
log_printf(DLOG, "'------------------------------------------------------\n");
return wid;
}
int audio_wave_load(int wid, const char *name, int looped)
{
char buf[1024];
SDL_AudioSpec spec;
Uint8 *data = NULL;
Uint32 size;
int format = -2;
int rate = 0; /* Warning suppressor */
int res;
int using_loadwav = 0;
/* Prepend path */
strncpy(buf, eel_path(), sizeof(buf));
#ifdef WIN32
strncat(buf, "\\", sizeof(buf));
#elif defined MACOS
strncat(buf, ":", sizeof(buf));
#else
strncat(buf, "/", sizeof(buf));
#endif
strncat(buf, name, sizeof(buf));
/* Check extension */
if(strstr(name, ".raw") || strstr(name, ".RAW"))
{
format = -1;
res = LoadRAW(buf, &data, &size, &format, &rate, &looped);
if(res < 0)
format = -1;
}
else if(strstr(name, ".agw") || strstr(name, ".AGW"))
return agw_load(wid, name); /* No full path here! */
else if(strstr(name, ".mid") || strstr(name, ".MID"))
return load_midi(wid, buf);
else
{
using_loadwav = 1;
res = SDL_LoadWAV(buf, &spec, &data, &size) ? 0 : -1;
}
wid = audio_wave_alloc(wid);
if(wid < 0)
return wid;
if(format >= 0)
{
wavetab[wid].format = format;
wavetab[wid].rate = rate;
}
else if(using_loadwav)
{
switch (spec.format)
{
case AUDIO_S8:
wavetab[wid].format = AF_MONO8;
break;
case AUDIO_S16SYS:
wavetab[wid].format = AF_MONO16;
break;
default:
log_printf(ELOG, "sound_load(): Unsupported wave format!\n");
SDL_FreeWAV((Uint8 *)(wavetab[wid].data.si8));
res = -1;
break;
}
if(spec.channels == 2)
++wavetab[wid].format;
wavetab[wid].rate = spec.freq;
}
if(res < 0)
{
log_printf(ELOG, "audio_wave_load(): Failed to load file"
" \"%s\"! (Path = \"%s\")\n", name,
eel_path());
audio_wave_free(wid);
return -3;
}
if(data)
audio_wave_load_mem(wid, data, size, looped);
if(using_loadwav)
SDL_FreeWAV(data);
else
free(data);
return wid;
}
int audio_wave_save(int wid, const char *name)
{
char buf[1024];
audio_wave_t *wave = audio_wave_get(wid);
if(!wave)
return -1;
/* Prepend path */
strncpy(buf, eel_path(), sizeof(buf));
#ifdef WIN32
strncat(buf, "\\", sizeof(buf));
#elif defined MACOS
strncat(buf, ":", sizeof(buf));
#else
strncat(buf, "/", sizeof(buf));
#endif
strncat(buf, name, sizeof(buf));
log_printf(DLOG, "Saving to \"%s\"\n", buf);
/* Check extension */
if(strstr(name, ".raw") || strstr(name, ".RAW"))
return SaveRAW(buf, wave->data.si8, wave->size,
(int)wave->format, wave->rate, wave->looped);
else
return -2;
}
void audio_wave_free(int wid)
{
int w, first, last;
CHECKINIT
if(wid < 0)
{
first = 0;
last = AUDIO_MAX_WAVES - 1;
}
else
first = last = wid;
for(w = first; w <= last; ++w)
{
if(!wavetab[w].data.si8)
continue;
if(HTF_FREE == wavetab[w].howtofree)
switch(wavetab[w].format)
{
case AF_MONO8:
case AF_STEREO8:
case AF_MONO16:
case AF_STEREO16:
case AF_MONO32:
case AF_STEREO32:
free(wavetab[w].data.si8);
break;
case AF_MIDI:
mf_close(wavetab[w].data.midi);
break;
}
wavetab[w].data.si8 = NULL;
wavetab[w].size = 0;
wavetab[w].xsize = 0;
wavetab[w].allocated = 0;
}
}
void audio_wave_info(int wid)
{
int w, first, last;
int count = 0;
int total_size = 0;
int total_time = 0;
if(wid < 0)
{
first = 0;
last = AUDIO_MAX_WAVES - 1;
}
else
first = last = wid;
log_printf(VLOG, "Waveform info:\n");
for(w = first; w <= last; ++w)
{
const char *f;
if(!wavetab[w].allocated)
continue;
switch(wavetab[w].format)
{
case AF_MONO8: f = "MONO8 "; break;
case AF_STEREO8: f = "STEREO8 "; break;
case AF_MONO16: f = "MONO16 "; break;
case AF_STEREO16: f = "STEREO16"; break;
case AF_MONO32: f = "MONO32 "; break;
case AF_STEREO32: f = "STEREO32"; break;
case AF_MIDI: f = "MIDI "; break;
default: f = "Unknown "; break;
}
if(wavetab[w].format == AF_MIDI)
log_printf(VLOG, " (%3d: %s %s, %d PPQN,\t%d events)\n",
w, f, wavetab[w].data.midi->title,
wavetab[w].rate, wavetab[w].size);
else
{
float d = (float)wavetab[w].samples / wavetab[w].rate;
log_printf(VLOG, " %3d: %s %s, %d Hz,\t%d bytes\t"
"(%.2f s)\n",
w, f, wavetab[w].looped ?
"LOOPED" : "ONESHOT",
wavetab[w].rate, wavetab[w].size, d);
total_size += wavetab[w].size;
total_time += d;
++count;
}
}
log_printf(VLOG, " Total %d waveforms, total size: %d bytes, "
"total time: %d s\n", count, total_size, total_time);
}
+261
View File
@@ -0,0 +1,261 @@
/*(LGPL)
---------------------------------------------------------------------------
a_wave.h - Wava Data Manager
---------------------------------------------------------------------------
* Copyright (C) 2001-2003, 2007 David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* Note on sample formats and clipping:
* * Operations on integer waveforms will saturate if the
* respective integer range is exceeded.
*
* * Operations on floating point waveforms will *not*
* saturate at all, under any circumstances.
*
* * Converting from floating point to integer formats,
* as well as mixing floating point waveforms into
* integer waveforms will saturate if the respective
* integer range is exceeded.
*/
#ifndef _A_WAVE_H_
#define _A_WAVE_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "a_types.h"
#include "a_midifile.h"
/*----------------------------------------------------------
Open/Close (Normally called by the audio engine.)
----------------------------------------------------------*/
void audio_wave_open(void);
void audio_wave_close(void);
typedef enum _audio_free_t
{
HTF_DONT = 0,
HTF_FREE
} _audio_free_t;
/*----------------------------------------------------------
Audio Wave (Raw wave data reference + info)
----------------------------------------------------------*/
typedef struct
{
/* Format info */
audio_formats_t format;
int rate; /* samples/second */
int looped; /* Must be here for preprocessing */
/* "Real" data */
union
{
Sint8 *si8;
Sint16 *si16;
float *f32;
midi_file_t *midi;
} data;
unsigned size; /* Data size in *bytes* */
unsigned xsize; /* End extension size in *bytes* */
/* Reference/allocation management */
_audio_free_t howtofree;
unsigned allocated;
/* Handy precalculated stuff */
unsigned speed; /* For original sample rate */
unsigned samples; /* Number of *frames*, actually */
unsigned play_samples; /* For the voice mixer... */
} audio_wave_t;
void _audio_wave_init(void);
/*----------------------------------------------------------
Basic Wave API
----------------------------------------------------------*/
/*
* Allocate a new wave for later use.
* If 'id' is -1, a wave id will be allocated automatically.
*
* Returns your wave id, or a negative value in the case of
* failure.
*/
int audio_wave_alloc(int wid);
/*
* Allocate a range of waves for later use, to prevent the from
* being stolen by the dynamic allocation system.
*
* Returns your first wave id, or a negative value in the case of
* failure.
*/
int audio_wave_alloc_range(int first_wid, int last_wid);
/*
* Get the low level struct for waveform 'wid'. (This is
* mostly meant for low level libraries, not applications.)
*
* Returns NULL if the waveform doesn't exist.
*/
audio_wave_t *audio_wave_get(int wid);
/*
* Set the path to data files.
*
* This must be a valid path, terminated by a
* '/', '\', ':' or whatever "directory separator"
* the current platform is using, and will be
* prepended to any external file names used in
* other calls.
*/
void audio_set_path(const char *path);
/* Returns the current path. */
const char *audio_path(void);
/*
* Loads a waveform from disk.
*
* If 'id' is -1, a new waveform will be allocated automatically.
*
* The file name can be the path to an audio file of any supported
* format.
*
* If the file format cannot be identified, the file is assumed to
* be raw data, and the audio format is assumed to be of the
* format specified for the waveform. You should set the proper
* format (using audio_wave_format()) *before* loading a raw audio
* file, to minimize the risk of extra work for the engine.
*
* Returns the id of the new wave, or a negative value in the
* case of failure.
*/
int audio_wave_load(int wid, const char *name, int looped);
/*
* Saves a waveform to disk.
*
FIXME: Temporary kludge:
FIXME: The file will be a raw data file with the first 3 bytes
FIXME: containing ASCII "RAW". The 4th byte contains the format code
FIXME: as defined by the audio_formats_t enum.
*/
int audio_wave_save(int wid, const char *name);
/*
* "Loads" a waveform from a raw data buffer in memory.
* The audio engine will *not* free the memory you pass here, but
* will copy it into a private buffer internally, so your buffer
* does not have to be static. (The internal copy is often needed
* anyway due to the need for preprocessing.)
*
* The data is assumed to be of the format specified with
* audio_wave_format().
*
* Returns the id of the new wave, or a negative value in the
* case of failure.
*/
int audio_wave_load_mem(int wid, void *data, unsigned size, int looped);
/*
* Removes a waveform from memory, and frees the wave id.
*/
void audio_wave_free(int wid); /* -1 to free all */
/*
* Specify the data format and sample rate of a waveform.
*
* If 'id' is -1, a new waveform will be allocated automatically.
*
* Note that this call *will not* convert any data! This call
* should normally not be used except when loading raw audio
* files.
*
* Returns the id of the new wave, or a negative value in the
* case of failure. (Yes, it *can* fail, as it may have to
* reallocate the wave data buffer!)
*/
int audio_wave_format(int wid, audio_formats_t fmt, int fs);
/*
* Creates a silent wave of the specified size in *samples*.
*
* The data will be of the format specified with
* audio_wave_format().
*
* Returns the id of the new wave, or a negative value in the
* case of failure.
*/
int audio_wave_blank(int wid, unsigned samples, int looped);
/*
* Convert a waveform from it's current format into the specified
* format. If there is no data, you just get a new empty wave of
* the specified format, or if new_vwid == wid, the format is
* changed into the one you specify.
*
* If new_wid is -1, a new wave will be allocated automatically.
* new_wid may equal wid, for "in-place" conversion.
*
* By setting either 'fmt' to -1 or 'fs' to 0, the respective
* parameter is copied from the original wave. Setting both to
* -1 respectively 0 is equivalent to calling audio_wave_clone().
*
* 'resamp' is the resampling method to use, if resampling is
* needed.
*
* Returns the new wave id, or a negative value in the case of
* failure.
*/
int audio_wave_convert(int wid, int new_wid, audio_formats_t fmt,
int fs, audio_resample_t resamp);
/*
* Copy a waveform.
*
* If new_wid is -1, a new wave will be allocated automatically.
*
* Returns the clone wave id, or a negative value in the case of
* failure.
*/
int audio_wave_clone(int wid, int new_wid);
/*
* Converts a waveform (if needed) and prepares it for playback
* by the real time synth engine. Passing -1 for 'wid' will
* prepare *all* waveforms that aren't ready for playback
* already.
*/
void audio_wave_prepare(int wid);
/*
* Dump waveform info to log.
*/
void audio_wave_info(int wid);
#ifdef __cplusplus
};
#endif
#endif /*_A_WAVE_H_*/
+1829
View File
File diff suppressed because it is too large Load Diff
+180
View File
@@ -0,0 +1,180 @@
/*(LGPL)
---------------------------------------------------------------------------
a_wca.h - WCA, the Wave Construction API
---------------------------------------------------------------------------
* Copyright (C) 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _A_WCA_H_
#define _A_WCA_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "a_wave.h"
#define WCA_MAX_ENV_STEPS 32
/* Reset all envelopes, modulators etc */
void wca_reset(void);
/*--------------
Modulation
---------------*/
typedef enum wca_modtargets_t
{
WCA_AMPLITUDE = 0, /* Amplitude; level */
WCA_BALANCE, /* Oscillator output orig/modulated balance */
WCA_FREQUENCY, /* Frequency; cut-off */
WCA_LIMIT, /* Max overtone frequency */
WCA_MOD1, /* Ratio; crossfade; morph */
WCA_MOD2, /* Amount; roll-off */
WCA_MOD3, /* Secondary amount; roll-off */
_WCA_MODTARGETS /* # of modulation targets */
} wca_modtargets_t;
/* Clear modulator state, and prepare for programming. */
void wca_mod_reset(wca_modtargets_t target);
/* Program 'target' to produce a constant value 'v' */
void wca_val(wca_modtargets_t target, float v);
/* Add envelope point */
void wca_env(wca_modtargets_t target, float duration, float v);
/*
* Set modulation component of envelope.
* (See a_agw.h for details.)
*/
void wca_mod(wca_modtargets_t target, float frequency,
float amplitude, float depth);
/*
TODO:
void wca_mod_wave(wca_modtargets_t target, int wid, float amp);
(FM/AM/...)
*/
/*--------------
Oscillator
---------------*/
/* (Please refer to a_agw.h for details on waveforms, modulation etc.) */
typedef enum wca_mixmodes_t
{
WCA_ADD = 0,
WCA_MUL,
WCA_FM,
WCA_FM_ADD,
WCA_SYNC,
WCA_SYNC_ADD
} wca_mixmodes_t;
/* (Please refer to a_agw.h for details on waveforms, modulation etc.) */
typedef enum wca_waveform_t
{
WCA_DC = 0,
WCA_SINE,
WCA_HALFSINE,
WCA_RECTSINE,
WCA_PULSE,
WCA_TRIANGLE,
WCA_SINEMORPH,
WCA_BLMORPH,
WCA_BLCROSS,
WCA_NOISE,
WCA_SPECTRUM,
WCA_ASPECTRUM,
WCA_HSPECTRUM,
WCA_AHSPECTRUM
} wca_waveform_t;
/* Add a component to a waveform. */
void wca_osc(int wid, wca_waveform_t wf, wca_mixmodes_t mm);
/*--------------
Filtering
---------------*/
typedef enum wca_filtertype_t
{
WCA_ALLPASS = 0,
WCA_LOWPASS_6DB,
WCA_HIGHPASS_6DB,
WCA_LOWPASS_12DB,
WCA_HIGHPASS_12DB,
WCA_BANDPASS_12DB,
WCA_NOTCH_12DB,
WCA_PEAK_12DB
} wca_filtertype_t;
/*
* Apply filter to a waveform.
* The frequency envelope controls cutoff frequency, and the
* amplitude envelope controls the filter Q. The ratio controls
* the original/filtered balance; 0.0 outputs only the filtered
* signal.
*/
void wca_filter(int wid, wca_filtertype_t ft);
/*
* Scale the amplitude of waveform 'wid' using the AMPLITUDE
* envelope.
*/
void wca_gain(int wid);
/*
* "Enhance" the treble of wave 'wid' by adding an octave shifted
* version of the frequencies around 'f'. 'level' sets the amount
* of artificial treble added.
*/
void wca_enhance(int wid, int f, float level);
/*
* Noise gate. 'f' is the LP/HP split frequency, 'thr' is the
* start threshold (which acts as the attack/release switch
* level, related to the amplitude of the HP part of the signal),
* and 'att' is the Auto Threshold Tracking frequency. 'min' is
* the minimum treble gain, when the gate is closed.
*/
void wca_gate(int wid, int f, float min, float thr, float att);
/*
TODO:
* Mix one waveform into another.
*
* The source waveform is converted and resampled into
* the format/rate of the destination waveform.
* AMPLITUDE controls the total gain, BALANCE the
* source/destination balance (0.0 is all source,
* 1.0 is all destination), FREQUENCY controls the
* playback speed of the source waveform. (1.0
* corresponds to the original playback speed.)
*/
void wca_mix(int src_wid, int dst_wid);
#ifdef __cplusplus
};
#endif
#endif /*_A_WCA_H_*/
+589
View File
@@ -0,0 +1,589 @@
/*(LGPL)
---------------------------------------------------------------------------
a_wcaosc.h - Oscillators for the Wave Construction API
---------------------------------------------------------------------------
* Copyright (C) 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* Not a "real" header - it just looks like one to great extent. ;-)
* It is best seen as a huge macro.
*/
static unsigned int rnd = 16576;
//Resets the noise generator
static void noise_reset(void)
{
rnd = 16576;
}
//Returns a pseudo random number in the range [-1.0, 1.0]
static inline float noise(void)
{
int out;
rnd *= 1566083941UL;
rnd++;
rnd &= 0xffffffffUL; /* NOP on 32 bit machines */
out = (int)(rnd * (rnd >> 16) >> 16);
return (float)(out - 32767) * ONEDIV32K;
}
#if 0
typedef struct soscillator_t
{
float a; /* amplitude */
float w; /* angular position */
float dwr; /* relative angular velocity */
} soscillator_t;
static int oscillators = 0;
static soscillator_t osc[MAX_SPECTRUM_OSCILLATORS];
static inline float osc_process(float dw0)
{
int i;
float acc = 0f;
for(i = 0; i < oscillators; ++i)
{
acc += sin(osc[i].w) * osc[i].a;
osc[i].w += dw0 * osc[i].dwr;
}
return acc;
}
#endif
static inline float rolloff(float f, float limit)
{
float a = f / limit;
return 1.0f - (.45 + .3*a)*a;
}
static double osc_w; /* Ohmega for most oscillators */
static float noise_out; /* S&H accumulator for noise */
static float osc_yit; /* State for recursive oscillators */
static inline void _osc_sine(char *sync, float *f,
float *mod1,
float *out, unsigned frames)
{
const float onediv8 = 1.0f / 8.0f;
unsigned s, os;
float dt = s_dt * onediv8;
for(s = 0; s < frames; ++s)
{
float acc = 0.0f;
float dw = f[s] * dt;
if(sync[s])
osc_w = 0.0f;
if(mod1[s])
for(os = 8; os; --os)
{
float mod = sin(M_PI * 2.0f * osc_w) * mod1[s];
acc += sin(M_PI * 2.0f * (osc_w + mod));
osc_w += dw;
}
else
for(os = 8; os; --os)
{
acc += sin(M_PI * 2.0f * osc_w);
osc_w += dw;
}
out[s] = acc * onediv8;
}
}
static inline void _osc_halfsine(char *sync, float *f,
float *mod1,
float *out, unsigned frames)
{
const float onediv2 = 1.0f / 2.0f;
unsigned s, os;
float dt = s_dt * onediv2;
for(s = 0; s < frames; ++s)
{
float acc = 0.0f;
float dw = f[s] * dt;
if(sync[s])
osc_w = 0.0f;
for(os = 2; os; --os)
{
float v = sin(M_PI * 2.0f * osc_w);
if(v < mod1[s])
v = mod1[s];
v -= 0.5f + mod1[s] * 0.5f;
if(mod1[s] < 1.0f)
v *= 2.0f / (1.0f - mod1[s]);
else
v = 1.0f;
acc += v;
osc_w += dw;
}
out[s] = acc * onediv2;
}
}
static inline void _osc_rectsine(char *sync, float *f,
float *mod1,
float *out, unsigned frames)
{
const float onediv4 = 1.0f / 4.0f;
unsigned s, os;
float dt = s_dt * onediv4;
for(s = 0; s < frames; ++s)
{
float acc = 0.0f;
float dw = f[s] * dt;
if(sync[s])
osc_w = 0.0f;
for(os = 4; os; --os)
{
float v = fabs(sin(M_PI * 2.0f * osc_w) + mod1[s]);
v -= fabs(mod1[s] * 0.5f) + 0.5f;
v *= 2.0f - 2.0f * fabs(mod1[s]);
acc += v;
osc_w += dw;
}
out[s] = acc * onediv4;
}
}
static inline void _osc_pulse(char *sync, float *f,
float *mod1,
float *out, unsigned frames)
{
const float onediv8 = 1.0f / 8.0f;
unsigned s, os;
float dt = s_dt * onediv8;
for(s = 0; s < frames; ++s)
{
float acc = 0.0f;
float dw = f[s] * dt;
if(sync[s])
osc_w = 0.0f;
for(os = 8; os; --os)
{
float saw = osc_w - floor(osc_w);
acc += saw > mod1[s] ? 1.0f : -1.0f;
osc_w += dw;
}
out[s] = acc * onediv8;
}
}
static inline void _osc_triangle(char *sync, float *f,
float *mod1,
float *out, unsigned frames)
{
const float onediv4 = 1.0f / 4.0f;
unsigned s, os;
float dt = s_dt * onediv4;
for(s = 0; s < frames; ++s)
{
float acc = 0.0f;
float dw = f[s] * dt;
if(sync[s])
osc_w = 0.0f;
if(0.0f == mod1[s])
for(os = 4; os; --os)
{
acc += (osc_w - floor(osc_w)) * 2.0f - 1.0f;
osc_w += dw;
}
else
for(os = 4; os; --os)
{
float v = osc_w - floor(osc_w);
if(v < mod1[s])
v = v / mod1[s];
else
v = (1.0f - v) / (1.0f - mod1[s]);
v *= 2.0f;
v -= 1.0f;
acc += v;
osc_w += dw;
}
out[s] = acc * onediv4;
}
}
static inline void _osc_sinemorph(char *sync, float *f,
float *mod1, float *mod2, float *limit,
float *out, unsigned frames)
{
unsigned s;
for(s = 0; s < frames; ++s)
{
float m1, m2;
if(sync[s])
{
osc_w = 0.0f;
osc_yit = 0.0f;
}
if(f[s] > limit[s] * 0.5f)
m1 = m2 = 0.0f;
else
{
float scale = 1.0f - f[s] / (limit[s] * 0.5f);
if(mod1[s] + mod2[s] > 1.0f)
scale *= 1.0f / (mod1[s] + mod2[s]);
m1 = mod1[s] * scale;
m2 = mod2[s] * scale;
}
osc_yit = sin(M_PI*2.0f*osc_w + m1 * osc_yit +
m2 * osc_yit*osc_yit);
out[s] = osc_yit;
osc_w += f[s] * s_dt;
}
}
static inline void _osc_blmorph(char *sync, float *f,
float *mod1, float *mod2, float *mod3, float *limit,
float *out, unsigned frames)
{
/*
FIXME: Frequency sweeping broken!
*/
unsigned s;
for(s = 0; s < frames; ++s)
{
float hlimit1, hlimit2, hlimit3;
float ha;
float n = 2.0f;
int running = 1;
float m1 = mod1[s]*mod1[s];
float m2 = mod2[s]*mod2[s];
float m3 = mod3[s]*mod3[s];
int count = 0;
if(sync[s])
osc_w = 0.0f;
/* Fundamental */
out[s] = sin(M_PI * 2.0f * osc_w * f[s]);
while(running)
{
running = 0;
/* Even harmonics (sawtooth) */
hlimit1 = f[s] * (1.0f - m1) + (limit[s] * m1);
if(f[s] * n <= hlimit1)
{
out[s] += sin(M_PI * 2.0f * osc_w * f[s] * n) *
(1.0f / n) *
rolloff(f[s] * n, hlimit1);
running = 1;
}
n += 1.0f;
if(++count > MAX_SPECTRUM_OSCILLATORS)
break;
/* Odd harmonics (sawtooth, square & triangle) */
hlimit1 = f[s] * (1.0f - m1) + (limit[s] * m1);
if(f[s] * n <= hlimit1)
ha = 1.0f / n * rolloff(f[s] * n, hlimit1);
else
ha = 0;
hlimit2 = f[s] * (1.0f - m2) + (limit[s] * m2);
if(f[s] * n <= hlimit2)
ha += 1.0f / n * rolloff(f[s] * n, hlimit2);
hlimit3 = f[s] * (1.0f-m3) + (limit[s] * m3);
if(f[s] * n <= hlimit3)
ha += -1.0f / (n*n) *
rolloff(f[s] * n, hlimit3);
if(ha != 0)
{
out[s] += sin(M_PI * 2.0f * osc_w * f[s] * n)
* ha;
running = 1;
}
n += 1.0f;
if(++count > MAX_SPECTRUM_OSCILLATORS)
break;
}
osc_w += s_dt;
}
}
static inline void _osc_blcross(char *sync, float *f,
float *mod1, float *mod2, float *mod3, float *limit,
float *out, unsigned frames)
{
/*
FIXME: Frequency sweeping broken!
*/
unsigned s;
for(s = 0; s < frames; ++s)
{
float n = 2.0f;
float ha;
int count = 0;
if(sync[s])
osc_w = 0.0f;
/* Fundamental */
out[s] = sin(M_PI * 2.0f * osc_w * f[s]);
while(1)
{
/* Even harmonics (sawtooth) */
if(f[s] * n > limit[s])
break;
ha = mod1[s] / n;
out[s] += sin(M_PI * 2.0f * osc_w * f[s] * n) * ha *
rolloff(f[s] * n, limit[s]);
n += 1.0f;
if(++count > MAX_SPECTRUM_OSCILLATORS)
break;
/* Odd harmonics (sawtooth, square & triangle) */
if(f[s] * n > limit[s])
break;
ha = (mod1[s] + mod2[s]) / n - mod3[s] / (n*n);
out[s] += sin(M_PI * 2.0f * osc_w * f[s] * n) * ha *
rolloff(f[s] * n, limit[s]);
n += 1.0f;
if(++count > MAX_SPECTRUM_OSCILLATORS)
break;
}
osc_w += s_dt;
}
}
static inline void _osc_noise(char *sync, float *f,
float *out, unsigned frames)
{
unsigned s;
for(s = 0; s < frames; ++s)
{
unsigned os;
float acc = 0.0f;
float dt = (2.0f * 0.25f) * f[s] * s_dt;
if(dt > 0.25f)
dt = 0.25f;
if(sync[s])
osc_w = 1.0f;
for(os = 4; os; --os)
{
if(osc_w >= 1.0f) /* new value every half period */
{
osc_w -= 1.0f;
noise_out = noise();
}
osc_w += dt;
acc += noise_out;
}
out[s] = 0.25f * acc;
}
}
static inline void _osc_spectrum(char *sync, float *f,
float *mod1, float *mod2, float *limit,
float *out, unsigned frames)
{
/*
FIXME: Frequency sweeping broken!
*/
unsigned s;
for(s = 0; s < frames; ++s)
{
float acc = 0.0f;
float sa = 1.0f;
float sf = 1.0f;
float m1 = mod1[s];
float lim;
int count = 0;
if(sync[s])
osc_w = 0.0f;
if(m1 <= 1.0f)
{
m1 = 10.0f;
lim = f[s];
}
else
lim = limit[s];
while(f[s] * sf <= lim)
{
acc += sin(M_PI * 2.0f * f[s] * osc_w * sf) * sa *
rolloff(sf, lim);
sf *= m1;
sa *= mod2[s];
if(++count > MAX_SPECTRUM_OSCILLATORS)
break;
}
out[s] = acc;
osc_w += s_dt;
}
}
static inline void _osc_aspectrum(char *sync, float *f,
float *mod1, float *mod2, float *limit,
float *out, unsigned frames)
{
/*
FIXME: Frequency sweeping broken!
*/
unsigned s;
for(s = 0; s < frames; ++s)
{
float acc = 0.0f;
float sa = 1.0f;
float sf = 1.0f;
float m1 = mod1[s];
float lim;
int count = 0;
if(sync[s])
osc_w = 0.0f;
if(m1 <= 1.0f)
{
m1 = 10.0f;
lim = f[s];
}
else
lim = limit[s];
while(f[s] * sf <= lim)
{
acc += sin(M_PI * 2.0f * f[s] * osc_w * sf) * sa *
rolloff(sf, lim);
sf += m1;
sa *= mod2[s];
if(++count > MAX_SPECTRUM_OSCILLATORS)
break;
}
out[s] = acc;
osc_w += s_dt;
}
}
static inline void _osc_hspectrum(char *sync, float *f,
float *mod1, float *mod2, float *mod3, float *limit,
float *out, unsigned frames)
{
/*
FIXME: Frequency sweeping broken!
*/
unsigned s;
for(s = 0; s < frames; ++s)
{
float sao = 1.0f;
float sae = mod2[s];
float n = 1.0f;
unsigned count = 0;
float acc = 0.0f;
if(sync[s])
osc_w = 0.0f;
while(1)
{
/* Odd overtones */
if(f[s] * n > limit[s])
break;
acc += sin(M_PI * 2.0f * osc_w * f[s] * n) * sao *
rolloff(f[s] * n, limit[s]);
n *= mod1[s];
sao *= mod2[s];
if(++count > MAX_SPECTRUM_OSCILLATORS)
break;
/* Even overtones */
if(f[s] * n > limit[s])
break;
acc += sin(M_PI * 2.0f * osc_w * f[s] * n) * sae *
rolloff(f[s] * n, limit[s]);
n *= mod1[s];
sae *= mod3[s];
if(++count > MAX_SPECTRUM_OSCILLATORS)
break;
}
out[s] = acc;
osc_w += s_dt;
}
}
static inline void _osc_ahspectrum(char *sync, float *f,
float *mod1, float *mod2, float *mod3, float *limit,
float *out, unsigned frames)
{
/*
FIXME: Frequency sweeping broken!
*/
unsigned s;
for(s = 0; s < frames; ++s)
{
float sao = 1.0f;
float sae = mod2[s];
float n = 1.0f;
unsigned count = 0;
float acc = 0.0f;
if(sync[s])
osc_w = 0.0f;
while(1)
{
/* Odd overtones */
if(f[s] * n > limit[s])
break;
acc += sin(M_PI * 2.0f * osc_w * f[s] * n) * sao *
rolloff(f[s] * n, limit[s]);
n += mod1[s];
sao *= mod2[s];
if(++count > MAX_SPECTRUM_OSCILLATORS)
break;
/* Even overtones */
if(f[s] * n > limit[s])
break;
acc += sin(M_PI * 2.0f * osc_w * f[s] * n) * sae *
rolloff(f[s] * n, limit[s]);
n += mod1[s];
sae *= mod3[s];
if(++count > MAX_SPECTRUM_OSCILLATORS)
break;
}
out[s] = acc;
osc_w += s_dt;
}
}
+1126
View File
File diff suppressed because it is too large Load Diff
+241
View File
@@ -0,0 +1,241 @@
/*(GPL)
---------------------------------------------------------------------------
audio.h - Public Audio Engine Interface
---------------------------------------------------------------------------
* Copyright (C) 19??, Masanao Izumo
* Copyright (C) 2001-2003, 2007 David Olofson
*
* Written for SGI DMedia API by Masanao Izumo <mo@goice.co.jp>
* Mostly rewritten by David Olofson <do@reologica.se>, 2001
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2.1 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _AUDIO_H_
#define _AUDIO_H_
#include "config.h"
#include "a_types.h"
#include "a_commands.h"
#include "a_wave.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Define this to get range checking in the API entry calls. */
#define AUDIO_SAFE
/* Limits - exceed these and things may blow up! */
#define AUDIO_MIN_OUTPUT_RATE 8000
#define AUDIO_MAX_OUTPUT_RATE 48000
#define AUDIO_MAX_MIX_RATE 128000
#define AUDIO_MAX_OVERSAMPLING 16
/* Sounds */
#define AUDIO_MAX_WAVES 128
#define AUDIO_MAX_PATCHES 128
/* Control */
/* Grous and channels; virtually no per-unit overhead. */
#define AUDIO_MAX_GROUPS 8
#define AUDIO_MAX_CHANNELS 32
/*
* Changes (David):
*------------------
* play_stop(-1) feature removed.
* Silent channels are now released automatically.
*
* get_audio_volume() removed.
* It cannot safely be used the way XKobo used it
* due to the asynchronous nature of the modified
* audio engine. (Background music attenuation is
* now done in a more reliable way.)
*
* play_loop() removed.
* Not needed with a callback or thread driven
* audio engine.
*
* API cleaned up; all function names now begin with
* 'audio_'.
*
* audio_control() API added.
*
*
* Changes in 0.4-pre6:
*----------------------
* audio_volume() removed, as it was redundant.
*
* AC_SEND_# dry/fx level controls added.
*
* AC_GROUP added.
*
* The term "channel" is replaced with "voice".
*
* The new term "channel" is different from "voice".
* Just like before, channel indices are used as
* addresses when starting/stopping sounds and
* sending control commands. However, the "new"
* channels are internally separated from the
* engine's mixing voices, which means that it's
* possible to implement polyphonic channels and
* dynamic channel allocation.
*
* New term "group" introduced.
* Channels are organized in groups. A group is
* physically a structure that holds "modifications"
* that are applied as control commands are passed
* from channels belonging to the group to the
* voices controlled by the respective channels.
* What this means is basically that a group can
* be used as a central master control for all
* channels that belong to it.
*
* Volume/send format changed to 16:16 fixed point.
*
* Float API added, where 1.0 corresponds to
* 0x0001.0000.
*
* Master volume and reverb API now uses float values.
*
* AC_PLAYRESET renamed AC_DETACH.
* The new version works in a slightly different way
* to AC_PLAYRESET.
*
* Normally (AC_DETACH == 0), the engine behaves
* like a MIDI synth; all control commands to a
* channel instantly affects *all* voices started
* from that channel. Voices are not disconnected
* until they stop playing. Group default control
* values are never used after initialization.
*
* With AC_DETACH enabled, audio_play() disconnects
* voices, so that they are not affected by any
* further control commands. That is, control
* commands to a new voice have to be sent *before*
* the voice is actually started! After a voice has
* been started and detached, all channel controls
* are reset to the defaults for the group which
* the channel belongs to.
*
* Note that audio_stop() *always* stops all voices
* started from a channel.
*
* Note that audio_group_control() is affected by
* the AC_DETACH feature as well. As it's
* impossible to update detached voices properly,
* audio_group_control() never touches AC_DETACH
* mode channels directly. New voices are still
* affected when they're launched, as that's when
* AC_DETACH mode channels check the controls of
* their parent groups.
*
* Removed the hardcoded 8 kHz default AC_SPEED.
*
* *Lots* of other changes, like:
* * Switched to channel->bus->master architecture.
* * Added mono8, mono16 and stereo16 data support.
* * Added high quality voice mixers.
* * Added uniform plugin API.
* * Cleaned up engine and API namespace.
*
* Changes in 0.4pre7:
*---------------------
* More than I can keep track of... Some of it is in
* ChangeLog, I hope! :-)
*
* Changes in 0.4pre8:
*---------------------
* (See Kobo Deluxe 0.4pre8 ChangeLog.)
*/
/*----------------------------------------------------------
Init/close
----------------------------------------------------------*/
/*
* Initialize audio engine. Will do nothing if called when
* the engine is already open.
*
* Returns 0 on success, or a negative value in the case
* of failure.
*/
int audio_open(void);
/*
* Start audio engine. Can also be used for restarting
* the engine with new parameters. Will open the engine
* first if required. Any loaded sounds and patches (if
* the engine is open, that is) are unaffected.
*
* Returns 0 on success, or a negative value in the case
* of failure.
*/
int audio_start(int rate, int latency, int use_oss, int use_midi, int pollaudio);
/*
* "Driver" call for engine low priority housekeeping
* work. Call this "frequently" - at least ten times per
* second if possible.
*
* Note that while this call may not do anything
* at all on some platforms, it could provide
* CRITICAL SERVICES to the engine on others!
*
* (Mac OS Classic would be an example of the latter;
* since the engine is running in interrupt context,
* plugins must be instantiated and destroyed in the
* context of audio_run() instead.)
*
* With "pollaudio" enabled:
* Fills up the driver's audio buffer, calling the
* engine callback as needed to generate data. In
* this mode, audio_run() must be called rather
* frequently to avoid drop-outs. (The exact
* requirement depends on the configured latency.)
*/
void audio_run(void);
/* Returns the estimated time of the next callback */
int audio_next_callback(void);
/*
* Stop the audio engine. Does not unload sounds or
* patches.
*/
void audio_stop(void);
/*
* Stops the audio engine if running, and then unloads
* all waves & patches.
*/
void audio_close(void);
void audio_quality(audio_quality_t quality);
void audio_set_limiter(float thres, float rels);
/*
* Patch Construction (low level)
*/
void audio_patch_param(int pid, int param, int arg);
void audio_patch_paramf(int pid, int param, float arg);
void audio_patch_control(int pid, int layer, int ctl, int arg);
void audio_patch_controlf(int pid, int layer, int ctl, float arg);
#ifdef __cplusplus
};
#endif
#endif /* _AUDIO_H_ */
+187
View File
@@ -0,0 +1,187 @@
/*
------------------------------------------------------------
audiodev.h: Little silly audio device "class"
------------------------------------------------------------
* Copyright (C) David Olofson, 1999, 2001
* This code is released under the terms of the LGPL
*/
#include "audiodev.h"
#include "kobolog.h"
#include <stdio.h>
#ifndef HAVE_OSS
void audiodev_init(struct audiodev_t *ad)
{
log_printf(ELOG, "OSS sound output not compiled in!\n");
}
int audiodev_open(struct audiodev_t *ad)
{
return -1;
}
void audiodev_close(struct audiodev_t *ad)
{
}
#else /* HAVE_OSS */
#include <unistd.h>
#include <math.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#ifdef OSS_USE_SOUNDCARD_H
#include <soundcard.h>
#else
#include <sys/soundcard.h>
#endif
void audiodev_init(struct audiodev_t *ad)
{
ad->devicename = "/dev/dsp";
ad->mode = O_WRONLY;
ad->rate = 44100;
ad->fragmentsize = 256;
ad->fragments = 4;
ad->infd = ad->outfd = 0;
}
/* Open the audio device */
int audiodev_open(struct audiodev_t *ad)
{
int r,v;
struct audio_buf_info abinfo;
int fragmentshift;
fragmentshift=(int)floor((log(ad->fragmentsize)/log(2.0))+0.5);
if(fragmentshift < 4)
fragmentshift = 4;
log_printf(DLOG, " Fragment size: %d bytes\n", 1<<fragmentshift);
ad->outfd = ad->infd = open(ad->devicename, ad->mode | O_NONBLOCK);
if(ad->outfd < 0)
{
log_printf(ELOG, "Failed to open audio device!\n");
return -1;
}
if(!(ad->mode & O_NONBLOCK))
{
r = fcntl(ad->outfd, F_SETFL, ad->mode);
if(r < 0)
{
log_printf(ELOG, "Failed to set blocking mode!\n");
close(ad->outfd);
return -2;
}
}
r = ioctl(ad->outfd, SNDCTL_DSP_RESET, NULL);
if(r < 0)
{
log_printf(ELOG, "Failed to reset device!\n");
close(ad->outfd);
return -2;
}
/* Redundant? */
v = 16;
r = ioctl(ad->outfd, SNDCTL_DSP_SAMPLESIZE, &v);
if(r < 0)
{
log_printf(ELOG, "16 bit samples not supported!\n");
close(ad->outfd);
return -2;
}
v = AFMT_S16_LE;
r = ioctl(ad->outfd, SNDCTL_DSP_SETFMT, &v);
if(r < 0)
{
log_printf(ELOG, "Failed to set sample format!\n");
close(ad->outfd);
return -3;
}
else if(v != AFMT_S16_LE)
{
log_printf(ELOG, "16 bit stereo LE not supported!\n");
close(ad->outfd);
return -4;
}
v = 1;
r = ioctl(ad->outfd, SNDCTL_DSP_STEREO, &v);
if(r < 0)
{
log_printf(ELOG, "Stereo not supported!\n");
close(ad->outfd);
return -5;
}
v = ad->rate;
r = ioctl(ad->outfd, SNDCTL_DSP_SPEED, &v);
if(r < 0)
{
log_printf(ELOG, "Failed to set %d Hz sample rate!\n",
ad->rate);
close(ad->outfd);
return -6;
}
if(O_RDWR == ad->mode)
{
r = ioctl(ad->outfd, SNDCTL_DSP_SETDUPLEX, NULL);
if(r < 0)
{
log_printf(ELOG, "Full duplex not supported!\n");
close(ad->outfd);
return -4;
}
v = ((ad->fragments+2) << 16) | fragmentshift;
}
else
v = ((ad->fragments) << 16) | fragmentshift;
r = ioctl(ad->outfd, SNDCTL_DSP_SETFRAGMENT, &v);
if(r < 0)
{
log_printf(ELOG, "Failed to set fragment size/count!\n");
close(ad->outfd);
return -5;
}
r = ioctl(ad->outfd, SNDCTL_DSP_GETOSPACE, &abinfo);
if(r >= 0)
{
log_printf(DLOG, " Driver: fragments = %d, fragsize = %d\n",
abinfo.fragstotal,
abinfo.fragsize);
if( (abinfo.fragstotal != ad->fragments)
&& (O_RDWR != ad->mode) )
log_printf(WLOG, "Number of fragments is %d, "
"not %d as requested.\n",
abinfo.fragstotal, ad->fragments);
if(abinfo.fragsize != ad->fragmentsize)
log_printf(WLOG, "Fragment size is %d, "
"not %d as requested.\n",
abinfo.fragsize, ad->fragmentsize);
}
return 0;
}
void audiodev_close(struct audiodev_t *ad)
{
if( (ad->infd != ad->outfd) && ad->infd )
close(ad->infd);
if(ad->outfd)
close(ad->outfd);
ad->infd = ad->outfd = 0;
}
#endif /* HAVE_OSS */
+32
View File
@@ -0,0 +1,32 @@
/*
------------------------------------------------------------
audiodev.h: Little silly audio device "class"
------------------------------------------------------------
* Copyright (C) David Olofson, 1999-2001
* This code is released under the terms of the LGPL
*/
#ifndef _AUDIODEV_H_
#define _AUDIODEV_H_
#ifdef __cplusplus
extern "C" {
#endif
typedef struct audiodev_t
{
const char *devicename;
int outfd, infd;
int mode;
int rate;
int fragmentsize;
int fragments;
} audiodev_t;
void audiodev_init(struct audiodev_t *ad);
int audiodev_open(struct audiodev_t *ad);
void audiodev_close(struct audiodev_t *ad);
#ifdef __cplusplus
};
#endif
#endif
+141
View File
@@ -0,0 +1,141 @@
/*(LGPL)
---------------------------------------------------------------------------
ptable.h - Fast Pointer Table
---------------------------------------------------------------------------
* Copyright (C) 2002, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _PTABLE_H_
#define _PTABLE_H_
/*
* Usage:
*
* ptable_t(object_t, #_of_elements) table_name;
*
* Fast macros:
* void ptable_init(ptable_t *pt);
* Initialize/clear table 'pt'.
*
* int ptable_add(ptable_t *pt, object_t *obj);
* Add element 'obj' to table 'pt'.
* Returns the index of the new element.
* Returns -1 if the table was full.
*
* void ptable_remove(ptable_t *pt, int ind);
* Remove object at index 'ind' from table 'pt'.
*
* Note that the removed object's index will be
* taken over by another object! If you want to
* do something to that object (in case it keeps
* it's index as a reference, for example), use
* the PTABLE_FOR_MOVED() macro.
*
* PTABLE_FOR_MOVED(ptable_t *pt, int index, object_t *object)
* <statement>;
* If objects were moved as a result of the last
* operation, perform <statement> for each one of
* them. (Currently, this can be only 0 or 1 objects.)
*
* Slow macros:
* int ptable_remove_obj(ptable_t *pt, object_t *obj);
* Remove any instances of 'obj' from table 'pt'.
* Returns 1 if any instances of 'obj' were found.
*
* int ptable_find(ptable_t *pt, object_t *obj);
* Returns the index of the first occurence of 'obj'.
* Returns -1 if the object was not found.
*
* PTABLE_FOR_ALL(ptable_t *pt, int index, object_t *object)
* <statement>;
* Loop over all objects in the list, executing <statement>
* for each one of them.
*/
#define ptable_t(otype, elements) \
struct { \
int size; \
int count; \
int last_moved; \
otype *table[elements]; \
}
#define ptable_init(pt) \
{ \
pt->size = sizeof(pt->table); \
pt->count = 0; \
}
#define ptable_add(pt, obj) \
{ \
int ret; \
if(pt->count >= pt->size) \
ret = -1; \
else \
{ \
ret = pt->count; \
pt->table[pt->count++] = obj; \
} \
ret; \
}
#define ptable_find(pt, obj) \
{ \
int i, ret = -1; \
for(i = 0; i < pt->count; ++i) \
if(pt->table[i] != obj) \
{ \
ret = i; \
break; \
} \
ret; \
}
#define ptable_remove_obj(pt, obj) \
{ \
int i, ret = 0; \
for(i = 0; i < pt->count; ++i) \
{ \
if(pt->table[i] != obj) \
continue; \
if(i < pt->count-1) \
pt->table[i] = pt->table[pt->count-1]; \
--pt->count; \
--i; \
ret = 1; \
} \
ret; \
}
#define ptable_remove(pt, ind) \
{ \
if(ind < pt->count-1) \
pt->table[ind] = pt->table[pt->count-1];\
--pt->count; \
}
#define PTABLE_FOR_ALL(pt, itvar, itobj) \
for(itvar = 0; itvar < pt->count, itobj = pt->table[itvar]; ++itvar)
#define PTABLE_FOR_MOVED(pt, itvar, itobj) \
for(itvar = pt->last_moved, \
itobj = pt->last_moved >= 0 ? \
pt->table[pt->last_moved] \
: \
(void *)0; \
pt->last_moved >= 0 ; )
#endif /*_PTABLE_H_*/
+327
View File
@@ -0,0 +1,327 @@
/*
------------------------------------------------------------
SFIFO 1.4
------------------------------------------------------------
* Simple portable lock-free FIFO
* (c) 2000-2002, David Olofson
-----------------------------------------------------------
TODO:
* Is there a way to avoid losing one byte of buffer
space to avoid extra variables or locking?
* Test more compilers and environments.
-----------------------------------------------------------
*/
#ifdef __KERNEL__
# include <linux/string.h>
# include <asm/uaccess.h>
# include <linux/malloc.h>
# define _sf_malloc(x) kmalloc(x, GFP_KERNEL)
# define _sf_free(x, y) kfree_s(x, y)
#else
# include <string.h>
# include <stdlib.h>
# define _sf_malloc(x) malloc(x)
# define _sf_free(x, y) free(x)
#endif
#include "sfifo.h"
#ifdef _SFIFO_TEST_
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define DBG(x) /*(x)*/
#define TEST_BUFSIZE 10
#else
#define DBG(x)
#endif
/*
* Alloc buffer, init FIFO etc...
*/
int sfifo_init(sfifo_t *f, unsigned size)
{
memset(f, 0, sizeof(sfifo_t));
if(size > SFIFO_MAX_BUFFER_SIZE)
return -EINVAL;
/*
* Set sufficient power-of-2 size.
*
* No, there's no bug. If you need
* room for N bytes, the buffer must
* be at least N+1 bytes. (The fifo
* can't tell 'empty' from 'full'
* without unsafe index manipulations
* otherwise.)
*/
f->size = 1;
for(; f->size <= size; f->size <<= 1)
;
/* Get buffer */
if( 0 == (f->buffer = (void *)_sf_malloc(f->size)) )
return -ENOMEM;
return 0;
}
/*
* Dealloc buffer etc...
*/
void sfifo_close(sfifo_t *f)
{
if(f->buffer)
{
_sf_free(f->buffer, f->size);
memset(f, 0, sizeof(sfifo_t));
}
}
/*
* Empty FIFO buffer
*/
void sfifo_flush(sfifo_t *f)
{
/* Reset positions */
f->readpos = 0;
f->writepos = 0;
}
/*
* Write bytes to a FIFO
* Return number of bytes written, or an error code
*/
int sfifo_write(sfifo_t *f, const void *_buf, unsigned len)
{
unsigned total;
int i;
const char *buf = (const char *)_buf;
if(!f->buffer)
return -ENODEV; /* No buffer! */
/* total = len = min(space, len) */
total = sfifo_space(f);
DBG(printf("sfifo_space() = %d\n",total));
if(len > total)
len = total;
else
total = len;
i = f->writepos;
if(i + len > f->size)
{
memcpy(f->buffer + i, buf, f->size - i);
buf += f->size - i;
len -= f->size - i;
i = 0;
}
memcpy(f->buffer + i, buf, len);
f->writepos = i + len;
return (int)total;
}
#ifdef __KERNEL__
/*
* Write bytes to a FIFO from a user space buffer
* Return number of bytes written, or an error code
*/
int sfifo_write_user(sfifo_t *f, const void *buf, unsigned len)
{
int total;
int i;
if(!f->buffer)
return -ENODEV; /* No buffer! */
/* total = len = min(space, len) */
total = sfifo_space(f);
if(len > total)
len = total;
else
total = len;
i = f->writepos;
if(i + len > f->size)
{
if(f->size - i)
{
if(copy_from_user(f->buffer + i, buf, f->size - i))
return -EFAULT;
buf += f->size - i;
len -= f->size - i;
}
i = 0;
}
if(copy_from_user(f->buffer + i, buf, len))
return -EFAULT;
f->writepos = i + len;
return total;
}
#endif
/*
* Read bytes from a FIFO
* Return number of bytes read, or an error code
*/
int sfifo_read(sfifo_t *f, void *_buf, unsigned len)
{
unsigned total;
int i;
char *buf = (char *)_buf;
if(!f->buffer)
return -ENODEV; /* No buffer! */
/* total = len = min(used, len) */
total = sfifo_used(f);
DBG(printf("sfifo_used() = %d\n",total));
if(len > total)
len = total;
else
total = len;
i = f->readpos;
if(i + len > f->size)
{
memcpy(buf, f->buffer + i, f->size - i);
buf += f->size - i;
len -= f->size - i;
i = 0;
}
memcpy(buf, f->buffer + i, len);
f->readpos = i + len;
return (int)total;
}
#ifdef __KERNEL__
/*
* Read bytes from a FIFO into a user space buffer
* Return number of bytes read, or an error code
*/
int sfifo_read_user(sfifo_t *f, void *buf, unsigned len)
{
int total;
int i;
if(!f->buffer)
return -ENODEV; /* No buffer! */
/* total = len = min(used, len) */
total = sfifo_used(f);
if(len > total)
len = total;
else
total = len;
i = f->readpos;
if(i + len > f->size)
{
if(f->size - i)
{
if(copy_to_user(buf, f->buffer + i, f->size - i))
return -EFAULT;
buf += f->size - i;
len -= f->size - i;
}
i = 0;
}
if(copy_to_user(buf, f->buffer + i, len))
return -EFAULT;
f->readpos = i + len;
return total;
}
#endif
#ifdef _SFIFO_TEST_
void *sender(void *arg)
{
char buf[TEST_BUFSIZE*2];
int i,j;
int cnt = 0;
int res;
sfifo_t *sf = (sfifo_t *)arg;
while(1)
{
j = sfifo_space(sf);
for(i = 0; i < j; ++i)
{
++cnt;
buf[i] = cnt;
}
res = sfifo_write(sf, &buf, j);
if(res != j)
{
printf("Write failed!\n");
sleep(1);
} else if(res)
printf("Wrote %d\n", res);
}
}
int main()
{
sfifo_t sf;
char last = 0;
pthread_t thread;
char buf[100] = "---------------------------------------";
printf("sfifo_init(&sf, %d) = %d\n",
TEST_BUFSIZE, sfifo_init(&sf, TEST_BUFSIZE)
);
#if 0
printf("sfifo_write(&sf,\"0123456789\",7) = %d\n",
sfifo_write(&sf,"0123456789",7) );
printf("sfifo_write(&sf,\"abcdefghij\",7) = %d\n",
sfifo_write(&sf,"abcdefghij",7) );
printf("sfifo_read(&sf,buf,8) = %d\n",
sfifo_read(&sf,buf,8) );
buf[20] = 0;
printf("buf =\"%s\"\n",buf);
printf("sfifo_write(&sf,\"0123456789\",7) = %d\n",
sfifo_write(&sf,"0123456789",7) );
printf("sfifo_read(&sf,buf,10) = %d\n",
sfifo_read(&sf,buf,10) );
buf[20] = 0;
printf("buf =\"%s\"\n",buf);
#else
pthread_create(&thread, NULL, sender, &sf);
while(1)
{
static int c = 0;
++last;
while( sfifo_read(&sf,buf,1) != 1 )
/*sleep(1)*/;
if(last != buf[0])
{
printf("Error %d!\n", buf[0]-last);
last = buf[0];
}
else
printf("Ok. (%d)\n",++c);
}
#endif
sfifo_close(&sf);
printf("sfifo_close(&sf)\n");
return 0;
}
#endif
+110
View File
@@ -0,0 +1,110 @@
/*
------------------------------------------------------------
SFIFO 1.4
------------------------------------------------------------
* Simple portable lock-free FIFO
* (c) 2000-2002, David Olofson
*
* Platform support:
* gcc / Linux / x86: Works
* gcc / Linux / x86 kernel: Works
* gcc / FreeBSD / x86: Works
* gcc / NetBSD / x86: Works
* gcc / Mac OS X / PPC: Works
* gcc / Win32 / x86: Works
* Borland C++ / DOS / x86RM: Works
* gcc / BeOS / x86: Works
* gcc / BeOS / PPC: Works
* ? / Solaris / x86 Works
* ? / Solaris / SPARC Works
* ? / AmigaOS / PPC Works
* Borland C++ / Win32 / x86PM16: Untested
* ? / Various Un*ces / ?: Untested
* ? / Mac OS Classic / PPC: Untested
* ? / ? / Alpha: Untested
* ? / ? / MIPS: Untested
*
* 1.2: Max buffer size halved, to avoid problems with
* the sign bit...
*
* 1.3: Critical buffer allocation bug fixed! For certain
* requested buffer sizes, older version would
* allocate a buffer of insufficient size, which
* would result in memory thrashing. (Amazing that
* I've manage to use this to the extent I have
* without running into this... *heh*)
*
* 1.4: Trying to make sfifo more robust, and easier to
* use. It's now safe to close an sfifo more than
* once. I've also changed some arguments and
* variables to unsigned, to prevent signed input
* from causing things to blow up. free/malloc
* wrappers renamed _sf_free/_sf_malloc, to avoid
* confusion and macro collisions.
*
*/
#ifndef _SFIFO_H_
#define _SFIFO_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <errno.h>
/*------------------------------------------------
"Private" stuff
------------------------------------------------*/
/*
* Porting note:
* Reads and writes of a variable of this type in memory
* must be *atomic*! 'int' is *not* atomic on all platforms.
* A safe type should be used, and sfifo should limit the
* maximum buffer size accordingly.
*/
typedef int sfifo_atomic_t;
#ifdef __TURBOC__
# define SFIFO_MAX_BUFFER_SIZE 0x7fff
#else /* Kludge: Assume 32 bit platform */
# define SFIFO_MAX_BUFFER_SIZE 0x7fffffff
#endif
typedef struct sfifo_t
{
char *buffer;
unsigned size; /* Number of bytes */
sfifo_atomic_t readpos; /* Read position */
sfifo_atomic_t writepos; /* Write position */
} sfifo_t;
#define SFIFO_SIZEMASK(x) ((x)->size - 1)
/*------------------------------------------------
API
------------------------------------------------*/
int sfifo_init(sfifo_t *f, unsigned size);
void sfifo_close(sfifo_t *f);
void sfifo_flush(sfifo_t *f);
int sfifo_write(sfifo_t *f, const void *buf, unsigned len);
int sfifo_read(sfifo_t *f, void *buf, unsigned len);
#define sfifo_used(x) (((x)->writepos - (x)->readpos) & SFIFO_SIZEMASK(x))
#define sfifo_space(x) ((x)->size - 1 - sfifo_used(x))
/*------------------------------------------------
Linux kernel space interface
------------------------------------------------*/
#ifdef __KERNEL__
int sfifo_write_user(sfifo_t *f, const void *buf, unsigned len);
int sfifo_read_user(sfifo_t *f, void *buf, unsigned len);
#else
# define sfifo_write_user sfifo_write
# define sfifo_read_user sfifo_read
#endif
#ifdef __cplusplus
};
#endif
#endif