├── bin └── .placeholder ├── lib └── .placeholder ├── examples ├── data │ └── dxn-oopk.xm ├── gbfs_stdio.h ├── Makefile ├── gbfs.h ├── example2.cpp ├── example.cpp ├── libgbfs.c └── gbfs_stdio.c ├── t ├── toplevel │ ├── env_sustain.xm │ ├── ins_fadeout.xm │ └── dump_render.c ├── framework │ ├── selftest.c │ ├── test_suite.h │ ├── test_suite.c │ ├── test_helpers.c │ ├── test_helpers.h │ ├── test_framework.h │ └── test_framework.c ├── unit_test.c ├── Makefile └── unit │ ├── test_serializer.c │ └── test_mixer.c ├── .ci └── Dockerfile ├── converter ├── serialize_module.h ├── serialize_instrument.h ├── serializer.h ├── serialize_instrument.c ├── serializer.c ├── pimpconv.c └── serialize_module.c ├── .travis.yml ├── src ├── pimp_render.h ├── amiga_delta_lut.h ├── load_module.h ├── pimp_debug.h ├── pimp_sample_bank.h ├── convert_sample.h ├── pimp_sample.h ├── pimp_math.h ├── pimp_envelope.h ├── pimp_config.h ├── pimp_mixer_portable.c ├── pimp_debug.c ├── pimp_base.h ├── pimp_channel_state.h ├── pimp_sample_bank.c ├── convert_sample.c ├── pimp_mixer.h ├── pimp_instrument.h ├── amiga_period_lut.h ├── pimp_envelope.c ├── pimp_module.h ├── pimp_effects.h ├── pimp_mod_context.h ├── pimp_mixer_clip_arm.S ├── pimp_math.c ├── pimp_gba.c ├── pimp_internal.h ├── pimp_mod_context.c ├── linear_delta_lut.h ├── pimp_mixer.c ├── pimp_mixer_arm.S ├── load_mod.c └── pimp_render.c ├── contrib ├── makefs.sh ├── makefs.bat └── lut_gen.py ├── LICENSE.TXT ├── include ├── pimp_types.h └── pimp_gba.h ├── README.md └── Makefile /bin/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/data/dxn-oopk.xm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kusma/pimpmobile/HEAD/examples/data/dxn-oopk.xm -------------------------------------------------------------------------------- /t/toplevel/env_sustain.xm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kusma/pimpmobile/HEAD/t/toplevel/env_sustain.xm -------------------------------------------------------------------------------- /t/toplevel/ins_fadeout.xm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kusma/pimpmobile/HEAD/t/toplevel/ins_fadeout.xm -------------------------------------------------------------------------------- /.ci/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM devkitpro/devkitarm 2 | 3 | RUN apt-get update && \ 4 | apt install -y gcc && \ 5 | apt-get clean && \ 6 | rm -rf /var/lib/apt/lists/* 7 | -------------------------------------------------------------------------------- /converter/serialize_module.h: -------------------------------------------------------------------------------- 1 | #ifndef DUMP_MODULE_H 2 | #define DUMP_MODULE_H 3 | 4 | struct pimp_module; 5 | struct serializer; 6 | 7 | void serialize_module(struct serializer *s, const struct pimp_module *mod); 8 | 9 | #endif /* DUMP_MODULE_H */ 10 | -------------------------------------------------------------------------------- /t/framework/selftest.c: -------------------------------------------------------------------------------- 1 | #include "test_framework.h" 2 | #include "test_helpers.h" 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | struct test_suite suite; 8 | ASSERT_INTS_EQUAL(&suite, 1, 0); 9 | return test_report_file(&suite, stderr); 10 | } 11 | -------------------------------------------------------------------------------- /t/framework/test_suite.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_SUITE_H 2 | #define TEST_SUITE_H 3 | 4 | struct test_suite 5 | { 6 | int test_count; 7 | int fail_count; 8 | int pass_count; 9 | }; 10 | 11 | void test_suite_init(struct test_suite *test_suite); 12 | 13 | #endif /* TEST_SUITE_H */ 14 | -------------------------------------------------------------------------------- /t/framework/test_suite.c: -------------------------------------------------------------------------------- 1 | #include "test_suite.h" 2 | 3 | #include 4 | #include 5 | #define ASSERT assert 6 | 7 | void test_suite_init(struct test_suite *test_suite) 8 | { 9 | ASSERT(NULL != test_suite); 10 | 11 | test_suite->test_count = 0; 12 | test_suite->fail_count = 0; 13 | test_suite->pass_count = 0; 14 | } 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | sudo: required 4 | 5 | services: docker 6 | 7 | install: 8 | - docker build -t build .ci/ 9 | - pip install --user cpp-coveralls 10 | 11 | script: 12 | - docker run -v $TRAVIS_BUILD_DIR:/build build make -C /build all examples check 13 | 14 | after_success: 15 | - coveralls --include src --gcov-options '\-lp' 16 | -------------------------------------------------------------------------------- /src/pimp_render.h: -------------------------------------------------------------------------------- 1 | /* pimp_render.h -- Interface for the actual audio-rendering in Pimpmobile 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_RENDER 7 | #define PIMP_RENDER 8 | 9 | #include "pimp_internal.h" 10 | #include "pimp_mod_context.h" 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | void pimp_render(struct pimp_mod_context *ctx, s8 *buf, u32 samples); 17 | 18 | #ifdef __cplusplus 19 | } 20 | #endif 21 | 22 | #endif /* PIMP_RENDER */ 23 | -------------------------------------------------------------------------------- /src/amiga_delta_lut.h: -------------------------------------------------------------------------------- 1 | #define AMIGA_DELTA_LUT_LOG2_SIZE 7 2 | const u16 pimp_amiga_delta_lut[65] = 3 | { 4 | 55928, 55067, 54233, 53423, 52638, 51875, 51134, 50414, 49713, 49032, 48370, 47725, 5 | 47097, 46485, 45889, 45308, 44742, 44190, 43651, 43125, 42611, 42110, 41621, 41142, 6 | 40675, 40218, 39771, 39334, 38906, 38488, 38078, 37678, 37285, 36901, 36524, 36155, 7 | 35794, 35439, 35092, 34751, 34417, 34089, 33768, 33452, 33142, 32838, 32540, 32247, 8 | 31959, 31676, 31398, 31125, 30857, 30593, 30334, 30079, 29828, 29582, 29339, 29101, 9 | 28866, 28635, 28408, 28184, 27964, 10 | }; 11 | 12 | -------------------------------------------------------------------------------- /contrib/makefs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # makefs.sh -- File system generator for Pimpmobile examples 3 | # Copyright (C) 2006-2007 Jørn Nystad and Erik Faye-Lund 4 | # For conditions of distribution and use, see copyright notice in LICENSE.TXT 5 | 6 | # check parameters 7 | if [ -z "$@" ] 8 | then 9 | echo "USAGE: makefs.bat filename.(mod/xm/s3m)" 10 | exit 1 11 | fi 12 | 13 | # convert 14 | bin/converter $@ 15 | [ $? -ne 0 ] && exit 1 16 | 17 | # make filesystem 18 | gbfs data.gbfs sample_bank.bin `echo $@ | sed -e "s/[^ ]\+/\0.bin/g"` 19 | 20 | # append filesystem to rom 21 | cat bin/example.bin > example.gba 22 | cat data.gbfs >> example.gba 23 | -------------------------------------------------------------------------------- /src/load_module.h: -------------------------------------------------------------------------------- 1 | /* load_module.h -- Module loader header file 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef LOAD_MODULE_H 7 | #define LOAD_MODULE_H 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include /* needed for FILE */ 14 | 15 | struct pimp_module; 16 | struct pimp_sample_bank; 17 | 18 | struct pimp_module *load_module_mod(FILE *fp, struct pimp_sample_bank *sample_bank); 19 | struct pimp_module *load_module_xm(FILE *fp, struct pimp_sample_bank *sample_bank); 20 | 21 | #ifdef __cplusplus 22 | } 23 | #endif 24 | 25 | #endif /* LOAD_MODULE_H */ 26 | -------------------------------------------------------------------------------- /t/framework/test_helpers.c: -------------------------------------------------------------------------------- 1 | #include "test_helpers.h" 2 | #include "test_framework.h" 3 | 4 | #include 5 | #include 6 | 7 | void test_int_array(struct test_suite *suite, const int *array, const int *reference, int size, const char *file, int line) 8 | { 9 | char temp[1024]; 10 | int err = 0; 11 | 12 | int i; 13 | for (i = 0; i < size; ++i) 14 | { 15 | char val = array[i]; 16 | char ref = reference[i]; 17 | if (val != ref) 18 | { 19 | snprintf(temp, 1024, "%s:%d -- element #%d not equal, got %X - expected %X", file, line, i + 1, val, ref); 20 | err = 1; 21 | break; 22 | } 23 | } 24 | 25 | if (0 != err) test_fail(suite, temp); 26 | else test_pass(suite); 27 | } 28 | -------------------------------------------------------------------------------- /t/unit_test.c: -------------------------------------------------------------------------------- 1 | /* unit_test.c -- Main entry for Pimpmobile unit tests 2 | * Copyright (C) 2007 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include "framework/test_framework.h" 7 | #include "framework/test_helpers.h" 8 | #include 9 | 10 | void test_mixer(struct test_suite *suite); 11 | void test_serializer(struct test_suite *suite); 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | /* setup test suite */ 16 | struct test_suite suite; 17 | test_suite_init(&suite); 18 | 19 | /* run tests */ 20 | TEST_RUN(&suite, test_mixer); 21 | TEST_RUN(&suite, test_serializer); 22 | 23 | /* report */ 24 | return test_report_file(&suite, stderr); 25 | } 26 | -------------------------------------------------------------------------------- /t/framework/test_helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_HELPERS_H 2 | #define TEST_HELPERS_H 3 | 4 | #include "test_framework.h" 5 | void test_int_array(struct test_suite *suite, const int *array, const int *reference, int size, const char *file, int line); 6 | 7 | #define TEST_RUN(suite, func) test_run(suite, func, #func); 8 | 9 | #define ASSERT_INTS_EQUAL(suite, value, expected) ASSERT_MSG(suite, (value) == (expected), test_printf(suite, "ints not equal, got %d - expected %d (%s:%d)", (int)(value), (int)(expected), __FILE__, __LINE__)) 10 | #define ASSERT_INT_ARRAYS_EQUAL(suite, array, reference, size) test_int_array(suite, array, reference, size, __FILE__, __LINE__) 11 | 12 | #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) 13 | 14 | #endif /* TEST_HELPERS_H */ 15 | -------------------------------------------------------------------------------- /converter/serialize_instrument.h: -------------------------------------------------------------------------------- 1 | /* serialize_instrument.h -- Serializer for instrument data 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef DUMP_INSTRUMENT_H 7 | #define DUMP_INSTRUMENT_H 8 | 9 | struct serializer; 10 | 11 | /* serialize a sample */ 12 | int serialize_sample(struct serializer *s, const struct pimp_sample *samp); 13 | 14 | /* serialize all samples in an instrument */ 15 | void serialize_instrument_data(struct serializer *s, const struct pimp_instrument *instr); 16 | 17 | /* serialize instrument structure */ 18 | void serialize_instrument(struct serializer *s, const struct pimp_instrument *instr); 19 | 20 | #endif /* DUMP_INSTRUMENT_H */ 21 | -------------------------------------------------------------------------------- /t/framework/test_framework.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_FRAMEWORK_H 2 | #define TEST_FRAMEWORK_H 3 | 4 | #include "test_suite.h" 5 | 6 | void test_run(struct test_suite *suite, void (*test_func)(struct test_suite *), const char *name); 7 | 8 | int test_fail(struct test_suite *suite, const char *error); 9 | int test_pass(struct test_suite *suite); 10 | char *test_printf(struct test_suite *suite, const char* fmt, ...); 11 | 12 | #include 13 | int test_report_file(struct test_suite *suite, FILE *fp); 14 | 15 | /* #define ASSERT(suite, expr) (!(expr) ? test_fail(test_printf(suite, "ASSERT(%s) failed (%s:%d)", #expr, __FILE__, __LINE__)) : test_pass()) */ 16 | #define ASSERT_MSG(suite, expr, error) (!(expr) ? test_fail(suite, error) : test_pass(suite)) 17 | 18 | #endif /* TEST_FRAMEWORK_H */ 19 | -------------------------------------------------------------------------------- /contrib/makefs.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM makefs.bat -- File system generator for Pimpmobile examples 3 | REM Copyright (C) 2006-2007 Jørn Nystad and Erik Faye-Lund 4 | REM For conditions of distribution and use, see copyright notice in LICENSE.TXT 5 | 6 | 7 | REM check parameters 8 | if %1x==x goto usage 9 | 10 | REM generate filename lists 11 | set _infiles= 12 | set _outfiles= 13 | :start 14 | if %1x==x goto end 15 | echo %1 16 | set _infiles=%_infiles% %1 17 | set _outfiles=%_outfiles% %1.bin 18 | shift 19 | goto start 20 | :end 21 | 22 | REM convert 23 | bin\converter %_infiles% 24 | if not %ERRORLEVEL%==0 goto :EOF 25 | 26 | REM make filesystem 27 | gbfs data.gbfs sample_bank.bin %_outfiles% 28 | 29 | REM append filesystem to rom 30 | copy /B bin\example.bin+data.gbfs example.gba 31 | 32 | goto :EOF 33 | 34 | :usage 35 | echo USAGE: makefs.bat filename.(mod/xm/s3m) 36 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006 Jørn Nystad and Erik Faye-Lund 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you 12 | must not claim that you wrote the original software. If you use 13 | this software in a product, an acknowledgment in the product 14 | documentation would be appreciated but is not required. 15 | 16 | 2. Altered source versions must be plainly marked as such, and 17 | must not be misrepresented as being the original software. 18 | 19 | 3. This notice may not be removed or altered from any source 20 | distribution. 21 | -------------------------------------------------------------------------------- /src/pimp_debug.h: -------------------------------------------------------------------------------- 1 | /* pimp_debug.h -- Debugging helpers 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_DEBUG_H 7 | #define PIMP_DEBUG_H 8 | 9 | /* current debug level */ 10 | #define DEBUG_LEVEL 100 11 | 12 | /* some standard debug levels */ 13 | #define DEBUG_LEVEL_INFO 10 14 | #define DEBUG_LEVEL_WARNING 50 15 | #define DEBUG_LEVEL_ERROR 100 16 | 17 | #ifdef DEBUG_PRINT_ENABLE 18 | #define DEBUG_PRINT(debug_level, X) do { if (DEBUG_LEVEL <= debug_level) iprintf X; } while(0) 19 | #else 20 | #define DEBUG_PRINT(debug_level, X) 21 | #endif 22 | 23 | #ifdef ASSERT_ENABLE 24 | #include 25 | #include 26 | #define ASSERT(expr) \ 27 | do { \ 28 | if (!(expr)) \ 29 | { \ 30 | fprintf(stderr, "*** ASSERTION \"%s\" FAILED AT %s:%d\n", #expr, __FILE__, __LINE__); \ 31 | abort(); \ 32 | } \ 33 | } while(0) 34 | #else 35 | #define ASSERT(expr) 36 | #endif 37 | 38 | #endif /* PIMP_DEBUG_H */ 39 | -------------------------------------------------------------------------------- /src/pimp_sample_bank.h: -------------------------------------------------------------------------------- 1 | /* pimp_sample_bank.h -- Sample databse for Pimpmobile 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_SAMPLE_BANK_H 7 | #define PIMP_SAMPLE_BANK_H 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include "pimp_base.h" 14 | #include "pimp_debug.h" 15 | 16 | struct pimp_sample_bank 17 | { 18 | void *data; 19 | pimp_size_t size; 20 | }; 21 | 22 | void pimp_sample_bank_init(struct pimp_sample_bank *sample_bank); 23 | 24 | static INLINE void *pimp_sample_bank_get_sample_data(const struct pimp_sample_bank *sample_bank, int offset) 25 | { 26 | ASSERT(NULL != sample_bank); 27 | ASSERT(offset < sample_bank->size); 28 | 29 | return (void*)((u8*)sample_bank->data + offset); 30 | } 31 | 32 | int pimp_sample_bank_find_sample_data(const struct pimp_sample_bank *sample_bank, void *data, pimp_size_t len); 33 | int pimp_sample_bank_insert_sample_data(struct pimp_sample_bank *sample_bank, void *data, pimp_size_t len); 34 | 35 | #ifdef __cplusplus 36 | } 37 | #endif 38 | 39 | #endif /* PIMP_SAMPLE_BANK_H */ 40 | -------------------------------------------------------------------------------- /src/convert_sample.h: -------------------------------------------------------------------------------- 1 | /* convert_sample.h -- Sample converter header 2 | * Copyright (C) 2006-2007 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef CONVERT_SAMPLE_H 7 | #define CONVERT_SAMPLE_H 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include /* for size_t */ 14 | #include "../src/pimp_base.h" 15 | #include "../src/pimp_debug.h" 16 | 17 | enum pimp_sample_format 18 | { 19 | PIMP_SAMPLE_U8, 20 | PIMP_SAMPLE_S8, 21 | PIMP_SAMPLE_U16, 22 | PIMP_SAMPLE_S16 23 | }; 24 | 25 | static INLINE size_t pimp_sample_format_get_size(enum pimp_sample_format format) 26 | { 27 | switch (format) 28 | { 29 | case PIMP_SAMPLE_U8: return sizeof(u8); 30 | case PIMP_SAMPLE_S8: return sizeof(s8); 31 | case PIMP_SAMPLE_U16: return sizeof(u16); 32 | case PIMP_SAMPLE_S16: return sizeof(s16); 33 | default: 34 | ASSERT(FALSE); 35 | return 0; 36 | } 37 | } 38 | 39 | void pimp_convert_sample(void *dst, enum pimp_sample_format dst_format, void *src, enum pimp_sample_format src_format, size_t size); 40 | 41 | #ifdef __cplusplus 42 | } 43 | #endif 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/pimp_sample.h: -------------------------------------------------------------------------------- 1 | /* pimp_sample.h -- sample structure for Pimpmobile 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_SAMPLE_H 7 | #define PIMP_SAMPLE_H 8 | 9 | 10 | typedef enum 11 | { 12 | SAMPLE_VIBRATO_SINE, 13 | SAMPLE_VIBRATO_RAMP_DOWN, 14 | SAMPLE_VIBRATO_SQUARE, 15 | SAMPLE_VIBRATO_RANDOM, /* supported by impulse-tracker but not FT2 */ 16 | SAMPLE_VIBRATO_RAMP_UP /* supported by FT2 but not impulse-tracker */ 17 | } pimp_vibrato_waveform; 18 | 19 | 20 | struct pimp_sample 21 | { 22 | /* offset relative to sample-bank */ 23 | pimp_rel_ptr data_ptr; 24 | 25 | u32 length; 26 | u32 loop_start; 27 | u32 loop_length; 28 | 29 | /* 30 | IT ONLY (later) 31 | u32 sustain_loop_start; 32 | u32 sustain_loop_end; 33 | */ 34 | s16 fine_tune; 35 | s16 rel_note; 36 | 37 | u8 volume; 38 | u8 loop_type; 39 | u8 pan; 40 | 41 | u8 vibrato_speed; 42 | u8 vibrato_depth; 43 | u8 vibrato_sweep; 44 | u8 vibrato_waveform; 45 | 46 | /* 47 | IT ONLY (later) 48 | u8 sustain_loop_type; 49 | */ 50 | }; 51 | 52 | 53 | #endif /* PIMP_SAMPLE_H */ 54 | -------------------------------------------------------------------------------- /src/pimp_math.h: -------------------------------------------------------------------------------- 1 | /* pimp_math.h -- Math routines for use in Pimpmobile 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_MATH_H 7 | #define PIMP_MATH_H 8 | 9 | #include "pimp_config.h" 10 | #include "pimp_base.h" 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | extern const unsigned char pimp_clz_lut[256]; 17 | 18 | static INLINE unsigned pimp_clz16(unsigned input) 19 | { 20 | /* one iteration of binary search */ 21 | unsigned c = 0; 22 | 23 | if (input & 0xFF00) input >>= 8; 24 | else c += 8; 25 | 26 | /* a 256 entries lut ain't too bad... */ 27 | return pimp_clz_lut[input] + c; 28 | } 29 | 30 | #ifndef NO_LINEAR_PERIODS 31 | unsigned pimp_get_linear_delta(unsigned int period, unsigned int delta_scale); 32 | unsigned pimp_get_linear_period(int note, int fine_tune); 33 | #endif 34 | 35 | #ifndef NO_AMIGA_PERIODS 36 | unsigned pimp_get_amiga_delta(unsigned period, unsigned int delta_scale); 37 | unsigned pimp_get_amiga_period(int note, int fine_tune); 38 | #endif 39 | 40 | #ifdef __cplusplus 41 | } 42 | #endif 43 | 44 | #endif /* PIMP_MATH_H */ 45 | -------------------------------------------------------------------------------- /src/pimp_envelope.h: -------------------------------------------------------------------------------- 1 | /* pimp_envelope.h -- Envelope data structure and routines 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_ENVELOPE_H 7 | #define PIMP_ENVELOPE_H 8 | 9 | #include "pimp_internal.h" 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | struct pimp_envelope 16 | { 17 | u16 node_tick[25]; 18 | s16 node_magnitude[25]; 19 | s16 node_delta[25]; 20 | 21 | u8 node_count; 22 | u8 flags; /* bit 0: loop enable, bit 1: sustain loop enable */ 23 | u8 loop_start, loop_end; 24 | u8 sustain_loop_start, sustain_loop_end; 25 | }; 26 | 27 | struct pimp_envelope_state 28 | { 29 | const struct pimp_envelope *env; 30 | s8 current_node; 31 | u32 current_tick; 32 | }; 33 | 34 | static INLINE void pimp_envelope_reset(struct pimp_envelope_state *state) 35 | { 36 | state->current_node = 0; 37 | state->current_tick = 0; 38 | } 39 | 40 | int pimp_envelope_sample(struct pimp_envelope_state *state); 41 | void pimp_envelope_advance_tick(struct pimp_envelope_state *state, BOOL sustain); 42 | void pimp_envelope_set_tick(struct pimp_envelope_state *state, int tick); 43 | 44 | #ifdef __cplusplus 45 | } 46 | #endif 47 | 48 | #endif /* PIMP_ENVELOPE_H */ 49 | -------------------------------------------------------------------------------- /examples/gbfs_stdio.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | gbfs_stdio.h -- devkitARM stdio integration interface for GBFS 4 | 5 | Copyright (c) 2007 Erik Faye-Lund 6 | 7 | This software is provided 'as-is', without any express or implied warranty. In 8 | no event will the authors be held liable for any damages arising from the use 9 | of this software. 10 | 11 | Permission is granted to anyone to use this software for any purpose, including 12 | commercial applications, and to alter it and redistribute it freely, subject to 13 | the following restrictions: 14 | 15 | 1. The origin of this software must not be misrepresented; you must not 16 | claim that you wrote the original software. If you use this software in 17 | a product, an acknowledgment in the product documentation would be 18 | appreciated but is not required. 19 | 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 23 | 3. This notice may not be removed or altered from any source distribution. 24 | 25 | */ 26 | 27 | #ifndef GBFS_STDIO_H 28 | #define GBFS_STDIO_H 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | int gbfs_init(int set_default); 35 | 36 | #ifdef __cplusplus 37 | } 38 | #endif 39 | 40 | #endif /* GBFS_STDLIB_H */ 41 | -------------------------------------------------------------------------------- /src/pimp_config.h: -------------------------------------------------------------------------------- 1 | /* pimp_config.h -- Compile-time configuration of Pimpmobile 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_CONFIG_H 7 | #define PIMP_CONFIG_H 8 | 9 | /* 32 is the maximum amount of channels in fasttracker2. a nice default. */ 10 | #define PIMP_CHANNEL_COUNT 32 11 | 12 | /* check the sample-rate calculator at http://www.pineight.com/gba/samplerates/ for more glitch-free periods */ 13 | 14 | #if 0 15 | #define PIMP_GBA_PERIOD (532) /* 31536.12 */ 16 | #define PIMP_GBA_PERIOD (627) /* 26757.92 */ 17 | #define PIMP_GBA_PERIOD (798) /* 21024.08 */ 18 | #define PIMP_GBA_PERIOD (836) /* 20068.44 */ 19 | #endif 20 | #define PIMP_GBA_PERIOD (924) /* 18157.16 */ 21 | #if 0 22 | #define PIMP_GBA_PERIOD (1254) /* 13378.96 */ 23 | #define PIMP_GBA_PERIOD (1463) /* 11467.68 */ 24 | #define PIMP_GBA_PERIOD (1596) /* 10512.04 */ 25 | #endif 26 | 27 | /* enable / disable assert */ 28 | /* #define DEBUG_PRINT_ENABLE */ 29 | /* #define ASSERT_ENABLE */ 30 | /* #define PRINT_PATTERNS */ 31 | 32 | #define PIMP_MIXER_IRQ_SAFE /* on by default */ 33 | #define PIMP_MIXER_USE_BRESENHAM_MIXER 34 | /* #define PIMP_MIXER_NO_MIXING */ 35 | /* #define PIMP_MIXER_NO_CLIPPING */ 36 | 37 | #endif /* PIMP_CONFIG_H */ 38 | -------------------------------------------------------------------------------- /src/pimp_mixer_portable.c: -------------------------------------------------------------------------------- 1 | /* pimp_mixer_portable.c -- A portable audio mixer 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include "pimp_base.h" 7 | #include "pimp_mixer.h" 8 | #include "pimp_debug.h" 9 | 10 | void pimp_mixer_clear(s32 *target, u32 samples) 11 | { 12 | int i; 13 | ASSERT(target != 0); 14 | 15 | for (i = 0; i < samples; ++i) 16 | { 17 | target[i] = 0; 18 | } 19 | } 20 | 21 | u32 pimp_mixer_mix_samples(s32 *target, u32 samples, const u8 *sample_data, u32 vol, u32 sample_cursor, s32 sample_cursor_delta) 22 | { 23 | int i; 24 | ASSERT(target != 0); 25 | ASSERT(sample_data != 0); 26 | 27 | for (i = 0; i < samples; ++i) 28 | { 29 | s32 samp = sample_data[sample_cursor >> 12]; 30 | sample_cursor += sample_cursor_delta; 31 | target[i] += samp * vol; 32 | } 33 | 34 | return sample_cursor; 35 | } 36 | 37 | void pimp_mixer_clip_samples(s8 *target, const s32 *source, u32 samples, u32 dc_offs) 38 | { 39 | int i; 40 | ASSERT(target != NULL); 41 | ASSERT(source != NULL); 42 | 43 | for (i = 0; i < samples; ++i) 44 | { 45 | s32 samp = source[i]; 46 | samp = (samp >> 8) - dc_offs; 47 | if (samp > 127) samp = 127; 48 | if (samp < -128) samp = -128; 49 | target[i] = samp; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/pimp_debug.c: -------------------------------------------------------------------------------- 1 | /* pimp_debug.c -- Debugging helpers 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include 7 | #include "pimp_internal.h" 8 | #include "pimp_debug.h" 9 | 10 | #if 0 11 | void print_pattern_entry(const pimp_pattern_entry *pe) 12 | { 13 | ASSERT(pe != NULL); 14 | 15 | if (pe->note != 0) 16 | { 17 | const int o = (pe->note - 1) / 12; 18 | const int n = (pe->note - 1) % 12; 19 | /* C, C#, D, D#, E, F, F#, G, G, A, A#, B */ 20 | iprintf("%c%c%X ", 21 | "CCDDEFFGGAAB"[n], 22 | "-#-#--#-#-#-"[n], o); 23 | } 24 | else iprintf("--- "); 25 | 26 | /* iprintf("%02X ", pe->volume_command); 27 | iprintf("%02X ", pe->effect_byte); */ 28 | iprintf("%02X %02X %X%02X\t", pe->instrument, pe->volume_command, pe->effect_byte, pe->effect_parameter); 29 | } 30 | 31 | void print_pattern(const pimp_module *mod, pimp_pattern *pat) 32 | { 33 | pimp_pattern_entry *pd = get_pattern_data(pat); 34 | 35 | iprintf("row count: %02x\n", pat->row_count); 36 | 37 | for (unsigned i = 0; i < 5; ++i) 38 | { 39 | for (unsigned j = 0; j < 4; ++j) 40 | { 41 | pimp_pattern_entry *pe = &pd[i * mod->channel_count + j]; 42 | print_pattern_entry(pe); 43 | } 44 | iprintf("\n"); 45 | } 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/pimp_base.h: -------------------------------------------------------------------------------- 1 | /* pimp_base.h -- Some base defines used in Pimpmobile 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_BASE_H 7 | #define PIMP_BASE_H 8 | 9 | #ifndef PIMP_DONT_DECLARE_BASIC_TYPES 10 | typedef signed char s8; 11 | typedef unsigned char u8; 12 | typedef signed short s16; 13 | typedef unsigned short u16; 14 | typedef signed int s32; 15 | typedef unsigned int u32; 16 | #endif 17 | typedef signed long long s64; 18 | typedef unsigned long long u64; 19 | 20 | #include 21 | typedef size_t pimp_size_t; 22 | typedef ptrdiff_t pimp_rel_ptr; 23 | 24 | #ifndef NULL 25 | #define NULL ((void*)0) 26 | #endif 27 | 28 | #ifndef BOOL 29 | #define BOOL int 30 | #endif 31 | 32 | #ifndef TRUE 33 | #define TRUE ((BOOL)1) 34 | #endif 35 | 36 | #ifndef FALSE 37 | #define FALSE ((BOOL)0) 38 | #endif 39 | 40 | #ifndef MAX 41 | #define MAX(x, y) ((x) > (y) ? (x) : (y)) 42 | #endif 43 | 44 | #ifndef MIN 45 | #define MIN(x, y) ((x) < (y) ? (x) : (y)) 46 | #endif 47 | 48 | #ifndef INLINE 49 | #define INLINE __inline 50 | #endif 51 | 52 | #ifndef UNIT_TESTING 53 | #define STATIC static 54 | #else 55 | #define STATIC 56 | #endif 57 | 58 | #ifndef PURE 59 | #define PURE __attribute__((pure)) 60 | #endif 61 | 62 | #endif /* PIMP_BASE_H */ 63 | -------------------------------------------------------------------------------- /src/pimp_channel_state.h: -------------------------------------------------------------------------------- 1 | /* pimp_channel_state.h -- The state for each module-channel 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_CHANNEL_STATE_H 7 | #define PIMP_CHANNEL_STATE_H 8 | 9 | #include "pimp_base.h" 10 | #include "pimp_envelope.h" 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | struct pimp_instrument; 17 | struct pimp_sample; 18 | 19 | struct pimp_channel_state 20 | { 21 | /* some current-states */ 22 | const struct pimp_instrument *instrument; 23 | const struct pimp_sample *sample; 24 | 25 | struct pimp_envelope_state vol_env; 26 | BOOL sustain; 27 | 28 | u8 note; 29 | u8 effect; 30 | u8 effect_param; 31 | u8 volume_command; 32 | 33 | s32 note_period; 34 | s32 period; 35 | 36 | s8 volume; 37 | u8 pan; 38 | 39 | /* vibrato states */ 40 | u8 vibrato_speed; 41 | u8 vibrato_depth; 42 | u8 vibrato_waveform; 43 | u8 vibrato_counter; 44 | 45 | /* pattern loop states */ 46 | u8 loop_target_order; 47 | u8 loop_target_row; 48 | u8 loop_counter; 49 | 50 | s32 porta_target; 51 | s32 fadeout; 52 | u16 porta_speed; 53 | s8 volume_slide_speed; 54 | u8 note_delay; 55 | 56 | u8 note_retrig; 57 | u8 retrig_tick; 58 | }; 59 | 60 | #ifdef __cplusplus 61 | } 62 | #endif 63 | 64 | #endif /* PIMP_CHANNEL_STATE_H */ 65 | -------------------------------------------------------------------------------- /converter/serializer.h: -------------------------------------------------------------------------------- 1 | /* serializer.h -- low-level serializer for pimpconv 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef SERIALIZER_H 7 | #define SERIALIZER_H 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include 14 | #include 15 | 16 | struct reloc { 17 | unsigned pos; 18 | void *ptr; 19 | }; 20 | 21 | struct serializer 22 | { 23 | unsigned buffer_size; 24 | unsigned char *data; 25 | unsigned pos; 26 | struct reloc *relocs; 27 | int num_relocs; 28 | }; 29 | 30 | void serializer_init(struct serializer *s); 31 | void serializer_deinit(struct serializer *s); 32 | 33 | void serializer_check_size(struct serializer *s, size_t needed_size); 34 | void serializer_align(struct serializer *s, int alignment); 35 | void serializer_set_pointer(struct serializer *s, void *ptr, int pos); 36 | void serializer_fixup_pointers(struct serializer *s); 37 | 38 | void serialize_byte(struct serializer *s, uint8_t b); 39 | void serialize_halfword(struct serializer *s, uint16_t h); 40 | void serialize_word(struct serializer *s, uint32_t w); 41 | void serialize_string(struct serializer *s, const char *str, const size_t len); 42 | void serialize_pointer(struct serializer *s, void *ptr); 43 | 44 | #ifdef __cplusplus 45 | } 46 | #endif 47 | 48 | #endif /* SERIALIZER_H */ 49 | -------------------------------------------------------------------------------- /t/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for pimpmobile module player 2 | # Copyright (C) 2005-2007 Jørn Nystad and Erik Faye-Lund 3 | # For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | 5 | CC = gcc 6 | CPPFLAGS = -I.. -DUNIT_TESTING -DDEBUG 7 | CFLAGS = -g -fprofile-arcs -ftest-coverage 8 | 9 | ifdef COMSPEC 10 | EXE_EXT=.exe 11 | else 12 | EXE_EXT= 13 | endif 14 | 15 | FRAMEWORK_SOURCES = \ 16 | framework/test_framework.c \ 17 | framework/test_helpers.c \ 18 | framework/test_suite.c 19 | 20 | TEST_SOURCES = \ 21 | unit_test.c \ 22 | unit/test_mixer.c \ 23 | unit/test_serializer.c 24 | 25 | TEST_DEPS_SOURCES = \ 26 | ../src/pimp_mixer.c \ 27 | ../src/pimp_mod_context.c \ 28 | ../src/pimp_mixer_portable.c \ 29 | ../src/pimp_envelope.c \ 30 | ../src/pimp_math.c \ 31 | ../src/pimp_render.c \ 32 | ../src/load_xm.c \ 33 | ../src/convert_sample.c \ 34 | ../src/pimp_sample_bank.c 35 | 36 | .PHONY: clean all run 37 | all: unit_test$(EXE_EXT) dump_render$(EXE_EXT) 38 | 39 | clean: 40 | $(RM) unit_test$(EXE_EXT) dump_render$(EXE_EXT) 41 | 42 | run: unit_test$(EXE_EXT) 43 | ./unit_test$(EXE_EXT) 44 | 45 | unit_test$(EXE_EXT): $(TEST_SOURCES) $(TEST_DEPS_SOURCES) $(FRAMEWORK_SOURCES) ../converter/serializer.c 46 | $(LINK.c) $^$(LOADLIBES) $(LDLIBS) -o $@ 47 | 48 | dump_render$(EXE_EXT): toplevel/dump_render.c $(TEST_DEPS_SOURCES) 49 | $(LINK.c) $^$(LOADLIBES) $(LDLIBS) -o $@ 50 | 51 | %.wav : %.sb 52 | sox -r 44100 $< $@ 53 | -------------------------------------------------------------------------------- /src/pimp_sample_bank.c: -------------------------------------------------------------------------------- 1 | /* pimp_sample_bank.c -- Sample databse for Pimpmobile 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include "pimp_sample_bank.h" 7 | #include "pimp_debug.h" 8 | 9 | #include 10 | #include 11 | 12 | void pimp_sample_bank_init(struct pimp_sample_bank *sample_bank) 13 | { 14 | sample_bank->data = NULL; 15 | sample_bank->size = 0; 16 | } 17 | 18 | int pimp_sample_bank_find_sample_data(const struct pimp_sample_bank *sample_bank, void *data, pimp_size_t len) 19 | { 20 | pimp_size_t i; 21 | ASSERT(NULL != sample_bank); 22 | 23 | if (len > sample_bank->size) return -1; /* not found */ 24 | for (i = 0; i < (sample_bank->size - len) + 1; ++i) 25 | { 26 | if (0 == memcmp(data, pimp_sample_bank_get_sample_data(sample_bank, i), len)) return i; 27 | } 28 | 29 | return -1; /* not found */ 30 | } 31 | 32 | int pimp_sample_bank_insert_sample_data(struct pimp_sample_bank *sample_bank, void *data, pimp_size_t len) 33 | { 34 | int pos = sample_bank->size; 35 | 36 | /* allocate more memory */ 37 | void *new_data = realloc( 38 | sample_bank->data, 39 | sample_bank->size + len 40 | ); 41 | if (NULL == new_data) return -1; 42 | 43 | /* update structure */ 44 | sample_bank->data = new_data; 45 | sample_bank->size += len; 46 | 47 | /* copy data */ 48 | { 49 | void *dst = (void*)((u8*)sample_bank->data + pos); 50 | memcpy(dst, data, len); 51 | } 52 | 53 | return pos; 54 | } 55 | -------------------------------------------------------------------------------- /include/pimp_types.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | pimp_types.h -- The type-definitions of Pimpmobile, a module playback library 4 | targeting the Nintendo GameBoy Advance 5 | 6 | 7 | Copyright (c) 2005 Jørn Nystad and Erik Faye-Lund 8 | 9 | This software is provided 'as-is', without any express or implied warranty. In 10 | no event will the authors be held liable for any damages arising from the use 11 | of this software. 12 | 13 | Permission is granted to anyone to use this software for any purpose, including 14 | commercial applications, and to alter it and redistribute it freely, subject to 15 | the following restrictions: 16 | 17 | 1. The origin of this software must not be misrepresented; you must not claim 18 | that you wrote the original software. If you use this software in a product, 19 | an acknowledgment in the product documentation would be appreciated but is 20 | not required. 21 | 22 | 2. Altered source versions must be plainly marked as such, and must not be 23 | misrepresented as being the original software. 24 | 25 | 3. This notice may not be removed or altered from any source distribution. 26 | 27 | */ 28 | 29 | #ifndef PIMP_TYPES_H 30 | #define PIMP_TYPES_H 31 | 32 | #ifdef __cplusplus 33 | extern "C" 34 | { 35 | #endif 36 | 37 | struct pimp_module; 38 | typedef void (*pimp_callback)(int, int); 39 | 40 | enum pimp_callback_type 41 | { 42 | PIMP_CALLBACK_LOOP = 0, 43 | PIMP_CALLBACK_SYNC = 1, 44 | PIMP_CALLBACK_NOTE = 2, 45 | PIMP_CALLBACK_UNSUPPORTED_EFFECT = 3, 46 | PIMP_CALLBACK_UNSUPPORTED_VOLUME_EFFECT = 4 47 | }; 48 | 49 | 50 | #ifdef __cplusplus 51 | } 52 | #endif 53 | 54 | #endif /* PIMP_TYPES_H */ 55 | -------------------------------------------------------------------------------- /src/convert_sample.c: -------------------------------------------------------------------------------- 1 | /* convert_sample.c -- Sample converter code 2 | * Copyright (C) 2006-2007 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include "pimp_base.h" 7 | #include "pimp_debug.h" 8 | #include "convert_sample.h" 9 | 10 | void pimp_convert_sample(void *dst, enum pimp_sample_format dst_format, void *src, enum pimp_sample_format src_format, size_t sample_count) 11 | { 12 | size_t i; 13 | 14 | ASSERT(NULL != src); 15 | ASSERT(NULL != dst); 16 | ASSERT(src != dst); 17 | 18 | ASSERT(dst_format == PIMP_SAMPLE_U8); 19 | 20 | for (i = 0; i < sample_count; ++i) 21 | { 22 | s32 new_sample = 0; 23 | 24 | /* fetch data, and get it to PIMP_SAMPLE_S16 format */ 25 | switch (src_format) 26 | { 27 | case PIMP_SAMPLE_U8: new_sample = ((s32)((s8*) src)[i] << 8) - (1 << 15); break; 28 | case PIMP_SAMPLE_S8: new_sample = ((s32)((s8*) src)[i] << 8) - (0 << 15); break; 29 | case PIMP_SAMPLE_U16: new_sample = ((s32)((u16*)src)[i] << 0) - (1 << 15); break; 30 | case PIMP_SAMPLE_S16: new_sample = ((s32)((s16*)src)[i] << 0) - (0 << 15); break; 31 | default: ASSERT(FALSE); 32 | } 33 | 34 | /* write back from S16 format */ 35 | switch (dst_format) 36 | { 37 | case PIMP_SAMPLE_U8: ((u8*) dst)[i] = (u8) ((new_sample + (1 << 15)) >> 8); break; 38 | case PIMP_SAMPLE_S8: ((s8*) dst)[i] = (s8) ((new_sample + (0 << 15)) >> 8); break; 39 | case PIMP_SAMPLE_U16: ((u16*)dst)[i] = (u16)((new_sample + (1 << 15)) >> 0); break; 40 | case PIMP_SAMPLE_S16: ((s16*)dst)[i] = (s16)((new_sample + (0 << 15)) >> 0); break; 41 | default: ASSERT(FALSE); 42 | } 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pimpmobile 2 | 3 | [![Build Status](https://travis-ci.org/kusma/pimpmobile.svg?branch=master)](https://travis-ci.org/kusma/pimpmobile) 4 | [![License: Zlib](https://img.shields.io/badge/License-Zlib-blue.svg)](https://opensource.org/licenses/Zlib) 5 | [![Coverage Status](https://coveralls.io/repos/github/kusma/pimpmobile/badge.svg?branch=master)](https://coveralls.io/github/kusma/pimpmobile?branch=master) 6 | 7 | Pimpmobile is a high performance sample-based music playback engine 8 | targeting the Nintendo Game Boy Advance. It is released under the 9 | Zlib license - a permissive open source license allowing commercial 10 | use. Please note that even though you are allowed to keep 11 | modifications to the source code, we strongly recommend giving 12 | notable improvement to the source code back to us. 13 | 14 | ## Features 15 | 16 | ### Currently implemented 17 | * File formats: Protracker MOD, FastTracker II XM 18 | * Most MOD effects, some XM effects 19 | * Good sound-quality - no "cheats" 20 | * Un-cached ARM7 optimized inner-loop 21 | * Relatively low IWRAM-usage (depends on config, around 4k in the default setup) 22 | * Separate sample-banks for sample-sharing across modules 23 | * Module sync-callbacks (E8x and Wxx) 24 | * Binary-only datafiles, generated by offline converter 25 | * All limitations are compile-time defines. No hard limits on channels etc. 26 | * Rather clean C code, with some ARM assembly optimizations. 27 | * Builds with (and depends on) devkitARM 28 | 29 | ### Planned 30 | 31 | * File formats: ScreamTracker S3M, ImpulseTracker IT 32 | * Documentation ;-) 33 | * Game-type sound effects, by reserving sample-channels 34 | * On-target file-loader (more convenient for libfat etc) 35 | * ARM RVDS builds 36 | * Extensive test-suite 37 | -------------------------------------------------------------------------------- /src/pimp_mixer.h: -------------------------------------------------------------------------------- 1 | /* pimp_mixer.h -- The state and interface for the various mixers 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_MIXER_H 7 | #define PIMP_MIXER_H 8 | 9 | #include "pimp_base.h" 10 | #include "pimp_config.h" 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | enum pimp_mixer_loop_type 17 | { 18 | LOOP_TYPE_NONE, 19 | LOOP_TYPE_FORWARD, 20 | LOOP_TYPE_PINGPONG 21 | }; 22 | 23 | struct pimp_mixer_channel_state 24 | { 25 | u32 sample_length; 26 | u32 loop_start; 27 | u32 loop_end; 28 | enum pimp_mixer_loop_type loop_type; 29 | const u8 *sample_data; 30 | u32 sample_cursor; 31 | s32 sample_cursor_delta; 32 | s32 volume; 33 | }; 34 | 35 | int pimp_mixer_detect_loop_event(const struct pimp_mixer_channel_state *chan, int samples); 36 | 37 | struct pimp_mixer 38 | { 39 | struct pimp_mixer_channel_state channels[PIMP_CHANNEL_COUNT]; 40 | s32 *mix_buffer; 41 | }; 42 | 43 | void pimp_mixer_reset(struct pimp_mixer *mixer); 44 | void pimp_mixer_mix(struct pimp_mixer *mixer, s8 *target, int samples); 45 | void pimp_mixer_mix_channel(struct pimp_mixer_channel_state *chan, s32 *target, u32 samples); 46 | 47 | void pimp_mixer_clear(s32 *target, u32 samples); 48 | u32 pimp_mixer_mix_samples(s32 *target, u32 samples, const u8 *sample_data, u32 vol, u32 sample_cursor, s32 sample_cursor_delta); 49 | void pimp_mixer_clip_samples(s8 *target, const s32 *source, u32 samples, u32 dc_offs); 50 | 51 | 52 | #ifdef __cplusplus 53 | } 54 | #endif 55 | 56 | #endif /* PIMP_MIXER_H */ 57 | -------------------------------------------------------------------------------- /t/toplevel/dump_render.c: -------------------------------------------------------------------------------- 1 | #include "../../src/pimp_mod_context.h" 2 | #include "../../src/pimp_sample_bank.h" 3 | #include "../../src/pimp_render.h" 4 | #include "../../src/load_module.h" 5 | 6 | #include 7 | #include 8 | 9 | #define SAMPLES 100000 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | int i; 14 | pimp_mixer mixer; 15 | pimp_mod_context ctx; 16 | struct pimp_sample_bank sample_bank; 17 | 18 | s32 mixbuf[SAMPLES]; 19 | signed char buf[SAMPLES]; 20 | 21 | FILE *fp; 22 | char *ifn, *ofn; 23 | const pimp_module *mod; 24 | 25 | /* check parameters */ 26 | if (argc < 3) 27 | { 28 | puts("too few arguments"); 29 | exit(1); 30 | } 31 | 32 | ifn = argv[1]; 33 | ofn = argv[2]; 34 | 35 | /* open input file */ 36 | fp = fopen(ifn, "rb"); 37 | if (NULL == fp) 38 | { 39 | fprintf(stderr, "*** failed to open %s for reading\n", ifn); 40 | exit(1); 41 | } 42 | 43 | /* load module */ 44 | pimp_sample_bank_init(&sample_bank); 45 | mod = load_module_xm(fp, &sample_bank); 46 | 47 | /* close input file */ 48 | fclose(fp); 49 | fp = NULL; 50 | 51 | /* check if module got loaded */ 52 | if (NULL == mod) 53 | { 54 | fprintf(stderr, "*** failed to load module %s\n", ifn); 55 | exit(1); 56 | } 57 | 58 | /* setup rendering */ 59 | mixer.mix_buffer = mixbuf; 60 | pimp_mod_context_init(&ctx, mod, (const u8*)sample_bank.data, &mixer, 44100.0f); 61 | 62 | /* render module to buffer */ 63 | pimp_render(&ctx, buf, SAMPLES); 64 | 65 | /* open output file for writing */ 66 | fp = fopen(ofn, "wb"); 67 | if (NULL == fp) 68 | { 69 | fprintf(stderr, "*** failed to open %s for writing\n", ofn); 70 | exit(1); 71 | } 72 | 73 | /* write buf to file */ 74 | fwrite(buf, 1, SAMPLES, fp); 75 | 76 | /* close file */ 77 | fclose(fp); 78 | fp = NULL; 79 | 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /include/pimp_gba.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | pimp_gba.h -- The interface of Pimpmobile, a module playback library 4 | targeting the Nintendo GameBoy Advance 5 | 6 | 7 | Copyright (c) 2005 Jørn Nystad and Erik Faye-Lund 8 | 9 | This software is provided 'as-is', without any express or implied warranty. In 10 | no event will the authors be held liable for any damages arising from the use 11 | of this software. 12 | 13 | Permission is granted to anyone to use this software for any purpose, including 14 | commercial applications, and to alter it and redistribute it freely, subject to 15 | the following restrictions: 16 | 17 | 1. The origin of this software must not be misrepresented; you must not claim 18 | that you wrote the original software. If you use this software in a product, 19 | an acknowledgment in the product documentation would be appreciated but is 20 | not required. 21 | 22 | 2. Altered source versions must be plainly marked as such, and must not be 23 | misrepresented as being the original software. 24 | 25 | 3. This notice may not be removed or altered from any source distribution. 26 | 27 | */ 28 | 29 | #ifndef PIMP_GBA_H 30 | #define PIMP_GBA_H 31 | 32 | #ifdef __cplusplus 33 | extern "C" 34 | { 35 | #endif 36 | 37 | #include "pimp_types.h" 38 | 39 | void pimp_gba_init(const struct pimp_module *module, const void *sample_bank); 40 | void pimp_gba_close(void); 41 | 42 | void pimp_gba_vblank(void); /* call this on the beginning of each vsync */ 43 | void pimp_gba_frame(void); /* call once each frame. doesn't need to be called in precious vblank time */ 44 | 45 | /* get information about playback */ 46 | int pimp_gba_get_row(void); 47 | int pimp_gba_get_order(void); 48 | void pimp_gba_set_pos(int row, int order); 49 | 50 | /* callback system (for music sync) */ 51 | void pimp_gba_set_callback(pimp_callback callback); 52 | 53 | #ifdef __cplusplus 54 | } 55 | #endif 56 | 57 | #endif /* PIMP_GBA_H */ 58 | -------------------------------------------------------------------------------- /src/pimp_instrument.h: -------------------------------------------------------------------------------- 1 | /* pimp_instrument.h -- Instrument data structure and getter functions 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_INSTRUMENT_H 7 | #define PIMP_INSTRUMENT_H 8 | 9 | #include "pimp_base.h" 10 | 11 | struct pimp_instrument 12 | { 13 | pimp_rel_ptr sample_ptr; 14 | pimp_rel_ptr vol_env_ptr; 15 | pimp_rel_ptr pan_env_ptr; 16 | 17 | #if 0 /* IT ONLY (later) */ 18 | pimp_rel_ptr pitch_env_ptr; 19 | #endif 20 | 21 | u16 volume_fadeout; 22 | 23 | u8 sample_count; /* number of samples tied to instrument */ 24 | u8 sample_map[120]; 25 | 26 | #if 0 /* IT ONLY (later) */ 27 | new_note_action_t new_note_action; 28 | duplicate_check_type_t duplicate_check_type; 29 | duplicate_check_action_t duplicate_check_action; 30 | 31 | s8 pitch_pan_separation; /* no idea what this one does */ 32 | u8 pitch_pan_center; /* not this on either; this one seems to be a note index */ 33 | #endif 34 | }; 35 | 36 | #include "pimp_internal.h" 37 | #include "pimp_debug.h" 38 | #include "pimp_envelope.h" 39 | #include "pimp_sample.h" 40 | 41 | static INLINE struct pimp_sample *pimp_instrument_get_sample(const struct pimp_instrument *instr, int i) 42 | { 43 | ASSERT(instr != NULL); 44 | return &((struct pimp_sample*)pimp_get_ptr(&instr->sample_ptr))[i]; 45 | } 46 | 47 | static INLINE struct pimp_envelope *pimp_instrument_get_vol_env(const struct pimp_instrument *instr) 48 | { 49 | ASSERT(instr != NULL); 50 | return (struct pimp_envelope*)(instr->vol_env_ptr == 0 ? NULL : pimp_get_ptr(&instr->vol_env_ptr)); 51 | } 52 | 53 | static INLINE struct pimp_envelope *pimp_instrument_get_pan_env(const struct pimp_instrument *instr) 54 | { 55 | ASSERT(instr != NULL); 56 | return (struct pimp_envelope*)(instr->pan_env_ptr == 0 ? NULL : pimp_get_ptr(&instr->pan_env_ptr)); 57 | } 58 | 59 | #endif /* PIMP_INSTRUMENT_H */ 60 | -------------------------------------------------------------------------------- /t/framework/test_framework.c: -------------------------------------------------------------------------------- 1 | #include "test_framework.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define ASSERT assert 8 | 9 | #define MAX_TEST_PRINTF_STRING_LEN 4096 10 | 11 | void test_run(struct test_suite *suite, void (*test_func)(struct test_suite *), const char *name) 12 | { 13 | int failures_before; 14 | int failures_after; 15 | 16 | ASSERT(NULL != suite); 17 | ASSERT(NULL != test_func); 18 | 19 | failures_before = suite->fail_count; 20 | test_func(suite); 21 | failures_after = suite->fail_count; 22 | 23 | if (failures_before != failures_after) 24 | { 25 | printf("test \"%s\" failed.\n", name); 26 | } 27 | } 28 | 29 | char *test_printf(struct test_suite *suite, const char* fmt, ...) 30 | { 31 | char temp[MAX_TEST_PRINTF_STRING_LEN]; 32 | char *string; 33 | int len = 0; 34 | va_list arglist; 35 | 36 | va_start(arglist, fmt); 37 | len = vsnprintf(temp, MAX_TEST_PRINTF_STRING_LEN, fmt, arglist); 38 | va_end(arglist); 39 | 40 | string = malloc(len + 1); 41 | if (NULL != string) 42 | { 43 | va_start(arglist, fmt); 44 | vsnprintf(string, len + 1, fmt, arglist); 45 | va_end(arglist); 46 | string[len] = 0; /* ensure string termination */ 47 | } 48 | 49 | return string; 50 | } 51 | 52 | int test_fail(struct test_suite *suite, const char *error) 53 | { 54 | ASSERT(NULL != suite); 55 | 56 | printf("TEST #%d FAILED: %s\n", suite->test_count + 1, error); 57 | suite->test_count++; 58 | suite->fail_count++; 59 | 60 | return suite->test_count; 61 | } 62 | 63 | int test_pass(struct test_suite *suite) 64 | { 65 | ASSERT(NULL != suite); 66 | 67 | suite->test_count++; 68 | suite->pass_count++; 69 | 70 | return suite->test_count; 71 | } 72 | 73 | int test_report_file(struct test_suite *suite, FILE *fp) 74 | { 75 | ASSERT(NULL != suite); 76 | ASSERT(NULL != fp); 77 | 78 | fprintf(fp, "%d/%d tests failed\n", suite->fail_count, suite->test_count); 79 | return suite->fail_count; 80 | } 81 | -------------------------------------------------------------------------------- /src/amiga_period_lut.h: -------------------------------------------------------------------------------- 1 | const u16 pimp_amiga_period_lut[] = 2 | { 3 | /* this is for negative fine-tunes... */ 4 | 907,900,894,887,881,875,868,862, /* B-0 */ 5 | 6 | 856,850,844,838,832,826,820,814, /* C-1 */ 7 | 808,802,796,791,785,779,774,768, /* C#1 */ 8 | 762,757,752,746,741,736,730,725, /* D-1 */ 9 | 720,715,709,704,699,694,689,684, /* D#1 */ 10 | 678,674,670,665,660,655,651,646, /* E-1 */ 11 | 640,637,632,628,623,619,614,610, /* F-1 */ 12 | 604,601,597,592,588,584,580,575, /* F#1 */ 13 | 570,567,563,559,555,551,547,543, /* G-1 */ 14 | 538,535,532,528,524,520,516,513, /* G#1 */ 15 | 508,505,502,498,495,491,487,484, /* A-1 */ 16 | 480,477,474,470,467,463,460,457, /* A#1 */ 17 | 453,450,447,444,441,437,434,431, /* B-1 */ 18 | 19 | 428,425,422,419,416,413,410,407, /* C-2 */ 20 | 404,401,398,395,392,390,387,384, /* C#2 */ 21 | 381,379,376,373,370,368,365,363, /* D-2 */ 22 | 360,357,355,352,350,347,345,342, /* D#2 */ 23 | 339,337,335,332,330,328,325,323, /* E-2 */ 24 | 320,318,316,314,312,309,307,305, /* F-2 */ 25 | 302,300,298,296,294,292,290,288, /* F#2 */ 26 | 285,284,282,280,278,276,274,272, /* G-2 */ 27 | 269,268,266,264,262,260,258,256, /* G#2 */ 28 | 254,253,251,249,247,245,244,242, /* A-2 */ 29 | 240,239,237,235,233,232,230,228, /* A#2 */ 30 | 226,225,224,222,220,219,217,216, /* B-2 */ 31 | 32 | 214,213,211,209,208,206,205,204, /* C-3 */ 33 | 202,201,199,198,196,195,193,192, /* C#3 */ 34 | 190,189,188,187,185,184,183,181, /* D-3 */ 35 | 180,179,177,176,175,174,172,171, /* D#3 */ 36 | 170,169,167,166,165,164,163,161, /* E-3 */ 37 | 160,159,158,157,156,155,154,152, /* F-3 */ 38 | 151,150,149,148,147,146,145,144, /* F#3 */ 39 | 143,142,141,140,139,138,137,136, /* G-3 */ 40 | 135,134,133,132,131,130,129,128, /* G#3 */ 41 | 127,126,125,125,124,123,122,121, /* A-3 */ 42 | 120,119,118,118,117,116,115,114, /* A#3 */ 43 | /* 113,113,112,111,110,109,109,108, */ /* B-3 */ 44 | 45 | /* this is for positive fine-tunes */ 46 | /* 107,106,105,104,104,103,102,102,*/ /* C-4 */ 47 | }; 48 | -------------------------------------------------------------------------------- /contrib/lut_gen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: latin-1 -*- 3 | # lut_gen.cpp -- Look-up table generator for Pimpmobile 4 | # Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 5 | # For conditions of distribution and use, see copyright notice in LICENSE.TXT 6 | 7 | AMIGA_DELTA_LUT_LOG2_SIZE = 7 8 | AMIGA_DELTA_LUT_SIZE = 1 << AMIGA_DELTA_LUT_LOG2_SIZE 9 | AMIGA_DELTA_LUT_FRAC_BITS = 15 - AMIGA_DELTA_LUT_LOG2_SIZE 10 | 11 | def print_lut_to_file(file, lutname, lut): 12 | max_elem = reduce(max, lut) 13 | min_elem = reduce(min, lut) 14 | assert(min_elem >= 0) 15 | assert(max_elem < (1 << 16)) 16 | file.write('const u16 %s[%d] =\n{\n\t' % (lutname, len(lut))) 17 | line_start = file.tell() 18 | for e in lut: 19 | file.write('%d, ' % e) 20 | if file.tell() > (line_start + 80): 21 | file.write('\n\t') 22 | line_start = file.tell() 23 | file.write('\n};\n\n') 24 | 25 | 26 | AMIGA_C4_FREQ = 8363.0 27 | AMIGA_C4_PERIOD = 1712 28 | NOTES_PER_OCTAVE = 12 29 | FINETUNES_PER_NOTE = 64 30 | FINETUNES_PER_OCTAVE = NOTES_PER_OCTAVE * FINETUNES_PER_NOTE 31 | 32 | def linear_delta_entry(note): 33 | temp = 2.0 ** (float(note) / FINETUNES_PER_OCTAVE) 34 | return int((temp * AMIGA_C4_FREQ) * 2 + 0.5) 35 | 36 | def amiga_delta_entry(fine_note): 37 | temp = float(((fine_note + (AMIGA_DELTA_LUT_SIZE / 2)) * (1 << 15)) / AMIGA_DELTA_LUT_SIZE) 38 | return int(((AMIGA_C4_FREQ * AMIGA_C4_PERIOD) / temp) * (1 << 6) + 0.5) 39 | 40 | def gen_linear_delta_lut(): 41 | return [linear_delta_entry(i) for i in range(0, FINETUNES_PER_OCTAVE)] 42 | 43 | def gen_amiga_delta_lut(): 44 | return [amiga_delta_entry(i) for i in range(0, (AMIGA_DELTA_LUT_SIZE / 2) + 1)] 45 | 46 | def dump_linear_lut(filename): 47 | linear_delta_lut = gen_linear_delta_lut() 48 | f = open(filename, 'w') 49 | print_lut_to_file(f, 'pimp_linear_delta_lut', linear_delta_lut) 50 | f.close() 51 | 52 | def dump_amiga_lut(filename): 53 | amiga_delta_lut = gen_amiga_delta_lut() 54 | f = open(filename, 'w') 55 | f.write('#define AMIGA_DELTA_LUT_LOG2_SIZE %d\n' % (AMIGA_DELTA_LUT_LOG2_SIZE)) 56 | print_lut_to_file(f, 'pimp_amiga_delta_lut', amiga_delta_lut) 57 | f.close() 58 | 59 | dump_linear_lut('src/linear_delta_lut.h') 60 | dump_amiga_lut('src/amiga_delta_lut.h') 61 | -------------------------------------------------------------------------------- /src/pimp_envelope.c: -------------------------------------------------------------------------------- 1 | /* pimp_envelope.c -- Envelope routines 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include "pimp_envelope.h" 7 | #include "pimp_debug.h" 8 | 9 | #include 10 | 11 | static int pimp_envelope_find_node(const struct pimp_envelope *env, int tick) 12 | { 13 | int i; 14 | 15 | ASSERT(NULL != env); 16 | ASSERT(tick >= 0); 17 | 18 | for (i = 1; i < env->node_count; ++i) 19 | { 20 | if (env->node_tick[i] > tick) 21 | { 22 | return i - 1; 23 | } 24 | } 25 | 26 | return env->node_count - 1; 27 | } 28 | 29 | int pimp_envelope_sample(struct pimp_envelope_state *state) 30 | { 31 | s32 delta; 32 | u32 internal_tick; 33 | int val; 34 | 35 | ASSERT(NULL != state); 36 | 37 | /* the magnitude of the envelope at tick N: 38 | * first, find the last node at or before tick N - its position is M 39 | * then, the magnitude of the envelope at tick N is given by 40 | * magnitude = node_magnitude[M] + ((node_delta[M] * (N - M)) >> 8) 41 | */ 42 | 43 | delta = state->env->node_delta[state->current_node]; 44 | internal_tick = state->current_tick - state->env->node_tick[state->current_node]; 45 | 46 | val = state->env->node_magnitude[state->current_node]; 47 | val += ((long long)delta * internal_tick) >> 9; 48 | 49 | return val << 2; 50 | } 51 | 52 | void pimp_envelope_advance_tick(struct pimp_envelope_state *state, BOOL sustain) 53 | { 54 | ASSERT(NULL != state); 55 | 56 | /* advance a tick */ 57 | state->current_tick++; 58 | 59 | /* check for sustain loop */ 60 | if ((state->env->flags & (1 << 1)) && (sustain == TRUE)) 61 | { 62 | if (state->current_tick >= state->env->sustain_loop_end) 63 | { 64 | state->current_node = state->env->sustain_loop_start; 65 | state->current_tick = state->env->node_tick[state->current_node]; 66 | } 67 | } 68 | 69 | /* check if we have passed the current node 70 | * we don't need to clamp the envelope-pos to the end, since the last delta is zero 71 | */ 72 | if (state->current_node < (state->env->node_count - 1)) 73 | { 74 | if (state->current_tick >= state->env->node_tick[state->current_node + 1]) 75 | { 76 | state->current_node++; 77 | } 78 | } 79 | } 80 | 81 | void pimp_envelope_set_tick(struct pimp_envelope_state *state, int tick) 82 | { 83 | state->current_node = pimp_envelope_find_node(state->env, tick); 84 | state->current_tick = tick; 85 | } 86 | -------------------------------------------------------------------------------- /src/pimp_module.h: -------------------------------------------------------------------------------- 1 | /* pimp_module.h -- module data structure and getter functions 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_MODULE_H 7 | #define PIMP_MODULE_H 8 | 9 | #include "pimp_base.h" 10 | #include "pimp_instrument.h" 11 | 12 | struct pimp_pattern 13 | { 14 | pimp_rel_ptr data_ptr; 15 | u16 row_count; 16 | }; 17 | 18 | /* packed, because it's all bytes. no member-alignment or anything needed */ 19 | struct pimp_channel 20 | { 21 | u8 pan; 22 | u8 volume; 23 | u8 mute; 24 | } __attribute__((packed)); 25 | 26 | typedef struct pimp_module 27 | { 28 | char name[32]; 29 | 30 | u32 flags; 31 | u32 reserved; /* for future flags */ 32 | 33 | pimp_rel_ptr order_ptr; 34 | pimp_rel_ptr pattern_ptr; 35 | pimp_rel_ptr channel_ptr; 36 | pimp_rel_ptr instrument_ptr; 37 | 38 | u16 period_low_clamp; 39 | u16 period_high_clamp; 40 | u16 order_count; 41 | 42 | u8 order_repeat; 43 | u8 volume; 44 | u8 tempo; 45 | u8 bpm; 46 | 47 | u8 instrument_count; 48 | u8 pattern_count; 49 | u8 channel_count; 50 | } pimp_module; 51 | 52 | #include "pimp_debug.h" 53 | #include "pimp_internal.h" 54 | 55 | /* pattern entry */ 56 | static INLINE struct pimp_pattern_entry *pimp_pattern_get_data(const struct pimp_pattern *pat) 57 | { 58 | ASSERT(pat != NULL); 59 | return (struct pimp_pattern_entry*)pimp_get_ptr(&pat->data_ptr); 60 | } 61 | 62 | static INLINE int pimp_module_get_order(const pimp_module *mod, int i) 63 | { 64 | char *array; 65 | ASSERT(mod != NULL); 66 | 67 | array = (char*)pimp_get_ptr(&mod->order_ptr); 68 | if (NULL == array) return -1; 69 | 70 | return array[i]; 71 | } 72 | 73 | static INLINE struct pimp_pattern *pimp_module_get_pattern(const pimp_module *mod, int i) 74 | { 75 | ASSERT(mod != NULL); 76 | return &((struct pimp_pattern*)pimp_get_ptr(&mod->pattern_ptr))[i]; 77 | } 78 | 79 | static INLINE struct pimp_channel *pimp_module_get_channel(const pimp_module *mod, int i) 80 | { 81 | ASSERT(mod != NULL); 82 | return &((struct pimp_channel*)pimp_get_ptr(&mod->channel_ptr))[i]; 83 | } 84 | 85 | static INLINE struct pimp_instrument *pimp_module_get_instrument(const pimp_module *mod, int i) 86 | { 87 | struct pimp_instrument *array; 88 | ASSERT(mod != NULL); 89 | 90 | array = (struct pimp_instrument*)pimp_get_ptr(&mod->instrument_ptr); 91 | if (NULL == array) return (struct pimp_instrument*)NULL; 92 | 93 | return &(array[i]); 94 | } 95 | 96 | #endif /* PIMP_MODULE_H */ 97 | -------------------------------------------------------------------------------- /src/pimp_effects.h: -------------------------------------------------------------------------------- 1 | /* pimp_effects.h -- Implementations of the actual effects 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_EFFECTS_H 7 | #define PIMP_EFFECTS_H 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #include "pimp_internal.h" 14 | 15 | static void porta_up(struct pimp_channel_state *chan, s32 period_low_clamp) 16 | { 17 | ASSERT(chan != 0); 18 | 19 | chan->period -= chan->porta_speed; 20 | if (chan->period < period_low_clamp) chan->period = period_low_clamp; 21 | } 22 | 23 | static void porta_down(struct pimp_channel_state *chan, s32 period_high_clamp) 24 | { 25 | ASSERT(chan != 0); 26 | 27 | chan->period += chan->porta_speed; 28 | if (chan->period > period_high_clamp) chan->period = period_high_clamp; 29 | } 30 | 31 | static void porta_note(struct pimp_channel_state *chan) 32 | { 33 | ASSERT(chan != 0); 34 | 35 | if (chan->period > chan->porta_target) 36 | { 37 | chan->period -= chan->porta_speed; 38 | if (chan->period < chan->porta_target) chan->period = chan->porta_target; 39 | } 40 | else if (chan->period < chan->porta_target) 41 | { 42 | chan->period += chan->porta_speed; 43 | if (chan->period > chan->porta_target) chan->period = chan->porta_target; 44 | } 45 | } 46 | 47 | static void volume_slide(struct pimp_channel_state *chan, int speed) 48 | { 49 | chan->volume += speed; 50 | if (chan->volume > 64) chan->volume = 64; 51 | if (chan->volume < 0) chan->volume = 0; 52 | } 53 | 54 | static void vibrato(struct pimp_channel_state *chan, s32 period_low_clamp, s32 period_high_clamp) 55 | { 56 | static const s16 sine_waveform[64] = { 57 | 0, 24, 49, 74, 97, 120, 141, 161, 58 | 180, 197, 212, 224, 235, 244, 250, 253, 59 | 255, 253, 250, 244, 235, 224, 212, 197, 60 | 180, 161, 141, 120, 97, 74, 49, 24, 61 | -0, -24, -49, -74, -97, -120, -141, -161, 62 | -180, -197, -212, -224, -235, -244, -250, -253, 63 | -255, -253, -250, -244, -235, -224, -212, -197, 64 | -180, -161, -141, -120, -97, -74, -49, -24 65 | }; 66 | 67 | chan->period = chan->note_period + (sine_waveform[chan->vibrato_counter & 63] * chan->vibrato_depth) / 32; 68 | 69 | if (chan->period < period_low_clamp) chan->period = period_low_clamp; 70 | if (chan->period > period_high_clamp) chan->period = period_high_clamp; 71 | 72 | chan->vibrato_counter += chan->vibrato_speed; 73 | } 74 | 75 | #ifdef __cplusplus 76 | } 77 | #endif 78 | 79 | #endif /* PIMP_EFFECTS_H */ 80 | -------------------------------------------------------------------------------- /src/pimp_mod_context.h: -------------------------------------------------------------------------------- 1 | /* pimp_mod_context.h -- The rendering-context for a module 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_MOD_CONTEXT_H 7 | #define PIMP_MOD_CONTEXT_H 8 | 9 | #include "../include/pimp_types.h" /* needed for pimp_callback */ 10 | #include "pimp_module.h" 11 | #include "pimp_mixer.h" 12 | 13 | #include "pimp_instrument.h" 14 | #include "pimp_envelope.h" 15 | #include "pimp_channel_state.h" 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | struct pimp_mod_context 22 | { 23 | u32 tick_len; 24 | u32 curr_tick_len; 25 | u32 remainder; 26 | 27 | s32 curr_row; 28 | s32 curr_order; 29 | 30 | u32 next_row; 31 | u32 next_order; 32 | 33 | u32 curr_bpm; 34 | u32 curr_tempo; 35 | u32 curr_tick; 36 | s32 global_volume; /* 24.8 fixed point */ 37 | 38 | struct pimp_pattern *curr_pattern; 39 | struct pimp_pattern *next_pattern; 40 | 41 | struct pimp_channel_state channels[PIMP_CHANNEL_COUNT]; 42 | 43 | const u8 *sample_bank; 44 | const pimp_module *mod; 45 | struct pimp_mixer *mixer; 46 | 47 | float samplerate; 48 | unsigned int delta_scale; 49 | 50 | pimp_callback callback; 51 | }; 52 | 53 | void pimp_mod_context_init(struct pimp_mod_context *ctx, const pimp_module *mod, const u8 *sample_bank, struct pimp_mixer *mixer, const float samplerate); 54 | void pimp_mod_context_set_samplerate(struct pimp_mod_context *ctx, const float samplerate); 55 | 56 | void pimp_mod_context_set_bpm(struct pimp_mod_context *ctx, int bpm); 57 | void pimp_mod_context_set_tempo(struct pimp_mod_context *ctx, int tempo); 58 | 59 | /* position manipulation */ 60 | void pimp_mod_context_set_pos(struct pimp_mod_context *ctx, int row, int order); 61 | void pimp_mod_context_set_next_pos(struct pimp_mod_context *ctx, int row, int order); 62 | void pimp_mod_context_update_next_pos(struct pimp_mod_context *ctx); 63 | 64 | static INLINE int pimp_mod_context_get_row(const struct pimp_mod_context *ctx) 65 | { 66 | return ctx->curr_row; 67 | } 68 | 69 | static INLINE int pimp_mod_context_get_order(const struct pimp_mod_context *ctx) 70 | { 71 | return ctx->curr_order; 72 | } 73 | 74 | static INLINE int pimp_mod_context_get_bpm(const struct pimp_mod_context *ctx) 75 | { 76 | return ctx->curr_bpm; 77 | } 78 | 79 | static INLINE int pimp_mod_context_get_tempo(const struct pimp_mod_context *ctx) 80 | { 81 | return ctx->curr_tempo; 82 | } 83 | 84 | #ifdef __cplusplus 85 | } 86 | #endif 87 | 88 | #endif /* PIMP_MOD_CONTEXT_H */ 89 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for pimpmobile module player examples 2 | # Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | # For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | 5 | ifeq ($(strip $(DEVKITPRO)),) 6 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=devkitPro") 7 | endif 8 | 9 | ifdef COMSPEC 10 | EXE_EXT=.exe 11 | else 12 | EXE_EXT= 13 | endif 14 | 15 | DEVKITARM = $(DEVKITPRO)/devkitARM 16 | LIBGBA = $(DEVKITPRO)/libgba 17 | export PATH := $(DEVKITARM)/bin:$(DEVKITPRO)/tools/bin:$(PATH) 18 | 19 | PIMPMOBILE = .. 20 | 21 | PREFIX ?= arm-none-eabi- 22 | CC = $(PREFIX)gcc 23 | CXX = $(PREFIX)g++ 24 | OBJCOPY = $(PREFIX)objcopy 25 | STRIP = $(PREFIX)strip 26 | LD = $(PREFIX)g++ 27 | AS = $(PREFIX)as 28 | AR = $(PREFIX)ar 29 | 30 | CPPFLAGS = -I$(DEVKITARM)/include -I$(LIBGBA)/include -I$(PIMPMOBILE)/include 31 | TARGET_COMMON = -mcpu=arm7tdmi -mtune=arm7tdmi -mthumb-interwork 32 | CFLAGS = $(TARGET_COMMON) -mlong-calls 33 | CXXFLAGS = $(TARGET_COMMON) -mlong-calls -fconserve-space -fno-rtti -fno-exceptions 34 | LDFLAGS = $(TARGET_COMMON) -Wl,--gc-section 35 | ASFLAGS = $(TARGET_COMMON) 36 | LDLIBS = -L$(PIMPMOBILE)/lib -lpimp_gba -L$(LIBGBA)/lib -lgba 37 | TARGET_ARCH = -specs=gba.specs 38 | CONVERTER = $(PIMPMOBILE)/bin/pimpconv$(EXE_EXT) 39 | 40 | ARM = -marm 41 | THUMB = -mthumb 42 | 43 | EXAMPLES = example example2 44 | 45 | .PHONY: all clean run 46 | all: $(EXAMPLES:=.gba) 47 | 48 | example-data.gbfs: data/dxn-oopk.xm 49 | $(CONVERTER) $< 50 | gbfs $@ sample_bank.bin $(<:=.bin) 51 | 52 | example2-data.gbfs: data/dxn-oopk.xm 53 | 54 | EXTRA_OBJS = \ 55 | libgbfs.o \ 56 | gbfs_stdio.o \ 57 | ../src/load_xm.o \ 58 | ../src/convert_sample.o \ 59 | ../src/pimp_sample_bank.o 60 | 61 | ifeq ($(DEBUG), 1) 62 | CPPFLAGS += -DDEBUG 63 | CXXFLAGS += -g3 -ggdb 64 | CFLAGS += -g3 -ggdb 65 | else 66 | CPPFLAGS += -DRELEASE -DNDEBUG 67 | CXXFLAGS += -O3 -fomit-frame-pointer 68 | CFLAGS += -O3 -fomit-frame-pointer 69 | endif 70 | 71 | clean: 72 | $(RM) $(EXAMPLES:=.gba) $(EXAMPLES:=-data.gbfs) $(EXTRA_OBJS) \ 73 | $(EXTRA_OBJS:.o=.d) sample_bank.bin 74 | 75 | %.gbfs: 76 | gbfs $@ $< 77 | 78 | %.elf: %.cpp $(EXTRA_OBJS) 79 | $(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@ 80 | 81 | %.bin: %.elf 82 | $(OBJCOPY) -O binary $< $@ 83 | padbin 256 $@ 84 | gbafix $@ -t$(basename $@) 85 | 86 | %.gba: %.bin %-data.gbfs 87 | cat $^ > $@ 88 | 89 | %.o: %.cpp 90 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(THUMB) -c $< -o $@ -MMD -MP -MF $(@:.o=.d) 91 | 92 | %.o: %.c 93 | $(CC) $(CPPFLAGS) $(CFLAGS) $(THUMB) -c $< -o $@ -MMD -MP -MF $(@:.o=.d) 94 | 95 | -include $(OBJS:.o=.d) 96 | -------------------------------------------------------------------------------- /examples/gbfs.h: -------------------------------------------------------------------------------- 1 | /* gbfs.h 2 | access object in a GBFS file 3 | 4 | Copyright 2002 Damian Yerrick 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 | AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 23 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | IN THE SOFTWARE. 25 | 26 | */ 27 | 28 | 29 | /* Dependency on prior include files 30 | 31 | Before you #include "gbfs.h", you should define the following types: 32 | typedef (unsigned 16-bit integer) u16; 33 | typedef (unsigned 32-bit integer) u32; 34 | Your gba.h should do this for you. 35 | */ 36 | 37 | #ifndef INCLUDE_GBFS_H 38 | #define INCLUDE_GBFS_H 39 | #ifdef __cplusplus 40 | extern "C" { 41 | #endif 42 | 43 | /* to make a 300 KB space called samples do GBFS_SPACE(samples, 300) */ 44 | 45 | #define GBFS_SPACE(filename, kbytes) \ 46 | const char filename[(kbytes)*1024] __attribute__ ((aligned (16))) = \ 47 | "PinEightGBFSSpace-" #filename "-" #kbytes ; 48 | 49 | typedef struct GBFS_FILE 50 | { 51 | char magic[16]; /* "PinEightGBFS\r\n\032\n" */ 52 | u32 total_len; /* total length of archive */ 53 | u16 dir_off; /* offset in bytes to directory */ 54 | u16 dir_nmemb; /* number of files */ 55 | char reserved[8]; /* for future use */ 56 | } GBFS_FILE; 57 | 58 | typedef struct GBFS_ENTRY 59 | { 60 | char name[24]; /* filename, nul-padded */ 61 | u32 len; /* length of object in bytes */ 62 | u32 data_offset; /* in bytes from beginning of file */ 63 | } GBFS_ENTRY; 64 | 65 | 66 | const GBFS_FILE *find_first_gbfs_file(const void *start); 67 | const void *skip_gbfs_file(const GBFS_FILE *file); 68 | const void *gbfs_get_obj(const GBFS_FILE *file, 69 | const char *name, 70 | u32 *len); 71 | const void *gbfs_get_nth_obj(const GBFS_FILE *file, 72 | size_t n, 73 | char *name, 74 | u32 *len); 75 | void *gbfs_copy_obj(void *dst, 76 | const GBFS_FILE *file, 77 | const char *name); 78 | size_t gbfs_count_objs(const GBFS_FILE *file); 79 | 80 | 81 | #ifdef __cplusplus 82 | } 83 | #endif 84 | #endif 85 | -------------------------------------------------------------------------------- /examples/example2.cpp: -------------------------------------------------------------------------------- 1 | /* example.cpp -- Pimpmobile GameBoy Advance example 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifndef REG_WAITCNT 14 | #define REG_WAITCNT (*(vu16*)(REG_BASE + 0x0204)) 15 | #endif 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include "../src/load_module.h" 23 | #define PIMP_DONT_DECLARE_BASIC_TYPES 24 | #include "../src/pimp_sample_bank.h" 25 | #include "gbfs_stdio.h" 26 | 27 | int accum = 0; 28 | void mix() 29 | { 30 | REG_TM2CNT_L = 0; 31 | REG_TM2CNT_H = 0; 32 | REG_TM2CNT_H = TIMER_START; 33 | pimp_gba_vblank(); 34 | pimp_gba_frame(); 35 | u32 value = REG_TM2CNT_L; 36 | accum += value; 37 | } 38 | 39 | void report() 40 | { 41 | float percent = float(accum * 100) / (1 << 24); 42 | iprintf("cpu: %d.%03d%% (%d c/f)\n", int(percent), int(percent * 1000) % 1000, accum / 60); 43 | accum = 0; 44 | } 45 | 46 | void timer3() 47 | { 48 | report(); 49 | } 50 | 51 | void vblank() 52 | { 53 | mix(); 54 | } 55 | 56 | void callback(int type, int data) 57 | { 58 | switch (type) 59 | { 60 | case PIMP_CALLBACK_SYNC: 61 | break; 62 | 63 | case PIMP_CALLBACK_UNSUPPORTED_EFFECT: 64 | iprintf("eff: %X\n", data); 65 | break; 66 | 67 | case PIMP_CALLBACK_UNSUPPORTED_VOLUME_EFFECT: 68 | iprintf("vol eff: %X\n", data); 69 | break; 70 | } 71 | } 72 | 73 | int main() 74 | { 75 | // REG_WAITCNT = 0x46d6; // lets set some cool waitstates... 76 | REG_WAITCNT = 0x46da; // lets set some cool waitstates... 77 | 78 | irqInit(); 79 | irqEnable(IRQ_VBLANK); 80 | consoleInit(0, 4, 0, NULL, 0, 15); 81 | 82 | BG_COLORS[0] = RGB5(0, 0, 0); 83 | BG_COLORS[241] = RGB5(31, 31, 31); 84 | REG_DISPCNT = MODE_0 | BG0_ON; 85 | 86 | gbfs_init(1); 87 | 88 | pimp_sample_bank sb; 89 | FILE *fp = fopen("dxn-oopk.xm", "rb"); 90 | if (!fp) 91 | { 92 | fprintf(stderr, "file not found\n"); 93 | return 1; 94 | } 95 | pimp_module *mod = load_module_xm(fp, &sb); 96 | fclose(fp); 97 | fp = NULL; 98 | 99 | if (NULL == mod) 100 | { 101 | fprintf(stderr, "failed to load module\n"); 102 | return 1; 103 | } 104 | 105 | pimp_gba_init(mod, sb.data); 106 | pimp_gba_set_callback(callback); 107 | 108 | irqSet(IRQ_TIMER3, timer3); 109 | irqEnable(IRQ_TIMER3); 110 | REG_TM3CNT_L = 0; 111 | REG_TM3CNT_H = TIMER_START | TIMER_IRQ | 2; 112 | 113 | irqSet(IRQ_VBLANK, vblank); 114 | irqEnable(IRQ_VBLANK); 115 | 116 | while (1) 117 | { 118 | VBlankIntrWait(); 119 | scanKeys(); 120 | int keys = keysDown(); 121 | if (keys & KEY_UP) pimp_gba_set_pos(0, pimp_gba_get_order() - 1); 122 | if (keys & KEY_DOWN) pimp_gba_set_pos(0, pimp_gba_get_order() + 1); 123 | if (keys & KEY_RIGHT) pimp_gba_set_pos(pimp_gba_get_row() + 8, pimp_gba_get_order()); 124 | if (keys & KEY_LEFT) pimp_gba_set_pos(pimp_gba_get_row() - 8, pimp_gba_get_order()); 125 | iprintf("%d %d\n", pimp_gba_get_order(), pimp_gba_get_row()); 126 | } 127 | 128 | pimp_gba_close(); 129 | return 0; 130 | } 131 | -------------------------------------------------------------------------------- /examples/example.cpp: -------------------------------------------------------------------------------- 1 | /* example.cpp -- Pimpmobile GameBoy Advance example 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifndef REG_WAITCNT 14 | #define REG_WAITCNT (*(vu16*)(REG_BASE + 0x0204)) 15 | #endif 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include "gbfs.h" 23 | 24 | const void *mod = NULL; 25 | GBFS_FILE const* fs; 26 | const void *sample_bank = 0; 27 | int curr_file = 0; 28 | int file_count = 0; 29 | 30 | int accum = 0; 31 | void mix() 32 | { 33 | REG_TM2CNT_L = 0; 34 | REG_TM2CNT_H = 0; 35 | REG_TM2CNT_H = TIMER_START; 36 | pimp_gba_vblank(); 37 | pimp_gba_frame(); 38 | u32 value = REG_TM2CNT_L; 39 | accum += value; 40 | } 41 | 42 | void report() 43 | { 44 | float percent = float(accum * 100) / (1 << 24); 45 | iprintf("cpu: %d.%03d%% (%d c/f)\n", int(percent), int(percent * 1000) % 1000, accum / 60); 46 | accum = 0; 47 | } 48 | 49 | void timer3() 50 | { 51 | report(); 52 | } 53 | 54 | void vblank() 55 | { 56 | mix(); 57 | } 58 | 59 | void callback(int type, int data) 60 | { 61 | switch (type) 62 | { 63 | case PIMP_CALLBACK_SYNC: 64 | break; 65 | 66 | case PIMP_CALLBACK_UNSUPPORTED_EFFECT: 67 | iprintf("eff: %X\n", data); 68 | break; 69 | 70 | case PIMP_CALLBACK_UNSUPPORTED_VOLUME_EFFECT: 71 | iprintf("vol eff: %X\n", data); 72 | break; 73 | } 74 | } 75 | 76 | void play_next_file() 77 | { 78 | static char name[32]; 79 | 80 | do 81 | { 82 | mod = gbfs_get_nth_obj(fs, curr_file++, name, 0); 83 | if (curr_file > file_count - 1) curr_file = 0; 84 | } 85 | while (strncmp(name, "sample_bank.bin", 32) == 0); 86 | 87 | pimp_gba_close(); 88 | iprintf("loading %s\n", name); 89 | pimp_gba_init((const pimp_module*)mod, sample_bank); 90 | pimp_gba_set_callback(callback); 91 | } 92 | 93 | int main() 94 | { 95 | // REG_WAITCNT = 0x46d6; // lets set some cool waitstates... 96 | REG_WAITCNT = 0x46da; // lets set some cool waitstates... 97 | 98 | irqInit(); 99 | irqEnable(IRQ_VBLANK); 100 | consoleInit(0, 4, 0, NULL, 0, 15); 101 | 102 | BG_COLORS[0] = RGB5(0, 0, 0); 103 | BG_COLORS[241] = RGB5(31, 31, 31); 104 | REG_DISPCNT = MODE_0 | BG0_ON; 105 | 106 | fs = find_first_gbfs_file((void*)0x08000000); 107 | file_count = gbfs_count_objs(fs); 108 | sample_bank = gbfs_get_obj(fs, "sample_bank.bin", 0); 109 | 110 | if (file_count <= 0) 111 | { 112 | iprintf("no files in file-system\n"); 113 | return 1; 114 | } 115 | 116 | play_next_file(); 117 | 118 | irqSet(IRQ_TIMER3, timer3); 119 | irqEnable(IRQ_TIMER3); 120 | REG_TM3CNT_L = 0; 121 | REG_TM3CNT_H = TIMER_START | TIMER_IRQ | 2; 122 | 123 | irqSet(IRQ_VBLANK, vblank); 124 | irqEnable(IRQ_VBLANK); 125 | 126 | while (1) 127 | { 128 | VBlankIntrWait(); 129 | scanKeys(); 130 | int keys = keysDown(); 131 | if (keys & KEY_RIGHT) pimp_gba_set_pos(0, pimp_gba_get_order() + 1); 132 | if (keys & KEY_LEFT) pimp_gba_set_pos(pimp_gba_get_row() + 8, pimp_gba_get_order()); 133 | if (keys & KEY_A) play_next_file(); 134 | } 135 | 136 | pimp_gba_close(); 137 | return 0; 138 | } 139 | -------------------------------------------------------------------------------- /converter/serialize_instrument.c: -------------------------------------------------------------------------------- 1 | /* serialize_instrument.c -- Serializer for instrument data 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include 7 | #include "../src/pimp_module.h" 8 | #include "../src/pimp_mixer.h" 9 | #include "serializer.h" 10 | 11 | #if 0 12 | #include 13 | #define TRACE() printf("AT %s:%d\n", __FILE__, __LINE__); 14 | #else 15 | #define TRACE() 16 | #endif 17 | 18 | int serialize_sample(struct serializer *s, const struct pimp_sample *samp) 19 | { 20 | int pos; 21 | 22 | ASSERT(NULL != samp); 23 | ASSERT(NULL != s); 24 | TRACE(); 25 | 26 | serializer_align(s, 4); 27 | pos = s->pos; 28 | 29 | serialize_word(s, samp->data_ptr); 30 | serialize_word(s, samp->length); 31 | serialize_word(s, samp->loop_start); 32 | serialize_word(s, samp->loop_length); 33 | 34 | serialize_halfword(s, samp->fine_tune); 35 | serialize_halfword(s, samp->rel_note); 36 | 37 | serialize_byte(s, samp->volume); 38 | serialize_byte(s, samp->loop_type); 39 | serialize_byte(s, samp->pan); 40 | 41 | serialize_byte(s, samp->vibrato_speed); 42 | serialize_byte(s, samp->vibrato_depth); 43 | serialize_byte(s, samp->vibrato_sweep); 44 | serialize_byte(s, samp->vibrato_waveform); 45 | 46 | return pos; 47 | } 48 | 49 | int serialize_envelope(struct serializer *s, const struct pimp_envelope *env) 50 | { 51 | int pos, i; 52 | 53 | ASSERT(NULL != env); 54 | ASSERT(NULL != s); 55 | TRACE(); 56 | 57 | serializer_align(s, 4); 58 | pos = s->pos; 59 | 60 | for (i = 0; i < 25; ++i) 61 | { 62 | serialize_halfword(s, env->node_tick[i]); 63 | } 64 | 65 | for (i = 0; i < 25; ++i) 66 | { 67 | serialize_halfword(s, env->node_magnitude[i]); 68 | } 69 | 70 | for (i = 0; i < 25; ++i) 71 | { 72 | serialize_halfword(s, env->node_delta[i]); 73 | } 74 | 75 | serialize_byte(s, env->node_count); 76 | serialize_byte(s, env->flags); 77 | 78 | serialize_byte(s, env->loop_start); 79 | serialize_byte(s, env->loop_end); 80 | 81 | serialize_byte(s, env->sustain_loop_start); 82 | serialize_byte(s, env->sustain_loop_end); 83 | 84 | return pos; 85 | } 86 | 87 | /* dumps all samples in an instrument, returns position of first sample */ 88 | void serialize_instrument_data(struct serializer *s, const struct pimp_instrument *instr) 89 | { 90 | struct pimp_envelope *vol_env, *pan_env; 91 | int i; 92 | 93 | ASSERT(NULL != instr); 94 | ASSERT(NULL != s); 95 | TRACE(); 96 | 97 | serializer_align(s, 4); 98 | serializer_set_pointer(s, pimp_get_ptr(&instr->sample_ptr), s->pos); 99 | 100 | for (i = 0; i < instr->sample_count; ++i) 101 | { 102 | serialize_sample(s, pimp_instrument_get_sample(instr, i)); 103 | } 104 | 105 | vol_env = pimp_instrument_get_vol_env(instr); 106 | if (NULL != vol_env) serializer_set_pointer(s, pimp_get_ptr(&instr->vol_env_ptr), serialize_envelope(s, vol_env)); 107 | 108 | pan_env = pimp_instrument_get_pan_env(instr); 109 | if (NULL != pan_env) serializer_set_pointer(s, pimp_get_ptr(&instr->pan_env_ptr), serialize_envelope(s, pan_env)); 110 | } 111 | 112 | /* dump instrument structure, return position */ 113 | void serialize_instrument(struct serializer *s, const struct pimp_instrument *instr) 114 | { 115 | int i; 116 | 117 | ASSERT(NULL != instr); 118 | ASSERT(NULL != s); 119 | TRACE(); 120 | 121 | serializer_align(s, 4); 122 | serialize_pointer(s, pimp_get_ptr(&instr->sample_ptr)); 123 | serialize_pointer(s, pimp_get_ptr(&instr->vol_env_ptr)); 124 | serialize_pointer(s, pimp_get_ptr(&instr->pan_env_ptr)); 125 | 126 | serialize_halfword(s, instr->volume_fadeout); 127 | serialize_byte(s, instr->sample_count); 128 | 129 | for (i = 0; i < 120; ++i) 130 | { 131 | serialize_byte(s, instr->sample_map[i]); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/pimp_mixer_clip_arm.S: -------------------------------------------------------------------------------- 1 | /* pimp_mixer_clip_arm.S -- ARM optimized sample clipping code 2 | * Copyright (C) 2005-2008 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | .text 7 | .section .iwram 8 | .arm 9 | .align 2 10 | 11 | @@ void pimp_mixer_clip_samples(s8 *target, s32 *source, u32 samples, u32 dc_offs); 12 | #define DST r0 13 | #define SRC r1 14 | #define COUNT r2 15 | #define BIAS r3 16 | 17 | #define S1 r4 18 | #define S2 r5 19 | #define S3 r6 20 | #define S4 r7 21 | 22 | #define CONST_00 r8 23 | #define CONST_FF r9 24 | 25 | #define FLIP_SIGN r10 26 | #define TEMP r11 27 | 28 | .global pimp_mixer_clip_samples 29 | .type pimp_mixer_clip_samples, %function 30 | pimp_mixer_clip_samples: 31 | stmfd sp!, {r4-r12, lr} @ store all registers but parameters and stack 32 | 33 | ldr FLIP_SIGN, =0x80808080 34 | ldr CONST_00, =0 35 | ldr CONST_FF, =0xFF 36 | rsb BIAS, BIAS, #128 37 | 38 | cmp COUNT, #7 39 | bmi .fixup_last 40 | 41 | @ fixup first, jump to the correct position 42 | @ TEMP = 3 - ((DST - 1) & 3) 43 | sub TEMP, DST, #1 44 | and TEMP, TEMP, #3 45 | rsb TEMP, TEMP, #3 46 | 47 | cmp TEMP, #0 48 | beq .no_fixup_first 49 | 50 | @ update COUNT 51 | sub COUNT, COUNT, TEMP 52 | 53 | @ clip a single sample pr iteration 54 | .fixup_first_loop: @ 10-13 cycles pr sample: 55 | ldr S1, [SRC], #4 @ 3 cycles 56 | add S1, BIAS, S1, asr #8 @ 1 cycle 57 | subs r12, CONST_00, S1, asr #8 @ 1 cycle 58 | andne S1, CONST_FF, r12, lsr #24 @ 1 cycle 59 | eor S1, S1, #0x80 @ 1 cycle 60 | strb S1, [DST], #1 @ 2 cycles 61 | 62 | @ loop 63 | subs TEMP, TEMP, #1 @ 1 cycle 64 | bne .fixup_first_loop @ 3 cycles 65 | .no_fixup_first: 66 | 67 | sub COUNT, COUNT, #4 68 | 69 | .clip_loop: @ 7 cycles pr sample: 70 | ldmia SRC!, {S1-S4} @ 6 cycles 71 | 72 | add S1, BIAS, S1, asr #8 @ 1 cycle 73 | subs TEMP, CONST_00, S1, asr #8 @ 1 cycle 74 | andne S1, CONST_FF, TEMP, lsr #24 @ 1 cycle 75 | 76 | add S2, BIAS, S2, asr #8 @ 1 cycle 77 | subs TEMP, CONST_00, S2, asr #8 @ 1 cycle 78 | andne S2, CONST_FF, TEMP, lsr #24 @ 1 cycle 79 | 80 | add S3, BIAS, S3, asr #8 @ 1 cycle 81 | subs TEMP, CONST_00, S3, asr #8 @ 1 cycle 82 | andne S3, CONST_FF, TEMP, lsr #24 @ 1 cycle 83 | 84 | add S4, BIAS, S4, asr #8 @ 1 cycle 85 | subs TEMP, CONST_00, S4, asr #8 @ 1 cycle 86 | andne S4, CONST_FF, TEMP, lsr #24 @ 1 cycle 87 | 88 | orr S1, S1, S2, lsl #8 @ 1 cycle 89 | orr S1, S1, S3, lsl #16 @ 1 cycle 90 | orr S1, S1, S4, lsl #24 @ 1 cycle 91 | eor S1, S1, FLIP_SIGN @ 1 cycle 92 | str S1, [DST], #4 @ 2 cycles 93 | 94 | subs COUNT, COUNT, #4 @ 1 cycle 95 | bpl .clip_loop @ 3 cycles 96 | @ total: 28 cycles 97 | @ 28 cycles / 4 samples = 7 cycles per sample 98 | 99 | add COUNT, COUNT, #4 100 | 101 | .fixup_last: 102 | cmp COUNT, #0 103 | beq .no_fixup_last 104 | 105 | @ clip a single sample pr iteration 106 | .fixup_last_loop: @ 10-13 cycles pr sample: 107 | ldr S1, [SRC], #4 @ 3 cycles 108 | add S1, BIAS, S1, asr #8 @ 1 cycle 109 | subs r12, CONST_00, S1, asr #8 @ 1 cycle 110 | andne S1, CONST_FF, r12, lsr #24 @ 1 cycle 111 | eor S1, S1, #0x80 @ 1 cycle 112 | strb S1, [DST], #1 @ 2 cycles 113 | 114 | @ loop 115 | subs COUNT, COUNT, #1 @ 1 cycle 116 | bne .fixup_last_loop @ 3 cycles 117 | .no_fixup_last: 118 | 119 | ldmfd sp!, {r4-r12, lr} @ restore all registers but parameters and stack 120 | bx lr 121 | -------------------------------------------------------------------------------- /t/unit/test_serializer.c: -------------------------------------------------------------------------------- 1 | #include "../framework/test_framework.h" 2 | #include "../framework/test_helpers.h" 3 | 4 | #include "../../converter/serializer.h" 5 | 6 | static void test_serializer_basic(struct test_suite *suite) 7 | { 8 | struct serializer s; 9 | 10 | serializer_init(&s); 11 | 12 | serialize_byte(&s, 0xde); 13 | serialize_byte(&s, 0xad); 14 | serialize_byte(&s, 0xbe); 15 | serialize_byte(&s, 0xef); 16 | 17 | ASSERT_INTS_EQUAL(suite, s.data[0], 0xde); 18 | ASSERT_INTS_EQUAL(suite, s.data[1], 0xad); 19 | ASSERT_INTS_EQUAL(suite, s.data[2], 0xbe); 20 | ASSERT_INTS_EQUAL(suite, s.data[3], 0xef); 21 | 22 | serializer_deinit(&s); 23 | } 24 | 25 | static void test_serializer_endianess(struct test_suite *suite) 26 | { 27 | struct serializer s; 28 | 29 | serializer_init(&s); 30 | 31 | 32 | /* assure that 32bit words are dumped in little endian format */ 33 | serialize_word(&s, 0xdeadbeef); 34 | ASSERT_INTS_EQUAL(suite, s.data[3], 0xde); 35 | ASSERT_INTS_EQUAL(suite, s.data[2], 0xad); 36 | ASSERT_INTS_EQUAL(suite, s.data[1], 0xbe); 37 | ASSERT_INTS_EQUAL(suite, s.data[0], 0xef); 38 | 39 | /* assure that 16bit halfwords are dumped in little endian format */ 40 | serialize_halfword(&s, 0xb00b); 41 | ASSERT_INTS_EQUAL(suite, s.data[5], 0xb0); 42 | ASSERT_INTS_EQUAL(suite, s.data[4], 0x0b); 43 | 44 | serializer_deinit(&s); 45 | } 46 | 47 | static void test_serializer_align_word(struct test_suite *suite) 48 | { 49 | struct serializer s; 50 | 51 | serializer_init(&s); 52 | 53 | /* 0 bytes offset */ 54 | serialize_word(&s, 0xdeadbeef); 55 | ASSERT_INTS_EQUAL(suite, s.data[3], 0xde); 56 | ASSERT_INTS_EQUAL(suite, s.data[2], 0xad); 57 | ASSERT_INTS_EQUAL(suite, s.data[1], 0xbe); 58 | ASSERT_INTS_EQUAL(suite, s.data[0], 0xef); 59 | 60 | /* 1 bytes offset */ 61 | serialize_byte(&s, 0x00); 62 | serialize_word(&s, 0xdeadbeef); 63 | ASSERT_INTS_EQUAL(suite, s.data[11], 0xde); 64 | ASSERT_INTS_EQUAL(suite, s.data[10], 0xad); 65 | ASSERT_INTS_EQUAL(suite, s.data[9], 0xbe); 66 | ASSERT_INTS_EQUAL(suite, s.data[8], 0xef); 67 | 68 | /* 2 bytes offset */ 69 | serialize_byte(&s, 0x00); 70 | serialize_byte(&s, 0x00); 71 | serialize_word(&s, 0xdeadbeef); 72 | ASSERT_INTS_EQUAL(suite, s.data[19], 0xde); 73 | ASSERT_INTS_EQUAL(suite, s.data[18], 0xad); 74 | ASSERT_INTS_EQUAL(suite, s.data[17], 0xbe); 75 | ASSERT_INTS_EQUAL(suite, s.data[16], 0xef); 76 | 77 | /* 3 bytes offset */ 78 | serialize_byte(&s, 0x00); 79 | serialize_byte(&s, 0x00); 80 | serialize_byte(&s, 0x00); 81 | serialize_word(&s, 0xdeadbeef); 82 | ASSERT_INTS_EQUAL(suite, s.data[27], 0xde); 83 | ASSERT_INTS_EQUAL(suite, s.data[26], 0xad); 84 | ASSERT_INTS_EQUAL(suite, s.data[25], 0xbe); 85 | ASSERT_INTS_EQUAL(suite, s.data[24], 0xef); 86 | 87 | /* 4 bytes offset */ 88 | serialize_byte(&s, 0x00); 89 | serialize_byte(&s, 0x00); 90 | serialize_byte(&s, 0x00); 91 | serialize_byte(&s, 0x00); 92 | serialize_word(&s, 0xdeadbeef); 93 | ASSERT_INTS_EQUAL(suite, s.data[35], 0xde); 94 | ASSERT_INTS_EQUAL(suite, s.data[34], 0xad); 95 | ASSERT_INTS_EQUAL(suite, s.data[33], 0xbe); 96 | ASSERT_INTS_EQUAL(suite, s.data[32], 0xef); 97 | 98 | serializer_deinit(&s); 99 | } 100 | 101 | static void test_serializer_align_halfword(struct test_suite *suite) 102 | { 103 | struct serializer s; 104 | 105 | serializer_init(&s); 106 | 107 | /* 0 bytes offset */ 108 | serialize_halfword(&s, 0xdead); 109 | ASSERT_INTS_EQUAL(suite, s.data[1], 0xde); 110 | ASSERT_INTS_EQUAL(suite, s.data[0], 0xad); 111 | 112 | /* 1 bytes offset */ 113 | serialize_byte(&s, 0x00); 114 | serialize_halfword(&s, 0xdead); 115 | ASSERT_INTS_EQUAL(suite, s.data[5], 0xde); 116 | ASSERT_INTS_EQUAL(suite, s.data[4], 0xad); 117 | 118 | /* 2 bytes offset */ 119 | serialize_byte(&s, 0x00); 120 | serialize_byte(&s, 0x00); 121 | serialize_halfword(&s, 0xdead); 122 | ASSERT_INTS_EQUAL(suite, s.data[9], 0xde); 123 | ASSERT_INTS_EQUAL(suite, s.data[8], 0xad); 124 | 125 | serializer_deinit(&s); 126 | } 127 | 128 | void test_serializer(struct test_suite *suite) 129 | { 130 | test_serializer_basic(suite); 131 | test_serializer_endianess(suite); 132 | test_serializer_align_word(suite); 133 | test_serializer_align_halfword(suite); 134 | } 135 | -------------------------------------------------------------------------------- /converter/serializer.c: -------------------------------------------------------------------------------- 1 | /* serializer.c -- low-level serializer for pimpconv 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include "serializer.h" 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #define ASSERT assert 13 | 14 | #ifndef MAX 15 | #define MAX(x, y) ((x) > (y) ? (x) : (y)) 16 | #endif 17 | 18 | void serializer_init(struct serializer *s) 19 | { 20 | ASSERT(NULL != s); 21 | 22 | s->data = NULL; 23 | s->buffer_size = 0; 24 | s->pos = 0; 25 | 26 | s->relocs = NULL; 27 | s->num_relocs = 0; 28 | } 29 | 30 | void serializer_deinit(struct serializer *s) 31 | { 32 | ASSERT(NULL != s); 33 | 34 | if (NULL != s->data) 35 | { 36 | free(s->data); 37 | s->data = NULL; 38 | } 39 | } 40 | 41 | void serializer_check_size(struct serializer *s, size_t needed_size) 42 | { 43 | ASSERT(NULL != s); 44 | 45 | if (s->buffer_size < (s->pos + needed_size)) 46 | { 47 | int old_size = s->buffer_size; 48 | s->buffer_size = MAX(s->buffer_size * 2, s->buffer_size + needed_size); 49 | 50 | s->data = realloc(s->data, s->buffer_size); 51 | 52 | if (NULL == s->data) 53 | { 54 | fputs("out of memory\n", stderr); 55 | abort(); 56 | } 57 | 58 | memset(&s->data[old_size], 0, (s->buffer_size - old_size)); 59 | } 60 | } 61 | 62 | void serializer_align(struct serializer *s, int alignment) 63 | { 64 | ASSERT(NULL != s); 65 | 66 | if ((s->pos % alignment) != 0) 67 | { 68 | int align_amt = alignment - (s->pos % alignment); 69 | serializer_check_size(s, align_amt); 70 | s->pos += align_amt; 71 | } 72 | } 73 | 74 | void serialize_byte(struct serializer *s, uint8_t b) 75 | { 76 | ASSERT(NULL != s); 77 | 78 | /* no alignment needed, make room for new data */ 79 | serializer_check_size(s, 1); 80 | 81 | ASSERT(s->pos < s->buffer_size); 82 | s->data[s->pos++] = b; 83 | } 84 | 85 | void serialize_halfword(struct serializer *s, uint16_t h) 86 | { 87 | ASSERT(NULL != s); 88 | 89 | /* align and make room for new data */ 90 | serializer_align(s, 2); 91 | serializer_check_size(s, 2); 92 | 93 | /* write data (little endian) */ 94 | s->data[s->pos++] = (unsigned char)(h >> 0); 95 | s->data[s->pos++] = (unsigned char)(h >> 8); 96 | } 97 | 98 | void serialize_word(struct serializer *s, uint32_t w) 99 | { 100 | ASSERT(NULL != s); 101 | 102 | /* align and make room for new data */ 103 | serializer_align(s, 4); 104 | serializer_check_size(s, 4); 105 | 106 | /* write data (little endian) */ 107 | s->data[s->pos++] = (unsigned char)(w >> 0); 108 | s->data[s->pos++] = (unsigned char)(w >> 8); 109 | s->data[s->pos++] = (unsigned char)(w >> 16); 110 | s->data[s->pos++] = (unsigned char)(w >> 24); 111 | } 112 | 113 | /* why on earth does this routine support zero len? */ 114 | void serialize_string(struct serializer *s, const char *str, const size_t len) 115 | { 116 | int i; 117 | size_t real_len, slen; 118 | 119 | ASSERT(NULL != s); 120 | 121 | /* no alignment needed */ 122 | serializer_check_size(s, len); 123 | 124 | /* determine length (min of slen and len) */ 125 | slen = strlen(str) + 1; 126 | if (len > 0) real_len = (len > slen) ? slen : len; 127 | else real_len = slen; 128 | 129 | for (i = 0; i < real_len; ++i) 130 | { 131 | s->data[s->pos + i] = str[i]; 132 | } 133 | 134 | if (len > 0) s->pos += len; 135 | else s->pos += slen; 136 | } 137 | 138 | void serialize_pointer(struct serializer *s, void *ptr) 139 | { 140 | ASSERT(NULL != s); 141 | 142 | serializer_align(s, 4); 143 | serializer_check_size(s, 4); 144 | 145 | if (!ptr) { 146 | serialize_word(s, 0); 147 | return; 148 | } 149 | 150 | s->relocs = realloc(s->relocs, sizeof(struct reloc) * (s->num_relocs + 1)); 151 | if (!s->relocs) { 152 | fputs("out of memory\n", stderr); 153 | abort(); 154 | } 155 | 156 | s->relocs[s->num_relocs].pos = s->pos; 157 | s->relocs[s->num_relocs].ptr = ptr; 158 | s->num_relocs++; 159 | 160 | serialize_word(s, 0xdeadbeef); 161 | } 162 | 163 | void serializer_set_pointer(struct serializer *s, void *ptr, int pos) 164 | { 165 | int i; 166 | 167 | ASSERT(NULL != s); 168 | 169 | for (i = 0; i < s->num_relocs; ++i) { 170 | unsigned int *target; 171 | 172 | if (s->relocs[i].ptr != ptr) 173 | continue; 174 | 175 | ASSERT(s->relocs[i].pos & 3 == 0); /* location muet be word-aligned */ 176 | 177 | target = (unsigned int*)(s->data + s->relocs[i].pos); 178 | ASSERT(*target == 0xdeadbeef); 179 | 180 | *target = pos - s->relocs[i].pos; 181 | } 182 | } 183 | 184 | 185 | void serializer_fixup_pointers(struct serializer *s) 186 | { 187 | int i; 188 | 189 | ASSERT(NULL != s); 190 | 191 | for (i = 0; i < s->num_relocs; ++i) { 192 | unsigned int *target = (unsigned int*)(s->data + s->relocs[i].pos); 193 | if (*target == 0xdeadbeef) { 194 | fputs("reloc not fixed up!\n", stderr); 195 | abort(); 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/pimp_math.c: -------------------------------------------------------------------------------- 1 | /* pimp_math.c -- Math routines for use in Pimpmobile 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include "pimp_math.h" 7 | #include "pimp_base.h" 8 | #include "pimp_debug.h" 9 | 10 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 11 | 12 | const u8 pimp_clz_lut[256] = 13 | { 14 | 0x8, 0x7, 0x6, 0x6, 0x5, 0x5, 0x5, 0x5, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 15 | 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 16 | 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 17 | 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 18 | 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 19 | 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 20 | 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 21 | 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 22 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 23 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 24 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 25 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 26 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 27 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 28 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 29 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 30 | }; 31 | 32 | 33 | #ifndef NO_LINEAR_PERIODS 34 | unsigned pimp_get_linear_period(int note, int fine_tune) 35 | { 36 | int xm_note; 37 | ASSERT(fine_tune >= -8); 38 | ASSERT(fine_tune < 8); 39 | 40 | xm_note = note - (12 * 1) - 1; /* we extended our note-range with one octave. */ 41 | 42 | return (10 * 12 * 16 * 4 - xm_note * 16 * 4 - fine_tune / 2); 43 | /* return 10 * 12 * 16 * 4 - note * 16 * 4 - fine_tune / 2; */ 44 | } 45 | 46 | #include "linear_delta_lut.h" 47 | unsigned pimp_get_linear_delta(unsigned int period, unsigned int delta_scale) 48 | { 49 | unsigned p = (12 * 16 * 4 * 14) - period; 50 | unsigned octave = p / (12 * 16 * 4); 51 | unsigned octave_period = p % (12 * 16 * 4); 52 | unsigned delta = pimp_linear_delta_lut[octave_period] << octave; 53 | 54 | /* BEHOLD: the expression of the devil (this compiles to one arm-instruction) */ 55 | delta = ((long long)delta * (delta_scale >> 3) + (1ULL << 31)) >> 32; 56 | return delta; 57 | } 58 | #endif /* NO_LINEAR_PERIODS */ 59 | 60 | #ifndef NO_AMIGA_PERIODS 61 | #include "amiga_period_lut.h" 62 | unsigned pimp_get_amiga_period(int note, int fine_tune) 63 | { 64 | unsigned int index; 65 | fine_tune /= 8; /* todo: interpolate instead? */ 66 | ASSERT(fine_tune >= -8); 67 | ASSERT(fine_tune <= 8); 68 | 69 | /* bias up one octave to prevent from negative values due to fine tune*/ 70 | index = 12 * 8 + note * 8 + fine_tune; 71 | 72 | /* handle notes outside of the mod-range by shifting up */ 73 | if (index < (12 * 8 * 5)) 74 | { 75 | unsigned int octave = index / (12 * 8); 76 | unsigned int octave_index = index % (12 * 8); 77 | return (((u32)pimp_amiga_period_lut[octave_index]) * 4) << (5 - octave); 78 | } 79 | 80 | /* handle notes outside of the mod-range by shifting down */ 81 | if (index >= ARRAY_SIZE(pimp_amiga_period_lut) + 12 * 8 * 5) 82 | { 83 | unsigned int octave = index / (12 * 8); 84 | unsigned int octave_index = index % (12 * 8); 85 | return (((u32)pimp_amiga_period_lut[octave_index]) * 4) >> (octave - 5); 86 | } 87 | 88 | return ((u32)pimp_amiga_period_lut[index - (12 * 8 * 5)]) * 4; 89 | } 90 | 91 | #include "amiga_delta_lut.h" 92 | #define AMIGA_DELTA_LUT_SIZE (1 << AMIGA_DELTA_LUT_LOG2_SIZE) 93 | #define AMIGA_DELTA_LUT_FRAC_BITS (15 - AMIGA_DELTA_LUT_LOG2_SIZE) 94 | unsigned pimp_get_amiga_delta(unsigned int period, unsigned int delta_scale) 95 | { 96 | int d1, d2; 97 | unsigned int delta; 98 | unsigned int shamt = pimp_clz16(period) - 1; 99 | unsigned int p = period << shamt; 100 | unsigned int p_frac = p & ((1 << AMIGA_DELTA_LUT_FRAC_BITS) - 1); 101 | p >>= AMIGA_DELTA_LUT_FRAC_BITS; 102 | 103 | /* interpolate table-entries for better result */ 104 | d1 = pimp_amiga_delta_lut[p - (AMIGA_DELTA_LUT_SIZE / 2)]; /* (8363 * 1712) / float(p) */ 105 | d2 = pimp_amiga_delta_lut[p + 1 - (AMIGA_DELTA_LUT_SIZE / 2)]; /* (8363 * 1712) / float(p + 1) */ 106 | delta = (d1 << AMIGA_DELTA_LUT_FRAC_BITS) + (d2 - d1) * p_frac; 107 | 108 | if (shamt > AMIGA_DELTA_LUT_FRAC_BITS) delta <<= shamt - AMIGA_DELTA_LUT_FRAC_BITS; 109 | else delta >>= AMIGA_DELTA_LUT_FRAC_BITS - shamt; 110 | 111 | /* BEHOLD: the expression of the devil 2.0 (this compiles to one arm-instruction) */ 112 | delta = ((long long)delta * delta_scale + (1ULL << 31)) >> 32; 113 | return delta; 114 | } 115 | #endif /* NO_AMIGA_PERIODS */ 116 | -------------------------------------------------------------------------------- /converter/pimpconv.c: -------------------------------------------------------------------------------- 1 | /* pimpconv.c -- Main entry-point for the tool to load and export bin-modules 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../src/load_module.h" 13 | #include "serialize_module.h" 14 | #include "serializer.h" 15 | 16 | #include "../src/pimp_module.h" 17 | #include "../src/pimp_sample_bank.h" 18 | 19 | void print_usage() 20 | { 21 | fprintf(stderr, "Usage: pimpconv [-d] filenames\n"); 22 | exit(EXIT_FAILURE); 23 | } 24 | 25 | static struct pimp_module *load_module(const char *filename, struct pimp_sample_bank *sample_bank) 26 | { 27 | struct pimp_module *mod; 28 | 29 | /* open file */ 30 | FILE *fp = fopen(filename, "rb"); 31 | if (!fp) 32 | { 33 | perror(filename); 34 | exit(EXIT_FAILURE); 35 | } 36 | 37 | /* try to load */ 38 | mod = load_module_xm(fp, sample_bank); 39 | if (NULL == mod) mod = load_module_mod(fp, sample_bank); 40 | 41 | /* report error if any */ 42 | if (NULL == mod) 43 | { 44 | if (errno) fprintf(stderr, "%s: Failed to load module, %s\n", filename, strerror(errno)); 45 | else fprintf(stderr, "%s: Failed to load module\n", filename); 46 | exit(EXIT_FAILURE); 47 | } 48 | 49 | /* close file */ 50 | fclose(fp); 51 | fp = NULL; 52 | 53 | /* get back on track */ 54 | return mod; 55 | } 56 | 57 | static void dump_module(struct pimp_module *mod, const char *filename) 58 | { 59 | FILE *fp = NULL; 60 | struct serializer s; 61 | 62 | ASSERT(NULL != mod); 63 | 64 | /* open target file */ 65 | fp = fopen(filename, "wb"); 66 | if (NULL == fp) 67 | { 68 | perror(filename); 69 | exit(EXIT_FAILURE); 70 | } 71 | 72 | /* serialize module to memory dump */ 73 | serializer_init(&s); 74 | serialize_module(&s, mod); 75 | serializer_fixup_pointers(&s); 76 | 77 | fwrite(s.data, 1, s.pos, fp); 78 | 79 | /* close target file */ 80 | fclose(fp); 81 | fp = NULL; 82 | 83 | /* get rid of serializer */ 84 | serializer_deinit(&s); 85 | } 86 | 87 | /* merge the samples used in module mod located in src with the sample data in dst to reduce memory usage */ 88 | static void merge_samples(struct pimp_sample_bank *dst, const struct pimp_sample_bank *src, struct pimp_module *mod) 89 | { 90 | int i; 91 | for (i = 0; i < mod->instrument_count; ++i) 92 | { 93 | int j; 94 | struct pimp_instrument *instr = pimp_module_get_instrument(mod, i); 95 | for (j = 0; j < instr->sample_count; ++j) 96 | { 97 | int pos; 98 | void *data = NULL; 99 | struct pimp_sample *samp = pimp_instrument_get_sample(instr, j); 100 | ASSERT(NULL != samp); 101 | 102 | data = pimp_sample_bank_get_sample_data(src, samp->data_ptr); 103 | ASSERT(NULL != data); 104 | 105 | pos = pimp_sample_bank_find_sample_data(dst, data, samp->length); 106 | if (pos < 0) 107 | { 108 | pos = pimp_sample_bank_insert_sample_data(dst, data, samp->length); 109 | } 110 | samp->data_ptr = pos; 111 | } 112 | } 113 | } 114 | 115 | int main(int argc, char *argv[]) 116 | { 117 | FILE *fp = NULL; 118 | int i; 119 | int modules_loaded; 120 | int dither = 0; 121 | const char *sample_bank_fn = "sample_bank.bin"; 122 | 123 | struct pimp_sample_bank master_sample_bank; 124 | struct serializer s; 125 | 126 | pimp_sample_bank_init(&master_sample_bank); 127 | serializer_init(&s); 128 | 129 | modules_loaded = 0; 130 | for (i = 1; i < argc; ++i) 131 | { 132 | const char *arg = argv[i]; 133 | if (arg[0] == '-') 134 | { 135 | if (strlen(arg) < 2) print_usage(); 136 | 137 | switch (arg[1]) 138 | { 139 | case 'd': 140 | case 'D': 141 | dither = 1; 142 | break; 143 | default: print_usage(); 144 | } 145 | } 146 | else 147 | { 148 | struct pimp_module *mod; 149 | const char *ifn = arg; 150 | 151 | struct pimp_sample_bank sample_bank; 152 | pimp_sample_bank_init(&sample_bank); 153 | 154 | /* load module */ 155 | if (isatty(STDOUT_FILENO)) printf("loading %s...\n", ifn); 156 | mod = load_module(ifn, &sample_bank); 157 | 158 | if (NULL != mod) 159 | { 160 | char ofn[256]; 161 | 162 | modules_loaded++; 163 | 164 | /* generate output filename */ 165 | strncpy(ofn, ifn, 256); 166 | strncat(ofn, ".bin", 256); 167 | 168 | /* dump sample data */ 169 | merge_samples(&master_sample_bank, &sample_bank, mod); 170 | 171 | /* dump module */ 172 | if (isatty(STDOUT_FILENO)) printf("dumping %s...\n", ofn); 173 | dump_module(mod, ofn); 174 | } 175 | } 176 | } 177 | 178 | if (0 == modules_loaded) 179 | { 180 | fprintf(stderr, "%s: No input files\n", argv[0]); 181 | exit(EXIT_FAILURE); 182 | } 183 | 184 | if (isatty(STDOUT_FILENO)) printf("dumping %s\n", sample_bank_fn); 185 | fp = fopen(sample_bank_fn, "wb"); 186 | if (NULL == fp) 187 | { 188 | perror(sample_bank_fn); 189 | exit(EXIT_FAILURE); 190 | } 191 | 192 | fwrite(master_sample_bank.data, 1, master_sample_bank.size, fp); 193 | fclose(fp); 194 | fp = NULL; 195 | 196 | serializer_deinit(&s); 197 | 198 | return 0; 199 | } 200 | -------------------------------------------------------------------------------- /src/pimp_gba.c: -------------------------------------------------------------------------------- 1 | /* pimp_gba.c -- GameBoy Advance c-interface for Pimpmobile 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include "pimp_render.h" 7 | #include "pimp_debug.h" 8 | 9 | typedef volatile u16 vu16; 10 | typedef volatile u32 vu32; 11 | #define IWRAM_DATA __attribute__((section(".iwram"))) 12 | #define EWRAM_DATA __attribute__((section(".ewram"))) 13 | 14 | #define DMA_DST_FIXED (2U << 21) 15 | #define DMA_SRC_FIXED (2U << 23) 16 | #define DMA_REPEAT (1U << 25) 17 | #define DMA32 (1U << 26) 18 | #define DMA_SPECIAL (3U << 28) 19 | #define DMA_ENABLE (1U << 31) 20 | 21 | #define SNDA_VOL_100 (1U << 2) 22 | #define SNDA_R_ENABLE (1U << 8) 23 | #define SNDA_L_ENABLE (1U << 9) 24 | #define SNDA_RESET_FIFO (1U << 11) 25 | #define TIMER_START (1U << 7) 26 | 27 | #define REG_BASE 0x04000000 28 | #define REG_SOUNDCNT_H (*((vu16*)(REG_BASE + 0x082))) 29 | #define REG_SOUNDCNT_X (*((vu16*)(REG_BASE + 0x084))) 30 | #define REG_TM0CNT_L (*((vu16*)(REG_BASE + 0x100))) 31 | #define REG_TM0CNT_H (*((vu16*)(REG_BASE + 0x102))) 32 | #define REG_DMA1SAD (*((vu32*)(REG_BASE + 0x0bc))) 33 | #define REG_DMA1DAD (*((vu32*)(REG_BASE + 0x0c0))) 34 | #define REG_DMA1CNT (*((vu32*)(REG_BASE + 0x0c4))) 35 | #define REG_FIFO_A (*((vu32*)(REG_BASE + 0x0A0))) 36 | 37 | void CpuFastSet( const void *src, void *dst, u32 mode) 38 | { 39 | asm ( 40 | "mov r0, %[src] \n" 41 | "mov r1, %[dst] \n" 42 | "mov r2, %[mode] \n" 43 | #ifdef __thumb__ 44 | "swi 0xC \n" 45 | #else 46 | "swi 0xC << 16 \n" 47 | #endif 48 | : /* no output */ 49 | : /* inputs */ 50 | [src] "r"(src), 51 | [dst] "r"(dst), 52 | [mode] "r"(mode) 53 | : "r0", "r1", "r2", "r3" /* clobbers */ 54 | ); 55 | } 56 | 57 | static struct pimp_mixer pimp_gba_mixer IWRAM_DATA; 58 | static struct pimp_mod_context pimp_gba_ctx EWRAM_DATA; 59 | 60 | /* setup some constants */ 61 | static const float samplerate = (float)(1 << 24) / PIMP_GBA_PERIOD; 62 | #define CYCLES_PR_FRAME 280896 63 | #define SOUND_BUFFER_SIZE (CYCLES_PR_FRAME / PIMP_GBA_PERIOD) 64 | 65 | /* mix and playback-buffers */ 66 | static s8 pimp_gba_sound_buffers[2][SOUND_BUFFER_SIZE]; 67 | static u32 pimp_gba_sound_buffer_index = 0; 68 | static s32 pimp_gba_mix_buffer[SOUND_BUFFER_SIZE]; 69 | 70 | void pimp_gba_init(const struct pimp_module *module, const void *sample_bank) 71 | { 72 | u32 zero = 0; 73 | pimp_gba_mixer.mix_buffer = pimp_gba_mix_buffer; 74 | pimp_mod_context_init(&pimp_gba_ctx, (const pimp_module*)module, (const u8*)sample_bank, &pimp_gba_mixer, samplerate); 75 | 76 | /* call BIOS-function CpuFastSet() to clear buffer */ 77 | CpuFastSet(&zero, &pimp_gba_sound_buffers[0][0], DMA_SRC_FIXED | ((SOUND_BUFFER_SIZE / 4) * 2)); 78 | REG_SOUNDCNT_H = SNDA_VOL_100 | SNDA_L_ENABLE | SNDA_R_ENABLE | SNDA_RESET_FIFO; 79 | REG_SOUNDCNT_X = (1 << 7); 80 | 81 | DEBUG_PRINT(DEBUG_LEVEL_INFO, ("samples pr frame: 0x%x\nsound buffer size: %d\n", SAMPLES_PR_FRAME, SOUND_BUFFER_SIZE)); 82 | 83 | /* setup timer */ 84 | REG_TM0CNT_L = (1 << 16) - PIMP_GBA_PERIOD; 85 | REG_TM0CNT_H = TIMER_START; 86 | } 87 | 88 | void pimp_gba_close() 89 | { 90 | REG_SOUNDCNT_X = 0; 91 | } 92 | 93 | void pimp_gba_vblank() 94 | { 95 | if (pimp_gba_sound_buffer_index == 0) 96 | { 97 | REG_DMA1CNT = 0; 98 | REG_DMA1SAD = (u32) &(pimp_gba_sound_buffers[0][0]); 99 | REG_DMA1DAD = (u32) ®_FIFO_A; 100 | REG_DMA1CNT = DMA_DST_FIXED | DMA_REPEAT | DMA32 | DMA_SPECIAL | DMA_ENABLE; 101 | } 102 | pimp_gba_sound_buffer_index ^= 1; 103 | } 104 | 105 | void pimp_gba_set_callback(pimp_callback in_callback) 106 | { 107 | pimp_gba_ctx.callback = in_callback; 108 | } 109 | 110 | void pimp_gba_set_pos(int row, int order) 111 | { 112 | pimp_mod_context_set_pos(&pimp_gba_ctx, row, order); 113 | } 114 | 115 | int pimp_gba_get_row() 116 | { 117 | return pimp_mod_context_get_row(&pimp_gba_ctx); 118 | } 119 | 120 | int pimp_gba_get_order() 121 | { 122 | return pimp_mod_context_get_order(&pimp_gba_ctx); 123 | } 124 | 125 | void pimp_gba_frame() 126 | { 127 | static volatile BOOL locked = FALSE; 128 | if (TRUE == locked) return; /* whops, we're in the middle of filling. sorry. you did something wrong! */ 129 | locked = TRUE; 130 | 131 | pimp_render(&pimp_gba_ctx, pimp_gba_sound_buffers[pimp_gba_sound_buffer_index], SOUND_BUFFER_SIZE); 132 | 133 | locked = FALSE; 134 | } 135 | 136 | #ifndef DEBUG 137 | void pimp_mixer_clear(s32 *target, const u32 samples) 138 | { 139 | int i; 140 | static const u32 zero = 0; 141 | const u32 *src = &zero; 142 | u32 *dst = (u32*)target; 143 | 144 | /* bit 24 = fixed source address, bit 0-20 = wordcount (must be dividable by 8) */ 145 | const int mode = (1 << 24) | (samples & ~7); 146 | 147 | ASSERT(NULL != src); 148 | ASSERT(NULL != dst); 149 | 150 | /* clear the samples that CpuFastSet() can't */ 151 | for (i = samples & 7; i; --i) 152 | { 153 | *dst++ = 0; 154 | } 155 | if (0 == (samples & ~7)) return; 156 | 157 | ASSERT(((int)src & 3) == 0); 158 | ASSERT(((int)dst & 3) == 0); 159 | 160 | /* call BIOS-function CpuFastSet() to clear buffer */ 161 | CpuFastSet(src, dst, mode); 162 | } 163 | #endif 164 | -------------------------------------------------------------------------------- /src/pimp_internal.h: -------------------------------------------------------------------------------- 1 | /* pimp_internal.h -- Internal enums and routines for Pimpmobile 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #ifndef PIMP_INTERNAL_H 7 | #define PIMP_INTERNAL_H 8 | 9 | #include "pimp_base.h" 10 | #include "pimp_config.h" 11 | #include "pimp_debug.h" 12 | 13 | static INLINE void *pimp_get_ptr(const pimp_rel_ptr *offset) 14 | { 15 | ASSERT(NULL != offset); 16 | if (*offset == 0) return NULL; 17 | return (char*)offset + *offset; 18 | } 19 | 20 | static INLINE void pimp_set_ptr(pimp_rel_ptr *dst, const void *ptr) 21 | { 22 | ASSERT(NULL != dst); 23 | if (NULL == ptr) *dst = 0; 24 | else *dst = (pimp_rel_ptr)ptr - (pimp_rel_ptr)dst; 25 | } 26 | 27 | #define KEY_OFF 121 28 | 29 | typedef enum 30 | { 31 | EFF_NONE = 0x00, 32 | EFF_PORTA_UP = 0x01, 33 | EFF_PORTA_DOWN = 0x02, 34 | EFF_PORTA_NOTE = 0x03, 35 | EFF_VIBRATO = 0x04, 36 | EFF_PORTA_NOTE_VOLUME_SLIDE = 0x05, 37 | EFF_VIBRATO_VOLUME_SLIDE = 0x06, 38 | EFF_TREMOLO = 0x07, 39 | EFF_SET_PAN = 0x08, 40 | EFF_SAMPLE_OFFSET = 0x09, 41 | EFF_VOLUME_SLIDE = 0x0A, 42 | EFF_JUMP_ORDER = 0x0B, 43 | EFF_SET_VOLUME = 0x0C, 44 | EFF_BREAK_ROW = 0x0D, 45 | EFF_MULTI_FX = 0x0E, 46 | EFF_TEMPO = 0x0F, 47 | EFF_SET_GLOBAL_VOLUME = 0x10, 48 | EFF_GLOBAL_VOLUME_SLIDE = 0x11, 49 | EFF_KEY_OFF = 0x14, 50 | EFF_SET_ENVELOPE_POSITION = 0x15, 51 | EFF_PAN_SLIDE = 0x19, 52 | EFF_MULTI_RETRIG = 0x1B, 53 | EFF_TREMOR = 0x1D, 54 | EFF_SYNC_CALLBACK = 0x20, 55 | /* missing 0x21 here, multi-command (X1 = extra fine porta up, X2 = extra fine porta down) */ 56 | EFF_ARPEGGIO = 0x24, 57 | EFF_SET_TEMPO = 0x25, 58 | EFF_SET_BPM = 0x26 59 | 60 | /* 61 | TODO: fill in the rest 62 | 27xx: set channel volume (impulse-tracker) 63 | 28xx: slide channel volume (impulse-tracker) 64 | 29xx: panning vibrato (impulse-tracker) 65 | 2Axx: fine vibrato 66 | 2Bxy: multi-effect (impulse-tracker only) 67 | 5x: panning vibrato waveform( sane as vibrato/tremolo waveform) 68 | 70: past note cut 69 | 71: past note off 70 | 72: past note fade 71 | 73: set NNA to note cut 72 | 74: set NNA to continue 73 | 75: set NNA to note-off 74 | 76: set NNA to note-fade 75 | 77: turn off volume envelope 76 | 78: turn on volume envelope 77 | 79: turn off panning envelope 78 | 7A: turn on panning envelope 79 | 7B: turn off pitch envelope 80 | 7C: turn on pitch envelope 81 | 91: "set surround sound" ??? 82 | 83 | */ 84 | } pimp_effect; 85 | 86 | typedef enum 87 | { 88 | EFF_AMIGA_FILTER = 0x0, 89 | EFF_FINE_PORTA_UP = 0x1, 90 | EFF_FINE_PORTA_DOWN = 0x2, 91 | EFF_PATTERN_LOOP = 0x6, 92 | EFF_RETRIG_NOTE = 0x9, 93 | EFF_FINE_VOLUME_SLIDE_UP = 0xA, 94 | EFF_FINE_VOLUME_SLIDE_DOWN = 0xB, 95 | EFF_NOTE_CUT_AFTER_X_TICKS = 0xC, 96 | EFF_NOTE_DELAY = 0xD 97 | /* 98 | 0x: Amiga filter on/off (ignored; according to everybody, it sucks) 99 | 1x: fine porta up 100 | 2x: fine porta down 101 | 3x: glissando control (0=off, 1=on) 102 | 4x: set vibrato waveform/control: 103 | 0=sine, 1=ramp-down, 2=square, 3=random 104 | +4: vibrato waveform position is not reset on new-note 105 | 5x: set fine-tune for note 106 | 6x: pattern loop command (x=0 marks start of loop, 107 | x != 0 marks end of loop, with x=loop iterations to apply) 108 | 7x: set tremolo control (same syntax as vibrato control) 109 | 8x: normally unused; Digitrakker uses this command 110 | to control the looping mode of the currently-playing sample 111 | (0=off, 1=forward, 3=pingpong) 112 | 9x: retrig note 113 | Ax: fine volume slide up 114 | Bx: fine volume slide down 115 | Cx: note cut after x ticks 116 | Dx: note delay by x ticks 117 | Ex: pattern delay by x frames 118 | Fx: FunkInvert (does anyone actually have an idea what this does?) 119 | */ 120 | } pimp_multi_effect; 121 | 122 | /* packed, because it's all bytes. no member-alignment or anything needed */ 123 | struct pimp_pattern_entry 124 | { 125 | u8 note; 126 | u8 instrument; 127 | u8 volume_command; 128 | u8 effect_byte; 129 | u8 effect_parameter; 130 | } __attribute__((packed)); 131 | 132 | enum 133 | { 134 | FLAG_LINEAR_PERIODS = (1 << 0), 135 | FLAG_LINEAR_VIBRATO = (1 << 1), 136 | FLAG_VOL_SLIDE_TICK0 = (1 << 2), 137 | FLAG_VIBRATO_TICK0 = (1 << 3), 138 | FLAG_VOL0_OPTIMIZE = (1 << 4), 139 | FLAG_TEMOR_EXTRA_DELAY = (1 << 5), 140 | FLAG_TEMOR_MEMORY = (1 << 6), 141 | FLAG_RETRIG_KILLS_NOTE = (1 << 7), 142 | FLAG_NOTE_CUT_KILLS_NOTE = (1 << 8), 143 | FLAG_ALLOW_NESTED_LOOPS = (1 << 9), 144 | FLAG_RETRIG_NOTE_PERIOD = (1 << 10), 145 | FLAG_DELAY_GLOBAL_VOLUME = (1 << 11), 146 | FLAG_SAMPLE_OFFSET_CLAMP = (1 << 12), 147 | FLAG_PORTA_NOTE_SHARE_MEMORY = (1 << 13), 148 | FLAG_PORTA_NOTE_MEMORY = (1 << 14) 149 | }; 150 | 151 | #endif /* PIMP_INTERNAL_H */ 152 | -------------------------------------------------------------------------------- /src/pimp_mod_context.c: -------------------------------------------------------------------------------- 1 | /* pimp_mod_context.c -- The rendering-context for a module 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include "pimp_mod_context.h" 7 | 8 | void pimp_mod_context_init(struct pimp_mod_context *ctx, const pimp_module *mod, const u8 *sample_bank, struct pimp_mixer *mixer, const float samplerate) 9 | { 10 | int i; 11 | ASSERT(ctx != NULL); 12 | 13 | ctx->mod = mod; 14 | ctx->sample_bank = sample_bank; 15 | ctx->mixer = mixer; 16 | pimp_mod_context_set_samplerate(ctx, samplerate); 17 | 18 | /* setup default player-state */ 19 | ctx->tick_len = 0; 20 | ctx->curr_tick_len = 0; 21 | 22 | ctx->curr_row = 0; 23 | ctx->curr_order = 0; 24 | ctx->curr_pattern = (struct pimp_pattern*)NULL; 25 | ctx->curr_tick = 0; 26 | 27 | ctx->next_row = 0; 28 | ctx->next_order = 0; 29 | ctx->next_pattern = pimp_module_get_pattern(ctx->mod, pimp_module_get_order(ctx->mod, ctx->next_order)); 30 | 31 | ctx->curr_bpm = 125; 32 | ctx->curr_tempo = 5; 33 | ctx->remainder = 0; 34 | 35 | ctx->global_volume = 1 << 9; /* 24.8 fixed point */ 36 | 37 | ctx->curr_pattern = pimp_module_get_pattern(mod, pimp_module_get_order(mod, ctx->curr_order)); 38 | pimp_mod_context_set_bpm(ctx, ctx->mod->bpm); 39 | ctx->curr_tempo = mod->tempo; 40 | ctx->curr_tick = ctx->curr_tempo; /* make sure we skip to the next row right away */ 41 | 42 | for (i = 0; i < PIMP_CHANNEL_COUNT; ++i) 43 | { 44 | struct pimp_channel_state *chan = &ctx->channels[i]; 45 | chan->instrument = (const struct pimp_instrument*)NULL; 46 | chan->sample = (const struct pimp_sample*) NULL; 47 | chan->vol_env.env = (const struct pimp_envelope*) NULL; 48 | 49 | chan->volume_command = 0; 50 | chan->effect = 0; 51 | chan->effect_param = 0; 52 | 53 | chan->note_delay = 0; 54 | chan->note_retrig = 0; 55 | chan->retrig_tick = 0; 56 | chan->porta_target = 0; 57 | chan->porta_speed = 0; 58 | chan->fadeout = 0; 59 | chan->volume_slide_speed = 0; 60 | 61 | chan->loop_target_order = 0; 62 | chan->loop_target_row = 0; 63 | chan->loop_counter = 0; 64 | 65 | pimp_envelope_reset(&chan->vol_env); 66 | } 67 | 68 | ctx->callback = (pimp_callback)NULL; 69 | 70 | pimp_mixer_reset(ctx->mixer); 71 | } 72 | 73 | void pimp_mod_context_set_samplerate(struct pimp_mod_context *ctx, const float samplerate) 74 | { 75 | ctx->samplerate = samplerate; 76 | ctx->delta_scale = (unsigned int)((1.0 / samplerate) * (1 << 6) * (1ULL << 32)); 77 | } 78 | 79 | /* "hard" jump in a module */ 80 | void pimp_mod_context_set_pos(struct pimp_mod_context *ctx, int row, int order) 81 | { 82 | ASSERT(ctx != NULL); 83 | 84 | if (1) 85 | { 86 | /* skip ahead to next pos right away */ 87 | ctx->curr_tick = ctx->curr_tempo; 88 | ctx->remainder = 0; 89 | } 90 | 91 | ctx->next_row = MAX(row, 0); 92 | ctx->next_order = MAX(order, 0); 93 | if (ctx->next_order >= ctx->mod->order_count) ctx->next_order = ctx->mod->order_repeat; 94 | ctx->next_pattern = pimp_module_get_pattern(ctx->mod, pimp_module_get_order(ctx->mod, ctx->next_order)); 95 | if (ctx->next_row >= ctx->next_pattern->row_count) ctx->next_row = ctx->next_pattern->row_count - 1; 96 | 97 | #if 0 98 | /* wrap row (seek forward) */ 99 | while (ctx->next_row >= ctx->next_pattern->row_count) 100 | { 101 | ctx->next_row -= ctx->next_pattern->row_count; 102 | ctx->next_order++; 103 | if (ctx->next_order >= ctx->mod->order_count) 104 | { 105 | ctx->next_order = ctx->mod->order_repeat; 106 | } 107 | ctx->next_pattern = pimp_module_get_pattern(ctx->mod, pimp_module_get_order(ctx->mod, ctx->next_order)); 108 | } 109 | #endif 110 | } 111 | 112 | /* make sure next pos isn't outside a pattern or the order-list */ 113 | static void pimp_mod_context_fix_next_pos(struct pimp_mod_context *ctx) 114 | { 115 | if (ctx->next_row == ctx->curr_pattern->row_count) 116 | { 117 | ctx->next_row = 0; 118 | ctx->next_order++; 119 | 120 | /* check for pattern loop */ 121 | if (ctx->next_order >= ctx->mod->order_count) ctx->next_order = ctx->mod->order_repeat; 122 | } 123 | 124 | if (ctx->next_order != ctx->curr_order) 125 | { 126 | ctx->next_pattern = pimp_module_get_pattern(ctx->mod, pimp_module_get_order(ctx->mod, ctx->next_order)); 127 | } 128 | } 129 | 130 | /* setup next position to be one row advanced in module */ 131 | void pimp_mod_context_update_next_pos(struct pimp_mod_context *ctx) 132 | { 133 | ctx->next_row = ctx->curr_row + 1; 134 | ctx->next_order = ctx->curr_order; 135 | pimp_mod_context_fix_next_pos(ctx); 136 | } 137 | 138 | /* setup next position to be a specific position. useful for jumping etc */ 139 | void pimp_mod_context_set_next_pos(struct pimp_mod_context *ctx, int row, int order) 140 | { 141 | ASSERT(ctx != NULL); 142 | 143 | ctx->next_row = row; 144 | ctx->next_order = order; 145 | pimp_mod_context_fix_next_pos(ctx); 146 | } 147 | 148 | 149 | void pimp_mod_context_set_bpm(struct pimp_mod_context *ctx, int bpm) 150 | { 151 | /* we're using 8 fractional-bits for the tick-length */ 152 | const int temp = (int)((ctx->samplerate * 5) * (1 << 8)); 153 | 154 | ASSERT(ctx != NULL); 155 | ASSERT(bpm > 0); 156 | 157 | ctx->tick_len = temp / (bpm * 2); 158 | } 159 | -------------------------------------------------------------------------------- /converter/serialize_module.c: -------------------------------------------------------------------------------- 1 | /* serialize_module.c -- Serializer for module structure 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | 7 | #include 8 | 9 | #include "serializer.h" 10 | #include "serialize_module.h" 11 | 12 | #include "../src/pimp_module.h" 13 | #include "../src/pimp_mixer.h" 14 | 15 | #if 0 16 | #include 17 | #define TRACE() printf("AT %s:%d\n", __FILE__, __LINE__); 18 | #else 19 | #define TRACE() 20 | #endif 21 | 22 | #include "serialize_instrument.h" 23 | 24 | void serialize_module_struct(struct serializer *s, const pimp_module *mod) 25 | { 26 | ASSERT(NULL != s); 27 | ASSERT(NULL != mod); 28 | TRACE(); 29 | 30 | /* dump pimp_module structure */ 31 | serialize_string(s, mod->name, 32); 32 | serialize_word(s, mod->flags); 33 | serialize_word(s, mod->reserved); 34 | 35 | serialize_pointer(s, pimp_get_ptr(&mod->order_ptr)); 36 | serialize_pointer(s, pimp_get_ptr(&mod->pattern_ptr)); 37 | serialize_pointer(s, pimp_get_ptr(&mod->channel_ptr)); 38 | serialize_pointer(s, pimp_get_ptr(&mod->instrument_ptr)); 39 | 40 | serialize_halfword(s, mod->period_low_clamp); 41 | serialize_halfword(s, mod->period_high_clamp); 42 | serialize_halfword(s, mod->order_count); 43 | 44 | serialize_byte(s, mod->order_repeat); 45 | serialize_byte(s, mod->volume); 46 | serialize_byte(s, mod->tempo); 47 | serialize_byte(s, mod->bpm); 48 | 49 | serialize_byte(s, mod->instrument_count); 50 | serialize_byte(s, mod->pattern_count); 51 | serialize_byte(s, mod->channel_count); 52 | } 53 | 54 | void serialize_channels(struct serializer *s, const struct pimp_module *mod) 55 | { 56 | int i; 57 | 58 | ASSERT(NULL != s); 59 | ASSERT(NULL != mod); 60 | TRACE(); 61 | 62 | /* dump channels (all bytes, no serializer_alignment required) */ 63 | serializer_align(s, 4); 64 | serializer_set_pointer(s, pimp_get_ptr(&mod->channel_ptr), s->pos); 65 | for (i = 0; i < mod->channel_count; ++i) 66 | { 67 | const struct pimp_channel *chan = pimp_module_get_channel(mod, i); 68 | serialize_byte(s, chan->pan); 69 | serialize_byte(s, chan->volume); 70 | serialize_byte(s, chan->mute); 71 | } 72 | } 73 | 74 | void serialize_instruments(struct serializer *s, const struct pimp_module *mod) 75 | { 76 | int i; 77 | 78 | ASSERT(NULL != s); 79 | ASSERT(NULL != mod); 80 | TRACE(); 81 | 82 | /* dump instrument structs */ 83 | serializer_align(s, 4); 84 | serializer_set_pointer(s, pimp_get_ptr(&mod->instrument_ptr), s->pos); 85 | for (i = 0; i < mod->instrument_count; ++i) 86 | { 87 | const struct pimp_instrument *instr = pimp_module_get_instrument(mod, i); 88 | serialize_instrument(s, instr); 89 | } 90 | 91 | /* dump instrument data (samples, envelopes etc) */ 92 | for (i = 0; i < mod->instrument_count; ++i) 93 | { 94 | const struct pimp_instrument *instr = pimp_module_get_instrument(mod, i); 95 | serialize_instrument_data(s, instr); 96 | } 97 | } 98 | 99 | void serialize_orders(struct serializer *s, const struct pimp_module *mod) 100 | { 101 | int i; 102 | 103 | ASSERT(NULL != s); 104 | ASSERT(NULL != mod); 105 | TRACE(); 106 | 107 | /* dump orders (all bytes, no serializer_alignment required) */ 108 | serializer_set_pointer(s, pimp_get_ptr(&mod->order_ptr), s->pos); 109 | for (i = 0; i < mod->order_count; ++i) 110 | { 111 | serialize_byte(s, pimp_module_get_order(mod, i)); 112 | } 113 | } 114 | 115 | void serialize_patterns(struct serializer *s, const struct pimp_module *mod) 116 | { 117 | int i; 118 | 119 | ASSERT(NULL != s); 120 | ASSERT(NULL != mod); 121 | TRACE(); 122 | 123 | /* dump array of pimp_pattern structs */ 124 | serializer_align(s, 4); 125 | serializer_set_pointer(s, pimp_get_ptr(&mod->pattern_ptr), s->pos); 126 | for (i = 0; i < mod->pattern_count; ++i) 127 | { 128 | const struct pimp_pattern *pattern = pimp_module_get_pattern(mod, i); 129 | serializer_align(s, 4); 130 | 131 | serialize_pointer(s, pimp_get_ptr(&pattern->data_ptr)); /* data_ptr */ 132 | serialize_halfword(s, pattern->row_count); 133 | } 134 | 135 | /* dump pattern data */ 136 | for (i = 0; i < mod->pattern_count; ++i) 137 | { 138 | int j; 139 | const struct pimp_pattern_entry *pattern_data = NULL; 140 | 141 | const struct pimp_pattern *pattern = pimp_module_get_pattern(mod, i); 142 | if (NULL == pattern) continue; 143 | 144 | pattern_data = pimp_pattern_get_data(pattern); 145 | if (NULL == pattern_data) continue; 146 | 147 | serializer_set_pointer(s, pimp_get_ptr(&pattern->data_ptr), s->pos); 148 | 149 | /* write the actual pattern data data */ 150 | for (j = 0; j < pattern->row_count * mod->channel_count; ++j) 151 | { 152 | const struct pimp_pattern_entry *pe = &pattern_data[j]; 153 | serialize_byte(s, pe->note); 154 | serialize_byte(s, pe->instrument); 155 | serialize_byte(s, pe->volume_command); 156 | serialize_byte(s, pe->effect_byte); 157 | serialize_byte(s, pe->effect_parameter); 158 | } 159 | } 160 | } 161 | 162 | void serialize_module(struct serializer *s, const struct pimp_module *mod) 163 | { 164 | ASSERT(NULL != s); 165 | ASSERT(NULL != mod); 166 | TRACE(); 167 | 168 | serialize_module_struct(s, mod); 169 | serialize_orders(s, mod); 170 | serialize_patterns(s, mod); 171 | serialize_channels(s, mod); 172 | serialize_instruments(s, mod); 173 | } 174 | -------------------------------------------------------------------------------- /src/linear_delta_lut.h: -------------------------------------------------------------------------------- 1 | const u16 pimp_linear_delta_lut[768] = 2 | { 3 | 16726, 16741, 16756, 16771, 16786, 16802, 16817, 16832, 16847, 16862, 16878, 16893, 4 | 16908, 16923, 16939, 16954, 16969, 16985, 17000, 17015, 17031, 17046, 17061, 17077, 5 | 17092, 17108, 17123, 17139, 17154, 17170, 17185, 17201, 17216, 17232, 17247, 17263, 6 | 17278, 17294, 17310, 17325, 17341, 17357, 17372, 17388, 17404, 17419, 17435, 17451, 7 | 17467, 17482, 17498, 17514, 17530, 17546, 17561, 17577, 17593, 17609, 17625, 17641, 8 | 17657, 17673, 17689, 17705, 17721, 17737, 17753, 17769, 17785, 17801, 17817, 17833, 9 | 17849, 17865, 17881, 17897, 17914, 17930, 17946, 17962, 17978, 17995, 18011, 18027, 10 | 18043, 18060, 18076, 18092, 18109, 18125, 18141, 18158, 18174, 18191, 18207, 18223, 11 | 18240, 18256, 18273, 18289, 18306, 18322, 18339, 18355, 18372, 18389, 18405, 18422, 12 | 18438, 18455, 18472, 18488, 18505, 18522, 18539, 18555, 18572, 18589, 18606, 18622, 13 | 18639, 18656, 18673, 18690, 18707, 18724, 18740, 18757, 18774, 18791, 18808, 18825, 14 | 18842, 18859, 18876, 18893, 18910, 18927, 18945, 18962, 18979, 18996, 19013, 19030, 15 | 19047, 19065, 19082, 19099, 19116, 19134, 19151, 19168, 19185, 19203, 19220, 19237, 16 | 19255, 19272, 19290, 19307, 19324, 19342, 19359, 19377, 19394, 19412, 19429, 19447, 17 | 19464, 19482, 19500, 19517, 19535, 19552, 19570, 19588, 19606, 19623, 19641, 19659, 18 | 19676, 19694, 19712, 19730, 19748, 19765, 19783, 19801, 19819, 19837, 19855, 19873, 19 | 19891, 19909, 19927, 19945, 19963, 19981, 19999, 20017, 20035, 20053, 20071, 20089, 20 | 20107, 20125, 20144, 20162, 20180, 20198, 20216, 20235, 20253, 20271, 20290, 20308, 21 | 20326, 20345, 20363, 20381, 20400, 20418, 20437, 20455, 20474, 20492, 20511, 20529, 22 | 20548, 20566, 20585, 20603, 20622, 20640, 20659, 20678, 20696, 20715, 20734, 20753, 23 | 20771, 20790, 20809, 20828, 20846, 20865, 20884, 20903, 20922, 20941, 20960, 20979, 24 | 20997, 21016, 21035, 21054, 21073, 21092, 21112, 21131, 21150, 21169, 21188, 21207, 25 | 21226, 21245, 21264, 21284, 21303, 21322, 21341, 21361, 21380, 21399, 21419, 21438, 26 | 21457, 21477, 21496, 21515, 21535, 21554, 21574, 21593, 21613, 21632, 21652, 21671, 27 | 21691, 21711, 21730, 21750, 21769, 21789, 21809, 21828, 21848, 21868, 21888, 21907, 28 | 21927, 21947, 21967, 21987, 22006, 22026, 22046, 22066, 22086, 22106, 22126, 22146, 29 | 22166, 22186, 22206, 22226, 22246, 22266, 22286, 22306, 22327, 22347, 22367, 22387, 30 | 22407, 22428, 22448, 22468, 22488, 22509, 22529, 22549, 22570, 22590, 22610, 22631, 31 | 22651, 22672, 22692, 22713, 22733, 22754, 22774, 22795, 22815, 22836, 22857, 22877, 32 | 22898, 22919, 22939, 22960, 22981, 23001, 23022, 23043, 23064, 23085, 23106, 23126, 33 | 23147, 23168, 23189, 23210, 23231, 23252, 23273, 23294, 23315, 23336, 23357, 23378, 34 | 23399, 23420, 23442, 23463, 23484, 23505, 23526, 23548, 23569, 23590, 23611, 23633, 35 | 23654, 23675, 23697, 23718, 23740, 23761, 23783, 23804, 23826, 23847, 23869, 23890, 36 | 23912, 23933, 23955, 23977, 23998, 24020, 24042, 24063, 24085, 24107, 24129, 24150, 37 | 24172, 24194, 24216, 24238, 24260, 24281, 24303, 24325, 24347, 24369, 24391, 24413, 38 | 24435, 24457, 24479, 24502, 24524, 24546, 24568, 24590, 24612, 24635, 24657, 24679, 39 | 24701, 24724, 24746, 24768, 24791, 24813, 24836, 24858, 24880, 24903, 24925, 24948, 40 | 24970, 24993, 25015, 25038, 25061, 25083, 25106, 25129, 25151, 25174, 25197, 25220, 41 | 25242, 25265, 25288, 25311, 25334, 25356, 25379, 25402, 25425, 25448, 25471, 25494, 42 | 25517, 25540, 25563, 25586, 25609, 25633, 25656, 25679, 25702, 25725, 25748, 25772, 43 | 25795, 25818, 25842, 25865, 25888, 25912, 25935, 25959, 25982, 26005, 26029, 26052, 44 | 26076, 26099, 26123, 26147, 26170, 26194, 26217, 26241, 26265, 26289, 26312, 26336, 45 | 26360, 26384, 26407, 26431, 26455, 26479, 26503, 26527, 26551, 26575, 26599, 26623, 46 | 26647, 26671, 26695, 26719, 26743, 26767, 26792, 26816, 26840, 26864, 26888, 26913, 47 | 26937, 26961, 26986, 27010, 27034, 27059, 27083, 27108, 27132, 27157, 27181, 27206, 48 | 27230, 27255, 27280, 27304, 27329, 27354, 27378, 27403, 27428, 27452, 27477, 27502, 49 | 27527, 27552, 27577, 27602, 27626, 27651, 27676, 27701, 27726, 27751, 27776, 27802, 50 | 27827, 27852, 27877, 27902, 27927, 27953, 27978, 28003, 28028, 28054, 28079, 28104, 51 | 28130, 28155, 28180, 28206, 28231, 28257, 28282, 28308, 28334, 28359, 28385, 28410, 52 | 28436, 28462, 28487, 28513, 28539, 28565, 28590, 28616, 28642, 28668, 28694, 28720, 53 | 28746, 28772, 28798, 28824, 28850, 28876, 28902, 28928, 28954, 28980, 29006, 29032, 54 | 29059, 29085, 29111, 29137, 29164, 29190, 29216, 29243, 29269, 29296, 29322, 29349, 55 | 29375, 29402, 29428, 29455, 29481, 29508, 29535, 29561, 29588, 29615, 29641, 29668, 56 | 29695, 29722, 29749, 29775, 29802, 29829, 29856, 29883, 29910, 29937, 29964, 29991, 57 | 30018, 30045, 30073, 30100, 30127, 30154, 30181, 30209, 30236, 30263, 30290, 30318, 58 | 30345, 30373, 30400, 30427, 30455, 30482, 30510, 30538, 30565, 30593, 30620, 30648, 59 | 30676, 30703, 30731, 30759, 30787, 30814, 30842, 30870, 30898, 30926, 30954, 30982, 60 | 31010, 31038, 31066, 31094, 31122, 31150, 31178, 31206, 31234, 31263, 31291, 31319, 61 | 31347, 31376, 31404, 31432, 31461, 31489, 31518, 31546, 31574, 31603, 31632, 31660, 62 | 31689, 31717, 31746, 31775, 31803, 31832, 31861, 31890, 31918, 31947, 31976, 32005, 63 | 32034, 32063, 32092, 32121, 32150, 32179, 32208, 32237, 32266, 32295, 32324, 32353, 64 | 32383, 32412, 32441, 32470, 32500, 32529, 32558, 32588, 32617, 32647, 32676, 32706, 65 | 32735, 32765, 32794, 32824, 32854, 32883, 32913, 32943, 32972, 33002, 33032, 33062, 66 | 33092, 33122, 33151, 33181, 33211, 33241, 33271, 33301, 33331, 33362, 33392, 33422, 67 | 68 | }; 69 | 70 | -------------------------------------------------------------------------------- /examples/libgbfs.c: -------------------------------------------------------------------------------- 1 | /* libgbfs.c 2 | access object in a GBFS file 3 | 4 | Copyright 2002-2004 Damian Yerrick 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 | AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 23 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | IN THE SOFTWARE. 25 | 26 | */ 27 | 28 | 29 | /* This code assumes a LITTLE ENDIAN target. It'll need a boatload 30 | of itohs and itohl calls if converted to run on Sega Genesis. It 31 | also assumes that the target uses 16-bit short and 32-bit longs. 32 | */ 33 | 34 | typedef unsigned short u16; 35 | typedef unsigned long u32; 36 | 37 | #include 38 | #include 39 | #include "gbfs.h" 40 | 41 | /* change this to the end of your ROM, or to 0x02040000 for multiboot */ 42 | #define GBFS_1ST_SEARCH_LIMIT ((const u32 *)0x02040000) 43 | #define GBFS_2ND_SEARCH_START ((const u32 *)0x08000000) 44 | #define GBFS_2ND_SEARCH_LIMIT ((const u32 *)0x0a000000) 45 | 46 | /* a power of two, less than or equal to the argument passed to 47 | padbin. Increasing the stride makes find_first_gbfs_file() 48 | faster at the cost of a slightly larger binary. */ 49 | #define GBFS_ALIGNMENT 256 50 | 51 | const GBFS_FILE *find_first_gbfs_file(const void *start) 52 | { 53 | /* align the pointer */ 54 | const u32 *here = (const u32 *) 55 | ((unsigned long)start & (-GBFS_ALIGNMENT)); 56 | const char rest_of_magic[] = "ightGBFS\r\n\x1a\n"; 57 | 58 | /* Linear-search first in multiboot space. */ 59 | while(here < GBFS_1ST_SEARCH_LIMIT) 60 | { 61 | /* We have to keep the magic code in two pieces; otherwise, 62 | this function may find itself and think it's a GBFS file. 63 | This obviously won't work if your compiler stores this 64 | numeric literal just before the literal string, but Devkit 65 | Advance R4 and R5 seem to keep numeric constant pools 66 | separate enough from string pools for this to work. 67 | */ 68 | if(*here == 0x456e6950) /* ASCII code for little endian "PinE" */ 69 | { 70 | /* We've matched the first four bytes. 71 | If the rest of the magic matches, then we've found a file. */ 72 | if(!memcmp(here + 1, rest_of_magic, 12)) 73 | return (const GBFS_FILE *)here; 74 | } 75 | here += GBFS_ALIGNMENT / sizeof(*here); 76 | } 77 | 78 | /* Now search in ROM space. */ 79 | if(here < GBFS_2ND_SEARCH_START) 80 | here = GBFS_2ND_SEARCH_START; 81 | while(here < GBFS_2ND_SEARCH_LIMIT) 82 | { 83 | /* Search loop same as above. */ 84 | if(*here == 0x456e6950) /* ASCII code for little endian "PinE" */ 85 | { 86 | if(!memcmp(here + 1, rest_of_magic, 12)) 87 | return (const GBFS_FILE *)here; 88 | } 89 | here += GBFS_ALIGNMENT / sizeof(*here); 90 | } 91 | return 0; 92 | } 93 | 94 | 95 | const void *skip_gbfs_file(const GBFS_FILE *file) 96 | { 97 | return ((char *)file + file->total_len); 98 | } 99 | 100 | 101 | static int namecmp(const void *a, const void *b) 102 | { 103 | return memcmp(a, b, 24); 104 | } 105 | 106 | 107 | const void *gbfs_get_obj(const GBFS_FILE *file, 108 | const char *name, 109 | u32 *len) 110 | { 111 | char key[24] = {0}; 112 | 113 | const GBFS_ENTRY *dirbase = (const GBFS_ENTRY *)((const char *)file + file->dir_off); 114 | size_t n_entries = file->dir_nmemb; 115 | const GBFS_ENTRY *here; 116 | 117 | strncpy(key, name, 24); 118 | 119 | here = bsearch(key, dirbase, 120 | n_entries, sizeof(GBFS_ENTRY), 121 | namecmp); 122 | if(!here) 123 | return NULL; 124 | 125 | if(len) 126 | *len = here->len; 127 | return (char *)file + here->data_offset; 128 | } 129 | 130 | 131 | const void *gbfs_get_nth_obj(const GBFS_FILE *file, 132 | size_t n, 133 | char *name, 134 | u32 *len) 135 | { 136 | const GBFS_ENTRY *dirbase = (const GBFS_ENTRY *)((const char *)file + file->dir_off); 137 | size_t n_entries = file->dir_nmemb; 138 | const GBFS_ENTRY *here = dirbase + n; 139 | 140 | if(n >= n_entries) 141 | return NULL; 142 | 143 | if(name) 144 | { 145 | strncpy(name, here->name, 24); 146 | name[24] = 0; 147 | } 148 | 149 | if(len) 150 | *len = here->len; 151 | 152 | return (char *)file + here->data_offset; 153 | } 154 | 155 | 156 | void *gbfs_copy_obj(void *dst, 157 | const GBFS_FILE *file, 158 | const char *name) 159 | { 160 | u32 len; 161 | const void *src = gbfs_get_obj(file, name, &len); 162 | 163 | if(!src) 164 | return NULL; 165 | 166 | memcpy(dst, src, len); 167 | return dst; 168 | } 169 | 170 | 171 | size_t gbfs_count_objs(const GBFS_FILE *file) 172 | { 173 | return file ? file->dir_nmemb : 0; 174 | } 175 | 176 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for pimpmobile module player 2 | # Copyright (C) 2005-2007 Jørn Nystad and Erik Faye-Lund 3 | # For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | 5 | # default-configuration 6 | TARGET ?= arm-gba 7 | HOST ?= $(shell $(CC) -dumpmachine) 8 | BUILD_DIR ?= build 9 | CONFIG ?= release 10 | 11 | # some common unix utils 12 | AWK ?= awk 13 | SORT ?= sort 14 | PR ?= pr 15 | CTAGS ?= ctags 16 | 17 | ifneq ($(strip $(DEVKITPRO)),) 18 | DEVKITARM = $(DEVKITPRO)/devkitARM 19 | export PATH := $(DEVKITARM)/bin:$(PATH) 20 | endif 21 | 22 | ifdef COMSPEC 23 | EXE_EXT = .exe 24 | else 25 | EXE_EXT = 26 | endif 27 | 28 | ifeq ($(TARGET), arm-gba) 29 | TARGET_PREFIX ?= arm-none-eabi- 30 | else 31 | TARGET_PREFIX = $(TARGET) 32 | endif 33 | 34 | define setup-gcc 35 | $(1)CC = $(2)gcc 36 | $(1)STRIP = $(2)strip 37 | $(1)LD = $(2)ld 38 | $(1)AS = $(2)as 39 | $(1)AR = $(2)ar 40 | endef 41 | 42 | define setup-armcc 43 | $(1)CC = armcc 44 | $(1)STRIP = strip 45 | $(1)LD = armlink 46 | $(1)AS = armasm 47 | $(1)AR = armar 48 | endef 49 | 50 | ifdef USE_ARMCC 51 | $(eval $(call setup-armcc,TARGET_)) 52 | else 53 | $(eval $(call setup-gcc,TARGET_, $(TARGET_PREFIX))) 54 | endif 55 | 56 | $(eval $(call setup-gcc,,)) 57 | 58 | ifneq ($(findstring $(MAKEFLAGS),s),s) 59 | QUIET_CC = @echo ' ' CC $@; 60 | QUIET_AS = @echo ' ' AS $@; 61 | QUIET_AR = @echo ' ' AR $@; 62 | QUIET_LINK = @echo ' ' LINK $@; 63 | endif 64 | 65 | MKDIR = mkdir -p 66 | 67 | ifeq ($(TARGET), arm-gba) 68 | TARGET_CPPFLAGS = -I$(DEVKITARM)/include -DTARGET_GBA 69 | TARGET_COMMON = -mcpu=arm7tdmi -mtune=arm7tdmi -mthumb-interwork 70 | TARGET_CFLAGS = $(TARGET_COMMON) -mlong-calls 71 | TARGET_LDFLAGS = $(TARGET_COMMON) -Wl,--gc-section 72 | TARGET_ASFLAGS = $(TARGET_COMMON) 73 | endif 74 | 75 | CPPFLAGS = 76 | CFLAGS = -pedantic -Wall -Wno-long-long 77 | LDFLAGS = 78 | ASFLAGS = 79 | ARFLAGS = rcs 80 | 81 | ARM = -marm 82 | THUMB = -mthumb 83 | 84 | SOURCES = \ 85 | src/pimp_render.c \ 86 | src/pimp_envelope.c \ 87 | src/pimp_mod_context.c \ 88 | src/pimp_math.c \ 89 | src/pimp_mixer.c 90 | 91 | ifeq ($(TARGET), arm-gba) 92 | SOURCES += src/pimp_gba.c 93 | endif 94 | 95 | ARM_SOURCES = \ 96 | src/pimp_math.c \ 97 | src/pimp_mixer.c 98 | 99 | PIMPCONV_SOURCES = \ 100 | converter/pimpconv.c \ 101 | converter/serializer.c \ 102 | converter/serialize_module.c \ 103 | converter/serialize_instrument.c \ 104 | src/convert_sample.c \ 105 | src/load_xm.c \ 106 | src/load_mod.c \ 107 | src/pimp_sample_bank.c 108 | 109 | ifeq ($(CONFIG), debug) 110 | CPPFLAGS += -DDEBUG 111 | CFLAGS += -g -ggdb 112 | SOURCES += src/pimp_mixer_portable.c 113 | SOURCES += src/pimp_debug.c 114 | else 115 | CPPFLAGS += -DRELEASE -DNDEBUG 116 | CFLAGS += -O3 -fomit-frame-pointer 117 | SOURCES += src/pimp_mixer_arm.S 118 | SOURCES += src/pimp_mixer_clip_arm.S 119 | endif 120 | 121 | ifeq ($(PROFILING), 1) 122 | CFLAGS += -finstrument-functions 123 | SOURCES += profiling/cyg-profile.c 124 | endif 125 | 126 | TARGET_BUILD_DIR = $(BUILD_DIR)/$(TARGET)/$(CONFIG) 127 | HOST_BUILD_DIR = $(BUILD_DIR)/$(HOST)/$(CONFIG) 128 | 129 | source-to-object = \ 130 | $(subst .c,.o, $(filter-out $(ARM_SOURCES), $(filter %.c,$1))) \ 131 | $(subst .c,.iwram.o, $(filter $(ARM_SOURCES), $(filter %.c,$1))) \ 132 | $(subst .S,.o, $(filter-out $(ARM_SOURCES), $(filter %.S,$1))) \ 133 | $(subst .S,.iwram.o, $(filter $(ARM_SOURCES), $(filter %.S,$1))) 134 | 135 | source-to-depend = $(subst .o,.d, $(call source-to-object, $1)) 136 | 137 | make-target-objs = $(addprefix $(TARGET_BUILD_DIR)/, $(call source-to-object, $1)) 138 | make-host-objs = $(addprefix $(HOST_BUILD_DIR)/, $(call source-to-object, $1)) 139 | 140 | make-target-deps = $(addprefix $(TARGET_BUILD_DIR)/, $(call source-to-depend, $1)) 141 | make-host-deps = $(addprefix $(HOST_BUILD_DIR)/, $(call source-to-depend, $1)) 142 | 143 | OBJS = $(call make-target-objs, $(SOURCES)) 144 | 145 | .PHONY: all clean check check-syntax examples 146 | 147 | all: lib/libpimp_gba.a bin/pimpconv$(EXE_EXT) 148 | 149 | clean: 150 | $(RM) lib/libpimp_gba.a $(call make-target-objs, $(SOURCES)) $(call make-target-deps, $(SOURCES)) 151 | $(RM) bin/pimpconv$(EXE_EXT) $(call make-host-objs, $(PIMPCONV_SOURCES)) $(call make-host-deps, $(PIMPCONV_SOURCES)) 152 | $(MAKE) -C examples clean 153 | 154 | distclean: 155 | $(RM) -r $(BUILD_DIR) 156 | 157 | check: 158 | $(MAKE) -C t run 159 | 160 | check-syntax: 161 | $(TARGET_CC) $(CPPFLAGS) $(TARGET_CPPFLAGS) $(CFLAGS) -fsyntax-only $(filter %.c,$(SOURCES)) 162 | 163 | examples: lib/libpimp_gba.a bin/pimpconv$(EXE_EXT) 164 | $(MAKE) -C examples 165 | 166 | TAGS: 167 | $(CTAGS) $(filter %.c,$(SOURCES)) 168 | 169 | $(call make-target-objs, $(filter-out $(ARM_SOURCES), $(SOURCES))): TARGET_CFLAGS += -mthumb 170 | $(call make-target-objs, $(filter $(ARM_SOURCES), $(SOURCES))): TARGET_CFLAGS += -marm 171 | 172 | bin/pimpconv$(EXE_EXT): $(call make-host-objs, $(PIMPCONV_SOURCES)) 173 | $(QUIET_LINK)$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) $(OUTPUT_OPTION) 174 | 175 | lib/libpimp_gba.a: $(OBJS) 176 | $(QUIET_AR)$(TARGET_AR) $(ARFLAGS) $@ $? 177 | 178 | # Override CC for target-builds 179 | $(TARGET_BUILD_DIR)/%.o: CC = $(TARGET_CC) 180 | $(TARGET_BUILD_DIR)/%.o: CPPFLAGS += $(TARGET_CPPFLAGS) 181 | $(TARGET_BUILD_DIR)/%.o: CFLAGS += $(TARGET_CFLAGS) 182 | $(TARGET_BUILD_DIR)/%.o: ASFLAGS += $(TARGET_ASFLAGS) 183 | 184 | # Override CC for host-builds 185 | bin/pimpconv$(EXE_EXT): LOADLIBES += -lm 186 | 187 | ### C 188 | 189 | $(TARGET_BUILD_DIR)/%.iwram.o: %.c 190 | @$(MKDIR) $(dir $@) 191 | $(QUIET_CC)$(COMPILE.c) $(OUTPUT_OPTION) $< -MMD -MP -MF $(@:.o=.d) 192 | 193 | $(TARGET_BUILD_DIR)/%.o: %.c 194 | @$(MKDIR) $(dir $@) 195 | $(QUIET_CC)$(COMPILE.c) $(OUTPUT_OPTION) $< -MMD -MP -MF $(@:.o=.d) 196 | 197 | $(HOST_BUILD_DIR)/%.o: %.c 198 | @$(MKDIR) $(dir $@) 199 | $(QUIET_CC)$(COMPILE.c) $(OUTPUT_OPTION) $< -MMD -MP -MF $(@:.o=.d) 200 | 201 | ### ASM 202 | 203 | $(TARGET_BUILD_DIR)/%.o: %.S 204 | @$(MKDIR) $(dir $@) 205 | $(QUIET_AS)$(COMPILE.S) $(OUTPUT_OPTION) $< -MMD -MP -MF $(@:.o=.d) 206 | 207 | # deps 208 | -include $(call make-target-deps, $(SOURCES)) 209 | -include $(call make-host-deps, $(PIMPCONV_SOURCES)) 210 | -------------------------------------------------------------------------------- /src/pimp_mixer.c: -------------------------------------------------------------------------------- 1 | /* pimp_mixer.c -- High level mixer code 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include "pimp_mixer.h" 7 | #include "pimp_debug.h" 8 | 9 | void pimp_mixer_reset(struct pimp_mixer *mixer) 10 | { 11 | u32 c; 12 | ASSERT(mixer != NULL); 13 | 14 | for (c = 0; c < PIMP_CHANNEL_COUNT; ++c) 15 | { 16 | mixer->channels[c].sample_data = 0; 17 | mixer->channels[c].sample_cursor = 0; 18 | mixer->channels[c].sample_cursor_delta = 0; 19 | } 20 | } 21 | 22 | #define CALC_LOOP_EVENT 23 | 24 | #ifndef CALC_LOOP_EVENT 25 | 26 | PURE int pimp_linear_search_loop_event(int event_cursor, int event_delta, const int max_samples) 27 | { 28 | int i; 29 | for (i = 0; i < max_samples; ++i) 30 | { 31 | event_cursor -= event_delta; 32 | if (event_cursor <= 0) 33 | { 34 | return i + 1; 35 | } 36 | } 37 | return -1; 38 | } 39 | 40 | #else /* CALC_LOOP_EVENT */ 41 | 42 | PURE int pimp_calc_loop_event(int event_cursor, int event_delta, const int max_samples) 43 | { 44 | int result; 45 | if (event_cursor == 0) return 1; 46 | if ((event_cursor - event_delta * max_samples) > 0) return -1; 47 | 48 | #ifdef TARGET_GBA 49 | { 50 | int number = event_cursor + event_delta - 1; 51 | int denom = event_delta; 52 | 53 | /* call BIOS-function to divide, as it's fast enough for our needs. */ 54 | asm ( 55 | "mov r0, %[number] \n" 56 | "mov r1, %[denom] \n" 57 | #ifdef __thumb__ 58 | "swi 0x6 \n" 59 | #else 60 | "swi 0x6 << 16 \n" 61 | #endif 62 | "mov %[result], r0 \n" 63 | : [result] "=r" (result) /* output */ 64 | : /* inputs */ 65 | [number] "r" (number), 66 | [denom] "r" (denom) 67 | : "r0", "r1", "r2", "r3" /* clobbers */ 68 | ); 69 | } 70 | #else 71 | result = (event_cursor + event_delta - 1) / event_delta; 72 | #endif 73 | 74 | ASSERT(result <= max_samples); 75 | return result; 76 | } 77 | 78 | #endif 79 | 80 | /* returns the number of samples that can be mixed before a loop event occurs */ 81 | PURE int pimp_mixer_detect_loop_event(const struct pimp_mixer_channel_state *chan, const int max_samples) 82 | { 83 | int event_delta, event_cursor; 84 | 85 | ASSERT(max_samples > 0); 86 | ASSERT(NULL != chan); 87 | 88 | switch (chan->loop_type) 89 | { 90 | case LOOP_TYPE_NONE: 91 | /* event is end of sample */ 92 | event_delta = chan->sample_cursor_delta; 93 | event_cursor = (chan->sample_length << 12) - chan->sample_cursor; 94 | break; 95 | 96 | case LOOP_TYPE_FORWARD: 97 | /* event is loop end */ 98 | event_delta = chan->sample_cursor_delta; 99 | event_cursor = (chan->loop_end << 12) - chan->sample_cursor; 100 | break; 101 | 102 | case LOOP_TYPE_PINGPONG: 103 | if (chan->sample_cursor_delta >= 0) 104 | { 105 | /* moving forwards through the sample */ 106 | /* event is loop end */ 107 | event_delta = chan->sample_cursor_delta; 108 | event_cursor = (chan->loop_end << 12) - chan->sample_cursor; 109 | } 110 | else 111 | { 112 | /* moving backwards through the sample */ 113 | /* event is loop start */ 114 | event_delta = -chan->sample_cursor_delta; 115 | event_cursor = -((chan->loop_start << 12) - chan->sample_cursor - 1); 116 | } 117 | break; 118 | default: 119 | /* should never happen */ 120 | ASSERT(FALSE); 121 | return 0; 122 | } 123 | 124 | ASSERT((event_cursor > 0) && (event_delta > 0)); 125 | 126 | #ifndef CALC_LOOP_EVENT 127 | return pimp_linear_search_loop_event(event_cursor, event_delta, max_samples); 128 | #else 129 | return pimp_calc_loop_event(event_cursor, event_delta, max_samples); 130 | #endif 131 | } 132 | 133 | /* returns false if we hit sample-end */ 134 | STATIC BOOL pimp_process_loop_event(struct pimp_mixer_channel_state *chan) 135 | { 136 | ASSERT(NULL != chan); 137 | switch (chan->loop_type) 138 | { 139 | case LOOP_TYPE_NONE: return FALSE; 140 | 141 | case LOOP_TYPE_FORWARD: 142 | do 143 | { 144 | ASSERT(chan->sample_cursor >= (chan->loop_end << 12)); /* we should be positioned AT or BEYOND event cursor when responding to event */ 145 | chan->sample_cursor -= (chan->loop_end - chan->loop_start) << 12; 146 | } 147 | while (chan->sample_cursor >= (chan->loop_end << 12)); 148 | break; 149 | 150 | case LOOP_TYPE_PINGPONG: 151 | do 152 | { 153 | if (chan->sample_cursor_delta >= 0) 154 | { 155 | ASSERT(chan->sample_cursor >= (chan->loop_end << 12)); /* we should be positioned AT or BEYOND event cursor when responding to event */ 156 | chan->sample_cursor -= chan->loop_end << 12; 157 | chan->sample_cursor = -chan->sample_cursor; 158 | chan->sample_cursor += chan->loop_end << 12; 159 | ASSERT(chan->sample_cursor > 0); 160 | chan->sample_cursor -= 1; 161 | } 162 | else 163 | { 164 | ASSERT(chan->sample_cursor >= (chan->loop_start << 12)); /* we should be positioned AT or BEYOND event cursor when responding to event */ 165 | chan->sample_cursor -= (chan->loop_start) << 12; 166 | chan->sample_cursor = -chan->sample_cursor; 167 | chan->sample_cursor += (chan->loop_start) << 12; 168 | ASSERT(chan->sample_cursor > (chan->loop_start << 12)); 169 | chan->sample_cursor -= 1; 170 | } 171 | chan->sample_cursor_delta = -chan->sample_cursor_delta; 172 | } 173 | while ((chan->sample_cursor > (chan->loop_end << 12)) || (chan->sample_cursor < (chan->loop_start << 12))); 174 | break; 175 | default: ASSERT(FALSE); /* should never happen */ 176 | } 177 | 178 | return TRUE; 179 | } 180 | 181 | void pimp_mixer_mix_channel(struct pimp_mixer_channel_state *chan, s32 *target, u32 samples) 182 | { 183 | ASSERT(NULL != target); 184 | ASSERT(NULL != chan); 185 | 186 | while (samples > 0) 187 | { 188 | int safe_samples = pimp_mixer_detect_loop_event(chan, samples); 189 | int mix_samples = safe_samples; 190 | 191 | ASSERT(samples >= safe_samples); 192 | 193 | if (safe_samples < 0) mix_samples = samples; 194 | 195 | pimp_mixer_mix_samples(target, mix_samples, chan->sample_data, chan->volume, chan->sample_cursor, chan->sample_cursor_delta); 196 | chan->sample_cursor = chan->sample_cursor + chan->sample_cursor_delta * mix_samples; 197 | 198 | if (safe_samples < 0) break; /* done. */ 199 | 200 | target += safe_samples; /* move target pointer */ 201 | samples -= safe_samples; 202 | 203 | if (FALSE == pimp_process_loop_event(chan)) 204 | { 205 | /* the sample has stopped, we need to fill the rest of the buffer with the dc-offset, so it doesn't ruin our unsigned mixing-thing */ 206 | while (samples--) 207 | { 208 | *target++ += chan->volume * 128; 209 | } 210 | 211 | /* terminate sample */ 212 | chan->sample_data = 0; 213 | 214 | return; 215 | } 216 | 217 | /* check that process_loop_event() didn't put us outside the sample */ 218 | ASSERT((chan->sample_cursor >> 12) < chan->sample_length); 219 | ASSERT((chan->sample_cursor >> 12) >= 0); 220 | } 221 | } 222 | 223 | void pimp_mixer_mix(struct pimp_mixer *mixer, s8 *target, int samples) 224 | { 225 | u32 c; 226 | int dc_offs; 227 | 228 | ASSERT(NULL != mixer); 229 | ASSERT(NULL != mixer->mix_buffer); 230 | ASSERT(NULL != target); 231 | ASSERT(samples >= 0); 232 | 233 | pimp_mixer_clear(mixer->mix_buffer, samples); 234 | 235 | dc_offs = 0; 236 | for (c = 0; c < PIMP_CHANNEL_COUNT; ++c) 237 | { 238 | struct pimp_mixer_channel_state *chan = &mixer->channels[c]; 239 | if ((NULL != chan->sample_data) && (chan->volume > 0)) 240 | { 241 | pimp_mixer_mix_channel(chan, mixer->mix_buffer, samples); 242 | dc_offs += chan->volume * 128; 243 | } 244 | } 245 | 246 | dc_offs >>= 8; 247 | 248 | pimp_mixer_clip_samples(target, mixer->mix_buffer, samples, dc_offs); 249 | } 250 | -------------------------------------------------------------------------------- /t/unit/test_mixer.c: -------------------------------------------------------------------------------- 1 | #include "../framework/test_framework.h" 2 | #include "../framework/test_helpers.h" 3 | 4 | #include "src/pimp_mixer.h" 5 | #include 6 | #include 7 | 8 | /* 9 | 10 | typedef struct 11 | { 12 | u32 sample_length; 13 | u32 loop_start; 14 | u32 loop_end; 15 | pimp_mixer_loop_type loop_type; 16 | const u8 *sample_data; 17 | u32 sample_cursor; 18 | s32 sample_cursor_delta; 19 | u32 event_cursor; 20 | s32 volume; 21 | } pimp_mixer_channel_state; 22 | 23 | typedef struct 24 | { 25 | pimp_mixer_channel_state channels[CHANNELS]; 26 | s32 *mix_buffer; 27 | } pimp_mixer; 28 | 29 | void pimp_mixer_mix(pimp_mixer *mixer, s8 *target, int samples); 30 | */ 31 | 32 | 33 | #define MAX_TARGET_SIZE 1024 34 | s8 target[MAX_TARGET_SIZE + 2]; 35 | s32 mix_buffer[MAX_TARGET_SIZE + 2]; 36 | 37 | static void test_mixer_basic(struct test_suite *suite) 38 | { 39 | /* 40 | cases that need testing: 41 | - that mixer never writes outside mix- and target buffers 42 | - that odd mix-buffer sizes does not write outside buffer 43 | */ 44 | int target_size; 45 | 46 | struct pimp_mixer mixer; 47 | pimp_mixer_reset(&mixer); 48 | mixer.mix_buffer = mix_buffer + 1; 49 | 50 | /* try all buffer sizes */ 51 | for (target_size = 0; target_size < MAX_TARGET_SIZE; ++target_size) 52 | { 53 | s8 rnd = rand(); 54 | 55 | target[0] = rnd; 56 | target[target_size + 1] = rnd; 57 | mix_buffer[0] = rnd; 58 | mix_buffer[target_size + 1] = rnd; 59 | 60 | pimp_mixer_mix(&mixer, target + 1, target_size); 61 | 62 | /* test that values outside the buffer haven't been written */ 63 | /* target */ 64 | ASSERT_INTS_EQUAL(suite, target[0], rnd); 65 | ASSERT_INTS_EQUAL(suite, target[target_size + 1], rnd); 66 | /* mix_buffer */ 67 | ASSERT_INTS_EQUAL(suite, mix_buffer[target_size + 1], rnd); 68 | ASSERT_INTS_EQUAL(suite, mix_buffer[0], rnd); 69 | } 70 | } 71 | 72 | #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) 73 | 74 | int pimp_linear_search_loop_event(int event_cursor, int event_delta, int max_samples); 75 | int pimp_calc_loop_event(int event_cursor, int event_delta, int max_samples); 76 | 77 | /* reference implementation */ 78 | STATIC PURE int ref_search_loop_event(int event_cursor, int event_delta, const int max_samples) 79 | { 80 | int i; 81 | for (i = 0; i < max_samples; ++i) 82 | { 83 | event_cursor -= event_delta; 84 | if (event_cursor <= 0) 85 | { 86 | return i + 1; 87 | } 88 | } 89 | return -1; 90 | } 91 | 92 | static void test_looping(struct test_suite *suite) 93 | { 94 | /* 95 | cases that need testing: 96 | - forward looping 97 | - ping pong looping 98 | - that a loop never reads outside of the start and stop points 99 | - that a loop happens at the correct sub-sample 100 | */ 101 | 102 | struct pimp_mixer_channel_state chan; 103 | 104 | const u8 sample_data[] = { 0x00, 0x01, 0x02, 0x03, 0x04 }; 105 | 106 | /* quick, let's test pimp_mixer_detect_loop_event() */ 107 | { 108 | int i; 109 | 110 | for (i = 0; i < 1000000; ++i) 111 | { 112 | int max_samples = rand() % 1024; 113 | int event_cursor = abs(rand() * rand()); 114 | int event_delta = abs(rand() * rand()) % (1 << 19); 115 | int correct = ref_search_loop_event(event_cursor, event_delta, max_samples); 116 | int res = pimp_calc_loop_event(event_cursor, event_delta, max_samples); 117 | ASSERT_INTS_EQUAL(suite, res, correct); 118 | } 119 | 120 | chan.loop_type = LOOP_TYPE_FORWARD; 121 | chan.loop_start = 0; 122 | chan.loop_end = 4; 123 | chan.sample_cursor = 0 << 12; 124 | chan.sample_cursor_delta = 1 << 12; 125 | 126 | /* test that the clamp to buffer size happens at the right place */ 127 | ASSERT_INTS_EQUAL(suite, pimp_mixer_detect_loop_event(&chan, 1), -1); 128 | ASSERT_INTS_EQUAL(suite, pimp_mixer_detect_loop_event(&chan, 2), -1); 129 | ASSERT_INTS_EQUAL(suite, pimp_mixer_detect_loop_event(&chan, 3), -1); 130 | ASSERT_INTS_EQUAL(suite, pimp_mixer_detect_loop_event(&chan, 4), 4); 131 | ASSERT_INTS_EQUAL(suite, pimp_mixer_detect_loop_event(&chan, 5), 4); 132 | 133 | /* see that the correct amount of samples are mixed event at the border values -- both these locations are inside the same sample */ 134 | chan.sample_cursor = (0 << 12) + 1; 135 | ASSERT_INTS_EQUAL(suite, pimp_mixer_detect_loop_event(&chan, 5), 4); 136 | chan.sample_cursor = (1 << 12) - 1; 137 | ASSERT_INTS_EQUAL(suite, pimp_mixer_detect_loop_event(&chan, 5), 4); 138 | } 139 | 140 | 141 | chan.sample_length = ARRAY_SIZE(sample_data); 142 | chan.loop_start = 0; 143 | chan.loop_end = 4; 144 | chan.loop_type = LOOP_TYPE_FORWARD; 145 | chan.sample_data = sample_data; 146 | chan.sample_cursor = 0 << 12; 147 | chan.sample_cursor_delta = 1 << 12; 148 | chan.volume = 1; 149 | 150 | { 151 | const s32 forward_loop_ref[] = { 152 | 0x00, 0x01, 0x02, 0x03, /* loop */ 153 | 0x00, 0x01, 0x02, 0x03 /* done */ 154 | }; 155 | 156 | int target_size = 8; 157 | 158 | /* loop should happen exactly at loop-end */ 159 | chan.sample_cursor = 0 << 12; 160 | chan.sample_cursor_delta = 1 << 12; 161 | memset(mix_buffer, 0, target_size * sizeof(u32)); 162 | pimp_mixer_mix_channel(&chan, mix_buffer, target_size); 163 | ASSERT_INT_ARRAYS_EQUAL(suite, mix_buffer, forward_loop_ref, ARRAY_SIZE(forward_loop_ref)); 164 | 165 | /* loop should happen right after loop-end */ 166 | chan.sample_cursor = (0 << 12) + 1; 167 | chan.sample_cursor_delta = 1 << 12; 168 | memset(mix_buffer, 0, target_size * sizeof(u32)); 169 | pimp_mixer_mix_channel(&chan, mix_buffer, target_size); 170 | ASSERT_INT_ARRAYS_EQUAL(suite, mix_buffer, forward_loop_ref, ARRAY_SIZE(forward_loop_ref)); 171 | 172 | /* loop should happen way after loop-end */ 173 | chan.sample_cursor = (1 << 12) - 1; 174 | chan.sample_cursor_delta = 1 << 12; 175 | memset(mix_buffer, 0, target_size * sizeof(u32)); 176 | pimp_mixer_mix_channel(&chan, mix_buffer, target_size); 177 | ASSERT_INT_ARRAYS_EQUAL(suite, mix_buffer, forward_loop_ref, ARRAY_SIZE(forward_loop_ref)); 178 | } 179 | 180 | { 181 | const s32 pingpong_loop_ref[] = { 182 | 0x00, 0x01, 0x02, 0x03, /* change direction */ 183 | 0x03, 0x02, 0x01, 0x00, /* change direction */ 184 | 0x00, 0x01, 0x02, 0x03 /* done */ }; 185 | int target_size = 8 + 4; 186 | 187 | /* loop should happen exactly at loop-end */ 188 | chan.loop_type = LOOP_TYPE_PINGPONG; 189 | chan.sample_cursor = 0 << 12; 190 | chan.sample_cursor_delta = 1 << 12; 191 | memset(mix_buffer, 0, target_size * sizeof(u32)); 192 | pimp_mixer_mix_channel(&chan, mix_buffer, target_size); 193 | ASSERT_INT_ARRAYS_EQUAL(suite, mix_buffer, pingpong_loop_ref, ARRAY_SIZE(pingpong_loop_ref)); 194 | 195 | /* loop should happen right after loop-end */ 196 | chan.loop_type = LOOP_TYPE_PINGPONG; 197 | chan.sample_cursor = (0 << 12) + 1; 198 | chan.sample_cursor_delta = 1 << 12; 199 | memset(mix_buffer, 0, target_size * sizeof(u32)); 200 | pimp_mixer_mix_channel(&chan, mix_buffer, target_size); 201 | ASSERT_INT_ARRAYS_EQUAL(suite, mix_buffer, pingpong_loop_ref, ARRAY_SIZE(pingpong_loop_ref)); 202 | 203 | /* loop should happen way after loop-end */ 204 | chan.loop_type = LOOP_TYPE_PINGPONG; 205 | chan.sample_cursor = (1 << 12) - 1; 206 | chan.sample_cursor_delta = 1 << 12; 207 | memset(mix_buffer, 0, target_size * sizeof(u32)); 208 | pimp_mixer_mix_channel(&chan, mix_buffer, target_size); 209 | ASSERT_INT_ARRAYS_EQUAL(suite, mix_buffer, pingpong_loop_ref, ARRAY_SIZE(pingpong_loop_ref)); 210 | } 211 | } 212 | 213 | void test_mixer(struct test_suite *suite) 214 | { 215 | test_mixer_basic(suite); 216 | test_looping(suite); 217 | } 218 | -------------------------------------------------------------------------------- /examples/gbfs_stdio.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | gbfs_stdio.c -- devkitARM stdio integration code for GBFS 4 | 5 | Copyright (c) 2007 Erik Faye-Lund 6 | 7 | This software is provided 'as-is', without any express or implied warranty. In 8 | no event will the authors be held liable for any damages arising from the use 9 | of this software. 10 | 11 | Permission is granted to anyone to use this software for any purpose, including 12 | commercial applications, and to alter it and redistribute it freely, subject to 13 | the following restrictions: 14 | 15 | 1. The origin of this software must not be misrepresented; you must not 16 | claim that you wrote the original software. If you use this software in 17 | a product, an acknowledgment in the product documentation would be 18 | appreciated but is not required. 19 | 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 23 | 3. This notice may not be removed or altered from any source distribution. 24 | 25 | */ 26 | 27 | #include "gbfs_stdio.h" 28 | 29 | typedef unsigned char u8; 30 | typedef unsigned short u16; 31 | typedef unsigned long u32; 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "gbfs.h" 37 | 38 | #include 39 | #define ASSERT(expr) assert(expr) 40 | 41 | #include 42 | #include 43 | 44 | struct file_state 45 | { 46 | const u8 *data; 47 | u32 size; 48 | int pos; 49 | }; 50 | 51 | static const GBFS_FILE *file_system = NULL; 52 | 53 | static const char *make_absolute_path(const char *path) 54 | { 55 | /* skip file system identifier, we only support one anyway */ 56 | if (strchr (path, ':') != NULL) 57 | { 58 | path = strchr (path, ':') + 1; 59 | } 60 | 61 | /* no subdirectory-support. everything is in the root. */ 62 | if (path[0] == '/') path++; 63 | 64 | return path; 65 | } 66 | 67 | static int gbfs_open_r(struct _reent *r, void *fileStruct, const char *path, int flags,int mode) 68 | { 69 | struct file_state *file = (struct file_state *)fileStruct; 70 | 71 | /* we're a read-only file system */ 72 | if ((flags & 3) != O_RDONLY) 73 | { 74 | r->_errno = EROFS; 75 | return -1; 76 | } 77 | 78 | path = make_absolute_path(path); 79 | 80 | /* init file-data */ 81 | file->size = 0; 82 | file->pos = 0; 83 | file->data = gbfs_get_obj(file_system, path, &file->size); 84 | 85 | /* check for failure */ 86 | if (NULL == file->data) return -1; 87 | 88 | return (int)file; 89 | } 90 | 91 | static int gbfs_close_r(struct _reent *r, int fd) 92 | { 93 | struct file_state *file = (struct file_state *)fd; 94 | 95 | /* nothing to be done */ 96 | 97 | return 0; 98 | } 99 | 100 | static off_t gbfs_seek_r(struct _reent *r, int fd, off_t pos, int dir) 101 | { 102 | int position; 103 | struct file_state *file = (struct file_state *)fd; 104 | 105 | /* select origo */ 106 | switch (dir) 107 | { 108 | case SEEK_SET: 109 | position = pos; 110 | break; 111 | 112 | case SEEK_CUR: 113 | position = file->pos + pos; 114 | break; 115 | 116 | case SEEK_END: 117 | position = file->size + pos; 118 | break; 119 | 120 | default: 121 | r->_errno = EINVAL; 122 | return -1; 123 | } 124 | 125 | /* check for seeks outside the file */ 126 | if (position < 0 || position > file->size) 127 | { 128 | r->_errno = EINVAL; 129 | return -1; 130 | } 131 | 132 | /* update state */ 133 | file->pos = position; 134 | return file->pos; 135 | } 136 | 137 | static ssize_t gbfs_read_r(struct _reent *r, int fd, char *ptr, size_t len) 138 | { 139 | int i; 140 | struct file_state *file = (struct file_state *)fd; 141 | 142 | /* clamp len to file-length */ 143 | if (len + file->pos > file->size) 144 | { 145 | r->_errno = EOVERFLOW; 146 | len = file->size - file->pos; 147 | } 148 | 149 | /* if we're already at the end of the file, there's nothing to copy */ 150 | if (file->pos == file->size) return 0; 151 | 152 | /* copy data */ 153 | for (i = 0; i < len; ++i) 154 | { 155 | ASSERT(file->pos < file->size); 156 | ASSERT(file->pos >= 0); 157 | *ptr++ = file->data[file->pos++]; 158 | } 159 | 160 | return i; 161 | } 162 | 163 | static void gbfs_stat_file(struct file_state *file, struct stat *st) 164 | { 165 | st->st_dev = 0; /* device id */ 166 | st->st_ino = 0; /* TODO: find a way to get the file-index instead here */ 167 | st->st_mode = S_IRUSR | S_IRGRP | S_IROTH | S_IFREG; 168 | st->st_nlink = 1; 169 | st->st_uid = 0; /* root user */ 170 | st->st_gid = 0; /* wheel group */ 171 | st->st_rdev = 0; 172 | st->st_size = file->size; 173 | st->st_blksize = 1; /* we're not working on blocks, so one byte per block is all fine */ 174 | st->st_blocks = (st->st_size + 511) / 512; 175 | 176 | st->st_atime = 0; /* 1970-01-01 00:00:00 FTW */ 177 | st->st_mtime = 0; 178 | st->st_ctime = 0; 179 | } 180 | 181 | 182 | static int gbfs_fstat_r(struct _reent *r, int fd, struct stat *st) 183 | { 184 | struct file_state *file = (struct file_state *)fd; 185 | gbfs_stat_file(file, st); 186 | } 187 | 188 | static int gbfs_stat_r(struct _reent *r, const char *path, struct stat *st) 189 | { 190 | struct file_state file; 191 | 192 | path = make_absolute_path(path); 193 | 194 | /* init file-data */ 195 | file.size = 0; 196 | file.pos = 0; 197 | file.data = gbfs_get_obj(file_system, path, &file.size); 198 | 199 | if (NULL == file.data) 200 | { 201 | r->_errno = ENOENT; 202 | return -1; 203 | } 204 | 205 | gbfs_stat_file(&file, st); 206 | return 0; 207 | } 208 | 209 | static int gbfs_chdir_r(struct _reent *r, const char *path) 210 | { 211 | /* skip file system identifier, we only support one anyway */ 212 | path = make_absolute_path(path); 213 | 214 | /* no subdirs, so we'll only support the top level - ie empty string after stripping */ 215 | if (path[0] != '\0') 216 | { 217 | r->_errno = ENOTDIR; 218 | return -1; 219 | } 220 | 221 | return 0; 222 | } 223 | 224 | struct dir_state 225 | { 226 | int current_file; 227 | }; 228 | 229 | static DIR_ITER* gbfs_diropen_r(struct _reent *r, DIR_ITER *dirState, const char *path) 230 | { 231 | struct dir_state* dir = (struct dir_state*)dirState->dirStruct; 232 | 233 | /* skip file system identifier, we only support one anyway */ 234 | path = make_absolute_path(path); 235 | 236 | /* the only supported path is the root of the file system. */ 237 | if (path[0] != '\0') 238 | { 239 | if (NULL == gbfs_get_obj(file_system, path, NULL)) r->_errno = ENOENT; 240 | else r->_errno = ENOTDIR; 241 | return NULL; 242 | } 243 | 244 | /* reset file index */ 245 | dir->current_file = 0; 246 | 247 | return (DIR_ITER*)dir; 248 | } 249 | 250 | static int gbfs_dirreset_r(struct _reent *r, DIR_ITER *dirState) 251 | { 252 | struct dir_state* dir = (struct dir_state*)dirState->dirStruct; 253 | 254 | /* reset file index */ 255 | dir->current_file = 0; 256 | 257 | return 0; 258 | } 259 | 260 | static int gbfs_dirnext_r(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) 261 | { 262 | struct file_state file; 263 | struct dir_state* dir = (struct dir_state*)dirState->dirStruct; 264 | 265 | /* init file-data */ 266 | file.size = 0; 267 | file.pos = 0; 268 | file.data = gbfs_get_nth_obj(file_system, dir->current_file, filename, &file.size); 269 | 270 | /* check for failure (no more files) */ 271 | if (file.data == NULL) return -1; 272 | 273 | /* stat */ 274 | gbfs_stat_file(&file, filestat); 275 | 276 | /* skip to next file */ 277 | dir->current_file++; 278 | 279 | return 0; 280 | } 281 | 282 | static int gbfs_dirclose_r (struct _reent *r, DIR_ITER *dirState) 283 | { 284 | return 0; 285 | } 286 | 287 | static devoptab_t d; 288 | int gbfs_init(int set_default) 289 | { 290 | int dev_id; 291 | 292 | file_system = find_first_gbfs_file((void*)0x08000000); 293 | if (NULL == file_system) return 0; 294 | 295 | memset(&d, 0, sizeof(devoptab_t)); 296 | d.name = "gbfs"; 297 | d.structSize = sizeof(struct file_state); 298 | d.dirStateSize = sizeof(struct dir_state); 299 | d.open_r = gbfs_open_r; 300 | d.close_r = gbfs_close_r; 301 | d.seek_r = gbfs_seek_r; 302 | d.read_r = gbfs_read_r; 303 | d.stat_r = gbfs_stat_r; 304 | d.fstat_r = gbfs_fstat_r; 305 | d.chdir_r = gbfs_chdir_r; 306 | d.diropen_r = gbfs_diropen_r; 307 | d.dirreset_r = gbfs_dirreset_r; 308 | d.dirnext_r = gbfs_dirnext_r; 309 | d.dirclose_r = gbfs_dirclose_r; 310 | 311 | dev_id = AddDevice(&d); 312 | if (0 != set_default) setDefaultDevice(dev_id); 313 | 314 | return 1; 315 | } 316 | -------------------------------------------------------------------------------- /src/pimp_mixer_arm.S: -------------------------------------------------------------------------------- 1 | /* pimp_mixer_arm.S -- ARM optimized mixer code 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | .text 7 | .section .iwram 8 | .arm 9 | .align 2 10 | 11 | @ no mixing: 361612 12 | @ without bresenham: 13 | @ inline-asm version: 1211037 14 | @ current: 1150788 15 | @ with bresenham: 16 | @ inline-asm version: 1163062 17 | @ current: 1105083 18 | 19 | 20 | @irq-safe: 1121327 21 | @irq-unsafe: 1105083 22 | 23 | #include "pimp_config.h" 24 | 25 | #ifndef PIMP_MIXER_IRQ_SAFE 26 | .stack_store: 27 | .word 0 28 | .ime_store: 29 | .word 0 30 | #endif 31 | 32 | #ifdef PIMP_MIXER_USE_BRESENHAM_MIXER 33 | .sample_data_store: 34 | .word 0 35 | #endif 36 | 37 | .mixer_jumptable: 38 | .word .mix0 39 | .word .mix1 40 | .word .mix2 41 | .word .mix3 42 | .word .mix4 43 | .word .mix5 44 | .word .mix6 45 | .word .mix7 46 | 47 | .global pimp_mixer_mix_samples 48 | .type pimp_mixer_mix_samples, %function 49 | pimp_mixer_mix_samples: 50 | stmfd sp!, {r4-r12, lr} @ store all registers but parameters and stack 51 | 52 | #ifdef PIMP_MIXER_NO_MIXING 53 | ldmfd sp!, {r4-r12, lr} @ restore rest of registers 54 | bx lr @ return to caller 55 | #endif 56 | 57 | #ifndef PIMP_MIXER_IRQ_SAFE 58 | str sp, .stack_store @ store stack pointer so we can use that register in our mixer (note, interrupts must be disabled for this to be safe) 59 | #endif 60 | 61 | #define TARGET r0 62 | #define COUNTER r1 63 | #define SAMPLE_DATA r2 64 | #define VOLUME r3 65 | #define SAMPLE_CURSOR lr 66 | #define SAMPLE_CURSOR_DELTA r12 67 | 68 | @ load rest of parameters 69 | ldr SAMPLE_CURSOR, [sp, #40] 70 | ldr SAMPLE_CURSOR_DELTA, [sp, #44] 71 | 72 | @ find how many samples to fixup 73 | and r4, COUNTER, #7 74 | 75 | @ fixup, jump to the correct position 76 | adr r5, .mixer_jumptable 77 | ldr pc, [r5, r4, lsl #2] 78 | 79 | @ fixup code unrolled 7 times 80 | .mix7: 81 | ldr r6, [TARGET] 82 | ldrb r5, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 83 | mla r6, r5, VOLUME, r6 84 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 85 | str r6, [TARGET], #4 86 | 87 | .mix6: 88 | ldr r6, [TARGET] 89 | ldrb r5, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 90 | mla r6, r5, VOLUME, r6 91 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 92 | str r6, [TARGET], #4 93 | 94 | .mix5: 95 | ldr r6, [TARGET] 96 | ldrb r5, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 97 | mla r6, r5, VOLUME, r6 98 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 99 | str r6, [TARGET], #4 100 | 101 | .mix4: 102 | ldr r6, [TARGET] 103 | ldrb r5, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 104 | mla r6, r5, VOLUME, r6 105 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 106 | str r6, [TARGET], #4 107 | 108 | .mix3: 109 | ldr r6, [TARGET] 110 | ldrb r5, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 111 | mla r6, r5, VOLUME, r6 112 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 113 | str r6, [TARGET], #4 114 | 115 | .mix2: 116 | ldr r6, [TARGET] 117 | ldrb r5, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 118 | mla r6, r5, VOLUME, r6 119 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 120 | str r6, [TARGET], #4 121 | 122 | .mix1: 123 | ldr r6, [TARGET] 124 | ldrb r5, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 125 | mla r6, r5, VOLUME, r6 126 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 127 | str r6, [TARGET], #4 128 | 129 | .mix0: 130 | 131 | movs COUNTER, COUNTER, asr #3 @ divide counter by 8 132 | beq .ret @ if no more samples, return 133 | 134 | #ifndef PIMP_MIXER_IRQ_SAFE 135 | ldr r4, =0x4000208 @ load address of REG_IME 136 | ldr r5, [r4] @ load value of REG_IME 137 | str r5, .ime_store @ stash for later 138 | eor r6, r6 139 | str r6, [r4] @ disable interrupt 140 | #endif 141 | 142 | #ifdef PIMP_MIXER_USE_BRESENHAM_MIXER 143 | // if ((sample_cursor_delta & ~((1UL << 12) - 1)) == 0) 144 | @ check if bresenham mixer can be used or not 145 | ldr r4, =0xfffff000 @ ~((1UL << 12) - 1)) 146 | tst SAMPLE_CURSOR_DELTA, r4 @ any bits set? 147 | beq .bresenham_mixer @ no? lets go! 148 | #endif 149 | 150 | #ifdef PIMP_MIXER_IRQ_SAFE 151 | #define TEMP r11 152 | #define UNROLL_RANGE r4-r10 153 | #else 154 | #define TEMP sp 155 | #define UNROLL_RANGE r4-r11 156 | #endif 157 | 158 | .simple_loop: 159 | ldmia TARGET, {UNROLL_RANGE} 160 | 161 | ldrb TEMP, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 162 | mla r4, TEMP, VOLUME, r4 163 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 164 | 165 | ldrb TEMP, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 166 | mla r5, TEMP, VOLUME, r5 167 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 168 | 169 | ldrb TEMP, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 170 | mla r6, TEMP, VOLUME, r6 171 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 172 | 173 | ldrb TEMP, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 174 | mla r7, TEMP, VOLUME, r7 175 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 176 | 177 | ldrb TEMP, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 178 | mla r8, TEMP, VOLUME, r8 179 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 180 | 181 | ldrb TEMP, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 182 | mla r9, TEMP, VOLUME, r9 183 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 184 | 185 | ldrb TEMP, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 186 | mla r10, TEMP, VOLUME, r10 187 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 188 | 189 | #ifndef PIMP_MIXER_IRQ_SAFE 190 | ldrb TEMP, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 191 | mla r11, TEMP, VOLUME, r11 192 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 193 | 194 | stmia TARGET!, {UNROLL_RANGE} 195 | #else 196 | stmia TARGET!, {UNROLL_RANGE} 197 | 198 | @ mix a single sample 199 | ldr r10, [TARGET] 200 | ldrb TEMP, [SAMPLE_DATA, SAMPLE_CURSOR, lsr #12] 201 | mla r10, TEMP, VOLUME, r10 202 | add SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 203 | str r10, [TARGET], #4 204 | #endif 205 | 206 | subs COUNTER, COUNTER, #1 207 | bne .simple_loop 208 | 209 | #ifndef PIMP_MIXER_IRQ_SAFE 210 | ldr r4, =0x4000208 @ load address of REG_IME 211 | ldr r5, .ime_store @ stash for later 212 | str r5, [r4] @ write value to REG_IME 213 | 214 | ldr sp, .stack_store @ restore stack pointer 215 | #endif 216 | 217 | .ret: 218 | @ clean return 219 | mov r0, SAMPLE_CURSOR 220 | 221 | ldmfd sp!, {r4-r12, lr} @ restore rest of registers 222 | bx lr @ return to caller 223 | 224 | #ifdef PIMP_MIXER_USE_BRESENHAM_MIXER 225 | 226 | .bresenham_mixer: 227 | str SAMPLE_DATA, .sample_data_store @ stash away SAMPLE_DATA for later use 228 | add SAMPLE_DATA, SAMPLE_DATA, SAMPLE_CURSOR, lsr #12 @ modify pointer so it points to the fist sample in frame 229 | 230 | mov SAMPLE_CURSOR, SAMPLE_CURSOR, asl #20 231 | mov SAMPLE_CURSOR_DELTA, SAMPLE_CURSOR_DELTA, asl #20 232 | 233 | ldrb TEMP, [SAMPLE_DATA], #1 234 | mul TEMP, VOLUME, TEMP 235 | .bresenham_loop: 236 | ldmia TARGET, {UNROLL_RANGE} 237 | 238 | add r4, TEMP, r4 239 | adds SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 240 | ldrcsb TEMP, [SAMPLE_DATA], #1 241 | mulcs TEMP, VOLUME, TEMP 242 | 243 | add r5, TEMP, r5 244 | adds SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 245 | ldrcsb TEMP, [SAMPLE_DATA], #1 246 | mulcs TEMP, VOLUME, TEMP 247 | 248 | add r6, TEMP, r6 249 | adds SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 250 | ldrcsb TEMP, [SAMPLE_DATA], #1 251 | mulcs TEMP, VOLUME, TEMP 252 | 253 | add r7, TEMP, r7 254 | adds SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 255 | ldrcsb TEMP, [SAMPLE_DATA], #1 256 | mulcs TEMP, VOLUME, TEMP 257 | 258 | add r8, TEMP, r8 259 | adds SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 260 | ldrcsb TEMP, [SAMPLE_DATA], #1 261 | mulcs TEMP, VOLUME, TEMP 262 | 263 | add r9, TEMP, r9 264 | adds SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 265 | ldrcsb TEMP, [SAMPLE_DATA], #1 266 | mulcs TEMP, VOLUME, TEMP 267 | 268 | add r10, TEMP, r10 269 | adds SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 270 | ldrcsb TEMP, [SAMPLE_DATA], #1 271 | mulcs TEMP, VOLUME, TEMP 272 | 273 | #ifndef PIMP_MIXER_IRQ_SAFE 274 | add r11, TEMP, r11 275 | adds SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 276 | ldrcsb TEMP, [SAMPLE_DATA], #1 277 | mulcs TEMP, VOLUME, TEMP 278 | 279 | stmia TARGET!, {UNROLL_RANGE} 280 | #else 281 | stmia TARGET!, {UNROLL_RANGE} 282 | 283 | ldr r10, [TARGET] 284 | add r10, TEMP, r10 285 | adds SAMPLE_CURSOR, SAMPLE_CURSOR, SAMPLE_CURSOR_DELTA 286 | ldrcsb TEMP, [SAMPLE_DATA], #1 287 | mulcs TEMP, VOLUME, TEMP 288 | str r10, [TARGET], #4 289 | #endif 290 | 291 | subs COUNTER, COUNTER, #1 292 | bne .bresenham_loop 293 | 294 | #ifndef PIMP_MIXER_IRQ_SAFE 295 | ldr r4, =0x4000208 @ load address of REG_IME 296 | ldr r5, .ime_store @ stash for later 297 | str r5, [r4] @ write value to REG_IME 298 | 299 | ldr sp, .stack_store @ restore stack pointer 300 | #endif 301 | 302 | ldr r0, .sample_data_store @ restore the old sample data 303 | 304 | @ calculate how the sample cursor changed 305 | sub r0, SAMPLE_DATA, r0 306 | sub r0, r0, #1 307 | mov r0, r0, lsl #12 308 | add r0, SAMPLE_CURSOR, asr #20 309 | 310 | @ return to caller 311 | ldmfd sp!, {r4-r12, lr} @ restore rest of registers 312 | bx lr 313 | #endif 314 | -------------------------------------------------------------------------------- /src/load_mod.c: -------------------------------------------------------------------------------- 1 | /* load_mod.c -- Protracker MOD loading code 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "load_module.h" 14 | #include "convert_sample.h" 15 | #include "pimp_module.h" 16 | #include "pimp_mixer.h" /* for pimp_loop_type enum */ 17 | #include "pimp_sample_bank.h" 18 | 19 | #if 0 20 | float log2(float f) 21 | { 22 | return log(f) / log(2.0f); 23 | } 24 | #endif 25 | 26 | int return_nearest_note(int p) 27 | { 28 | double log2p, note; 29 | int note_int; 30 | 31 | if (p < 14) return 0; /* this is not a note */ 32 | 33 | log2p = log(p) / log(2); 34 | note = (log2(428 << 5) - log2p) * 12.0 + 1; 35 | 36 | note_int = (int)floor(note + 0.5); 37 | 38 | if (note_int <= 0 || note_int > 120) return 0; /* this is not a note */ 39 | return note_int; 40 | } 41 | 42 | static BOOL load_instrument(FILE *fp, struct pimp_instrument *instr, struct pimp_sample_bank *sample_bank) 43 | { 44 | unsigned char buf[256]; 45 | struct pimp_sample *sample; 46 | 47 | if (fread(buf, 1, 22, fp) != 22) 48 | return FALSE; 49 | buf[22] = '\0'; 50 | 51 | sample = malloc(sizeof(struct pimp_sample)); 52 | if (NULL == sample) return FALSE; 53 | 54 | /* only one sample per instrument in MOD */ 55 | instr->sample_count = 1; 56 | 57 | memset(sample, 0, sizeof(struct pimp_sample)); 58 | pimp_set_ptr(&instr->sample_ptr, sample); 59 | 60 | 61 | /* fill out the instrument-data. this is not stored in the module at all. */ 62 | /* strcpy(instr->name, (const char*)buf); */ 63 | pimp_set_ptr(&instr->vol_env_ptr, NULL); 64 | pimp_set_ptr(&instr->pan_env_ptr, NULL); 65 | instr->volume_fadeout = 0; 66 | #if 0 67 | instr.new_note_action = NNA_CUT; /* "emulate" PT2-behavour on IT-fields */ 68 | instr.duplicate_check_type = DCT_OFF; 69 | instr.duplicate_check_action = DCA_CUT; 70 | instr.pitch_pan_separation = 0; 71 | instr.pitch_pan_center = 0; 72 | #endif 73 | memset(instr->sample_map, 0, 120); 74 | 75 | /* strcpy(samp->name, (const char*)buf); */ 76 | /* sample->sustain_loop_type = LOOP_NONE; 77 | sample->sustain_loop_start = 0; 78 | sample->sustain_loop_end = 0; */ 79 | sample->pan = 0; 80 | /* sample->ignore_default_pan_position = true; */ 81 | sample->rel_note = 0; 82 | sample->vibrato_speed = 0; 83 | sample->vibrato_depth = 0; 84 | sample->vibrato_sweep = 0; 85 | sample->vibrato_waveform = SAMPLE_VIBRATO_SINE; /* just a dummy */ 86 | 87 | if (fread(buf, 1, 2, fp) != 2) 88 | return FALSE; 89 | sample->length = ((buf[0] << 8) | buf[1]) << 1; 90 | 91 | if (fread(buf, 1, 1, fp) != 1) 92 | return FALSE; 93 | if (buf[0] > 7) sample->fine_tune = (buf[0] - 16) << 4; 94 | else sample->fine_tune = buf[0] << 4; 95 | 96 | if (fread(&sample->volume, 1, 1, fp) != 1) 97 | return FALSE; 98 | 99 | if (fread(buf, 1, 2, fp) != 2) 100 | return FALSE; 101 | sample->loop_start = ((buf[0] << 8) | buf[1]) << 1; 102 | if (sample->loop_start > sample->length) sample->loop_start = 0; 103 | 104 | if (fread(buf, 1, 2, fp) != 2) 105 | return FALSE; 106 | sample->loop_length = ((buf[0] << 8) | buf[1]) << 1; 107 | if (sample->loop_start + sample->loop_length > sample->length) sample->loop_length = sample->length - sample->loop_start; 108 | 109 | if ((sample->loop_start <= 2) && (sample->loop_length <= 4)) sample->loop_type = LOOP_TYPE_NONE; 110 | else sample->loop_type = LOOP_TYPE_FORWARD; 111 | 112 | return TRUE; 113 | } 114 | 115 | 116 | #define MAKE_WORD(a,b,c,d) ((((u32)(a)) << 24) | (((u32)(b)) << 16) | (((u32)(c)) << 8) | ((u32)(d))) 117 | 118 | static int get_channel_count(u32 sig) 119 | { 120 | int c = -1; 121 | switch (sig) 122 | { 123 | case MAKE_WORD('1', 'C', 'H', 'N'): c = 1; break; 124 | case MAKE_WORD('2', 'C', 'H', 'N'): c = 2; break; 125 | case MAKE_WORD('3', 'C', 'H', 'N'): c = 3; break; 126 | case MAKE_WORD('4', 'C', 'H', 'N'): c = 4; break; 127 | case MAKE_WORD('5', 'C', 'H', 'N'): c = 5; break; 128 | case MAKE_WORD('6', 'C', 'H', 'N'): c = 6; break; 129 | case MAKE_WORD('7', 'C', 'H', 'N'): c = 7; break; 130 | case MAKE_WORD('8', 'C', 'H', 'N'): c = 8; break; 131 | case MAKE_WORD('9', 'C', 'H', 'N'): c = 9; break; 132 | case MAKE_WORD('1', '0', 'C', 'H'): c = 10; break; 133 | case MAKE_WORD('1', '1', 'C', 'H'): c = 11; break; 134 | case MAKE_WORD('1', '2', 'C', 'H'): c = 12; break; 135 | case MAKE_WORD('1', '3', 'C', 'H'): c = 13; break; 136 | case MAKE_WORD('1', '4', 'C', 'H'): c = 14; break; 137 | case MAKE_WORD('1', '5', 'C', 'H'): c = 15; break; 138 | case MAKE_WORD('1', '6', 'C', 'H'): c = 16; break; 139 | case MAKE_WORD('1', '7', 'C', 'H'): c = 17; break; 140 | case MAKE_WORD('1', '8', 'C', 'H'): c = 18; break; 141 | case MAKE_WORD('1', '9', 'C', 'H'): c = 19; break; 142 | case MAKE_WORD('2', '0', 'C', 'H'): c = 20; break; 143 | case MAKE_WORD('2', '1', 'C', 'H'): c = 21; break; 144 | case MAKE_WORD('2', '2', 'C', 'H'): c = 22; break; 145 | case MAKE_WORD('2', '3', 'C', 'H'): c = 23; break; 146 | case MAKE_WORD('2', '4', 'C', 'H'): c = 24; break; 147 | case MAKE_WORD('2', '5', 'C', 'H'): c = 25; break; 148 | case MAKE_WORD('2', '6', 'C', 'H'): c = 26; break; 149 | case MAKE_WORD('2', '7', 'C', 'H'): c = 27; break; 150 | case MAKE_WORD('2', '8', 'C', 'H'): c = 28; break; 151 | case MAKE_WORD('2', '9', 'C', 'H'): c = 29; break; 152 | case MAKE_WORD('M', '.', 'K', '.'): c = 4; break; 153 | case MAKE_WORD('M', '!', 'K', '!'): c = 4; break; 154 | case MAKE_WORD('F', 'L', 'T', '4'): c = 4; break; 155 | case MAKE_WORD('C', 'D', '8', '1'): c = 8; break; 156 | } 157 | return c; 158 | } 159 | 160 | pimp_module *load_module_mod(FILE *fp, struct pimp_sample_bank *sample_bank) 161 | { 162 | int i, p; 163 | pimp_module *mod; 164 | int channel_count; 165 | int max_pattern; 166 | 167 | u8 sig_data[4]; 168 | u32 sig; 169 | 170 | if (fseek(fp, 1080, SEEK_SET) < 0 || 171 | fread(&sig_data, 1, 4, fp) != 4) 172 | return NULL; 173 | 174 | sig = MAKE_WORD(sig_data[0], sig_data[1], sig_data[2], sig_data[3]); 175 | channel_count = get_channel_count(sig); 176 | if (channel_count <= 0) return NULL; 177 | 178 | mod = malloc(sizeof(pimp_module)); 179 | if (NULL == mod) return NULL; 180 | 181 | memset(mod, 0, sizeof(pimp_module)); 182 | 183 | mod->period_low_clamp = 113; /* B-3 in MOD */ 184 | mod->period_high_clamp = 856; /* C-1 in MOD */ 185 | 186 | /* this should be correct if no notes outside this range is used */ 187 | mod->period_low_clamp = 108; /* B-3 with fine tune 8 in MOD */ 188 | mod->period_high_clamp = 907; /* C-1 wuth fine tune -8 in MOD */ 189 | 190 | mod->volume = 64; 191 | mod->tempo = 6; 192 | mod->bpm = 125; 193 | 194 | mod->flags = FLAG_TEMOR_EXTRA_DELAY | FLAG_TEMOR_MEMORY | FLAG_PORTA_NOTE_MEMORY; 195 | 196 | 197 | /* song name */ 198 | rewind(fp); 199 | { 200 | char name[20 + 1]; 201 | if (fread(name, 20, 1, fp) != 1) 202 | return NULL; 203 | name[20] = '\0'; /* make sure name is zero-terminated */ 204 | strcpy(mod->name, name); 205 | } 206 | 207 | /* setup channel-settings */ 208 | { 209 | struct pimp_channel *channels; 210 | mod->channel_count = channel_count; 211 | 212 | /* allocate channel array */ 213 | channels = malloc(sizeof(struct pimp_channel) * mod->channel_count); 214 | if (NULL == channels) return NULL; 215 | 216 | memset(channels, 0, sizeof(struct pimp_channel) * mod->channel_count); 217 | 218 | pimp_set_ptr(&mod->channel_ptr, channels); 219 | 220 | /* setup default pr channel settings. */ 221 | for (i = 0; i < mod->channel_count; ++i) 222 | { 223 | channels[i].pan = 127; 224 | channels[i].volume = 64; 225 | channels[i].mute = 0; 226 | } 227 | } 228 | 229 | /* load instruments */ 230 | { 231 | struct pimp_instrument *instruments; 232 | mod->instrument_count = 31; 233 | instruments = malloc(sizeof(struct pimp_instrument) * mod->instrument_count); 234 | if (NULL == instruments) return NULL; 235 | 236 | memset(instruments, 0, sizeof(struct pimp_instrument) * mod->instrument_count); 237 | 238 | pimp_set_ptr(&mod->instrument_ptr, instruments); 239 | for (i = 0; i < mod->instrument_count; ++i) 240 | { 241 | BOOL ret = load_instrument(fp, &instruments[i], sample_bank); 242 | if (FALSE == ret) 243 | { 244 | /* TODO: cleanup */ 245 | return NULL; 246 | } 247 | } 248 | } 249 | 250 | /* read order count */ 251 | { 252 | /* read byte */ 253 | unsigned char order_count; 254 | if (fread(&order_count, 1, 1, fp) != 1) 255 | return NULL; 256 | 257 | /* clamp and warn */ 258 | if (order_count > 128) 259 | { 260 | fprintf(stderr, "warning: excessive orders in module, discarding.\n"); 261 | order_count = 128; 262 | } 263 | 264 | mod->order_count = order_count; 265 | } 266 | 267 | { 268 | /* allocate memory */ 269 | unsigned char *orders = malloc(mod->order_count); 270 | if (NULL == orders) return NULL; 271 | 272 | memset(orders, 0, mod->order_count); 273 | pimp_set_ptr(&mod->order_ptr, orders); 274 | 275 | #if 1 276 | /* we're assuming this byte to be repeat position, but we don't really know ;) */ 277 | if (fread(&mod->order_repeat, 1, 1, fp) != 1) 278 | return NULL; 279 | 280 | if (mod->order_repeat >= mod->order_count) 281 | { 282 | fprintf(stderr, "warning: repeating at out-of-range order, setting repeat order to 0\n"); 283 | mod->order_repeat = 0; 284 | } 285 | #else 286 | mod->order_repeat = 0; 287 | fseek(fp, 1, SEEK_CUR); /* discard unused byte (this may be repeat position, but that is impossible to tell) */ 288 | #endif 289 | 290 | max_pattern = 0; 291 | for (i = 0; i < mod->order_count; ++i) 292 | { 293 | if (fread(&orders[i], 1, 1, fp) != 1) 294 | return NULL; 295 | if (orders[i] > max_pattern) max_pattern = orders[i]; 296 | } 297 | } 298 | 299 | if (fseek(fp, 128 - mod->order_count, SEEK_CUR) < 0 || /* discard unused orders */ 300 | fseek(fp, 4, SEEK_CUR) < 0) /* discard mod-signature (already loaded) */ 301 | return NULL; 302 | 303 | /* load patterns */ 304 | { 305 | /* track the min and max note. this is used to detect if the module has notes outside traditional mod-limits */ 306 | int min_period = 99999; 307 | int max_period = -99999; 308 | 309 | struct pimp_pattern *patterns; 310 | mod->pattern_count = max_pattern + 1; 311 | 312 | patterns = malloc(sizeof(struct pimp_pattern) * mod->pattern_count); 313 | if (NULL == patterns) return NULL; 314 | 315 | memset(patterns, 0, sizeof(struct pimp_pattern) * mod->pattern_count); 316 | pimp_set_ptr(&mod->pattern_ptr, patterns); 317 | 318 | /* load patterns */ 319 | for (p = 0; p < mod->pattern_count; ++p) 320 | { 321 | struct pimp_pattern_entry *pattern_data; 322 | 323 | struct pimp_pattern *pat = &patterns[p]; 324 | pat->row_count = 64; 325 | 326 | pattern_data = malloc(sizeof(struct pimp_pattern_entry) * mod->channel_count * pat->row_count); 327 | if (NULL == pattern_data) return NULL; 328 | pimp_set_ptr(&pat->data_ptr, pattern_data); 329 | 330 | /* clear memory */ 331 | memset(pattern_data, 0, sizeof(struct pimp_pattern_entry) * mod->channel_count * pat->row_count); 332 | 333 | for (i = 0; i < 64; ++i) 334 | { 335 | int j; 336 | for (j = 0; j < mod->channel_count; ++j) 337 | { 338 | unsigned char buf[4]; 339 | int period; 340 | 341 | struct pimp_pattern_entry *pe = &pattern_data[i * mod->channel_count + j]; 342 | if (fread(buf, 1, 4, fp) != 4) 343 | return NULL; 344 | period = ((buf[0] & 0x0F) << 8) + buf[1]; 345 | 346 | pe->instrument = (buf[0] & 0x0F0) + (buf[2] >> 4); 347 | pe->note = return_nearest_note(period); /* - 12; */ 348 | pe->effect_byte = buf[2] & 0xF; 349 | pe->effect_parameter = buf[3]; 350 | 351 | if (period > max_period) max_period = period; 352 | if (period < min_period) min_period = period; 353 | } 354 | } 355 | } 356 | 357 | /* if there are periods in the file outside the default pt2-range, assume ft2-range */ 358 | if (min_period < mod->period_low_clamp || max_period > mod->period_high_clamp) 359 | { 360 | mod->period_low_clamp = 1; 361 | mod->period_high_clamp = 32767; 362 | } 363 | } 364 | 365 | /* load samples */ 366 | for (i = 0; i < 31; ++i) 367 | { 368 | struct pimp_sample *samp; 369 | struct pimp_instrument *instr = pimp_module_get_instrument(mod, i); 370 | samp = pimp_instrument_get_sample(instr, 0); 371 | 372 | if (samp->length > 0 && samp->length > 2) 373 | { 374 | enum pimp_sample_format src_format = PIMP_SAMPLE_S8; 375 | enum pimp_sample_format dst_format = PIMP_SAMPLE_U8; 376 | 377 | void *dst_waveform; 378 | void *src_waveform = malloc(samp->length * pimp_sample_format_get_size(src_format)); 379 | 380 | if (NULL == src_waveform || 381 | fread(src_waveform, samp->length * pimp_sample_format_get_size(src_format), 1, fp) != 1) 382 | return FALSE; 383 | 384 | ASSERT(pimp_sample_format_get_size(src_format) == pimp_sample_format_get_size(dst_format)); 385 | 386 | dst_waveform = malloc(samp->length * pimp_sample_format_get_size(dst_format)); 387 | if (NULL == dst_waveform) return FALSE; 388 | 389 | pimp_convert_sample(dst_waveform, dst_format, src_waveform, src_format, samp->length); 390 | 391 | /* insert into sample bank. */ 392 | { 393 | int pos = pimp_sample_bank_insert_sample_data(sample_bank, dst_waveform, samp->length); 394 | if (pos < 0) 395 | { 396 | free(dst_waveform); 397 | dst_waveform = NULL; 398 | 399 | free(src_waveform); 400 | src_waveform = NULL; 401 | 402 | fprintf(stderr, "failed to insert module into sample bank\n"); 403 | return FALSE; 404 | } 405 | samp->data_ptr = pos; 406 | } 407 | 408 | free(src_waveform); 409 | src_waveform = NULL; 410 | 411 | free(dst_waveform); 412 | dst_waveform = NULL; 413 | } 414 | else 415 | { 416 | ASSERT(0); /* This is a bad fix */ 417 | /* TODO: Handle no sample case */ 418 | samp->data_ptr = 0; 419 | } 420 | } 421 | return mod; 422 | } 423 | -------------------------------------------------------------------------------- /src/pimp_render.c: -------------------------------------------------------------------------------- 1 | /* pimp_render.c -- The actual rendering-code of Pimpmobile 2 | * Copyright (C) 2005-2006 Jørn Nystad and Erik Faye-Lund 3 | * For conditions of distribution and use, see copyright notice in LICENSE.TXT 4 | */ 5 | 6 | #include 7 | 8 | #include "pimp_internal.h" 9 | #include "pimp_render.h" 10 | #include "pimp_debug.h" 11 | #include "pimp_mixer.h" 12 | #include "pimp_math.h" 13 | #include "pimp_effects.h" 14 | 15 | static int pimp_channel_get_volume(struct pimp_channel_state *chan) 16 | { 17 | int volume; 18 | ASSERT(NULL != chan); 19 | 20 | volume = chan->volume; 21 | 22 | if (NULL != chan->vol_env.env) 23 | { 24 | /* envelope */ 25 | volume = (volume * pimp_envelope_sample(&chan->vol_env)) >> 8; 26 | pimp_envelope_advance_tick(&chan->vol_env, chan->sustain); 27 | 28 | /* fadeout */ 29 | volume = (volume * chan->fadeout) >> 16; 30 | if (!chan->sustain) chan->fadeout -= chan->instrument->volume_fadeout; 31 | 32 | if (chan->fadeout <= 0) 33 | { 34 | /* TODO: kill sample */ 35 | chan->fadeout = 0; 36 | } 37 | } 38 | else 39 | { 40 | if (!chan->sustain) volume = 0; 41 | } 42 | 43 | return volume; 44 | } 45 | 46 | static void note_on(const struct pimp_mod_context *ctx, struct pimp_mixer_channel_state *mc, struct pimp_channel_state *chan) 47 | { 48 | /* according to mixer_comments2.txt, vibrato counter is reset at new notes */ 49 | chan->vibrato_counter = 0; 50 | 51 | if (chan->instrument->sample_count == 0) 52 | { 53 | /* TODO: this should be handeled in the converter, and as an assert. */ 54 | 55 | /* stupid musician, tried to play an empty instrument... */ 56 | mc->sample_data = NULL; 57 | mc->sample_cursor = 0; 58 | mc->sample_cursor_delta = 0; 59 | } 60 | else 61 | { 62 | chan->sample = pimp_instrument_get_sample(chan->instrument, chan->instrument->sample_map[chan->note]); 63 | mc->sample_cursor = 0; 64 | mc->sample_data = ctx->sample_bank + chan->sample->data_ptr; 65 | mc->sample_length = chan->sample->length; 66 | mc->loop_type = (enum pimp_mixer_loop_type)chan->sample->loop_type; 67 | mc->loop_start = chan->sample->loop_start; 68 | mc->loop_end = chan->sample->loop_start + chan->sample->loop_length; 69 | 70 | if (ctx->mod->flags & FLAG_LINEAR_PERIODS) 71 | { 72 | chan->note_period = pimp_get_linear_period(((s32)chan->note) + chan->sample->rel_note, chan->sample->fine_tune); 73 | } 74 | else 75 | { 76 | chan->note_period = pimp_get_amiga_period(((s32)chan->note) + chan->sample->rel_note, chan->sample->fine_tune); 77 | } 78 | 79 | chan->period = chan->note_period; 80 | } 81 | } 82 | 83 | #define EFFECT_MISSING(ctx, eff_id) do { \ 84 | DEBUG_PRINT(DEBUG_LEVEL_ERROR, ("** eff: %x\n", eff_id)); \ 85 | if ((ctx)->callback != NULL) \ 86 | { \ 87 | ctx->callback(PIMP_CALLBACK_UNSUPPORTED_EFFECT, (eff_id)); \ 88 | } \ 89 | } while(0) 90 | 91 | #define VOLUME_EFFECT_MISSING(ctx, eff_id) do { \ 92 | DEBUG_PRINT(DEBUG_LEVEL_ERROR, ("** vol eff: %x\n", eff_id)); \ 93 | if ((ctx)->callback != NULL) \ 94 | { \ 95 | ctx->callback(PIMP_CALLBACK_UNSUPPORTED_VOLUME_EFFECT, (eff_id)); \ 96 | } \ 97 | } while(0) 98 | 99 | static void pimp_mod_context_update_row(struct pimp_mod_context *ctx) 100 | { 101 | u32 c; 102 | ASSERT(ctx != 0); 103 | 104 | ctx->curr_tick = 0; 105 | ctx->curr_row = ctx->next_row; 106 | ctx->curr_order = ctx->next_order; 107 | ctx->curr_pattern = ctx->next_pattern; 108 | pimp_mod_context_update_next_pos(ctx); 109 | 110 | for (c = 0; c < ctx->mod->channel_count; ++c) 111 | { 112 | BOOL period_dirty = FALSE; 113 | BOOL volume_dirty = FALSE; 114 | 115 | struct pimp_channel_state *chan = &ctx->channels[c]; 116 | struct pimp_mixer_channel_state *mc = &ctx->mixer->channels[c]; 117 | 118 | const struct pimp_pattern_entry *note = &pimp_pattern_get_data(ctx->curr_pattern)[ctx->curr_row * ctx->mod->channel_count + c]; 119 | 120 | #ifdef PRINT_PATTERNS 121 | print_pattern_entry(*note); 122 | #endif 123 | 124 | chan->note = note->note; 125 | chan->effect = note->effect_byte; 126 | chan->effect_param = note->effect_parameter; 127 | chan->volume_command = note->volume_command; 128 | 129 | if (note->note == KEY_OFF) 130 | { 131 | chan->sustain = FALSE; 132 | volume_dirty = TRUE; /* we need to update volume if note off killed note */ 133 | } 134 | else 135 | { 136 | if (note->instrument > 0) 137 | { 138 | chan->instrument = pimp_module_get_instrument(ctx->mod, note->instrument - 1); 139 | 140 | chan->vol_env.env = pimp_instrument_get_vol_env(chan->instrument); 141 | pimp_envelope_reset(&chan->vol_env); 142 | chan->sustain = TRUE; 143 | chan->fadeout = 1 << 16; 144 | 145 | if (NULL != chan->sample) 146 | { 147 | chan->volume = chan->sample->volume; 148 | volume_dirty = TRUE; 149 | } 150 | } 151 | 152 | if (note->note > 0) 153 | { 154 | if ( 155 | (NULL != chan->instrument) && 156 | (EFF_PORTA_NOTE != chan->effect) && 157 | (EFF_PORTA_NOTE_VOLUME_SLIDE != chan->effect) && 158 | !((EFF_MULTI_FX == chan->effect) && (EFF_NOTE_DELAY == chan->effect_param)) 159 | ) 160 | { 161 | note_on(ctx, mc, chan); 162 | period_dirty = TRUE; 163 | } 164 | 165 | if (chan->effect == EFF_SAMPLE_OFFSET) 166 | { 167 | mc->sample_cursor = (chan->effect_param * 256) << 12; 168 | 169 | if (mc->sample_cursor > (mc->sample_length << 12)) 170 | { 171 | if (ctx->mod->flags & FLAG_SAMPLE_OFFSET_CLAMP) mc->sample_cursor = mc->sample_length << 12; 172 | else mc->sample_data = NULL; /* kill sample */ 173 | } 174 | } 175 | } 176 | } 177 | 178 | if (note->instrument > 0) 179 | { 180 | chan->volume = chan->sample->volume; 181 | volume_dirty = TRUE; 182 | } 183 | 184 | switch (chan->volume_command >> 4) 185 | { 186 | case 0x0: break; /* do nothing */ 187 | 188 | case 0x1: 189 | case 0x2: 190 | case 0x3: 191 | case 0x4: 192 | case 0x5: /* set volume */ 193 | if (note->volume_command > 0x50) 194 | { 195 | /* volume commands 0x51..0x5f doesn't seem to be defined. */ 196 | DEBUG_PRINT(DEBUG_LEVEL_ERROR, ("unsupported volume-command %02X\n", note->volume_command)); 197 | VOLUME_EFFECT_MISSING(ctx, chan->volume_command); 198 | } 199 | else 200 | { 201 | chan->volume = note->volume_command - 0x10; 202 | volume_dirty = TRUE; 203 | } 204 | break; 205 | 206 | case 0x6: break; /* volume slide down */ 207 | case 0x7: break; /* volume slide up */ 208 | 209 | case 0x8: /* fine volume slide down */ 210 | volume_slide(chan, -(chan->volume_command & 0xF)); 211 | volume_dirty = TRUE; 212 | break; 213 | 214 | case 0x9: /* fine volume slide up */ 215 | volume_slide(chan, chan->volume_command & 0xF); 216 | volume_dirty = TRUE; 217 | break; 218 | 219 | case 0xa: /* set vibrato speed */ 220 | VOLUME_EFFECT_MISSING(ctx, chan->volume_command); 221 | break; 222 | 223 | case 0xb: /* vibrato */ 224 | VOLUME_EFFECT_MISSING(ctx, chan->volume_command); 225 | break; 226 | 227 | case 0xc: /* set panning */ 228 | /* COMPLETELY UNTESTED CODE!!! */ 229 | chan->pan = (chan->volume_command & 0xF) << 4; 230 | break; 231 | 232 | case 0xd: /* pan slide left */ 233 | { 234 | /* COMPLETELY UNTESTED CODE!!! */ 235 | int new_pan = ((int)chan->pan) - ((chan->volume_command & 0xF) << 4); 236 | if (new_pan < 0) new_pan = 0; 237 | chan->pan = new_pan; 238 | } 239 | break; 240 | 241 | case 0xe: /* pan slide right */ 242 | { 243 | /* COMPLETELY UNTESTED CODE!!! */ 244 | int new_pan = ((int)chan->pan) + ((chan->volume_command & 0xF) << 4); 245 | if (new_pan > 255) new_pan = 255; 246 | chan->pan = new_pan; 247 | } 248 | break; 249 | 250 | case 0xf: /* tone porta */ 251 | VOLUME_EFFECT_MISSING(ctx, chan->volume_command); 252 | break; 253 | 254 | default: 255 | ASSERT(FALSE); /* should never happen */ 256 | VOLUME_EFFECT_MISSING(ctx, chan->volume_command); 257 | } 258 | 259 | switch (chan->effect) 260 | { 261 | case EFF_NONE: break; 262 | 263 | case EFF_PORTA_UP: 264 | if (chan->effect_param != 0) chan->porta_speed = chan->effect_param * 4; 265 | break; 266 | 267 | case EFF_PORTA_DOWN: 268 | if (chan->effect_param != 0) chan->porta_speed = chan->effect_param * 4; 269 | break; 270 | 271 | case EFF_PORTA_NOTE: 272 | if (note->note > 0) 273 | { 274 | /* fine tune and relative note are taken into account */ 275 | if (ctx->mod->flags & FLAG_LINEAR_PERIODS) chan->porta_target = pimp_get_linear_period(note->note + chan->sample->rel_note, chan->sample->fine_tune); 276 | else chan->porta_target = pimp_get_amiga_period(note->note + chan->sample->rel_note, chan->sample->fine_tune); 277 | 278 | /* clamp porta-target period (should not be done for S3M) */ 279 | if (chan->porta_target > ctx->mod->period_high_clamp) chan->porta_target = ctx->mod->period_high_clamp; 280 | if (chan->porta_target < ctx->mod->period_low_clamp) chan->porta_target = ctx->mod->period_low_clamp; 281 | } 282 | if (chan->effect_param != 0) chan->porta_speed = chan->effect_param * 4; 283 | break; 284 | 285 | case EFF_VIBRATO: 286 | if (0 != (chan->effect_param & 0xF0)) chan->vibrato_speed = chan->effect_param >> 4; 287 | if (0 != (chan->effect_param & 0x0F)) chan->vibrato_depth = chan->effect_param & 0x0F; 288 | break; 289 | 290 | case EFF_PORTA_NOTE_VOLUME_SLIDE: 291 | if (note->note > 0) 292 | { 293 | /* no fine tune or relative note here, boooy */ 294 | if (ctx->mod->flags & FLAG_LINEAR_PERIODS) chan->porta_target = pimp_get_linear_period(note->note + chan->sample->rel_note, 0); 295 | else chan->porta_target = pimp_get_amiga_period(note->note, 0); 296 | 297 | /* clamp porta-target period (should not be done for S3M) */ 298 | if (chan->porta_target > ctx->mod->period_high_clamp) chan->porta_target = ctx->mod->period_high_clamp; 299 | if (chan->porta_target < ctx->mod->period_low_clamp) chan->porta_target = ctx->mod->period_low_clamp; 300 | } 301 | 302 | if (chan->effect_param & 0xF0) 303 | { 304 | chan->volume_slide_speed = chan->effect_param >> 4; 305 | } 306 | else if (chan->effect_param & 0x0F) 307 | { 308 | chan->volume_slide_speed = -(chan->effect_param & 0xF); 309 | } 310 | break; 311 | 312 | case EFF_VIBRATO_VOLUME_SLIDE: EFFECT_MISSING(ctx, chan->effect); break; 313 | case EFF_TREMOLO: EFFECT_MISSING(ctx, chan->effect); break; 314 | 315 | case EFF_SET_PAN: 316 | chan->pan = chan->effect_param; 317 | break; 318 | 319 | case EFF_SAMPLE_OFFSET: break; 320 | 321 | case EFF_VOLUME_SLIDE: 322 | if (chan->effect_param & 0xF0) 323 | { 324 | chan->volume_slide_speed = chan->effect_param >> 4; 325 | } 326 | else if (chan->effect_param & 0x0F) 327 | { 328 | chan->volume_slide_speed = -(chan->effect_param & 0xF); 329 | } 330 | break; 331 | 332 | case EFF_JUMP_ORDER: 333 | /* go to order xy */ 334 | pimp_mod_context_set_next_pos( ctx, 0, chan->effect_param ); 335 | break; 336 | 337 | case EFF_SET_VOLUME: 338 | chan->volume = chan->effect_param; 339 | if (chan->volume > 64) chan->volume = 64; 340 | volume_dirty = TRUE; 341 | break; 342 | 343 | case EFF_BREAK_ROW: 344 | { 345 | /* go to next order, row xy (decimal) */ 346 | int new_row = (chan->effect_param >> 4) * 10 + (chan->effect_param & 0xF); 347 | int new_order = ctx->curr_order + 1; 348 | pimp_mod_context_set_next_pos(ctx, new_row, new_order); 349 | } 350 | break; 351 | 352 | case EFF_MULTI_FX: 353 | switch (chan->effect_param >> 4) 354 | { 355 | case EFF_AMIGA_FILTER: break; 356 | 357 | case EFF_FINE_PORTA_UP: 358 | porta_up(chan, ctx->mod->period_low_clamp); 359 | period_dirty = TRUE; 360 | break; 361 | 362 | case EFF_FINE_PORTA_DOWN: 363 | porta_down(chan, ctx->mod->period_high_clamp); 364 | period_dirty = TRUE; 365 | break; 366 | 367 | case EFF_PATTERN_LOOP: 368 | if (0 == (chan->effect_param & 0xF)) 369 | { 370 | chan->loop_target_order = ctx->curr_order; 371 | chan->loop_target_row = ctx->curr_row; 372 | } 373 | else 374 | { 375 | if (0 == chan->loop_counter) 376 | { 377 | chan->loop_counter = chan->effect_param & 0xF; 378 | } 379 | else chan->loop_counter--; 380 | 381 | if (0 < chan->loop_counter) 382 | { 383 | pimp_mod_context_set_next_pos(ctx, chan->loop_target_row, chan->loop_target_order); 384 | } 385 | } 386 | break; 387 | 388 | case EFF_RETRIG_NOTE: 389 | if ((note->effect_parameter & 0xF) != 0) 390 | { 391 | chan->note_retrig = note->effect_parameter & 0xF; 392 | } 393 | break; 394 | 395 | case EFF_FINE_VOLUME_SLIDE_UP: 396 | volume_slide(chan, chan->effect_param & 0xF); 397 | volume_dirty = TRUE; 398 | break; 399 | 400 | case EFF_FINE_VOLUME_SLIDE_DOWN: 401 | volume_slide(chan, -(chan->effect_param & 0xF)); 402 | volume_dirty = TRUE; 403 | break; 404 | 405 | case EFF_NOTE_DELAY: 406 | chan->note_delay = chan->effect_param & 0xF; 407 | chan->note = note->note; 408 | break; 409 | 410 | default: 411 | EFFECT_MISSING(ctx, (chan->effect << 4) | (chan->effect_param >> 4)); 412 | } 413 | break; 414 | 415 | case EFF_TEMPO: 416 | if (note->effect_parameter < 0x20) ctx->curr_tempo = chan->effect_param; 417 | else pimp_mod_context_set_bpm(ctx, chan->effect_param); 418 | break; 419 | 420 | case EFF_SET_GLOBAL_VOLUME: EFFECT_MISSING(ctx, chan->effect); break; 421 | case EFF_GLOBAL_VOLUME_SLIDE: EFFECT_MISSING(ctx, chan->effect); break; 422 | 423 | case EFF_KEY_OFF: 424 | if (chan->effect_param == ctx->curr_tick) 425 | { 426 | chan->sustain = FALSE; 427 | volume_dirty = TRUE; /* we need to update volume if note off killed note */ 428 | } 429 | break; 430 | 431 | case EFF_SET_ENVELOPE_POSITION: EFFECT_MISSING(ctx, chan->effect); break; 432 | case EFF_PAN_SLIDE: EFFECT_MISSING(ctx, chan->effect); break; 433 | 434 | case EFF_MULTI_RETRIG: 435 | if ((note->effect_parameter & 0xF0) != 0) DEBUG_PRINT(DEBUG_LEVEL_ERROR, ("multi retrig x-parameter != 0 not supported\n")); 436 | if ((note->effect_parameter & 0x0F) != 0) chan->note_retrig = note->effect_parameter & 0xF; 437 | break; 438 | 439 | case EFF_TREMOR: EFFECT_MISSING(ctx, chan->effect); break; 440 | 441 | case EFF_SYNC_CALLBACK: 442 | if (ctx->callback != NULL) ctx->callback(PIMP_CALLBACK_SYNC, chan->effect_param); 443 | break; 444 | 445 | case EFF_ARPEGGIO: EFFECT_MISSING(ctx, chan->effect); break; 446 | case EFF_SET_TEMPO: EFFECT_MISSING(ctx, chan->effect); break; 447 | case EFF_SET_BPM: EFFECT_MISSING(ctx, chan->effect); break; 448 | 449 | default: 450 | EFFECT_MISSING(ctx, chan->effect); 451 | } 452 | 453 | if (period_dirty) 454 | { 455 | if (ctx->mod->flags & FLAG_LINEAR_PERIODS) 456 | { 457 | mc->sample_cursor_delta = pimp_get_linear_delta(chan->period, ctx->delta_scale); 458 | } 459 | else 460 | { 461 | mc->sample_cursor_delta = pimp_get_amiga_delta(chan->period, ctx->delta_scale); 462 | } 463 | } 464 | 465 | if (volume_dirty || chan->vol_env.env != 0) 466 | { 467 | mc->volume = (pimp_channel_get_volume(chan) * ctx->global_volume) >> 8; 468 | } 469 | } 470 | 471 | #ifdef PRINT_PATTERNS 472 | iprintf("\n"); 473 | #endif 474 | } 475 | 476 | static void pimp_mod_context_update_tick(struct pimp_mod_context *ctx) 477 | { 478 | u32 c; 479 | if (ctx->mod == NULL) return; /* no module active (sound-effects can still be playing, though) */ 480 | 481 | if (ctx->curr_tick == ctx->curr_tempo) 482 | { 483 | pimp_mod_context_update_row(ctx); 484 | ctx->curr_tick++; 485 | return; 486 | } 487 | 488 | for (c = 0; c < ctx->mod->channel_count; ++c) 489 | { 490 | struct pimp_channel_state *chan = &ctx->channels[c]; 491 | struct pimp_mixer_channel_state *mc = &ctx->mixer->channels[c]; 492 | BOOL period_dirty = FALSE; 493 | BOOL volume_dirty = FALSE; 494 | 495 | switch (chan->volume_command >> 4) 496 | { 497 | case 0x0: /* do nothing */ 498 | break; 499 | 500 | case 0x1: 501 | case 0x2: 502 | case 0x3: 503 | case 0x4: 504 | case 0x5: /* set volume */ 505 | break; 506 | 507 | case 0x6: 508 | /* volume slide down */ 509 | volume_slide(chan, -(chan->volume_command & 0xF)); 510 | volume_dirty = TRUE; 511 | break; 512 | 513 | case 0x7: 514 | /* volume slide up */ 515 | volume_slide(chan, chan->volume_command & 0xF); 516 | volume_dirty = TRUE; 517 | break; 518 | 519 | case 0x8: break; /* fine volume slide down */ 520 | case 0x9: break; /* fine volume slide up */ 521 | case 0xa: break; /* set vibrato speed */ 522 | case 0xb: break; /* vibrato */ 523 | case 0xc: break; /* set panning */ 524 | case 0xd: break; /* pan slide left */ 525 | case 0xe: break; /* pan slide right */ 526 | case 0xf: break; /* tone porta */ 527 | 528 | default: 529 | ASSERT(FALSE); /* should never happen */ 530 | VOLUME_EFFECT_MISSING(ctx, chan->volume_command); 531 | } 532 | 533 | switch (chan->effect) 534 | { 535 | case EFF_NONE: break; 536 | 537 | case EFF_PORTA_UP: 538 | porta_up(chan, ctx->mod->period_low_clamp); 539 | period_dirty = TRUE; 540 | break; 541 | 542 | case EFF_PORTA_DOWN: 543 | porta_down(chan, ctx->mod->period_high_clamp); 544 | period_dirty = TRUE; 545 | break; 546 | 547 | case EFF_PORTA_NOTE: 548 | porta_note(chan); 549 | period_dirty = TRUE; 550 | break; 551 | 552 | case EFF_VIBRATO: 553 | vibrato(chan, ctx->mod->period_low_clamp, ctx->mod->period_high_clamp); 554 | period_dirty = TRUE; 555 | break; 556 | 557 | case EFF_PORTA_NOTE_VOLUME_SLIDE: 558 | porta_note(chan); 559 | period_dirty = TRUE; 560 | 561 | volume_slide(chan, chan->volume_slide_speed); 562 | volume_dirty = TRUE; 563 | break; 564 | 565 | case EFF_SET_PAN: break; 566 | case EFF_SAMPLE_OFFSET: break; 567 | 568 | case EFF_VOLUME_SLIDE: 569 | volume_slide(chan, chan->volume_slide_speed); 570 | volume_dirty = TRUE; 571 | break; 572 | 573 | case EFF_MULTI_FX: 574 | switch (chan->effect_param >> 4) 575 | { 576 | case EFF_AMIGA_FILTER: break; 577 | case EFF_FINE_PORTA_UP: break; 578 | case EFF_FINE_PORTA_DOWN: break; 579 | case EFF_PATTERN_LOOP: break; 580 | 581 | case EFF_RETRIG_NOTE: 582 | chan->retrig_tick++; 583 | if (chan->retrig_tick == chan->note_retrig) 584 | { 585 | mc->sample_cursor = 0; 586 | chan->retrig_tick = 0; 587 | } 588 | break; 589 | 590 | case EFF_FINE_VOLUME_SLIDE_UP: 591 | case EFF_FINE_VOLUME_SLIDE_DOWN: 592 | break; /* fine volume slide is only done on tick0 */ 593 | 594 | case EFF_NOTE_DELAY: 595 | /* note on */ 596 | if (--chan->note_delay == 0) 597 | { 598 | note_on(ctx, mc, chan); 599 | period_dirty = TRUE; 600 | } 601 | break; 602 | } 603 | break; 604 | 605 | case EFF_KEY_OFF: 606 | if (chan->effect_param == ctx->curr_tick) 607 | { 608 | chan->sustain = FALSE; 609 | volume_dirty = TRUE; /* we need to update volume if note off killed note */ 610 | } 611 | break; 612 | 613 | case EFF_MULTI_RETRIG: 614 | chan->retrig_tick++; 615 | if (chan->retrig_tick == chan->note_retrig) 616 | { 617 | mc->sample_cursor = 0; 618 | chan->retrig_tick = 0; 619 | } 620 | break; 621 | } 622 | 623 | /* period to delta-conversion */ 624 | if (period_dirty) 625 | { 626 | if (ctx->mod->flags & FLAG_LINEAR_PERIODS) 627 | { 628 | mc->sample_cursor_delta = pimp_get_linear_delta(chan->period, ctx->delta_scale); 629 | } 630 | else 631 | { 632 | mc->sample_cursor_delta = pimp_get_amiga_delta(chan->period, ctx->delta_scale); 633 | } 634 | } 635 | 636 | if (volume_dirty || chan->vol_env.env != 0) 637 | { 638 | mc->volume = (pimp_channel_get_volume(chan) * ctx->global_volume) >> 8; 639 | } 640 | } 641 | 642 | ctx->curr_tick++; 643 | } 644 | 645 | void pimp_render(struct pimp_mod_context *ctx, s8 *buf, u32 samples) 646 | { 647 | while (TRUE) 648 | { 649 | int samples_to_mix = MIN(ctx->remainder, samples); 650 | if (samples_to_mix != 0) pimp_mixer_mix(ctx->mixer, buf, samples_to_mix); 651 | 652 | buf += samples_to_mix; 653 | samples -= samples_to_mix; 654 | ctx->remainder -= samples_to_mix; 655 | 656 | if (samples == 0) break; 657 | 658 | pimp_mod_context_update_tick(ctx); 659 | 660 | /* fixed point tick length */ 661 | ctx->curr_tick_len += ctx->tick_len; 662 | ctx->remainder = ctx->curr_tick_len >> 8; 663 | ctx->curr_tick_len -= (ctx->curr_tick_len >> 8) << 8; 664 | } 665 | } 666 | --------------------------------------------------------------------------------