From 27be5bc72855a0fbbdae230bc144624c9eb85f5e Mon Sep 17 00:00:00 2001 From: Michiel Van Der Kolk Date: Thu, 17 Mar 2005 20:50:03 +0000 Subject: Initial check in dumb 0.9.2 - has a few usages of floating point that should be rewritten to fixed point. seems to compile cleanly for iriver. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@6197 a1c6a512-1295-4272-9138-f99709370657 --- apps/codecs/dumb/src/allegro/alplay.c | 270 +++ apps/codecs/dumb/src/allegro/datduh.c | 60 + apps/codecs/dumb/src/allegro/datit.c | 62 + apps/codecs/dumb/src/allegro/datmod.c | 61 + apps/codecs/dumb/src/allegro/dats3m.c | 61 + apps/codecs/dumb/src/allegro/datunld.c | 31 + apps/codecs/dumb/src/allegro/datxm.c | 62 + apps/codecs/dumb/src/allegro/packfile.c | 98 + apps/codecs/dumb/src/core/atexit.c | 71 + apps/codecs/dumb/src/core/duhlen.c | 34 + apps/codecs/dumb/src/core/dumbfile.c | 401 ++++ apps/codecs/dumb/src/core/loadduh.c | 42 + apps/codecs/dumb/src/core/makeduh.c | 92 + apps/codecs/dumb/src/core/rawsig.c | 44 + apps/codecs/dumb/src/core/readduh.c | 107 + apps/codecs/dumb/src/core/register.c | 104 + apps/codecs/dumb/src/core/rendduh.c | 202 ++ apps/codecs/dumb/src/core/rendsig.c | 299 +++ apps/codecs/dumb/src/core/unload.c | 58 + apps/codecs/dumb/src/helpers/clickrem.c | 270 +++ apps/codecs/dumb/src/helpers/memfile.c | 96 + apps/codecs/dumb/src/helpers/resample.c | 1177 +++++++++++ apps/codecs/dumb/src/helpers/sampbuf.c | 47 + apps/codecs/dumb/src/helpers/silence.c | 29 + apps/codecs/dumb/src/helpers/stdfile.c | 93 + apps/codecs/dumb/src/it/itload.c | 43 + apps/codecs/dumb/src/it/itmisc.c | 175 ++ apps/codecs/dumb/src/it/itorder.c | 63 + apps/codecs/dumb/src/it/itread.c | 1181 +++++++++++ apps/codecs/dumb/src/it/itrender.c | 3512 +++++++++++++++++++++++++++++++ apps/codecs/dumb/src/it/itunload.c | 71 + apps/codecs/dumb/src/it/loadmod.c | 42 + apps/codecs/dumb/src/it/loads3m.c | 42 + apps/codecs/dumb/src/it/loadxm.c | 42 + apps/codecs/dumb/src/it/readmod.c | 594 ++++++ apps/codecs/dumb/src/it/reads3m.c | 668 ++++++ apps/codecs/dumb/src/it/readxm.c | 998 +++++++++ apps/codecs/dumb/src/it/xmeffect.c | 242 +++ 38 files changed, 11544 insertions(+) create mode 100644 apps/codecs/dumb/src/allegro/alplay.c create mode 100644 apps/codecs/dumb/src/allegro/datduh.c create mode 100644 apps/codecs/dumb/src/allegro/datit.c create mode 100644 apps/codecs/dumb/src/allegro/datmod.c create mode 100644 apps/codecs/dumb/src/allegro/dats3m.c create mode 100644 apps/codecs/dumb/src/allegro/datunld.c create mode 100644 apps/codecs/dumb/src/allegro/datxm.c create mode 100644 apps/codecs/dumb/src/allegro/packfile.c create mode 100644 apps/codecs/dumb/src/core/atexit.c create mode 100644 apps/codecs/dumb/src/core/duhlen.c create mode 100644 apps/codecs/dumb/src/core/dumbfile.c create mode 100644 apps/codecs/dumb/src/core/loadduh.c create mode 100644 apps/codecs/dumb/src/core/makeduh.c create mode 100644 apps/codecs/dumb/src/core/rawsig.c create mode 100644 apps/codecs/dumb/src/core/readduh.c create mode 100644 apps/codecs/dumb/src/core/register.c create mode 100644 apps/codecs/dumb/src/core/rendduh.c create mode 100644 apps/codecs/dumb/src/core/rendsig.c create mode 100644 apps/codecs/dumb/src/core/unload.c create mode 100644 apps/codecs/dumb/src/helpers/clickrem.c create mode 100644 apps/codecs/dumb/src/helpers/memfile.c create mode 100644 apps/codecs/dumb/src/helpers/resample.c create mode 100644 apps/codecs/dumb/src/helpers/sampbuf.c create mode 100644 apps/codecs/dumb/src/helpers/silence.c create mode 100644 apps/codecs/dumb/src/helpers/stdfile.c create mode 100644 apps/codecs/dumb/src/it/itload.c create mode 100644 apps/codecs/dumb/src/it/itmisc.c create mode 100644 apps/codecs/dumb/src/it/itorder.c create mode 100644 apps/codecs/dumb/src/it/itread.c create mode 100644 apps/codecs/dumb/src/it/itrender.c create mode 100644 apps/codecs/dumb/src/it/itunload.c create mode 100644 apps/codecs/dumb/src/it/loadmod.c create mode 100644 apps/codecs/dumb/src/it/loads3m.c create mode 100644 apps/codecs/dumb/src/it/loadxm.c create mode 100644 apps/codecs/dumb/src/it/readmod.c create mode 100644 apps/codecs/dumb/src/it/reads3m.c create mode 100644 apps/codecs/dumb/src/it/readxm.c create mode 100644 apps/codecs/dumb/src/it/xmeffect.c (limited to 'apps/codecs/dumb/src') diff --git a/apps/codecs/dumb/src/allegro/alplay.c b/apps/codecs/dumb/src/allegro/alplay.c new file mode 100644 index 0000000000..983bde105b --- /dev/null +++ b/apps/codecs/dumb/src/allegro/alplay.c @@ -0,0 +1,270 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * alplay.c - Functions to play a DUH through / / \ \ + * an Allegro audio stream. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include + +#include "aldumb.h" + + + +#define ADP_PLAYING 1 + +struct AL_DUH_PLAYER +{ + int flags; + long bufsize; + int freq; + AUDIOSTREAM *stream; + DUH_SIGRENDERER *sigrenderer; /* If this is NULL, stream is invalid. */ + float volume; + int silentcount; +}; + + + +AL_DUH_PLAYER *al_start_duh(DUH *duh, int n_channels, long pos, float volume, long bufsize, int freq) +{ + AL_DUH_PLAYER *dp; + + /* This restriction is imposed by Allegro. */ + ASSERT(n_channels > 0); + ASSERT(n_channels <= 2); + + if (!duh) + return NULL; + + dp = malloc(sizeof(*dp)); + if (!dp) + return NULL; + + dp->flags = ADP_PLAYING; + dp->bufsize = bufsize; + dp->freq = freq; + + dp->stream = play_audio_stream(bufsize, 16, n_channels - 1, freq, 255, 128); + + if (!dp->stream) { + free(dp); + return NULL; + } + + voice_set_priority(dp->stream->voice, 255); + + dp->sigrenderer = duh_start_sigrenderer(duh, 0, n_channels, pos); + + if (!dp->sigrenderer) { + stop_audio_stream(dp->stream); + free(dp); + return NULL; + } + + dp->volume = volume; + dp->silentcount = 0; + + return dp; +} + + + +void al_stop_duh(AL_DUH_PLAYER *dp) +{ + if (dp) { + if (dp->sigrenderer) { + duh_end_sigrenderer(dp->sigrenderer); + stop_audio_stream(dp->stream); + } + free(dp); + } +} + + + +void al_pause_duh(AL_DUH_PLAYER *dp) +{ + if (dp && dp->sigrenderer && (dp->flags & ADP_PLAYING)) { + voice_stop(dp->stream->voice); + dp->flags &= ~ADP_PLAYING; + } +} + + + +void al_resume_duh(AL_DUH_PLAYER *dp) +{ + if (dp && dp->sigrenderer && !(dp->flags & ADP_PLAYING)) { + voice_start(dp->stream->voice); + dp->flags |= ADP_PLAYING; + } +} + + + +void al_duh_set_priority(AL_DUH_PLAYER *dp, int priority) +{ + if (dp && dp->sigrenderer) + voice_set_priority(dp->stream->voice, priority); +} + + + +void al_duh_set_volume(AL_DUH_PLAYER *dp, float volume) +{ + if (dp) + dp->volume = volume; +} + + + +int al_poll_duh(AL_DUH_PLAYER *dp) +{ + unsigned short *sptr; + long n; + long size; + int n_channels; + + if (!dp || !dp->sigrenderer) + return 1; + + if (!(dp->flags & ADP_PLAYING)) + return 0; + + sptr = get_audio_stream_buffer(dp->stream); + + if (!sptr) + return 0; + + n = duh_render(dp->sigrenderer, 16, 1, dp->volume, 65536.0 / dp->freq, dp->bufsize, sptr); + + if (n == 0) { + if (++dp->silentcount >= 2) { + duh_end_sigrenderer(dp->sigrenderer); + free_audio_stream_buffer(dp->stream); + stop_audio_stream(dp->stream); + dp->sigrenderer = NULL; + return 1; + } + } + + n_channels = duh_sigrenderer_get_n_channels(dp->sigrenderer); + n *= n_channels; + size = dp->bufsize * n_channels; + for (; n < size; n++) + sptr[n] = 0x8000; + + free_audio_stream_buffer(dp->stream); + + return 0; +} + + + +long al_duh_get_position(AL_DUH_PLAYER *dp) +{ + return dp ? duh_sigrenderer_get_position(dp->sigrenderer) : -1; +} + + + +AL_DUH_PLAYER *al_duh_encapsulate_sigrenderer(DUH_SIGRENDERER *sigrenderer, float volume, long bufsize, int freq) +{ + AL_DUH_PLAYER *dp; + int n_channels; + + if (!sigrenderer) + return NULL; + + dp = malloc(sizeof(*dp)); + if (!dp) + return NULL; + + n_channels = duh_sigrenderer_get_n_channels(sigrenderer); + + /* This restriction is imposed by Allegro. */ + ASSERT(n_channels > 0); + ASSERT(n_channels <= 2); + + dp->flags = ADP_PLAYING; + dp->bufsize = bufsize; + dp->freq = freq; + + dp->stream = play_audio_stream(bufsize, 16, n_channels - 1, freq, 255, 128); + + if (!dp->stream) { + free(dp); + return NULL; + } + + voice_set_priority(dp->stream->voice, 255); + + dp->sigrenderer = sigrenderer; + + dp->volume = volume; + dp->silentcount = 0; + + return dp; +} + + + +DUH_SIGRENDERER *al_duh_get_sigrenderer(AL_DUH_PLAYER *dp) +{ + return dp ? dp->sigrenderer : NULL; +} + + + +/* IMPORTANT: This function will return NULL if the music has ended. */ +// Should this be changed? User might want to hack the underlying SIGRENDERER +// and resurrect it (e.g. change pattern number), before it gets destroyed... +DUH_SIGRENDERER *al_duh_decompose_to_sigrenderer(AL_DUH_PLAYER *dp) +{ + if (dp) { + DUH_SIGRENDERER *sigrenderer = dp->sigrenderer; + if (sigrenderer) stop_audio_stream(dp->stream); + free(dp); + return sigrenderer; + } + return NULL; +} + + + +/* DEPRECATED */ +AL_DUH_PLAYER *al_duh_encapsulate_renderer(DUH_SIGRENDERER *dr, float volume, long bufsize, int freq) +{ + return al_duh_encapsulate_sigrenderer(dr, volume, bufsize, freq); +} + + + +/* DEPRECATED */ +DUH_SIGRENDERER *al_duh_get_renderer(AL_DUH_PLAYER *dp) +{ + return al_duh_get_sigrenderer(dp); +} + + + +/* DEPRECATED */ +DUH_SIGRENDERER *al_duh_decompose_to_renderer(AL_DUH_PLAYER *dp) +{ + return al_duh_decompose_to_sigrenderer(dp); +} diff --git a/apps/codecs/dumb/src/allegro/datduh.c b/apps/codecs/dumb/src/allegro/datduh.c new file mode 100644 index 0000000000..672e3c820c --- /dev/null +++ b/apps/codecs/dumb/src/allegro/datduh.c @@ -0,0 +1,60 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * datduh.c - Integration with Allegro's / / \ \ + * datafiles. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "aldumb.h" +#include "internal/aldumb.h" + + + +static void *dat_read_duh(PACKFILE *f, long size) +{ + DUMBFILE *df; + DUH *duh; + + (void)size; + + df = dumbfile_open_packfile(f); + + if (!df) + return NULL; + + duh = read_duh(df); + + dumbfile_close(df); + + return duh; +} + + + +/* dumb_register_dat_duh(): tells Allegro about the DUH datafile object. If + * you intend to load a datafile containing a DUH object, you must call this + * function first. It is recommended you pass DAT_DUH, but you may have a + * reason to use a different type (apart from pride, that doesn't count). + */ +void dumb_register_dat_duh(long type) +{ + register_datafile_object( + type, + &dat_read_duh, + &_dat_unload_duh + ); +} diff --git a/apps/codecs/dumb/src/allegro/datit.c b/apps/codecs/dumb/src/allegro/datit.c new file mode 100644 index 0000000000..8f58f14234 --- /dev/null +++ b/apps/codecs/dumb/src/allegro/datit.c @@ -0,0 +1,62 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * datit.c - Integration of IT files with / / \ \ + * Allegro's datafiles. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "aldumb.h" +#include "internal/aldumb.h" + + + +static void *dat_read_it(PACKFILE *f, long size) +{ + DUMBFILE *df; + DUH *duh; + + (void)size; + + df = dumbfile_open_packfile(f); + + if (!df) + return NULL; + + duh = dumb_read_it(df); + + dumbfile_close(df); + + return duh; +} + + + +/* dumb_register_dat_it(): tells Allegro about the IT datafile object. If you + * intend to load a datafile containing an IT object, you must call this + * function first. It is recommended you pass DUMB_DAT_IT, but you may have a + * reason to use a different type (perhaps you already have a datafile with + * IT files in and they use a different type). + */ +void dumb_register_dat_it(long type) +{ + register_datafile_object( + type, + &dat_read_it, + &_dat_unload_duh + ); +} + diff --git a/apps/codecs/dumb/src/allegro/datmod.c b/apps/codecs/dumb/src/allegro/datmod.c new file mode 100644 index 0000000000..850b17b444 --- /dev/null +++ b/apps/codecs/dumb/src/allegro/datmod.c @@ -0,0 +1,61 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * datmod.c - Integration of MOD files with / / \ \ + * Allegro's datafiles. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "aldumb.h" +#include "internal/aldumb.h" + + + +static void *dat_read_mod(PACKFILE *f, long size) +{ + DUMBFILE *df; + DUH *duh; + + (void)size; + + df = dumbfile_open_packfile(f); + + if (!df) + return NULL; + + duh = dumb_read_mod(df); + + dumbfile_close(df); + + return duh; +} + + + +/* dumb_register_dat_mod(): tells Allegro about the MOD datafile object. If + * you intend to load a datafile containing a MOD object, you must call this + * function first. It is recommended you pass DUMB_DAT_MOD, but you may have + * a reason to use a different type (perhaps you already have a datafile with + * MOD files in and they use a different type). + */ +void dumb_register_dat_mod(long type) +{ + register_datafile_object( + type, + &dat_read_mod, + &_dat_unload_duh + ); +} diff --git a/apps/codecs/dumb/src/allegro/dats3m.c b/apps/codecs/dumb/src/allegro/dats3m.c new file mode 100644 index 0000000000..a0fc74420e --- /dev/null +++ b/apps/codecs/dumb/src/allegro/dats3m.c @@ -0,0 +1,61 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * dats3m.c - Integration of S3M files with / / \ \ + * Allegro's datafiles. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "aldumb.h" +#include "internal/aldumb.h" + + + +static void *dat_read_s3m(PACKFILE *f, long size) +{ + DUMBFILE *df; + DUH *duh; + + (void)size; + + df = dumbfile_open_packfile(f); + + if (!df) + return NULL; + + duh = dumb_read_s3m(df); + + dumbfile_close(df); + + return duh; +} + + + +/* dumb_register_dat_s3m(): tells Allegro about the S3M datafile object. If + * you intend to load a datafile containing an S3M object, you must call this + * function first. It is recommended you pass DUMB_DAT_S3M, but you may have + * a reason to use a different type (perhaps you already have a datafile with + * S3M files in and they use a different type). + */ +void dumb_register_dat_s3m(long type) +{ + register_datafile_object( + type, + &dat_read_s3m, + &_dat_unload_duh + ); +} diff --git a/apps/codecs/dumb/src/allegro/datunld.c b/apps/codecs/dumb/src/allegro/datunld.c new file mode 100644 index 0000000000..71906445e0 --- /dev/null +++ b/apps/codecs/dumb/src/allegro/datunld.c @@ -0,0 +1,31 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * datunld.c - Unload function for integration / / \ \ + * with Allegro's datafiles. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "aldumb.h" +#include "internal/aldumb.h" + + + +void _dat_unload_duh(void *duh) +{ + unload_duh(duh); +} + diff --git a/apps/codecs/dumb/src/allegro/datxm.c b/apps/codecs/dumb/src/allegro/datxm.c new file mode 100644 index 0000000000..6cb98d87c1 --- /dev/null +++ b/apps/codecs/dumb/src/allegro/datxm.c @@ -0,0 +1,62 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * datxm.c - Integration of XM files with / / \ \ + * Allegro's datafiles. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "aldumb.h" +#include "internal/aldumb.h" + + + +static void *dat_read_xm(PACKFILE *f, long size) +{ + DUMBFILE *df; + DUH *duh; + + (void)size; + + df = dumbfile_open_packfile(f); + + if (!df) + return NULL; + + duh = dumb_read_xm(df); + + dumbfile_close(df); + + return duh; +} + + + +/* dumb_register_dat_xm(): tells Allegro about the XM datafile object. If you + * intend to load a datafile containing an XM object, you must call this + * function first. It is recommended you pass DUMB_DAT_XM, but you may have a + * reason to use a different type (perhaps you already have a datafile with + * XM files in and they use a different type). + */ +void dumb_register_dat_xm(long type) +{ + register_datafile_object( + type, + &dat_read_xm, + &_dat_unload_duh + ); +} + diff --git a/apps/codecs/dumb/src/allegro/packfile.c b/apps/codecs/dumb/src/allegro/packfile.c new file mode 100644 index 0000000000..525baebd4e --- /dev/null +++ b/apps/codecs/dumb/src/allegro/packfile.c @@ -0,0 +1,98 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * packfile.c - Packfile input module. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * Note that this does not use file compression; | \ / / + * for that you must open the file yourself and | ' / + * then use dumbfile_open_packfile(). \__/ + */ + +#include + +#include "aldumb.h" + + + +static void *dumb_packfile_open(const char *filename) +{ + return pack_fopen(filename, F_READ); +} + + + +static int dumb_packfile_skip(void *f, long n) +{ + return pack_fseek(f, n); +} + + + +static int dumb_packfile_getc(void *f) +{ + return pack_getc(f); +} + + + +static long dumb_packfile_getnc(char *ptr, long n, void *f) +{ + return pack_fread(ptr, n, f); +} + + + +static void dumb_packfile_close(void *f) +{ + pack_fclose(f); +} + + + +static DUMBFILE_SYSTEM packfile_dfs = { + &dumb_packfile_open, + &dumb_packfile_skip, + &dumb_packfile_getc, + &dumb_packfile_getnc, + &dumb_packfile_close +}; + + + +void dumb_register_packfiles(void) +{ + register_dumbfile_system(&packfile_dfs); +} + + + +static DUMBFILE_SYSTEM packfile_dfs_leave_open = { + NULL, + &dumb_packfile_skip, + &dumb_packfile_getc, + &dumb_packfile_getnc, + NULL +}; + + + +DUMBFILE *dumbfile_open_packfile(PACKFILE *p) +{ + return dumbfile_open_ex(p, &packfile_dfs_leave_open); +} + + + +DUMBFILE *dumbfile_from_packfile(PACKFILE *p) +{ + return p ? dumbfile_open_ex(p, &packfile_dfs) : NULL; +} diff --git a/apps/codecs/dumb/src/core/atexit.c b/apps/codecs/dumb/src/core/atexit.c new file mode 100644 index 0000000000..16c6abdb2c --- /dev/null +++ b/apps/codecs/dumb/src/core/atexit.c @@ -0,0 +1,71 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * atexit.c - Library Clean-up Management. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +typedef struct DUMB_ATEXIT_PROC +{ + struct DUMB_ATEXIT_PROC *next; + void (*proc)(void); +} +DUMB_ATEXIT_PROC; + + + +static DUMB_ATEXIT_PROC *dumb_atexit_proc = NULL; + + + +int dumb_atexit(void (*proc)(void)) +{ + DUMB_ATEXIT_PROC *dap = dumb_atexit_proc; + + while (dap) { + if (dap->proc == proc) return 0; + dap = dap->next; + } + + dap = malloc(sizeof(*dap)); + + if (!dap) + return -1; + + dap->next = dumb_atexit_proc; + dap->proc = proc; + dumb_atexit_proc = dap; + + return 0; +} + + + +void dumb_exit(void) +{ + while (dumb_atexit_proc) { + DUMB_ATEXIT_PROC *next = dumb_atexit_proc->next; + (*dumb_atexit_proc->proc)(); + free(dumb_atexit_proc); + dumb_atexit_proc = next; + } +} diff --git a/apps/codecs/dumb/src/core/duhlen.c b/apps/codecs/dumb/src/core/duhlen.c new file mode 100644 index 0000000000..4500d0a50f --- /dev/null +++ b/apps/codecs/dumb/src/core/duhlen.c @@ -0,0 +1,34 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * duhlen.c - Function to return the length of / / \ \ + * a DUH. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * Note that the length of a DUH is a constant | ' / + * stored in the DUH struct and in the DUH disk \__/ + * format. It will be calculated on loading for + * other formats in which the length is not explicitly stored. Also note that + * it does not necessarily correspond to the length of time for which the DUH + * will generate samples. Rather it represents a suitable point for a player + * such as Winamp to stop, and in any good DUH it will allow for any final + * flourish to fade out and be appreciated. + */ + +#include "dumb.h" +#include "internal/dumb.h" + + + +long duh_get_length(DUH *duh) +{ + return duh ? duh->length : 0; +} diff --git a/apps/codecs/dumb/src/core/dumbfile.c b/apps/codecs/dumb/src/core/dumbfile.c new file mode 100644 index 0000000000..71108c0c3b --- /dev/null +++ b/apps/codecs/dumb/src/core/dumbfile.c @@ -0,0 +1,401 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * dumbfile.c - Hookable, strictly sequential / / \ \ + * file input functions. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" + + + +static DUMBFILE_SYSTEM *the_dfs = NULL; + + + +void register_dumbfile_system(DUMBFILE_SYSTEM *dfs) +{ + ASSERT(dfs); + ASSERT(dfs->open); + ASSERT(dfs->getc); + ASSERT(dfs->close); + the_dfs = dfs; +} + + + +struct DUMBFILE +{ + DUMBFILE_SYSTEM *dfs; + void *file; + long pos; +}; + + + +DUMBFILE *dumbfile_open(const char *filename) +{ + DUMBFILE *f; + + ASSERT(the_dfs); + + f = malloc(sizeof(*f)); + + if (!f) + return NULL; + + f->dfs = the_dfs; + + f->file = (*the_dfs->open)(filename); + + if (!f->file) { + free(f); + return NULL; + } + + f->pos = 0; + + return f; +} + + + +DUMBFILE *dumbfile_open_ex(void *file, DUMBFILE_SYSTEM *dfs) +{ + DUMBFILE *f; + + ASSERT(dfs); + ASSERT(dfs->getc); + ASSERT(file); + + f = malloc(sizeof(*f)); + + if (!f) { + if (dfs->close) + (*dfs->close)(file); + return NULL; + } + + f->dfs = dfs; + f->file = file; + + f->pos = 0; + + return f; +} + + + +long dumbfile_pos(DUMBFILE *f) +{ + ASSERT(f); + + return f->pos; +} + + + +int dumbfile_skip(DUMBFILE *f, long n) +{ + int rv; + + ASSERT(f); + ASSERT(n >= 0); + + if (f->pos < 0) + return -1; + + f->pos += n; + + if (f->dfs->skip) { + rv = (*f->dfs->skip)(f->file, n); + if (rv) { + f->pos = -1; + return rv; + } + } else { + while (n) { + rv = (*f->dfs->getc)(f->file); + if (rv < 0) { + f->pos = -1; + return rv; + } + n--; + } + } + + return 0; +} + + + +int dumbfile_getc(DUMBFILE *f) +{ + int rv; + + ASSERT(f); + + if (f->pos < 0) + return -1; + + rv = (*f->dfs->getc)(f->file); + + if (rv < 0) { + f->pos = -1; + return rv; + } + + f->pos++; + + return rv; +} + + + +int dumbfile_igetw(DUMBFILE *f) +{ + int l, h; + + ASSERT(f); + + if (f->pos < 0) + return -1; + + l = (*f->dfs->getc)(f->file); + if (l < 0) { + f->pos = -1; + return l; + } + + h = (*f->dfs->getc)(f->file); + if (h < 0) { + f->pos = -1; + return h; + } + + f->pos += 2; + + return l | (h << 8); +} + + + +int dumbfile_mgetw(DUMBFILE *f) +{ + int l, h; + + ASSERT(f); + + if (f->pos < 0) + return -1; + + h = (*f->dfs->getc)(f->file); + if (h < 0) { + f->pos = -1; + return h; + } + + l = (*f->dfs->getc)(f->file); + if (l < 0) { + f->pos = -1; + return l; + } + + f->pos += 2; + + return l | (h << 8); +} + + + +long dumbfile_igetl(DUMBFILE *f) +{ + unsigned long rv, b; + + ASSERT(f); + + if (f->pos < 0) + return -1; + + rv = (*f->dfs->getc)(f->file); + if ((signed long)rv < 0) { + f->pos = -1; + return rv; + } + + b = (*f->dfs->getc)(f->file); + if ((signed long)b < 0) { + f->pos = -1; + return b; + } + rv |= b << 8; + + b = (*f->dfs->getc)(f->file); + if ((signed long)b < 0) { + f->pos = -1; + return b; + } + rv |= b << 16; + + b = (*f->dfs->getc)(f->file); + if ((signed long)b < 0) { + f->pos = -1; + return b; + } + rv |= b << 24; + + f->pos += 4; + + return rv; +} + + + +long dumbfile_mgetl(DUMBFILE *f) +{ + unsigned long rv, b; + + ASSERT(f); + + if (f->pos < 0) + return -1; + + rv = (*f->dfs->getc)(f->file); + if ((signed long)rv < 0) { + f->pos = -1; + return rv; + } + rv <<= 24; + + b = (*f->dfs->getc)(f->file); + if ((signed long)b < 0) { + f->pos = -1; + return b; + } + rv |= b << 16; + + b = (*f->dfs->getc)(f->file); + if ((signed long)b < 0) { + f->pos = -1; + return b; + } + rv |= b << 8; + + b = (*f->dfs->getc)(f->file); + if ((signed long)b < 0) { + f->pos = -1; + return b; + } + rv |= b; + + f->pos += 4; + + return rv; +} + + + +unsigned long dumbfile_cgetul(DUMBFILE *f) +{ + unsigned long rv = 0; + int v; + + do { + v = dumbfile_getc(f); + + if (v < 0) + return v; + + rv <<= 7; + rv |= v & 0x7F; + } while (v & 0x80); + + return rv; +} + + + +signed long dumbfile_cgetsl(DUMBFILE *f) +{ + unsigned long rv = dumbfile_cgetul(f); + + if (f->pos < 0) + return rv; + + return (rv >> 1) | (rv << 31); +} + + + +long dumbfile_getnc(char *ptr, long n, DUMBFILE *f) +{ + long rv; + + ASSERT(f); + ASSERT(n >= 0); + + if (f->pos < 0) + return -1; + + if (f->dfs->getnc) { + rv = (*f->dfs->getnc)(ptr, n, f->file); + if (rv < n) { + f->pos = -1; + return MAX(rv, 0); + } + } else { + for (rv = 0; rv < n; rv++) { + int c = (*f->dfs->getc)(f->file); + if (c < 0) { + f->pos = -1; + return rv; + } + *ptr++ = c; + } + } + + f->pos += rv; + + return rv; +} + + + +int dumbfile_error(DUMBFILE *f) +{ + ASSERT(f); + + return f->pos < 0; +} + + + +int dumbfile_close(DUMBFILE *f) +{ + int rv; + + ASSERT(f); + + rv = f->pos < 0; + + if (f->dfs->close) + (*f->dfs->close)(f->file); + + free(f); + + return rv; +} diff --git a/apps/codecs/dumb/src/core/loadduh.c b/apps/codecs/dumb/src/core/loadduh.c new file mode 100644 index 0000000000..7dfe5cc100 --- /dev/null +++ b/apps/codecs/dumb/src/core/loadduh.c @@ -0,0 +1,42 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * loadduh.c - Code to read a DUH from a file, / / \ \ + * opening and closing the file for | < / \_ + * you. | \/ /\ / + * \_ / > / + * By entheh. | \ / / + * | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/dumb.h" + + + +/* load_duh(): loads a .duh file, returning a pointer to a DUH struct. + * When you have finished with it, you must pass the pointer to unload_duh() + * so that the memory can be freed. + */ +DUH *load_duh(const char *filename) +{ + DUH *duh; + DUMBFILE *f = dumbfile_open(filename); + + if (!f) + return NULL; + + duh = read_duh(f); + + dumbfile_close(f); + + return duh; +} diff --git a/apps/codecs/dumb/src/core/makeduh.c b/apps/codecs/dumb/src/core/makeduh.c new file mode 100644 index 0000000000..1e422fb502 --- /dev/null +++ b/apps/codecs/dumb/src/core/makeduh.c @@ -0,0 +1,92 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * makeduh.c - Function to construct a DUH from / / \ \ + * its components. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +static DUH_SIGNAL *make_signal(DUH_SIGTYPE_DESC *desc, sigdata_t *sigdata) +{ + DUH_SIGNAL *signal; + + ASSERT((desc->start_sigrenderer && desc->end_sigrenderer) || (!desc->start_sigrenderer && !desc->end_sigrenderer)); + ASSERT(desc->sigrenderer_get_samples && desc->sigrenderer_get_current_sample); + + signal = malloc(sizeof(*signal)); + + if (!signal) { + if (desc->unload_sigdata) + if (sigdata) + (*desc->unload_sigdata)(sigdata); + return NULL; + } + + signal->desc = desc; + signal->sigdata = sigdata; + + return signal; +} + + + +DUH *make_duh(long length, int n_signals, DUH_SIGTYPE_DESC *desc[], sigdata_t *sigdata[]) +{ + DUH *duh = malloc(sizeof(*duh)); + int i; + int fail; + + if (duh) { + duh->n_signals = n_signals; + + duh->signal = malloc(n_signals * sizeof(*duh->signal)); + + if (!duh->signal) { + free(duh); + duh = NULL; + } + } + + if (!duh) { + for (i = 0; i < n_signals; i++) + if (desc[i]->unload_sigdata) + if (sigdata[i]) + (*desc[i]->unload_sigdata)(sigdata[i]); + return NULL; + } + + fail = 0; + + for (i = 0; i < n_signals; i++) { + duh->signal[i] = make_signal(desc[i], sigdata[i]); + if (!duh->signal[i]) + fail = 1; + } + + if (fail) { + unload_duh(duh); + return NULL; + } + + duh->length = length; + + return duh; +} diff --git a/apps/codecs/dumb/src/core/rawsig.c b/apps/codecs/dumb/src/core/rawsig.c new file mode 100644 index 0000000000..926c990655 --- /dev/null +++ b/apps/codecs/dumb/src/core/rawsig.c @@ -0,0 +1,44 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * rawsig.c - Function to retrieve raw signal / / \ \ + * data from a DUH provided you know | < / \_ + * what type of signal it is. | \/ /\ / + * \_ / > / + * By entheh. | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +/* You have to specify the type of sigdata, proving you know what to do with + * the pointer. If you get it wrong, you can expect NULL back. + */ +sigdata_t *duh_get_raw_sigdata(DUH *duh, int sig, long type) +{ + DUH_SIGNAL *signal; + + if (!duh) return NULL; + + if ((unsigned int)sig >= (unsigned int)duh->n_signals) return NULL; + + signal = duh->signal[sig]; + + if (signal && signal->desc->type == type) + return signal->sigdata; + + return NULL; +} diff --git a/apps/codecs/dumb/src/core/readduh.c b/apps/codecs/dumb/src/core/readduh.c new file mode 100644 index 0000000000..514b04a077 --- /dev/null +++ b/apps/codecs/dumb/src/core/readduh.c @@ -0,0 +1,107 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * readduh.c - Code to read a DUH from an open / / \ \ + * file. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +static DUH_SIGNAL *read_signal(DUH *duh, DUMBFILE *f) +{ + DUH_SIGNAL *signal; + long type; + + signal = malloc(sizeof(*signal)); + + if (!signal) + return NULL; + + type = dumbfile_mgetl(f); + if (dumbfile_error(f)) { + free(signal); + return NULL; + } + + signal->desc = _dumb_get_sigtype_desc(type); + if (!signal->desc) { + free(signal); + return NULL; + } + + if (signal->desc->load_sigdata) { + signal->sigdata = (*signal->desc->load_sigdata)(duh, f); + if (!signal->sigdata) { + free(signal); + return NULL; + } + } else + signal->sigdata = NULL; + + return signal; +} + + + +/* read_duh(): reads a DUH from an already open DUMBFILE, and returns its + * pointer, or null on error. The file is not closed. + */ +DUH *read_duh(DUMBFILE *f) +{ + DUH *duh; + int i; + + if (dumbfile_mgetl(f) != DUH_SIGNATURE) + return NULL; + + duh = malloc(sizeof(*duh)); + if (!duh) + return NULL; + + duh->length = dumbfile_igetl(f); + if (dumbfile_error(f) || duh->length <= 0) { + free(duh); + return NULL; + } + + duh->n_signals = dumbfile_igetl(f); + if (dumbfile_error(f) || duh->n_signals <= 0) { + free(duh); + return NULL; + } + + duh->signal = malloc(sizeof(*duh->signal) * duh->n_signals); + if (!duh->signal) { + free(duh); + return NULL; + } + + for (i = 0; i < duh->n_signals; i++) + duh->signal[i] = NULL; + + for (i = 0; i < duh->n_signals; i++) { + if (!(duh->signal[i] = read_signal(duh, f))) { + unload_duh(duh); + return NULL; + } + } + + return duh; +} diff --git a/apps/codecs/dumb/src/core/register.c b/apps/codecs/dumb/src/core/register.c new file mode 100644 index 0000000000..9eed45f796 --- /dev/null +++ b/apps/codecs/dumb/src/core/register.c @@ -0,0 +1,104 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * register.c - Signal type registration. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +static DUH_SIGTYPE_DESC_LINK *sigtype_desc = NULL; +static DUH_SIGTYPE_DESC_LINK **sigtype_desc_tail = &sigtype_desc; + + + +/* destroy_sigtypes(): frees all memory allocated while registering signal + * types. This function is set up to be called by dumb_exit(). + */ +static void destroy_sigtypes(void) +{ + DUH_SIGTYPE_DESC_LINK *desc_link = sigtype_desc, *next; + sigtype_desc = NULL; + sigtype_desc_tail = &sigtype_desc; + + while (desc_link) { + next = desc_link->next; + free(desc_link); + desc_link = next; + } +} + + + +/* dumb_register_sigtype(): registers a new signal type with DUMB. The signal + * type is identified by a four-character string (e.g. "WAVE"), which you can + * encode using the the DUMB_ID() macro (e.g. DUMB_ID('W','A','V','E')). The + * signal's behaviour is defined by four functions, whose pointers you pass + * here. See the documentation for details. + * + * If a DUH tries to use a signal that has not been registered using this + * function, then the library will fail to load the DUH. + */ +void dumb_register_sigtype(DUH_SIGTYPE_DESC *desc) +{ + DUH_SIGTYPE_DESC_LINK *desc_link = sigtype_desc; + + ASSERT((desc->load_sigdata && desc->unload_sigdata) || (!desc->load_sigdata && !desc->unload_sigdata)); + ASSERT((desc->start_sigrenderer && desc->end_sigrenderer) || (!desc->start_sigrenderer && !desc->end_sigrenderer)); + ASSERT(desc->sigrenderer_get_samples && desc->sigrenderer_get_current_sample); + + if (desc_link) { + do { + if (desc_link->desc->type == desc->type) { + desc_link->desc = desc; + return; + } + desc_link = desc_link->next; + } while (desc_link); + } else + dumb_atexit(&destroy_sigtypes); + + desc_link = *sigtype_desc_tail = malloc(sizeof(DUH_SIGTYPE_DESC_LINK)); + + if (!desc_link) + return; + + desc_link->next = NULL; + sigtype_desc_tail = &desc_link->next; + + desc_link->desc = desc; +} + + + +/* _dumb_get_sigtype_desc(): searches the registered functions for a signal + * type matching the parameter. If such a sigtype is found, it returns a + * pointer to a sigtype descriptor containing the necessary functions to + * manage the signal. If none is found, it returns NULL. + */ +DUH_SIGTYPE_DESC *_dumb_get_sigtype_desc(long type) +{ + DUH_SIGTYPE_DESC_LINK *desc_link = sigtype_desc; + + while (desc_link && desc_link->desc->type != type) + desc_link = desc_link->next; + + return desc_link->desc; +} diff --git a/apps/codecs/dumb/src/core/rendduh.c b/apps/codecs/dumb/src/core/rendduh.c new file mode 100644 index 0000000000..39db8ab8a3 --- /dev/null +++ b/apps/codecs/dumb/src/core/rendduh.c @@ -0,0 +1,202 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * rendduh.c - Functions for rendering a DUH into / / \ \ + * an end-user sample format. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +/* On the x86, we can use some tricks to speed stuff up */ +#if (defined _MSC_VER) || (defined __DJGPP__) || (defined __MINGW__) +// Can't we detect Linux and other x86 platforms here? :/ + +#define FAST_MID(var, min, max) { \ + var -= (min); \ + var &= (~var) >> (sizeof(var) * CHAR_BIT - 1); \ + var += (min); \ + var -= (max); \ + var &= var >> (sizeof(var) * CHAR_BIT - 1); \ + var += (max); \ +} + +#define CONVERT8(src, pos, signconv) { \ + signed int f = (src + 0x8000) >> 16; \ + FAST_MID(f, -128, 127); \ + ((char*)sptr)[pos] = (char)f ^ signconv; \ +} + +#define CONVERT16(src, pos, signconv) { \ + signed int f = (src + 0x80) >> 8; \ + FAST_MID(f, -32768, 32767); \ + ((short*)sptr)[pos] = (short)(f ^ signconv); \ +} + +#else + +#define CONVERT8(src, pos, signconv) \ +{ \ + signed int f = (src + 0x8000) >> 16; \ + f = MID(-128, f, 127); \ + ((char *)sptr)[pos] = (char)f ^ signconv; \ +} + + + +#define CONVERT16(src, pos, signconv) \ +{ \ + signed int f = (src + 0x80) >> 8; \ + f = MID(-32768, f, 32767); \ + ((short *)sptr)[pos] = (short)(f ^ signconv); \ +} + +#endif + + + +/* DEPRECATED */ +DUH_SIGRENDERER *duh_start_renderer(DUH *duh, int n_channels, long pos) +{ + return duh_start_sigrenderer(duh, 0, n_channels, pos); +} + + + +long duh_render( + DUH_SIGRENDERER *sigrenderer, + int bits, int unsign, + float volume, float delta, + long size, void *sptr +) +{ + long n; + + sample_t **sampptr; + + int n_channels; + + ASSERT(bits == 8 || bits == 16); + ASSERT(sptr); + + if (!sigrenderer) + return 0; + + n_channels = duh_sigrenderer_get_n_channels(sigrenderer); + + ASSERT(n_channels > 0); + /* This restriction will be removed when need be. At the moment, tightly + * optimised loops exist for exactly one or two channels. + */ + ASSERT(n_channels <= 2); + + sampptr = create_sample_buffer(n_channels, size); + + if (!sampptr) + return 0; + + dumb_silence(sampptr[0], n_channels * size); + + size = duh_sigrenderer_get_samples(sigrenderer, volume, delta, size, sampptr); + + if (bits == 16) { + int signconv = unsign ? 0x8000 : 0x0000; + + if (n_channels == 2) { + for (n = 0; n < size; n++) { + CONVERT16(sampptr[0][n], n << 1, signconv); + } + for (n = 0; n < size; n++) { + CONVERT16(sampptr[1][n], (n << 1) + 1, signconv); + } + } else { + for (n = 0; n < size; n++) { + CONVERT16(sampptr[0][n], n, signconv); + } + } + } else { + char signconv = unsign ? 0x80 : 0x00; + + if (n_channels == 2) { + for (n = 0; n < size; n++) { + CONVERT8(sampptr[0][n], n << 1, signconv); + } + for (n = 0; n < size; n++) { + CONVERT8(sampptr[1][n], (n << 1) + 1, signconv); + } + } else { + for (n = 0; n < size; n++) { + CONVERT8(sampptr[0][n], n, signconv); + } + } + } + + destroy_sample_buffer(sampptr); + + return size; +} + + + +/* DEPRECATED */ +int duh_renderer_get_n_channels(DUH_SIGRENDERER *dr) +{ + return duh_sigrenderer_get_n_channels(dr); +} + + + +/* DEPRECATED */ +long duh_renderer_get_position(DUH_SIGRENDERER *dr) +{ + return duh_sigrenderer_get_position(dr); +} + + + +/* DEPRECATED */ +void duh_end_renderer(DUH_SIGRENDERER *dr) +{ + duh_end_sigrenderer(dr); +} + + + +/* DEPRECATED */ +DUH_SIGRENDERER *duh_renderer_encapsulate_sigrenderer(DUH_SIGRENDERER *sigrenderer) +{ + return sigrenderer; +} + + + +/* DEPRECATED */ +DUH_SIGRENDERER *duh_renderer_get_sigrenderer(DUH_SIGRENDERER *dr) +{ + return dr; +} + + + +/* DEPRECATED */ +DUH_SIGRENDERER *duh_renderer_decompose_to_sigrenderer(DUH_SIGRENDERER *dr) +{ + return dr; +} diff --git a/apps/codecs/dumb/src/core/rendsig.c b/apps/codecs/dumb/src/core/rendsig.c new file mode 100644 index 0000000000..a36ceb41cf --- /dev/null +++ b/apps/codecs/dumb/src/core/rendsig.c @@ -0,0 +1,299 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * rendsig.c - Wrappers to render samples from / / \ \ + * the signals in a DUH. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +struct DUH_SIGRENDERER +{ + DUH_SIGTYPE_DESC *desc; + + sigrenderer_t *sigrenderer; + + int n_channels; + + long pos; + int subpos; + + DUH_SIGRENDERER_ANALYSER_CALLBACK callback; + void *callback_data; +}; + + + +DUH_SIGRENDERER *duh_start_sigrenderer(DUH *duh, int sig, int n_channels, long pos) +{ + DUH_SIGRENDERER *sigrenderer; + + DUH_SIGNAL *signal; + DUH_START_SIGRENDERER proc; + + if ((unsigned int)sig >= (unsigned int)duh->n_signals) + return NULL; + + signal = duh->signal[sig]; + if (!signal) + return NULL; + + sigrenderer = malloc(sizeof(*sigrenderer)); + if (!sigrenderer) + return NULL; + + sigrenderer->desc = signal->desc; + + proc = sigrenderer->desc->start_sigrenderer; + + if (proc) { + duh->signal[sig] = NULL; + sigrenderer->sigrenderer = (*proc)(duh, signal->sigdata, n_channels, pos); + duh->signal[sig] = signal; + + if (!sigrenderer->sigrenderer) { + free(sigrenderer); + return NULL; + } + } else + sigrenderer->sigrenderer = NULL; + + sigrenderer->n_channels = n_channels; + + sigrenderer->pos = pos; + sigrenderer->subpos = 0; + + sigrenderer->callback = NULL; + + return sigrenderer; +} + + + +#include +void duh_sigrenderer_set_callback( + DUH_SIGRENDERER *sigrenderer, + DUH_SIGRENDERER_CALLBACK callback, void *data +) +{ + (void)sigrenderer; + (void)callback; + (void)data; +/* FIXME + fprintf(stderr, + "Call to deprecated function duh_sigrenderer_set_callback(). The callback\n" + "was not installed. See dumb/docs/deprec.txt for how to fix this.\n");*/ +} + + + +void duh_sigrenderer_set_analyser_callback( + DUH_SIGRENDERER *sigrenderer, + DUH_SIGRENDERER_ANALYSER_CALLBACK callback, void *data +) +{ + if (sigrenderer) { + sigrenderer->callback = callback; + sigrenderer->callback_data = data; + } +} + + + +int duh_sigrenderer_get_n_channels(DUH_SIGRENDERER *sigrenderer) +{ + return sigrenderer ? sigrenderer->n_channels : 0; +} + + + +long duh_sigrenderer_get_position(DUH_SIGRENDERER *sigrenderer) +{ + return sigrenderer ? sigrenderer->pos : -1; +} + + + +void duh_sigrenderer_set_sigparam( + DUH_SIGRENDERER *sigrenderer, + unsigned char id, long value +) +{ + DUH_SIGRENDERER_SET_SIGPARAM proc; + + if (!sigrenderer) return; + + proc = sigrenderer->desc->sigrenderer_set_sigparam; + if (proc) + (*proc)(sigrenderer->sigrenderer, id, value); + else + TRACE("Parameter #%d = %ld for signal %c%c%c%c, which does not take parameters.\n", + (int)id, + value, + (int)(sigrenderer->desc->type >> 24), + (int)(sigrenderer->desc->type >> 16), + (int)(sigrenderer->desc->type >> 8), + (int)(sigrenderer->desc->type)); +} + + + +long duh_sigrenderer_get_samples( + DUH_SIGRENDERER *sigrenderer, + float volume, float delta, + long size, sample_t **samples +) +{ + long rendered; + LONG_LONG t; + + if (!sigrenderer) return 0; + + rendered = (*sigrenderer->desc->sigrenderer_get_samples) + (sigrenderer->sigrenderer, volume, delta, size, samples); + + if (rendered) { + if (sigrenderer->callback) + (*sigrenderer->callback)(sigrenderer->callback_data, + (const sample_t *const *)samples, sigrenderer->n_channels, rendered); + + t = sigrenderer->subpos + (LONG_LONG)(delta * 65536.0 + 0.5) * rendered; + + sigrenderer->pos += (long)(t >> 16); + sigrenderer->subpos = (int)t & 65535; + } + + return rendered; +} + + + +/* DEPRECATED */ +long duh_render_signal( + DUH_SIGRENDERER *sigrenderer, + float volume, float delta, + long size, sample_t **samples +) +{ + sample_t **s = create_sample_buffer(sigrenderer->n_channels, size); + long rendered; + long i; + int j; + if (!s) return 0; + rendered = duh_sigrenderer_get_samples(sigrenderer, volume, delta, size, s); + for (j = 0; j < sigrenderer->n_channels; j++) + for (i = 0; i < rendered; i++) + samples[j][i] += s[j][i] >> 8; + destroy_sample_buffer(s); + return rendered; +} + + + +void duh_sigrenderer_get_current_sample(DUH_SIGRENDERER *sigrenderer, float volume, sample_t *samples) +{ + if (sigrenderer) + (*sigrenderer->desc->sigrenderer_get_current_sample)(sigrenderer->sigrenderer, volume, samples); +} + + + +void duh_end_sigrenderer(DUH_SIGRENDERER *sigrenderer) +{ + if (sigrenderer) { + if (sigrenderer->desc->end_sigrenderer) + if (sigrenderer->sigrenderer) + (*sigrenderer->desc->end_sigrenderer)(sigrenderer->sigrenderer); + + free(sigrenderer); + } +} + + + +DUH_SIGRENDERER *duh_encapsulate_raw_sigrenderer(sigrenderer_t *vsigrenderer, DUH_SIGTYPE_DESC *desc, int n_channels, long pos) +{ + DUH_SIGRENDERER *sigrenderer; + + if (desc->start_sigrenderer && !vsigrenderer) return NULL; + + sigrenderer = malloc(sizeof(*sigrenderer)); + if (!sigrenderer) { + if (desc->end_sigrenderer) + if (vsigrenderer) + (*desc->end_sigrenderer)(vsigrenderer); + return NULL; + } + + sigrenderer->desc = desc; + sigrenderer->sigrenderer = vsigrenderer; + + sigrenderer->n_channels = n_channels; + + sigrenderer->pos = pos; + sigrenderer->subpos = 0; + + sigrenderer->callback = NULL; + + return sigrenderer; +} + + + +sigrenderer_t *duh_get_raw_sigrenderer(DUH_SIGRENDERER *sigrenderer, long type) +{ + if (sigrenderer && sigrenderer->desc->type == type) + return sigrenderer->sigrenderer; + + return NULL; +} + + + +#if 0 +// This function is disabled because we don't know whether we want to destroy +// the sigrenderer if the type doesn't match. We don't even know if we need +// the function at all. Who would want to keep an IT_SIGRENDERER (for +// instance) without keeping the DUH_SIGRENDERER? +sigrenderer_t *duh_decompose_to_raw_sigrenderer(DUH_SIGRENDERER *sigrenderer, long type) +{ + if (sigrenderer && sigrenderer->desc->type == type) { + + + + if (sigrenderer) { + if (sigrenderer->desc->end_sigrenderer) + if (sigrenderer->sigrenderer) + (*sigrenderer->desc->end_sigrenderer)(sigrenderer->sigrenderer); + + free(sigrenderer); + } + + + + + + + return sigrenderer->sigrenderer; + } + + return NULL; +} +#endif diff --git a/apps/codecs/dumb/src/core/unload.c b/apps/codecs/dumb/src/core/unload.c new file mode 100644 index 0000000000..3bf0285cd1 --- /dev/null +++ b/apps/codecs/dumb/src/core/unload.c @@ -0,0 +1,58 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * unload.c - Code to free a DUH from memory. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/dumb.h" + + + +static void destroy_signal(DUH_SIGNAL *signal) +{ + if (signal) { + if (signal->desc) + if (signal->desc->unload_sigdata) + if (signal->sigdata) + (*signal->desc->unload_sigdata)(signal->sigdata); + + free(signal); + } +} + + + +/* unload_duh(): destroys a DUH struct. You must call this for every DUH + * struct created, when you've finished with it. + */ +void unload_duh(DUH *duh) +{ + int i; + + if (duh) { + if (duh->signal) { + for (i = 0; i < duh->n_signals; i++) + destroy_signal(duh->signal[i]); + + free(duh->signal); + } + + free(duh); + } +} diff --git a/apps/codecs/dumb/src/helpers/clickrem.c b/apps/codecs/dumb/src/helpers/clickrem.c new file mode 100644 index 0000000000..ddc861d931 --- /dev/null +++ b/apps/codecs/dumb/src/helpers/clickrem.c @@ -0,0 +1,270 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * clickrem.c - Click removal helpers. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include +#include "dumb.h" + + + +typedef struct DUMB_CLICK DUMB_CLICK; + + +struct DUMB_CLICK_REMOVER +{ + DUMB_CLICK *click; + int n_clicks; + + int offset; +}; + + +struct DUMB_CLICK +{ + DUMB_CLICK *next; + long pos; + sample_t step; +}; + + + +DUMB_CLICK_REMOVER *dumb_create_click_remover(void) +{ + DUMB_CLICK_REMOVER *cr = malloc(sizeof(*cr)); + if (!cr) return NULL; + + cr->click = NULL; + cr->n_clicks = 0; + + cr->offset = 0; + + return cr; +} + + + +void dumb_record_click(DUMB_CLICK_REMOVER *cr, long pos, sample_t step) +{ + DUMB_CLICK *click; + + ASSERT(pos >= 0); + + if (!cr || !step) return; + + if (pos == 0) { + cr->offset -= step; + return; + } + + click = malloc(sizeof(*click)); + if (!click) return; + + click->pos = pos; + click->step = step; + + click->next = cr->click; + cr->click = click; + cr->n_clicks++; +} + + + +static DUMB_CLICK *dumb_click_mergesort(DUMB_CLICK *click, int n_clicks) +{ + int i; + DUMB_CLICK *c1, *c2, **cp; + + if (n_clicks <= 1) return click; + + /* Split the list into two */ + c1 = click; + cp = &c1; + for (i = 0; i < n_clicks; i += 2) cp = &(*cp)->next; + c2 = *cp; + *cp = NULL; + + /* Sort the sublists */ + c1 = dumb_click_mergesort(c1, (n_clicks + 1) >> 1); + c2 = dumb_click_mergesort(c2, n_clicks >> 1); + + /* Merge them */ + cp = &click; + while (c1 && c2) { + if (c1->pos > c2->pos) { + *cp = c2; + c2 = c2->next; + } else { + *cp = c1; + c1 = c1->next; + } + cp = &(*cp)->next; + } + if (c2) + *cp = c2; + else + *cp = c1; + + return click; +} + + + +void dumb_remove_clicks(DUMB_CLICK_REMOVER *cr, sample_t *samples, long length, float halflife) +{ + DUMB_CLICK *click; + long pos = 0; + int offset; + int factor; + + if (!cr) return; + + factor = (int)floor(pow(0.5, 1.0/halflife) * (1U << 31)); + + click = dumb_click_mergesort(cr->click, cr->n_clicks); + cr->click = NULL; + cr->n_clicks = 0; + + while (click) { + DUMB_CLICK *next = click->next; + ASSERT(click->pos <= length); + offset = cr->offset; + if (offset < 0) { + offset = -offset; + while (pos < click->pos) { + samples[pos++] -= offset; + offset = (int)((LONG_LONG)(offset << 1) * factor >> 32); + } + offset = -offset; + } else { + while (pos < click->pos) { + samples[pos++] += offset; + offset = (int)((LONG_LONG)(offset << 1) * factor >> 32); + } + } + cr->offset = offset - click->step; + free(click); + click = next; + } + + offset = cr->offset; + if (offset < 0) { + offset = -offset; + while (pos < length) { + samples[pos++] -= offset; + offset = (int)((LONG_LONG)(offset << 1) * factor >> 32); + } + offset = -offset; + } else { + while (pos < length) { + samples[pos++] += offset; + offset = (int)((LONG_LONG)(offset << 1) * factor >> 32); + } + } + cr->offset = offset; +} + + + +sample_t dumb_click_remover_get_offset(DUMB_CLICK_REMOVER *cr) +{ + return cr ? cr->offset : 0; +} + + + +void dumb_destroy_click_remover(DUMB_CLICK_REMOVER *cr) +{ + if (cr) { + DUMB_CLICK *click = cr->click; + while (click) { + DUMB_CLICK *next = click->next; + free(click); + click = next; + } + free(cr); + } +} + + + +DUMB_CLICK_REMOVER **dumb_create_click_remover_array(int n) +{ + int i; + DUMB_CLICK_REMOVER **cr; + if (n <= 0) return NULL; + cr = malloc(n * sizeof(*cr)); + if (!cr) return NULL; + for (i = 0; i < n; i++) cr[i] = dumb_create_click_remover(); + return cr; +} + + + +void dumb_record_click_array(int n, DUMB_CLICK_REMOVER **cr, long pos, sample_t *step) +{ + if (cr) { + int i; + for (i = 0; i < n; i++) + dumb_record_click(cr[i], pos, step[i]); + } +} + + + +void dumb_record_click_negative_array(int n, DUMB_CLICK_REMOVER **cr, long pos, sample_t *step) +{ + if (cr) { + int i; + for (i = 0; i < n; i++) + dumb_record_click(cr[i], pos, -step[i]); + } +} + + + +void dumb_remove_clicks_array(int n, DUMB_CLICK_REMOVER **cr, sample_t **samples, long length, float halflife) +{ + if (cr) { + int i; + for (i = 0; i < n; i++) + dumb_remove_clicks(cr[i], samples[i], length, halflife); + } +} + + + +void dumb_click_remover_get_offset_array(int n, DUMB_CLICK_REMOVER **cr, sample_t *offset) +{ + if (cr) { + int i; + for (i = 0; i < n; i++) + if (cr[i]) offset[i] += cr[i]->offset; + } +} + + + +void dumb_destroy_click_remover_array(int n, DUMB_CLICK_REMOVER **cr) +{ + if (cr) { + int i; + for (i = 0; i < n; i++) dumb_destroy_click_remover(cr[i]); + free(cr); + } +} diff --git a/apps/codecs/dumb/src/helpers/memfile.c b/apps/codecs/dumb/src/helpers/memfile.c new file mode 100644 index 0000000000..b65ab5f78d --- /dev/null +++ b/apps/codecs/dumb/src/helpers/memfile.c @@ -0,0 +1,96 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * memfile.c - Module for reading data from / / \ \ + * memory using a DUMBFILE. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include + +#include "dumb.h" + + + +typedef struct MEMFILE MEMFILE; + +struct MEMFILE +{ + const char *ptr; + long left; +}; + + + +static int dumb_memfile_skip(void *f, long n) +{ + MEMFILE *m = f; + if (n > m->left) return -1; + m->ptr += n; + m->left -= n; + return 0; +} + + + +static int dumb_memfile_getc(void *f) +{ + MEMFILE *m = f; + if (m->left <= 0) return -1; + m->left--; + return *(const unsigned char *)m->ptr++; +} + + + +static long dumb_memfile_getnc(char *ptr, long n, void *f) +{ + MEMFILE *m = f; + if (n > m->left) n = m->left; + memcpy(ptr, m->ptr, n); + m->ptr += n; + m->left -= n; + return n; +} + + + +static void dumb_memfile_close(void *f) +{ + free(f); +} + + + +static DUMBFILE_SYSTEM memfile_dfs = { + NULL, + &dumb_memfile_skip, + &dumb_memfile_getc, + &dumb_memfile_getnc, + &dumb_memfile_close +}; + + + +DUMBFILE *dumbfile_open_memory(const char *data, long size) +{ + MEMFILE *m = malloc(sizeof(*m)); + if (!m) return NULL; + + m->ptr = data; + m->left = size; + + return dumbfile_open_ex(m, &memfile_dfs); +} diff --git a/apps/codecs/dumb/src/helpers/resample.c b/apps/codecs/dumb/src/helpers/resample.c new file mode 100644 index 0000000000..d8b238fc71 --- /dev/null +++ b/apps/codecs/dumb/src/helpers/resample.c @@ -0,0 +1,1177 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * resample.c - Resampling helper. / / \ \ + * | < / \_ + * By Bob and entheh. | \/ /\ / + * \_ / > / + * In order to find a good trade-off between | \ / / + * speed and accuracy in this code, some tests | ' / + * were carried out regarding the behaviour of \__/ + * long long ints with gcc. The following code + * was tested: + * + * int a, b, c; + * c = ((long long)a * b) >> 16; + * + * DJGPP GCC Version 3.0.3 generated the following assembly language code for + * the multiplication and scaling, leaving the 32-bit result in EAX. + * + * movl -8(%ebp), %eax ; read one int into EAX + * imull -4(%ebp) ; multiply by the other; result goes in EDX:EAX + * shrdl $16, %edx, %eax ; shift EAX right 16, shifting bits in from EDX + * + * Note that a 32*32->64 multiplication is performed, allowing for high + * accuracy. On the Pentium 2 and above, shrdl takes two cycles (generally), + * so it is a minor concern when four multiplications are being performed + * (the cubic resampler). On the Pentium MMX and earlier, it takes four or + * more cycles, so this method is unsuitable for use in the low-quality + * resamplers. + * + * Since "long long" is a gcc-specific extension, we use LONG_LONG instead, + * defined in dumb.h. We may investigate later what code MSVC generates, but + * if it seems too slow then we suggest you use a good compiler. + * + * FIXME: these comments are somewhat out of date now. + */ + +#include +#include "dumb.h" + + + +/* Compile with -DHEAVYDEBUG if you want to make sure the pick-up function is + * called when it should be. There will be a considerable performance hit, + * since at least one condition has to be tested for every sample generated. + */ +#ifdef HEAVYDEBUG +#define HEAVYASSERT(cond) ASSERT(cond) +#else +#define HEAVYASSERT(cond) +#endif + + + +//#define MULSC(a, b) ((int)((LONG_LONG)(a) * (b) >> 16)) +//#define MULSC(a, b) ((a) * ((b) >> 2) >> 14) +#define MULSC(a, b) ((int)((LONG_LONG)((a) << 4) * ((b) << 12) >> 32)) + + + +/* A global variable for controlling resampling quality wherever a local + * specification doesn't override it. The following values are valid: + * + * 0 - DUMB_RQ_ALIASING - fastest + * 1 - DUMB_RQ_LINEAR + * 2 - DUMB_RQ_CUBIC - nicest + * + * Values outside the range 0-2 will behave the same as the nearest + * value within the range. + */ +int dumb_resampling_quality = 2; + + + +void dumb_reset_resampler(DUMB_RESAMPLER *resampler, sample_t *src, long pos, long start, long end) +{ + resampler->src = src; + resampler->pos = pos; + resampler->subpos = 0; + resampler->start = start; + resampler->end = end; + resampler->dir = 1; + resampler->pickup = NULL; + resampler->pickup_data = NULL; + resampler->min_quality = 0; + resampler->max_quality = DUMB_RQ_N_LEVELS - 1; + resampler->x[2] = resampler->x[1] = resampler->x[0] = 0; + resampler->overshot = -1; +} + + + +DUMB_RESAMPLER *dumb_start_resampler(sample_t *src, long pos, long start, long end) +{ + DUMB_RESAMPLER *resampler = malloc(sizeof(*resampler)); + if (!resampler) return NULL; + dumb_reset_resampler(resampler, src, pos, start, end); + return resampler; +} + + + +/* For convenience, returns nonzero on stop. */ +static int process_pickup(DUMB_RESAMPLER *resampler) +{ + if (resampler->overshot < 0) { + resampler->overshot = 0; + dumb_resample(resampler, NULL, 2, 0, 1.0f); + resampler->x[0] = resampler->x[1]; + } + + for (;;) { + if (resampler->dir < 0) { + if (resampler->overshot >= 3 && resampler->pos+3 >= resampler->start) resampler->x[0] = resampler->src[resampler->pos+3]; + if (resampler->overshot >= 2 && resampler->pos+2 >= resampler->start) resampler->x[1] = resampler->src[resampler->pos+2]; + if (resampler->overshot >= 1 && resampler->pos+1 >= resampler->start) resampler->x[2] = resampler->src[resampler->pos+1]; + resampler->overshot = resampler->start - resampler->pos - 1; + } else { + if (resampler->overshot >= 3 && resampler->pos-3 < resampler->end) resampler->x[0] = resampler->src[resampler->pos-3]; + if (resampler->overshot >= 2 && resampler->pos-2 < resampler->end) resampler->x[1] = resampler->src[resampler->pos-2]; + if (resampler->overshot >= 1 && resampler->pos-1 < resampler->end) resampler->x[2] = resampler->src[resampler->pos-1]; + resampler->overshot = resampler->pos - resampler->end; + } + + if (resampler->overshot < 0) { + resampler->overshot = 0; + return 0; + } + + if (!resampler->pickup) { + resampler->dir = 0; + return 1; + } + (*resampler->pickup)(resampler, resampler->pickup_data); + if (resampler->dir == 0) return 1; + ASSERT(resampler->dir == -1 || resampler->dir == 1); + } +} + + + +/* Executes the content 'iterator' times. + * Clobbers the 'iterator' variable. + * The loop is unrolled by four. + */ +#define LOOP4(iterator, CONTENT) \ +{ \ + if ((iterator) & 2) { \ + CONTENT; \ + CONTENT; \ + } \ + if ((iterator) & 1) { \ + CONTENT; \ + } \ + (iterator) >>= 2; \ + while (iterator) { \ + CONTENT; \ + CONTENT; \ + CONTENT; \ + CONTENT; \ + (iterator)--; \ + } \ +} + + + +long dumb_resample(DUMB_RESAMPLER *resampler, sample_t *dst, long dst_size, float volume, float delta) +{ + int dt; + int vol; + long done; + long todo; + int quality; + + if (!resampler || resampler->dir == 0) return 0; + ASSERT(resampler->dir == -1 || resampler->dir == 1); + + done = 0; + dt = (int)(delta * 65536.0 + 0.5); + vol = (int)floor(volume * 65536.0 + 0.5); + + if (vol == 0) dst = NULL; + + quality = dumb_resampling_quality; + if (quality > resampler->max_quality) quality = resampler->max_quality; + else if (quality < resampler->min_quality) quality = resampler->min_quality; + + while (done < dst_size) { + if (process_pickup(resampler)) return done; + + if ((resampler->dir ^ dt) < 0) + dt = -dt; + + if (resampler->dir < 0) + todo = (long)((((LONG_LONG)(resampler->pos - resampler->start) << 16) + resampler->subpos - dt) / -dt); + else + todo = (long)((((LONG_LONG)(resampler->end - resampler->pos) << 16) - resampler->subpos - 1 + dt) / dt); + + if (todo < 0) + todo = 0; + else if (todo > dst_size - done) + todo = dst_size - done; + + done += todo; + + { + sample_t *src = resampler->src; + long pos = resampler->pos; + int subpos = resampler->subpos; + long diff = pos; + long overshot; + if (resampler->dir < 0) { + if (!dst) { + /* Silence or simulation */ + LONG_LONG new_subpos = subpos + dt * todo; + pos += (long)(new_subpos >> 16); + subpos = (long)new_subpos & 65535; + } else if (quality <= DUMB_RQ_ALIASING) { + /* Aliasing, backwards */ + sample_t xbuf[2]; + sample_t *x = &xbuf[0]; + sample_t *xstart; + xbuf[0] = resampler->x[1]; + xbuf[1] = resampler->x[2]; + while (todo && x < &xbuf[2]) { + HEAVYASSERT(pos >= resampler->start); + *dst++ += MULSC(x[0], vol); + subpos += dt; + pos += subpos >> 16; + x -= subpos >> 16; + subpos &= 65535; + todo--; + } + x = xstart = &src[pos]; + LOOP4(todo, + *dst++ += MULSC(x[2], vol); + subpos += dt; + x += subpos >> 16; + subpos &= 65535; + ); + pos += x - xstart; + } else if (quality <= DUMB_RQ_LINEAR) { + /* Linear interpolation, backwards */ + sample_t xbuf[3]; + sample_t *x = &xbuf[1]; + xbuf[0] = resampler->x[1]; + xbuf[1] = resampler->x[2]; + xbuf[2] = src[pos]; + while (todo && x < &xbuf[3]) { + HEAVYASSERT(pos >= resampler->start); + *dst++ += MULSC(x[0] + MULSC(x[-1] - x[0], subpos), vol); + subpos += dt; + pos += subpos >> 16; + x -= subpos >> 16; + subpos &= 65535; + todo--; + } + x = &src[pos]; + LOOP4(todo, + HEAVYASSERT(pos >= resampler->start); + *dst++ += MULSC(x[1] + MULSC(x[2] - x[1], subpos), vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + ); + } else { + /* Cubic interpolation, backwards */ + sample_t xbuf[6]; + sample_t *x = &xbuf[3]; + sample_t *lastx = NULL; + int a = 0, b = 0, c = 0; + xbuf[0] = resampler->x[0]; + xbuf[1] = resampler->x[1]; + xbuf[2] = resampler->x[2]; + xbuf[3] = src[pos]; + if (pos-1 >= resampler->start) xbuf[4] = src[pos-1]; + if (pos-2 >= resampler->start) xbuf[5] = src[pos-2]; + while (todo && x < &xbuf[6]) { + HEAVYASSERT(pos >= resampler->start); + if (lastx != x) { + lastx = x; + a = (((x[-1] - x[-2]) << 1) + (x[-1] - x[-2]) + (x[-3] - x[0])) >> 1; + b = (x[-2] << 1) + x[0] - ((5 * x[-1] + x[-3]) >> 1); + c = (x[-2] - x[0]) >> 1; + } + *dst++ += MULSC(MULSC(MULSC(MULSC(a, subpos) + b, subpos) + c, subpos) + x[-1], vol); + subpos += dt; + pos += subpos >> 16; + x -= subpos >> 16; + subpos &= 65535; + todo--; + } + x = &src[pos]; + lastx = NULL; + LOOP4(todo, + HEAVYASSERT(pos >= resampler->start); + if (lastx != x) { + lastx = x; + a = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (x[3] - x[0])) >> 1; + b = (x[2] << 1) + x[0] - ((5 * x[1] + x[3]) >> 1); + c = (x[2] - x[0]) >> 1; + } + *dst++ += MULSC(MULSC(MULSC(MULSC(a, subpos) + b, subpos) + c, subpos) + x[1], vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + ); + } + diff = diff - pos; + overshot = resampler->start - pos - 1; + if (diff >= 3) { + resampler->x[0] = overshot >= 3 ? 0 : src[pos+3]; + resampler->x[1] = overshot >= 2 ? 0 : src[pos+2]; + resampler->x[2] = overshot >= 1 ? 0 : src[pos+1]; + } else if (diff >= 2) { + resampler->x[0] = resampler->x[2]; + resampler->x[1] = overshot >= 2 ? 0 : src[pos+2]; + resampler->x[2] = overshot >= 1 ? 0 : src[pos+1]; + } else if (diff >= 1) { + resampler->x[0] = resampler->x[1]; + resampler->x[1] = resampler->x[2]; + resampler->x[2] = overshot >= 1 ? 0 : src[pos+1]; + } + } else { + if (!dst) { + /* Silence or simulation */ + LONG_LONG new_subpos = subpos + dt * todo; + pos += (long)(new_subpos >> 16); + subpos = (long)new_subpos & 65535; + } else if (dumb_resampling_quality <= DUMB_RQ_ALIASING) { + /* Aliasing, forwards */ + sample_t xbuf[2]; + sample_t *x = &xbuf[0]; + sample_t *xstart; + xbuf[0] = resampler->x[1]; + xbuf[1] = resampler->x[2]; + while (todo && x < &xbuf[2]) { + HEAVYASSERT(pos < resampler->end); + *dst++ += MULSC(x[0], vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + todo--; + } + x = xstart = &src[pos]; + LOOP4(todo, + *dst++ += MULSC(x[-2], vol); + subpos += dt; + x += subpos >> 16; + subpos &= 65535; + ); + pos += x - xstart; + } else if (dumb_resampling_quality <= DUMB_RQ_LINEAR) { + /* Linear interpolation, forwards */ + sample_t xbuf[3]; + sample_t *x = &xbuf[1]; + xbuf[0] = resampler->x[1]; + xbuf[1] = resampler->x[2]; + xbuf[2] = src[pos]; + while (todo && x < &xbuf[3]) { + HEAVYASSERT(pos < resampler->end); + *dst++ += MULSC(x[-1] + MULSC(x[0] - x[-1], subpos), vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + todo--; + } + x = &src[pos]; + LOOP4(todo, + HEAVYASSERT(pos < resampler->end); + *dst++ += MULSC(x[-2] + MULSC(x[-1] - x[-2], subpos), vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + ); + } else { + /* Cubic interpolation, forwards */ + sample_t xbuf[6]; + sample_t *x = &xbuf[3]; + sample_t *lastx = NULL; + int a = 0, b = 0, c = 0; + xbuf[0] = resampler->x[0]; + xbuf[1] = resampler->x[1]; + xbuf[2] = resampler->x[2]; + xbuf[3] = src[pos]; + if (pos+1 < resampler->end) xbuf[4] = src[pos+1]; + if (pos+2 < resampler->end) xbuf[5] = src[pos+2]; + while (todo && x < &xbuf[6]) { + HEAVYASSERT(pos < resampler->end); + if (lastx != x) { + lastx = x; + a = (((x[-2] - x[-1]) << 1) + (x[-2] - x[-1]) + (x[0] - x[-3])) >> 1; + b = (x[-1] << 1) + x[-3] - ((5 * x[-2] + x[0]) >> 1); + c = (x[-1] - x[-3]) >> 1; + } + *dst++ += MULSC(MULSC(MULSC(MULSC(a, subpos) + b, subpos) + c, subpos) + x[-2], vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + todo--; + } + x = &src[pos]; + lastx = NULL; + LOOP4(todo, + HEAVYASSERT(pos < resampler->end); + if (lastx != x) { + lastx = x; + a = (((x[-2] - x[-1]) << 1) + (x[-2] - x[-1]) + (x[0] - x[-3])) >> 1; + b = (x[-1] << 1) + x[-3] - ((5 * x[-2] + x[0]) >> 1); + c = (x[-1] - x[-3]) >> 1; + } + *dst++ += MULSC(MULSC(MULSC(MULSC(a, subpos) + b, subpos) + c, subpos) + x[-2], vol); + subpos += dt; + pos += subpos >> 16; + x += subpos >> 16; + subpos &= 65535; + ); + } + diff = pos - diff; + overshot = pos - resampler->end; + if (diff >= 3) { + resampler->x[0] = overshot >= 3 ? 0 : src[pos-3]; + resampler->x[1] = overshot >= 2 ? 0 : src[pos-2]; + resampler->x[2] = overshot >= 1 ? 0 : src[pos-1]; + } else if (diff >= 2) { + resampler->x[0] = resampler->x[2]; + resampler->x[1] = overshot >= 2 ? 0 : src[pos-2]; + resampler->x[2] = overshot >= 1 ? 0 : src[pos-1]; + } else if (diff >= 1) { + resampler->x[0] = resampler->x[1]; + resampler->x[1] = resampler->x[2]; + resampler->x[2] = overshot >= 1 ? 0 : src[pos-1]; + } + } + resampler->pos = pos; + resampler->subpos = subpos; + } + } + + return done; +} + + + +sample_t dumb_resample_get_current_sample(DUMB_RESAMPLER *resampler, float volume) +{ + int vol; + sample_t *src; + long pos; + int subpos; + int quality; + + if (!resampler || resampler->dir == 0) return 0; + ASSERT(resampler->dir == -1 || resampler->dir == 1); + + if (process_pickup(resampler)) return 0; + + vol = (int)floor(volume * 65536.0 + 0.5); + if (vol == 0) return 0; + + quality = dumb_resampling_quality; + if (quality > resampler->max_quality) quality = resampler->max_quality; + else if (quality < resampler->min_quality) quality = resampler->min_quality; + + src = resampler->src; + pos = resampler->pos; + subpos = resampler->subpos; + + if (resampler->dir < 0) { + HEAVYASSERT(pos >= resampler->start); + if (dumb_resampling_quality <= 0) { + /* Aliasing, backwards */ + return MULSC(src[pos], vol); + } else if (quality <= DUMB_RQ_LINEAR) { + /* Linear interpolation, backwards */ + return MULSC(resampler->x[2] + MULSC(resampler->x[1] - resampler->x[2], subpos), vol); + } else { + /* Cubic interpolation, backwards */ + sample_t *x = resampler->x; + int a, b, c; + a = (((x[2] - x[1]) << 1) + (x[2] - x[1]) + (x[0] - src[pos])) >> 1; + b = (x[1] << 1) + src[pos] - ((5 * x[2] + x[0]) >> 1); + c = (x[1] - src[pos]) >> 1; + return MULSC(MULSC(MULSC(MULSC(a, subpos) + b, subpos) + c, subpos) + x[2], vol); + } + } else { + HEAVYASSERT(pos < resampler->end); + if (dumb_resampling_quality <= 0) { + /* Aliasing */ + return MULSC(src[pos], vol); + } else if (dumb_resampling_quality <= DUMB_RQ_LINEAR) { + /* Linear interpolation, forwards */ + return MULSC(resampler->x[1] + MULSC(resampler->x[2] - resampler->x[1], subpos), vol); + } else { + /* Cubic interpolation, forwards */ + sample_t *x = resampler->x; + int a, b, c; + a = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (src[pos] - x[0])) >> 1; + b = (x[2] << 1) + x[0] - ((5 * x[1] + src[pos]) >> 1); + c = (x[2] - x[0]) >> 1; + return MULSC(MULSC(MULSC(MULSC(a, subpos) + b, subpos) + c, subpos) + x[1], vol); + } + } +} + + + +void dumb_end_resampler(DUMB_RESAMPLER *resampler) +{ + if (resampler) + free(resampler); +} + + + +#if 0 +/* The following macro is used to overcome the fact that most C + * compilers (including gcc and MSVC) can't correctly multiply signed + * integers outside the range -32768 to 32767. i86 assembler versions + * don't need to use this method, since the processor does in fact + * have instructions to multiply large numbers correctly - which + * means using assembly language could make a significant difference + * to the speed. + * + * The basic method is as follows. We halve the subposition (how far + * we are between samples), so it never exceeds 32767. We also halve + * the delta, which is the amount to be added to the subposition each + * time. Then we unroll the loop twofold, so that we can add the lost + * one every other time if necessary (since the halving may have + * resulted in rounding down). + * + * This method doesn't incur any cumulative inaccuracies. There is a + * very slight loss of quality, which I challenge anyone to notice - + * but the position will advance at *exactly* the same rate as it + * would if we didn't use this method. This also means the pitch is + * exactly the same, which may even make a difference to trained + * musicians when resampling down a lot :) + * + * Each time this macro is invoked, DO_RESAMPLE(inc) must be defined + * to calculate the samples by the appropriate equation (linear, + * cubic, etc.). See the individual cases for examples of how this is + * done. + */ +#define MAKE_RESAMPLER() \ +{ \ + if (dt & 1) { \ + long todo2; \ + \ + dt >>= 1; \ + \ + if (src_subpos & 1) { \ + src_subpos >>= 1; \ + DO_RESAMPLE(1); \ + todo--; \ + } else \ + src_subpos >>= 1; \ + \ + todo2 = todo >> 1; \ + \ + while (todo2) { \ + DO_RESAMPLE(0); \ + DO_RESAMPLE(1); \ + todo2--; \ + } \ + \ + if (todo & 1) { \ + DO_RESAMPLE(0); \ + src_subpos = (src_subpos << 1) | 1; \ + } else \ + src_subpos <<= 1; \ + \ + todo = 0; \ + dt = (dt << 1) | 1; \ + } else { \ + long subposbit = src_subpos & 1; \ + dt >>= 1; \ + src_subpos >>= 1; \ + \ + if (todo & 1) { \ + DO_RESAMPLE(0); \ + } \ + \ + todo >>= 1; \ + \ + while (todo) { \ + DO_RESAMPLE(0); \ + DO_RESAMPLE(0); \ + todo--; \ + } \ + \ + src_subpos = (src_subpos << 1) | subposbit; \ + dt <<= 1; \ + } \ +} + + + +sample_t dumb_resample_get_current_sample( + sample_t *src, long *_src_pos, int *_src_subpos, + long src_start, long src_end, + float volume, int *_dir, + DUMB_RESAMPLE_PICKUP pickup, void *pickup_data +) +{ + long src_pos = *_src_pos; + int src_subpos = *_src_subpos; + int dir = _dir ? *_dir : 1; + + sample_t value = 0; + + if (dir == 0) + return 0; + + ASSERT(dir == 1 || dir == -1); + + if (dir < 0 ? (src_pos < src_start) : (src_pos >= src_end)) { + + /* If there's no pick-up function, we stop. */ + if (!pickup) { + dir = 0; + goto end; + } + + /* Process the pick-up. It may need invoking more than once. */ + do { + dir = (*pickup)(src, &src_pos, &src_subpos, &src_start, &src_end, dir, pickup_data); + + if (dir == 0) + goto end; + + ASSERT(dir == 1 || dir == -1); + } while (dir < 0 ? (src_pos < src_start) : (src_pos >= src_end)); + } + + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); + + if (dumb_resampling_quality == 0) { + /* Aliasing (coarse) */ + int volume_fact = (int)(volume * 16384.0); + value = (src[src_pos] * volume_fact) >> 14; + } else if (dumb_resampling_quality <= 2) { + /* Linear interpolation */ + int volume_fact = (int)(volume * 16384.0); + int subpos = src_subpos >> 1; + value = ((src[src_pos] + ((((src[src_pos + 1] - src[src_pos]) >> 1) * subpos) >> 14)) * volume_fact) >> 14; + } else if (dumb_resampling_quality == 3) { + /* Quadratic interpolation */ + int volume_fact = (int)(volume * 16384.0); + int a, b; + sample_t *x; + int subpos = src_subpos >> 1; + x = &src[src_pos]; + a = ((x[0] + x[2]) >> 1) - x[1]; + b = ((x[2] - x[0]) >> 1) - (a << 1); + value = (((((((a * subpos) >> 15) + b) * subpos) >> 15) + x[0]) * volume_fact) >> 14; + } else { + /* Cubic interpolation */ + int volume_fact = (int)(volume * 16384.0); + int a, b, c; + sample_t *x; + int subpos = src_subpos >> 1; + x = &src[src_pos]; + a = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (x[3] - x[0])) >> 1; + b = (x[2] << 1) + x[0] - ((5 * x[1] + x[3]) >> 1); + c = (x[2] - x[0]) >> 1; + value = (((int)(((LONG_LONG)((int)(((LONG_LONG)((int)(((LONG_LONG)a * subpos) >> 15) + b) * subpos) >> 15) + c) * subpos) >> 15) + x[1]) * volume_fact) >> 14; + } + + end: + + *_src_pos = src_pos; + *_src_subpos = src_subpos; + if (_dir) *_dir = dir; + + return value; +} + + + +long dumb_resample( + sample_t *src, long *_src_pos, int *_src_subpos, + long src_start, long src_end, + sample_t *dst, long dst_size, + float volume, float delta, int *_dir, + DUMB_RESAMPLE_PICKUP pickup, void *pickup_data +) +{ + int dt = (int)(delta * 65536.0 + 0.5); + long s = 0; /* Current position in the destination buffer */ + + long src_pos = *_src_pos; + int src_subpos = *_src_subpos; + int dir = _dir ? *_dir : 1; + + int linear_average; + + if (dir == 0) + return 0; + + ASSERT(dir == 1 || dir == -1); + + linear_average = dst && dumb_resampling_quality >= 2 && dt > 65536; + + if (dir < 0) dt = -dt; + + if (linear_average) + volume /= delta; + + while (s < dst_size) { + + long todo; + + /* Process pick-ups first, just in case. */ + + if (linear_average) { + + /* For linear average, the pick-up point could split a sum into + * two parts. We handle this by putting the pick-up code inside + * the summing loop. Note that this code is only executed when we + * know that a pick-up is necessary somewhere during this sum + * (although it is always executed once for the first sample). + * We use a separate loop further down when we know we won't have + * to do a pick-up, so the condition does not need testing inside + * the loop. + */ + + float sum; + long i; + int advance; + int x[3]; + + advance = src_subpos + dt; + + /* Make these negative. Then they stay within the necessary + * range for integer multiplication, -32768 to 32767 ;) + */ + x[0] = ~(src_subpos >> 1); /* = -1 - (src_subpos >> 1) */ + x[2] = x[0] ^ 0x7FFF; /* = -32768 + (src_subpos >> 1) */ + + sum = (float)(-((src[src_pos] * (x+1)[dir]) >> 15)); + + i = src_pos + (advance >> 16); + src_pos += dir; + src_subpos = (dir >> 1) & 65535; /* changes 1,-1 to 0,65535 */ + + advance &= 65535; + + /* i is the index of the first sample NOT to sum fully, + * regardless of the direction of resampling. + */ + + while (dir < 0 ? (i < src_start) : (i >= src_end)) { + if (dir < 0) { + while (src_pos >= src_start) + sum += src[src_pos--]; + } else { + while (src_pos < src_end) + sum += src[src_pos++]; + } + + i -= src_pos; + /* i is now the number of samples left to sum fully, except + * it's negative if we're going backwards. + */ + + if (!pickup) { + dir = 0; + goto endsum; + } + + dir = (*pickup)(src, &src_pos, &src_subpos, &src_start, &src_end, dir, pickup_data); + + if (dir == 0) + goto endsum; + + ASSERT(dir == 1 || dir == -1); + + if ((dir ^ dt) < 0) { + dt = -dt; + advance ^= 65535; + i = -i; + } + + i += src_pos; + /* There, i is back to normal. */ + } + + for (; src_pos != i; src_pos += dir) + sum += src[src_pos]; + + src_subpos = advance; + + x[2] = src_subpos >> 1; + x[0] = x[2] ^ 0x7FFF; /* = 32767 - (src_subpos >> 1) */ + + sum += (src[src_pos] * (x+1)[dir]) >> 15; + + endsum: + + sum *= volume; + dst[s] += (int)sum; + + s++; + + if (dir == 0) + break; + + } else if (dir < 0 ? (src_pos < src_start) : (src_pos >= src_end)) { + + /* If there's no pick-up function, we stop. */ + if (!pickup) { + dir = 0; + break; + } + + /* Process the pick-up. It may need invoking more than once. */ + do { + dir = (*pickup)(src, &src_pos, &src_subpos, &src_start, &src_end, dir, pickup_data); + + if (dir == 0) + goto end; + + ASSERT(dir == 1 || dir == -1); + } while (dir < 0 ? (src_pos < src_start) : (src_pos >= src_end)); + + /* Update sign of dt to match that of dir. */ + if ((dir ^ dt) < 0) + dt = -dt; + } + + /* Work out how many contiguous samples we can now render. */ + if (dir < 0) + todo = (long)((((LONG_LONG)(src_pos - src_start) << 16) + src_subpos) / -dt); + else + todo = (long)((((LONG_LONG)(src_end - src_pos) << 16) - src_subpos - 1) / dt); + + /* The above equations work out how many complete dt-sized + * intervals there are between the current position and the loop + * point (provided there is a little fractional extra). The linear + * average function needs complete intervals - but the other + * resamplers only read a sample from the beginning of each interval, + * so they can process one extra sample in their main loops (so we + * increment todo in a moment). + * + * The linear average function makes up the extra sample using the + * specialised pick-up code above. + * + * Note that our above pick-up process should have absolutely ensured + * that the result of this function will be nonnegative. + */ + + ASSERT(todo >= 0); + + if (!linear_average) + todo++; + + /* Of course we don't want to overrun the output buffer! */ + if (todo > dst_size - s) + todo = dst_size - s; + + if (!dst) { + + LONG_LONG t = src_subpos + (LONG_LONG)dt * todo; + src_pos += (long)(t >> 16); + src_subpos = (int)t & 0xFFFFl; + + s += todo; + + } else if (linear_average) { + + float sum; + long i; + int advance; + int x[3]; + + while (todo) { + + advance = src_subpos + dt; + + /* Make these negative. Then they stay within the necessary + * range for integer multiplication, -32768 to 32767 ;) + */ + x[0] = ~(src_subpos >> 1); /* = -1 - (src_subpos >> 1) */ + x[2] = x[0] ^ 0x7FFF; /* = -32768 + (src_subpos >> 1) */ + + sum = (float)(-((src[src_pos] * (x+1)[dir]) >> 15)); + + i = src_pos + (advance >> 16); + src_pos += dir; + src_subpos = (dir >> 1) & 65535; /* changes 1,-1 to 0,65535 */ + + advance &= 65535; + + /* i is the index of the first sample NOT to sum fully, + * regardless of the direction of resampling. + */ + + HEAVYASSERT(dir < 0 ? (i >= src_start) : (i < src_end)); + + for (; src_pos != i; src_pos += dir) + sum += src[src_pos]; + + src_subpos = advance; + + x[2] = src_subpos >> 1; + x[0] = x[2] ^ 0x7FFF; /* = 32767 - (src_subpos >> 1) */ + + sum += (src[src_pos] * (x+1)[dir]) >> 15; + + sum *= volume; + dst[s] += (int)sum; + + s++; + todo--; + } + + } else if (dumb_resampling_quality == 0 || (dumb_resampling_quality == 1 && delta >= 1.0)) { + + /* Aliasing (coarse) */ + int volume_fact = (int)(volume * 16384.0); + + do { + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); + dst[s] += ((src[src_pos] * volume_fact) >> 14); + src_subpos += dt; + src_pos += src_subpos >> 16; + src_subpos &= 0xFFFFl; + s++; + } while (--todo); + + } else if (dumb_resampling_quality <= 2) { + + /* Linear interpolation */ + int volume_fact = (int)(volume * 16384.0); + + #define DO_RESAMPLE(inc) \ + { \ + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); \ + \ + dst[s] += (((src[src_pos] + ((((src[src_pos + 1] - src[src_pos]) >> 1) * src_subpos) >> 14)) * volume_fact) >> 14); \ + \ + src_subpos += dt + inc; \ + src_pos += src_subpos >> 15; \ + src_subpos &= 0x7FFFl; \ + s++; \ + } + + MAKE_RESAMPLER(); + + #undef DO_RESAMPLE + + } else if (dumb_resampling_quality == 3) { + + /* Quadratic interpolation */ + + int volume_fact = (int)(volume * 16384.0); + int a = 0, b = 0; + sample_t *x = NULL; + int last_src_pos = -1; + + /* AIM: no integer multiplicands must transcend the range -32768 to 32767. + * This limitation is imposed by most compilers, including gcc and MSVC. + * + * a = 0.5 * (s0 + s2) - s1 + * b = -1.5 * s0 + 2 * s1 - 0.5 * s2 + * c = s0 + * + * s = (a * t + b) * t + c + * + * In fixed-point: + * + * a = ((s0 + s2) >> 1) - s1 + * b = ((-3 * s0 - s2) >> 1) + (s1 << 1) + * + * s = (((((a * t) >> 16) + b) * t) >> 16) + s0 + * + * With t halved (since t can reach 65535): + * + * s = (((((a * t) >> 15) + b) * t) >> 15) + s0 + * + * a currently reaches 65536 + * b currently reaches 131072 + * + * So we must use aon2 + * + * s = (((((aon2 * t) >> 14) + b) * t) >> 15) + s0 + * + * ((aon2 * t) >> 14) + b is 5 times too big + * so we must divide by 8 + * + * s = (((((aon2 * t) >> 17) + bon8) * t) >> 12) + s0 + * + * aon2 = ((s0 + s2) >> 2) - (s1 >> 1) + * bon8 = ((-3 * s0 - s2) >> 4) + (s1 >> 2) + * or: + * bon8 = ((s2 - s0) >> 4) - (aon2 >> 1) + */ + + /* Unh4x0r3d version: + #define DO_RESAMPLE(inc) \ + { \ + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); \ + \ + if (src_pos != last_src_pos) { \ + last_src_pos = src_pos; \ + x = &src[src_pos]; \ + a = ((x[0] + x[2]) >> 2) - (x[1] >> 1); \ + b = ((x[2] - x[0]) >> 4) - (a >> 1); \ + } \ + \ + dst[s] += ((((((((a * src_subpos) >> 17) + b) * src_subpos) >> 12) + x[0]) * volume_fact) >> 14); \ + \ + src_subpos += dt + inc; \ + src_pos += src_subpos >> 15; \ + src_subpos &= 0x7FFFl; \ + s++; \ + } + */ + + /* H4x0r3d version: */ + #define DO_RESAMPLE(inc) \ + { \ + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); \ + \ + if (src_pos != last_src_pos) { \ + last_src_pos = src_pos; \ + x = &src[src_pos]; \ + a = ((x[0] + x[2]) >> 1) - x[1]; \ + b = ((x[2] - x[0]) >> 1) - (a << 1); \ + } \ + \ + dst[s] += ((((((((a * src_subpos) >> 15) + b) * src_subpos) >> 15) + x[0]) * volume_fact) >> 14); \ + \ + src_subpos += dt + inc; \ + src_pos += src_subpos >> 15; \ + src_subpos &= 0x7FFFl; \ + s++; \ + } + + MAKE_RESAMPLER(); + + #undef DO_RESAMPLE + + } else { + + /* Cubic interpolation */ + + int volume_fact = (int)(volume * 16384.0); + int a = 0, b = 0, c = 0; + sample_t *x = NULL; + int last_src_pos = -1; + + /* AIM: never multiply integers outside the range -32768 to 32767. + * + * a = 1.5f * (x[1] - x[2]) + (x[3] - x[0]) * 0.5f; + * b = 2.0f * x[2] + x[0] - 2.5f * x[1] - x[3] * 0.5f; + * c = (x[2] - x[0]) * 0.5f; + * + * s = ((a * t + b) * t + c) * t + x[1]; + * + * Fixed-point version: + * + * a = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (x[3] - x[0])) >> 1; + * b = (x[2] << 1) + x[0] - ((5 * x[1] + x[3]) >> 1); + * c = (x[2] - x[0]) >> 1; + * + * s = ((((((((a * t) >> 15) + b) * t) >> 15) + c) * t) >> 15) + x[1]; + * (with t already halved, maximum 32767) + * + * a is in (((1+1)*2)+(1+1)+(1+1))/2 = 8 times the required range + * b is in (1*2)+1+((5*1+1)/2) = 6 times + * c is in the required range + * + * We must use aon8 + * + * s = ((((((((aon8 * t) >> 12) + b) * t) >> 15) + c) * t) >> 15) + x[1]; + * + * But ((aon8 * t) >> 12) is in 2^(15+15-12) = 2^18 = 8 times + * b is in 6 times + * so we divide both ((aon8 * t) >> 12) and b by 16 + * + * s = ((((((((aon8 * t) >> 16) + bon16) * t) >> 11) + c) * t) >> 15) + x[1]; + * + * ((... + bon16) * t) >> 11 is 16 times too big + * c is in the correct range + * we must divide both by 32 + * + * s = ((((((((aon8 * t) >> 16) + bon16) * t) >> 16) + con32) * t) >> 10) + x[1]; + * + * aon8 = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (x[3] - x[0])) >> 4; + * bon16 = ((x[2] << 2) + (x[0] << 1) - (5 * x[1] + x[3])) >> 5; + * con32 = (x[2] - x[0]) >> 6; + * + * A lot of accuracy is lost here. It is quite likely that some + * of the above would cancel anyway, so the scaling down wouldn't + * have to be so severe. However, I'm not in the mood to work it + * out now :P + * + * It may also be worth investigating whether doing this stuff + * in floats would be faster. + */ + + /* Unh4x0r3d version: + #define DO_RESAMPLE(inc) \ + { \ + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); \ + \ + if (src_pos != last_src_pos) { \ + last_src_pos = src_pos; \ + x = &src[src_pos]; \ + a = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (x[3] - x[0])) >> 4; \ + b = ((x[2] << 2) + (x[0] << 1) - (5 * x[1] + x[3])) >> 5; \ + c = (x[2] - x[0]) >> 6; \ + } \ + \ + dst[s] += ((((((((((a * src_subpos) >> 16) + b) * src_subpos) >> 16) + c) * src_subpos) >> 10) + x[1]) * volume_fact) >> 14; \ + \ + src_subpos += dt + inc; \ + src_pos += src_subpos >> 15; \ + src_subpos &= 0x7FFFl; \ + s++; \ + } + */ + + /* H4x0r3d version: */ + #define DO_RESAMPLE(inc) \ + { \ + HEAVYASSERT(dir < 0 ? (src_pos >= src_start) : (src_pos < src_end)); \ + \ + if (src_pos != last_src_pos) { \ + last_src_pos = src_pos; \ + x = &src[src_pos]; \ + a = (((x[1] - x[2]) << 1) + (x[1] - x[2]) + (x[3] - x[0])) >> 1; \ + b = (x[2] << 1) + x[0] - ((5 * x[1] + x[3]) >> 1); \ + c = (x[2] - x[0]) >> 1; \ + } \ + \ + dst[s] += (((int)(((LONG_LONG)((int)(((LONG_LONG)((int)(((LONG_LONG)a * src_subpos) >> 15) + b) * src_subpos) >> 15) + c) * src_subpos) >> 15) + x[1]) * volume_fact) >> 14; \ + \ + src_subpos += dt + inc; \ + src_pos += src_subpos >> 15; \ + src_subpos &= 0x7FFFl; \ + s++; \ + } + + MAKE_RESAMPLER(); + + #undef DO_RESAMPLE + + } + + } + + end: + + ASSERT(s <= dst_size); + + *_src_pos = src_pos; + *_src_subpos = src_subpos; + if (_dir) *_dir = dir; + + return s; +} +#endif diff --git a/apps/codecs/dumb/src/helpers/sampbuf.c b/apps/codecs/dumb/src/helpers/sampbuf.c new file mode 100644 index 0000000000..75510c729a --- /dev/null +++ b/apps/codecs/dumb/src/helpers/sampbuf.c @@ -0,0 +1,47 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * sampbuf.c - Helper for allocating sample / / \ \ + * buffers. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include "dumb.h" + + + +sample_t **create_sample_buffer(int n_channels, long length) +{ + int i; + sample_t **samples = malloc(n_channels * sizeof(*samples)); + if (!samples) return NULL; + samples[0] = malloc(n_channels * length * sizeof(*samples[0])); + if (!samples[0]) { + free(samples); + return NULL; + } + for (i = 1; i < n_channels; i++) samples[i] = samples[i-1] + length; + return samples; +} + + + +void destroy_sample_buffer(sample_t **samples) +{ + if (samples) { + free(samples[0]); + free(samples); + } +} diff --git a/apps/codecs/dumb/src/helpers/silence.c b/apps/codecs/dumb/src/helpers/silence.c new file mode 100644 index 0000000000..4d5fdcf4da --- /dev/null +++ b/apps/codecs/dumb/src/helpers/silence.c @@ -0,0 +1,29 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * silence.c - Silencing helper. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include "dumb.h" + + + +void dumb_silence(sample_t *samples, long length) +{ + memset(samples, 0, length * sizeof(*samples)); +} + diff --git a/apps/codecs/dumb/src/helpers/stdfile.c b/apps/codecs/dumb/src/helpers/stdfile.c new file mode 100644 index 0000000000..2f02539a92 --- /dev/null +++ b/apps/codecs/dumb/src/helpers/stdfile.c @@ -0,0 +1,93 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * stdfile.c - stdio file input module. / / \ \ + * | < / \_ + * By entheh. | \/ /\ / + * \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" + + + +static void *dumb_stdfile_open(const char *filename) +{ + return fopen(filename, "rb"); +} + + + +static int dumb_stdfile_skip(void *f, long n) +{ + return fseek(f, n, SEEK_CUR); +} + + + +static int dumb_stdfile_getc(void *f) +{ + return fgetc(f); +} + + + +static long dumb_stdfile_getnc(char *ptr, long n, void *f) +{ + return fread(ptr, 1, n, f); +} + + + +static void dumb_stdfile_close(void *f) +{ + fclose(f); +} + + + +static DUMBFILE_SYSTEM stdfile_dfs = { + &dumb_stdfile_open, + &dumb_stdfile_skip, + &dumb_stdfile_getc, + &dumb_stdfile_getnc, + &dumb_stdfile_close +}; + + + +void dumb_register_stdfiles(void) +{ + register_dumbfile_system(&stdfile_dfs); +} + + + +static DUMBFILE_SYSTEM stdfile_dfs_leave_open = { + NULL, + &dumb_stdfile_skip, + &dumb_stdfile_getc, + &dumb_stdfile_getnc, + NULL +}; + + + +DUMBFILE *dumbfile_open_stdfile(FILE *p) +{ + DUMBFILE *d = dumbfile_open_ex(p, &stdfile_dfs_leave_open); + + return d; +} diff --git a/apps/codecs/dumb/src/it/itload.c b/apps/codecs/dumb/src/it/itload.c new file mode 100644 index 0000000000..a377619a88 --- /dev/null +++ b/apps/codecs/dumb/src/it/itload.c @@ -0,0 +1,43 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * itload.c - Code to read an Impulse Tracker / / \ \ + * file, opening and closing it for | < / \_ + * you. | \/ /\ / + * \_ / > / + * By entheh. Don't worry Bob, you're credited | \ / / + * in itread.c! | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/it.h" + + + +/* dumb_load_it(): loads an IT file into a DUH struct, returning a pointer to + * the DUH struct. When you have finished with it, you must pass the pointer + * to unload_duh() so that the memory can be freed. + */ +DUH *dumb_load_it(const char *filename) +{ + DUH *duh; + DUMBFILE *f = dumbfile_open(filename); + + if (!f) + return NULL; + + duh = dumb_read_it(f); + + dumbfile_close(f); + + return duh; +} + diff --git a/apps/codecs/dumb/src/it/itmisc.c b/apps/codecs/dumb/src/it/itmisc.c new file mode 100644 index 0000000000..b69a64df10 --- /dev/null +++ b/apps/codecs/dumb/src/it/itmisc.c @@ -0,0 +1,175 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * itmisc.c - Miscellaneous functions relating / / \ \ + * to module files. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/it.h" + + + +DUMB_IT_SIGDATA *duh_get_it_sigdata(DUH *duh) +{ + return duh_get_raw_sigdata(duh, 0, SIGTYPE_IT); +} + + + +int dumb_it_sd_get_n_orders(DUMB_IT_SIGDATA *sd) +{ + return sd ? sd->n_orders : 0; +} + + + +int dumb_it_sd_get_initial_global_volume(DUMB_IT_SIGDATA *sd) +{ + return sd ? sd->global_volume : 0; +} + + + +void dumb_it_sd_set_initial_global_volume(DUMB_IT_SIGDATA *sd, int gv) +{ + if (sd) sd->global_volume = gv; +} + + + +int dumb_it_sd_get_mixing_volume(DUMB_IT_SIGDATA *sd) +{ + return sd ? sd->mixing_volume : 0; +} + + + +void dumb_it_sd_set_mixing_volume(DUMB_IT_SIGDATA *sd, int mv) +{ + if (sd) sd->mixing_volume = mv; +} + + + +int dumb_it_sd_get_initial_speed(DUMB_IT_SIGDATA *sd) +{ + return sd ? sd->speed : 0; +} + + + +void dumb_it_sd_set_initial_speed(DUMB_IT_SIGDATA *sd, int speed) +{ + if (sd) sd->speed = speed; +} + + + +int dumb_it_sd_get_initial_tempo(DUMB_IT_SIGDATA *sd) +{ + return sd ? sd->tempo : 0; +} + + + +void dumb_it_sd_set_initial_tempo(DUMB_IT_SIGDATA *sd, int tempo) +{ + if (sd) sd->tempo = tempo; +} + + + +int dumb_it_sd_get_initial_channel_volume(DUMB_IT_SIGDATA *sd, int channel) +{ + ASSERT(channel >= 0 && channel < DUMB_IT_N_CHANNELS); + return sd ? sd->channel_volume[channel] : 0; +} + +void dumb_it_sd_set_initial_channel_volume(DUMB_IT_SIGDATA *sd, int channel, int volume) +{ + ASSERT(channel >= 0 && channel < DUMB_IT_N_CHANNELS); + if (sd) sd->channel_volume[channel] = volume; +} + + + +int dumb_it_sr_get_current_order(DUMB_IT_SIGRENDERER *sr) +{ + return sr ? sr->order : -1; +} + + + +int dumb_it_sr_get_current_row(DUMB_IT_SIGRENDERER *sr) +{ + return sr ? sr->row : -1; +} + + + +int dumb_it_sr_get_global_volume(DUMB_IT_SIGRENDERER *sr) +{ + return sr ? sr->globalvolume : 0; +} + + + +void dumb_it_sr_set_global_volume(DUMB_IT_SIGRENDERER *sr, int gv) +{ + if (sr) sr->globalvolume = gv; +} + + + +int dumb_it_sr_get_tempo(DUMB_IT_SIGRENDERER *sr) +{ + return sr ? sr->tempo : 0; +} + + + +void dumb_it_sr_set_tempo(DUMB_IT_SIGRENDERER *sr, int tempo) +{ + if (sr) sr->tempo = tempo; +} + + + +int dumb_it_sr_get_speed(DUMB_IT_SIGRENDERER *sr) +{ + return sr ? sr->speed : 0; +} + + + +void dumb_it_sr_set_speed(DUMB_IT_SIGRENDERER *sr, int speed) +{ + if (sr) sr->speed = speed; +} + + + +int dumb_it_sr_get_channel_volume(DUMB_IT_SIGRENDERER *sr, int channel) +{ + return sr ? sr->channel[channel].channelvolume : 0; +} + + + +void dumb_it_sr_set_channel_volume(DUMB_IT_SIGRENDERER *sr, int channel, int volume) +{ + if (sr) sr->channel[channel].channelvolume = volume; +} diff --git a/apps/codecs/dumb/src/it/itorder.c b/apps/codecs/dumb/src/it/itorder.c new file mode 100644 index 0000000000..6959f05443 --- /dev/null +++ b/apps/codecs/dumb/src/it/itorder.c @@ -0,0 +1,63 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * itorder.c - Code to fix invalid patterns in / / \ \ + * the pattern table. | < / \_ + * | \/ /\ / + * By Julien Cugniere. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + + + +#include + +#include "dumb.h" +#include "internal/it.h" + + + +/* This function ensures that any pattern mentioned in the order table but + * not present in the pattern table is treated as an empty 64 rows pattern. + * This is done by adding such a dummy pattern at the end of the pattern + * table, and redirect invalid orders to it. + * Patterns 254 and 255 are left untouched, unless the signal is an XM. + */ +int _dumb_it_fix_invalid_orders(DUMB_IT_SIGDATA *sigdata) +{ + int i; + int found_some = 0; + + int first_invalid = sigdata->n_patterns; + int last_invalid = (sigdata->flags & IT_WAS_AN_XM) ? 255 : 253; + + for (i = 0; i < sigdata->n_orders; i++) { + if (sigdata->order[i] >= first_invalid && sigdata->order[i] <= last_invalid) { + sigdata->order[i] = sigdata->n_patterns; + found_some = 1; + } + } + + if (found_some) { + IT_PATTERN *new_pattern = realloc(sigdata->pattern, sizeof(*sigdata->pattern) * (sigdata->n_patterns + 1)); + if (!new_pattern) + return -1; + + new_pattern[sigdata->n_patterns].n_rows = 64; + new_pattern[sigdata->n_patterns].n_entries = 0; + new_pattern[sigdata->n_patterns].entry = NULL; + sigdata->pattern = new_pattern; + sigdata->n_patterns++; + } + + return 0; +} diff --git a/apps/codecs/dumb/src/it/itread.c b/apps/codecs/dumb/src/it/itread.c new file mode 100644 index 0000000000..7384b30a92 --- /dev/null +++ b/apps/codecs/dumb/src/it/itread.c @@ -0,0 +1,1181 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * itread.c - Code to read an Impulse Tracker / / \ \ + * module from an open file. | < / \_ + * | \/ /\ / + * Based on the loader from an IT player by Bob. \_ / > / + * Adapted for DUMB by entheh. | \ / / + * | ' / + * \__/ + */ + +#include +#include //might not be necessary later; required for memset + +#include "dumb.h" +#include "internal/it.h" + + + +#define INVESTIGATE_OLD_INSTRUMENTS + + + +static int it_seek(DUMBFILE *f, long offset) +{ + long pos = dumbfile_pos(f); + + if (pos > offset) + return -1; + + if (pos < offset) + if (dumbfile_skip(f, offset - pos)) + return -1; + + return 0; +} + + + +typedef unsigned char byte; +typedef unsigned short word; +typedef unsigned long dword; + + + +static unsigned char *sourcebuf = NULL; +static unsigned char *sourcepos = NULL; +static unsigned char *sourceend; +static int rembits = 0; + + + +static int readblock(DUMBFILE *f) +{ + long size; + int c; + + size = dumbfile_igetw(f); + if (size < 0) + return size; + + sourcebuf = malloc(size); + if (!sourcebuf) + return -1; + + c = dumbfile_getnc((char *)sourcebuf, size, f); + if (c < size) { + free(sourcebuf); + sourcebuf = NULL; + return -1; + } + + sourcepos = sourcebuf; + sourceend = sourcebuf + size; + rembits = 8; + return 0; +} + + + +static void freeblock(void) +{ + free(sourcebuf); + sourcebuf = NULL; +} + + + +static int readbits(int bitwidth) +{ + int val = 0; + int b = 0; + + if (sourcepos >= sourceend) return val; + + while (bitwidth > rembits) { + val |= *sourcepos++ << b; + if (sourcepos >= sourceend) return val; + b += rembits; + bitwidth -= rembits; + rembits = 8; + } + + val |= (*sourcepos & ((1 << bitwidth) - 1)) << b; + *sourcepos >>= bitwidth; + rembits -= bitwidth; + + return val; +} + + + +/** WARNING - do we even need to pass `right`? */ +/** WARNING - why bother memsetting at all? The whole array is written... */ +// if we do memset, dumb_silence() would be neater... +static int decompress8(DUMBFILE *f, sample_t *left, sample_t *right, int len, int cmwt) +{ + int blocklen, blockpos; + byte bitwidth; + word val; + char d1, d2; + + memset(left, 0, len * sizeof(*left)); + if (right) { + memset(right, 0, len * sizeof(*right)); + len <<= 1; + } + + while (len > 0) { + //Read a block of compressed data: + if (readblock(f)) + return -1; + //Set up a few variables + blocklen = (len < 0x8000) ? len : 0x8000; //Max block length is 0x8000 bytes + blockpos = 0; + bitwidth = 9; + d1 = d2 = 0; + //Start the decompression: + while (blockpos < blocklen) { + //Read a value: + val = (word)readbits(bitwidth); + //Check for bit width change: + + if (bitwidth < 7) { //Method 1: + if (val == (1 << (bitwidth - 1))) { + val = (word)readbits(3) + 1; + bitwidth = (val < bitwidth) ? val : val + 1; + continue; + } + } + else if (bitwidth < 9) { //Method 2 + byte border = (0xFF >> (9 - bitwidth)) - 4; + + if (val > border && val <= (border + 8)) { + val -= border; + bitwidth = (val < bitwidth) ? val : val + 1; + continue; + } + } + else if (bitwidth == 9) { //Method 3 + if (val & 0x100) { + bitwidth = (val + 1) & 0xFF; + continue; + } + } + else { //Illegal width, abort ? + freeblock(); + return -1; + } + + //Expand the value to signed byte: + { + char v; //The sample value: + if (bitwidth < 8) { + byte shift = 8 - bitwidth; + v = (val << shift); + v >>= shift; + } + else + v = (char)val; + + //And integrate the sample value + //(It always has to end with integration doesn't it ? ;-) + d1 += v; + d2 += d1; + } + + //Store ! + /* Version 2.15 was an unofficial version with hacked compression + * code. Yay, better compression :D + */ + if (right && (len & 1)) + *right++ = (int)(signed char)(cmwt == 0x215 ? d2 : d1) << 16; + else + *left++ = (int)(signed char)(cmwt == 0x215 ? d2 : d1) << 16; + len--; + blockpos++; + } + freeblock(); + } + return 0; +} + + + +static int decompress16(DUMBFILE *f, sample_t *left, sample_t *right, int len, int cmwt) +{ + int blocklen, blockpos; + byte bitwidth; + long val; + short d1, d2; + + memset(left, 0, len * sizeof(*left)); + if (right) { + memset(right, 0, len * sizeof(*right)); + len <<= 1; + } + + while (len > 0) { + //Read a block of compressed data: + if (readblock(f)) + return -1; + //Set up a few variables + blocklen = (len < 0x4000) ? len : 0x4000; // Max block length is 0x4000 bytes + blockpos = 0; + bitwidth = 17; + d1 = d2 = 0; + //Start the decompression: + while (blockpos < blocklen) { + val = readbits(bitwidth); + //Check for bit width change: + + if (bitwidth < 7) { //Method 1: + if (val == (1 << (bitwidth - 1))) { + val = readbits(4) + 1; + bitwidth = (val < bitwidth) ? val : val + 1; + continue; + } + } + else if (bitwidth < 17) { //Method 2 + word border = (0xFFFF >> (17 - bitwidth)) - 8; + + if (val > border && val <= (border + 16)) { + val -= border; + bitwidth = val < bitwidth ? val : val + 1; + continue; + } + } + else if (bitwidth == 17) { //Method 3 + if (val & 0x10000) { + bitwidth = (val + 1) & 0xFF; + continue; + } + } + else { //Illegal width, abort ? + freeblock(); + return -1; + } + + //Expand the value to signed byte: + { + short v; //The sample value: + if (bitwidth < 16) { + byte shift = 16 - bitwidth; + v = (short)(val << shift); + v >>= shift; + } + else + v = (short)val; + + //And integrate the sample value + //(It always has to end with integration doesn't it ? ;-) + d1 += v; + d2 += d1; + } + + //Store ! + /* Version 2.15 was an unofficial version with hacked compression + * code. Yay, better compression :D + */ + if (right && (len & 1)) + *right++ = (int)(signed short)(cmwt == 0x215 ? d2 : d1) << 8; + else + *left++ = (int)(signed short)(cmwt == 0x215 ? d2 : d1) << 8; + len--; + blockpos++; + } + freeblock(); + } + return 0; +} + + + +static int it_read_envelope(IT_ENVELOPE *envelope, DUMBFILE *f) +{ + int n; + + envelope->flags = dumbfile_getc(f); + envelope->n_nodes = dumbfile_getc(f); + envelope->loop_start = dumbfile_getc(f); + envelope->loop_end = dumbfile_getc(f); + envelope->sus_loop_start = dumbfile_getc(f); + envelope->sus_loop_end = dumbfile_getc(f); + for (n = 0; n < envelope->n_nodes; n++) { + envelope->node_y[n] = dumbfile_getc(f); + envelope->node_t[n] = dumbfile_igetw(f); + } + dumbfile_skip(f, 75 - envelope->n_nodes * 3 + 1); + + return dumbfile_error(f); +} + + + +static int it_read_old_instrument(IT_INSTRUMENT *instrument, DUMBFILE *f) +{ + int n; + + if (dumbfile_mgetl(f) != IT_INSTRUMENT_SIGNATURE) + return -1; + + /* Skip DOS Filename. */ + dumbfile_skip(f, 13); + + instrument->volume_envelope.flags = dumbfile_getc(f); + instrument->volume_envelope.loop_start = dumbfile_getc(f); + instrument->volume_envelope.loop_end = dumbfile_getc(f); + instrument->volume_envelope.sus_loop_start = dumbfile_getc(f); + instrument->volume_envelope.sus_loop_end = dumbfile_getc(f); + + /* Skip two unused bytes. */ + dumbfile_skip(f, 2); + + /* In the old instrument format, fadeout ranges from 0 to 64, and is + * subtracted at intervals from a value starting at 512. In the new + * format, all these values are doubled. Therefore we double when loading + * from the old instrument format - that way we don't have to think about + * it later. + */ + instrument->fadeout = dumbfile_igetw(f) << 1; + instrument->new_note_action = dumbfile_getc(f); + instrument->dup_check_type = dumbfile_getc(f); + instrument->dup_check_action = DCA_NOTE_CUT; // This might be wrong! + /** WARNING - what is the duplicate check action for old-style instruments? */ + + /* Skip Tracker Version and Number of Samples. These are only used in + * separate instrument files. Also skip unused bytes and Instrument Name. + */ + dumbfile_skip(f, 36); + + instrument->pp_separation = 0; + instrument->pp_centre = 60; + instrument->global_volume = 128; + /** WARNING - should global_volume be 64 or something? */ + instrument->default_pan = 32; + /** WARNING - should default_pan be 128, meaning don`t use? */ + instrument->random_volume = 0; + instrument->random_pan = 0; + + for (n = 0; n < 120; n++) { + instrument->map_note[n] = dumbfile_getc(f); + instrument->map_sample[n] = dumbfile_getc(f); + } + + /* Skip "Volume envelope (200 bytes)". */ + // - need to know better what this is for though. + dumbfile_skip(f, 200); + +#if defined(INVESTIGATE_OLD_INSTRUMENTS) && 0 + fprintf(stderr, "Inst %02d Env:", n); +#endif + + for (n = 0; n < 25; n++) + { + instrument->volume_envelope.node_t[n] = dumbfile_getc(f); + instrument->volume_envelope.node_y[n] = dumbfile_getc(f); + +#if defined(INVESTIGATE_OLD_INSTRUMENTS) && 0 + fprintf(stderr, " %d,%d", + instrument->volume_envelope.node_t[n], + instrument->volume_envelope.node_y[n]); +#endif + + // This loop is unfinished, as we can probably escape from it before + // the end if we want to. Hence the otherwise useless dumbfile_skip() + // call below. + } + dumbfile_skip(f, 50 - (n << 1)); + instrument->volume_envelope.n_nodes = n; + +#if defined(INVESTIGATE_OLD_INSTRUMENTS) && 0 + fprintf(stderr, "\n"); +#endif + + if (dumbfile_error(f)) + return -1; + + instrument->filter_cutoff = 127; + instrument->filter_resonance = 0; + + instrument->pan_envelope.flags = 0; + instrument->pitch_envelope.flags = 0; + + return 0; +} + + + +static int it_read_instrument(IT_INSTRUMENT *instrument, DUMBFILE *f) +{ + int n; + + if (dumbfile_mgetl(f) != IT_INSTRUMENT_SIGNATURE) + return -1; + + /* Skip DOS Filename. */ + dumbfile_skip(f, 13); + + instrument->new_note_action = dumbfile_getc(f); + instrument->dup_check_type = dumbfile_getc(f); + instrument->dup_check_action = dumbfile_getc(f); + instrument->fadeout = dumbfile_igetw(f); + instrument->pp_separation = dumbfile_getc(f); + instrument->pp_centre = dumbfile_getc(f); + instrument->global_volume = dumbfile_getc(f); + instrument->default_pan = dumbfile_getc(f); + instrument->random_volume = dumbfile_getc(f); + instrument->random_pan = dumbfile_getc(f); + + /* Skip Tracker Version and Number of Samples. These are only used in + * separate instrument files. Also skip unused byte and Instrument Name. + */ + dumbfile_skip(f, 30); + + instrument->filter_cutoff = dumbfile_getc(f); + instrument->filter_resonance = dumbfile_getc(f); + + /* Skip MIDI Channel, Program and Bank. */ + dumbfile_skip(f, 4); + + for (n = 0; n < 120; n++) { + instrument->map_note[n] = dumbfile_getc(f); + instrument->map_sample[n] = dumbfile_getc(f); + } + + if (dumbfile_error(f)) + return -1; + + if (it_read_envelope(&instrument->volume_envelope, f)) return -1; + if (it_read_envelope(&instrument->pan_envelope, f)) return -1; + if (it_read_envelope(&instrument->pitch_envelope, f)) return -1; + + return 0; +} + + + +static int it_read_sample_header(IT_SAMPLE *sample, unsigned char *convert, long *offset, DUMBFILE *f) +{ + if (dumbfile_mgetl(f) != IT_SAMPLE_SIGNATURE) + return -1; + + /* Skip DOS Filename. */ + dumbfile_skip(f, 13); + + sample->global_volume = dumbfile_getc(f); + sample->flags = dumbfile_getc(f); + sample->default_volume = dumbfile_getc(f); + + /* Skip Sample Name. */ + dumbfile_skip(f, 26); + + *convert = dumbfile_getc(f); + sample->default_pan = dumbfile_getc(f); + sample->length = dumbfile_igetl(f); + sample->loop_start = dumbfile_igetl(f); + sample->loop_end = dumbfile_igetl(f); + sample->C5_speed = dumbfile_igetl(f); + sample->sus_loop_start = dumbfile_igetl(f); + sample->sus_loop_end = dumbfile_igetl(f); + +#ifdef STEREO_SAMPLES_COUNT_AS_TWO + if (sample->flags & IT_SAMPLE_STEREO) { + sample->length >>= 1; + sample->loop_start >>= 1; + sample->loop_end >>= 1; + sample->C5_speed >>= 1; + sample->sus_loop_start >>= 1; + sample->sus_loop_end >>= 1; + } +#endif + + if (sample->flags & IT_SAMPLE_EXISTS) { + if (sample->length <= 0) + sample->flags &= ~IT_SAMPLE_EXISTS; + else { + if ((unsigned int)sample->loop_end > (unsigned int)sample->length) + sample->flags &= ~IT_SAMPLE_LOOP; + else if ((unsigned int)sample->loop_start >= (unsigned int)sample->loop_end) + sample->flags &= ~IT_SAMPLE_LOOP; + + if ((unsigned int)sample->sus_loop_end > (unsigned int)sample->length) + sample->flags &= ~IT_SAMPLE_SUS_LOOP; + else if ((unsigned int)sample->sus_loop_start >= (unsigned int)sample->sus_loop_end) + sample->flags &= ~IT_SAMPLE_SUS_LOOP; + + /* We may be able to truncate the sample to save memory. */ + if (sample->flags & IT_SAMPLE_LOOP) { + if ((sample->flags & IT_SAMPLE_SUS_LOOP) && sample->sus_loop_end >= sample->loop_end) + sample->length = sample->sus_loop_end; + else + sample->length = sample->loop_end; + } + } + } + + *offset = dumbfile_igetl(f); + + sample->vibrato_speed = dumbfile_getc(f); + sample->vibrato_depth = dumbfile_getc(f); + sample->vibrato_rate = dumbfile_getc(f); + sample->vibrato_waveform = dumbfile_getc(f); + + return dumbfile_error(f); +} + + + +static long it_read_sample_data(int cmwt, IT_SAMPLE *sample, unsigned char convert, DUMBFILE *f) +{ + long n; + + sample->left = malloc(sample->length * sizeof(*sample->left)); + if (!sample->left) + return -1; + + if (sample->flags & IT_SAMPLE_STEREO) { + sample->right = malloc(sample->length * sizeof(*sample->right)); + if (!sample->right) + return -1; + } + + if (sample->flags & 8) { + /* If the sample is packed, then we must unpack it. */ + + /** WARNING - unresolved business here... test with ModPlug? */ + + if (sample->flags & IT_SAMPLE_STEREO) + exit(37); + +/* +//#ifndef STEREO_SAMPLES_COUNT_AS_TWO + ASSERT(!(sample->flags & IT_SAMPLE_STEREO)); +//#endif +*/ + if (sample->flags & IT_SAMPLE_16BIT) + decompress16(f, sample->left, sample->right, sample->length, cmwt); + else + decompress8(f, sample->left, sample->right, sample->length, cmwt); + } else if (sample->flags & IT_SAMPLE_STEREO) { + if (sample->flags & IT_SAMPLE_16BIT) { + if (convert & 2) { + for (n = 0; n < sample->length; n++) { + sample->left[n] = (int)(signed short)dumbfile_mgetw(f) << 8; + sample->right[n] = (int)(signed short)dumbfile_mgetw(f) << 8; + } + } else { + for (n = 0; n < sample->length; n++) { + sample->left[n] = (int)(signed short)dumbfile_igetw(f) << 8; + sample->right[n] = (int)(signed short)dumbfile_igetw(f) << 8; + } + } + } else { + for (n = 0; n < sample->length; n++) { + sample->left[n] = (int)(signed char)dumbfile_getc(f) << 16; + sample->right[n] = (int)(signed char)dumbfile_getc(f) << 16; + } + } + } else if (sample->flags & IT_SAMPLE_16BIT) { + if (convert & 2) + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed short)dumbfile_mgetw(f) << 8; + else + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed short)dumbfile_igetw(f) << 8; + } else + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed char)dumbfile_getc(f) << 16; + + if (dumbfile_error(f)) + return -1; + + if (!(convert & 1)) { + /* Convert to signed. */ + for (n = 0; n < sample->length; n++) + sample->left[n] ^= 0xFF800000; + + if (sample->right) + for (n = 0; n < sample->length; n++) + sample->right[n] ^= 0xFF800000; + } + + /* NOT SUPPORTED: + * + * convert & 4 - Samples stored as delta values + * convert & 16 - Samples stored as TX-Wave 12-bit values + * convert & 32 - Left/Right/All Stereo prompt + */ + + return 0; +} + + + +#define DETECT_DUPLICATE_CHANNELS +#ifdef DETECT_DUPLICATE_CHANNELS +#include +#endif +static int it_read_pattern(IT_PATTERN *pattern, DUMBFILE *f, unsigned char *buffer) +{ + unsigned char cmask[DUMB_IT_N_CHANNELS]; + unsigned char cnote[DUMB_IT_N_CHANNELS]; + unsigned char cinstrument[DUMB_IT_N_CHANNELS]; + unsigned char cvolpan[DUMB_IT_N_CHANNELS]; + unsigned char ceffect[DUMB_IT_N_CHANNELS]; + unsigned char ceffectvalue[DUMB_IT_N_CHANNELS]; +#ifdef DETECT_DUPLICATE_CHANNELS + IT_ENTRY *dupentry[DUMB_IT_N_CHANNELS]; +#endif + + int n_entries = 0; + int buflen; + int bufpos = 0; + + IT_ENTRY *entry; + + unsigned char channel; + unsigned char mask; + + memset(cmask, 0, sizeof(cmask)); + memset(cnote, 0, sizeof(cnote)); + memset(cinstrument, 0, sizeof(cinstrument)); + memset(cvolpan, 0, sizeof(cvolpan)); + memset(ceffect, 0, sizeof(ceffect)); + memset(ceffectvalue, 0, sizeof(ceffectvalue)); +#ifdef DETECT_DUPLICATE_CHANNELS + { + int i; + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) dupentry[i] = NULL; + } +#endif + + buflen = dumbfile_igetw(f); + pattern->n_rows = dumbfile_igetw(f); + + /* Skip four unused bytes. */ + dumbfile_skip(f, 4); + + if (dumbfile_error(f)) + return -1; + + /* Read in the pattern data. */ + dumbfile_getnc(buffer, buflen, f); + + if (dumbfile_error(f)) + return -1; + + /* Scan the pattern data, and work out how many entries we need room for. */ + while (bufpos < buflen) { + unsigned char b = buffer[bufpos++]; + + if (b == 0) { + /* End of row */ + n_entries++; + continue; + } + + channel = (b - 1) & 63; + + if (b & 128) + cmask[channel] = mask = buffer[bufpos++]; + else + mask = cmask[channel]; + + { + static const unsigned char used[16] = {0, 1, 1, 2, 1, 2, 2, 3, 2, 3, 3, 4, 3, 4, 4, 5}; + n_entries += (mask != 0); + bufpos += used[mask & 15]; + } + } + + pattern->n_entries = n_entries; + + pattern->entry = malloc(n_entries * sizeof(*pattern->entry)); + + if (!pattern->entry) + return -1; + + bufpos = 0; + memset(cmask, 0, sizeof(cmask)); + + entry = pattern->entry; + + while (bufpos < buflen) { + unsigned char b = buffer[bufpos++]; + + if (b == 0) { + /* End of row */ + IT_SET_END_ROW(entry); + entry++; +#ifdef DETECT_DUPLICATE_CHANNELS + { + int i; + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) dupentry[i] = NULL; + } +#endif + continue; + } + + channel = (b - 1) & 63; + + if (b & 128) + cmask[channel] = mask = buffer[bufpos++]; + else + mask = cmask[channel]; + + if (mask) { + entry->mask = (mask & 15) | (mask >> 4); + entry->channel = channel; + + if (mask & IT_ENTRY_NOTE) + cnote[channel] = entry->note = buffer[bufpos++]; + else if (mask & (IT_ENTRY_NOTE << 4)) + entry->note = cnote[channel]; + + if (mask & IT_ENTRY_INSTRUMENT) + cinstrument[channel] = entry->instrument = buffer[bufpos++]; + else if (mask & (IT_ENTRY_INSTRUMENT << 4)) + entry->instrument = cinstrument[channel]; + + if (mask & IT_ENTRY_VOLPAN) + cvolpan[channel] = entry->volpan = buffer[bufpos++]; + else if (mask & (IT_ENTRY_VOLPAN << 4)) + entry->volpan = cvolpan[channel]; + + if (mask & IT_ENTRY_EFFECT) { + ceffect[channel] = entry->effect = buffer[bufpos++]; + ceffectvalue[channel] = entry->effectvalue = buffer[bufpos++]; + } else { + entry->effect = ceffect[channel]; + entry->effectvalue = ceffectvalue[channel]; + } + +#if defined( DETECT_DUPLICATE_CHANNELS) && 0 + if (dupentry[channel]) { + FILE *f = fopen("dupentry.txt", "a"); + if (!f) abort(); + fprintf(f, "Two events on channel %d:", channel); + fprintf(f, " Event #1:"); + if (dupentry[channel]->mask & IT_ENTRY_NOTE ) fprintf(f, " %03d", dupentry[channel]->note ); else fprintf(f, " ..."); + if (dupentry[channel]->mask & IT_ENTRY_INSTRUMENT) fprintf(f, " %03d", dupentry[channel]->instrument); else fprintf(f, " ..."); + if (dupentry[channel]->mask & IT_ENTRY_VOLPAN ) fprintf(f, " %03d", dupentry[channel]->volpan ); else fprintf(f, " ..."); + if (dupentry[channel]->mask & IT_ENTRY_EFFECT) fprintf(f, " %c%02X\n", 'A' - 1 + dupentry[channel]->effect, dupentry[channel]->effectvalue); else fprintf(f, " ...\n"); + fprintf(f, " Event #2:"); + if (entry->mask & IT_ENTRY_NOTE ) fprintf(f, " %03d", entry->note ); else fprintf(f, " ..."); + if (entry->mask & IT_ENTRY_INSTRUMENT) fprintf(f, " %03d", entry->instrument); else fprintf(f, " ..."); + if (entry->mask & IT_ENTRY_VOLPAN ) fprintf(f, " %03d", entry->volpan ); else fprintf(f, " ..."); + if (entry->mask & IT_ENTRY_EFFECT) fprintf(f, " %c%02X\n", 'A' - 1 + entry->effect, entry->effectvalue); else fprintf(f, " ...\n"); + fclose(f); + } + dupentry[channel] = entry; +#endif + + entry++; + } + } + + ASSERT(entry == pattern->entry + n_entries); + + return 0; +} + + + +/* Currently we assume the sample data are stored after the sample headers in + * module files. This assumption may be unjustified; let me know if you have + * trouble. + */ + +#define IT_COMPONENT_INSTRUMENT 1 +#define IT_COMPONENT_PATTERN 2 +#define IT_COMPONENT_SAMPLE 3 + +typedef struct IT_COMPONENT +{ + unsigned char type; + unsigned char n; + long offset; + short sampfirst; /* component[sampfirst] = first sample data after this */ + short sampnext; /* sampnext is used to create linked lists of sample data */ +} +IT_COMPONENT; + + + +static int it_component_compare(const void *e1, const void *e2) +{ + return ((const IT_COMPONENT *)e1)->offset - + ((const IT_COMPONENT *)e2)->offset; +} + + + +static sigdata_t *it_load_sigdata(DUMBFILE *f) +{ + DUMB_IT_SIGDATA *sigdata; + + int cwt, cmwt; + int special; + + IT_COMPONENT *component; + int n_components = 0; + + unsigned char sample_convert[256]; + + int n; + + unsigned char *buffer; + + if (dumbfile_mgetl(f) != IT_SIGNATURE) + return NULL; + + sigdata = malloc(sizeof(*sigdata)); + + if (!sigdata) + return NULL; + + sigdata->order = NULL; + sigdata->instrument = NULL; + sigdata->sample = NULL; + sigdata->pattern = NULL; + sigdata->midi = NULL; + sigdata->checkpoint = NULL; + + /* Skip song name and pattern row highlight info. */ + dumbfile_skip(f, 28); + + sigdata->n_orders = dumbfile_igetw(f); + sigdata->n_instruments = dumbfile_igetw(f); + sigdata->n_samples = dumbfile_igetw(f); + sigdata->n_patterns = dumbfile_igetw(f); + + cwt = dumbfile_igetw(f); + cmwt = dumbfile_igetw(f); + + sigdata->flags = dumbfile_igetw(f); + special = dumbfile_igetw(f); + + sigdata->global_volume = dumbfile_getc(f); + sigdata->mixing_volume = dumbfile_getc(f); + sigdata->speed = dumbfile_getc(f); + if (sigdata->speed == 0) sigdata->speed = 6; // Should we? What about tempo? + sigdata->tempo = dumbfile_getc(f); + sigdata->pan_separation = dumbfile_getc(f); /** WARNING: use this */ + + /* Skip Pitch Wheel Depth, Message Length, Message Offset and Reserved. */ + dumbfile_skip(f, 11); + + dumbfile_getnc(sigdata->channel_pan, DUMB_IT_N_CHANNELS, f); + dumbfile_getnc(sigdata->channel_volume, DUMB_IT_N_CHANNELS, f); + + if (dumbfile_error(f) || sigdata->n_orders <= 0 || sigdata->n_instruments > 256 || sigdata->n_samples > 256 || sigdata->n_patterns > 256) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + sigdata->order = malloc(sigdata->n_orders); + if (!sigdata->order) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (sigdata->n_instruments) { + sigdata->instrument = malloc(sigdata->n_instruments * sizeof(*sigdata->instrument)); + if (!sigdata->instrument) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + } + + if (sigdata->n_samples) { + sigdata->sample = malloc(sigdata->n_samples * sizeof(*sigdata->sample)); + if (!sigdata->sample) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (n = 0; n < sigdata->n_samples; n++) + sigdata->sample[n].right = sigdata->sample[n].left = NULL; + } + + if (sigdata->n_patterns) { + sigdata->pattern = malloc(sigdata->n_patterns * sizeof(*sigdata->pattern)); + if (!sigdata->pattern) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (n = 0; n < sigdata->n_patterns; n++) + sigdata->pattern[n].entry = NULL; + } + + dumbfile_getnc(sigdata->order, sigdata->n_orders, f); + sigdata->restart_position = 0; + + component = malloc(768 * sizeof(*component)); + if (!component) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + for (n = 0; n < sigdata->n_instruments; n++) { + component[n_components].type = IT_COMPONENT_INSTRUMENT; + component[n_components].n = n; + component[n_components].offset = dumbfile_igetl(f); + component[n_components].sampfirst = -1; + n_components++; + } + + for (n = 0; n < sigdata->n_samples; n++) { + component[n_components].type = IT_COMPONENT_SAMPLE; + component[n_components].n = n; + component[n_components].offset = dumbfile_igetl(f); + component[n_components].sampfirst = -1; + n_components++; + } + + for (n = 0; n < sigdata->n_patterns; n++) { + long offset = dumbfile_igetl(f); + if (offset) { + component[n_components].type = IT_COMPONENT_PATTERN; + component[n_components].n = n; + component[n_components].offset = offset; + component[n_components].sampfirst = -1; + n_components++; + } else { + /* Empty 64-row pattern */ + sigdata->pattern[n].n_rows = 64; + sigdata->pattern[n].n_entries = 0; + } + } + + if (dumbfile_error(f)) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (!(sigdata->flags & 128) != !(special & 8)) { +#if 0 + fprintf(stderr, "Flags Bit 7 (\"Request embedded MIDI configuration\"): %s\n", sigdata->flags & 128 ? "=SET=" : "clear"); + fprintf(stderr, "Special Bit 3 (\"MIDI configuration embedded\") : %s\n", special & 8 ? "=SET=" : "clear"); + fprintf(stderr, "entheh would like to investigate this IT file.\n"); + fprintf(stderr, "Please contact him! entheh@users.sf.net\n"); +#endif + } + + if (special & 8) { + /* MIDI configuration is embedded. */ + unsigned char mididata[32]; + int i; + sigdata->midi = malloc(sizeof(*sigdata->midi)); + if (!sigdata->midi) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + // Should we be happy with this outcome in some situations? + } + // What are we skipping? + i = dumbfile_igetw(f); + if (dumbfile_error(f) || dumbfile_skip(f, 8*i)) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + /* Read embedded MIDI configuration */ + // What are the first 9 commands for? + if (dumbfile_skip(f, 32*9)) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (i = 0; i < 16; i++) { + unsigned char len = 0; + int j, leftdigit = -1; + if (dumbfile_getnc(mididata, 32, f) < 32) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + sigdata->midi->SFmacroz[i] = 0; + for (j = 0; j < 32; j++) { + if (leftdigit >= 0) { + if (mididata[j] == 0) { + sigdata->midi->SFmacro[i][len++] = leftdigit; + break; + } else if (mididata[j] == ' ') + sigdata->midi->SFmacro[i][len++] = leftdigit; + else if (mididata[j] >= '0' && mididata[j] <= '9') + sigdata->midi->SFmacro[i][len++] = (leftdigit << 4) | (mididata[j] - '0'); + else if (mididata[j] >= 'A' && mididata[j] <= 'F') + sigdata->midi->SFmacro[i][len++] = (leftdigit << 4) | (mididata[j] - 'A' + 0xA); + leftdigit = -1; + } else if (mididata[j] == 0) + break; + else if (mididata[j] == 'z') + sigdata->midi->SFmacroz[i] |= 1 << len++; + else if (mididata[j] >= '0' && mididata[j] <= '9') + leftdigit = mididata[j] - '0'; + else if (mididata[j] >= 'A' && mididata[j] <= 'F') + leftdigit = mididata[j] - 'A' + 0xA; + } + sigdata->midi->SFmacrolen[i] = len; + } + for (i = 0; i < 128; i++) { + unsigned char len = 0; + int j, leftdigit = -1; + dumbfile_getnc(mididata, 32, f); + for (j = 0; j < 32; j++) { + if (leftdigit >= 0) { + if (mididata[j] == 0) { + sigdata->midi->Zmacro[i][len++] = leftdigit; + break; + } else if (mididata[j] == ' ') + sigdata->midi->Zmacro[i][len++] = leftdigit; + else if (mididata[j] >= '0' && mididata[j] <= '9') + sigdata->midi->Zmacro[i][len++] = (leftdigit << 4) | (mididata[j] - '0'); + else if (mididata[j] >= 'A' && mididata[j] <= 'F') + sigdata->midi->Zmacro[i][len++] = (leftdigit << 4) | (mididata[j] - 'A' + 0xA); + leftdigit = -1; + } else if (mididata[j] == 0) + break; + else if (mididata[j] >= '0' && mididata[j] <= '9') + leftdigit = mididata[j] - '0'; + else if (mididata[j] >= 'A' && mididata[j] <= 'F') + leftdigit = mididata[j] - 'A' + 0xA; + } + sigdata->midi->Zmacrolen[i] = len; + } + } + + sigdata->flags &= IT_REAL_FLAGS; + + qsort(component, n_components, sizeof(IT_COMPONENT), &it_component_compare); + + buffer = malloc(65536); + if (!buffer) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + for (n = 0; n < n_components; n++) { + long offset; + int m; + + if (it_seek(f, component[n].offset)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + switch (component[n].type) { + + case IT_COMPONENT_INSTRUMENT: + if (cmwt < 0x200) + m = it_read_old_instrument(&sigdata->instrument[component[n].n], f); + else + m = it_read_instrument(&sigdata->instrument[component[n].n], f); + + if (m) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + break; + + case IT_COMPONENT_PATTERN: + if (it_read_pattern(&sigdata->pattern[component[n].n], f, buffer)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + break; + + case IT_COMPONENT_SAMPLE: + if (it_read_sample_header(&sigdata->sample[component[n].n], &sample_convert[component[n].n], &offset, f)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (sigdata->sample[component[n].n].flags & IT_SAMPLE_EXISTS) { + short *sample; + + for (m = n + 1; m < n_components; m++) + if (component[m].offset > offset) + break; + m--; + + sample = &component[m].sampfirst; + + while (*sample >= 0 && component[*sample].offset <= offset) + sample = &component[*sample].sampnext; + + component[n].sampnext = *sample; + *sample = n; + + component[n].offset = offset; + } + } + + m = component[n].sampfirst; + + while (m >= 0) { + if (it_seek(f, component[m].offset)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (it_read_sample_data(cmwt, &sigdata->sample[component[m].n], sample_convert[component[m].n], f)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + m = component[m].sampnext; + } + } + + free(buffer); + free(component); + + _dumb_it_fix_invalid_orders(sigdata); + + return sigdata; +} + + + +DUH *dumb_read_it(DUMBFILE *f) +{ + sigdata_t *sigdata; + long length; + + DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it; + + sigdata = it_load_sigdata(f); + + if (!sigdata) + return NULL; + + length = _dumb_it_build_checkpoints(sigdata); + + return make_duh(length, 1, &descptr, &sigdata); +} + diff --git a/apps/codecs/dumb/src/it/itrender.c b/apps/codecs/dumb/src/it/itrender.c new file mode 100644 index 0000000000..103654fd77 --- /dev/null +++ b/apps/codecs/dumb/src/it/itrender.c @@ -0,0 +1,3512 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * itrender.c - Code to render an Impulse Tracker / / \ \ + * module. | < / \_ + * | \/ /\ / + * Written - painstakingly - by entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include + +#include "dumb.h" +#include "internal/it.h" + + + +static IT_PLAYING *dup_playing(IT_PLAYING *src, IT_CHANNEL *dstchannel, IT_CHANNEL *srcchannel) +{ + IT_PLAYING *dst; + + if (!src) return NULL; + + dst = malloc(sizeof(*dst)); + if (!dst) return NULL; + + dst->flags = src->flags; + + ASSERT(src->channel); + dst->channel = &dstchannel[src->channel - srcchannel]; + dst->sample = src->sample; + dst->instrument = src->instrument; + dst->env_instrument = src->env_instrument; + + dst->sampnum = src->sampnum; + dst->instnum = src->instnum; + + dst->channel_volume = src->channel_volume; + + dst->volume = src->volume; + dst->pan = src->pan; + + dst->note = src->note; + + dst->filter_cutoff = src->filter_cutoff; + dst->filter_resonance = src->filter_resonance; + + dst->true_filter_cutoff = src->true_filter_cutoff; + dst->true_filter_resonance = src->true_filter_resonance; + + dst->vibrato_speed = src->vibrato_speed; + dst->vibrato_depth = src->vibrato_depth; + dst->vibrato_n = src->vibrato_n; + dst->vibrato_time = src->vibrato_time; + + dst->tremolo_speed = src->tremolo_speed; + dst->tremolo_depth = src->tremolo_depth; + dst->tremolo_time = src->tremolo_time; + + dst->sample_vibrato_time = src->sample_vibrato_time; + dst->sample_vibrato_depth = src->sample_vibrato_depth; + + dst->slide = src->slide; + dst->delta = src->delta; + + dst->volume_envelope = src->volume_envelope; + dst->pan_envelope = src->pan_envelope; + dst->pitch_envelope = src->pitch_envelope; + + dst->fadeoutcount = src->fadeoutcount; + + dst->filter_state[0] = src->filter_state[0]; + dst->filter_state[1] = src->filter_state[1]; + + dst->resampler[0] = src->resampler[0]; + dst->resampler[1] = src->resampler[1]; + dst->resampler[1].pickup_data = dst->resampler[0].pickup_data = dst; + dst->time_lost = src->time_lost; + + return dst; +} + + + +static void dup_channel(IT_CHANNEL *dst, IT_CHANNEL *src) +{ + dst->flags = src->flags; + + dst->volume = src->volume; + dst->volslide = src->volslide; + dst->xm_volslide = src->xm_volslide; + + dst->pan = src->pan; + dst->truepan = src->truepan; + + dst->channelvolume = src->channelvolume; + dst->channelvolslide = src->channelvolslide; + + dst->instrument = src->instrument; + dst->note = src->note; + + dst->SFmacro = src->SFmacro; + + dst->filter_cutoff = src->filter_cutoff; + dst->filter_resonance = src->filter_resonance; + + dst->note_cut_count = src->note_cut_count; + dst->note_delay_count = src->note_delay_count; + dst->note_delay_entry = src->note_delay_entry; + + dst->arpeggio = src->arpeggio; + dst->retrig = src->retrig; + dst->xm_retrig = src->xm_retrig; + dst->retrig_tick = src->retrig_tick; + + dst->tremor_time = src->tremor_time; + + dst->portamento = src->portamento; + dst->toneporta = src->toneporta; + dst->destnote = src->destnote; + + dst->sample = src->sample; + dst->truenote = src->truenote; + + dst->midi_state = src->midi_state; + + dst->lastvolslide = src->lastvolslide; + dst->lastDKL = src->lastDKL; + dst->lastEF = src->lastEF; + dst->lastG = src->lastG; + dst->lastHspeed = src->lastHspeed; + dst->lastHdepth = src->lastHdepth; + dst->lastRspeed = src->lastRspeed; + dst->lastRdepth = src->lastRdepth; + dst->lastI = src->lastI; + dst->lastJ = src->lastJ; + dst->lastN = src->lastN; + dst->lastO = src->lastO; + dst->high_offset = src->high_offset; + dst->lastQ = src->lastQ; + dst->lastS = src->lastS; + dst->pat_loop_row = src->pat_loop_row; + dst->pat_loop_count = src->pat_loop_count; + dst->lastW = src->lastW; + + dst->xm_lastE1 = src->xm_lastE1; + dst->xm_lastE2 = src->xm_lastE2; + dst->xm_lastEA = src->xm_lastEA; + dst->xm_lastEB = src->xm_lastEB; + dst->xm_lastX1 = src->xm_lastX1; + dst->xm_lastX2 = src->xm_lastX2; + + dst->playing = dup_playing(src->playing, dst, src); +} + + + +/* Allocate the new callbacks first, then pass them to this function! + * It will free them on failure. + */ +static DUMB_IT_SIGRENDERER *dup_sigrenderer(DUMB_IT_SIGRENDERER *src, int n_channels, IT_CALLBACKS *callbacks) +{ + DUMB_IT_SIGRENDERER *dst; + int i; + + if (!src) { + if (callbacks) free(callbacks); + return NULL; + } + + dst = malloc(sizeof(*dst)); + if (!dst) { + if (callbacks) free(callbacks); + return NULL; + } + + dst->sigdata = src->sigdata; + + dst->n_channels = n_channels; + + dst->globalvolume = src->globalvolume; + dst->globalvolslide = src->globalvolslide; + + dst->tempo = src->tempo; + dst->temposlide = src->temposlide; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) + dup_channel(&dst->channel[i], &src->channel[i]); + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) + dst->playing[i] = dup_playing(src->playing[i], dst->channel, src->channel); + + dst->tick = src->tick; + dst->speed = src->speed; + dst->rowcount = src->rowcount; + + dst->order = src->order; + dst->row = src->row; + dst->processorder = src->processorder; + dst->processrow = src->processrow; + dst->breakrow = src->breakrow; + dst->pat_loop_row = src->pat_loop_row; + + dst->n_rows = src->n_rows; + + dst->entry_start = src->entry_start; + dst->entry = src->entry; + dst->entry_end = src->entry_end; + + dst->time_left = src->time_left; + dst->sub_time_left = src->sub_time_left; + + dst->click_remover = NULL; + + dst->callbacks = callbacks; + + return dst; +} + + + +static IT_MIDI default_midi = { + /* unsigned char SFmacro[16][16]; */ + { + {0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + }, + /* unsigned char SFmacrolen[16]; */ + {4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + /* unsigned short SFmacroz[16]; */ + /* Bitfield; bit 0 set = z in first position */ + { + 0x0008, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 + }, + /* unsigned char Zmacro[128][16]; */ + { + {0xF0, 0xF0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF0, 0xF0, 0x01, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + }, + /* unsigned char Zmacrolen[128]; */ + { + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + } +}; + + + +static void it_reset_filter_state(IT_FILTER_STATE *state) +{ + state->currsample = 0; + state->prevsample = 0; +} + + + +#define LOG10 2.30258509299 + +/* IMPORTANT: This function expects one extra sample in 'src' so it can apply + * click removal. It reads size samples, starting from src[0], and writes its + * output starting at dst[pos]. The pos parameter is required for getting + * click removal right. + */ +static void it_filter(DUMB_CLICK_REMOVER *cr, IT_FILTER_STATE *state, sample_t *dst, long pos, sample_t *src, long size, int sampfreq, int cutoff, int resonance) +{ + float currsample = state->currsample; + float prevsample = state->prevsample; + + float a, b, c; + + { + float inv_angle = (float)(sampfreq * pow(0.5, 0.25 + cutoff*(1.0/(24< 2.0f) d = 2.0f; + d = (loss - d) * inv_angle; + e = inv_angle * inv_angle; + a = 1.0f / (1.0f + d + e); + c = -e * a; + b = 1.0f - a - c; +#else + a = 1.0f / (inv_angle*inv_angle + inv_angle*loss + loss); + c = -(inv_angle*inv_angle) * a; + b = 1.0f - a - c; +#endif + } + + dst += pos; + + if (cr) { + float startstep = src[0]*a + currsample*b + prevsample*c; + dumb_record_click(cr, pos, (sample_t)startstep); + } + +#define INT_FILTERS +#ifdef INT_FILTERS +#define MULSCA(a, b) ((int)((LONG_LONG)((a) << 4) * (b) >> 32)) +#define SCALEB 12 + { + int ai = (int)(a * (1 << (16+SCALEB))); + int bi = (int)(b * (1 << (16+SCALEB))); + int ci = (int)(c * (1 << (16+SCALEB))); + sample_t csi = (sample_t)currsample; + sample_t psi = (sample_t)prevsample; + sample_t *dst_end = dst + size; + while (dst < dst_end) { + { + sample_t nsi = MULSCA(*src++, ai) + MULSCA(csi, bi) + MULSCA(psi, ci); + psi = csi; + csi = nsi; + } + *dst++ += csi; + } + currsample = csi; + prevsample = psi; + } +#else + { + int i = size % 3; + while (i > 0) { + { + float newsample = *src++*a + currsample*b + prevsample*c; + prevsample = currsample; + currsample = newsample; + } + *dst++ += (sample_t)currsample; + i--; + } + i = size / 3; + while (i > 0) { + float newsample; + /* Gotta love unrolled loops! */ + *dst++ += (sample_t)(newsample = *src++*a + currsample*b + prevsample*c); + *dst++ += (sample_t)(prevsample = *src++*a + newsample*b + currsample*c); + *dst++ += (sample_t)(currsample = *src++*a + prevsample*b + newsample*c); + i--; + } + } +#endif + + if (cr) { + float endstep = *src*a + currsample*b + prevsample*c; + dumb_record_click(cr, pos + size, -(sample_t)endstep); + } + + state->currsample = currsample; + state->prevsample = prevsample; +} + +#undef LOG10 + + + +static signed char it_sine[256] = { + 0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 16, 17, 19, 20, 22, 23, + 24, 26, 27, 29, 30, 32, 33, 34, 36, 37, 38, 39, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59, + 59, 60, 60, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 60, 60, + 59, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, + 45, 44, 43, 42, 41, 39, 38, 37, 36, 34, 33, 32, 30, 29, 27, 26, + 24, 23, 22, 20, 19, 17, 16, 14, 12, 11, 9, 8, 6, 5, 3, 2, + 0, -2, -3, -5, -6, -8, -9,-11,-12,-14,-16,-17,-19,-20,-22,-23, + -24,-26,-27,-29,-30,-32,-33,-34,-36,-37,-38,-39,-41,-42,-43,-44, + -45,-46,-47,-48,-49,-50,-51,-52,-53,-54,-55,-56,-56,-57,-58,-59, + -59,-60,-60,-61,-61,-62,-62,-62,-63,-63,-63,-64,-64,-64,-64,-64, + -64,-64,-64,-64,-64,-64,-63,-63,-63,-62,-62,-62,-61,-61,-60,-60, + -59,-59,-58,-57,-56,-56,-55,-54,-53,-52,-51,-50,-49,-48,-47,-46, + -45,-44,-43,-42,-41,-39,-38,-37,-36,-34,-33,-32,-30,-29,-27,-26, + -24,-23,-22,-20,-19,-17,-16,-14,-12,-11, -9, -8, -6, -5, -3, -2 +}; + + + +#if 0 +/** WARNING: use these! */ +/** JULIEN: Plus for XM compatibility it could be interesting to rename + * it_sawtooth[] to it_rampdown[], and add an it_rampup[]. + * Also, still for XM compat', twood be good if it was possible to tell the + * the player not to retrig' the waveform on a new instrument. + * Both of these are only for completness though, as I don't think it would + * be very noticeable ;) + */ +/** ENTHEH: IT also has the 'don't retrig' thingy :) */ + +static signed char it_sawtooth[256] = { + 64, 63, 63, 62, 62, 61, 61, 60, 60, 59, 59, 58, 58, 57, 57, 56, + 56, 55, 55, 54, 54, 53, 53, 52, 52, 51, 51, 50, 50, 49, 49, 48, + 48, 47, 47, 46, 46, 45, 45, 44, 44, 43, 43, 42, 42, 41, 41, 40, + 40, 39, 39, 38, 38, 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 32, + 32, 31, 31, 30, 30, 29, 29, 28, 28, 27, 27, 26, 26, 25, 25, 24, + 24, 23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 18, 18, 17, 17, 16, + 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 11, 10, 10, 9, 9, 8, + 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, + 0, -1, -1, -2, -2, -3, -3, -4, -4, -5, -5, -6, -6, -7, -7, -8, + -8, -9, -9,-10,-10,-11,-11,-12,-12,-13,-13,-14,-14,-15,-15,-16, + -16,-17,-17,-18,-18,-19,-19,-20,-20,-21,-21,-22,-22,-23,-23,-24, + -24,-25,-25,-26,-26,-27,-27,-28,-28,-29,-29,-30,-30,-31,-31,-32, + -32,-33,-33,-34,-34,-35,-35,-36,-36,-37,-37,-38,-38,-39,-39,-40, + -40,-41,-41,-42,-42,-43,-43,-44,-44,-45,-45,-46,-46,-47,-47,-48, + -48,-49,-49,-50,-50,-51,-51,-52,-52,-53,-53,-54,-54,-55,-55,-56, + -56,-57,-57,-58,-58,-59,-59,-60,-60,-61,-61,-62,-62,-63,-63,-64 +}; + + + +static signed char it_squarewave[256] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +#endif + + + +static void reset_tick_counts(DUMB_IT_SIGRENDERER *sigrenderer) +{ + int i; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; + channel->note_cut_count = 0; + channel->note_delay_count = 0; + } +} + + + +static void reset_effects(DUMB_IT_SIGRENDERER *sigrenderer) +{ + int i; + + sigrenderer->globalvolslide = 0; + sigrenderer->temposlide = 0; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; + channel->volslide = 0; + channel->xm_volslide = 0; + channel->channelvolslide = 0; + channel->arpeggio = 0; + channel->retrig = 0; + if (channel->xm_retrig) { + channel->xm_retrig = 0; + channel->retrig_tick = 0; + } + channel->tremor_time &= 127; + channel->portamento = 0; + channel->toneporta = 0; + if (channel->playing) { + channel->playing->vibrato_n = 0; + channel->playing->tremolo_speed = 0; + channel->playing->tremolo_depth = 0; + } + } +} + + + +static void update_tremor(IT_CHANNEL *channel) +{ + if ((channel->tremor_time & 128) && channel->playing) { + if (channel->tremor_time == 128) + channel->tremor_time = (channel->lastI >> 4) | 192; + else if (channel->tremor_time == 192) + channel->tremor_time = (channel->lastI & 15) | 128; + else + channel->tremor_time--; + } +} + + + +static void it_pickup_loop(DUMB_RESAMPLER *resampler, void *data) +{ + resampler->pos -= resampler->end - resampler->start; + ((IT_PLAYING *)data)->time_lost += resampler->end - resampler->start; +} + + + +static void it_pickup_pingpong_loop(DUMB_RESAMPLER *resampler, void *data) +{ + if (resampler->dir < 0) { + resampler->pos = (resampler->start << 1) - 1 - resampler->pos; + resampler->subpos ^= 65535; + resampler->dir = 1; + ((IT_PLAYING *)data)->time_lost += (resampler->end - resampler->start) << 1; + } else { + resampler->pos = (resampler->end << 1) - 1 - resampler->pos; + resampler->subpos ^= 65535; + resampler->dir = -1; + } +} + + + +static void it_pickup_stop_at_end(DUMB_RESAMPLER *resampler, void *data) +{ + (void)data; + + if (resampler->dir < 0) { + resampler->pos = (resampler->start << 1) - 1 - resampler->pos; + resampler->subpos ^= 65535; + /* By rights, time_lost would be updated here. However, there is no + * need at this point; it will not be used. + * + * ((IT_PLAYING *)data)->time_lost += (resampler->src_end - resampler->src_start) << 1; + */ + resampler->dir = 1; + } else + resampler->dir = 0; +} + + + +static void it_playing_update_resamplers(IT_PLAYING *playing) +{ + if ((playing->sample->flags & IT_SAMPLE_SUS_LOOP) && !(playing->flags & IT_PLAYING_SUSTAINOFF)) { + playing->resampler[0].start = playing->sample->sus_loop_start; + playing->resampler[0].end = playing->sample->sus_loop_end; + if (playing->sample->flags & IT_SAMPLE_PINGPONG_SUS_LOOP) + playing->resampler[0].pickup = &it_pickup_pingpong_loop; + else + playing->resampler[0].pickup = &it_pickup_loop; + } else if (playing->sample->flags & IT_SAMPLE_LOOP) { + playing->resampler[0].start = playing->sample->loop_start; + playing->resampler[0].end = playing->sample->loop_end; + if (playing->sample->flags & IT_SAMPLE_PINGPONG_LOOP) + playing->resampler[0].pickup = &it_pickup_pingpong_loop; + else + playing->resampler[0].pickup = &it_pickup_loop; + } else { + if (playing->sample->flags & IT_SAMPLE_SUS_LOOP) + playing->resampler[0].start = playing->sample->sus_loop_start; + else + playing->resampler[0].start = 0; + playing->resampler[0].end = playing->sample->length; + playing->resampler[0].pickup = &it_pickup_stop_at_end; + } + playing->resampler[1].start = playing->resampler[0].start; + playing->resampler[1].end = playing->resampler[0].end; + playing->resampler[1].pickup = playing->resampler[0].pickup; + ASSERT(playing->resampler[0].pickup_data == playing); + ASSERT(playing->resampler[1].pickup_data == playing); +} + + + +/* This should be called whenever the sample or sample position changes. */ +static void it_playing_reset_resamplers(IT_PLAYING *playing, long pos) +{ + dumb_reset_resampler(&playing->resampler[0], playing->sample->left, pos, 0, 0); + dumb_reset_resampler(&playing->resampler[1], playing->sample->right, pos, 0, 0); + playing->resampler[1].pickup_data = playing->resampler[0].pickup_data = playing; + playing->time_lost = 0; + playing->flags &= ~IT_PLAYING_DEAD; + it_playing_update_resamplers(playing); +} + + + +static void update_retrig(IT_CHANNEL *channel) +{ + if (channel->xm_retrig) { + channel->retrig_tick--; + if (channel->retrig_tick <= 0) { + if (channel->playing) it_playing_reset_resamplers(channel->playing, 0); + channel->retrig_tick = channel->xm_retrig; + } + } else if (channel->retrig & 0x0F) { + channel->retrig_tick--; + if (channel->retrig_tick <= 0) { + if (channel->retrig < 0x10) { + } else if (channel->retrig < 0x20) { + channel->volume--; + if (channel->volume > 64) channel->volume = 0; + } else if (channel->retrig < 0x30) { + channel->volume -= 2; + if (channel->volume > 64) channel->volume = 0; + } else if (channel->retrig < 0x40) { + channel->volume -= 4; + if (channel->volume > 64) channel->volume = 0; + } else if (channel->retrig < 0x50) { + channel->volume -= 8; + if (channel->volume > 64) channel->volume = 0; + } else if (channel->retrig < 0x60) { + channel->volume -= 16; + if (channel->volume > 64) channel->volume = 0; + } else if (channel->retrig < 0x70) { + channel->volume <<= 1; + channel->volume /= 3; + } else if (channel->retrig < 0x80) { + channel->volume >>= 1; + } else if (channel->retrig < 0x90) { + } else if (channel->retrig < 0xA0) { + channel->volume++; + if (channel->volume > 64) channel->volume = 64; + } else if (channel->retrig < 0xB0) { + channel->volume += 2; + if (channel->volume > 64) channel->volume = 64; + } else if (channel->retrig < 0xC0) { + channel->volume += 4; + if (channel->volume > 64) channel->volume = 64; + } else if (channel->retrig < 0xD0) { + channel->volume += 8; + if (channel->volume > 64) channel->volume = 64; + } else if (channel->retrig < 0xE0) { + channel->volume += 16; + if (channel->volume > 64) channel->volume = 64; + } else if (channel->retrig < 0xF0) { + channel->volume *= 3; + channel->volume >>= 1; + if (channel->volume > 64) channel->volume = 64; + } else { + channel->volume <<= 1; + if (channel->volume > 64) channel->volume = 64; + } + if (channel->playing) it_playing_reset_resamplers(channel->playing, 0); + channel->retrig_tick = channel->retrig & 0x0F; + } + } +} + + + +static void update_smooth_effects(DUMB_IT_SIGRENDERER *sigrenderer) +{ + int i; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; + IT_PLAYING *playing = channel->playing; + + if (playing) { + playing->vibrato_time += playing->vibrato_n * + (playing->vibrato_speed << 2); + playing->tremolo_time += playing->tremolo_speed << 2; + } + } +} + + + +static void update_effects(DUMB_IT_SIGRENDERER *sigrenderer) +{ + int i; + + if (sigrenderer->globalvolslide) { + sigrenderer->globalvolume += sigrenderer->globalvolslide; + if (sigrenderer->globalvolume > 128) { + if (sigrenderer->globalvolslide >= 0) + sigrenderer->globalvolume = 128; + else + sigrenderer->globalvolume = 0; + } + } + + if (sigrenderer->temposlide) { + sigrenderer->tempo += sigrenderer->temposlide; + if (sigrenderer->tempo < 32) { + if (sigrenderer->temposlide >= 0) + sigrenderer->tempo = 255; + else + sigrenderer->tempo = 32; + } + } + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; + IT_PLAYING *playing = channel->playing; + + if (channel->xm_volslide) { + channel->volume += channel->xm_volslide; + if (channel->volume > 64) { + if (channel->xm_volslide >= 0) + channel->volume = 64; + else + channel->volume = 0; + } + } + + if (channel->volslide) { + channel->volume += channel->volslide; + if (channel->volume > 64) { + if (channel->volslide >= 0) + channel->volume = 64; + else + channel->volume = 0; + } + } + + if (channel->channelvolslide) { + channel->channelvolume += channel->channelvolslide; + if (channel->channelvolume > 64) { + if (channel->channelvolslide >= 0) + channel->channelvolume = 64; + else + channel->channelvolume = 0; + } + if (channel->playing) + channel->playing->channel_volume = channel->channelvolume; + } + + update_tremor(channel); + + channel->arpeggio = (channel->arpeggio << 4) | (channel->arpeggio >> 8); + channel->arpeggio &= 0xFFF; + + update_retrig(channel); + + if (playing) { + playing->slide += channel->portamento; + + if (sigrenderer->sigdata->flags & IT_LINEAR_SLIDES) { + if (channel->toneporta && channel->destnote < 120) { + int currpitch = ((playing->note - 60) << 8) + playing->slide; + int destpitch = (channel->destnote - 60) << 8; + if (currpitch > destpitch) { + currpitch -= channel->toneporta; + if (currpitch < destpitch) { + currpitch = destpitch; + channel->destnote = IT_NOTE_OFF; + } + } else if (currpitch < destpitch) { + currpitch += channel->toneporta; + if (currpitch > destpitch) { + currpitch = destpitch; + channel->destnote = IT_NOTE_OFF; + } + } + playing->slide = currpitch - ((playing->note - 60) << 8); + } + } else { + if (channel->toneporta && channel->destnote < 120) { + float amiga_multiplier = playing->sample->C5_speed * (1.0f / AMIGA_DIVISOR); + + float deltanote = (float)pow(DUMB_SEMITONE_BASE, 60 - playing->note); + /* deltanote is 1.0 for C-5, 0.5 for C-6, etc. */ + + float deltaslid = deltanote - playing->slide * amiga_multiplier; + + float destdelta = (float)pow(DUMB_SEMITONE_BASE, 60 - channel->destnote); + if (deltaslid < destdelta) { + playing->slide -= channel->toneporta; + deltaslid = deltanote - playing->slide * amiga_multiplier; + if (deltaslid > destdelta) { + playing->note = channel->destnote; + playing->slide = 0; + channel->destnote = IT_NOTE_OFF; + } + } else { + playing->slide += channel->toneporta; + deltaslid = deltanote - playing->slide * amiga_multiplier; + if (deltaslid < destdelta) { + playing->note = channel->destnote; + playing->slide = 0; + channel->destnote = IT_NOTE_OFF; + } + } + } + } + } + } + + update_smooth_effects(sigrenderer); +} + + + +static void update_pattern_variables(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; + + if (entry->mask & IT_ENTRY_EFFECT) { + if (entry->effect == IT_S) { + unsigned char effectvalue = entry->effectvalue; + if (effectvalue == 0) + effectvalue = channel->lastS; + channel->lastS = effectvalue; + switch (effectvalue >> 4) { + //case IT_S7: + case IT_S_PATTERN_LOOP: + { + unsigned char v = effectvalue & 15; + if (v == 0) + channel->pat_loop_row = sigrenderer->processrow; + else { + if (channel->pat_loop_count == 0) { + channel->pat_loop_count = v; + sigrenderer->pat_loop_row = channel->pat_loop_row; + } else { + if (--channel->pat_loop_count) + sigrenderer->pat_loop_row = channel->pat_loop_row; + else if (!(sigrenderer->sigdata->flags & IT_WAS_AN_XM)) + channel->pat_loop_row = sigrenderer->processrow + 1; + } + } + } + break; + case IT_S_PATTERN_DELAY: + sigrenderer->rowcount = 1 + (effectvalue & 15); + break; + } + } + } +} + + + +/* This function guarantees that channel->sample will always be valid if it + * is nonzero. In other words, to check if it is valid, simply check if it is + * nonzero. + */ +static void instrument_to_sample(DUMB_IT_SIGDATA *sigdata, IT_CHANNEL *channel) +{ + if (sigdata->flags & IT_USE_INSTRUMENTS) { + if (channel->instrument >= 1 && channel->instrument <= sigdata->n_instruments) { + if (channel->note < 120) { + channel->sample = sigdata->instrument[channel->instrument-1].map_sample[channel->note]; + channel->truenote = sigdata->instrument[channel->instrument-1].map_note[channel->note]; + } else + channel->sample = 0; + } else + channel->sample = 0; + } else { + channel->sample = channel->instrument; + channel->truenote = channel->note; + } + if (!(channel->sample >= 1 && channel->sample <= sigdata->n_samples && (sigdata->sample[channel->sample-1].flags & IT_SAMPLE_EXISTS))) + channel->sample = 0; +} + + + +static void fix_sample_looping(IT_PLAYING *playing) +{ + if ((playing->sample->flags & (IT_SAMPLE_LOOP | IT_SAMPLE_SUS_LOOP)) == + (IT_SAMPLE_LOOP | IT_SAMPLE_SUS_LOOP)) { + if (playing->resampler[0].dir < 0) { + playing->resampler[1].pos = playing->resampler[0].pos = (playing->sample->sus_loop_end << 1) - 1 - playing->resampler[0].pos; + playing->resampler[1].subpos = playing->resampler[0].subpos ^= 65535; + playing->resampler[1].dir = playing->resampler[0].dir = 1; + } + + playing->resampler[1].pos = playing->resampler[0].pos += playing->time_lost; + } +} + + + +static void retrigger_it_envelopes(DUMB_IT_SIGDATA *sigdata, IT_CHANNEL *channel) +{ + channel->playing->volume_envelope.next_node = 0; + channel->playing->volume_envelope.tick = -1; + channel->playing->pan_envelope.next_node = 0; + channel->playing->pan_envelope.tick = -1; + channel->playing->pitch_envelope.next_node = 0; + channel->playing->pitch_envelope.tick = -1; + channel->playing->fadeoutcount = 1024; + // Should we remove IT_PLAYING_BACKGROUND? Test with sample with sustain loop... + channel->playing->flags &= ~(IT_PLAYING_BACKGROUND | IT_PLAYING_SUSTAINOFF | IT_PLAYING_FADING | IT_PLAYING_DEAD); + it_playing_update_resamplers(channel->playing); + + if (channel->sample) + if (sigdata->flags & IT_USE_INSTRUMENTS) + channel->playing->env_instrument = &sigdata->instrument[channel->instrument-1]; +} + + + +static void it_retrigger_note(DUMB_IT_SIGRENDERER *sigrenderer, IT_CHANNEL *channel) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + unsigned char nna; + int i; + + if (channel->playing) { +#ifdef INVALID_NOTES_CAUSE_NOTE_CUT + if (channel->note == IT_NOTE_OFF) + nna = NNA_NOTE_OFF; + else if (channel->note >= 120 || !channel->playing->instrument || (channel->playing->flags & IT_PLAYING_DEAD)) + nna = NNA_NOTE_CUT; + else + nna = channel->playing->instrument->new_note_action; +#else + if (channel->note == IT_NOTE_CUT) + nna = NNA_NOTE_CUT; + if (channel->note >= 120) + nna = NNA_NOTE_OFF; + else if (!channel->playing->instrument || (channel->playing->flags & IT_PLAYING_DEAD)) + nna = NNA_NOTE_CUT; + else + nna = channel->playing->instrument->new_note_action; +#endif + + switch (nna) { + case NNA_NOTE_CUT: + free(channel->playing); + channel->playing = NULL; + break; + case NNA_NOTE_OFF: + channel->playing->flags |= IT_PLAYING_BACKGROUND | IT_PLAYING_SUSTAINOFF; + fix_sample_looping(channel->playing); + it_playing_update_resamplers(channel->playing); + if (channel->playing->instrument) + if ((channel->playing->instrument->volume_envelope.flags & (IT_ENVELOPE_ON | IT_ENVELOPE_LOOP_ON)) != IT_ENVELOPE_ON) + channel->playing->flags |= IT_PLAYING_FADING; + break; + case NNA_NOTE_FADE: + channel->playing->flags |= IT_PLAYING_BACKGROUND | IT_PLAYING_FADING; + break; + } + } + + if (channel->sample == 0 || channel->note >= 120) + return; + + channel->destnote = IT_NOTE_OFF; + + if (channel->playing) { + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (!sigrenderer->playing[i]) { + sigrenderer->playing[i] = channel->playing; + channel->playing = NULL; + break; + } + } +/** WARNING - come up with some more heuristics for replacing old notes */ +#if 0 + if (channel->playing) { + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (sigrenderer->playing[i]->flags & IT_PLAYING_BACKGROUND) { + write_seqtime(); + sequence_c(SEQUENCE_STOP_SIGNAL); + sequence_c(i); + channel->VChannel = &module->VChannel[i]; + break; + } + } + } +#endif + } + + if (channel->playing) + free(channel->playing); + + channel->playing = malloc(sizeof(*channel->playing)); + + if (!channel->playing) + return; + + channel->playing->flags = 0; + channel->playing->channel = channel; + channel->playing->sample = &sigdata->sample[channel->sample-1]; + if (sigdata->flags & IT_USE_INSTRUMENTS) + channel->playing->instrument = &sigdata->instrument[channel->instrument-1]; + else + channel->playing->instrument = NULL; + channel->playing->env_instrument = channel->playing->instrument; + channel->playing->sampnum = channel->sample; + channel->playing->instnum = channel->instrument; + channel->playing->channel_volume = channel->channelvolume; + channel->playing->note = channel->truenote; + channel->playing->filter_cutoff = 127; + channel->playing->filter_resonance = 0; + channel->playing->true_filter_cutoff = 127 << 8; + channel->playing->true_filter_resonance = 0; + channel->playing->vibrato_speed = 0; + channel->playing->vibrato_depth = 0; + channel->playing->vibrato_n = 0; + channel->playing->vibrato_time = 0; + channel->playing->tremolo_speed = 0; + channel->playing->tremolo_depth = 0; + channel->playing->tremolo_time = 0; + channel->playing->sample_vibrato_time = 0; + channel->playing->sample_vibrato_depth = 0; + channel->playing->slide = 0; + channel->playing->volume_envelope.next_node = 0; + channel->playing->volume_envelope.tick = -1; + channel->playing->pan_envelope.next_node = 0; + channel->playing->pan_envelope.tick = -1; + channel->playing->pitch_envelope.next_node = 0; + channel->playing->pitch_envelope.tick = -1; + channel->playing->fadeoutcount = 1024; + it_reset_filter_state(&channel->playing->filter_state[0]); + it_reset_filter_state(&channel->playing->filter_state[1]); + it_playing_reset_resamplers(channel->playing, 0); + + /** WARNING - is everything initialised? */ +} + + + +static void get_default_volpan(DUMB_IT_SIGDATA *sigdata, IT_CHANNEL *channel) +{ + if (channel->sample == 0) + return; + + channel->volume = sigdata->sample[channel->sample-1].default_volume; + + { + int pan = sigdata->sample[channel->sample-1].default_pan; + if (pan >= 128 && pan <= 192) { + channel->pan = pan - 128; + return; + } + } + + if (sigdata->flags & IT_USE_INSTRUMENTS) { + IT_INSTRUMENT *instrument = &sigdata->instrument[channel->instrument-1]; + if (instrument->default_pan <= 64) + channel->pan = instrument->default_pan; + if (instrument->filter_cutoff >= 128) + channel->filter_cutoff = instrument->filter_cutoff - 128; + if (instrument->filter_resonance >= 128) + channel->filter_resonance = instrument->filter_resonance - 128; + } +} + + + +static void get_true_pan(DUMB_IT_SIGDATA *sigdata, IT_CHANNEL *channel) +{ + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + + if (!IT_IS_SURROUND_SHIFTED(channel->truepan) && (sigdata->flags & IT_USE_INSTRUMENTS)) { + IT_INSTRUMENT *instrument = &sigdata->instrument[channel->instrument-1]; + int truepan = channel->truepan; + truepan += (channel->note - instrument->pp_centre) * instrument->pp_separation << (IT_ENVELOPE_SHIFT - 3); + channel->truepan = MID(0, truepan, 64 << IT_ENVELOPE_SHIFT); + } +} + + + +static void post_process_it_volpan(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; + + if (entry->mask & IT_ENTRY_VOLPAN) { + if (entry->volpan <= 84) { + /* Volume */ + /* Fine volume slide up */ + /* Fine volume slide down */ + } else if (entry->volpan <= 94) { + /* Volume slide up */ + unsigned char v = entry->volpan - 85; + if (v == 0) + v = channel->lastvolslide; + channel->lastvolslide = v; + /* = effect Dx0 where x == entry->volpan - 85 */ + channel->volslide = v; + } else if (entry->volpan <= 104) { + /* Volume slide down */ + unsigned char v = entry->volpan - 95; + if (v == 0) + v = channel->lastvolslide; + channel->lastvolslide = v; + /* = effect D0x where x == entry->volpan - 95 */ + channel->volslide = -v; + } else if (entry->volpan <= 114) { + /* Portamento down */ + unsigned char v = (entry->volpan - 105) << 2; + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + channel->portamento -= v << 4; + } else if (entry->volpan <= 124) { + /* Portamento up */ + unsigned char v = (entry->volpan - 115) << 2; + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + channel->portamento += v << 4; + } else if (entry->volpan <= 202) { + /* Pan */ + /* Tone Portamento */ + } else if (entry->volpan <= 212) { + /* Vibrato */ + unsigned char v = entry->volpan - 203; + if (v == 0) + v = channel->lastHdepth; + else { + v <<= 2; + channel->lastHdepth = v; + } + if (channel->playing) { + channel->playing->vibrato_speed = channel->lastHspeed; + channel->playing->vibrato_depth = v; + channel->playing->vibrato_n++; + } + } + } +} + + + +static void it_send_midi(DUMB_IT_SIGRENDERER *sigrenderer, IT_CHANNEL *channel, unsigned char byte) +{ + if (sigrenderer->callbacks->midi) + if ((*sigrenderer->callbacks->midi)(sigrenderer->callbacks->midi_data, channel - sigrenderer->channel, byte)) + return; + + switch (channel->midi_state) { + case 4: /* Ready to receive resonance parameter */ + if (byte < 0x80) channel->filter_resonance = byte; + channel->midi_state = 0; + break; + case 3: /* Ready to receive cutoff parameter */ + if (byte < 0x80) channel->filter_cutoff = byte; + channel->midi_state = 0; + break; + case 2: /* Ready for byte specifying which parameter will follow */ + if (byte == 0) /* Cutoff */ + channel->midi_state = 3; + else if (byte == 1) /* Resonance */ + channel->midi_state = 4; + else + channel->midi_state = 0; + break; + default: /* Counting initial F0 bytes */ + switch (byte) { + case 0xF0: + channel->midi_state++; + break; + case 0xFA: + case 0xFC: + case 0xFF: + /* Reset filter parameters for all channels */ + { + int i; + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + sigrenderer->channel[i].filter_cutoff = 127; + sigrenderer->channel[i].filter_resonance = 0; + //// should we be resetting channel[i].playing->filter_* here? + } + } + /* Fall through */ + default: + channel->midi_state = 0; + break; + } + } +} + + + +/* Returns 1 if a callback caused termination of playback. */ +static int process_effects(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + + IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; + + if (entry->mask & IT_ENTRY_EFFECT) { + switch (entry->effect) { +/* +Notes about effects (as compared to other module formats) + +C This is now in *HEX*. (Used to be in decimal in ST3) +E/F/G/H/U You need to check whether the song uses Amiga/Linear slides. +H/U Vibrato in Impulse Tracker is two times finer than in + any other tracker and is updated EVERY tick. + If "Old Effects" is *ON*, then the vibrato is played in the + normal manner (every non-row tick and normal depth) +E/F/G These commands ALL share the same memory. +Oxx Offsets to samples are to the 'xx00th' SAMPLE. (ie. for + 16 bit samples, the offset is xx00h*2) + Oxx past the sample end will be ignored, unless "Old Effects" + is ON, in which case the Oxx will play from the end of the + sample. +Yxy This uses a table 4 times larger (hence 4 times slower) than + vibrato or tremelo. If the waveform is set to random, then + the 'speed' part of the command is interpreted as a delay. +*/ + case IT_SET_SPEED: + if (entry->effectvalue) + sigrenderer->tick = sigrenderer->speed = entry->effectvalue; + else if (sigdata->flags & IT_WAS_AN_XM) { + sigrenderer->speed = 0; + if (sigrenderer->callbacks->xm_speed_zero && (*sigrenderer->callbacks->xm_speed_zero)(sigrenderer->callbacks->xm_speed_zero_data)) + return 1; + } + break; + + case IT_JUMP_TO_ORDER: sigrenderer->processorder = entry->effectvalue - 1; sigrenderer->processrow = 0xFFFE; break; + case IT_BREAK_TO_ROW: sigrenderer->breakrow = entry->effectvalue; sigrenderer->processrow = 0xFFFE; break; + + case IT_VOLSLIDE_VIBRATO: + if (channel->playing) { + channel->playing->vibrato_speed = channel->lastHspeed; + channel->playing->vibrato_depth = channel->lastHdepth; + channel->playing->vibrato_n++; + } + /* Fall through and process volume slide. */ + case IT_VOLUME_SLIDE: + case IT_VOLSLIDE_TONEPORTA: + /* The tone portamento component is handled elsewhere. */ + { + unsigned char v = entry->effectvalue; + if (!(sigdata->flags & IT_WAS_A_MOD)) { + if (v == 0) + v = channel->lastDKL; + channel->lastDKL = v; + } + if ((v & 0x0F) == 0) { /* Dx0 */ + channel->volslide = v >> 4; + if (channel->volslide == 15 && !(sigdata->flags & IT_WAS_AN_XM)) { + channel->volume += 15; + if (channel->volume > 64) channel->volume = 64; + } + } else if ((v & 0xF0) == 0) { /* D0x */ + channel->volslide = -v; + if (channel->volslide == -15 && !(sigdata->flags & IT_WAS_AN_XM)) { + channel->volume -= 15; + if (channel->volume > 64) channel->volume = 0; + } + } else if ((v & 0x0F) == 0x0F) { /* DxF */ + channel->volume += v >> 4; + if (channel->volume > 64) channel->volume = 64; + } else if ((v & 0xF0) == 0xF0) { /* DFx */ + channel->volume -= v & 15; + if (channel->volume > 64) channel->volume = 0; + } + } + break; + case IT_XM_FINE_VOLSLIDE_DOWN: + { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->xm_lastEB; + channel->xm_lastEB = v; + channel->volume -= v; + if (channel->volume > 64) channel->volume = 0; + } + break; + case IT_XM_FINE_VOLSLIDE_UP: + { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->xm_lastEA; + channel->xm_lastEA = v; + channel->volume += v; + if (channel->volume > 64) channel->volume = 64; + } + break; + case IT_PORTAMENTO_DOWN: + { + unsigned char v = entry->effectvalue; + if (sigdata->flags & IT_WAS_AN_XM) { + if (!(sigdata->flags & IT_WAS_A_MOD)) { + if (v == 0xF0) + v |= channel->xm_lastE2; + else if (v >= 0xF0) + channel->xm_lastE2 = v & 15; + else if (v == 0xE0) + v |= channel->xm_lastX2; + else + channel->xm_lastX2 = v & 15; + } + } else { + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + } + if (channel->playing) { + if ((v & 0xF0) == 0xF0) + channel->playing->slide -= (v & 15) << 4; + else if ((v & 0xF0) == 0xE0) + channel->playing->slide -= (v & 15) << 2; + else + channel->portamento -= v << 4; + } + } + break; + case IT_PORTAMENTO_UP: + { + unsigned char v = entry->effectvalue; + if (sigdata->flags & IT_WAS_AN_XM) { + if (!(sigdata->flags & IT_WAS_A_MOD)) { + if (v == 0xF0) + v |= channel->xm_lastE1; + else if (v >= 0xF0) + channel->xm_lastE1 = v & 15; + else if (v == 0xE0) + v |= channel->xm_lastX1; + else + channel->xm_lastX1 = v & 15; + } + } else { + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + } + if (channel->playing) { + if ((v & 0xF0) == 0xF0) + channel->playing->slide += (v & 15) << 4; + else if ((v & 0xF0) == 0xE0) + channel->playing->slide += (v & 15) << 2; + else + channel->portamento += v << 4; + } + } + break; + case IT_XM_PORTAMENTO_DOWN: + { + unsigned char v = entry->effectvalue; + if (!(sigdata->flags & IT_WAS_A_MOD)) { + if (v == 0) + v = channel->lastJ; + channel->lastJ = v; + } + if (channel->playing) + channel->portamento -= v << 4; + } + break; + case IT_XM_PORTAMENTO_UP: + { + unsigned char v = entry->effectvalue; + if (!(sigdata->flags & IT_WAS_A_MOD)) { + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + } + if (channel->playing) + channel->portamento += v << 4; + } + break; + case IT_VIBRATO: + { + unsigned char speed = entry->effectvalue >> 4; + unsigned char depth = entry->effectvalue & 15; + if (speed == 0) + speed = channel->lastHspeed; + channel->lastHspeed = speed; + if (depth == 0) + depth = channel->lastHdepth; + else { + if (sigdata->flags & IT_OLD_EFFECTS) + depth <<= 3; + else + depth <<= 2; + channel->lastHdepth = depth; + } + if (channel->playing) { + channel->playing->vibrato_speed = speed; + channel->playing->vibrato_depth = depth; + channel->playing->vibrato_n++; + } + } + break; + case IT_TREMOR: + { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->lastI; + else if (!(sigdata->flags & IT_OLD_EFFECTS)) { + if (v & 0xF0) v -= 0x10; + if (v & 0x0F) v -= 0x01; + } + channel->lastI = v; + channel->tremor_time |= 128; + } + update_tremor(channel); + break; + case IT_ARPEGGIO: + { + unsigned char v = entry->effectvalue; + /* XM files have no memory for arpeggio (000 = no effect) + * and we use lastJ for portamento down instead. + */ + if (!(sigdata->flags & IT_WAS_AN_XM)) { + if (v == 0) + v = channel->lastJ; + channel->lastJ = v; + } + channel->arpeggio = v; + } + break; + case IT_SET_CHANNEL_VOLUME: + if (sigdata->flags & IT_WAS_AN_XM) + channel->volume = MIN(entry->effectvalue, 64); + else if (entry->effectvalue <= 64) + channel->channelvolume = entry->effectvalue; +#ifdef VOLUME_OUT_OF_RANGE_SETS_MAXIMUM + else + channel->channelvolume = 64; +#endif + if (channel->playing) + channel->playing->channel_volume = channel->channelvolume; + break; + case IT_CHANNEL_VOLUME_SLIDE: + { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->lastN; + channel->lastN = v; + if ((v & 0x0F) == 0) { /* Nx0 */ + channel->channelvolslide = v >> 4; + } else if ((v & 0xF0) == 0) { /* N0x */ + channel->channelvolslide = -v; + } else { + if ((v & 0x0F) == 0x0F) { /* NxF */ + channel->channelvolume += v >> 4; + if (channel->channelvolume > 64) channel->channelvolume = 64; + } else if ((v & 0xF0) == 0xF0) { /* NFx */ + channel->channelvolume -= v & 15; + if (channel->channelvolume > 64) channel->channelvolume = 0; + } else + break; + if (channel->playing) + channel->playing->channel_volume = channel->channelvolume; + } + } + break; + case IT_SET_SAMPLE_OFFSET: + { + unsigned char v = entry->effectvalue; + if (sigdata->flags & IT_WAS_A_MOD) { + if (v == 0) break; + } else { + if (v == 0) + v = channel->lastO; + channel->lastO = v; + } + /* Note: we set the offset even if tone portamento is + * specified. Impulse Tracker does the same. + */ + if (entry->mask & IT_ENTRY_NOTE) { + if (channel->playing) { + int offset = ((int)channel->high_offset << 16) | ((int)v << 8); + IT_PLAYING *playing = channel->playing; + IT_SAMPLE *sample = playing->sample; + int end; + if ((sample->flags & IT_SAMPLE_SUS_LOOP) && !(playing->flags & IT_PLAYING_SUSTAINOFF)) + end = sample->sus_loop_end; + else if (sample->flags & IT_SAMPLE_LOOP) + end = sample->loop_end; + else + end = sample->length; + if (offset < end) + it_playing_reset_resamplers(playing, offset); + else if (sigdata->flags & IT_OLD_EFFECTS) + it_playing_reset_resamplers(playing, end); + } + } + } + break; + //case IT_PANNING_SLIDE: + /** JULIEN: guess what? the docs are wrong! (how unusual ;) + * Pxy seems to memorize its previous value... and there + * might be other mistakes like that... (sigh!) + */ + /** ENTHEH: umm... but... the docs say that Pxy memorises its + * value... don't they? :o + */ + case IT_RETRIGGER_NOTE: + { + unsigned char v = entry->effectvalue; + if (sigdata->flags & IT_WAS_AN_XM) { + if ((v & 0x0F) == 0) v |= channel->lastQ & 0x0F; + if ((v & 0xF0) == 0) v |= channel->lastQ & 0xF0; + } else { + if (v == 0) + v = channel->lastQ; + } + channel->lastQ = v; + if ((v & 0x0F) == 0) v |= 0x01; + channel->retrig = v; + if (entry->mask & IT_ENTRY_NOTE) { + channel->retrig_tick = v & 0x0F; + /* Emulate a bug */ + if (sigdata->flags & IT_WAS_AN_XM) + update_retrig(channel); + } else + update_retrig(channel); + } + break; + case IT_XM_RETRIGGER_NOTE: + channel->retrig_tick = channel->xm_retrig = entry->effectvalue; + if (entry->effectvalue == 0) + if (channel->playing) it_playing_reset_resamplers(channel->playing, 0); + break; + case IT_TREMOLO: + { + unsigned char speed = entry->effectvalue >> 4; + unsigned char depth = entry->effectvalue & 15; + if (speed == 0) + speed = channel->lastRspeed; + channel->lastRspeed = speed; + if (depth == 0) + depth = channel->lastRdepth; + channel->lastRdepth = depth; + if (channel->playing) { + channel->playing->tremolo_speed = speed; + channel->playing->tremolo_depth = depth; + } + } + break; + case IT_S: + { + /* channel->lastS was set in update_pattern_variables(). */ + unsigned char effectvalue = channel->lastS; + switch (effectvalue >> 4) { + //case IT_S_SET_FILTER: + //case IT_S_SET_GLISSANDO_CONTROL: + //case IT_S_FINETUNE: + //case IT_S_SET_VIBRATO_WAVEFORM: + //case IT_S_SET_TREMOLO_WAVEFORM: + //case IT_S_SET_PANBRELLO_WAVEFORM: + /* Waveforms for commands S3x, S4x and S5x: + * 0: Sine wave + * 1: Ramp down + * 2: Square wave + * 3: Random wave + */ + case IT_S_FINE_PATTERN_DELAY: + sigrenderer->tick += effectvalue & 15; + break; + //case IT_S7: + case IT_S_SET_PAN: + channel->pan = + ((effectvalue & 15) << 2) | + ((effectvalue & 15) >> 2); + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + break; + case IT_S_SET_SURROUND_SOUND: + if ((effectvalue & 15) == 1) + channel->pan = IT_SURROUND; + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + break; + case IT_S_SET_HIGH_OFFSET: + channel->high_offset = effectvalue & 15; + break; + //case IT_S_PATTERN_LOOP: + case IT_S_DELAYED_NOTE_CUT: + channel->note_cut_count = effectvalue & 15; + if (!channel->note_cut_count) { + if (sigdata->flags & IT_WAS_AN_XM) + channel->volume = 0; + else + channel->note_cut_count = 1; + } + break; + case IT_S_SET_MIDI_MACRO: + channel->SFmacro = effectvalue & 15; + break; + } + } + break; + case IT_SET_SONG_TEMPO: + { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->lastW; + channel->lastW = v; + if (v < 0x10) + sigrenderer->temposlide = -v; + else if (v < 0x20) + sigrenderer->temposlide = v & 15; + else + sigrenderer->tempo = v; + } + break; + case IT_FINE_VIBRATO: + { + unsigned char speed = entry->effectvalue >> 4; + unsigned char depth = entry->effectvalue & 15; + if (speed == 0) + speed = channel->lastHspeed; + channel->lastHspeed = speed; + if (depth == 0) + depth = channel->lastHdepth; + else { + if (sigdata->flags & IT_OLD_EFFECTS) + depth <<= 1; + channel->lastHdepth = depth; + } + if (channel->playing) { + channel->playing->vibrato_speed = speed; + channel->playing->vibrato_depth = depth; + channel->playing->vibrato_n++; + } + } + break; + case IT_SET_GLOBAL_VOLUME: + if (entry->effectvalue <= 128) + sigrenderer->globalvolume = entry->effectvalue; +#ifdef VOLUME_OUT_OF_RANGE_SETS_MAXIMUM + else + sigrenderer->globalvolume = 128; +#endif + break; + case IT_GLOBAL_VOLUME_SLIDE: + { + unsigned char v = entry->effectvalue; + if (v == 0) + v = channel->lastW; + channel->lastW = v; + if ((v & 0x0F) == 0) { /* Wx0 */ + sigrenderer->globalvolslide = + (sigdata->flags & IT_WAS_AN_XM) ? (v >> 4)*2 : (v >> 4); + } else if ((v & 0xF0) == 0) { /* W0x */ + sigrenderer->globalvolslide = + (sigdata->flags & IT_WAS_AN_XM) ? (-v)*2 : (-v); + } else if ((v & 0x0F) == 0x0F) { /* WxF */ + sigrenderer->globalvolume += v >> 4; + if (sigrenderer->globalvolume > 128) sigrenderer->globalvolume = 128; + } else if ((v & 0xF0) == 0xF0) { /* WFx */ + sigrenderer->globalvolume -= v & 15; + if (sigrenderer->globalvolume > 128) sigrenderer->globalvolume = 0; + } + } + break; + case IT_SET_PANNING: + channel->pan = (entry->effectvalue + 2) >> 2; + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + break; + //case IT_PANBRELLO: + case IT_MIDI_MACRO: + { + IT_MIDI *midi = sigdata->midi ? sigdata->midi : &default_midi; + if (entry->effectvalue >= 0x80) { + int n = midi->Zmacrolen[entry->effectvalue-0x80]; + int i; + for (i = 0; i < n; i++) + it_send_midi(sigrenderer, channel, midi->Zmacro[entry->effectvalue-0x80][i]); + } else { + int n = midi->SFmacrolen[channel->SFmacro]; + int i, j; + for (i = 0, j = 1; i < n; i++, j <<= 1) + it_send_midi(sigrenderer, channel, + midi->SFmacroz[channel->SFmacro] & j ? + entry->effectvalue : midi->SFmacro[channel->SFmacro][i]); + } + } + break; + } + } + + if (!(sigdata->flags & IT_WAS_AN_XM)) + post_process_it_volpan(sigrenderer, entry); + + return 0; +} + + + +static int process_it_note_data(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; + + // When tone portamento and instrument are specified: + // If Gxx is off: + // - same sample, do nothing but portamento + // - diff sample, retrigger all but keep current note+slide + do porta + // - if instrument is invalid, nothing; if sample is invalid, cut + // If Gxx is on: + // - same sample or new sample invalid, retrigger envelopes + // - diff sample/inst, start using new envelopes + // When tone portamento is specified alone, sample won't change. + // TODO: consider what happens with instrument alone after all this... + + if (entry->mask & (IT_ENTRY_NOTE | IT_ENTRY_INSTRUMENT)) { + if (entry->mask & IT_ENTRY_INSTRUMENT) + channel->instrument = entry->instrument; + instrument_to_sample(sigdata, channel); + if (channel->note < 120) { + if ((sigdata->flags & IT_USE_INSTRUMENTS) && channel->sample == 0) + return 1; + if (entry->mask & IT_ENTRY_INSTRUMENT) + get_default_volpan(sigdata, channel); + } else + it_retrigger_note(sigrenderer, channel); + } + + /** WARNING: This is not ideal, since channel->playing might not get allocated owing to lack of memory... */ + if (channel->playing && + (((entry->mask & IT_ENTRY_VOLPAN) && entry->volpan >= 193 && entry->volpan <= 202) || + ((entry->mask & IT_ENTRY_EFFECT) && (entry->effect == IT_TONE_PORTAMENTO || entry->effect == IT_VOLSLIDE_TONEPORTA)))) + { + if (entry->mask & IT_ENTRY_INSTRUMENT) { + if (sigdata->flags & IT_COMPATIBLE_GXX) + retrigger_it_envelopes(sigdata, channel); + else if ((!(sigdata->flags & IT_USE_INSTRUMENTS) || + (channel->instrument >= 1 && channel->instrument <= sigdata->n_instruments)) && + channel->sample != channel->playing->sampnum) + { + unsigned char note = channel->playing->note; + int slide = channel->playing->slide; + it_retrigger_note(sigrenderer, channel); + if (channel->playing) { + channel->playing->note = note; + channel->playing->slide = slide; + // Should we be preserving sample_vibrato_time? depth? + } + } + } + + if ((entry->mask & IT_ENTRY_VOLPAN) && entry->volpan >= 193 && entry->volpan <= 202) { + /* Tone Portamento in the volume column */ + static const unsigned char slidetable[] = {0, 1, 4, 8, 16, 32, 64, 96, 128, 255}; + unsigned char v = slidetable[entry->volpan - 193]; + if (sigdata->flags & IT_COMPATIBLE_GXX) { + if (v == 0) + v = channel->lastG; + channel->lastG = v; + } else { + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + } + if (entry->mask & IT_ENTRY_NOTE) + if (channel->sample) + channel->destnote = channel->truenote; + channel->toneporta = v << 4; + } else { + /* Tone Portamento in the effect column */ + unsigned char v; + if (entry->effect == IT_TONE_PORTAMENTO) + v = entry->effectvalue; + else + v = 0; + if (sigdata->flags & IT_COMPATIBLE_GXX) { + if (v == 0) + v = channel->lastG; + channel->lastG = v; + } else { + if (v == 0) + v = channel->lastEF; + channel->lastEF = v; + } + if (entry->mask & IT_ENTRY_NOTE) + if (channel->sample) + channel->destnote = channel->truenote; + channel->toneporta = v << 4; + } + } else if ((entry->mask & IT_ENTRY_NOTE) || + ((entry->mask & IT_ENTRY_INSTRUMENT) && (!channel->playing || entry->instrument != channel->playing->instnum))) + { + if (channel->note < 120) { + get_true_pan(sigdata, channel); + it_retrigger_note(sigrenderer, channel); + } + } + + if (entry->mask & IT_ENTRY_VOLPAN) { + if (entry->volpan <= 64) { + /* Volume */ + channel->volume = entry->volpan; + } else if (entry->volpan <= 74) { + /* Fine volume slide up */ + unsigned char v = entry->volpan - 65; + if (v == 0) + v = channel->lastvolslide; + channel->lastvolslide = v; + /* = effect DxF where x == entry->volpan - 65 */ + channel->volume += v; + if (channel->volume > 64) channel->volume = 64; + } else if (entry->volpan <= 84) { + /* Fine volume slide down */ + unsigned char v = entry->volpan - 75; + if (v == 0) + v = channel->lastvolslide; + channel->lastvolslide = v; + /* = effect DFx where x == entry->volpan - 75 */ + channel->volume -= v; + if (channel->volume > 64) channel->volume = 0; + } else if (entry->volpan < 128) { + /* Volume slide up */ + /* Volume slide down */ + /* Portamento down */ + /* Portamento up */ + } else if (entry->volpan <= 192) { + /* Pan */ + channel->pan = entry->volpan - 128; + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + } + /* else */ + /* Tone Portamento */ + /* Vibrato */ + } + return 0; +} + + + +static void retrigger_xm_envelopes(IT_PLAYING *playing) +{ + playing->volume_envelope.next_node = 0; + playing->volume_envelope.tick = -1; + playing->pan_envelope.next_node = 0; + playing->pan_envelope.tick = -1; + playing->fadeoutcount = 1024; +} + + + +static void process_xm_note_data(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; + + if (entry->mask & IT_ENTRY_INSTRUMENT) { + channel->instrument = entry->instrument; + instrument_to_sample(sigdata, channel); + if (channel->playing) { + /* Retrigger vol/pan envelopes if enabled, and cancel fadeout. + * Also reset vol/pan to that of _original_ instrument. + */ + channel->playing->flags &= ~(IT_PLAYING_SUSTAINOFF | IT_PLAYING_FADING); + it_playing_update_resamplers(channel->playing); + + channel->volume = channel->playing->sample->default_volume; + if (channel->pan >= 128 && channel->pan <= 192) + channel->pan = channel->playing->sample->default_pan - 128; + + retrigger_xm_envelopes(channel->playing); + } + } + + if (entry->mask & IT_ENTRY_NOTE) { + if (!(entry->mask & IT_ENTRY_INSTRUMENT)) + instrument_to_sample(sigdata, channel); + + if (channel->note >= 120) { + if (channel->playing) { + if (!(sigdata->instrument[channel->instrument-1].volume_envelope.flags & IT_ENVELOPE_ON)) + if (!(entry->mask & IT_ENTRY_INSTRUMENT)) + channel->volume = 0; + channel->playing->flags |= IT_PLAYING_SUSTAINOFF | IT_PLAYING_FADING; + it_playing_update_resamplers(channel->playing); + } + } else if (channel->sample == 0) { + /** If we get here, one of the following is the case: + ** 1. The instrument has never been specified on this channel. + ** 2. The specified instrument is invalid. + ** 3. The instrument has no sample mapped to the selected note. + ** What should happen? + ** + ** Experimentation shows that any existing note stops and cannot + ** be brought back. A subsequent instrument change fixes that. + **/ + if (channel->playing) { + free(channel->playing); + channel->playing = NULL; + } + return; + } else if (channel->playing && (entry->mask & IT_ENTRY_VOLPAN) && ((entry->volpan>>4) == 0xF)) { + /* Don't retrigger note; portamento in the volume column. */ + } else if (channel->playing && + (entry->mask & IT_ENTRY_EFFECT) && + (entry->effect == IT_TONE_PORTAMENTO || + entry->effect == IT_VOLSLIDE_TONEPORTA)) { + /* Don't retrigger note; portamento in the effects column. */ + } else { + channel->destnote = IT_NOTE_OFF; + + if (!channel->playing) { + channel->playing = malloc(sizeof(*channel->playing)); + if (!channel->playing) + return; + // Adding the following seems to do the trick for the case where a piece starts with an instrument alone and then some notes alone. + retrigger_xm_envelopes(channel->playing); + } + + channel->playing->flags = 0; + channel->playing->channel = channel; + channel->playing->sample = &sigdata->sample[channel->sample-1]; + if (sigdata->flags & IT_USE_INSTRUMENTS) + channel->playing->instrument = &sigdata->instrument[channel->instrument-1]; + else + channel->playing->instrument = NULL; + channel->playing->env_instrument = channel->playing->instrument; + channel->playing->sampnum = channel->sample; + channel->playing->instnum = channel->instrument; + channel->playing->channel_volume = channel->channelvolume; + channel->playing->note = channel->truenote; + channel->playing->filter_cutoff = 127; + channel->playing->filter_resonance = 0; + channel->playing->true_filter_cutoff = 127 << 8; + channel->playing->true_filter_resonance = 0; + channel->playing->vibrato_speed = 0; + channel->playing->vibrato_depth = 0; + channel->playing->vibrato_n = 0; + channel->playing->vibrato_time = 0; + channel->playing->tremolo_speed = 0; + channel->playing->tremolo_depth = 0; + channel->playing->tremolo_time = 0; + channel->playing->sample_vibrato_time = 0; + channel->playing->sample_vibrato_depth = 0; + channel->playing->slide = 0; + it_reset_filter_state(&channel->playing->filter_state[0]); // Are these + it_reset_filter_state(&channel->playing->filter_state[1]); // necessary? + it_playing_reset_resamplers(channel->playing, 0); + + /** WARNING - is everything initialised? */ + } + } + + if ((entry->mask & (IT_ENTRY_NOTE | IT_ENTRY_INSTRUMENT)) == (IT_ENTRY_NOTE | IT_ENTRY_INSTRUMENT)) { + if (channel->playing) retrigger_xm_envelopes(channel->playing); + get_default_volpan(sigdata, channel); + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + } + + if ((entry->mask & IT_ENTRY_VOLPAN) && ((entry->volpan>>4) == 0xF)) { + /* Tone Portamento */ + unsigned char v = (entry->volpan & 15) << 4; + if (v == 0) + v = channel->lastG; + channel->lastG = v; + if (entry->mask & IT_ENTRY_NOTE) + if (channel->sample) + channel->destnote = channel->truenote; + channel->toneporta = v << 4; + } else if ((entry->mask & IT_ENTRY_EFFECT) && + (entry->effect == IT_TONE_PORTAMENTO || + entry->effect == IT_VOLSLIDE_TONEPORTA)) { + unsigned char v; + if (entry->effect == IT_TONE_PORTAMENTO) + v = entry->effectvalue; + else + v = 0; + if (v == 0) + v = channel->lastG; + channel->lastG = v; + if (entry->mask & IT_ENTRY_NOTE) + if (channel->sample) + channel->destnote = channel->truenote; + channel->toneporta = v << 4; + } + + if (entry->mask & IT_ENTRY_VOLPAN) { + int effect = entry->volpan >> 4; + int value = entry->volpan & 15; + switch (effect) { + case 0x6: /* Volume slide down */ + channel->xm_volslide = -value; + break; + case 0x7: /* Volume slide up */ + channel->xm_volslide = value; + break; + case 0x8: /* Fine volume slide down */ + channel->volume -= value; + if (channel->volume > 64) channel->volume = 0; + break; + case 0x9: /* Fine volume slide up */ + channel->volume += value; + if (channel->volume > 64) channel->volume = 64; + break; + case 0xA: /* Set vibrato speed */ + if (value) + channel->lastHspeed = value; + if (channel->playing) + channel->playing->vibrato_speed = channel->lastHspeed; + break; + case 0xB: /* Vibrato */ + if (value) + channel->lastHdepth = value << 2; /** WARNING: correct ? */ + if (channel->playing) { + channel->playing->vibrato_depth = channel->lastHdepth; + channel->playing->vibrato_speed = channel->lastHspeed; + channel->playing->vibrato_n++; + } + break; + case 0xC: /* Set panning */ + channel->pan = (value*64)/15; + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + break; + case 0xD: /* Pan slide left */ + // TODO + // channel->xm_panslide = -value; + break; + case 0xE: /* Pan slide Right */ + // TODO + // channel->xm_panslide = value; + break; + case 0xF: /* Tone porta */ + break; + default: /* Volume */ + channel->volume = entry->volpan - 0x10; + break; + } + } +} + + + +/* This function assumes !IT_IS_END_ROW(entry). */ +static int process_note_data(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + + if (sigdata->flags & IT_WAS_AN_XM) + process_xm_note_data(sigrenderer, entry); + else + if (process_it_note_data(sigrenderer, entry)) return 0; + + return process_effects(sigrenderer, entry); +} + + + +static int process_entry(DUMB_IT_SIGRENDERER *sigrenderer, IT_ENTRY *entry) +{ + IT_CHANNEL *channel = &sigrenderer->channel[(int)entry->channel]; + + if (entry->mask & IT_ENTRY_NOTE) + channel->note = entry->note; + + if ((entry->mask & IT_ENTRY_EFFECT) && entry->effect == IT_S) { + /* channel->lastS was set in update_pattern_variables(). */ + unsigned char effectvalue = channel->lastS; + if (effectvalue >> 4 == IT_S_NOTE_DELAY) { + channel->note_delay_count = effectvalue & 15; + if (channel->note_delay_count == 0) + channel->note_delay_count = 1; + channel->note_delay_entry = entry; + return 0; + } + } + + return process_note_data(sigrenderer, entry); +} + + + +static void update_tick_counts(DUMB_IT_SIGRENDERER *sigrenderer) +{ + int i; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; + + if (channel->note_cut_count) { + channel->note_cut_count--; + if (channel->note_cut_count == 0) { + if (sigrenderer->sigdata->flags & IT_WAS_AN_XM) + channel->volume = 0; + else if (channel->playing) { + free(channel->playing); + channel->playing = NULL; + } + } + } else if (channel->note_delay_count) { + channel->note_delay_count--; + if (channel->note_delay_count == 0) + process_note_data(sigrenderer, channel->note_delay_entry); + /* Don't bother checking the return value; if the note + * was delayed, there can't have been a speed=0. + */ + } + } +} + + + +static int envelope_get_y(IT_ENVELOPE *envelope, IT_PLAYING_ENVELOPE *pe) +{ + int ys, ye; + int ts, te; + int t; + + if (pe->next_node <= 0) + return envelope->node_y[0] << IT_ENVELOPE_SHIFT; + + if (pe->next_node >= envelope->n_nodes) + return envelope->node_y[envelope->n_nodes-1] << IT_ENVELOPE_SHIFT; + + ys = envelope->node_y[pe->next_node-1] << IT_ENVELOPE_SHIFT; + ts = envelope->node_t[pe->next_node-1]; + te = envelope->node_t[pe->next_node]; + + if (ts == te) + return ys; + + ye = envelope->node_y[pe->next_node] << IT_ENVELOPE_SHIFT; + + t = pe->tick; + + return ys + (ye - ys) * (t - ts) / (te - ts); +} + + + +static int it_envelope_end(IT_PLAYING *playing, IT_ENVELOPE *envelope, IT_PLAYING_ENVELOPE *pe) +{ + if (pe->next_node >= envelope->n_nodes) + return 1; + + if (pe->tick < envelope->node_t[pe->next_node]) return 0; + + if ((envelope->flags & IT_ENVELOPE_LOOP_ON) && + envelope->loop_end >= pe->next_node && + envelope->node_t[envelope->loop_end] <= pe->tick) return 0; + + if ((envelope->flags & IT_ENVELOPE_SUSTAIN_LOOP) && + !(playing->flags & IT_PLAYING_SUSTAINOFF) && + envelope->sus_loop_end >= pe->next_node && + envelope->node_t[envelope->sus_loop_end] <= pe->tick) return 0; + + if (envelope->node_t[envelope->n_nodes-1] <= pe->tick) return 1; + + return 0; +} + + + +/* This returns 1 if the envelope finishes. */ +static int update_it_envelope(IT_PLAYING *playing, IT_ENVELOPE *envelope, IT_PLAYING_ENVELOPE *pe) +{ + if (!(envelope->flags & IT_ENVELOPE_ON)) + return 0; + + if (pe->next_node >= envelope->n_nodes) + return 1; + + while (pe->tick >= envelope->node_t[pe->next_node]) { + if ((envelope->flags & IT_ENVELOPE_LOOP_ON) && pe->next_node == envelope->loop_end) { + pe->next_node = envelope->loop_start; + pe->tick = envelope->node_t[envelope->loop_start]; + return it_envelope_end(playing, envelope, pe); + } + if ((envelope->flags & IT_ENVELOPE_SUSTAIN_LOOP) && !(playing->flags & IT_PLAYING_SUSTAINOFF) && pe->next_node == envelope->sus_loop_end) { + pe->next_node = envelope->sus_loop_start; + pe->tick = envelope->node_t[envelope->sus_loop_start]; + return it_envelope_end(playing, envelope, pe); + } + + pe->next_node++; + + if (pe->next_node >= envelope->n_nodes) + return 1; + } + + pe->tick++; + + return it_envelope_end(playing, envelope, pe); +} + + + +static void update_it_envelopes(IT_PLAYING *playing) +{ + IT_ENVELOPE *envelope = &playing->env_instrument->volume_envelope; + + if (update_it_envelope(playing, envelope, &playing->volume_envelope)) { + playing->flags |= IT_PLAYING_FADING; + if (envelope->n_nodes && envelope->node_y[envelope->n_nodes-1] == 0) + playing->flags |= IT_PLAYING_DEAD; + } + + update_it_envelope(playing, &playing->env_instrument->pan_envelope, &playing->pan_envelope); + update_it_envelope(playing, &playing->env_instrument->pitch_envelope, &playing->pitch_envelope); +} + + + +static int xm_envelope_is_sustaining(IT_PLAYING *playing, IT_ENVELOPE *envelope, IT_PLAYING_ENVELOPE *pe) +{ + if ((envelope->flags & IT_ENVELOPE_SUSTAIN_LOOP) && !(playing->flags & IT_PLAYING_SUSTAINOFF)) + if (envelope->sus_loop_start < envelope->n_nodes) + if (pe->tick == envelope->node_t[envelope->sus_loop_start]) + return 1; + return 0; +} + + + +static void update_xm_envelope(IT_PLAYING *playing, IT_ENVELOPE *envelope, IT_PLAYING_ENVELOPE *pe) +{ + if (!(envelope->flags & IT_ENVELOPE_ON)) + return; + + if (xm_envelope_is_sustaining(playing, envelope, pe)) + return; + + if (pe->tick >= envelope->node_t[envelope->n_nodes-1]) + return; + + pe->tick++; + + /* pe->next_node must be kept up to date for envelope_get_y(). */ + while (pe->tick > envelope->node_t[pe->next_node]) + pe->next_node++; + + if ((envelope->flags & IT_ENVELOPE_LOOP_ON) && envelope->loop_end < envelope->n_nodes) { + if (pe->tick == envelope->node_t[envelope->loop_end]) { + pe->next_node = MID(0, envelope->loop_start, envelope->n_nodes - 1); + pe->tick = envelope->node_t[pe->next_node]; + } + } + + if (xm_envelope_is_sustaining(playing, envelope, pe)) + return; + + if (pe->tick >= envelope->node_t[envelope->n_nodes-1]) + return; +} + + + +static void update_xm_envelopes(IT_PLAYING *playing) +{ + update_xm_envelope(playing, &playing->env_instrument->volume_envelope, &playing->volume_envelope); + update_xm_envelope(playing, &playing->env_instrument->pan_envelope, &playing->pan_envelope); +} + + + +static void update_fadeout(DUMB_IT_SIGDATA *sigdata, IT_PLAYING *playing) +{ + if (playing->flags & IT_PLAYING_FADING) { + playing->fadeoutcount -= playing->env_instrument->fadeout; + if (playing->fadeoutcount <= 0) { + playing->fadeoutcount = 0; + if (!(sigdata->flags & IT_WAS_AN_XM)) + playing->flags |= IT_PLAYING_DEAD; + } + } +} + + + +static void process_playing(DUMB_IT_SIGDATA *sigdata, IT_PLAYING *playing) +{ + if (playing->instrument) { + if (sigdata->flags & IT_WAS_AN_XM) + update_xm_envelopes(playing); + else + update_it_envelopes(playing); + update_fadeout(sigdata, playing); + } + + //Calculate final volume if required + //Calculate final pan if required + + if (sigdata->flags & IT_WAS_AN_XM) { + /* 'depth' is used to store the tick number for XM files. */ + if (playing->sample_vibrato_depth < playing->sample->vibrato_rate) + playing->sample_vibrato_depth++; + } else { + playing->sample_vibrato_depth += playing->sample->vibrato_rate; + if (playing->sample_vibrato_depth > playing->sample->vibrato_depth << 8) + playing->sample_vibrato_depth = playing->sample->vibrato_depth << 8; + } + + playing->sample_vibrato_time += playing->sample->vibrato_speed; +} + + + +static void process_all_playing(DUMB_IT_SIGRENDERER *sigrenderer) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + int i; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; + IT_PLAYING *playing = channel->playing; + + if (playing) { + int vibrato_shift = it_sine[playing->vibrato_time]; + vibrato_shift *= playing->vibrato_n; + vibrato_shift *= playing->vibrato_depth; + vibrato_shift >>= 4; + + if (sigdata->flags & IT_OLD_EFFECTS) + vibrato_shift = -vibrato_shift; + + playing->volume = channel->volume; + playing->pan = channel->truepan; + + if (sigdata->flags & IT_LINEAR_SLIDES) { + int currpitch = ((playing->note - 60) << 8) + playing->slide + + vibrato_shift; + + /* We add a feature here, which is that of keeping the pitch + * within range. Otherwise it crashes. Trust me. It happened. + * The limit 32768 gives almost 11 octaves either way. + */ + if (currpitch < -32768) + currpitch = -32768; + else if (currpitch > 32767) + currpitch = 32767; + + playing->delta = (float)pow(DUMB_PITCH_BASE, currpitch); + playing->delta *= playing->sample->C5_speed / 65536.0f; + } else { + int slide = playing->slide + vibrato_shift; + + playing->delta = (float)pow(DUMB_SEMITONE_BASE, 60 - playing->note); + /* playing->delta is 1.0 for C-5, 0.5 for C-6, etc. */ + + playing->delta *= 1.0f / playing->sample->C5_speed; + + playing->delta -= slide / AMIGA_DIVISOR; + + if (playing->delta < (1.0f / 65536.0f) / 32768.0f) { + // Should XM notes die if Amiga slides go out of range? + playing->flags |= IT_PLAYING_DEAD; + continue; + } + + playing->delta = (1.0f / 65536.0f) / playing->delta; + } + + playing->delta *= (float)pow(DUMB_SEMITONE_BASE, channel->arpeggio >> 8); + + playing->filter_cutoff = channel->filter_cutoff; + playing->filter_resonance = channel->filter_resonance; + } + } + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + if (sigrenderer->channel[i].playing) { + process_playing(sigdata, sigrenderer->channel[i].playing); + if (!(sigdata->flags & IT_WAS_AN_XM)) { + if ((sigrenderer->channel[i].playing->flags & (IT_PLAYING_BACKGROUND | IT_PLAYING_DEAD)) == (IT_PLAYING_BACKGROUND | IT_PLAYING_DEAD)) { + free(sigrenderer->channel[i].playing); + sigrenderer->channel[i].playing = NULL; + } + } + } + } + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (sigrenderer->playing[i]) { + process_playing(sigdata, sigrenderer->playing[i]); + if (sigrenderer->playing[i]->flags & IT_PLAYING_DEAD) { + free(sigrenderer->playing[i]); + sigrenderer->playing[i] = NULL; + } + } + } +} + + + +static int process_tick(DUMB_IT_SIGRENDERER *sigrenderer) +{ + DUMB_IT_SIGDATA *sigdata = sigrenderer->sigdata; + + // Set note vol/freq to vol/freq set for each channel + + if (sigrenderer->speed && --sigrenderer->tick == 0) { + reset_tick_counts(sigrenderer); + sigrenderer->tick = sigrenderer->speed; + sigrenderer->rowcount--; + if (sigrenderer->rowcount == 0) { + sigrenderer->rowcount = 1; + if (sigrenderer->pat_loop_row >= 0) { + int n = sigrenderer->pat_loop_row - 1; + sigrenderer->row = sigrenderer->processrow = n; + sigrenderer->pat_loop_row = -1; + if (n < 0) + sigrenderer->entry = NULL; + else { + sigrenderer->entry = sigrenderer->entry_start; + while (n) { + while (sigrenderer->entry < sigrenderer->entry_end) { + if (IT_IS_END_ROW(sigrenderer->entry)) { + sigrenderer->entry++; + break; + } + sigrenderer->entry++; + } + n--; + } + } + } + + sigrenderer->processrow++; + + if (sigrenderer->processrow >= sigrenderer->n_rows) { + IT_PATTERN *pattern; + int n; + + sigrenderer->processrow = sigrenderer->breakrow; + sigrenderer->breakrow = 0; + + for (;;) { + sigrenderer->processorder++; + + if (sigrenderer->processorder >= sigdata->n_orders) { + sigrenderer->processorder = sigdata->restart_position; + if (sigrenderer->processorder >= sigdata->n_orders) { + /* Restarting beyond end. We'll loop for now. */ + sigrenderer->processorder = -1; + continue; + } + } + + n = sigdata->order[sigrenderer->processorder]; + + if (n < sigdata->n_patterns) + break; + +#ifdef INVALID_ORDERS_END_SONG + if (n != IT_ORDER_SKIP) + sigrenderer->processorder = -1; +#else + if (n == IT_ORDER_END) + sigrenderer->processorder = -1; +#endif + } + + pattern = &sigdata->pattern[n]; + + sigrenderer->n_rows = pattern->n_rows; + + if (sigrenderer->processrow >= sigrenderer->n_rows) + sigrenderer->processrow = 0; + +/** WARNING - everything pertaining to a new pattern initialised? */ + + sigrenderer->entry = sigrenderer->entry_start = pattern->entry; + sigrenderer->entry_end = sigrenderer->entry + pattern->n_entries; + + if (sigrenderer->order >= sigrenderer->processorder) { + if (sigrenderer->callbacks->loop) { + if ((*sigrenderer->callbacks->loop)(sigrenderer->callbacks->loop_data)) + return 1; + if (sigrenderer->speed == 0) + goto speed0; /* I love goto */ + } + } + sigrenderer->order = sigrenderer->processorder; + + n = sigrenderer->processrow; + while (n) { + while (sigrenderer->entry < sigrenderer->entry_end) { + if (IT_IS_END_ROW(sigrenderer->entry)) { + sigrenderer->entry++; + break; + } + sigrenderer->entry++; + } + n--; + } + sigrenderer->row = sigrenderer->processrow; + } else { + if (sigrenderer->entry) { + while (sigrenderer->entry < sigrenderer->entry_end) { + if (IT_IS_END_ROW(sigrenderer->entry)) { + sigrenderer->entry++; + break; + } + sigrenderer->entry++; + } + sigrenderer->row++; + } else { + sigrenderer->entry = sigrenderer->entry_start; + sigrenderer->row = 0; + } + } + + reset_effects(sigrenderer); + + { + IT_ENTRY *entry = sigrenderer->entry; + + while (entry < sigrenderer->entry_end && !IT_IS_END_ROW(entry)) + update_pattern_variables(sigrenderer, entry++); + } + + { + IT_ENTRY *entry = sigrenderer->entry; + + while (entry < sigrenderer->entry_end && !IT_IS_END_ROW(entry)) + if (process_entry(sigrenderer, entry++)) + return 1; + } + + if (!(sigdata->flags & IT_OLD_EFFECTS)) + update_smooth_effects(sigrenderer); + } else { + { + IT_ENTRY *entry = sigrenderer->entry; + + while (entry < sigrenderer->entry_end && !IT_IS_END_ROW(entry)) + process_effects(sigrenderer, entry++); + /* Don't bother checking the return value; if there + * was a pattern delay, there can't be a speed=0. + */ + } + + update_effects(sigrenderer); + } + } else { + speed0: + update_effects(sigrenderer); + update_tick_counts(sigrenderer); + } + + process_all_playing(sigrenderer); + + sigrenderer->time_left += TICK_TIME_DIVIDEND / sigrenderer->tempo; + + return 0; +} + + + +int dumb_it_max_to_mix = 64; + + + +static float calculate_volume(DUMB_IT_SIGRENDERER *sigrenderer, IT_PLAYING *playing, float volume) +{ + if (volume != 0) { + int vol; + + if (playing->channel->flags & IT_CHANNEL_MUTED) + return 0; + + if ((playing->channel->tremor_time & 192) == 128) + return 0; + + vol = it_sine[playing->tremolo_time]; + vol *= playing->tremolo_depth; + + vol = (playing->volume << 5) + vol; + + if (vol <= 0) + return 0; + + if (vol > 64 << 5) + vol = 64 << 5; + + volume *= vol; /* 64 << 5 */ + volume *= playing->sample->global_volume; /* 64 */ + volume *= playing->channel_volume; /* 64 */ + volume *= sigrenderer->globalvolume; /* 128 */ + volume *= sigrenderer->sigdata->mixing_volume; /* 128 */ + volume *= 1.0f / ((64 << 5) * 64.0f * 64.0f * 128.0f * 128.0f); + + if (volume && playing->instrument) { + if (playing->env_instrument->volume_envelope.flags & IT_ENVELOPE_ON) { + volume *= envelope_get_y(&playing->env_instrument->volume_envelope, &playing->volume_envelope); + volume *= 1.0f / (64 << IT_ENVELOPE_SHIFT); + } + volume *= playing->instrument->global_volume; /* 128 */ + volume *= playing->fadeoutcount; /* 1024 */ + volume *= 1.0f / (128.0f * 1024.0f); + } + } + + return volume; +} + + + +static int apply_pan_envelope(IT_PLAYING *playing) +{ + int pan = playing->pan; + if (pan <= 64 << IT_ENVELOPE_SHIFT && playing->env_instrument && (playing->env_instrument->pan_envelope.flags & IT_ENVELOPE_ON)) { + int p = envelope_get_y(&playing->env_instrument->pan_envelope, &playing->pan_envelope); + if (pan > 32 << IT_ENVELOPE_SHIFT) + p *= (64 << IT_ENVELOPE_SHIFT) - pan; + else + p *= pan; + pan += p >> (5 + IT_ENVELOPE_SHIFT); + } + return pan; +} + + + +/* Note: if a click remover is provided, and store_end_sample is set, then + * the end point will be computed twice. This situation should not arise. + */ +static long render_playing(DUMB_IT_SIGRENDERER *sigrenderer, IT_PLAYING *playing, float volume, float delta, long pos, long size, sample_t **samples, int store_end_sample, int *left_to_mix) +{ + int pan; + + long size_rendered; + + if (playing->flags & IT_PLAYING_DEAD) + return 0; + + if (*left_to_mix <= 0) + volume = 0; + + pan = apply_pan_envelope(playing); + +#define RESAMPLERV(rv, resampler, dst, volume) \ +{ \ + rv = dumb_resample(resampler, dst, size, volume, delta); \ + if (store_end_sample) \ + (dst)[rv] = RESAMPLE_VALUE(resampler, volume); \ +} + +#define RESAMPLE(resampler, dst, volume) \ +{ \ + int i; \ + RESAMPLERV(i, resampler, dst, volume); \ +} + +#define RESAMPLE_VALUE(resampler, volume) \ + dumb_resample_get_current_sample(resampler, volume) + + if (volume == 0) { + size_rendered = dumb_resample(&playing->resampler[0], NULL, size, 0, delta); + if (playing->sample->flags & IT_SAMPLE_STEREO) + dumb_resample(&playing->resampler[1], NULL, size, 0, delta); + } else { + if (sigrenderer->n_channels == 2) { + float vol = volume; + DUMB_RESAMPLER start = playing->resampler[0]; + if (!IT_IS_SURROUND_SHIFTED(pan)) vol *= 2.0f - pan * (1.0f / (32 << IT_ENVELOPE_SHIFT)); + if (sigrenderer->click_remover && sigrenderer->click_remover[0]) + dumb_record_click(sigrenderer->click_remover[0], pos, RESAMPLE_VALUE(&playing->resampler[0], vol)); + RESAMPLERV(size_rendered, &playing->resampler[0], samples[0] + pos, vol); + if (sigrenderer->click_remover && sigrenderer->click_remover[0]) + dumb_record_click(sigrenderer->click_remover[0], pos + size_rendered, -RESAMPLE_VALUE(&playing->resampler[0], vol)); + vol = -vol; + if (!IT_IS_SURROUND_SHIFTED(pan)) vol += 2.0f * volume; + if (playing->sample->flags & IT_SAMPLE_STEREO) { + if (sigrenderer->click_remover && sigrenderer->click_remover[1]) + dumb_record_click(sigrenderer->click_remover[1], pos, RESAMPLE_VALUE(&playing->resampler[1], vol)); + RESAMPLE(&playing->resampler[1], samples[1] + pos, vol); + if (sigrenderer->click_remover && sigrenderer->click_remover[1]) + dumb_record_click(sigrenderer->click_remover[1], pos + size_rendered, -RESAMPLE_VALUE(&playing->resampler[1], vol)); + } else { + playing->resampler[0] = start; + if (sigrenderer->click_remover && sigrenderer->click_remover[1]) + dumb_record_click(sigrenderer->click_remover[1], pos, RESAMPLE_VALUE(&playing->resampler[0], vol)); + RESAMPLE(&playing->resampler[0], samples[1] + pos, vol); + if (sigrenderer->click_remover && sigrenderer->click_remover[1]) + dumb_record_click(sigrenderer->click_remover[1], pos + size_rendered, -RESAMPLE_VALUE(&playing->resampler[0], vol)); + } + } else { + if (playing->sample->flags & IT_SAMPLE_STEREO) { + float vol = 0.5f * volume; + if (!IT_IS_SURROUND_SHIFTED(pan)) vol *= 2.0f - pan * (1.0f / (32 << IT_ENVELOPE_SHIFT)); + if (sigrenderer->click_remover && sigrenderer->click_remover[0]) { + sample_t startstep, endstep; + startstep = RESAMPLE_VALUE(&playing->resampler[0], vol); + RESAMPLE(&playing->resampler[0], samples[0] + pos, vol); + endstep = RESAMPLE_VALUE(&playing->resampler[0], vol); + if (!IT_IS_SURROUND_SHIFTED(pan)) vol = 2.0f * volume - vol; + startstep += RESAMPLE_VALUE(&playing->resampler[1], vol); + RESAMPLERV(size_rendered, &playing->resampler[1], samples[0] + pos, vol); + endstep += RESAMPLE_VALUE(&playing->resampler[1], vol); + dumb_record_click(sigrenderer->click_remover[0], pos, startstep); + dumb_record_click(sigrenderer->click_remover[0], pos + size_rendered, -endstep); + } else { + RESAMPLE(&playing->resampler[0], samples[0] + pos, vol); + if (!IT_IS_SURROUND_SHIFTED(pan)) vol = 2.0f * volume - vol; + RESAMPLERV(size_rendered, &playing->resampler[1], samples[0] + pos, vol); + } + } else { + if (sigrenderer->click_remover && sigrenderer->click_remover[0]) + dumb_record_click(sigrenderer->click_remover[0], pos, RESAMPLE_VALUE(&playing->resampler[0], volume)); + RESAMPLERV(size_rendered, &playing->resampler[0], samples[0] + pos, volume); + if (sigrenderer->click_remover && sigrenderer->click_remover[0]) + dumb_record_click(sigrenderer->click_remover[0], pos + size_rendered, -RESAMPLE_VALUE(&playing->resampler[0], volume)); + } + } + (*left_to_mix)--; + } + + if (playing->resampler[0].dir == 0) + playing->flags |= IT_PLAYING_DEAD; + + return size_rendered; +} + + + +typedef struct IT_TO_MIX +{ + IT_PLAYING *playing; + float volume; +} +IT_TO_MIX; + + + +static int it_to_mix_compare(const void *e1, const void *e2) +{ + if (((const IT_TO_MIX *)e1)->volume > ((const IT_TO_MIX *)e2)->volume) + return -1; + + if (((const IT_TO_MIX *)e1)->volume < ((const IT_TO_MIX *)e2)->volume) + return 1; + + return 0; +} + + + +static void apply_pitch_modifications(DUMB_IT_SIGDATA *sigdata, IT_PLAYING *playing, float *delta, int *cutoff) +{ + { + int sample_vibrato_shift = it_sine[playing->sample_vibrato_time]; + + if (sigdata->flags & IT_WAS_AN_XM) { + int depth = playing->sample->vibrato_depth; /* True depth */ + if (playing->sample->vibrato_rate) { + depth *= playing->sample_vibrato_depth; /* Tick number */ + depth /= playing->sample->vibrato_rate; /* XM sweep */ + } + sample_vibrato_shift *= depth; + } else + sample_vibrato_shift *= playing->sample_vibrato_depth >> 8; + + sample_vibrato_shift >>= 4; + + *delta *= (float)pow(DUMB_PITCH_BASE, sample_vibrato_shift); + } + + if (playing->env_instrument && + (playing->env_instrument->pitch_envelope.flags & IT_ENVELOPE_ON)) + { + int p = envelope_get_y(&playing->env_instrument->pitch_envelope, &playing->pitch_envelope); + if (playing->env_instrument->pitch_envelope.flags & IT_ENVELOPE_PITCH_IS_FILTER) + *cutoff = (*cutoff * (p+(32<> (6 + IT_ENVELOPE_SHIFT); + else + *delta *= (float)pow(DUMB_PITCH_BASE, p >> (IT_ENVELOPE_SHIFT - 7)); + } +} + + + +static void render(DUMB_IT_SIGRENDERER *sigrenderer, float volume, float delta, long pos, long size, sample_t **samples) +{ + int i; + + int n_to_mix = 0; + IT_TO_MIX to_mix[DUMB_IT_TOTAL_CHANNELS]; + int left_to_mix = dumb_it_max_to_mix; + + sample_t **samples_to_filter = NULL; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + if (sigrenderer->channel[i].playing && !(sigrenderer->channel[i].playing->flags & IT_PLAYING_DEAD)) { + to_mix[n_to_mix].playing = sigrenderer->channel[i].playing; + to_mix[n_to_mix].volume = volume == 0 ? 0 : calculate_volume(sigrenderer, sigrenderer->channel[i].playing, volume); + n_to_mix++; + } + } + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (sigrenderer->playing[i]) { /* Won't be dead; it would have been freed. */ + to_mix[n_to_mix].playing = sigrenderer->playing[i]; + to_mix[n_to_mix].volume = volume == 0 ? 0 : calculate_volume(sigrenderer, sigrenderer->playing[i], volume); + n_to_mix++; + } + } + + if (volume != 0) + qsort(to_mix, n_to_mix, sizeof(IT_TO_MIX), &it_to_mix_compare); + + for (i = 0; i < n_to_mix; i++) { + IT_PLAYING *playing = to_mix[i].playing; + float note_delta = delta * playing->delta; + int cutoff = playing->filter_cutoff << IT_ENVELOPE_SHIFT; + + apply_pitch_modifications(sigrenderer->sigdata, playing, ¬e_delta, &cutoff); + + if (cutoff != 127 << IT_ENVELOPE_SHIFT || playing->filter_resonance != 0) { + playing->true_filter_cutoff = cutoff; + playing->true_filter_resonance = playing->filter_resonance; + } + + if (to_mix[i].volume && (playing->true_filter_cutoff != 127 << IT_ENVELOPE_SHIFT || playing->true_filter_resonance != 0)) { + if (!samples_to_filter) { + samples_to_filter = create_sample_buffer(sigrenderer->n_channels, size + 1); + if (!samples_to_filter) { + render_playing(sigrenderer, playing, 0, note_delta, pos, size, NULL, 0, &left_to_mix); + continue; + } + } + { + long size_rendered; + DUMB_CLICK_REMOVER **cr = sigrenderer->click_remover; + dumb_silence(samples_to_filter[0], sigrenderer->n_channels * (size + 1)); + sigrenderer->click_remover = NULL; + size_rendered = render_playing(sigrenderer, playing, to_mix[i].volume, note_delta, 0, size, samples_to_filter, 1, &left_to_mix); + sigrenderer->click_remover = cr; + it_filter(cr ? cr[0] : NULL, &playing->filter_state[0], samples[0], pos, samples_to_filter[0], size_rendered, + 65536.0f/delta, playing->true_filter_cutoff, playing->true_filter_resonance); + if (sigrenderer->n_channels == 2) + it_filter(cr ? cr[1] : NULL, &playing->filter_state[1], samples[1], pos, samples_to_filter[1], size_rendered, + 65536.0f/delta, playing->true_filter_cutoff, playing->true_filter_resonance); + // warning: filtering is not prevented by low left_to_mix! + } + } else { + it_reset_filter_state(&playing->filter_state[0]); + it_reset_filter_state(&playing->filter_state[1]); + render_playing(sigrenderer, playing, to_mix[i].volume, note_delta, pos, size, samples, 0, &left_to_mix); + } + } + + destroy_sample_buffer(samples_to_filter); + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + if (sigrenderer->channel[i].playing) { + if ((sigrenderer->channel[i].playing->flags & (IT_PLAYING_BACKGROUND | IT_PLAYING_DEAD)) == (IT_PLAYING_BACKGROUND | IT_PLAYING_DEAD)) { + free(sigrenderer->channel[i].playing); + sigrenderer->channel[i].playing = NULL; + } + } + } + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) { + if (sigrenderer->playing[i]) { + if (sigrenderer->playing[i]->flags & IT_PLAYING_DEAD) { + free(sigrenderer->playing[i]); + sigrenderer->playing[i] = NULL; + } + } + } +} + + + +static DUMB_IT_SIGRENDERER *init_sigrenderer(DUMB_IT_SIGDATA *sigdata, int n_channels, int startorder, IT_CALLBACKS *callbacks, DUMB_CLICK_REMOVER **cr) +{ + DUMB_IT_SIGRENDERER *sigrenderer; + int i; + + if (startorder > sigdata->n_orders) { + free(callbacks); + dumb_destroy_click_remover_array(n_channels, cr); + return NULL; + } + + sigrenderer = malloc(sizeof(*sigrenderer)); + if (!sigrenderer) { + free(callbacks); + dumb_destroy_click_remover_array(n_channels, cr); + return NULL; + } + + sigrenderer->callbacks = callbacks; + sigrenderer->click_remover = cr; + + sigrenderer->sigdata = sigdata; + sigrenderer->n_channels = n_channels; + sigrenderer->globalvolume = sigdata->global_volume; + sigrenderer->tempo = sigdata->tempo; + + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) { + IT_CHANNEL *channel = &sigrenderer->channel[i]; +#if IT_CHANNEL_MUTED != 1 +#error this is wrong +#endif + channel->flags = sigdata->channel_pan[i] >> 7; + channel->volume = (sigdata->flags & IT_WAS_AN_XM) ? 0 : 64; + channel->pan = sigdata->channel_pan[i] & 0x7F; + channel->truepan = channel->pan << IT_ENVELOPE_SHIFT; + channel->channelvolume = sigdata->channel_volume[i]; + channel->instrument = 0; + channel->note = 0; + channel->SFmacro = 0; + channel->filter_cutoff = 127; + channel->filter_resonance = 0; + channel->xm_retrig = 0; + channel->retrig_tick = 0; + channel->tremor_time = 0; + channel->midi_state = 0; + channel->lastvolslide = 0; + channel->lastDKL = 0; + channel->lastEF = 0; + channel->lastG = 0; + channel->lastHspeed = 0; + channel->lastHdepth = 0; + channel->lastRspeed = 0; + channel->lastRdepth = 0; + channel->lastI = 0; + channel->lastJ = 0; + channel->lastN = 0; + channel->lastO = 0; + channel->high_offset = 0; + channel->lastQ = 0; + channel->lastS = 0; + channel->pat_loop_row = 0; + channel->pat_loop_count = 0; + channel->lastW = 0; + channel->xm_lastE1 = 0; + channel->xm_lastE2 = 0; + channel->xm_lastEA = 0; + channel->xm_lastEB = 0; + channel->xm_lastX1 = 0; + channel->xm_lastX2 = 0; + channel->playing = NULL; + } + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) + sigrenderer->playing[i] = NULL; + + sigrenderer->speed = sigdata->speed; + + sigrenderer->processrow = 0; + sigrenderer->breakrow = 0; + sigrenderer->pat_loop_row = -1; + sigrenderer->rowcount = 1; + + reset_tick_counts(sigrenderer); + + sigrenderer->tick = sigrenderer->speed; + + { + IT_PATTERN *pattern; + int n; + + sigrenderer->processorder = startorder; + for (;;) { + n = sigdata->order[sigrenderer->processorder]; + + if (n < sigdata->n_patterns) + break; + +#ifdef INVALID_ORDERS_END_SONG + if (n != IT_ORDER_SKIP) +#else + if (n == IT_ORDER_END) +#endif + { + _dumb_it_end_sigrenderer(sigrenderer); + return NULL; + } + + sigrenderer->processorder++; + if (sigrenderer->processorder >= sigdata->n_orders) + sigrenderer->processorder = 0; + if (sigrenderer->processorder == startorder) { + _dumb_it_end_sigrenderer(sigrenderer); + return NULL; + } + } + + pattern = &sigdata->pattern[n]; + + sigrenderer->n_rows = pattern->n_rows; + +/** WARNING - everything pertaining to a new pattern initialised? */ + + sigrenderer->entry = sigrenderer->entry_start = pattern->entry; + sigrenderer->entry_end = sigrenderer->entry + pattern->n_entries; + + sigrenderer->order = sigrenderer->processorder; + sigrenderer->row = 0; + } + + reset_effects(sigrenderer); + + { + IT_ENTRY *entry = sigrenderer->entry; + + while (entry < sigrenderer->entry_end && !IT_IS_END_ROW(entry)) + update_pattern_variables(sigrenderer, entry++); + } + + { + IT_ENTRY *entry = sigrenderer->entry; + + while (entry < sigrenderer->entry_end && !IT_IS_END_ROW(entry)) { + if (process_entry(sigrenderer, entry++)) { + /* Oops, that song ended quickly! */ + _dumb_it_end_sigrenderer(sigrenderer); + return NULL; + } + } + } + + if (!(sigdata->flags & IT_OLD_EFFECTS)) + update_smooth_effects(sigrenderer); + + process_all_playing(sigrenderer); + + sigrenderer->time_left = TICK_TIME_DIVIDEND / sigrenderer->tempo; + sigrenderer->sub_time_left = 0; + + return sigrenderer; +} + + + +void dumb_it_set_loop_callback(DUMB_IT_SIGRENDERER *sigrenderer, int (*callback)(void *data), void *data) +{ + if (sigrenderer) { + sigrenderer->callbacks->loop = callback; + sigrenderer->callbacks->loop_data = data; + } +} + + + +void dumb_it_set_xm_speed_zero_callback(DUMB_IT_SIGRENDERER *sigrenderer, int (*callback)(void *data), void *data) +{ + if (sigrenderer) { + sigrenderer->callbacks->xm_speed_zero = callback; + sigrenderer->callbacks->xm_speed_zero_data = data; + } +} + + + +void dumb_it_set_midi_callback(DUMB_IT_SIGRENDERER *sigrenderer, int (*callback)(void *data, int channel, unsigned char byte), void *data) +{ + if (sigrenderer) { + sigrenderer->callbacks->midi = callback; + sigrenderer->callbacks->midi_data = data; + } +} + + + +static IT_CALLBACKS *create_callbacks(void) +{ + IT_CALLBACKS *callbacks = malloc(sizeof(*callbacks)); + if (!callbacks) return NULL; + callbacks->loop = NULL; + callbacks->xm_speed_zero = NULL; + callbacks->midi = NULL; + return callbacks; +} + + + +static DUMB_IT_SIGRENDERER *dumb_it_init_sigrenderer(DUMB_IT_SIGDATA *sigdata, int n_channels, int startorder) +{ + IT_CALLBACKS *callbacks; + + if (!sigdata) return NULL; + + callbacks = create_callbacks(); + if (!callbacks) return NULL; + + return init_sigrenderer(sigdata, n_channels, startorder, callbacks, + dumb_create_click_remover_array(n_channels)); +} + + + +DUH_SIGRENDERER *dumb_it_start_at_order(DUH *duh, int n_channels, int startorder) +{ + DUMB_IT_SIGDATA *itsd = duh_get_it_sigdata(duh); + DUMB_IT_SIGRENDERER *itsr = dumb_it_init_sigrenderer(itsd, n_channels, startorder); + return duh_encapsulate_it_sigrenderer(itsr, n_channels, 0); +} + + + +static sigrenderer_t *it_start_sigrenderer(DUH *duh, sigdata_t *vsigdata, int n_channels, long pos) +{ + DUMB_IT_SIGDATA *sigdata = vsigdata; + DUMB_IT_SIGRENDERER *sigrenderer; + + (void)duh; + + { + IT_CALLBACKS *callbacks = create_callbacks(); + if (!callbacks) return NULL; + + if (sigdata->checkpoint) { + IT_CHECKPOINT *checkpoint = sigdata->checkpoint; + while (checkpoint->next && checkpoint->next->time < pos) + checkpoint = checkpoint->next; + sigrenderer = dup_sigrenderer(checkpoint->sigrenderer, n_channels, callbacks); + if (!sigrenderer) return NULL; + sigrenderer->click_remover = dumb_create_click_remover_array(n_channels); + pos -= checkpoint->time; + } else { + sigrenderer = init_sigrenderer(sigdata, n_channels, 0, callbacks, + dumb_create_click_remover_array(n_channels)); + if (!sigrenderer) return NULL; + } + } + + for (;;) { + if (sigrenderer->time_left >= pos) + break; + + render(sigrenderer, 0, 1.0f, 0, sigrenderer->time_left, NULL); + + pos -= sigrenderer->time_left; + sigrenderer->time_left = 0; + + if (process_tick(sigrenderer)) { + _dumb_it_end_sigrenderer(sigrenderer); + return NULL; + } + } + + render(sigrenderer, 0, 1.0f, 0, pos, NULL); + sigrenderer->time_left -= pos; + + /** WARNING - everything initialised? */ + + return sigrenderer; +} + + + +static long it_sigrenderer_get_samples( + sigrenderer_t *vsigrenderer, + float volume, float delta, + long size, sample_t **samples +) +{ + DUMB_IT_SIGRENDERER *sigrenderer = vsigrenderer; + long pos; + int dt; + long todo; + LONG_LONG t; + + if (sigrenderer->order < 0) return 0; + + pos = 0; + dt = (int)(delta * 65536.0f + 0.5f); + + /* When samples is finally used in render_playing(), it won't be used if + * volume is 0. + */ + if (!samples) volume = 0; + + for (;;) { + todo = (long)((((LONG_LONG)sigrenderer->time_left << 16) | sigrenderer->sub_time_left) / dt); + + if (todo >= size) + break; + + render(sigrenderer, volume, delta, pos, todo, samples); + + pos += todo; + size -= todo; + + t = sigrenderer->sub_time_left - (LONG_LONG)todo * dt; + sigrenderer->sub_time_left = (long)t & 65535; + sigrenderer->time_left += (long)(t >> 16); + + if (process_tick(sigrenderer)) { + sigrenderer->order = -1; + sigrenderer->row = -1; + return pos; + } + } + + render(sigrenderer, volume, delta, pos, size, samples); + + pos += size; + + t = sigrenderer->sub_time_left - (LONG_LONG)size * dt; + sigrenderer->sub_time_left = (long)t & 65535; + sigrenderer->time_left += (long)(t >> 16); + + dumb_remove_clicks_array(sigrenderer->n_channels, sigrenderer->click_remover, samples, pos, 512.0f / delta); + + return pos; +} + + + +static void it_sigrenderer_get_current_sample(sigrenderer_t *vsigrenderer, float volume, sample_t *samples) +{ + DUMB_IT_SIGRENDERER *sigrenderer = vsigrenderer; + (void)volume; // for consideration: in any of these such functions, is 'volume' going to be required? + dumb_click_remover_get_offset_array(sigrenderer->n_channels, sigrenderer->click_remover, samples); +} + + + +void _dumb_it_end_sigrenderer(sigrenderer_t *vsigrenderer) +{ + DUMB_IT_SIGRENDERER *sigrenderer = vsigrenderer; + + int i; + + if (sigrenderer) { + for (i = 0; i < DUMB_IT_N_CHANNELS; i++) + if (sigrenderer->channel[i].playing) + free(sigrenderer->channel[i].playing); + + for (i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++) + if (sigrenderer->playing[i]) + free(sigrenderer->playing[i]); + + dumb_destroy_click_remover_array(sigrenderer->n_channels, sigrenderer->click_remover); + + if (sigrenderer->callbacks) + free(sigrenderer->callbacks); + + free(vsigrenderer); + } +} + + + +DUH_SIGTYPE_DESC _dumb_sigtype_it = { + SIGTYPE_IT, + NULL, + &it_start_sigrenderer, + NULL, + &it_sigrenderer_get_samples, + &it_sigrenderer_get_current_sample, + &_dumb_it_end_sigrenderer, + &_dumb_it_unload_sigdata +}; + + + +DUH_SIGRENDERER *duh_encapsulate_it_sigrenderer(DUMB_IT_SIGRENDERER *it_sigrenderer, int n_channels, long pos) +{ + return duh_encapsulate_raw_sigrenderer(it_sigrenderer, &_dumb_sigtype_it, n_channels, pos); +} + + + +DUMB_IT_SIGRENDERER *duh_get_it_sigrenderer(DUH_SIGRENDERER *sigrenderer) +{ + return duh_get_raw_sigrenderer(sigrenderer, SIGTYPE_IT); +} + + + +/* Values of 64 or more will access NNA channels here. */ +void dumb_it_sr_get_channel_state(DUMB_IT_SIGRENDERER *sr, int channel, DUMB_IT_CHANNEL_STATE *state) +{ + IT_PLAYING *playing; + int t; /* temporary var for holding accurate pan and filter cutoff */ + float delta; + ASSERT(channel < DUMB_IT_TOTAL_CHANNELS); + if (!sr) { state->sample = 0; return; } + if (channel >= DUMB_IT_N_CHANNELS) { + playing = sr->playing[channel - DUMB_IT_N_CHANNELS]; + if (!playing) { state->sample = 0; return; } + } else { + playing = sr->channel[channel].playing; + if (!playing) { state->sample = 0; return; } + } + + if (playing->flags & IT_PLAYING_DEAD) { state->sample = 0; return; } + + state->channel = playing->channel - sr->channel; + state->sample = playing->sampnum; + state->volume = calculate_volume(sr, playing, 1.0f); + + t = apply_pan_envelope(playing); + state->pan = (unsigned char)((t + 128) >> IT_ENVELOPE_SHIFT); + state->subpan = (signed char)t; + + delta = playing->delta * 65536.0f; + t = playing->filter_cutoff << IT_ENVELOPE_SHIFT; + apply_pitch_modifications(sr->sigdata, playing, &delta, &t); + state->freq = (int)delta; + if (t == 127 << IT_ENVELOPE_SHIFT && playing->filter_resonance == 0) { + state->filter_resonance = playing->true_filter_resonance; + t = playing->true_filter_cutoff; + } else + state->filter_resonance = playing->filter_resonance; + state->filter_cutoff = (unsigned char)(t >> 8); + state->filter_subcutoff = (unsigned char)t; +} + + + +int dumb_it_callback_terminate(void *data) +{ + (void)data; + return 1; +} + + + +int dumb_it_callback_midi_block(void *data, int channel, unsigned char byte) +{ + (void)data; + (void)channel; + (void)byte; + return 1; +} + + + +#define IT_CHECKPOINT_INTERVAL (30 * 65536) /* Half a minute */ + + + +/* Returns the length of the module, up until it first loops. */ +long _dumb_it_build_checkpoints(DUMB_IT_SIGDATA *sigdata) +{ + IT_CHECKPOINT *checkpoint = malloc(sizeof(*checkpoint)); + if (!checkpoint) return 0; + checkpoint->time = 0; + checkpoint->sigrenderer = dumb_it_init_sigrenderer(sigdata, 0, 0); + if (!checkpoint->sigrenderer) { + free(checkpoint); + return 0; + } + checkpoint->sigrenderer->callbacks->loop = &dumb_it_callback_terminate; + checkpoint->sigrenderer->callbacks->xm_speed_zero = &dumb_it_callback_terminate; + sigdata->checkpoint = checkpoint; + + for (;;) { + long l; + DUMB_IT_SIGRENDERER *sigrenderer = dup_sigrenderer(checkpoint->sigrenderer, 0, checkpoint->sigrenderer->callbacks); + checkpoint->sigrenderer->callbacks = NULL; + if (!sigrenderer) { + checkpoint->next = NULL; + return checkpoint->time; + } + + l = it_sigrenderer_get_samples(sigrenderer, 0, 1.0f, IT_CHECKPOINT_INTERVAL, NULL); + if (l < IT_CHECKPOINT_INTERVAL) { + _dumb_it_end_sigrenderer(sigrenderer); + checkpoint->next = NULL; + return checkpoint->time + l; + } + + checkpoint->next = malloc(sizeof(*checkpoint->next)); + if (!checkpoint->next) { + _dumb_it_end_sigrenderer(sigrenderer); + return checkpoint->time + IT_CHECKPOINT_INTERVAL; + } + + checkpoint->next->time = checkpoint->time + IT_CHECKPOINT_INTERVAL; + checkpoint = checkpoint->next; + checkpoint->sigrenderer = sigrenderer; + } +} diff --git a/apps/codecs/dumb/src/it/itunload.c b/apps/codecs/dumb/src/it/itunload.c new file mode 100644 index 0000000000..8f282bea2e --- /dev/null +++ b/apps/codecs/dumb/src/it/itunload.c @@ -0,0 +1,71 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * itunload.c - Code to free an Impulse Tracker / / \ \ + * module from memory. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include + +#include "dumb.h" +#include "internal/it.h" + + + +void _dumb_it_unload_sigdata(sigdata_t *vsigdata) +{ + if (vsigdata) { + DUMB_IT_SIGDATA *sigdata = vsigdata; + int n; + + if (sigdata->order) + free(sigdata->order); + + if (sigdata->instrument) + free(sigdata->instrument); + + if (sigdata->sample) { + for (n = 0; n < sigdata->n_samples; n++) { + if (sigdata->sample[n].left) + free(sigdata->sample[n].left); + if (sigdata->sample[n].right) + free(sigdata->sample[n].right); + } + free(sigdata->sample); + } + + if (sigdata->pattern) { + for (n = 0; n < sigdata->n_patterns; n++) + if (sigdata->pattern[n].entry) + free(sigdata->pattern[n].entry); + free(sigdata->pattern); + } + + if (sigdata->midi) + free(sigdata->midi); + + { + IT_CHECKPOINT *checkpoint = sigdata->checkpoint; + while (checkpoint) { + IT_CHECKPOINT *next = checkpoint->next; + _dumb_it_end_sigrenderer(checkpoint->sigrenderer); + free(checkpoint); + checkpoint = next; + } + } + + free(vsigdata); + } +} diff --git a/apps/codecs/dumb/src/it/loadmod.c b/apps/codecs/dumb/src/it/loadmod.c new file mode 100644 index 0000000000..f7bbcb50e6 --- /dev/null +++ b/apps/codecs/dumb/src/it/loadmod.c @@ -0,0 +1,42 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * loadmod.c - Code to read a good old-fashioned / / \ \ + * Amiga module file, opening and | < / \_ + * closing it for you. | \/ /\ / + * \_ / > / + * By entheh. | \ / / + * | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/it.h" + + + +/* dumb_load_mod(): loads a MOD file into a DUH struct, returning a pointer + * to the DUH struct. When you have finished with it, you must pass the + * pointer to unload_duh() so that the memory can be freed. + */ +DUH *dumb_load_mod(const char *filename) +{ + DUH *duh; + DUMBFILE *f = dumbfile_open(filename); + + if (!f) + return NULL; + + duh = dumb_read_mod(f); + + dumbfile_close(f); + + return duh; +} diff --git a/apps/codecs/dumb/src/it/loads3m.c b/apps/codecs/dumb/src/it/loads3m.c new file mode 100644 index 0000000000..dfe6a9268e --- /dev/null +++ b/apps/codecs/dumb/src/it/loads3m.c @@ -0,0 +1,42 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * loads3m.c - Code to read a ScreamTracker 3 / / \ \ + * file, opening and closing it for | < / \_ + * you. | \/ /\ / + * \_ / > / + * By entheh. | \ / / + * | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/it.h" + + + +/* dumb_load_s3m(): loads an S3M file into a DUH struct, returning a pointer + * to the DUH struct. When you have finished with it, you must pass the + * pointer to unload_duh() so that the memory can be freed. + */ +DUH *dumb_load_s3m(const char *filename) +{ + DUH *duh; + DUMBFILE *f = dumbfile_open(filename); + + if (!f) + return NULL; + + duh = dumb_read_s3m(f); + + dumbfile_close(f); + + return duh; +} diff --git a/apps/codecs/dumb/src/it/loadxm.c b/apps/codecs/dumb/src/it/loadxm.c new file mode 100644 index 0000000000..9cee06d6ee --- /dev/null +++ b/apps/codecs/dumb/src/it/loadxm.c @@ -0,0 +1,42 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * loadxm.c - Code to read a Fast Tracker II / / \ \ + * file, opening and closing it for | < / \_ + * you. | \/ /\ / + * \_ / > / + * By entheh. | \ / / + * | ' / + * \__/ + */ + +#include "dumb.h" +#include "internal/it.h" + + + +/* dumb_load_xm(): loads an XM file into a DUH struct, returning a pointer + * to the DUH struct. When you have finished with it, you must pass the + * pointer to unload_duh() so that the memory can be freed. + */ +DUH *dumb_load_xm(const char *filename) +{ + DUH *duh; + DUMBFILE *f = dumbfile_open(filename); + + if (!f) + return NULL; + + duh = dumb_read_xm(f); + + dumbfile_close(f); + + return duh; +} diff --git a/apps/codecs/dumb/src/it/readmod.c b/apps/codecs/dumb/src/it/readmod.c new file mode 100644 index 0000000000..452c8900a4 --- /dev/null +++ b/apps/codecs/dumb/src/it/readmod.c @@ -0,0 +1,594 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * readmod.c - Code to read a good old-fashioned / / \ \ + * Amiga module from an open file. | < / \_ + * | \/ /\ / + * By Ben Davis. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +#include +#include +#include + +#include "dumb.h" +#include "internal/it.h" + + + +static int it_mod_read_pattern(IT_PATTERN *pattern, DUMBFILE *f, int n_channels, unsigned char *buffer) +{ + int pos; + int channel; + int row; + IT_ENTRY *entry; + + pattern->n_rows = 64; + + if (n_channels == 0) { + /* Read the first four channels, leaving gaps for the rest. */ + for (pos = 0; pos < 64*8*4; pos += 8*4) + dumbfile_getnc(buffer + pos, 4*4, f); + /* Read the other channels into the gaps we left. */ + for (pos = 4*4; pos < 64*8*4; pos += 8*4) + dumbfile_getnc(buffer + pos, 4*4, f); + + n_channels = 8; + } else + dumbfile_getnc(buffer, 64 * n_channels * 4, f); + + if (dumbfile_error(f)) + return -1; + + /* compute number of entries */ + pattern->n_entries = 64; /* Account for the row end markers */ + pos = 0; + for (row = 0; row < 64; row++) { + for (channel = 0; channel < n_channels; channel++) { + if (buffer[pos+0] | buffer[pos+1] | buffer[pos+2] | buffer[pos+3]) + pattern->n_entries++; + pos += 4; + } + } + + pattern->entry = malloc(pattern->n_entries * sizeof(*pattern->entry)); + if (!pattern->entry) + return -1; + + entry = pattern->entry; + pos = 0; + for (row = 0; row < 64; row++) { + for (channel = 0; channel < n_channels; channel++) { + if (buffer[pos+0] | buffer[pos+1] | buffer[pos+2] | buffer[pos+3]) { + unsigned char sample = (buffer[pos+0] & 0xF0) | (buffer[pos+2] >> 4); + int period = ((int)(buffer[pos+0] & 0x0F) << 8) | buffer[pos+1]; + + entry->channel = channel; + entry->mask = 0; + + if (period) { + int note; + entry->mask |= IT_ENTRY_NOTE; + + /* frequency = (AMIGA_DIVISOR / 8) / (period * 2) + * C-1: period = 214 -> frequency = 16726 + * so, set C5_speed to 16726 + * and period = 214 should translate to C5 aka 60 + * halve the period, go up an octive + * + * period = 214 / pow(DUMB_SEMITONE_BASE, note - 60) + * pow(DUMB_SEMITONE_BASE, note - 60) = 214 / period + * note - 60 = log(214/period) / log(DUMB_SEMITONE_BASE) + */ + note = (int)floor(log(214.0/period) / log(DUMB_SEMITONE_BASE) + 60.5); + entry->note = MID(0, note, 119); + // or should we preserve the period? + //entry->note = buffer[pos+0] & 0x0F; /* High nibble */ + //entry->volpan = buffer[pos+1]; /* Low byte */ + // and what about finetune? + } + + if (sample) { + entry->mask |= IT_ENTRY_INSTRUMENT; + entry->instrument = sample; + } + + _dumb_it_xm_convert_effect(buffer[pos+2] & 0x0F, buffer[pos+3], entry); + + entry++; + } + pos += 4; + } + IT_SET_END_ROW(entry); + entry++; + } + + return 0; +} + + + +/* This function does not skip the name (22 bytes); it is assumed the caller + * has already done that. + */ +static int it_mod_read_sample_header(IT_SAMPLE *sample, DUMBFILE *f) +{ + int finetune; + +/** + 21 22 Chars Sample 1 name. If the name is not a full + 22 chars in length, it will be null + terminated. + +If +the sample name begins with a '#' character (ASCII $23 (35)) then this is +assumed not to be an instrument name, and is probably a message. +*/ + sample->length = dumbfile_mgetw(f) << 1; + finetune = (signed char)(dumbfile_getc(f) << 4) >> 4; /* signed nibble */ +/** Each finetune step changes the note 1/8th of a semitone. */ + sample->global_volume = 64; + sample->default_volume = dumbfile_getc(f); // Should we be setting global_volume to this instead? + sample->loop_start = dumbfile_mgetw(f) << 1; + sample->loop_end = sample->loop_start + (dumbfile_mgetw(f) << 1); +/** +Once this sample has been played completely from beginning +to end, if the repeat length (next field) is greater than two bytes it +will loop back to this position in the sample and continue playing. Once +it has played for the repeat length, it continues to loop back to the +repeat start offset. This means the sample continues playing until it is +told to stop. +*/ + + if (sample->length <= 0) { + sample->flags = 0; + return 0; + } + + sample->flags = IT_SAMPLE_EXISTS; + + sample->default_pan = 0; + sample->C5_speed = (long)(16726.0*pow(DUMB_PITCH_BASE, finetune*32)); + // the above line might be wrong + + if (sample->loop_end > sample->length) + sample->loop_end = sample->length; + + if (sample->loop_end - sample->loop_start > 2) + sample->flags |= IT_SAMPLE_LOOP; + + sample->vibrato_speed = 0; + sample->vibrato_depth = 0; + sample->vibrato_rate = 0; + sample->vibrato_waveform = 0; // do we have to set _all_ these? + + return dumbfile_error(f); +} + + + +static int it_mod_read_sample_data(IT_SAMPLE *sample, DUMBFILE *f) +{ + long i; + long truncated_size; + + /* let's get rid of the sample data coming after the end of the loop */ + if ((sample->flags & IT_SAMPLE_LOOP) && sample->loop_end < sample->length) { + truncated_size = sample->length - sample->loop_end; + sample->length = sample->loop_end; + } else { + truncated_size = 0; + } + + sample->left = malloc(sample->length * sizeof(*sample->left)); + + if (!sample->left) + return -1; + + /* Sample data are stored in "8-bit two's compliment format" (sic). */ + for (i = 0; i < sample->length; i++) + sample->left[i] = (int)(signed char)dumbfile_getc(f) << 16; + + /* skip truncated data */ + dumbfile_skip(f, truncated_size); + // Should we be truncating it? + + if (dumbfile_error(f)) + return -1; + + return 0; +} + + + +typedef struct BUFFERED_MOD BUFFERED_MOD; + +struct BUFFERED_MOD +{ + unsigned char *buffered; + long ptr, len; + DUMBFILE *remaining; +}; + + + +static int buffer_mod_skip(void *f, long n) +{ + BUFFERED_MOD *bm = f; + if (bm->buffered) { + bm->ptr += n; + if (bm->ptr >= bm->len) { + free(bm->buffered); + bm->buffered = NULL; + return dumbfile_skip(bm->remaining, bm->ptr - bm->len); + } + return 0; + } + return dumbfile_skip(bm->remaining, n); +} + + + +static int buffer_mod_getc(void *f) +{ + BUFFERED_MOD *bm = f; + if (bm->buffered) { + int rv = bm->buffered[bm->ptr++]; + if (bm->ptr >= bm->len) { + free(bm->buffered); + bm->buffered = NULL; + } + return rv; + } + return dumbfile_getc(bm->remaining); +} + + + +static long buffer_mod_getnc(char *ptr, long n, void *f) +{ + BUFFERED_MOD *bm = f; + if (bm->buffered) { + int left = bm->len - bm->ptr; + if (n >= left) { + int rv; + memcpy(ptr, bm->buffered + bm->ptr, left); + free(bm->buffered); + bm->buffered = NULL; + rv = dumbfile_getnc(ptr + left, n - left, bm->remaining); + return left + MAX(rv, 0); + } + memcpy(ptr, bm->buffered + bm->ptr, n); + bm->ptr += n; + return n; + } + return dumbfile_getnc(ptr, n, bm->remaining); +} + + + +static void buffer_mod_close(void *f) +{ + BUFFERED_MOD *bm = f; + if (bm->buffered) free(bm->buffered); + /* Do NOT close bm->remaining */ + free(f); +} + + + +DUMBFILE_SYSTEM buffer_mod_dfs = { + NULL, + &buffer_mod_skip, + &buffer_mod_getc, + &buffer_mod_getnc, + &buffer_mod_close +}; + + + +#define MOD_FFT_OFFSET (20 + 31*(22+2+1+1+2+2) + 1 + 1 + 128) + +static DUMBFILE *dumbfile_buffer_mod(DUMBFILE *f, unsigned long *fft) +{ + BUFFERED_MOD *bm = malloc(sizeof(*bm)); + if (!bm) return NULL; + + bm->buffered = malloc(MOD_FFT_OFFSET + 4); + if (!bm->buffered) { + free(bm); + return NULL; + } + + bm->len = dumbfile_getnc(bm->buffered, MOD_FFT_OFFSET + 4, f); + + if (bm->len > 0) { + if (bm->len >= MOD_FFT_OFFSET + 4) + *fft = (unsigned long)bm->buffered[MOD_FFT_OFFSET ] << 24 + | (unsigned long)bm->buffered[MOD_FFT_OFFSET+1] << 16 + | (unsigned long)bm->buffered[MOD_FFT_OFFSET+2] << 8 + | (unsigned long)bm->buffered[MOD_FFT_OFFSET+3]; + else + *fft = 0; + bm->ptr = 0; + } else { + free(bm->buffered); + bm->buffered = NULL; + } + + bm->remaining = f; + + return dumbfile_open_ex(bm, &buffer_mod_dfs); +} + + + +static DUMB_IT_SIGDATA *it_mod_load_sigdata(DUMBFILE *f) +{ + DUMB_IT_SIGDATA *sigdata; + int n_channels; + int i; + unsigned long fft; + + f = dumbfile_buffer_mod(f, &fft); + if (!f) + return NULL; + + /** + 1 20 Chars Title of the song. If the title is not a + full 20 chars in length, it will be null- + terminated. + */ + if (dumbfile_skip(f, 20)) { + dumbfile_close(f); + return NULL; + } + + sigdata = malloc(sizeof(*sigdata)); + if (!sigdata) { + dumbfile_close(f); + return NULL; + } + + sigdata->n_samples = 31; + + switch (fft) { + case DUMB_ID('M','.','K','.'): + case DUMB_ID('M','!','K','!'): + case DUMB_ID('M','&','K','!'): + case DUMB_ID('N','.','T','.'): + case DUMB_ID('F','L','T','4'): + n_channels = 4; + break; + case DUMB_ID('F','L','T','8'): + n_channels = 0; + /* 0 indicates a special case; two four-channel patterns must be + * combined into one eight-channel pattern. Pattern indexes must + * be halved. Why oh why do they obfuscate so? + */ + for (i = 0; i < 128; i++) + sigdata->order[i] >>= 1; + break; + case DUMB_ID('C','D','8','1'): + case DUMB_ID('O','C','T','A'): + case DUMB_ID('O','K','T','A'): + n_channels = 8; + break; + case DUMB_ID('1','6','C','N'): + n_channels = 16; + break; + case DUMB_ID('3','2','C','N'): + n_channels = 32; + break; + default: + /* If we get an illegal tag, assume 4 channels 15 samples. */ + if ((fft & 0x0000FFFFL) == DUMB_ID(0,0,'C','H')) { + if (fft >= '1' << 24 && fft < '4' << 24) { + n_channels = ((fft & 0x00FF0000L) >> 16) - '0'; + if ((unsigned int)n_channels >= 10) { + /* Rightmost character wasn't a digit. */ + n_channels = 4; + sigdata->n_samples = 15; + } else { + n_channels += (((fft & 0xFF000000L) >> 24) - '0') * 10; + /* MODs should really only go up to 32 channels, but we're lenient. */ + if ((unsigned int)(n_channels - 1) >= DUMB_IT_N_CHANNELS - 1) { + /* No channels or too many? Can't be right... */ + n_channels = 4; + sigdata->n_samples = 15; + } + } + } else { + n_channels = 4; + sigdata->n_samples = 15; + } + } else if ((fft & 0x00FFFFFFL) == DUMB_ID(0,'C','H','N')) { + n_channels = (fft >> 24) - '0'; + if ((unsigned int)(n_channels - 1) >= 9) { + /* Character was '0' or it wasn't a digit */ + n_channels = 4; + sigdata->n_samples = 15; + } + } else if ((fft & 0xFFFFFF00L) == DUMB_ID('T','D','Z',0)) { + n_channels = (fft & 0x000000FFL) - '0'; + if ((unsigned int)(n_channels - 1) >= 9) { + /* We've been very lenient, given that it should have + * been 1, 2 or 3, but this MOD has been very naughty and + * must be punished. + */ + n_channels = 4; + sigdata->n_samples = 15; + } + } else { + n_channels = 4; + sigdata->n_samples = 15; + } + } + + sigdata->sample = malloc(sigdata->n_samples * sizeof(*sigdata->sample)); + if (!sigdata->sample) { + free(sigdata); + dumbfile_close(f); + return NULL; + } + + sigdata->order = NULL; + sigdata->instrument = NULL; + sigdata->pattern = NULL; + sigdata->midi = NULL; + sigdata->checkpoint = NULL; + + for (i = 0; i < sigdata->n_samples; i++) + sigdata->sample[i].right = sigdata->sample[i].left = NULL; + + for (i = 0; i < sigdata->n_samples; i++) { + if (dumbfile_skip(f, 22) || + it_mod_read_sample_header(&sigdata->sample[i], f)) + { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + } + + sigdata->n_orders = dumbfile_getc(f); + sigdata->restart_position = dumbfile_getc(f); + // what if this is >= 127? what about with Fast Tracker II? + + if (sigdata->n_orders <= 0 || sigdata->n_orders > 128) { // is this right? + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + + //if (sigdata->restart_position >= sigdata->n_orders) + //sigdata->restart_position = 0; + + sigdata->order = malloc(128); /* We may need to scan the extra ones! */ + if (!sigdata->order) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + if (dumbfile_getnc(sigdata->order, 128, f) < 128) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + + /* "The old NST format contains only 15 samples (instead of 31). Further + * it doesn't contain a file format tag (id). So Pattern data offset is + * at 20+15*30+1+1+128." + * - Then I shall assume the File Format Tag never exists if there are + * only 15 samples. I hope this isn't a faulty assumption... + */ + if (sigdata->n_samples == 31) + dumbfile_skip(f, 4); + + /* Work out how many patterns there are. */ + sigdata->n_patterns = -1; + for (i = 0; i < 128; i++) + if (sigdata->n_patterns < sigdata->order[i]) + sigdata->n_patterns = sigdata->order[i]; + sigdata->n_patterns++; + + /* May as well try to save a tiny bit of memory. */ + if (sigdata->n_orders < 128) { + unsigned char *order = realloc(sigdata->order, sigdata->n_orders); + if (order) sigdata->order = order; + } + + sigdata->pattern = malloc(sigdata->n_patterns * sizeof(*sigdata->pattern)); + if (!sigdata->pattern) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + for (i = 0; i < sigdata->n_patterns; i++) + sigdata->pattern[i].entry = NULL; + + /* Read in the patterns */ + { + unsigned char *buffer = malloc(256 * n_channels); /* 64 rows * 4 bytes */ + if (!buffer) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + for (i = 0; i < sigdata->n_patterns; i++) { + if (it_mod_read_pattern(&sigdata->pattern[i], f, n_channels, buffer) != 0) { + free(buffer); + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + } + free(buffer); + } + + /* And finally, the sample data */ + for (i = 0; i < sigdata->n_samples; i++) { + if (it_mod_read_sample_data(&sigdata->sample[i], f)) { + _dumb_it_unload_sigdata(sigdata); + dumbfile_close(f); + return NULL; + } + } + + dumbfile_close(f); /* Destroy the BUFFERED_MOD DUMBFILE we were using. */ + /* The DUMBFILE originally passed to our function is intact. */ + + /* Now let's initialise the remaining variables, and we're done! */ + sigdata->flags = IT_WAS_AN_XM | IT_WAS_A_MOD | IT_OLD_EFFECTS | IT_COMPATIBLE_GXX | IT_STEREO; + + sigdata->global_volume = 128; + sigdata->mixing_volume = 48; + /* We want 50 ticks per second; 50/6 row advances per second; + * 50*10=500 row advances per minute; 500/4=125 beats per minute. + */ + sigdata->speed = 6; + sigdata->tempo = 125; + sigdata->pan_separation = 128; + + memset(sigdata->channel_volume, 64, DUMB_IT_N_CHANNELS); + + for (i = 0; i < DUMB_IT_N_CHANNELS; i += 4) { + sigdata->channel_pan[i+0] = 16; + sigdata->channel_pan[i+1] = 48; + sigdata->channel_pan[i+2] = 48; + sigdata->channel_pan[i+3] = 16; + } + + _dumb_it_fix_invalid_orders(sigdata); + + return sigdata; +} + + + +DUH *dumb_read_mod(DUMBFILE *f) +{ + sigdata_t *sigdata; + long length; + + DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it; + + sigdata = it_mod_load_sigdata(f); + + if (!sigdata) + return NULL; + + length = _dumb_it_build_checkpoints(sigdata); + + return make_duh(length, 1, &descptr, &sigdata); +} diff --git a/apps/codecs/dumb/src/it/reads3m.c b/apps/codecs/dumb/src/it/reads3m.c new file mode 100644 index 0000000000..6a8b84dd55 --- /dev/null +++ b/apps/codecs/dumb/src/it/reads3m.c @@ -0,0 +1,668 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * reads3m.c - Code to read a ScreamTracker 3 / / \ \ + * module from an open file. | < / \_ + * | \/ /\ / + * By entheh. \_ / > / + * | \ / / + * | ' / + * \__/ + */ + +// IT_STEREO... :o +#include +#include + +#include "dumb.h" +#include "internal/it.h" + + + +/** WARNING: this is duplicated in itread.c */ +static int it_seek(DUMBFILE *f, long offset) +{ + long pos = dumbfile_pos(f); + + if (pos > offset) + return -1; + + if (pos < offset) + if (dumbfile_skip(f, offset - pos)) + return -1; + + return 0; +} + + + +static int it_s3m_read_sample_header(IT_SAMPLE *sample, long *offset, DUMBFILE *f) +{ + unsigned char type; + int flags; + + type = dumbfile_getc(f); + + if (type > 1) { + /** WARNING: no adlib support */ + } + + /* Skip DOS Filename */ + dumbfile_skip(f, 13); + + *offset = dumbfile_igetw(f) << 4; + + sample->length = dumbfile_igetl(f); + sample->loop_start = dumbfile_igetl(f); + sample->loop_end = dumbfile_igetl(f); + + sample->default_volume = dumbfile_getc(f); + + dumbfile_skip(f, 1); + + if (dumbfile_getc(f) != 0) + /* Sample is packed apparently (or error reading from file). We don't + * know how to read packed samples. + */ + return -1; + + flags = dumbfile_getc(f); + + sample->C5_speed = dumbfile_igetl(f) << 1; + + /* Skip four unused bytes, three internal variables and sample name. */ + dumbfile_skip(f, 4+2+2+4+28); + + if (type == 0) { + /* Looks like no-existy. Anyway, there's for sure no 'SCRS'... */ + sample->flags &= ~IT_SAMPLE_EXISTS; + return dumbfile_error(f); + } + + if (dumbfile_mgetl(f) != DUMB_ID('S','C','R','S')) + return -1; + + sample->global_volume = 64; + + sample->flags = IT_SAMPLE_EXISTS; + if (flags & 1) sample->flags |= IT_SAMPLE_LOOP; + if (flags & 2) sample->flags |= IT_SAMPLE_STEREO; + if (flags & 4) sample->flags |= IT_SAMPLE_16BIT; + + sample->default_pan = 0; // 0 = don't use, or 160 = centre? + + if (sample->length <= 0) + sample->flags &= ~IT_SAMPLE_EXISTS; + else if (sample->flags & IT_SAMPLE_LOOP) { + if ((unsigned int)sample->loop_end > (unsigned int)sample->length) + sample->flags &= ~IT_SAMPLE_LOOP; + else if ((unsigned int)sample->loop_start >= (unsigned int)sample->loop_end) + sample->flags &= ~IT_SAMPLE_LOOP; + else + /* ScreamTracker seems not to save what comes after the loop end + * point, but rather to assume it is a duplicate of what comes at + * the loop start point. I am not completely sure of this though. + * It is easy to evade; simply truncate the sample. + */ + sample->length = sample->loop_end; + } + + + //Do we need to set all these? + sample->vibrato_speed = 0; + sample->vibrato_depth = 0; + sample->vibrato_rate = 0; + sample->vibrato_waveform = IT_VIBRATO_SINE; + + return dumbfile_error(f); +} + + + +static int it_s3m_read_sample_data(IT_SAMPLE *sample, int ffi, DUMBFILE *f) +{ + long n; + + sample->left = malloc(sample->length * sizeof(*sample->left)); + if (!sample->left) + return -1; + + if (sample->flags & IT_SAMPLE_STEREO) { + sample->right = malloc(sample->length * sizeof(*sample->right)); + if (!sample->right) + return -1; + } + + if (sample->flags & IT_SAMPLE_STEREO) { + if (sample->flags & IT_SAMPLE_16BIT) { + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed short)dumbfile_igetw(f) << 8; + for (n = 0; n < sample->length; n++) + sample->right[n] = (int)(signed short)dumbfile_igetw(f) << 8; + } else { + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed char)dumbfile_getc(f) << 16; + for (n = 0; n < sample->length; n++) + sample->right[n] = (int)(signed char)dumbfile_getc(f) << 16; + } + } else if (sample->flags & IT_SAMPLE_16BIT) + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed short)dumbfile_igetw(f) << 8; + else + for (n = 0; n < sample->length; n++) + sample->left[n] = (int)(signed char)dumbfile_getc(f) << 16; + + if (dumbfile_error(f)) + return -1; + + if (ffi != 1) { + /* Convert to signed. */ + for (n = 0; n < sample->length; n++) + sample->left[n] ^= 0xFF800000; + + if (sample->right) + for (n = 0; n < sample->length; n++) + sample->right[n] ^= 0xFF800000; + } + + return 0; +} + + + +static int it_s3m_read_pattern(IT_PATTERN *pattern, DUMBFILE *f, unsigned char *buffer) +{ + int buflen = 0; + int bufpos = 0; + + IT_ENTRY *entry; + + unsigned char channel; + + /* Haha, this is hilarious! + * + * Well, after some experimentation, it seems that different S3M writers + * define the format in different ways. The S3M docs say that the first + * two bytes hold the "length of [the] packed pattern", and the packed + * pattern data follow. Judging by the contents of ARMANI.S3M, packaged + * with ScreamTracker itself, the measure of length _includes_ the two + * bytes used to store the length; in other words, we should read + * (length - 2) more bytes. However, aryx.s3m, packaged with ModPlug + * Tracker, excludes these two bytes, so (length) more bytes must be + * read. + * + * Call me crazy, but I just find it insanely funny that the format was + * misunderstood in this way :D + * + * Now we can't just risk reading two extra bytes, because then we + * overshoot, and DUMBFILEs don't support backward seeking (for a good + * reason). Luckily, there is a way. We can read the data little by + * little, and stop when we have 64 rows in memory. Provided we protect + * against buffer overflow, this method should work with all sensibly + * written S3M files. If you find one for which it does not work, please + * let me know at entheh@users.sf.net so I can look at it. + */ + + /* Discard the length. */ + dumbfile_skip(f, 2); + + if (dumbfile_error(f)) + return -1; + + pattern->n_rows = 0; + pattern->n_entries = 0; + + /* Read in the pattern data, little by little, and work out how many + * entries we need room for. Sorry, but this is just so funny... + */ + for (;;) { + unsigned char b = buffer[buflen++] = dumbfile_getc(f); + +#if 1 + static const unsigned char used[8] = {0, 2, 1, 3, 2, 4, 3, 5}; + channel = b & 31; + b >>= 5; + pattern->n_entries++; + if (b) { + if (buflen + used[b] >= 65536) return -1; + dumbfile_getnc(buffer + buflen, used[b], f); + buflen += used[b]; + } else { + /* End of row */ + if (++pattern->n_rows == 64) break; + if (buflen >= 65536) return -1; + } +#else + if (b == 0) { + /* End of row */ + pattern->n_entries++; + if (++pattern->n_rows == 64) break; + if (buflen >= 65536) return -1; + } else { + static const unsigned char used[8] = {0, 2, 1, 3, 2, 4, 3, 5}; + channel = b & 31; + b >>= 5; + if (b) { + pattern->n_entries++; + if (buflen + used[b] >= 65536) return -1; + dumbfile_getnc(buffer + buflen, used[b], f); + buflen += used[b]; + } + } +#endif + + /* We have ensured that buflen < 65536 at this point, so it is safe + * to iterate and read at least one more byte without checking. + * However, now would be a good time to check for errors reading from + * the file. + */ + + if (dumbfile_error(f)) + return -1; + } + + pattern->entry = malloc(pattern->n_entries * sizeof(*pattern->entry)); + + if (!pattern->entry) + return -1; + + entry = pattern->entry; + + while (bufpos < buflen) { + unsigned char b = buffer[bufpos++]; + +#if 1 + if (!(b & ~31)) +#else + if (b == 0) +#endif + { + /* End of row */ + IT_SET_END_ROW(entry); + entry++; + continue; + } + + channel = b & 31; + + if (b & 224) { + entry->mask = 0; + entry->channel = channel; + + if (b & 32) { + unsigned char n = buffer[bufpos++]; + if (n != 255) { + if (n == 254) + entry->note = IT_NOTE_CUT; + else + entry->note = (n >> 4) * 12 + (n & 15); + entry->mask |= IT_ENTRY_NOTE; + } + + entry->instrument = buffer[bufpos++]; + if (entry->instrument) + entry->mask |= IT_ENTRY_INSTRUMENT; + } + + if (b & 64) { + entry->volpan = buffer[bufpos++]; + if (entry->volpan != 255) + entry->mask |= IT_ENTRY_VOLPAN; + } + + if (b & 128) { + entry->effect = buffer[bufpos++]; + entry->effectvalue = buffer[bufpos++]; + if (entry->effect != 255) { + entry->mask |= IT_ENTRY_EFFECT; + if (entry->effect == IT_BREAK_TO_ROW) + entry->effectvalue -= (entry->effectvalue >> 4) * 6; + } + /** WARNING: ARGH! CONVERT TEH EFFECTS!@~ */ + } + + entry++; + } + } + + ASSERT(entry == pattern->entry + pattern->n_entries); + + return 0; +} + + + +/** WARNING: this is duplicated in itread.c - also bad practice to use the same struct name unless they are unified in a header */ +/* Currently we assume the sample data are stored after the sample headers in + * module files. This assumption may be unjustified; let me know if you have + * trouble. + */ + +#define IT_COMPONENT_INSTRUMENT 1 +#define IT_COMPONENT_PATTERN 2 +#define IT_COMPONENT_SAMPLE 3 + +typedef struct IT_COMPONENT +{ + unsigned char type; + unsigned char n; + long offset; + short sampfirst; /* component[sampfirst] = first sample data after this */ + short sampnext; /* sampnext is used to create linked lists of sample data */ +} +IT_COMPONENT; + + + +static int it_component_compare(const void *e1, const void *e2) +{ + return ((const IT_COMPONENT *)e1)->offset - + ((const IT_COMPONENT *)e2)->offset; +} + + + +static DUMB_IT_SIGDATA *it_s3m_load_sigdata(DUMBFILE *f) +{ + DUMB_IT_SIGDATA *sigdata; + + int flags, cwtv, ffi; + int default_pan_present; + + IT_COMPONENT *component; + int n_components = 0; + + int n; + + unsigned char *buffer; + + /* Skip song name. */ + if (dumbfile_skip(f, 28)) return NULL; + + if (dumbfile_getc(f) != 0x1A) return NULL; + if (dumbfile_getc(f) != 16) return NULL; + + if (dumbfile_skip(f, 2)) return NULL; + + sigdata = malloc(sizeof(*sigdata)); + + if (!sigdata) + return NULL; + + sigdata->order = NULL; + sigdata->instrument = NULL; + sigdata->sample = NULL; + sigdata->pattern = NULL; + sigdata->midi = NULL; + sigdata->checkpoint = NULL; + + sigdata->n_orders = dumbfile_igetw(f); + sigdata->n_instruments = 0; + sigdata->n_samples = dumbfile_igetw(f); + sigdata->n_patterns = dumbfile_igetw(f); + + if (dumbfile_error(f) || sigdata->n_orders <= 0 || sigdata->n_samples > 256 || sigdata->n_patterns > 256) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + sigdata->order = malloc(sigdata->n_orders); + if (!sigdata->order) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (sigdata->n_samples) { + sigdata->sample = malloc(sigdata->n_samples * sizeof(*sigdata->sample)); + if (!sigdata->sample) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (n = 0; n < sigdata->n_samples; n++) + sigdata->sample[n].right = sigdata->sample[n].left = NULL; + } + + if (sigdata->n_patterns) { + sigdata->pattern = malloc(sigdata->n_patterns * sizeof(*sigdata->pattern)); + if (!sigdata->pattern) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (n = 0; n < sigdata->n_patterns; n++) + sigdata->pattern[n].entry = NULL; + } + + flags = dumbfile_igetw(f); + + cwtv = dumbfile_igetw(f); + + if (cwtv == 0x1300) { + /** WARNING: volume slides on every frame */ + } + + ffi = dumbfile_igetw(f); + + /** WARNING: which ones? */ + sigdata->flags = IT_STEREO | IT_OLD_EFFECTS | IT_COMPATIBLE_GXX; + + if (dumbfile_mgetl(f) != DUMB_ID('S','C','R','M')) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + sigdata->global_volume = dumbfile_getc(f) << 1; + sigdata->speed = dumbfile_getc(f); + if (sigdata->speed == 0) sigdata->speed = 6; // Should we? What about tempo? + sigdata->tempo = dumbfile_getc(f); + /*master_volume = */dumbfile_getc(f); // 7 bits; +128 for stereo + //what do we do with master_volume? it's not the same as mixing volume... + sigdata->mixing_volume = 48; + + /* Skip GUS Ultra Click Removal byte. */ + dumbfile_getc(f); + + default_pan_present = dumbfile_getc(f); + + dumbfile_skip(f, 8); + + /* Skip Special Custom Data Pointer. */ + /** WARNING: investigate this? */ + dumbfile_igetw(f); + + /* Channel settings for 32 channels, 255=unused, +128=disabled */ + { + int i; + for (i = 0; i < 32; i++) { + int c = dumbfile_getc(f); + if (!(c & (128 | 16))) { /* +128=disabled, +16=Adlib */ + sigdata->channel_volume[i] = 64; + sigdata->channel_pan[i] = c & 8 ? 12 : 3; + /** WARNING: ah, but it should be 7 for mono... */ + } else { + /** WARNING: this could be improved if we support channel muting... */ + sigdata->channel_volume[i] = 0; + sigdata->channel_pan[i] = 7; + } + } + } + + /* Orders, byte each, length = sigdata->n_orders (should be even) */ + dumbfile_getnc(sigdata->order, sigdata->n_orders, f); + sigdata->restart_position = 0; + + component = malloc(768*sizeof(*component)); + if (!component) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + for (n = 0; n < sigdata->n_samples; n++) { + component[n_components].type = IT_COMPONENT_SAMPLE; + component[n_components].n = n; + component[n_components].offset = dumbfile_igetw(f) << 4; + component[n_components].sampfirst = -1; + n_components++; + } + + for (n = 0; n < sigdata->n_patterns; n++) { + long offset = dumbfile_igetw(f) << 4; + if (offset) { + component[n_components].type = IT_COMPONENT_PATTERN; + component[n_components].n = n; + component[n_components].offset = offset; + component[n_components].sampfirst = -1; + n_components++; + } else { + /** WARNING: Empty 64-row pattern ... ? (this does happen!) */ + sigdata->pattern[n].n_rows = 64; + sigdata->pattern[n].n_entries = 0; + } + } + + qsort(component, n_components, sizeof(IT_COMPONENT), &it_component_compare); + + /* I found a really dumb S3M file that claimed to contain default pan + * data but didn't contain any. Programs would load it by reading part of + * the first instrument header, assuming the data to be default pan + * positions, and then rereading the instrument module. We cannot do this + * without obfuscating the file input model, so we insert an extra check + * here that we won't overrun the start of the first component. + */ + if (default_pan_present == 252 && component[0].offset >= dumbfile_pos(f) + 32) { + /* Channel default pan positions */ + int i; + for (i = 0; i < 32; i++) { + int c = dumbfile_getc(f); + if (c & 32) + sigdata->channel_pan[i] = c & 15; + } + } + + { + int i; + for (i = 0; i < 32; i++) { + sigdata->channel_pan[i] -= (sigdata->channel_pan[i] & 8) >> 3; + sigdata->channel_pan[i] = ((int)sigdata->channel_pan[i] << 5) / 7; + } + } + + /** WARNING: is this right? */ + sigdata->pan_separation = 64; + + if (dumbfile_error(f)) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + buffer = malloc(65536); + if (!buffer) { + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + for (n = 0; n < n_components; n++) { + long offset; + int m; + + if (it_seek(f, component[n].offset)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + switch (component[n].type) { + + case IT_COMPONENT_PATTERN: + if (it_s3m_read_pattern(&sigdata->pattern[component[n].n], f, buffer)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + break; + + case IT_COMPONENT_SAMPLE: + if (it_s3m_read_sample_header(&sigdata->sample[component[n].n], &offset, f)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (sigdata->sample[component[n].n].flags & IT_SAMPLE_EXISTS) { + short *sample; + + for (m = n + 1; m < n_components; m++) + if (component[m].offset > offset) + break; + m--; + + sample = &component[m].sampfirst; + + while (*sample >= 0 && component[*sample].offset <= offset) + sample = &component[*sample].sampnext; + + component[n].sampnext = *sample; + *sample = n; + + component[n].offset = offset; + } + } + + m = component[n].sampfirst; + + while (m >= 0) { + if (it_seek(f, component[m].offset)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (it_s3m_read_sample_data(&sigdata->sample[component[m].n], ffi, f)) { + free(buffer); + free(component); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + m = component[m].sampnext; + } + } + + free(buffer); + free(component); + + _dumb_it_fix_invalid_orders(sigdata); + + return sigdata; +} + + + +DUH *dumb_read_s3m(DUMBFILE *f) +{ + sigdata_t *sigdata; + long length; + + DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it; + + sigdata = it_s3m_load_sigdata(f); + + if (!sigdata) + return NULL; + + length = _dumb_it_build_checkpoints(sigdata); + + return make_duh(length, 1, &descptr, &sigdata); +} diff --git a/apps/codecs/dumb/src/it/readxm.c b/apps/codecs/dumb/src/it/readxm.c new file mode 100644 index 0000000000..2aeda1fa67 --- /dev/null +++ b/apps/codecs/dumb/src/it/readxm.c @@ -0,0 +1,998 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * readxm.c - Code to read a Fast Tracker II / / \ \ + * module from an open file. | < / \_ + * | \/ /\ / + * By Julien Cugniere. Some bits of code taken \_ / > / + * from reads3m.c. | \ / / + * | ' / + * \__/ + */ + +#include +#include +#include + +#include "dumb.h" +#include "internal/it.h" + + + +/** TODO: + + * XM_TREMOLO doesn't sound quite right... + * XM_E_SET_FINETUNE todo. + * XM_SET_ENVELOPE_POSITION todo. + + * VIBRATO conversion needs to be checked (sample/effect/volume). Plus check + that effect memory is correct when using XM_VOLSLIDE_VIBRATO. + - sample vibrato (instrument vibrato) is now handled correctly. - entheh + + * XM_E_SET_VIBRATO/TREMOLO_CONTROL: effectvalue&4 -> don't retrig wave when + a new instrument is played. In retrigger_note()?. Is it worth implementing? + + * Lossy fadeout approximation. 0..31 converted to 0 --> won't fade at all. + + * Replace DUMB's sawtooth by ramp_down/ramp_up. Update XM loader. + + * A lot of things need to be reset when the end of the song is reached. + + * It seems that IT and XM don't behave the same way when dealing with + mixed loops. When IT encounters multiple SBx (x>0) commands on the same + row, it decrements the loop count for all, but only execute the loop of + the last one (highest channel). FT2 only decrements the loop count of the + last one. Not that I know of any modules using so convoluted combinations! + + * Maybe we could remove patterns that don't appear in the order table ? Or + provide a function to "optimize" a DUMB_IT_SIGDATA ? + +*/ + + + +#define XM_LINEAR_FREQUENCY 1 /* otherwise, use amiga slides */ + +#define XM_ENTRY_PACKED 128 +#define XM_ENTRY_NOTE 1 +#define XM_ENTRY_INSTRUMENT 2 +#define XM_ENTRY_VOLUME 4 +#define XM_ENTRY_EFFECT 8 +#define XM_ENTRY_EFFECTVALUE 16 + +#define XM_NOTE_OFF 97 + +#define XM_ENVELOPE_ON 1 +#define XM_ENVELOPE_SUSTAIN 2 +#define XM_ENVELOPE_LOOP 4 + +#define XM_SAMPLE_NO_LOOP 0 +#define XM_SAMPLE_FORWARD_LOOP 1 +#define XM_SAMPLE_PINGPONG_LOOP 2 +#define XM_SAMPLE_16BIT 16 +#define XM_SAMPLE_STEREO 32 + +#define XM_VIBRATO_SINE 0 +#define XM_VIBRATO_SQUARE 1 +#define XM_VIBRATO_RAMP_DOWN 2 +#define XM_VIBRATO_RAMP_UP 3 + + + +/* Probably useless :) */ +static const char xm_convert_vibrato[] = { + IT_VIBRATO_SINE, + IT_VIBRATO_SQUARE, + IT_VIBRATO_SAWTOOTH, + IT_VIBRATO_SAWTOOTH +}; + + + +#define XM_MAX_SAMPLES_PER_INSTRUMENT 16 + + + +/* Extra data that doesn't fit inside IT_INSTRUMENT */ +typedef struct XM_INSTRUMENT_EXTRA +{ + int n_samples; + int vibrato_type; + int vibrato_sweep; /* 0-0xFF */ + int vibrato_depth; /* 0-0x0F */ + int vibrato_speed; /* 0-0x3F */ +} +XM_INSTRUMENT_EXTRA; + + + +/* Frees the original block if it can't resize it or if size is 0, and acts + * as malloc if ptr is NULL. + */ +static void *safe_realloc(void *ptr, size_t size) +{ + if (ptr == NULL) + return malloc(size); + + if (size == 0) { + free(ptr); + return NULL; + } else { + void *new_block = realloc(ptr, size); + if (!new_block) + free(ptr); + return new_block; + } +} + + + +/* The interpretation of the XM volume column is left to the player. Here, we + * just filter bad values. + */ +// This function is so tiny now, should we inline it? +static void it_xm_convert_volume(int volume, IT_ENTRY *entry) +{ + entry->mask |= IT_ENTRY_VOLPAN; + entry->volpan = volume; + + switch (volume >> 4) { + case 0xA: /* set vibrato speed */ + case 0xB: /* vibrato */ + case 0xF: /* tone porta */ + case 0x6: /* vol slide up */ + case 0x7: /* vol slide down */ + case 0x8: /* fine vol slide up */ + case 0x9: /* fine vol slide down */ + case 0xC: /* set panning */ + case 0xD: /* pan slide left */ + case 0xE: /* pan slide right */ + case 0x1: /* set volume */ + case 0x2: /* set volume */ + case 0x3: /* set volume */ + case 0x4: /* set volume */ + break; + + case 0x5: + if (volume == 0x50) + break; /* set volume */ + /* else fall through */ + + default: + entry->mask &= ~IT_ENTRY_VOLPAN; + break; + } +} + + + +static int it_xm_read_pattern(IT_PATTERN *pattern, DUMBFILE *f, int n_channels, unsigned char *buffer) +{ + int size; + int pos; + int channel; + int row; + int effect, effectvalue; + IT_ENTRY *entry; + + /* pattern header size */ + if (dumbfile_igetl(f) != 0x09) { + TRACE("XM error: unexpected pattern header size\n"); + return -1; + } + + /* pattern data packing type */ + if (dumbfile_getc(f) != 0) { + TRACE("XM error: unexpected pattern packing type\n"); + return -1; + } + + pattern->n_rows = dumbfile_igetw(f); /* 1..256 */ + size = dumbfile_igetw(f); + pattern->n_entries = 0; + + if (dumbfile_error(f)) + return -1; + + if (size == 0) + return 0; + + if (size > 1280 * n_channels) { + TRACE("XM error: pattern data size > %d bytes\n", 1280 * n_channels); + return -1; + } + + if (dumbfile_getnc(buffer, size, f) < size) + return -1; + + /* compute number of entries */ + pattern->n_entries = 0; + pos = channel = row = 0; + while (pos < size) { + if (!(buffer[pos] & XM_ENTRY_PACKED) || (buffer[pos] & 31)) + pattern->n_entries++; + + channel++; + if (channel >= n_channels) { + channel = 0; + row++; + pattern->n_entries++; + } + + if (buffer[pos] & XM_ENTRY_PACKED) { + static const char offset[] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5 }; + pos += 1 + offset[buffer[pos] & 31]; + } else { + pos += 5; + } + } + + if (row != pattern->n_rows) { + TRACE("XM error: wrong number of rows in pattern data\n"); + return -1; + } + + pattern->entry = malloc(pattern->n_entries * sizeof(*pattern->entry)); + if (!pattern->entry) + return -1; + + /* read the entries */ + entry = pattern->entry; + pos = channel = row = 0; + while (pos < size) { + unsigned char mask; + + if (buffer[pos] & XM_ENTRY_PACKED) + mask = buffer[pos++] & 31; + else + mask = 31; + + if (mask) { + ASSERT(entry < pattern->entry + pattern->n_entries); + + entry->channel = channel; + entry->mask = 0; + + if (mask & XM_ENTRY_NOTE) { + int note = buffer[pos++]; /* 1-96 <=> C0-B7 */ + entry->note = (note == XM_NOTE_OFF) ? (IT_NOTE_OFF) : (note-1); + entry->mask |= IT_ENTRY_NOTE; + } + + if (mask & XM_ENTRY_INSTRUMENT) { + entry->instrument = buffer[pos++]; /* 1-128 */ + entry->mask |= IT_ENTRY_INSTRUMENT; + } + + if (mask & XM_ENTRY_VOLUME) + it_xm_convert_volume(buffer[pos++], entry); + + effect = effectvalue = 0; + if (mask & XM_ENTRY_EFFECT) effect = buffer[pos++]; + if (mask & XM_ENTRY_EFFECTVALUE) effectvalue = buffer[pos++]; + _dumb_it_xm_convert_effect(effect, effectvalue, entry); + + entry++; + } + + channel++; + if (channel >= n_channels) { + channel = 0; + row++; + IT_SET_END_ROW(entry); + entry++; + } + } + + return 0; +} + + + +static int it_xm_make_envelope(IT_ENVELOPE *envelope, const unsigned short *data, int y_offset) +{ + int i, pos; + + if (envelope->n_nodes > 12) { + TRACE("XM error: wrong number of envelope nodes (%d)\n", envelope->n_nodes); + envelope->n_nodes = 0; + return -1; + } + + pos = 0; + for (i = 0; i < envelope->n_nodes; i++) { + envelope->node_t[i] = data[pos++]; + if (data[pos] > 64) { + TRACE("XM error: out-of-range envelope node (node_y[%d]=%d)\n", i, data[pos]); + envelope->n_nodes = 0; + return -1; + } + envelope->node_y[i] = (signed char)(data[pos++] + y_offset); + } + + return 0; +} + + + +static int it_xm_read_instrument(IT_INSTRUMENT *instrument, XM_INSTRUMENT_EXTRA *extra, DUMBFILE *f) +{ + unsigned long size, bytes_read; + unsigned short vol_points[24]; + unsigned short pan_points[24]; + int i, type; + + /* Header size. Tends to be more than the actual size of the structure. + * So unread bytes must be skipped before reading the first sample + * header. + */ + size = dumbfile_igetl(f); + + //memset(instrument, 0, sizeof(*instrument)); + dumbfile_skip(f, 22); /* Instrument name */ + dumbfile_skip(f, 1); /* Instrument type. Should be 0, but seems random. */ + extra->n_samples = dumbfile_igetw(f); + + if (dumbfile_error(f) || (unsigned int)extra->n_samples > XM_MAX_SAMPLES_PER_INSTRUMENT) + return -1; + + bytes_read = 4 + 22 + 1 + 2; + + if (extra->n_samples) { + /* sample header size */ + if (dumbfile_igetl(f) != 0x28) { + TRACE("XM error: unexpected sample header size\n"); + return -1; + } + + /* sample map */ + for (i = 0; i < 96; i++) { + instrument->map_sample[i] = dumbfile_getc(f) + 1; + instrument->map_note[i] = i; + } + + if (dumbfile_error(f)) + return 1; + + /* volume/panning envelopes */ + for (i = 0; i < 24; i++) + vol_points[i] = dumbfile_igetw(f); + for (i = 0; i < 24; i++) + pan_points[i] = dumbfile_igetw(f); + + instrument->volume_envelope.n_nodes = dumbfile_getc(f); + instrument->pan_envelope.n_nodes = dumbfile_getc(f); + + if (dumbfile_error(f)) + return -1; + + instrument->volume_envelope.sus_loop_start = dumbfile_getc(f); + instrument->volume_envelope.loop_start = dumbfile_getc(f); + instrument->volume_envelope.loop_end = dumbfile_getc(f); + + instrument->pan_envelope.sus_loop_start = dumbfile_getc(f); + instrument->pan_envelope.loop_start = dumbfile_getc(f); + instrument->pan_envelope.loop_end = dumbfile_getc(f); + + /* The envelope handler for XM files won't use sus_loop_end. */ + + type = dumbfile_getc(f); + instrument->volume_envelope.flags = 0; + if ((type & XM_ENVELOPE_ON) && instrument->volume_envelope.n_nodes) + instrument->volume_envelope.flags |= IT_ENVELOPE_ON; + if (type & XM_ENVELOPE_LOOP) instrument->volume_envelope.flags |= IT_ENVELOPE_LOOP_ON; +#if 1 + if (type & XM_ENVELOPE_SUSTAIN) instrument->volume_envelope.flags |= IT_ENVELOPE_SUSTAIN_LOOP; +#else // This is now handled in itrender.c + /* let's avoid fading out when reaching the last envelope node */ + if (!(type & XM_ENVELOPE_LOOP)) { + instrument->volume_envelope.loop_start = instrument->volume_envelope.n_nodes-1; + instrument->volume_envelope.loop_end = instrument->volume_envelope.n_nodes-1; + } + instrument->volume_envelope.flags |= IT_ENVELOPE_LOOP_ON; +#endif + + type = dumbfile_getc(f); + instrument->pan_envelope.flags = 0; + if ((type & XM_ENVELOPE_ON) && instrument->pan_envelope.n_nodes) + instrument->pan_envelope.flags |= IT_ENVELOPE_ON; + if (type & XM_ENVELOPE_LOOP) instrument->pan_envelope.flags |= IT_ENVELOPE_LOOP_ON; // should this be here? + if (type & XM_ENVELOPE_SUSTAIN) instrument->pan_envelope.flags |= IT_ENVELOPE_SUSTAIN_LOOP; + + if (it_xm_make_envelope(&instrument->volume_envelope, vol_points, 0) != 0) { + TRACE("XM error: volume envelope\n"); + if (instrument->volume_envelope.flags & IT_ENVELOPE_ON) return -1; + } + + if (it_xm_make_envelope(&instrument->pan_envelope, pan_points, -32) != 0) { + TRACE("XM error: pan envelope\n"); + if (instrument->pan_envelope.flags & IT_ENVELOPE_ON) return -1; + } + + instrument->pitch_envelope.flags = 0; + + extra->vibrato_type = dumbfile_getc(f); + extra->vibrato_sweep = dumbfile_getc(f); + extra->vibrato_depth = dumbfile_getc(f); + extra->vibrato_speed = dumbfile_getc(f); + + if (dumbfile_error(f) || extra->vibrato_type >= 4) + return -1; + + /** WARNING: lossy approximation */ + instrument->fadeout = (dumbfile_igetw(f)*128 + 64)/0xFFF; + + dumbfile_skip(f, 2); /* reserved */ + + bytes_read += 4 + 96 + 48 + 48 + 14*1 + 2 + 2; + } + + if (dumbfile_skip(f, size - bytes_read)) + return -1; + + instrument->new_note_action = NNA_NOTE_CUT; + instrument->dup_check_type = DCT_OFF; + instrument->dup_check_action = DCA_NOTE_CUT; + instrument->pp_separation = 0; + instrument->pp_centre = 60; /* C-5 */ + instrument->global_volume = 128; + instrument->default_pan = 32; + instrument->random_volume = 0; + instrument->random_pan = 0; + instrument->filter_cutoff = 0; + instrument->filter_resonance = 0; + + return 0; +} + + + +/* I (entheh) have two XM files saved by a very naughty program. After a + * 16-bit sample, it saved a rogue byte. The length of the sample was indeed + * an odd number, incremented to include the rogue byte. + * + * In this function we are converting sample lengths and loop points so they + * are measured in samples. This means we forget about the extra bytes, and + * they don't get skipped. So we fail trying to read the next instrument. + * + * To get around this, this function returns the number of rogue bytes that + * won't be accounted for by reading sample->length samples. It returns a + * negative number on failure. + */ +static int it_xm_read_sample_header(IT_SAMPLE *sample, DUMBFILE *f) +{ + int type; + int relative_note_number; /* relative to C4 */ + int finetune; + int roguebytes; + int roguebytesmask; + + sample->length = dumbfile_igetl(f); + sample->loop_start = dumbfile_igetl(f); + sample->loop_end = sample->loop_start + dumbfile_igetl(f); + sample->global_volume = 64; + sample->default_volume = dumbfile_getc(f); + finetune = (signed char)dumbfile_getc(f); /* -128..127 <=> -1 semitone .. +127/128 of a semitone */ + type = dumbfile_getc(f); + sample->default_pan = (dumbfile_getc(f)*64)/255 | 128; /* 0-255 */ + relative_note_number = (signed char)dumbfile_getc(f); + + dumbfile_skip(f, 1); /* reserved */ + dumbfile_skip(f, 22); /* sample name */ + + if (dumbfile_error(f)) + return -1; + + sample->C5_speed = (long)(16726.0*pow(DUMB_SEMITONE_BASE, relative_note_number)*pow(DUMB_PITCH_BASE, finetune*2)); + + sample->flags = IT_SAMPLE_EXISTS; + + roguebytes = (int)sample->length; + roguebytesmask = 3; + + if (type & XM_SAMPLE_16BIT) { + sample->flags |= IT_SAMPLE_16BIT; + sample->length >>= 1; + sample->loop_start >>= 1; + sample->loop_end >>= 1; + } else + roguebytesmask >>= 1; + + if (type & XM_SAMPLE_STEREO) { + sample->flags |= IT_SAMPLE_STEREO; + sample->length >>= 1; + sample->loop_start >>= 1; + sample->loop_end >>= 1; + } else + roguebytesmask >>= 1; + + roguebytes &= roguebytesmask; + + if ((unsigned int)sample->loop_start < (unsigned int)sample->loop_end) { + if (type & XM_SAMPLE_FORWARD_LOOP) sample->flags |= IT_SAMPLE_LOOP; + if (type & XM_SAMPLE_PINGPONG_LOOP) sample->flags |= IT_SAMPLE_LOOP | IT_SAMPLE_PINGPONG_LOOP; + } + + if (sample->length <= 0) + sample->flags &= ~IT_SAMPLE_EXISTS; + else if ((unsigned int)sample->loop_end > (unsigned int)sample->length) + sample->flags &= ~IT_SAMPLE_LOOP; + else if ((unsigned int)sample->loop_start >= (unsigned int)sample->loop_end) + sample->flags &= ~IT_SAMPLE_LOOP; + + return roguebytes; +} + + + +static int it_xm_read_sample_data(IT_SAMPLE *sample, unsigned char roguebytes, DUMBFILE *f) +{ + int old; + long i; + long truncated_size; + + if (!(sample->flags & IT_SAMPLE_EXISTS)) + return dumbfile_skip(f, roguebytes); + + /* let's get rid of the sample data coming after the end of the loop */ + if ((sample->flags & IT_SAMPLE_LOOP) && sample->loop_end < sample->length) { + truncated_size = sample->length - sample->loop_end; + sample->length = sample->loop_end; + } else { + truncated_size = 0; + } + + sample->left = malloc(sample->length * sizeof(*sample->left)); + if (!sample->left) + return -1; + + if (sample->flags & IT_SAMPLE_STEREO) { + sample->right = malloc(sample->length * sizeof(*sample->right)); + if (!sample->right) + return -1; + } + + /* sample data is stored as signed delta values */ + old = 0; + if (sample->flags & IT_SAMPLE_16BIT) { + for (i = 0; i < sample->length; i++) { + old = sample->left[i] = (int)(signed short)(old + dumbfile_igetw(f)); + sample->left[i] <<= 8; + } + } else { + for (i = 0; i < sample->length; i++) { + old = sample->left[i] = (int)(signed char)(old + dumbfile_getc(f)); + sample->left[i] <<= 16; + } + } + + /* skip truncated data */ + dumbfile_skip(f, (sample->flags & IT_SAMPLE_16BIT) ? (2*truncated_size) : (truncated_size)); + + if (sample->flags & IT_SAMPLE_STEREO) { + old = 0; + if (sample->flags & IT_SAMPLE_16BIT) { + for (i = 0; i < sample->length; i++) { + old = sample->right[i] = (int)(signed short)(old + dumbfile_igetw(f)); + sample->right[i] <<= 8; + } + } else { + for (i = 0; i < sample->length; i++) { + old = sample->right[i] = (int)(signed char)(old + dumbfile_getc(f)); + sample->right[i] <<= 16; + } + } + /* skip truncated data */ + dumbfile_skip(f, (sample->flags & IT_SAMPLE_16BIT) ? (2*truncated_size) : (truncated_size)); + } + + dumbfile_skip(f, roguebytes); + + if (dumbfile_error(f)) + return -1; + + return 0; +} + + + +/* "Real programmers don't document. If it was hard to write, + * it should be hard to understand." + * + * (Never trust the documentation provided with a tracker. + * Real files are the only truth...) + */ +static DUMB_IT_SIGDATA *it_xm_load_sigdata(DUMBFILE *f) +{ + DUMB_IT_SIGDATA *sigdata; + char id_text[18]; + + int flags; + int n_channels; + int total_samples; + int i, j; + + /* check ID text */ + if (dumbfile_getnc(id_text, 17, f) < 17) + return NULL; + id_text[17] = 0; + if (strcmp(id_text, "Extended Module: ") != 0) { + TRACE("XM error: Not an Extended Module\n"); + return NULL; + } + + /* song name */ + if (dumbfile_skip(f, 20)) + return NULL; + + if (dumbfile_getc(f) != 0x1A) { + TRACE("XM error: 0x1A not found\n"); + return NULL; + } + + /* tracker name */ + if (dumbfile_skip(f, 20)) + return NULL; + + /* version number */ + if (dumbfile_igetw(f) != 0x0104) { + TRACE("XM error: wrong format version\n"); + return NULL; + } + + /* + ------------------ + --- Header --- + ------------------ + */ + + /* header size */ + if (dumbfile_igetl(f) != 0x0114) { + TRACE("XM error: unexpected header size\n"); + return NULL; + } + + sigdata = malloc(sizeof(*sigdata)); + if (!sigdata) + return NULL; + + sigdata->order = NULL; + sigdata->instrument = NULL; + sigdata->sample = NULL; + sigdata->pattern = NULL; + sigdata->midi = NULL; + sigdata->checkpoint = NULL; + + sigdata->n_samples = 0; + sigdata->n_orders = dumbfile_igetw(f); + sigdata->restart_position = dumbfile_igetw(f); + n_channels = dumbfile_igetw(f); /* max 32 but we'll be lenient */ + sigdata->n_patterns = dumbfile_igetw(f); + sigdata->n_instruments = dumbfile_igetw(f); /* max 128 */ + flags = dumbfile_igetw(f); + sigdata->speed = dumbfile_igetw(f); + sigdata->tempo = dumbfile_igetw(f); + + /* sanity checks */ + if (dumbfile_error(f) || sigdata->n_orders <= 0 || sigdata->n_orders > 256 || sigdata->n_patterns > 256 || sigdata->n_instruments > 128 || n_channels > DUMB_IT_N_CHANNELS) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + //if (sigdata->restart_position >= sigdata->n_orders) + //sigdata->restart_position = 0; + + /* order table */ + sigdata->order = malloc(sigdata->n_orders*sizeof(*sigdata->order)); + if (!sigdata->order) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + dumbfile_getnc(sigdata->order, sigdata->n_orders, f); + dumbfile_skip(f, 256 - sigdata->n_orders); + + if (dumbfile_error(f)) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + /* + -------------------- + --- Patterns --- + -------------------- + */ + + sigdata->pattern = malloc(sigdata->n_patterns * sizeof(*sigdata->pattern)); + if (!sigdata->pattern) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (i = 0; i < sigdata->n_patterns; i++) + sigdata->pattern[i].entry = NULL; + + { + unsigned char *buffer = malloc(1280 * n_channels); /* 256 rows * 5 bytes */ + if (!buffer) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (i = 0; i < sigdata->n_patterns; i++) { + if (it_xm_read_pattern(&sigdata->pattern[i], f, n_channels, buffer) != 0) { + free(buffer); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + } + free(buffer); + } + + /* + ----------------------------------- + --- Instruments and Samples --- + ----------------------------------- + */ + + sigdata->instrument = malloc(sigdata->n_instruments * sizeof(*sigdata->instrument)); + if (!sigdata->instrument) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + /* With XM, samples are not global, they're part of an instrument. In a + * file, each instrument is stored with its samples. Because of this, I + * don't know how to find how many samples are present in the file. Thus + * I have to do n_instruments reallocation on sigdata->sample. + * Looking at FT2, it doesn't seem possible to have more than 16 samples + * per instrument (even though n_samples is stored as 2 bytes). So maybe + * we could allocate a 128*16 array of samples, and shrink it back to the + * correct size when we know it? + * Alternatively, I could allocate samples by blocks of N (still O(n)), + * or double the number of allocated samples when I need more (O(log n)). + */ + total_samples = 0; + sigdata->sample = NULL; + + for (i = 0; i < sigdata->n_instruments; i++) { + XM_INSTRUMENT_EXTRA extra; + + if (it_xm_read_instrument(&sigdata->instrument[i], &extra, f) < 0) { + TRACE("XM error: instrument %d\n", i+1); + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + + if (extra.n_samples) { + unsigned char roguebytes[XM_MAX_SAMPLES_PER_INSTRUMENT]; + + /* adjust instrument sample map (make indices absolute) */ + for (j = 0; j < 96; j++) + sigdata->instrument[i].map_sample[j] += total_samples; + + sigdata->sample = safe_realloc(sigdata->sample, sizeof(*sigdata->sample)*(total_samples+extra.n_samples)); + if (!sigdata->sample) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + for (j = total_samples; j < total_samples+extra.n_samples; j++) + sigdata->sample[j].right = sigdata->sample[j].left = NULL; + + /* read instrument's samples */ + for (j = 0; j < extra.n_samples; j++) { + IT_SAMPLE *sample = &sigdata->sample[total_samples+j]; + int b = it_xm_read_sample_header(sample, f); + if (b < 0) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + roguebytes[j] = b; + // Any reason why these can't be set inside it_xm_read_sample_header()? + sample->vibrato_speed = extra.vibrato_speed; + sample->vibrato_depth = extra.vibrato_depth; + sample->vibrato_rate = extra.vibrato_sweep; + /* Rate and sweep don't match, but the difference is + * accounted for in itrender.c. + */ + sample->vibrato_waveform = xm_convert_vibrato[extra.vibrato_type]; + } + for (j = 0; j < extra.n_samples; j++) { + if (it_xm_read_sample_data(&sigdata->sample[total_samples+j], roguebytes[j], f) != 0) { + _dumb_it_unload_sigdata(sigdata); + return NULL; + } + } + total_samples += extra.n_samples; + } + } + + sigdata->n_samples = total_samples; + + sigdata->flags = IT_WAS_AN_XM | IT_OLD_EFFECTS | IT_COMPATIBLE_GXX | IT_STEREO | IT_USE_INSTRUMENTS; + // Are we OK with IT_COMPATIBLE_GXX off? + // + // When specifying note + instr + tone portamento, and an old note is still playing (even after note off): + // - If Compatible Gxx is on, the new note will be triggered only if the instrument _changes_. + // - If Compatible Gxx is off, the new note will always be triggered, provided the instrument is specified. + // - FT2 seems to do the latter (unconfirmed). + + // Err, wait. XM playback has its own code. The change made to the IT + // playbackc code didn't affect XM playback. Forget this then. There's + // still a bug in XM playback though, and it'll need some investigation... + // tomorrow... + + // UPDATE: IT_COMPATIBLE_GXX is required to be on, so that tone porta has + // separate memory from portamento. + + if (flags & XM_LINEAR_FREQUENCY) + sigdata->flags |= IT_LINEAR_SLIDES; + + sigdata->global_volume = 128; + sigdata->mixing_volume = 48; + sigdata->pan_separation = 128; + + memset(sigdata->channel_volume, 64, DUMB_IT_N_CHANNELS); + memset(sigdata->channel_pan, 32, DUMB_IT_N_CHANNELS); + + _dumb_it_fix_invalid_orders(sigdata); + + return sigdata; +} + + + +#if 0 // no fucking way, dude! + +/* The length returned is the time required to play from the beginning of the + * file to the last row of the last order (which is when the player will + * loop). Depending on the song, the sound might stop sooner. + * Due to fixed point roundoffs, I think this is only reliable to the second. + * Full precision could be achieved by using a double during the computation, + * or maybe a LONG_LONG. + */ +long it_compute_length(const DUMB_IT_SIGDATA *sigdata) +{ + IT_PATTERN *pattern; + int tempo, speed; + int loop_start[IT_N_CHANNELS]; + char loop_count[IT_N_CHANNELS]; + int order, entry; + int row_first_entry = 0; + int jump, jump_dest; + int delay, fine_delay; + int i; + long t; + + if (!sigdata) + return 0; + + tempo = sigdata->tempo; + speed = sigdata->speed; + order = entry = 0; + jump = jump_dest = 0; + t = 0; + + /* for each PATTERN */ + for (order = 0; order < sigdata->n_orders; order++) { + + if (sigdata->order[order] == IT_ORDER_END) break; + if (sigdata->order[order] == IT_ORDER_SKIP) continue; + + for (i = 0; i < IT_N_CHANNELS; i++) + loop_count[i] = -1; + + pattern = &sigdata->pattern[ sigdata->order[order] ]; + entry = 0; + if (jump == IT_BREAK_TO_ROW) { + int row = 0; + while (row < jump_dest) + if (pattern->entry[entry++].channel >= IT_N_CHANNELS) + row++; + } + + /* for each ROW */ + while (entry < pattern->n_entries) { + row_first_entry = entry; + delay = fine_delay = 0; + jump = 0; + + /* for each note NOTE */ + while (entry < pattern->n_entries && pattern->entry[entry].channel < IT_N_CHANNELS) { + int value = pattern->entry[entry].effectvalue; + int channel = pattern->entry[entry].channel; + + switch (pattern->entry[entry].effect) { + + case IT_SET_SPEED: speed = value; break; + + case IT_JUMP_TO_ORDER: + if (value <= order) /* infinite loop */ + return 0; + jump = IT_JUMP_TO_ORDER; + jump_dest = value; + break; + + case IT_BREAK_TO_ROW: + jump = IT_BREAK_TO_ROW; + jump_dest = value; + break; + + case IT_S: + switch (HIGH(value)) { + case IT_S_PATTERN_DELAY: delay = LOW(value); break; + case IT_S_FINE_PATTERN_DELAY: fine_delay = LOW(value); break; + case IT_S_PATTERN_LOOP: + if (LOW(value) == 0) { + loop_start[channel] = row_first_entry; + } else { + if (loop_count[channel] == -1) + loop_count[channel] = LOW(value); + + if (loop_count[channel]) { + jump = IT_S_PATTERN_LOOP; + jump_dest = loop_start[channel]; + } + loop_count[channel]--; + } + break; + } + break; + + case IT_SET_SONG_TEMPO: + switch (HIGH(value)) { /* slides happen every non-row frames */ + case 0: tempo = tempo - LOW(value)*(speed-1); break; + case 1: tempo = tempo + LOW(value)*(speed-1); break; + default: tempo = value; + } + tempo = MID(32, tempo, 255); + break; + } + + entry++; + } + + /* end of ROW */ + entry++; + t += TICK_TIME_DIVIDEND * (speed*(1+delay) + fine_delay) / tempo; + + if (jump == IT_JUMP_TO_ORDER) { + order = jump_dest - 1; + break; + } else if (jump == IT_BREAK_TO_ROW) + break; + else if (jump == IT_S_PATTERN_LOOP) + entry = jump_dest - 1; + } + + /* end of PATTERN */ + } + + return t; +} + +#endif /* 0 */ + + + +DUH *dumb_read_xm(DUMBFILE *f) +{ + sigdata_t *sigdata; + long length; + + DUH_SIGTYPE_DESC *descptr = &_dumb_sigtype_it; + + sigdata = it_xm_load_sigdata(f); + + if (!sigdata) + return NULL; + + length = _dumb_it_build_checkpoints(sigdata); + + return make_duh(length, 1, &descptr, &sigdata); +} diff --git a/apps/codecs/dumb/src/it/xmeffect.c b/apps/codecs/dumb/src/it/xmeffect.c new file mode 100644 index 0000000000..51995f3bb8 --- /dev/null +++ b/apps/codecs/dumb/src/it/xmeffect.c @@ -0,0 +1,242 @@ +/* _______ ____ __ ___ ___ + * \ _ \ \ / \ / \ \ / / ' ' ' + * | | \ \ | | || | \/ | . . + * | | | | | | || ||\ /| | + * | | | | | | || || \/ | | ' ' ' + * | | | | | | || || | | . . + * | |_/ / \ \__// || | | + * /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque + * / \ + * / . \ + * xmeffect.c - Code for converting MOD/XM / / \ \ + * effects to IT effects. | < / \_ + * | \/ /\ / + * By Julien Cugniere. Ripped out of readxm.c \_ / > / + * by entheh. | \ / / + * | ' / + * \__/ + */ + + + +#include +#include + +#include "dumb.h" +#include "internal/it.h" + + + +#if 0 +unsigned char **_dumb_malloc2(int w, int h) +{ + unsigned char **line = malloc(h * sizeof(*line)); + int i; + if (!line) return NULL; + + line[0] = malloc(w * h * sizeof(*line[0])); + if (!line[0]) { + free(line); + return NULL; + } + + for (i = 1; i < h; i++) + line[i] = line[i-1] + w; + + memset(line[0], 0, w*h); + + return line; +} + + + +void _dumb_free2(unsigned char **line) +{ + if (line) { + if (line[0]) + free(line[0]); + free(line); + } +} + + + +/* Effects having a memory. 2 means that the two parts of the effectvalue + * should be handled separately. + */ +static const char xm_has_memory[] = { +/* 0 1 2 3 4 5 6 7 8 9 A B C D (E) F G H K L P R T (X) */ + 0, 1, 1, 1, 2, 1, 1, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, + +/* E0 E1 E2 E3 E4 E5 E6 E7 E9 EA EB EC ED EE X1 X2 */ + 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +#endif + + + +/* Effects marked with 'special' are handled specifically in itrender.c */ +void _dumb_it_xm_convert_effect(int effect, int value, IT_ENTRY *entry) +{ +const int log = 0; + + if ((!effect && !value) || (effect >= XM_N_EFFECTS)) + return; + +if (log) printf("%c%02X", (effect<10)?('0'+effect):('A'+effect-10), value); + + /* Linearisation of the effect number... */ + if (effect == XM_E) { + effect = EBASE + HIGH(value); + value = LOW(value); + } else if (effect == XM_X) { + effect = XBASE + HIGH(value); + value = LOW(value); + } + +if (log) printf(" - %2d %02X", effect, value); + +#if 0 // This should be handled in itrender.c! + /* update effect memory */ + switch (xm_has_memory[effect]) { + case 1: + if (!value) + value = memory[entry->channel][effect]; + else + memory[entry->channel][effect] = value; + break; + + case 2: + if (!HIGH(value)) + SET_HIGH(value, HIGH(memory[entry->channel][effect])); + else + SET_HIGH(memory[entry->channel][effect], HIGH(value)); + + if (!LOW(value)) + SET_LOW(value, LOW(memory[entry->channel][effect])); + else + SET_LOW(memory[entry->channel][effect], LOW(value)); + break; + } +#endif + + /* convert effect */ + entry->mask |= IT_ENTRY_EFFECT; + switch (effect) { + + case XM_APPREGIO: effect = IT_ARPEGGIO; break; + case XM_VIBRATO: effect = IT_VIBRATO; break; + case XM_TONE_PORTAMENTO: effect = IT_TONE_PORTAMENTO; break; /** TODO: glissando control */ + case XM_TREMOLO: effect = IT_TREMOLO; break; + case XM_SET_PANNING: effect = IT_SET_PANNING; break; + case XM_SAMPLE_OFFSET: effect = IT_SET_SAMPLE_OFFSET; break; + case XM_POSITION_JUMP: effect = IT_JUMP_TO_ORDER; break; + case XM_MULTI_RETRIG: effect = IT_RETRIGGER_NOTE; break; + case XM_TREMOR: effect = IT_TREMOR; break; + case XM_PORTAMENTO_UP: effect = IT_XM_PORTAMENTO_UP; break; + case XM_PORTAMENTO_DOWN: effect = IT_XM_PORTAMENTO_DOWN; break; + case XM_SET_CHANNEL_VOLUME: effect = IT_SET_CHANNEL_VOLUME; break; /* special */ + case XM_VOLSLIDE_TONEPORTA: effect = IT_VOLSLIDE_TONEPORTA; break; /* special */ + case XM_VOLSLIDE_VIBRATO: effect = IT_VOLSLIDE_VIBRATO; break; /* special */ + + case XM_PATTERN_BREAK: + effect = IT_BREAK_TO_ROW; + value = BCD_TO_NORMAL(value); + break; + + case XM_VOLUME_SLIDE: /* special */ + effect = IT_VOLUME_SLIDE; + value = HIGH(value) ? EFFECT_VALUE(HIGH(value), 0) : EFFECT_VALUE(0, LOW(value)); + break; + + case XM_PANNING_SLIDE: + effect = IT_PANNING_SLIDE; + value = HIGH(value) ? EFFECT_VALUE(0, HIGH(value)) : EFFECT_VALUE(LOW(value), 0); + break; + + case XM_GLOBAL_VOLUME_SLIDE: /* special */ + effect = IT_GLOBAL_VOLUME_SLIDE; + value = HIGH(value) ? EFFECT_VALUE(HIGH(value), 0) : EFFECT_VALUE(0, LOW(value)); + break; + + case XM_SET_TEMPO_BPM: + effect = (value < 0x20) ? (IT_SET_SPEED) : (IT_SET_SONG_TEMPO); + break; + + case XM_SET_GLOBAL_VOLUME: + effect = IT_SET_GLOBAL_VOLUME; + value *= 2; + break; + + case XM_KEY_OFF: + /** WARNING: In FT2, the value seems to do something... Oh well, + * this is undocumented anyway! + */ + entry->mask &= ~IT_ENTRY_EFFECT; + entry->mask |= IT_ENTRY_NOTE; + entry->note = IT_NOTE_OFF; + break; + + case EBASE+XM_E_SET_FILTER: effect = SBASE+IT_S_SET_FILTER; break; + case EBASE+XM_E_SET_GLISSANDO_CONTROL: effect = SBASE+IT_S_SET_GLISSANDO_CONTROL; break; /** TODO */ + case EBASE+XM_E_SET_FINETUNE: effect = SBASE+IT_S_FINETUNE; break; /** TODO */ + case EBASE+XM_E_SET_LOOP: effect = SBASE+IT_S_PATTERN_LOOP; break; + case EBASE+XM_E_NOTE_CUT: effect = SBASE+IT_S_DELAYED_NOTE_CUT; break; + case EBASE+XM_E_NOTE_DELAY: effect = SBASE+IT_S_NOTE_DELAY; break; + case EBASE+XM_E_PATTERN_DELAY: effect = SBASE+IT_S_PATTERN_DELAY; break; + case EBASE+XM_E_FINE_VOLSLIDE_UP: effect = IT_XM_FINE_VOLSLIDE_UP; break; + case EBASE+XM_E_FINE_VOLSLIDE_DOWN: effect = IT_XM_FINE_VOLSLIDE_DOWN; break; + + case EBASE + XM_E_FINE_PORTA_UP: + effect = IT_PORTAMENTO_UP; + value = EFFECT_VALUE(0xF, value); + break; + + case EBASE + XM_E_FINE_PORTA_DOWN: + effect = IT_PORTAMENTO_DOWN; + value = EFFECT_VALUE(0xF, value); + break; + + case EBASE + XM_E_RETRIG_NOTE: + effect = IT_XM_RETRIGGER_NOTE; + value = EFFECT_VALUE(0, value); + break; + + case EBASE + XM_E_SET_VIBRATO_CONTROL: + effect = SBASE+IT_S_SET_VIBRATO_WAVEFORM; + value &= ~4; /** TODO: value&4 -> don't retrig wave */ + break; + + case EBASE + XM_E_SET_TREMOLO_CONTROL: + effect = SBASE+IT_S_SET_TREMOLO_WAVEFORM; + value &= ~4; /** TODO: value&4 -> don't retrig wave */ + break; + + case XBASE + XM_X_EXTRAFINE_PORTA_UP: + effect = IT_PORTAMENTO_UP; + value = EFFECT_VALUE(0xE, value); + break; + + case XBASE + XM_X_EXTRAFINE_PORTA_DOWN: + effect = IT_PORTAMENTO_DOWN; + value = EFFECT_VALUE(0xE, value); + break; + + default: + /* user effect (often used in demos for synchronisation) */ + entry->mask &= ~IT_ENTRY_EFFECT; + } + +if (log) printf(" - %2d %02X", effect, value); + + /* Inverse linearisation... */ + if (effect >= SBASE && effect < SBASE+16) { + value = EFFECT_VALUE(effect-SBASE, value); + effect = IT_S; + } + +if (log) printf(" - %c%02X\n", 'A'+effect-1, value); + + entry->effect = effect; + entry->effectvalue = value; +} -- cgit v1.2.3