push third-party/libxm
This commit is contained in:
parent
c07aa3c9a8
commit
329ef82747
40
third-party/libxm/CMakeLists.txt
vendored
Normal file
40
third-party/libxm/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
|
||||
PROJECT(libxm C)
|
||||
|
||||
FUNCTION(OPTION_AND_DEFINE name description default_value)
|
||||
OPTION(${name} ${description} ${default_value})
|
||||
IF(${name})
|
||||
ADD_DEFINITIONS(-D${name}=1)
|
||||
ELSE()
|
||||
ADD_DEFINITIONS(-D${name}=0)
|
||||
ENDIF()
|
||||
ENDFUNCTION()
|
||||
|
||||
OPTION_AND_DEFINE(XM_DEBUG "Enable debug symbols and print debugging messages to stderr" "ON")
|
||||
OPTION_AND_DEFINE(XM_DEFENSIVE "Defensively check XM data for errors/inconsistencies" "ON")
|
||||
OPTION_AND_DEFINE(XM_BIG_ENDIAN "Use big endian byte order (unfinished)" "OFF")
|
||||
|
||||
OPTION_AND_DEFINE(XM_LINEAR_INTERPOLATION "Use linear interpolation (CPU hungry)" "ON")
|
||||
OPTION_AND_DEFINE(XM_RAMPING "Enable ramping (smooth volume/panning transitions, CPU hungry)" "ON")
|
||||
OPTION_AND_DEFINE(XM_STRINGS "Store module, instrument and sample names in context" "OFF")
|
||||
OPTION_AND_DEFINE(XM_LIBXMIZE_DELTA_SAMPLES "Delta-code samples in libxmize format" "ON")
|
||||
|
||||
OPTION(XM_DEMO_MODE "Optimize for size (then link statically against libxms and strip dead code)" "OFF")
|
||||
|
||||
ADD_DEFINITIONS("-Wall -pedantic --std=c11")
|
||||
|
||||
IF(XM_DEBUG)
|
||||
ADD_DEFINITIONS("-g")
|
||||
ENDIF()
|
||||
|
||||
IF(XM_DEMO_MODE)
|
||||
ADD_DEFINITIONS("-Os -fdata-sections -ffunction-sections -flto -fuse-linker-plugin -fvisibility=hidden")
|
||||
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
|
||||
ELSE()
|
||||
ADD_DEFINITIONS("-O2")
|
||||
ENDIF()
|
||||
|
||||
LIST(APPEND XM_INCLUDE_DIRS "${libxm_SOURCE_DIR}/include")
|
||||
LIST(APPEND XM_LIBRARIES "m")
|
||||
|
||||
ADD_SUBDIRECTORY(src)
|
262
third-party/libxm/include/xm.h
vendored
Normal file
262
third-party/libxm/include/xm.h
vendored
Normal file
@ -0,0 +1,262 @@
|
||||
/* Author: Romain "Artefact2" Dalmaso <artefact2@gmail.com> */
|
||||
/* Contributor: Dan Spencer <dan@atomicpotato.net> */
|
||||
|
||||
/* This program is free software. It comes without any warranty, to the
|
||||
* extent permitted by applicable law. You can redistribute it and/or
|
||||
* modify it under the terms of the Do What The Fuck You Want To Public
|
||||
* License, Version 2, as published by Sam Hocevar. See
|
||||
* http://sam.zoy.org/wtfpl/COPYING for more details. */
|
||||
|
||||
#pragma once
|
||||
#ifndef __has_xm_h
|
||||
#define __has_xm_h
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct xm_context_s;
|
||||
typedef struct xm_context_s xm_context_t;
|
||||
|
||||
/** Create a XM context.
|
||||
*
|
||||
* @param moddata the contents of the module
|
||||
* @param rate play rate in Hz, recommended value of 48000
|
||||
*
|
||||
* @returns 0 on success
|
||||
* @returns 1 if module data is not sane
|
||||
* @returns 2 if memory allocation failed
|
||||
*
|
||||
* @deprecated This function is unsafe!
|
||||
* @see xm_create_context_safe()
|
||||
*/
|
||||
int xm_create_context(xm_context_t**, const char* moddata, uint32_t rate);
|
||||
|
||||
/** Create a XM context.
|
||||
*
|
||||
* @param moddata the contents of the module
|
||||
* @param moddata_length the length of the contents of the module, in bytes
|
||||
* @param rate play rate in Hz, recommended value of 48000
|
||||
*
|
||||
* @returns 0 on success
|
||||
* @returns 1 if module data is not sane
|
||||
* @returns 2 if memory allocation failed
|
||||
*/
|
||||
int xm_create_context_safe(xm_context_t**, const char* moddata, size_t moddata_length, uint32_t rate);
|
||||
|
||||
/** Free a XM context created by xm_create_context(). */
|
||||
void xm_free_context(xm_context_t*);
|
||||
|
||||
/** Play the module and put the sound samples in an output buffer.
|
||||
*
|
||||
* @param output buffer of 2*numsamples elements
|
||||
* @param maximum numsamples number of samples to generate
|
||||
*
|
||||
* @return number of generated samples.
|
||||
*/
|
||||
int xm_generate_samples(xm_context_t*, float* output, size_t numsamples);
|
||||
|
||||
/** Seeks to the start clearing the loop count.
|
||||
*
|
||||
* @return number of generated samples.
|
||||
*/
|
||||
void xm_restart(xm_context_t*);
|
||||
|
||||
|
||||
/** Set the maximum number of times a module can loop. After the
|
||||
* specified number of loops, calls to xm_generate_samples will only
|
||||
* generate silence. You can control the current number of loops with
|
||||
* xm_get_loop_count().
|
||||
*
|
||||
* @param loopcnt maximum number of loops. Use 0 to loop
|
||||
* indefinitely. */
|
||||
void xm_set_max_loop_count(xm_context_t*, uint8_t loopcnt);
|
||||
|
||||
/** Get the loop count of the currently playing module. This value is
|
||||
* 0 when the module is still playing, 1 when the module has looped
|
||||
* once, etc. */
|
||||
uint8_t xm_get_loop_count(xm_context_t*);
|
||||
|
||||
|
||||
|
||||
/** Seek to a specific position in a module.
|
||||
*
|
||||
* WARNING, WITH BIG LETTERS: seeking modules is broken by design,
|
||||
* don't expect miracles.
|
||||
*/
|
||||
void xm_seek(xm_context_t*, uint8_t pot, uint8_t row, uint16_t tick);
|
||||
|
||||
|
||||
|
||||
/** Mute or unmute a channel.
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*
|
||||
* @return whether the channel was muted.
|
||||
*/
|
||||
bool xm_mute_channel(xm_context_t*, uint16_t, bool);
|
||||
|
||||
/** Mute or unmute an instrument.
|
||||
*
|
||||
* @note Instrument numbers go from 1 to
|
||||
* xm_get_number_of_instruments(...).
|
||||
*
|
||||
* @return whether the instrument was muted.
|
||||
*/
|
||||
bool xm_mute_instrument(xm_context_t*, uint16_t, bool);
|
||||
|
||||
|
||||
|
||||
/** Get the module name as a NUL-terminated string. */
|
||||
const char* xm_get_module_name(xm_context_t*);
|
||||
|
||||
/** Get the tracker name as a NUL-terminated string. */
|
||||
const char* xm_get_tracker_name(xm_context_t*);
|
||||
|
||||
|
||||
|
||||
/** Get the number of channels. */
|
||||
uint16_t xm_get_number_of_channels(xm_context_t*);
|
||||
|
||||
/** Get the module length (in patterns). */
|
||||
uint16_t xm_get_module_length(xm_context_t*);
|
||||
|
||||
/** Get the number of patterns. */
|
||||
uint16_t xm_get_number_of_patterns(xm_context_t*);
|
||||
|
||||
/** Get the number of rows of a pattern.
|
||||
*
|
||||
* @note Pattern numbers go from 0 to
|
||||
* xm_get_number_of_patterns(...)-1.
|
||||
*/
|
||||
uint16_t xm_get_number_of_rows(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the number of instruments. */
|
||||
uint16_t xm_get_number_of_instruments(xm_context_t*);
|
||||
|
||||
/** Get the number of samples of an instrument.
|
||||
*
|
||||
* @note Instrument numbers go from 1 to
|
||||
* xm_get_number_of_instruments(...).
|
||||
*/
|
||||
uint16_t xm_get_number_of_samples(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the internal buffer for a given sample waveform.
|
||||
*
|
||||
* This buffer can be read from or written to, at any time, but the
|
||||
* length cannot change. The buffer must be cast to (int8_t*) or
|
||||
* (int16_t*) depending on the sample type.
|
||||
*
|
||||
* @note Instrument numbers go from 1 to
|
||||
* xm_get_number_of_instruments(...).
|
||||
*
|
||||
* @note Sample numbers go from 0 to
|
||||
* xm_get_nubmer_of_samples(...,instr)-1.
|
||||
*/
|
||||
void* xm_get_sample_waveform(xm_context_t*, uint16_t instr, uint16_t sample, size_t* length, uint8_t* bits);
|
||||
|
||||
|
||||
|
||||
/** Get the current module speed.
|
||||
*
|
||||
* @param bpm will receive the current BPM
|
||||
* @param tempo will receive the current tempo (ticks per line)
|
||||
*/
|
||||
void xm_get_playing_speed(xm_context_t*, uint16_t* bpm, uint16_t* tempo);
|
||||
|
||||
/** Get the current position in the module being played.
|
||||
*
|
||||
* @param pattern_index if not NULL, will receive the current pattern
|
||||
* index in the POT (pattern order table)
|
||||
*
|
||||
* @param pattern if not NULL, will receive the current pattern number
|
||||
*
|
||||
* @param row if not NULL, will receive the current row
|
||||
*
|
||||
* @param samples if not NULL, will receive the total number of
|
||||
* generated samples (divide by sample rate to get seconds of
|
||||
* generated audio)
|
||||
*/
|
||||
void xm_get_position(xm_context_t*, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples);
|
||||
|
||||
/** Get the latest time (in number of generated samples) when a
|
||||
* particular instrument was triggered in any channel.
|
||||
*
|
||||
* @note Instrument numbers go from 1 to
|
||||
* xm_get_number_of_instruments(...).
|
||||
*/
|
||||
uint64_t xm_get_latest_trigger_of_instrument(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the latest time (in number of generated samples) when a
|
||||
* particular sample was triggered in any channel.
|
||||
*
|
||||
* @note Instrument numbers go from 1 to
|
||||
* xm_get_number_of_instruments(...).
|
||||
*
|
||||
* @note Sample numbers go from 0 to
|
||||
* xm_get_nubmer_of_samples(...,instr)-1.
|
||||
*/
|
||||
uint64_t xm_get_latest_trigger_of_sample(xm_context_t*, uint16_t instr, uint16_t sample);
|
||||
|
||||
/** Get the latest time (in number of generated samples) when any
|
||||
* instrument was triggered in a given channel.
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*/
|
||||
uint64_t xm_get_latest_trigger_of_channel(xm_context_t*, uint16_t);
|
||||
|
||||
/** Checks whether a channel is active (ie: is playing something).
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*/
|
||||
bool xm_is_channel_active(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the instrument number currently playing in a channel.
|
||||
*
|
||||
* @returns instrument number, or 0 if channel is not active.
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*
|
||||
* @note Instrument numbers go from 1 to
|
||||
* xm_get_number_of_instruments(...).
|
||||
*/
|
||||
uint16_t xm_get_instrument_of_channel(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the frequency of the sample currently playing in a channel.
|
||||
*
|
||||
* @returns a frequency in Hz. If the channel is not active, return
|
||||
* value is undefined.
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*/
|
||||
float xm_get_frequency_of_channel(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the volume of the sample currently playing in a channel. This
|
||||
* takes into account envelopes, etc.
|
||||
*
|
||||
* @returns a volume between 0 or 1. If the channel is not active,
|
||||
* return value is undefined.
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*/
|
||||
float xm_get_volume_of_channel(xm_context_t*, uint16_t);
|
||||
|
||||
/** Get the panning of the sample currently playing in a channel. This
|
||||
* takes into account envelopes, etc.
|
||||
*
|
||||
* @returns a panning between 0 (L) and 1 (R). If the channel is not
|
||||
* active, return value is undefined.
|
||||
*
|
||||
* @note Channel numbers go from 1 to xm_get_number_of_channels(...).
|
||||
*/
|
||||
float xm_get_panning_of_channel(xm_context_t*, uint16_t);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
2
third-party/libxm/src/CMakeLists.txt
vendored
Normal file
2
third-party/libxm/src/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
ADD_LIBRARY(xms STATIC xm.c context.c load.c play.c)
|
||||
INCLUDE_DIRECTORIES(${XM_INCLUDE_DIRS})
|
256
third-party/libxm/src/context.c
vendored
Normal file
256
third-party/libxm/src/context.c
vendored
Normal file
@ -0,0 +1,256 @@
|
||||
/* Author: Romain "Artefact2" Dalmaso <artefact2@gmail.com> */
|
||||
|
||||
/* This program is free software. It comes without any warranty, to the
|
||||
* extent permitted by applicable law. You can redistribute it and/or
|
||||
* modify it under the terms of the Do What The Fuck You Want To Public
|
||||
* License, Version 2, as published by Sam Hocevar. See
|
||||
* http://sam.zoy.org/wtfpl/COPYING for more details. */
|
||||
|
||||
#include "xm_internal.h"
|
||||
|
||||
#define OFFSET(ptr) do { \
|
||||
(ptr) = (void*)((intptr_t)(ptr) + (intptr_t)(*ctxp)); \
|
||||
} while(0)
|
||||
|
||||
#define CHECK_CHANNEL(ctx, c) do { \
|
||||
if(XM_DEBUG && ((c) == 0 || (c) > (ctx)->module.num_channels)) \
|
||||
DEBUG("invalid channel %d", (c)); \
|
||||
} while(0)
|
||||
|
||||
#define CHECK_INSTRUMENT(ctx, i) do { \
|
||||
if(XM_DEBUG && ((i) == 0 || (i) > (ctx)->module.num_instruments)) \
|
||||
DEBUG("invalid instrument %d", (i)); \
|
||||
} while(0)
|
||||
|
||||
#define CHECK_SAMPLE(ctx, i, s) do { \
|
||||
CHECK_INSTRUMENT((ctx), (i)); \
|
||||
if(XM_DEBUG && ((s) > (ctx)->module.instruments[(i)].num_samples)) \
|
||||
DEBUG("invalid sample %d for instrument %d", (s), (i)); \
|
||||
} while(0)
|
||||
|
||||
|
||||
|
||||
int xm_create_context(xm_context_t** ctxp, const char* moddata, uint32_t rate) {
|
||||
return xm_create_context_safe(ctxp, moddata, SIZE_MAX, rate);
|
||||
}
|
||||
|
||||
int xm_create_context_safe(xm_context_t** ctxp, const char* moddata, size_t moddata_length, uint32_t rate) {
|
||||
size_t bytes_needed;
|
||||
char* mempool;
|
||||
xm_context_t* ctx;
|
||||
|
||||
if(XM_DEFENSIVE) {
|
||||
int ret;
|
||||
if((ret = xm_check_sanity_preload(moddata, moddata_length))) {
|
||||
DEBUG("xm_check_sanity_preload() returned %i, module is not safe to load", ret);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
bytes_needed = xm_get_memory_needed_for_context(moddata, moddata_length);
|
||||
mempool = malloc(bytes_needed);
|
||||
if(mempool == NULL && bytes_needed > 0) {
|
||||
/* malloc() failed, trouble ahead */
|
||||
DEBUG("call to malloc() failed, returned %p", (void*)mempool);
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Initialize most of the fields to 0, 0.f, NULL or false depending on type */
|
||||
memset(mempool, 0, bytes_needed);
|
||||
|
||||
ctx = (*ctxp = (xm_context_t*)mempool);
|
||||
ctx->ctx_size = bytes_needed; /* Keep original requested size for xmconvert */
|
||||
mempool += sizeof(xm_context_t);
|
||||
|
||||
ctx->rate = rate;
|
||||
mempool = xm_load_module(ctx, moddata, moddata_length, mempool);
|
||||
|
||||
ctx->channels = (xm_channel_context_t*)mempool;
|
||||
mempool += ctx->module.num_channels * sizeof(xm_channel_context_t);
|
||||
|
||||
ctx->global_volume = 1.f;
|
||||
ctx->amplification = .25f; /* XXX: some bad modules may still clip. Find out something better. */
|
||||
|
||||
#if XM_RAMPING
|
||||
ctx->volume_ramp = (1.f / 128.f);
|
||||
#endif
|
||||
|
||||
for(uint8_t i = 0; i < ctx->module.num_channels; ++i) {
|
||||
xm_channel_context_t* ch = ctx->channels + i;
|
||||
|
||||
ch->ping = true;
|
||||
ch->vibrato_waveform = XM_SINE_WAVEFORM;
|
||||
ch->vibrato_waveform_retrigger = true;
|
||||
ch->tremolo_waveform = XM_SINE_WAVEFORM;
|
||||
ch->tremolo_waveform_retrigger = true;
|
||||
|
||||
ch->volume = ch->volume_envelope_volume = ch->fadeout_volume = 1.0f;
|
||||
ch->panning = ch->panning_envelope_panning = .5f;
|
||||
ch->actual_volume[0] = .0f;
|
||||
ch->actual_volume[1] = .0f;
|
||||
}
|
||||
|
||||
ctx->row_loop_count = (uint8_t*)mempool;
|
||||
mempool += ctx->module.length * MAX_NUM_ROWS * sizeof(uint8_t);
|
||||
|
||||
if(XM_DEFENSIVE) {
|
||||
int ret;
|
||||
if((ret = xm_check_sanity_postload(ctx))) {
|
||||
DEBUG("xm_check_sanity_postload() returned %i, module is not safe to play", ret);
|
||||
xm_free_context(ctx);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void xm_free_context(xm_context_t* context) {
|
||||
free(context);
|
||||
}
|
||||
|
||||
void xm_set_max_loop_count(xm_context_t* context, uint8_t loopcnt) {
|
||||
context->max_loop_count = loopcnt;
|
||||
}
|
||||
|
||||
uint8_t xm_get_loop_count(xm_context_t* context) {
|
||||
return context->loop_count;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void xm_seek(xm_context_t* ctx, uint8_t pot, uint8_t row, uint16_t tick) {
|
||||
ctx->current_table_index = pot;
|
||||
ctx->current_row = row;
|
||||
ctx->current_tick = tick;
|
||||
ctx->remaining_samples_in_tick = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool xm_mute_channel(xm_context_t* ctx, uint16_t channel, bool mute) {
|
||||
CHECK_CHANNEL(ctx, channel);
|
||||
bool old = ctx->channels[channel - 1].muted;
|
||||
ctx->channels[channel - 1].muted = mute;
|
||||
return old;
|
||||
}
|
||||
|
||||
bool xm_mute_instrument(xm_context_t* ctx, uint16_t instr, bool mute) {
|
||||
CHECK_INSTRUMENT(ctx, instr);
|
||||
bool old = ctx->module.instruments[instr - 1].muted;
|
||||
ctx->module.instruments[instr - 1].muted = mute;
|
||||
return old;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if XM_STRINGS
|
||||
const char* xm_get_module_name(xm_context_t* ctx) {
|
||||
return ctx->module.name;
|
||||
}
|
||||
|
||||
const char* xm_get_tracker_name(xm_context_t* ctx) {
|
||||
return ctx->module.trackername;
|
||||
}
|
||||
#else
|
||||
const char* xm_get_module_name(xm_context_t* ctx) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* xm_get_tracker_name(xm_context_t* ctx) {
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
uint16_t xm_get_number_of_channels(xm_context_t* ctx) {
|
||||
return ctx->module.num_channels;
|
||||
}
|
||||
|
||||
uint16_t xm_get_module_length(xm_context_t* ctx) {
|
||||
return ctx->module.length;
|
||||
}
|
||||
|
||||
uint16_t xm_get_number_of_patterns(xm_context_t* ctx) {
|
||||
return ctx->module.num_patterns;
|
||||
}
|
||||
|
||||
uint16_t xm_get_number_of_rows(xm_context_t* ctx, uint16_t pattern) {
|
||||
if(pattern < ctx->module.num_patterns)
|
||||
return ctx->module.patterns[pattern].num_rows;
|
||||
return DEFAULT_PATTERN_LENGTH;
|
||||
}
|
||||
|
||||
uint16_t xm_get_number_of_instruments(xm_context_t* ctx) {
|
||||
return ctx->module.num_instruments;
|
||||
}
|
||||
|
||||
uint16_t xm_get_number_of_samples(xm_context_t* ctx, uint16_t instrument) {
|
||||
CHECK_INSTRUMENT(ctx, instrument);
|
||||
return ctx->module.instruments[instrument - 1].num_samples;
|
||||
}
|
||||
|
||||
void* xm_get_sample_waveform(xm_context_t* ctx, uint16_t i, uint16_t s, size_t* size, uint8_t* bits) {
|
||||
CHECK_SAMPLE(ctx, i, s);
|
||||
*size = ctx->module.instruments[i - 1].samples[s].length;
|
||||
*bits = ctx->module.instruments[i - 1].samples[s].bits;
|
||||
return ctx->module.instruments[i - 1].samples[s].data8;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void xm_get_playing_speed(xm_context_t* ctx, uint16_t* bpm, uint16_t* tempo) {
|
||||
if(bpm) *bpm = ctx->bpm;
|
||||
if(tempo) *tempo = ctx->tempo;
|
||||
}
|
||||
|
||||
void xm_get_position(xm_context_t* ctx, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples) {
|
||||
if(pattern_index) *pattern_index = ctx->current_table_index;
|
||||
if(pattern) *pattern = ctx->module.pattern_table[ctx->current_table_index];
|
||||
if(row) *row = ctx->current_row;
|
||||
if(samples) *samples = ctx->generated_samples;
|
||||
}
|
||||
|
||||
uint64_t xm_get_latest_trigger_of_instrument(xm_context_t* ctx, uint16_t instr) {
|
||||
CHECK_INSTRUMENT(ctx, instr);
|
||||
return ctx->module.instruments[instr - 1].latest_trigger;
|
||||
}
|
||||
|
||||
uint64_t xm_get_latest_trigger_of_sample(xm_context_t* ctx, uint16_t instr, uint16_t sample) {
|
||||
CHECK_SAMPLE(ctx, instr, sample);
|
||||
return ctx->module.instruments[instr - 1].samples[sample].latest_trigger;
|
||||
}
|
||||
|
||||
uint64_t xm_get_latest_trigger_of_channel(xm_context_t* ctx, uint16_t chn) {
|
||||
CHECK_CHANNEL(ctx, chn);
|
||||
return ctx->channels[chn - 1].latest_trigger;
|
||||
}
|
||||
|
||||
bool xm_is_channel_active(xm_context_t* ctx, uint16_t chn) {
|
||||
CHECK_CHANNEL(ctx, chn);
|
||||
xm_channel_context_t* ch = ctx->channels + (chn - 1);
|
||||
return ch->instrument != NULL && ch->sample != NULL && ch->sample_position >= 0;
|
||||
}
|
||||
|
||||
float xm_get_frequency_of_channel(xm_context_t* ctx, uint16_t chn) {
|
||||
CHECK_CHANNEL(ctx, chn);
|
||||
return ctx->channels[chn - 1].frequency;
|
||||
}
|
||||
|
||||
float xm_get_volume_of_channel(xm_context_t* ctx, uint16_t chn) {
|
||||
CHECK_CHANNEL(ctx, chn);
|
||||
return ctx->channels[chn - 1].volume * ctx->global_volume;
|
||||
}
|
||||
|
||||
float xm_get_panning_of_channel(xm_context_t* ctx, uint16_t chn) {
|
||||
CHECK_CHANNEL(ctx, chn);
|
||||
return ctx->channels[chn - 1].panning;
|
||||
}
|
||||
|
||||
uint16_t xm_get_instrument_of_channel(xm_context_t* ctx, uint16_t chn) {
|
||||
CHECK_CHANNEL(ctx, chn);
|
||||
xm_channel_context_t* ch = ctx->channels + (chn - 1);
|
||||
if(ch->instrument == NULL) return 0;
|
||||
return 1 + (ch->instrument - ctx->module.instruments);
|
||||
}
|
416
third-party/libxm/src/load.c
vendored
Normal file
416
third-party/libxm/src/load.c
vendored
Normal file
@ -0,0 +1,416 @@
|
||||
/* Author: Romain "Artefact2" Dalmaso <artefact2@gmail.com> */
|
||||
/* Contributor: Dan Spencer <dan@atomicpotato.net> */
|
||||
|
||||
/* This program is free software. It comes without any warranty, to the
|
||||
* extent permitted by applicable law. You can redistribute it and/or
|
||||
* modify it under the terms of the Do What The Fuck You Want To Public
|
||||
* License, Version 2, as published by Sam Hocevar. See
|
||||
* http://sam.zoy.org/wtfpl/COPYING for more details. */
|
||||
|
||||
#include "xm_internal.h"
|
||||
|
||||
/* .xm files are little-endian. */
|
||||
|
||||
/* Bounded reader macros.
|
||||
* If we attempt to read the buffer out-of-bounds, pretend that the buffer is
|
||||
* infinitely padded with zeroes.
|
||||
*/
|
||||
#define READ_U8_BOUND(offset, bound) (((offset) < (bound)) ? (*(uint8_t*)(moddata + (offset))) : 0)
|
||||
#define READ_U16_BOUND(offset, bound) ((uint16_t)READ_U8_BOUND(offset, bound) | ((uint16_t)READ_U8_BOUND((offset) + 1, bound) << 8))
|
||||
#define READ_U32_BOUND(offset, bound) ((uint32_t)READ_U16_BOUND(offset, bound) | ((uint32_t)READ_U16_BOUND((offset) + 2, bound) << 16))
|
||||
#define READ_MEMCPY_BOUND(ptr, offset, length, bound) memcpy_pad(ptr, length, moddata, bound, offset)
|
||||
|
||||
#define READ_U8(offset) READ_U8_BOUND(offset, moddata_length)
|
||||
#define READ_U16(offset) READ_U16_BOUND(offset, moddata_length)
|
||||
#define READ_U32(offset) READ_U32_BOUND(offset, moddata_length)
|
||||
#define READ_MEMCPY(ptr, offset, length) READ_MEMCPY_BOUND(ptr, offset, length, moddata_length)
|
||||
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
static inline void memcpy_pad(void* dst, size_t dst_len, const void* src, size_t src_len, size_t offset) {
|
||||
uint8_t* dst_c = dst;
|
||||
const uint8_t* src_c = src;
|
||||
|
||||
/* how many bytes can be copied without overrunning `src` */
|
||||
size_t copy_bytes = (src_len >= offset) ? (src_len - offset) : 0;
|
||||
copy_bytes = copy_bytes > dst_len ? dst_len : copy_bytes;
|
||||
|
||||
memcpy(dst_c, src_c + offset, copy_bytes);
|
||||
/* padded bytes */
|
||||
memset(dst_c + copy_bytes, 0, dst_len - copy_bytes);
|
||||
}
|
||||
|
||||
int xm_check_sanity_preload(const char* module, size_t module_length) {
|
||||
if(module_length < 60) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if(memcmp("Extended Module: ", module, 17) != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(module[37] != 0x1A) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if(module[59] != 0x01 || module[58] != 0x04) {
|
||||
/* Not XM 1.04 */
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int xm_check_sanity_postload(xm_context_t* ctx) {
|
||||
/* @todo: plenty of stuff to do here… */
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t xm_get_memory_needed_for_context(const char* moddata, size_t moddata_length) {
|
||||
size_t memory_needed = 0;
|
||||
size_t offset = 60; /* Skip the first header */
|
||||
uint16_t num_channels;
|
||||
uint16_t num_patterns;
|
||||
uint16_t num_instruments;
|
||||
|
||||
/* Read the module header */
|
||||
|
||||
num_channels = READ_U16(offset + 8);
|
||||
num_patterns = READ_U16(offset + 10);
|
||||
memory_needed += num_patterns * sizeof(xm_pattern_t);
|
||||
|
||||
num_instruments = READ_U16(offset + 12);
|
||||
memory_needed += num_instruments * sizeof(xm_instrument_t);
|
||||
|
||||
memory_needed += MAX_NUM_ROWS * READ_U16(offset + 4) * sizeof(uint8_t); /* Module length */
|
||||
|
||||
/* Header size */
|
||||
offset += READ_U32(offset);
|
||||
|
||||
/* Read pattern headers */
|
||||
for(uint16_t i = 0; i < num_patterns; ++i) {
|
||||
uint16_t num_rows;
|
||||
|
||||
num_rows = READ_U16(offset + 5);
|
||||
memory_needed += num_rows * num_channels * sizeof(xm_pattern_slot_t);
|
||||
|
||||
/* Pattern header length + packed pattern data size */
|
||||
offset += READ_U32(offset) + READ_U16(offset + 7);
|
||||
}
|
||||
|
||||
/* Read instrument headers */
|
||||
for(uint16_t i = 0; i < num_instruments; ++i) {
|
||||
uint16_t num_samples;
|
||||
uint32_t sample_size_aggregate = 0;
|
||||
|
||||
num_samples = READ_U16(offset + 27);
|
||||
memory_needed += num_samples * sizeof(xm_sample_t);
|
||||
|
||||
/* Instrument header size */
|
||||
uint32_t ins_header_size = READ_U32(offset);
|
||||
if (ins_header_size == 0 || ins_header_size > INSTRUMENT_HEADER_LENGTH)
|
||||
ins_header_size = INSTRUMENT_HEADER_LENGTH;
|
||||
offset += ins_header_size;
|
||||
|
||||
for(uint16_t j = 0; j < num_samples; ++j) {
|
||||
uint32_t sample_size;
|
||||
|
||||
sample_size = READ_U32(offset);
|
||||
sample_size_aggregate += sample_size;
|
||||
memory_needed += sample_size;
|
||||
offset += 40; /* See comment in xm_load_module() */
|
||||
}
|
||||
|
||||
offset += sample_size_aggregate;
|
||||
}
|
||||
|
||||
memory_needed += num_channels * sizeof(xm_channel_context_t);
|
||||
memory_needed += sizeof(xm_context_t);
|
||||
|
||||
return memory_needed;
|
||||
}
|
||||
|
||||
char* xm_load_module(xm_context_t* ctx, const char* moddata, size_t moddata_length, char* mempool) {
|
||||
size_t offset = 0;
|
||||
xm_module_t* mod = &(ctx->module);
|
||||
|
||||
/* Read XM header */
|
||||
#if XM_STRINGS
|
||||
READ_MEMCPY(mod->name, offset + 17, MODULE_NAME_LENGTH);
|
||||
READ_MEMCPY(mod->trackername, offset + 38, TRACKER_NAME_LENGTH);
|
||||
#endif
|
||||
offset += 60;
|
||||
|
||||
/* Read module header */
|
||||
uint32_t header_size = READ_U32(offset);
|
||||
|
||||
mod->length = READ_U16(offset + 4);
|
||||
mod->restart_position = READ_U16(offset + 6);
|
||||
mod->num_channels = READ_U16(offset + 8);
|
||||
mod->num_patterns = READ_U16(offset + 10);
|
||||
mod->num_instruments = READ_U16(offset + 12);
|
||||
|
||||
mod->patterns = (xm_pattern_t*)mempool;
|
||||
mempool += mod->num_patterns * sizeof(xm_pattern_t);
|
||||
|
||||
mod->instruments = (xm_instrument_t*)mempool;
|
||||
mempool += mod->num_instruments * sizeof(xm_instrument_t);
|
||||
|
||||
uint16_t flags = READ_U32(offset + 14);
|
||||
mod->frequency_type = (flags & (1 << 0)) ? XM_LINEAR_FREQUENCIES : XM_AMIGA_FREQUENCIES;
|
||||
|
||||
ctx->tempo = READ_U16(offset + 16);
|
||||
ctx->bpm = READ_U16(offset + 18);
|
||||
|
||||
READ_MEMCPY(mod->pattern_table, offset + 20, PATTERN_ORDER_TABLE_LENGTH);
|
||||
offset += header_size;
|
||||
|
||||
/* Read patterns */
|
||||
for(uint16_t i = 0; i < mod->num_patterns; ++i) {
|
||||
uint16_t packed_patterndata_size = READ_U16(offset + 7);
|
||||
xm_pattern_t* pat = mod->patterns + i;
|
||||
|
||||
pat->num_rows = READ_U16(offset + 5);
|
||||
|
||||
pat->slots = (xm_pattern_slot_t*)mempool;
|
||||
mempool += mod->num_channels * pat->num_rows * sizeof(xm_pattern_slot_t);
|
||||
|
||||
/* Pattern header length */
|
||||
offset += READ_U32(offset);
|
||||
|
||||
if(packed_patterndata_size == 0) {
|
||||
/* No pattern data is present */
|
||||
memset(pat->slots, 0, sizeof(xm_pattern_slot_t) * pat->num_rows * mod->num_channels);
|
||||
} else {
|
||||
/* This isn't your typical for loop */
|
||||
for(uint16_t j = 0, k = 0; j < packed_patterndata_size; ++k) {
|
||||
uint8_t note = READ_U8(offset + j);
|
||||
xm_pattern_slot_t* slot = pat->slots + k;
|
||||
|
||||
if(note & (1 << 7)) {
|
||||
/* MSB is set, this is a compressed packet */
|
||||
++j;
|
||||
|
||||
if(note & (1 << 0)) {
|
||||
/* Note follows */
|
||||
slot->note = READ_U8(offset + j);
|
||||
++j;
|
||||
} else {
|
||||
slot->note = 0;
|
||||
}
|
||||
|
||||
if(note & (1 << 1)) {
|
||||
/* Instrument follows */
|
||||
slot->instrument = READ_U8(offset + j);
|
||||
++j;
|
||||
} else {
|
||||
slot->instrument = 0;
|
||||
}
|
||||
|
||||
if(note & (1 << 2)) {
|
||||
/* Volume column follows */
|
||||
slot->volume_column = READ_U8(offset + j);
|
||||
++j;
|
||||
} else {
|
||||
slot->volume_column = 0;
|
||||
}
|
||||
|
||||
if(note & (1 << 3)) {
|
||||
/* Effect follows */
|
||||
slot->effect_type = READ_U8(offset + j);
|
||||
++j;
|
||||
} else {
|
||||
slot->effect_type = 0;
|
||||
}
|
||||
|
||||
if(note & (1 << 4)) {
|
||||
/* Effect parameter follows */
|
||||
slot->effect_param = READ_U8(offset + j);
|
||||
++j;
|
||||
} else {
|
||||
slot->effect_param = 0;
|
||||
}
|
||||
} else {
|
||||
/* Uncompressed packet */
|
||||
slot->note = note;
|
||||
slot->instrument = READ_U8(offset + j + 1);
|
||||
slot->volume_column = READ_U8(offset + j + 2);
|
||||
slot->effect_type = READ_U8(offset + j + 3);
|
||||
slot->effect_param = READ_U8(offset + j + 4);
|
||||
j += 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offset += packed_patterndata_size;
|
||||
}
|
||||
|
||||
/* Read instruments */
|
||||
for(uint16_t i = 0; i < ctx->module.num_instruments; ++i) {
|
||||
xm_instrument_t* instr = mod->instruments + i;
|
||||
|
||||
/* Original FT2 would load instruments with a direct read into the
|
||||
instrument data structure that was previously zeroed. This means
|
||||
that if the declared length was less than INSTRUMENT_HEADER_LENGTH,
|
||||
all excess data would be zeroed. This is used by the XM compressor
|
||||
BoobieSqueezer. To implement this, bound all reads to the header size. */
|
||||
uint32_t ins_header_size = READ_U32(offset);
|
||||
if (ins_header_size == 0 || ins_header_size > INSTRUMENT_HEADER_LENGTH)
|
||||
ins_header_size = INSTRUMENT_HEADER_LENGTH;
|
||||
|
||||
#if XM_STRINGS
|
||||
READ_MEMCPY_BOUND(instr->name, offset + 4, INSTRUMENT_NAME_LENGTH, offset + ins_header_size);
|
||||
instr->name[INSTRUMENT_NAME_LENGTH] = 0;
|
||||
#endif
|
||||
instr->num_samples = READ_U16_BOUND(offset + 27, offset + ins_header_size);
|
||||
|
||||
if(instr->num_samples > 0) {
|
||||
/* Read extra header properties */
|
||||
READ_MEMCPY_BOUND(instr->sample_of_notes, offset + 33, NUM_NOTES, offset + ins_header_size);
|
||||
|
||||
instr->volume_envelope.num_points = READ_U8_BOUND(offset + 225, offset + ins_header_size);
|
||||
if (instr->volume_envelope.num_points > NUM_ENVELOPE_POINTS)
|
||||
instr->volume_envelope.num_points = NUM_ENVELOPE_POINTS;
|
||||
|
||||
instr->panning_envelope.num_points = READ_U8_BOUND(offset + 226, offset + ins_header_size);
|
||||
if (instr->panning_envelope.num_points > NUM_ENVELOPE_POINTS)
|
||||
instr->panning_envelope.num_points = NUM_ENVELOPE_POINTS;
|
||||
|
||||
for(uint8_t j = 0; j < instr->volume_envelope.num_points; ++j) {
|
||||
instr->volume_envelope.points[j].frame = READ_U16_BOUND(offset + 129 + 4 * j, offset + ins_header_size);
|
||||
instr->volume_envelope.points[j].value = READ_U16_BOUND(offset + 129 + 4 * j + 2, offset + ins_header_size);
|
||||
}
|
||||
|
||||
for(uint8_t j = 0; j < instr->panning_envelope.num_points; ++j) {
|
||||
instr->panning_envelope.points[j].frame = READ_U16_BOUND(offset + 177 + 4 * j, offset + ins_header_size);
|
||||
instr->panning_envelope.points[j].value = READ_U16_BOUND(offset + 177 + 4 * j + 2, offset + ins_header_size);
|
||||
}
|
||||
|
||||
instr->volume_envelope.sustain_point = READ_U8_BOUND(offset + 227, offset + ins_header_size);
|
||||
instr->volume_envelope.loop_start_point = READ_U8_BOUND(offset + 228, offset + ins_header_size);
|
||||
instr->volume_envelope.loop_end_point = READ_U8_BOUND(offset + 229, offset + ins_header_size);
|
||||
|
||||
instr->panning_envelope.sustain_point = READ_U8_BOUND(offset + 230, offset + ins_header_size);
|
||||
instr->panning_envelope.loop_start_point = READ_U8_BOUND(offset + 231, offset + ins_header_size);
|
||||
instr->panning_envelope.loop_end_point = READ_U8_BOUND(offset + 232, offset + ins_header_size);
|
||||
|
||||
// Fix broken modules with loop points outside of defined points
|
||||
if (instr->volume_envelope.num_points > 0) {
|
||||
instr->volume_envelope.loop_start_point =
|
||||
MIN(instr->volume_envelope.loop_start_point, instr->volume_envelope.num_points-1);
|
||||
instr->volume_envelope.loop_end_point =
|
||||
MIN(instr->volume_envelope.loop_end_point, instr->volume_envelope.num_points-1);
|
||||
}
|
||||
if (instr->panning_envelope.num_points > 0) {
|
||||
instr->panning_envelope.loop_start_point =
|
||||
MIN(instr->panning_envelope.loop_start_point, instr->panning_envelope.num_points-1);
|
||||
instr->panning_envelope.loop_end_point =
|
||||
MIN(instr->panning_envelope.loop_end_point, instr->panning_envelope.num_points-1);
|
||||
}
|
||||
|
||||
uint8_t flags = READ_U8_BOUND(offset + 233, offset + ins_header_size);
|
||||
instr->volume_envelope.enabled = flags & (1 << 0);
|
||||
instr->volume_envelope.sustain_enabled = flags & (1 << 1);
|
||||
instr->volume_envelope.loop_enabled = flags & (1 << 2);
|
||||
|
||||
flags = READ_U8_BOUND(offset + 234, offset + ins_header_size);
|
||||
instr->panning_envelope.enabled = flags & (1 << 0);
|
||||
instr->panning_envelope.sustain_enabled = flags & (1 << 1);
|
||||
instr->panning_envelope.loop_enabled = flags & (1 << 2);
|
||||
|
||||
instr->vibrato_type = READ_U8_BOUND(offset + 235, offset + ins_header_size);
|
||||
if(instr->vibrato_type == 2) {
|
||||
instr->vibrato_type = 1;
|
||||
} else if(instr->vibrato_type == 1) {
|
||||
instr->vibrato_type = 2;
|
||||
}
|
||||
instr->vibrato_sweep = READ_U8_BOUND(offset + 236, offset + ins_header_size);
|
||||
instr->vibrato_depth = READ_U8_BOUND(offset + 237, offset + ins_header_size);
|
||||
instr->vibrato_rate = READ_U8_BOUND(offset + 238, offset + ins_header_size);
|
||||
instr->volume_fadeout = READ_U16_BOUND(offset + 239, offset + ins_header_size);
|
||||
|
||||
instr->samples = (xm_sample_t*)mempool;
|
||||
mempool += instr->num_samples * sizeof(xm_sample_t);
|
||||
} else {
|
||||
instr->samples = NULL;
|
||||
}
|
||||
|
||||
/* Instrument header size */
|
||||
offset += ins_header_size;
|
||||
|
||||
for(uint16_t j = 0; j < instr->num_samples; ++j) {
|
||||
/* Read sample header */
|
||||
xm_sample_t* sample = instr->samples + j;
|
||||
|
||||
sample->length = READ_U32(offset);
|
||||
sample->loop_start = READ_U32(offset + 4);
|
||||
sample->loop_length = READ_U32(offset + 8);
|
||||
sample->loop_end = sample->loop_start + sample->loop_length;
|
||||
sample->volume = (float)READ_U8(offset + 12) / (float)0x40;
|
||||
sample->finetune = (int8_t)READ_U8(offset + 13);
|
||||
|
||||
/* Fix invalid loop definitions */
|
||||
if (sample->loop_start > sample->length)
|
||||
sample->loop_start = sample->length;
|
||||
if (sample->loop_end > sample->length)
|
||||
sample->loop_end = sample->length;
|
||||
sample->loop_length = sample->loop_end - sample->loop_start;
|
||||
|
||||
uint8_t flags = READ_U8(offset + 14);
|
||||
if((flags & 3) == 0 || sample->loop_length == 0) {
|
||||
sample->loop_type = XM_NO_LOOP;
|
||||
} else if((flags & 3) == 1) {
|
||||
sample->loop_type = XM_FORWARD_LOOP;
|
||||
} else {
|
||||
sample->loop_type = XM_PING_PONG_LOOP;
|
||||
}
|
||||
|
||||
sample->bits = (flags & (1 << 4)) ? 16 : 8;
|
||||
|
||||
sample->panning = (float)READ_U8(offset + 15) / (float)0xFF;
|
||||
sample->relative_note = (int8_t)READ_U8(offset + 16);
|
||||
#if XM_STRINGS
|
||||
READ_MEMCPY(sample->name, offset + 18, SAMPLE_NAME_LENGTH);
|
||||
sample->name[SAMPLE_NAME_LENGTH] = 0;
|
||||
#endif
|
||||
sample->data8 = (int8_t*)mempool;
|
||||
mempool += sample->length;
|
||||
|
||||
if(sample->bits == 16) {
|
||||
sample->loop_start >>= 1;
|
||||
sample->loop_length >>= 1;
|
||||
sample->loop_end >>= 1;
|
||||
sample->length >>= 1;
|
||||
}
|
||||
|
||||
/* Notice that, even if there's a "sample header size" in the
|
||||
instrument header, that value seems ignored, and might even
|
||||
be wrong in some corrupted modules. */
|
||||
offset += 40;
|
||||
}
|
||||
|
||||
for(uint16_t j = 0; j < instr->num_samples; ++j) {
|
||||
/* Read sample data */
|
||||
xm_sample_t* sample = instr->samples + j;
|
||||
uint32_t length = sample->length;
|
||||
|
||||
if(sample->bits == 16) {
|
||||
int16_t v = 0;
|
||||
for(uint32_t k = 0; k < length; ++k) {
|
||||
v = v + (int16_t)READ_U16(offset + (k << 1));
|
||||
sample->data16[k] = v;
|
||||
}
|
||||
offset += sample->length << 1;
|
||||
} else {
|
||||
int8_t v = 0;
|
||||
for(uint32_t k = 0; k < length; ++k) {
|
||||
v = v + (int8_t)READ_U8(offset + k);
|
||||
sample->data8[k] = v;
|
||||
}
|
||||
offset += sample->length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mempool;
|
||||
}
|
1428
third-party/libxm/src/play.c
vendored
Normal file
1428
third-party/libxm/src/play.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
third-party/libxm/src/xm.c
vendored
Normal file
9
third-party/libxm/src/xm.c
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/* Author: Romain "Artefact2" Dalmaso <artefact2@gmail.com> */
|
||||
|
||||
/* This program is free software. It comes without any warranty, to the
|
||||
* extent permitted by applicable law. You can redistribute it and/or
|
||||
* modify it under the terms of the Do What The Fuck You Want To Public
|
||||
* License, Version 2, as published by Sam Hocevar. See
|
||||
* http://sam.zoy.org/wtfpl/COPYING for more details. */
|
||||
|
||||
#include "xm_internal.h"
|
317
third-party/libxm/src/xm_internal.h
vendored
Normal file
317
third-party/libxm/src/xm_internal.h
vendored
Normal file
@ -0,0 +1,317 @@
|
||||
/* Author: Romain "Artefact2" Dalmaso <artefact2@gmail.com> */
|
||||
|
||||
/* This program is free software. It comes without any warranty, to the
|
||||
* extent permitted by applicable law. You can redistribute it and/or
|
||||
* modify it under the terms of the Do What The Fuck You Want To Public
|
||||
* License, Version 2, as published by Sam Hocevar. See
|
||||
* http://sam.zoy.org/wtfpl/COPYING for more details. */
|
||||
|
||||
#include <xm.h>
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#if XM_DEBUG
|
||||
#include <stdio.h>
|
||||
#define DEBUG(fmt, ...) do { \
|
||||
fprintf(stderr, "%s(): " fmt "\n", __func__, __VA_ARGS__); \
|
||||
fflush(stderr); \
|
||||
} while(0)
|
||||
#else
|
||||
#define DEBUG(...)
|
||||
#endif
|
||||
|
||||
#if XM_BIG_ENDIAN
|
||||
#error "Big endian platforms are not yet supported, sorry"
|
||||
/* Make sure the compiler stops, even if #error is ignored */
|
||||
extern int __fail[-1];
|
||||
#endif
|
||||
|
||||
/* ----- XM constants ----- */
|
||||
|
||||
#define SAMPLE_NAME_LENGTH 22
|
||||
#define INSTRUMENT_HEADER_LENGTH 263
|
||||
#define INSTRUMENT_NAME_LENGTH 22
|
||||
#define MODULE_NAME_LENGTH 20
|
||||
#define TRACKER_NAME_LENGTH 20
|
||||
#define PATTERN_ORDER_TABLE_LENGTH 256
|
||||
#define NUM_NOTES 96
|
||||
#define NUM_ENVELOPE_POINTS 12
|
||||
#define MAX_NUM_ROWS 256
|
||||
#define DEFAULT_PATTERN_LENGTH 64
|
||||
|
||||
#if XM_RAMPING
|
||||
#define XM_SAMPLE_RAMPING_POINTS 0x20
|
||||
#endif
|
||||
|
||||
/* ----- Data types ----- */
|
||||
|
||||
enum xm_waveform_type_e {
|
||||
XM_SINE_WAVEFORM = 0,
|
||||
XM_RAMP_DOWN_WAVEFORM = 1,
|
||||
XM_SQUARE_WAVEFORM = 2,
|
||||
XM_RANDOM_WAVEFORM = 3,
|
||||
XM_RAMP_UP_WAVEFORM = 4,
|
||||
};
|
||||
typedef enum xm_waveform_type_e xm_waveform_type_t;
|
||||
|
||||
enum xm_loop_type_e {
|
||||
XM_NO_LOOP,
|
||||
XM_FORWARD_LOOP,
|
||||
XM_PING_PONG_LOOP,
|
||||
};
|
||||
typedef enum xm_loop_type_e xm_loop_type_t;
|
||||
|
||||
enum xm_frequency_type_e {
|
||||
XM_LINEAR_FREQUENCIES,
|
||||
XM_AMIGA_FREQUENCIES,
|
||||
};
|
||||
typedef enum xm_frequency_type_e xm_frequency_type_t;
|
||||
|
||||
struct xm_envelope_point_s {
|
||||
uint16_t frame;
|
||||
uint16_t value;
|
||||
};
|
||||
typedef struct xm_envelope_point_s xm_envelope_point_t;
|
||||
|
||||
struct xm_envelope_s {
|
||||
xm_envelope_point_t points[NUM_ENVELOPE_POINTS];
|
||||
uint8_t num_points;
|
||||
uint8_t sustain_point;
|
||||
uint8_t loop_start_point;
|
||||
uint8_t loop_end_point;
|
||||
bool enabled;
|
||||
bool sustain_enabled;
|
||||
bool loop_enabled;
|
||||
};
|
||||
typedef struct xm_envelope_s xm_envelope_t;
|
||||
|
||||
struct xm_sample_s {
|
||||
#if XM_STRINGS
|
||||
char name[SAMPLE_NAME_LENGTH + 1];
|
||||
#endif
|
||||
uint8_t bits; /* Either 8 or 16 */
|
||||
|
||||
uint32_t length;
|
||||
uint32_t loop_start;
|
||||
uint32_t loop_length;
|
||||
uint32_t loop_end;
|
||||
float volume;
|
||||
int8_t finetune;
|
||||
xm_loop_type_t loop_type;
|
||||
float panning;
|
||||
int8_t relative_note;
|
||||
uint64_t latest_trigger;
|
||||
|
||||
union {
|
||||
int8_t* data8;
|
||||
int16_t* data16;
|
||||
};
|
||||
};
|
||||
typedef struct xm_sample_s xm_sample_t;
|
||||
|
||||
struct xm_instrument_s {
|
||||
#if XM_STRINGS
|
||||
char name[INSTRUMENT_NAME_LENGTH + 1];
|
||||
#endif
|
||||
uint16_t num_samples;
|
||||
uint8_t sample_of_notes[NUM_NOTES];
|
||||
xm_envelope_t volume_envelope;
|
||||
xm_envelope_t panning_envelope;
|
||||
xm_waveform_type_t vibrato_type;
|
||||
uint8_t vibrato_sweep;
|
||||
uint8_t vibrato_depth;
|
||||
uint8_t vibrato_rate;
|
||||
uint16_t volume_fadeout;
|
||||
uint64_t latest_trigger;
|
||||
bool muted;
|
||||
|
||||
xm_sample_t* samples;
|
||||
};
|
||||
typedef struct xm_instrument_s xm_instrument_t;
|
||||
|
||||
struct xm_pattern_slot_s {
|
||||
uint8_t note; /* 1-96, 97 = Key Off note */
|
||||
uint8_t instrument; /* 1-128 */
|
||||
uint8_t volume_column;
|
||||
uint8_t effect_type;
|
||||
uint8_t effect_param;
|
||||
};
|
||||
typedef struct xm_pattern_slot_s xm_pattern_slot_t;
|
||||
|
||||
struct xm_pattern_s {
|
||||
uint16_t num_rows;
|
||||
xm_pattern_slot_t* slots; /* Array of size num_rows * num_channels */
|
||||
};
|
||||
typedef struct xm_pattern_s xm_pattern_t;
|
||||
|
||||
struct xm_module_s {
|
||||
#if XM_STRINGS
|
||||
char name[MODULE_NAME_LENGTH + 1];
|
||||
char trackername[TRACKER_NAME_LENGTH + 1];
|
||||
#endif
|
||||
uint16_t length;
|
||||
uint16_t restart_position;
|
||||
uint16_t num_channels;
|
||||
uint16_t num_patterns;
|
||||
uint16_t num_instruments;
|
||||
xm_frequency_type_t frequency_type;
|
||||
uint8_t pattern_table[PATTERN_ORDER_TABLE_LENGTH];
|
||||
|
||||
xm_pattern_t* patterns;
|
||||
xm_instrument_t* instruments; /* Instrument 1 has index 0,
|
||||
* instrument 2 has index 1, etc. */
|
||||
};
|
||||
typedef struct xm_module_s xm_module_t;
|
||||
|
||||
struct xm_channel_context_s {
|
||||
float note;
|
||||
float orig_note; /* The original note before effect modifications, as read in the pattern. */
|
||||
xm_instrument_t* instrument; /* Could be NULL */
|
||||
xm_sample_t* sample; /* Could be NULL */
|
||||
xm_pattern_slot_t* current;
|
||||
|
||||
float sample_position;
|
||||
float period;
|
||||
float frequency;
|
||||
float step;
|
||||
bool ping; /* For ping-pong samples: true is -->, false is <-- */
|
||||
|
||||
float volume; /* Ideally between 0 (muted) and 1 (loudest) */
|
||||
float panning; /* Between 0 (left) and 1 (right); 0.5 is centered */
|
||||
|
||||
uint16_t autovibrato_ticks;
|
||||
|
||||
bool sustained;
|
||||
float fadeout_volume;
|
||||
float volume_envelope_volume;
|
||||
float panning_envelope_panning;
|
||||
uint16_t volume_envelope_frame_count;
|
||||
uint16_t panning_envelope_frame_count;
|
||||
|
||||
float autovibrato_note_offset;
|
||||
|
||||
bool arp_in_progress;
|
||||
uint8_t arp_note_offset;
|
||||
uint8_t volume_slide_param;
|
||||
uint8_t fine_volume_slide_param;
|
||||
uint8_t global_volume_slide_param;
|
||||
uint8_t panning_slide_param;
|
||||
uint8_t portamento_up_param;
|
||||
uint8_t portamento_down_param;
|
||||
uint8_t fine_portamento_up_param;
|
||||
uint8_t fine_portamento_down_param;
|
||||
uint8_t extra_fine_portamento_up_param;
|
||||
uint8_t extra_fine_portamento_down_param;
|
||||
uint8_t tone_portamento_param;
|
||||
float tone_portamento_target_period;
|
||||
uint8_t multi_retrig_param;
|
||||
uint8_t note_delay_param;
|
||||
uint8_t pattern_loop_origin; /* Where to restart a E6y loop */
|
||||
uint8_t pattern_loop_count; /* How many loop passes have been done */
|
||||
bool vibrato_in_progress;
|
||||
xm_waveform_type_t vibrato_waveform;
|
||||
bool vibrato_waveform_retrigger; /* True if a new note retriggers the waveform */
|
||||
uint8_t vibrato_param;
|
||||
uint16_t vibrato_ticks; /* Position in the waveform */
|
||||
float vibrato_note_offset;
|
||||
xm_waveform_type_t tremolo_waveform;
|
||||
bool tremolo_waveform_retrigger;
|
||||
uint8_t tremolo_param;
|
||||
uint8_t tremolo_ticks;
|
||||
float tremolo_volume;
|
||||
uint8_t tremor_param;
|
||||
bool tremor_on;
|
||||
|
||||
uint64_t latest_trigger;
|
||||
bool muted;
|
||||
|
||||
#if XM_RAMPING
|
||||
/* These values are updated at the end of each tick, to save
|
||||
* a couple of float operations on every generated sample. */
|
||||
float target_volume[2];
|
||||
|
||||
unsigned long frame_count;
|
||||
float end_of_previous_sample[XM_SAMPLE_RAMPING_POINTS];
|
||||
#endif
|
||||
|
||||
float actual_volume[2];
|
||||
};
|
||||
typedef struct xm_channel_context_s xm_channel_context_t;
|
||||
|
||||
struct xm_context_s {
|
||||
size_t ctx_size; /* Must be first, see xm_create_context_from_libxmize() */
|
||||
xm_module_t module;
|
||||
uint32_t rate;
|
||||
|
||||
uint16_t tempo;
|
||||
uint16_t bpm;
|
||||
float global_volume;
|
||||
float amplification;
|
||||
|
||||
#if XM_RAMPING
|
||||
/* How much is a channel final volume allowed to change per
|
||||
* sample; this is used to avoid abrubt volume changes which
|
||||
* manifest as "clicks" in the generated sound. */
|
||||
float volume_ramp;
|
||||
#endif
|
||||
|
||||
uint8_t current_table_index;
|
||||
uint8_t current_row;
|
||||
uint16_t current_tick; /* Can go below 255, with high tempo and a pattern delay */
|
||||
float remaining_samples_in_tick;
|
||||
uint64_t generated_samples;
|
||||
|
||||
bool position_jump;
|
||||
bool pattern_break;
|
||||
uint8_t jump_dest;
|
||||
uint8_t jump_row;
|
||||
|
||||
/* Extra ticks to be played before going to the next row -
|
||||
* Used for EEy effect */
|
||||
uint16_t extra_ticks;
|
||||
|
||||
uint8_t* row_loop_count; /* Array of size MAX_NUM_ROWS * module_length */
|
||||
uint8_t loop_count;
|
||||
uint8_t max_loop_count;
|
||||
|
||||
xm_channel_context_t* channels;
|
||||
};
|
||||
|
||||
/* ----- Internal API ----- */
|
||||
|
||||
/** Check the module data for errors/inconsistencies.
|
||||
*
|
||||
* @returns 0 if everything looks OK. Module should be safe to load.
|
||||
*/
|
||||
int xm_check_sanity_preload(const char*, size_t);
|
||||
|
||||
/** Check a loaded module for errors/inconsistencies.
|
||||
*
|
||||
* @returns 0 if everything looks OK.
|
||||
*/
|
||||
int xm_check_sanity_postload(xm_context_t*);
|
||||
|
||||
/** Get the number of bytes needed to store the module data in a
|
||||
* dynamically allocated blank context.
|
||||
*
|
||||
* Things that are dynamically allocated:
|
||||
* - sample data
|
||||
* - sample structures in instruments
|
||||
* - pattern data
|
||||
* - row loop count arrays
|
||||
* - pattern structures in module
|
||||
* - instrument structures in module
|
||||
* - channel contexts
|
||||
* - context structure itself
|
||||
|
||||
* @returns 0 if everything looks OK.
|
||||
*/
|
||||
size_t xm_get_memory_needed_for_context(const char*, size_t);
|
||||
|
||||
/** Populate the context from module data.
|
||||
*
|
||||
* @returns pointer to the memory pool
|
||||
*/
|
||||
char* xm_load_module(xm_context_t*, const char*, size_t, char*);
|
Loading…
Reference in New Issue
Block a user