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:
@@ -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
|
||||
@@ -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
@@ -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, ¶m, &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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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_*/
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_*/
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_*/
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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_*/
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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_*/
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_*/
|
||||
@@ -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 */
|
||||
};
|
||||
@@ -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_*/
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_*/
|
||||
@@ -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
@@ -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*/
|
||||
}
|
||||
@@ -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_*/
|
||||
@@ -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.
|
||||
*/
|
||||
}
|
||||
@@ -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_*/
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_*/
|
||||
@@ -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
|
||||
};
|
||||
@@ -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_*/
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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_*/
|
||||
@@ -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
@@ -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_*/
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_*/
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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_*/
|
||||
@@ -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];
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+180
@@ -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_*/
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+241
@@ -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_ */
|
||||
@@ -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 */
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user