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
This commit is contained in:
Michiel Van Der Kolk 2005-03-17 20:50:03 +00:00
parent 7e7662bb71
commit 27be5bc728
67 changed files with 18488 additions and 1 deletions

View file

@ -0,0 +1,270 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* alplay.c - Functions to play a DUH through / / \ \
* an Allegro audio stream. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#include <allegro.h>
#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);
}

View file

@ -0,0 +1,60 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* datduh.c - Integration with Allegro's / / \ \
* datafiles. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <allegro.h>
#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
);
}

View file

@ -0,0 +1,62 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* datit.c - Integration of IT files with / / \ \
* Allegro's datafiles. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <allegro.h>
#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
);
}

View file

@ -0,0 +1,61 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* datmod.c - Integration of MOD files with / / \ \
* Allegro's datafiles. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <allegro.h>
#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
);
}

View file

@ -0,0 +1,61 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* dats3m.c - Integration of S3M files with / / \ \
* Allegro's datafiles. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <allegro.h>
#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
);
}

View file

@ -0,0 +1,31 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* datunld.c - Unload function for integration / / \ \
* with Allegro's datafiles. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <allegro.h>
#include "aldumb.h"
#include "internal/aldumb.h"
void _dat_unload_duh(void *duh)
{
unload_duh(duh);
}

View file

@ -0,0 +1,62 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* datxm.c - Integration of XM files with / / \ \
* Allegro's datafiles. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <allegro.h>
#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
);
}

View file

@ -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 <allegro.h>
#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;
}

View file

@ -0,0 +1,71 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* atexit.c - Library Clean-up Management. / / \ \
* | < / \_
* By entheh. | \/ /\ /
* \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#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;
}
}

View file

@ -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;
}

View file

@ -0,0 +1,401 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* dumbfile.c - Hookable, strictly sequential / / \ \
* file input functions. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#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;
}

View file

@ -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;
}

View file

@ -0,0 +1,92 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* makeduh.c - Function to construct a DUH from / / \ \
* its components. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#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;
}

View file

@ -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 <stdlib.h>
#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;
}

View file

@ -0,0 +1,107 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* readduh.c - Code to read a DUH from an open / / \ \
* file. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#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;
}

View file

@ -0,0 +1,104 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* register.c - Signal type registration. / / \ \
* | < / \_
* By entheh. | \/ /\ /
* \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#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;
}

View file

@ -0,0 +1,202 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* rendduh.c - Functions for rendering a DUH into / / \ \
* an end-user sample format. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#include <limits.h>
#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;
}

View file

@ -0,0 +1,299 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* rendsig.c - Wrappers to render samples from / / \ \
* the signals in a DUH. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#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 <stdio.h>
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

View file

@ -0,0 +1,58 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* unload.c - Code to free a DUH from memory. / / \ \
* | < / \_
* By entheh. | \/ /\ /
* \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#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);
}
}

View file

@ -0,0 +1,270 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* clickrem.c - Click removal helpers. / / \ \
* | < / \_
* By entheh. | \/ /\ /
* \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#include <math.h>
#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);
}
}

View file

@ -0,0 +1,96 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* memfile.c - Module for reading data from / / \ \
* memory using a DUMBFILE. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#include <string.h>
#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);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,47 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* sampbuf.c - Helper for allocating sample / / \ \
* buffers. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#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);
}
}

View file

@ -0,0 +1,29 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* silence.c - Silencing helper. / / \ \
* | < / \_
* By entheh. | \/ /\ /
* \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <string.h>
#include "dumb.h"
void dumb_silence(sample_t *samples, long length)
{
memset(samples, 0, length * sizeof(*samples));
}

View file

@ -0,0 +1,93 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* stdfile.c - stdio file input module. / / \ \
* | < / \_
* By entheh. | \/ /\ /
* \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdio.h>
#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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -0,0 +1,63 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* itorder.c - Code to fix invalid patterns in / / \ \
* the pattern table. | < / \_
* | \/ /\ /
* By Julien Cugniere. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#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;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,71 @@
/* _______ ____ __ ___ ___
* \ _ \ \ / \ / \ \ / / ' ' '
* | | \ \ | | || | \/ | . .
* | | | | | | || ||\ /| |
* | | | | | | || || \/ | | ' ' '
* | | | | | | || || | | . .
* | |_/ / \ \__// || | |
* /_______/ynamic \____/niversal /__\ /____\usic /| . . ibliotheque
* / \
* / . \
* itunload.c - Code to free an Impulse Tracker / / \ \
* module from memory. | < / \_
* | \/ /\ /
* By entheh. \_ / > /
* | \ / /
* | ' /
* \__/
*/
#include <stdlib.h>
#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);
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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 <stdlib.h>
#include <string.h>
#include <math.h>
#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);
}

View file

@ -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 <stdlib.h>
#include <string.h>
#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);
}

View file

@ -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 <stdlib.h>
#include <string.h>
#include <math.h>
#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);
}

View file

@ -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 <stdlib.h>
#include <string.h>
#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;
}