├── .gitignore ├── Android.mk ├── LICENSE ├── include └── c_synth │ ├── synth_assert.h │ ├── synth_errors.h │ └── synth.h ├── src ├── include │ └── c_synth_internal │ │ ├── synth_renderer.h │ │ ├── synth_prng.h │ │ ├── synth_parser.h │ │ ├── synth_volume.h │ │ ├── synth_track.h │ │ ├── synth_lexer.h │ │ ├── synth_audio.h │ │ ├── synth_note.h │ │ └── synth_types.h ├── synth_renderer.c ├── synth_prng.c ├── synth_volume.c ├── synth_audio.c └── synth_track.c ├── samples ├── drum-test.mml ├── ld34_bass_1.mml ├── ld31.mml ├── ld34_bass_2.mml ├── ld34_melody.mml ├── jjat-boss.mml ├── sonic2-chemical.mml └── ld34.mml ├── README.md ├── tst ├── tst_compileSong.c ├── tst_countSamples.c ├── tst_renderTrack.c ├── tst_playMultipleSongsSDL2.c └── tst_playSongSDL2.c └── Makefile /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | bin/ 31 | 32 | *.swp 33 | -------------------------------------------------------------------------------- /Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | ########################### 4 | # 5 | # CSynth shared library 6 | # 7 | ########################### 8 | 9 | include $(CLEAR_VARS) 10 | 11 | LOCAL_MODULE := CSynth 12 | 13 | LOCAL_C_INCLUDES := $(LOCAL_PATH)/../include $(LOCAL_PATH)/include 14 | 15 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) 16 | 17 | LOCAL_SRC_FILES := \ 18 | $(LOCAL_PATH)/synth.c \ 19 | $(LOCAL_PATH)/synth_audio.c \ 20 | $(LOCAL_PATH)/synth_lexer.c \ 21 | $(LOCAL_PATH)/synth_note.c \ 22 | $(LOCAL_PATH)/synth_parser.c \ 23 | $(LOCAL_PATH)/synth_prng.c \ 24 | $(LOCAL_PATH)/synth_renderer.c \ 25 | $(LOCAL_PATH)/synth_track.c \ 26 | $(LOCAL_PATH)/synth_volume.c 27 | 28 | LOCAL_SHARED_LIBRARIES := SDL2 29 | LOCAL_CFLAGS += -DUSE_SDL2 30 | LOCAL_LDLIBS := -ldl -landroid 31 | 32 | include $(BUILD_SHARED_LIBRARY) 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Gabriel Francisco Mandaji 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 must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /include/c_synth/synth_assert.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file include/synth/synth_assert.h 3 | * 4 | * Macros for asserting stuff, without killing the process. 5 | * TODO LOG whenever an assert fails! 6 | */ 7 | #ifndef __SYNTH_ASSERT_H_ 8 | #define __SYNTH_ASSERT_H_ 9 | 10 | /** 11 | * Assert that the expression is true and jump to a __err label on failure. 12 | * 13 | * You must set that label, and the error handling code, whenever you use this! 14 | */ 15 | #define SYNTH_ASSERT(stmt) \ 16 | do { \ 17 | if (!(stmt)) { \ 18 | goto __err; \ 19 | } \ 20 | } while(0) 21 | 22 | /** 23 | * Assert that the expression is true and jump to a __err label on failure. 24 | * Also, 'rv' is set to the desired error code 'err'. 25 | * 26 | * You must set that label - and the error handling code - and define an 'err' 27 | *variable whenever you use this! 28 | */ 29 | #define SYNTH_ASSERT_ERR(stmt, err) \ 30 | do { \ 31 | if (!(stmt)) { \ 32 | rv = err; \ 33 | goto __err; \ 34 | } \ 35 | } while(0) 36 | 37 | #endif 38 | 39 | -------------------------------------------------------------------------------- /include/c_synth/synth_errors.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file include/synth/synth_errors.h 3 | * 4 | * Enumeration of possible errors 5 | */ 6 | #ifndef __SYNTH_ERRORS_H 7 | #define __SYNTH_ERRORS_H 8 | 9 | typedef enum { 10 | SYNTH_OK = 0, 11 | SYNTH_ALREADY_STARTED, 12 | SYNTH_INTERNAL_ERR, 13 | SYNTH_MEM_ERR, 14 | SYNTH_OPEN_FILE_ERR, 15 | SYNTH_FUNCTION_NOT_IMPLEMENTED, 16 | SYNTH_INVALID_TOKEN, 17 | SYNTH_EOF, 18 | SYNTH_EOS, 19 | SYNTH_UNEXPECTED_TOKEN, 20 | SYNTH_EMPTY_SEQUENCE, 21 | SYNTH_INVALID_WAVE, 22 | SYNTH_NOT_INITIALIZED, 23 | SYNTH_ALREADY_INITIALIZED, 24 | SYNTH_THREAD_ALREADY_INITIALIZED, 25 | SYNTH_BAD_PARAM_ERR, 26 | SYNTH_THREAD_INIT_FAILED, 27 | SYNTH_BUFFER_ALREADY_INITIALIZED, 28 | SYNTH_BUFFER_NOT_ENOUGH_SAMPLES, 29 | SYNTH_COULDNT_LOCK, 30 | SYNTH_NO_ERRORS, 31 | SYNTH_INVALID_INDEX, 32 | SYNTH_COMPLEX_LOOPPOINT, 33 | SYNTH_NOT_LOOPABLE, 34 | SYNTH_COMPASS_OVERFLOW, 35 | SYNTH_BAD_LOOP_START, 36 | SYNTH_BAD_LOOP_END, 37 | SYNTH_BAD_LOOP_POINT, 38 | SYNTH_MAX_ERR 39 | } synth_err; 40 | 41 | #endif 42 | 43 | -------------------------------------------------------------------------------- /src/include/c_synth_internal/synth_renderer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file src/include/c_synth_internal/synth_renderer.h 3 | */ 4 | #ifndef __SYNTH_INTERNAL_RENDERER_H__ 5 | #define __SYNTH_INTERNAL_RENDERER_H__ 6 | 7 | /** 8 | * Initialize the renderer for a given audio 9 | * 10 | * @param [ in]pCtx The renderer context 11 | * @param [ in]pAudio The audio to be rendered 12 | * @param [ in]frequency The synth frequency in samples per second 13 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 14 | */ 15 | synth_err synthRenderer_init(synthRendererCtx *pCtx, synthAudio *pAudio, 16 | int frequency); 17 | 18 | /** 19 | * Returns the position back to the start but don't modify anything related to 20 | * audio 21 | * 22 | * @param [ in]pCtx The renderer context 23 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 24 | */ 25 | synth_err synthRenderer_resetPosition(synthRendererCtx *pCtx); 26 | 27 | /** 28 | * Return a note's length in samples, considering its position within the 29 | * compass. This function also update the renderer internal state (i.e., the 30 | * position within the compass) 31 | * 32 | * @param [out]pLen The note's length in samples 33 | * @param [ in]pCtx The renderer context 34 | * @param [ in]pNote The note 35 | */ 36 | synth_err synthRenderer_getNoteLengthAndUpdate(int *pLen, 37 | synthRendererCtx *pCtx, synthNote *pNote); 38 | 39 | #endif /* __SYNTH_INTERNAL_RENDERER_H__ */ 40 | 41 | -------------------------------------------------------------------------------- /samples/drum-test.mml: -------------------------------------------------------------------------------- 1 | MML//=========================================================================// 2 | // Simple drum test // 3 | //============================================================================// 4 | 5 | t140 6 | 7 | //============================================================================== 8 | // Hi-hat/Crash cymbal 9 | //------------------------------------------------------------------------------ 10 | 11 | w5 l16 v64 12 | 13 | // Hi-hat 14 | k3 q12 h80 15 | 16 | o4 gr r1 17 | 18 | o1 c r4 c+ r4 d r4 d+ r4 e r4 f r4 f+ r4 g r4 g+ r4 a r4 a+ r4 b r1 19 | 20 | o4 grgr r1 21 | 22 | o2 c r4 c+ r4 d r4 d+ r4 e r4 f r4 f+ r4 g r4 g+ r4 a r4 a+ r4 b r1 23 | 24 | o4 grgr gr r1 25 | 26 | o3 c r4 c+ r4 d r4 d+ r4 e r4 f r4 f+ r4 g r4 g+ r4 a r4 a+ r4 b r1 27 | 28 | o4 grgr grgr r1 29 | 30 | o4 c r4 c+ r4 d r4 d+ r4 e r4 f r4 f+ r4 g r4 g+ r4 a r4 a+ r4 b r1 31 | 32 | o4 grgr grgr gr r1 33 | 34 | o5 c r4 c+ r4 d r4 d+ r4 e r4 f r4 f+ r4 g r4 g+ r4 a r4 a+ r4 b r1 35 | 36 | o4 grgr grgr grgr r1 37 | 38 | o6 c r4 c+ r4 d r4 d+ r4 e r4 f r4 f+ r4 g r4 g+ r4 a r4 a+ r4 b r1 39 | 40 | o4 grgr grgr grgr gr r1 41 | 42 | o7 c r4 c+ r4 d r4 d+ r4 e r4 f r4 f+ r4 g r4 g+ r4 a r4 a+ r4 b r1 43 | 44 | o4 grgr grgr grgr grgr r1 45 | 46 | o8 c r4 c+ r4 d r4 d+ r4 e r4 f r4 f+ r4 g r4 g+ r4 a r4 a+ r4 b 47 | 48 | //============================================================================== 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | c_synth 2 | ============ 3 | 4 | c_synth is a chiptune-ish software synthesizer. It takes a MML (actually a 5 | language based on MML) song as input and compiles it into the memory. The 6 | compiled buffer may then be played through whichever backend is desired, as long 7 | as it accepts samples in little endian and use either 16 or 8 bits samples. 8 | 9 | ## Compiling and installing 10 | 11 | All the following steps where also tested on Windows, using 12 | [MinGW](http://www.mingw.org/). 13 | 14 | There are no dependencies to build the library. After cloning it, simply run: 15 | 16 | ``` 17 | $ sudo make install DEBUG=yes 18 | $ sudo make install RELEASE=yes 19 | ``` 20 | 21 | The library will be installed on /usr/lib/c_synth and the headers on 22 | /usr/include/c_synth. 23 | 24 | ## Testing and running 25 | 26 | There are a few songs on the directory 'samples/'. They may be compiled and 27 | played using the examples on the directory 'tst/'. 28 | 29 | Some tests uses SDL2 to actually play the compiled songs. On debian-based 30 | distros, that library may be installed by running: 31 | 32 | ``` 33 | $ sudo apt-get install libsdl2-dev 34 | ``` 35 | 36 | For other linux distros, look into your package manager or download the 37 | [source](https://www.libsdl.org/download-2.0.php) and compile it manually. 38 | 39 | To build the tests, run: 40 | 41 | ``` 42 | $ make tests 43 | ``` 44 | 45 | To play one of the songs: 46 | 47 | ``` 48 | ./bin/Linux/tst_repeatSongSDL2 --frequency 44100 --mode 2chan-16 --file samples/sonic2-chemical.mml 49 | ``` 50 | 51 | To exit, press Ctrl-C. 52 | 53 | -------------------------------------------------------------------------------- /src/include/c_synth_internal/synth_prng.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Pseudo-random number generator. There are also other PRNG-based 3 | * functionalities, like a gaussian white noise generator. 4 | * 5 | * @file src/include/synth_internal/synth_prng.h 6 | */ 7 | #ifndef __SYNTH_PRNG_H__ 8 | #define __SYNTH_PRNG_H__ 9 | 10 | #include 11 | #include 12 | 13 | /** 14 | * (Re)Initialize the Pseudo-random number generator with the desired seed 15 | * 16 | * @param [ in]pCtx The context 17 | * @param [ in]seed The desired seed 18 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 19 | */ 20 | synth_err synthPRNG_init(synthPRNGCtx *pCtx, unsigned int seed); 21 | 22 | /** 23 | * Retrieve a (pseudo) random value in the range [0, 0xFFFFFFFF] 24 | * 25 | * @param [out]pVal The generated value 26 | * @param [ in]pCtx The context 27 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 28 | */ 29 | synth_err synthPRNG_getUint(unsigned int *pVal, synthPRNGCtx *pCtx); 30 | 31 | /** 32 | * Retrieve a (pseudo) random value in the range [0.0, 1.0] 33 | * 34 | * @param [out]pVal The generated value 35 | * @param [ in]pCtx The context 36 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 37 | */ 38 | synth_err synthPRNG_getDouble(double *pVal, synthPRNGCtx *pCtx); 39 | 40 | /** 41 | * Generate points for a gaussian white noise 42 | * 43 | * @param [out]pVal The generated noise, in range [-1.0, 1.0] 44 | * @param [ in]pCtx The contx 45 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 46 | */ 47 | synth_err synthPRNG_getGaussianNoise(double *pVal, synthPRNGCtx *pCtx); 48 | 49 | #endif /* __SYNTH_PRNG_H__ */ 50 | 51 | -------------------------------------------------------------------------------- /samples/ld34_bass_1.mml: -------------------------------------------------------------------------------- 1 | MML//=======================================// 2 | // Song written for 'MK-F417XX3' // 3 | // a game by GFM // 4 | // made for Ludum Dare #34 // 5 | //==========================================// 6 | 7 | t160 8 | 9 | //============================================ 10 | // Bass-line-1: 11 | // 25% square wave 12 | // play notes for 60% of its duration 13 | //-------------------------------------------- 14 | 15 | w2 o3 l4 k5 q60 h65 v(35, 20) > 16 | 17 | // -- 1 -> 4 ------------------------------- 18 | l1 19 | a 20 | < c > 21 | f 22 | g 23 | 24 | // -- 5 -> 8 ------------------------------- 25 | a2 a4. a8 26 | < c2 c4. c8> 27 | f2 f4. f8 28 | g2 g4. g8 29 | 30 | l4 31 | // -- 9 -> 10 ------------------------------- 32 | r g2 g 33 | r g g g 34 | 35 | $ // -- Repeat on 10 ------------------------- 36 | 37 | // -- 11 -> 22 ------------------------------- 38 | l4 39 | [a a a a 40 | < c c c c> 41 | f f f f 42 | g g g g]3 43 | 44 | // Go to a higher pitch (and tone) 45 | l1 46 | // -- 23 -> 26 ------------------------------- 47 | [a < 48 | c 49 | f 50 | g >] 51 | 52 | // -- 27 -> 30 ------------------------------- 53 | a+ < 54 | c+ 55 | f+ 56 | g+ > 57 | 58 | // -- 31 -> 34 ------------------------------- 59 | b < 60 | d 61 | g 62 | a > 63 | 64 | // First part in a higher tone 65 | 66 | // -- 35 -> 46 ------------------------------- 67 | l4 68 | [b b b b 69 | < d d d d> 70 | g g g g 71 | a a a a]3 72 | 73 | // Go back to the original pitch 74 | 75 | l1 76 | // -- 47 -> 50 ------------------------------- 77 | a+ < 78 | c+ 79 | f+ 80 | g+ > 81 | 82 | // -- 51 -> 54 ------------------------------- 83 | a < 84 | c 85 | f 86 | g > 87 | 88 | //============================================ 89 | 90 | -------------------------------------------------------------------------------- /samples/ld31.mml: -------------------------------------------------------------------------------- 1 | MML//=========================================================================// 2 | // Song originally written for 'Cold Rembembrance', // 3 | // a Ludum Dare # 31 entry by GFM // 4 | // Original version: // 5 | // https://github.com/SirGFM/ld31/blob/v1.0/assets/vmml/song.mml // 6 | //============================================================================// 7 | 8 | t90 9 | 10 | //============================================================================== 11 | // Bass-line: 25% square wave 12 | //------------------------------------------------------------------------------ 13 | 14 | w2 v(40, 30) o3 15 | 16 | $ 17 | 18 | [grr drr]3 19 | grr rdr 20 | rrg rrr 21 | drr r 22 | 23 | ; //============================================================================ 24 | 25 | 26 | //============================================================================== 27 | // Main-melody: 50% square wave 28 | //------------------------------------------------------------------------------ 29 | 30 | w0 v(50, 40) o5 31 | 32 | $ 33 | 34 | q75 35 | ra+2 rf2 36 | ra+ rf2 37 | 38 | ra+ 39 | 40 | q87 41 | rga a+ 42 | gaa+ a+2r 43 | rar rr 44 | 45 | ; //============================================================================ 46 | 47 | //============================================================================== 48 | // "Accompaniment": 50% square wave 49 | //------------------------------------------------------------------------------ 50 | 51 | w0 v(40, 20) o5 52 | 53 | $ 54 | 55 | r ra2 56 | rr ra2 57 | 58 | rr rrr 59 | rrr rrr 60 | rrr rrr 61 | rrr r 62 | 63 | //============================================================================== 64 | 65 | -------------------------------------------------------------------------------- /src/include/c_synth_internal/synth_parser.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file @src/include/synth_internal/synth_parser.h 3 | */ 4 | #ifndef __SYNTH_PARSER_H__ 5 | #define __SYNTH_PARSER_H__ 6 | 7 | #include 8 | 9 | /** 10 | * Initialize the parser 11 | * 12 | * The default settings are as follows: 13 | * - BMP: 60bmp 14 | * - Octave: 4th 15 | * - Duration: quarter note 16 | * - Keyoff: 75% (i.e., pressed for that amount of the duration) 17 | * - Pan: 50% (i.e., equal to both channels) 18 | * - Wave: 50% square 19 | * - Volume: 50% 20 | * 21 | * @param [out]pParser The parser context to be initialized 22 | * @param [ in]pCtx The synthesizer context 23 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 24 | */ 25 | synth_err synthParser_init(synthParserCtx *pParser, synthCtx *pCtx); 26 | 27 | /** 28 | * Return the error string 29 | * 30 | * This string is statically allocated and mustn't be freed by user 31 | * 32 | * @param [out]ppError The error string 33 | * @param [ in]pParser The parser context 34 | * @param [ in]pCtx The synthesizer context 35 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_NO_ERRORS 36 | */ 37 | synth_err synthParser_getErrorString(char **ppError, synthParserCtx *pParser, 38 | synthCtx *pCtx); 39 | 40 | /** 41 | * Parse the currently loaded file into an audio 42 | * 43 | * This function uses a lexer to break the file into tokens, as it does 44 | * retrieve track, notes etc from the main synthesizer context 45 | * 46 | * Note: The context's lexer must have already been initialized; Therefore, 47 | * it's safer to simply use 'synthAudio_compile' (which calls this function), 48 | * or, at least, look at how that function is implemented 49 | * 50 | * @param [ in]pParser The parser context 51 | * @param [ in]pCtx The synthesizer context 52 | * @param [ in]pAudio A clean audio object, that will be filled with the 53 | * parsed song 54 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR, ... 55 | */ 56 | synth_err synthParser_getAudio(synthParserCtx *pParser, synthCtx *pCtx, 57 | synthAudio *pAudio); 58 | 59 | #endif /* __SYNTH_PARSER_H__ */ 60 | 61 | -------------------------------------------------------------------------------- /src/include/c_synth_internal/synth_volume.h: -------------------------------------------------------------------------------- 1 | /** 2 | * A 'volume' represents a function on the time domain, which controls each the 3 | * volume for each sample on a note; For simplicity (on the lib's side), 4 | * whenever a new volume is created it can never be deleted; However, if two 5 | * notes shares the same function, they will point to the same 'volume object' 6 | * 7 | * Volumes are stored in a simple array, since they are only searched and 8 | * created on compilation time; So, even if lots of volumes are used and they 9 | * slow down the compilation, there will be no side effects while rendering the 10 | * song 11 | * 12 | * @file src/include/synth_internal/synth_volume.h 13 | */ 14 | #ifndef __SYNTH_VOLUME_H__ 15 | #define __SYNTH_VOLUME_H__ 16 | 17 | #include 18 | 19 | #include 20 | 21 | /** 22 | * Retrieve a constant volume 23 | * 24 | * If the required volume isn't found, it will be instantiated and returned 25 | * 26 | * @param [out]pVol The index of the volume 27 | * @param [ in]pCtx The synthesizer context 28 | * @param [ in]amp The requested amplitude (in the range [0, 255]) 29 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 30 | */ 31 | synth_err synthVolume_getConst(int *pVol, synthCtx *pCtx, int amp); 32 | 33 | /** 34 | * Retrieve a linear volume 35 | * 36 | * If the required volume isn't found, it will be instantiated and returned 37 | * 38 | * @param [out]pVol The index of the volume 39 | * @param [ in]pCtx The synthesizer context 40 | * @param [ in]ini The initial amplitude (in the range [0, 255]) 41 | * @param [ in]fin The final amplitude (in the range [0, 255]) 42 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 43 | */ 44 | synth_err synthVolume_getLinear(int *pVol, synthCtx *pCtx, int ini, int fin); 45 | 46 | /** 47 | * Retrieve the volume at a given percentage of a note 48 | * 49 | * @param [out]pAmp The note's amplitude 50 | * @param [ in]pVol The volume 51 | * @param [ in]perc Percentage into the note (in the range [0, 1024)) 52 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 53 | */ 54 | synth_err synthVolume_getAmplitude(int *pAmp, synthVolume *pVol, int perc); 55 | 56 | #endif /* __SYNTH_VOLUME_H__ */ 57 | 58 | -------------------------------------------------------------------------------- /src/include/c_synth_internal/synth_track.h: -------------------------------------------------------------------------------- 1 | /** 2 | * A sequence of notes 3 | * 4 | * @file src/include/synth_internal/synth_track.h 5 | */ 6 | #ifndef __SYNTH_TRACK_H__ 7 | #define __SYNTH_TRACK_H__ 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | /** 15 | * Retrieve a new track (alloc it as possible and necessary) 16 | * 17 | * @param [out]ppTrack The new track 18 | * @param [ in]pCtx The synthesizer context 19 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 20 | */ 21 | synth_err synthTrack_init(synthTrack **ppTrack, synthCtx *pCtx); 22 | 23 | /** 24 | * Retrieve the number of samples in a track 25 | * 26 | * @param [out]pLen The length of the track in samples 27 | * @param [ in]pTrack The track 28 | * @param [ in]pCtx The synthesizer context 29 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 30 | */ 31 | synth_err synthTrack_getLength(int *pLen, synthTrack *pTrack, synthCtx *pCtx); 32 | 33 | /** 34 | * Retrieve the number of samples until a track's loop point 35 | * 36 | * @param [out]pLen The length of the track's intro 37 | * @param [ in]pTrack The track 38 | * @param [ in]pCtx The synthesizer context 39 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 40 | */ 41 | synth_err synthTrack_getIntroLength(int *pLen, synthTrack *pTrack, 42 | synthCtx *pCtx); 43 | 44 | /** 45 | * Retrieve whether a track is loopable or not 46 | * 47 | * @param [ in]pTrack The track 48 | * @return SYNTH_TRUE, SYNTH_FALSE 49 | */ 50 | synth_bool synthTrack_isLoopable(synthTrack *pTrack); 51 | 52 | /** 53 | * Render a track into a buffer 54 | * 55 | * The buffer must be prepared by the caller, and it must have 56 | * 'synth_getTrackLength' bytes times the number of bytes per samples 57 | * 58 | * @param [ in]pBuf Buffer that will be filled with the track 59 | * @param [ in]pTrack The track 60 | * @param [ in]pCtx The synthesizer context 61 | * @param [ in]mode Desired mode for the wave 62 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 63 | */ 64 | synth_err synthTrack_render(char *pBuf, synthTrack *pTrack, synthCtx *pCtx, 65 | synthBufMode mode); 66 | 67 | #endif /* __SYNTH_TRACK_H__ */ 68 | 69 | -------------------------------------------------------------------------------- /samples/ld34_bass_2.mml: -------------------------------------------------------------------------------- 1 | MML//=======================================// 2 | // Song written for 'MK-F417XX3' // 3 | // a game by GFM // 4 | // made for Ludum Dare #34 // 5 | //==========================================// 6 | 7 | t160 8 | 9 | //============================================ 10 | // Bass-line-2: 11 | // 25% square wave, 12 | // play notes for 60% of its duration 13 | //-------------------------------------------- 14 | 15 | w2 o3 l4 k5 q60 h65 v(30, 15) 16 | 17 | // -- 1 -> 8 ------------------------------- 18 | [r8 e2.. 19 | r8 g2.. 20 | r8 c2.. 21 | r8 d2..] 22 | 23 | l4 24 | // -- 9 -> 10 ------------------------------- 25 | r d2 d 26 | r d d d 27 | 28 | $ // -- Repeat on 10 ------------------------- 29 | 30 | l4 31 | // -- 11 -> 14 ------------------------------- 32 | r8 // All the following '^8' are extending the 33 | // previous note because of this 'desync' 34 | e e e e8 35 | ^8 g g g g8 36 | ^8 c c c c8 37 | ^8 d d d d8 38 | 39 | // -- 15 -> 18 ------------------------------- 40 | ^8 e e e e8 41 | ^8 g g g g8 42 | ^8 c c c c8 43 | ^8 d d d d8 44 | 45 | // -- 19 -> 22 ------------------------------- 46 | ^8 e e e e8 47 | ^8 g g g g8 48 | ^8 c c c c8 49 | ^8 d d d d8 // No extension to sync back with 50 | // bass-line-1 51 | 52 | // Go to a higher pitch (and tone) 53 | l1 54 | // -- 23 -> 26 ------------------------------- 55 | [e < 56 | g 57 | c 58 | d >] 59 | 60 | // -- 27 -> 30 ------------------------------- 61 | f < 62 | g+ 63 | c+ 64 | d+ > 65 | 66 | // -- 31 -> 34 ------------------------------- 67 | f+ < 68 | a 69 | d 70 | e > 71 | 72 | // First part in a higher tone 73 | 74 | l4 75 | // -- 35 -> 38 ------------------------------- 76 | r8 // All the following '^8' are extending the 77 | // previous note because of this 'desync' 78 | f+ f+ f+ f+8 79 | ^8 a a a a8 80 | ^8 d d d d8 81 | ^8 e e e e8 82 | 83 | // -- 39 -> 42 ------------------------------- 84 | ^8 f+ f+ f+ f+8 85 | ^8 a a a a8 86 | ^8 d d d d8 87 | ^8 e e e e8 88 | 89 | // -- 43 -> 46 ------------------------------- 90 | ^8 f+ f+ f+ f+8 91 | ^8 a a a a8 92 | ^8 d d d d8 93 | ^8 e e e e8 // No extension to sync back with 94 | // bass-line-1 95 | 96 | // Go back to the original pitch 97 | 98 | l1 99 | // -- 47 -> 50 ------------------------------- 100 | f < 101 | g+ 102 | c+ 103 | d+ > 104 | 105 | // -- 51 -> 54 ------------------------------- 106 | e < 107 | g 108 | c 109 | d > 110 | 111 | //============================================ 112 | 113 | -------------------------------------------------------------------------------- /samples/ld34_melody.mml: -------------------------------------------------------------------------------- 1 | MML//=======================================// 2 | // Song written for 'MK-F417XX3' // 3 | // a game by GFM // 4 | // made for Ludum Dare #34 // 5 | //==========================================// 6 | 7 | t160 8 | 9 | //============================================ 10 | // Melody-line-1: 11 | // 50% square wave, 12 | // play notes for 75% of its duration 13 | //-------------------------------------------- 14 | 15 | w0 k3 q70 h80 o5 l8 v(40, 10) 16 | 17 | // -- 1 -> 8 ------------------------------- 18 | [r4 e2. 19 | r4 g2. 20 | r4 > a2. < 21 | r4 > b2. <] 22 | 23 | l4 24 | // -- 9 -> 10 ------------------------------- 25 | r > b2 b < 26 | r > b b b < 27 | 28 | $ // -- Repeat on 10 ------------------------- 29 | 30 | l8 31 | // -- 11 -> 14 ------------------------------- 32 | e c > a < e c > a < e a 33 | g e c g e c g c 34 | c > a f < c > a f < c > f < 35 | d > b g < d > b g < d > g < 36 | 37 | // -- 15 -> 16 ------------------------------- 38 | e e e e e c > a < e 39 | g g g e e e d e 40 | 41 | // -- 17 -> 18 ------------------------------- 42 | c4. c4. c > g < 43 | d1 44 | 45 | l2 46 | // -- 19 -> 22 ------------------------------- 47 | e c 48 | g e 49 | > a f 50 | b g < 51 | 52 | // Go to a higher pitch (and tone) 53 | l8 54 | // -- 23 -> 26 ------------------------------- 55 | e c > a < e c > a < e < a 56 | < c1 > 57 | < d1 > 58 | < c2 > a r4. > 59 | 60 | // -- 27 -> 30 ------------------------------- 61 | e4. > g4. < c4 62 | g2 d2 63 | a1 64 | < c4. > b4. a4 65 | 66 | // -- 31 -> 34 ------------------------------- 67 | f c+ > a+ < f c+ > a+ < f < a+ 68 | < c+1 > 69 | < d+1 > 70 | < c+2 > a+ r4. > 71 | 72 | // -- 35 -> 38 ------------------------------- 73 | f+4. > a4. < d4 74 | a2 e2 75 | b1 76 | < d4. c+4. > b4 77 | 78 | // First part in a higher tone 79 | 80 | // -- 39 -> 42 ------------------------------- 81 | f+ f+ f+ f+ f+ d > b < f+ 82 | a a a f+ f+ f+ e f+ 83 | d4. d4. d > a < 84 | e1 85 | 86 | // -- 43 -> 46 ------------------------------- 87 | f+ d > b < f+ d > b < f+ b 88 | a f+ d a f+ d a d 89 | d > b g < d > b g < d > g < 90 | e > > c+ < a < e > > c+ < a < e > a < 91 | 92 | // -- 47 -> 50 ------------------------------- 93 | l2 94 | f+ d 95 | a f+ 96 | > b g 97 | > c+ < a < 98 | 99 | // Go back to the original pitch 100 | l8 101 | // -- 51 -> 54 ------------------------------- 102 | f c+ > a+ < f c+ > a+ < f < a+ 103 | < c+1 > 104 | < d+1 > 105 | < c+2 > a+ r4. > 106 | 107 | // -- 55 -> 58 ------------------------------- 108 | e4. > g4. < c4 109 | g2 d2 110 | a1 111 | < c4. > b4. a4 112 | 113 | //============================================ 114 | 115 | -------------------------------------------------------------------------------- /samples/jjat-boss.mml: -------------------------------------------------------------------------------- 1 | MML//=========================================================================// 2 | // Song originally written for 'JJAT' // 3 | // a game by GFM (made for BIG Festival 2015) // 4 | // Original version: // 5 | // https://github.com/SirGFM/big-15/blob/v1.0.0/assets/vmml/boss-battle.mml // 6 | //============================================================================// 7 | 8 | t180 9 | 10 | //============================================================================== 11 | // Bass-line-1: 25% square wave, play notes for 60% of its duration 12 | //------------------------------------------------------------------------------ 13 | 14 | w2 o3 l4 k5 q60 h65 v(35, 20) 15 | 16 | > 17 | 18 | a+ a+ a+ a+ $ 19 | [a+ a+ a+ a+ 20 | g+ g+ g+ g+ 21 | f+ f+ f+ f+ 22 | g+ g+ g+ g+] 23 | [a+ a+ a+ a+8 a+8 24 | g+ g+ g+ g+8 g+8 25 | f+ f+ f+ f+8 f+8 26 | g+ g+ g+ g+8 g+8] 27 | 28 | ; //============================================================================ 29 | 30 | //============================================================================== 31 | // Bass-line-2: 25% square wave, play notes for 60% of its duration 32 | //------------------------------------------------------------------------------ 33 | 34 | w2 o3 l4 k5 q60 h65 v(30, 15) 35 | 36 | f f f f $ 37 | [f f f f 38 | d+ d+ d+ d+ 39 | c+ c+ c+ c+ 40 | d+ d+ d+ d+] 41 | [f f f f8 f8 42 | d+ d+ d+ d+8 d+8 43 | c+ c+ c+ c+8 c+8 44 | d+ d+ d+ d+8 d+8] 45 | 46 | ; //============================================================================ 47 | 48 | //============================================================================== 49 | // Melody-line-1: 50% square wave, play notes for 75% of its duration 50 | //------------------------------------------------------------------------------ 51 | 52 | w0 k3 q70 h80 o5 l4 v(40, 10) 53 | 54 | r1 $ 55 | 56 | l4 57 | a+1 58 | < c1 59 | c+1 60 | d+2. c+ 61 | > 62 | 63 | a+1 64 | < c1 65 | c+1 66 | d+2. c+ 67 | > 68 | 69 | l8 70 | a+ f g+ a+ f g+ a+ f 71 | g+ a+ g+ a+ g+ 72 | g+ g+ g+ 73 | a+ a+ 74 | 75 | a+ f g+ a+ f g+ a+ f 76 | g+ a+ g+ a+ g+ 77 | g+ g+ g+ 78 | 10 | 11 | #include 12 | 13 | /** 14 | * Initialize the lexer, reading tokens from a SDL_RWops 15 | * 16 | * If the lexer has already been initialized, it will be reset and 17 | * re-initialized with this new source 18 | * 19 | * @param [ in]pCtx The lexer context, to be initialized 20 | * @param [ in]pFile The file 21 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_OPEN_FILE_ERR 22 | */ 23 | synth_err synthLexer_initFromSDL_RWops(synthLexCtx *pCtx, void *pFile); 24 | 25 | /** 26 | * Initialize the lexer, reading tokens from a file 27 | * 28 | * If the lexer has already been initialized, it will be reset and 29 | * re-initialized with this new source 30 | * 31 | * @param [ in]pCtx The lexer context, to be initialized 32 | * @param [ in]pFilename The file 33 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_OPEN_FILE_ERR 34 | */ 35 | synth_err synthLexer_initFromFile(synthLexCtx *pCtx, char *pFilename); 36 | 37 | /** 38 | * Initialize the lexer, reading tokens from a string 39 | * 40 | * If the lexer has already been initialized, it will be reset and 41 | * re-initialized with this new source 42 | * 43 | * @param [ in]pCtx The lexer context, to be initialized 44 | * @param [ in]pString The string 45 | * @param [ in]len The string's length 46 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 47 | */ 48 | synth_err synthLexer_initFromString(synthLexCtx *pCtx, char *pString, int len); 49 | 50 | /** 51 | * Clear a lexer and all of its resources 52 | * 53 | * This functions only needs really to be called when using a file as input 54 | * source, since, otherwise, everything is kept in RAM; 55 | * 56 | * @param [ in]pCtx The lexer context, to be initialized 57 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 58 | */ 59 | synth_err synthLexer_clear(synthLexCtx *pCtx); 60 | 61 | /** 62 | * Get the current line number 63 | * 64 | * @param [out]pVal The current line 65 | * @param [ in]pCtx The contex 66 | * return SYNTH_OK, SYNTH_BAD_PARAM_ERR 67 | */ 68 | synth_err synthLexer_getCurrentLine(int *pVal, synthLexCtx *pCtx); 69 | 70 | /** 71 | * Get the current position inside the line 72 | * 73 | * @param [out]pVal The the current position 74 | * @param [ in]pCtx The contex 75 | * return SYNTH_OK, SYNTH_BAD_PARAM_ERR 76 | */ 77 | synth_err synthLexer_getCurrentLinePosition(int *pVal, synthLexCtx *pCtx); 78 | 79 | /** 80 | * Get the last chracter read (that probably triggered an error) 81 | * 82 | * @param [out]pVal The last character 83 | * @param [ in]pCtx The contex 84 | * return SYNTH_OK, SYNTH_BAD_PARAM_ERR 85 | */ 86 | synth_err synthLexer_getLastCharacter(char *pVal, synthLexCtx *pCtx); 87 | 88 | /** 89 | * Get the token read on the previous getToken call 90 | * 91 | * @param [out]pToken The last read token 92 | * @param [ in]pCtx The context 93 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 94 | */ 95 | synth_err synthLexer_lookupToken(synth_token *pToken, synthLexCtx *pCtx); 96 | 97 | /** 98 | * Get the last read integer value 99 | * 100 | * @param [out]pVal The read integer 101 | * @param [ in]pCtx The context 102 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 103 | */ 104 | synth_err synthLexer_getValuei(int *pVal, synthLexCtx *pCtx); 105 | 106 | /** 107 | * Returns a printable string for a given token 108 | * 109 | * That that even if the token is invalid, the function returns OK; In that 110 | * case, though, the returned string says "unknown token" 111 | * 112 | * @param [out]ppStr The null-terminated, static string 113 | * @param [ in]token The token 114 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 115 | */ 116 | synth_err synthLexer_printToken(char **ppStr, synth_token token); 117 | 118 | /** 119 | * Get the next token on the context and its value (if any) 120 | * 121 | * @param [ in]pCtx The context 122 | * return SYNTH_OK, SYNTH_INVALID_TOKEN 123 | */ 124 | synth_err synthLexer_getToken(synthLexCtx *pCtx); 125 | 126 | #endif /* __SYNTH_LEXER_H__ */ 127 | 128 | -------------------------------------------------------------------------------- /src/include/c_synth_internal/synth_audio.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file src/include/c_synth_internal/synth_audio.h 3 | */ 4 | #ifndef __SYNTH_INTERNAL_AUDIO_H__ 5 | #define __SYNTH_INTERNAL_AUDIO_H__ 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | /** 13 | * Initialize a new audio, so a song can be compiled into it 14 | * 15 | * @param [out]pAudio Object that will be filled with the compiled song 16 | * @param [ in]pCtx The synthesizer context 17 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 18 | */ 19 | synth_err synthAudio_init(synthAudio **ppAudio, synthCtx *pCtx); 20 | 21 | /** 22 | * Compile a MML audio SDL_RWops into an object 23 | * 24 | * @param [ in]pAudio Object that will be filled with the compiled song 25 | * @param [ in]pCtx The synthesizer context 26 | * @param [ in]pFile File with the song's MML 27 | */ 28 | synth_err synthAudio_compileSDL_RWops(synthAudio *pAudio, synthCtx *pCtx, 29 | void *pFile); 30 | 31 | /** 32 | * Compile a MML audio file into an object 33 | * 34 | * @param [ in]pAudio Object that will be filled with the compiled song 35 | * @param [ in]pCtx The synthesizer context 36 | * @param [ in]pFilename File with the song's MML 37 | */ 38 | synth_err synthAudio_compileFile(synthAudio *pAudio, synthCtx *pCtx, 39 | char *pFilename); 40 | 41 | /** 42 | * Compile a MML audio string into a object 43 | * 44 | * @param [ in]pAudio Object that will be filled with the compiled song 45 | * @param [ in]pCtx The synthesizer context 46 | * @param [ in]pString The MML song 47 | * @param [ in]len The MML song's length 48 | */ 49 | synth_err synthAudio_compileString(synthAudio *pAudio, synthCtx *pCtx, 50 | char *pString, int len); 51 | 52 | /** 53 | * Return the audio BPM 54 | * 55 | * @param [out]pBpm The BPM 56 | * @param [ in]pAudio The audio 57 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 58 | */ 59 | synth_err synthAudio_getBpm(int *pBpm, synthAudio *pAudio); 60 | 61 | /** 62 | * Return the audio time signature 63 | * 64 | * @param [out]pTime The time signature 65 | * @param [ in]pAudio The audio 66 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 67 | */ 68 | synth_err synthAudio_getTimeSignature(int *pTime, synthAudio *pAudio); 69 | 70 | /** 71 | * Return the number of tracks in a song 72 | * 73 | * @param [out]pNum The number of tracks 74 | * @param [ in]pAudio The audio 75 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 76 | */ 77 | synth_err synthAudio_getTrackCount(int *pNum, synthAudio *pAudio); 78 | 79 | /** 80 | * Retrieve the number of samples in a track 81 | * 82 | * @param [out]pLen The length of the track in samples 83 | * @param [ in]pAudio The audio 84 | * @param [ in]pCtx The synthesizer context 85 | * @param [ in]track Track index 86 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_INVALID_INDEX 87 | */ 88 | synth_err synthAudio_getTrackLength(int *pLen, synthAudio *pAudio, 89 | synthCtx *pCtx, int track); 90 | 91 | /** 92 | * Retrieve the number of samples until a track's loop point 93 | * 94 | * @param [out]pLen The length of the track's intro 95 | * @param [ in]pAudio The audio 96 | * @param [ in]pCtx The synthesizer context 97 | * @param [ in]track The track 98 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 99 | */ 100 | synth_err synthAudio_getTrackIntroLength(int *pLen, synthAudio *pAudio, 101 | synthCtx *pCtx, int track); 102 | 103 | /** 104 | * Retrieve whether a track is loopable or not 105 | * 106 | * @param [ in]pAudio The audio 107 | * @param [ in]pCtx The synthesizer context 108 | * @param [ in]track The track 109 | * @return SYNTH_TRUE, SYNTH_FALSE 110 | */ 111 | synth_bool synthAudio_isTrackLoopable(synthAudio *pAudio, synthCtx *pCtx, 112 | int track); 113 | 114 | /** 115 | * Render a track into a buffer 116 | * 117 | * The buffer must be prepared by the caller, and it must have 118 | * 'synth_getTrackLength' bytes times the number of bytes per samples 119 | * 120 | * @param [ in]pBuf Buffer that will be filled with the track 121 | * @param [ in]pAudio The audio 122 | * @param [ in]pCtx The synthesizer context 123 | * @param [ in]pTrack The track 124 | * @param [ in]mode Desired mode for the wave 125 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 126 | */ 127 | synth_err synthAudio_renderTrack(char *pBuf, synthAudio *pAudio, synthCtx *pCtx, 128 | int track, synthBufMode mode); 129 | 130 | #endif /* __SYNTH_INTERNAL_AUDIO_H__ */ 131 | 132 | -------------------------------------------------------------------------------- /src/synth_renderer.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file src/synth_renderer.c 3 | */ 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /** 13 | * Initialize the renderer for a given audio 14 | * 15 | * @param [ in]pCtx The renderer context 16 | * @param [ in]pAudio The audio to be rendered 17 | * @param [ in]frequency The synth frequency in samples per second 18 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 19 | */ 20 | synth_err synthRenderer_init(synthRendererCtx *pCtx, synthAudio *pAudio, 21 | int frequency) { 22 | synth_err rv; 23 | int bpm, timeSign; 24 | 25 | /* Sanitize the arguments */ 26 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 27 | SYNTH_ASSERT_ERR(pAudio, SYNTH_BAD_PARAM_ERR); 28 | 29 | /* Get the audio BPM */ 30 | rv = synthAudio_getBpm(&bpm, pAudio); 31 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 32 | /* Get the time signature */ 33 | rv = synthAudio_getTimeSignature(&timeSign, pAudio); 34 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 35 | 36 | /* TODO Accept various time signatures */ 37 | 38 | /* Calculate the duration (in samples) of a complete compass (considering a 39 | * 4/4 time signature): 40 | * 41 | * 4 beats / N beats per min = 4 beats / (N / 60) beats per second 42 | * 4 * 60 / N = T s (duration of semibreve in seconds) 43 | * T s * F Hz = number of samples in a semibreve */ 44 | pCtx->samplesPerCompass = frequency * 240 / bpm; 45 | /* Store the time signature */ 46 | pCtx->timeSignature = timeSign; 47 | 48 | /* Make sure to reset the current length */ 49 | rv = synthRenderer_resetPosition(pCtx); 50 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 51 | 52 | rv = SYNTH_OK; 53 | __err: 54 | return rv; 55 | } 56 | 57 | /** 58 | * Returns the position back to the start but don't modify anything related to 59 | * audio 60 | * 61 | * @param [ in]pCtx The renderer context 62 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 63 | */ 64 | synth_err synthRenderer_resetPosition(synthRendererCtx *pCtx) { 65 | synth_err rv; 66 | 67 | /* Sanitize the arguments */ 68 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 69 | 70 | /* Reset the current length */ 71 | pCtx->curCompassLength = 0; 72 | pCtx->curCompassPosition = 0; 73 | 74 | rv = SYNTH_OK; 75 | __err: 76 | return rv; 77 | } 78 | 79 | /** 80 | * Return a note's length in samples, considering its position within the 81 | * compass. This function also update the renderer internal state (i.e., the 82 | * position within the compass) 83 | * 84 | * @param [out]pLen The note's length in samples 85 | * @param [ in]pCtx The renderer context 86 | * @param [ in]pNote The note 87 | */ 88 | synth_err synthRenderer_getNoteLengthAndUpdate(int *pLen, 89 | synthRendererCtx *pCtx, synthNote *pNote) { 90 | int bit, duration; 91 | synth_err rv; 92 | 93 | /* Sanitize the arguments */ 94 | SYNTH_ASSERT_ERR(pLen, SYNTH_BAD_PARAM_ERR); 95 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 96 | SYNTH_ASSERT_ERR(pNote, SYNTH_BAD_PARAM_ERR); 97 | 98 | /* Retrieve the note's duration in binary fixed point notation */ 99 | rv = synthNote_getDuration(&duration, pNote); 100 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 101 | 102 | /* TODO Accept various time signatures */ 103 | 104 | /* Update the current position within the compass */ 105 | pCtx->curCompassPosition += duration; 106 | /* This should be guaranteed else where, but... */ 107 | SYNTH_ASSERT_ERR(pCtx->curCompassPosition <= pCtx->timeSignature, 108 | SYNTH_COMPASS_OVERFLOW); 109 | 110 | if (pCtx->curCompassPosition == pCtx->timeSignature) { 111 | /* If this is the last beat on a compass, use all remaining samples */ 112 | *pLen = pCtx->samplesPerCompass - pCtx->curCompassLength; 113 | 114 | /* Reset the current compass */ 115 | rv = synthRenderer_resetPosition(pCtx); 116 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 117 | } 118 | else { 119 | int len; 120 | 121 | /* Otherwise, calculate the beat's length */ 122 | bit = 6; 123 | len = pCtx->samplesPerCompass; 124 | *pLen = 0; 125 | while (bit >= 0) { 126 | if (duration & (1 << bit)) { 127 | *pLen += len; 128 | } 129 | 130 | bit--; 131 | len >>= 1; 132 | } 133 | 134 | /* Update the position within the compass, in samples */ 135 | pCtx->curCompassLength += *pLen; 136 | } 137 | 138 | rv = SYNTH_OK; 139 | __err: 140 | return rv; 141 | } 142 | 143 | -------------------------------------------------------------------------------- /tst/tst_compileSong.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple test to compile a song either from the command line or from a file 3 | * 4 | * If no song is passed, it will use a default, static, songs 5 | * 6 | * @file tst/tst_compileSong.c 7 | */ 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | /* Simple test song */ 16 | static char __song[] = "MML t90 l16 o5 e e8 e r c e r g4 > g4 <"; 17 | 18 | /** 19 | * Entry point 20 | * 21 | * @param [ in]argc Number of arguments 22 | * @param [ in]argv List of arguments 23 | * @return The exit code 24 | */ 25 | int main(int argc, char *argv[]) { 26 | char *pSrc; 27 | int freq, handle, isFile, len; 28 | synthCtx *pCtx; 29 | synth_err rv; 30 | 31 | /* Clean the context, so it's not freed on error */ 32 | pCtx = 0; 33 | 34 | /* Store the default frequency */ 35 | freq = 44100; 36 | isFile = 0; 37 | pSrc = 0; 38 | len = 0; 39 | /* TODO Check argc/argv */ 40 | if (argc > 1) { 41 | int i; 42 | 43 | i = 1; 44 | while (i < argc) { 45 | #define IS_PARAM(l_cmd, s_cmd) \ 46 | if (strcmp(argv[i], l_cmd) == 0 || strcmp(argv[i], s_cmd) == 0) 47 | IS_PARAM("--string", "-s") { 48 | if (argc <= i + 1) { 49 | printf("Expected parameter but got nothing! Run " 50 | "'tst_compileSong --help' for usage!\n"); 51 | return 1; 52 | } 53 | 54 | /* Store the string and retrieve its length */ 55 | pSrc = argv[i + 1]; 56 | isFile = 0; 57 | len = strlen(argv[i + 1]); 58 | } 59 | IS_PARAM("--file", "-f") { 60 | if (argc <= i + 1) { 61 | printf("Expected parameter but got nothing! Run " 62 | "'tst_compileSong --help' for usage!\n"); 63 | return 1; 64 | } 65 | 66 | /* Store the filename */ 67 | pSrc = argv[i + 1]; 68 | isFile = 1; 69 | } 70 | IS_PARAM("--help", "-h") { 71 | printf("A simple test for the c_synth library\n" 72 | "\n" 73 | "Usage: tst_compileSong [--string | -s \"the song\"] " 74 | "[--file | -f ]\n" 75 | " [--help | -h]\n" 76 | "\n" 77 | "Only one song can be compiled at a time, and this " 78 | "program simply checks if it \n" 79 | "compiles successfully or not (no output is " 80 | "generated).\n" 81 | "On error, however, this program does display the " 82 | "cause and position of the \n" 83 | "error.\n" 84 | "\n" 85 | "If no argument is passed, it will compile a simple " 86 | "test song.\n"); 87 | 88 | return 0; 89 | } 90 | 91 | i += 2; 92 | #undef IS_PARAM 93 | } 94 | } 95 | 96 | /* Initialize it */ 97 | printf("Initialize the synthesizer...\n"); 98 | rv = synth_init(&pCtx, freq); 99 | SYNTH_ASSERT(rv == SYNTH_OK); 100 | 101 | /* Compile a song */ 102 | if (pSrc != 0) { 103 | if (isFile) { 104 | printf("Compiling song from file '%s'...\n", pSrc); 105 | rv = synth_compileSongFromFile(&handle, pCtx, pSrc); 106 | } 107 | else { 108 | printf("Compiling song '%s'...\n", pSrc); 109 | rv = synth_compileSongFromString(&handle, pCtx, pSrc, len); 110 | } 111 | } 112 | else { 113 | printf("Compiling static song '%s'...\n", __song); 114 | rv = synth_compileSongFromStringStatic(&handle, pCtx, __song); 115 | } 116 | 117 | if (rv != SYNTH_OK) { 118 | char *pError; 119 | synth_err irv; 120 | 121 | /* Retrieve and print the error */ 122 | irv = synth_getCompilerErrorString(&pError, pCtx); 123 | SYNTH_ASSERT_ERR(irv == SYNTH_OK, irv); 124 | 125 | printf("%s", pError); 126 | } 127 | else { 128 | printf("Song compiled successfully!\n"); 129 | } 130 | SYNTH_ASSERT(rv == SYNTH_OK); 131 | 132 | rv = SYNTH_OK; 133 | __err: 134 | if (rv != SYNTH_OK) { 135 | printf("An error happened!\n"); 136 | } 137 | 138 | if (pCtx) { 139 | printf("Releasing resources used by the lib...\n"); 140 | synth_free(&pCtx); 141 | } 142 | 143 | printf("Exiting...\n"); 144 | return rv; 145 | } 146 | 147 | -------------------------------------------------------------------------------- /samples/sonic2-chemical.mml: -------------------------------------------------------------------------------- 1 | MML//=========================================================================// 2 | // Sonic 2 - Chemical Plant Zone // 3 | // Original song: Masato Nakamura // 4 | // Source (last checked 2015/11/07) // 5 | // http://www.reddit.com/r/gamedev/comments/1f9l62/visual_mml_a_text_music_editor/ca98mgr 6 | //============================================================================// 7 | 8 | t140 9 | 10 | w6 l16 q12 v20 o7 k3 q12 h80 // Hi-hat 11 | $ g r g g g r g g g r g g g r g g 12 | g r g g g r g g g r g g g g g g; 13 | 14 | l16 v20 k3 q20 // Kick(w10o3h80c) + snare(w8o6h0d) 15 | [[w10o3h80c r r r w8o6h0d r w10o3h80c r r w10o3h80c r r w8o6h0d r w10o3h80c r]3 16 | w10o3h80c r r r w8o6h0d r w10o3h80c r r w10o3h80c w8o6h0d r w8o6h0d r w8o6h0d w8o6h0d]2 17 | $ 18 | [[w10o3h80c r r r w8o6h0d r w10o3h80c r r w10o3h80c r r w8o6h0d r w10o3h80c r]3 19 | w10o3h80c r r r w8o6h0d r w10o3h80c r r w10o3h80c w8o6h0d r w8o6h0d r w8o6h0d w8o6h0d]3 20 | [w10o3h80c r r r w8o6h0d r w10o3h80c r r w10o3h80c r r w8o6h0d r w10o3h80c r]3 21 | w10o3h80c r r r w8o6h0d r w10o3h80c r w8o6h0d w8o6h0d w8o6h0d w8o6h0d w8o6h0d w8o6h0d w8o6h0d w8o6h0d 22 | [[w10o3h80c r r r w8o6h0d r w10o3h80c r r w10o3h80c r r w8o6h0d r w10o3h80c r]3 23 | w10o3h80c r r r w8o6h0d r w10o3h80c r r w10o3h80c w8o6h0d r w8o6h0d r w8o6h0d w8o6h0d]2 24 | [w10o3h80c r r r w8o6h0d r w10o3h80c r r w10o3h80c r r w8o6h0d r w10o3h80c r]3 25 | w10o3h80c r r r w8o6h0d r w10o3h80c r w8o6h0d w8o6h0d w8o6h0d w8o6h0d w8o6h0d w8o6h0d w8o6h0d w8o6h0d; 26 | 27 | w3 l16 v16 o5 // Main chord 1 28 | [f+ r r r f+8. f+ r4. f+ f+ 29 | r r f+ f+ r r f+ q100 g+2 q75 r 30 | f+ r r r f+8. f+ r2 31 | r1] 32 | $ 33 | v32 34 | [f+ r r r f+8. f+ r4. f+ f+ 35 | r r f+ f+ r r f+ q100 g+2 q75 r 36 | f+ r r r f+8. f+ r2 37 | r1]4 38 | [d4 d4 e4 r4 39 | r1]4 40 | v64 41 | [a8 g+8 a8 g+ a8 a g+8 a8 g+8]4; 42 | 43 | w3 l16 v18 o5 // Main chord 2 44 | [a r r r a8. a r4. a a 45 | r r a a r r a q100 b2 q75 r 46 | a r r r a8. a r2 47 | r1] 48 | $ 49 | v64 50 | [a r r r a8. a r4. a a 51 | r r a a r r a q100 b2 q75 r 52 | a r r r a8. a r2 53 | r1]4 54 | [g+4 g+4 a4 r4 55 | r1]4 56 | r1 57 | [c+8 b8 c+8 b c+8 c+ b8 c+8 b8]3; 58 | 59 | w3 l16v10o7 // Main chord 3 60 | [a r r r a8. a r4. a a 61 | r r a a r r a q100 b2 q75 r 62 | a r r r a8. a r2 63 | r1] 64 | $ 65 | v8 66 | [a r r r a8. a r4. a a 67 | r r a a r r a q100 b2 q75 r 68 | a r r r a8. a r2 69 | r1]4 70 | [g+4 g+4 a4 r4 71 | r1]4 72 | r1 73 | r1 74 | v64 75 | >[f+8 e8 f+8 e f+8 f+ e8 f+8 e8]; 76 | 77 | w3 l16v16o7 // Main chord 4 78 | [r1]8 79 | $ 80 | [r1]27 81 | c+8 b8 c+8 b c+8 c+ b8 c+8 b8; 82 | 83 | w2 l16v48o4q75 // Twiddles 84 | [r2 r r f+ c+ e f+ r r 85 | r1]3 86 | r2 r r f+ c+ e f+ r r 87 | r2 < a8 b > r r 88 | $ 89 | [r2 r r f+ c+ e f+ r r 90 | r1 91 | r2 r r f+ c+ e f+ r r 92 | r2 < a8 b > r r] 93 | [r1 94 | r8 a r a r a r a r a r a4 95 | // TODO fix following line 96 | //r8 ) a r( a r( a r( a r( a r( a4 )))) 97 | f+ r2... 98 | // TODO fix following line 99 | q87 100 | r r4.. r r4.. 101 | // e*g+4.. d+*f+4.. 102 | q75] 103 | [r1]12; 104 | 105 | w4 l8v36o3q75 // Distorted bass 106 | [r1]3 107 | f+16 d+4.. e e e+ e+ 108 | f+ f+4 f+ f+ f+4 f+ 109 | f+ f+4 f+ f+16 d+8. e e+ 110 | f+ f+4 f+ f+ f+4 f+ 111 | e e4 e d+ d+4 d+ 112 | $ 113 | [f+ f+4 f+ f+ f+4 f+ 114 | f+ f+4 f+ f+16 d+8. e e+ 115 | f+ f+4 f+ f+ f+4 f+ 116 | e e4 e d+ d+4 d+]4 117 | e4 e+4 f+4 r4 118 | r1 119 | e4 e+4 f+4 r4 120 | r2. e d+ 121 | e4 e+4 f+4 r4 122 | r1 123 | e4 e+4 f+4 r4 124 | f+8 d+4. e4 e+4 125 | f+1 126 | [r1] 127 | r2 a16 a16 g+16 g16 ; 128 | 129 | w4 l8o3q75 // Clean bass 130 | [r1]8 131 | $ 132 | v32 133 | [[f+ f+ f+16 ]3 134 | e d+ d+16 ]4 135 | v80 136 | e4 f4 f+4 r4 137 | f+ f+ f+16 138 | e4 f4 f+4 r4 139 | f+ f+ f+16 140 | e4 f4 f+4 r4 141 | f+ f+ f+16 142 | e4 f4 f+4 r4 143 | [r1]4 144 | r2 a16 a16 g+16 g16 ; 145 | 146 | w2 l16v48o5 // Melody 147 | [r1]7 148 | r2. r8 f+ a 149 | $ 150 | b a b a8 a r4. f+ a 151 | b a b a8 b r a r b r 152 | a f+8 f+8 r8. r2 153 | r2. r8 f+ a 154 | b a b a8 a r4. f+ a 155 | b a b a8 b r a r b r 156 | a f+8 f+8 r8. r2 157 | r1 158 | [r8 b r b8 a b r b b a b8 a8 159 | a8 f+ r2. 160 | r8 b r b8 a b r b b a b8 a8 161 | r2..] 162 | r2. r c+ e f+ 163 | a8 f+ c+ e f+ r a8. f+ c+ e f+ r r 164 | r2. r c+ e f+ 165 | a8 f+ c+ e f+ r b8 a b r4 166 | r2. r c+ e f+ 167 | a8 f+ c+ e f+ r a8. f+ c+ e f+ r r 168 | r2. r c+ e f+ 169 | // TODO fix following lines 170 | // <- slide -> 171 | // |-----------------|--------| 172 | a8 b8 174 | // |2 ---------------------|--------------|---------| 175 | c+ c+ >a r f+ a8 f+16^16^8^4 f2 f+16^8^4^2 f+ a 176 | -------------------------------------------------------------------------------- /src/synth_prng.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Pseudo-random number generator. There are also other PRNG-based 3 | * functionalities, like a gaussian white noise generator. 4 | * 5 | * @file src/include/synth_internal/synth_prng.h 6 | */ 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define TAU 2.0 * 3.1415926535897 19 | 20 | /** 21 | * Advance the internal context to the next pseudo-random number 22 | * 23 | * @param [ in]pCtx The context 24 | */ 25 | static void synthPRNG_iterate(synthPRNGCtx *pCtx) { 26 | pCtx->seed = pCtx->a * pCtx->seed + pCtx->c; 27 | } 28 | 29 | /** 30 | * (Re)Initialize the Pseudo-random number generator with the desired seed 31 | * 32 | * @param [ in]pCtx The context 33 | * @param [out]seed The desired seed 34 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 35 | */ 36 | synth_err synthPRNG_init(synthPRNGCtx *pCtx, unsigned int seed) { 37 | synth_err rv; 38 | 39 | /* Sanitize the arguments */ 40 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 41 | 42 | /* Set the parameters */ 43 | pCtx->a = 0x0019660d; 44 | pCtx->c = 0x3c6ef35f; 45 | pCtx->seed = seed; 46 | 47 | /* Set the noise type to Box-Muller */ 48 | pCtx->type = NW_BOXMULLER; 49 | 50 | /* Advance the internal state because... why not? */ 51 | synthPRNG_iterate(pCtx); 52 | 53 | pCtx->isInit = 1; 54 | 55 | rv = SYNTH_OK; 56 | __err: 57 | return rv; 58 | } 59 | 60 | /** 61 | * Retrieve a (pseudo) random value in the range [0, 0xFFFFFFFF] 62 | * 63 | * @param [out]pVal The generated value 64 | * @param [ in]pCtx The context 65 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 66 | */ 67 | synth_err synthPRNG_getUint(unsigned int *pVal, synthPRNGCtx *pCtx) { 68 | synth_err rv; 69 | 70 | /* Sanitize the arguments */ 71 | SYNTH_ASSERT_ERR(pVal, SYNTH_BAD_PARAM_ERR); 72 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 73 | 74 | /* If it isn't initialized, initialize with the time (a bad seed, but 75 | * still...) */ 76 | if (!pCtx->isInit) { 77 | rv = synthPRNG_init(pCtx, (unsigned int)time(0)); 78 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 79 | } 80 | 81 | /* Retrieve the random number */ 82 | *pVal = pCtx->seed; 83 | 84 | /* Update the internal state */ 85 | synthPRNG_iterate(pCtx); 86 | 87 | rv = SYNTH_OK; 88 | __err: 89 | return rv; 90 | } 91 | 92 | /** 93 | * Retrieve a (pseudo) random value in the range [0.0, 1.0] 94 | * 95 | * @param [out]pVal The generated value 96 | * @param [ in]pCtx The context 97 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 98 | */ 99 | synth_err synthPRNG_getDouble(double *pVal, synthPRNGCtx *pCtx) { 100 | synth_err rv; 101 | 102 | /* Sanitize the arguments */ 103 | SYNTH_ASSERT_ERR(pVal, SYNTH_BAD_PARAM_ERR); 104 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 105 | 106 | /* If it isn't initialized, initialize with the time (a bad seed, but 107 | * still...) */ 108 | if (!pCtx->isInit) { 109 | rv = synthPRNG_init(pCtx, (unsigned int)time(0)); 110 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 111 | } 112 | 113 | /* Retrieve the random number */ 114 | *pVal = (double)pCtx->seed / (double)(0xffffffff); 115 | 116 | /* Update the internal state */ 117 | synthPRNG_iterate(pCtx); 118 | 119 | rv = SYNTH_OK; 120 | __err: 121 | return rv; 122 | } 123 | 124 | /** 125 | * Generate points for a gaussian white noise 126 | * 127 | * @param [out]pVal The generated noise, in range [-1.0, 1.0] 128 | * @param [ in]pCtx The contx 129 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 130 | */ 131 | synth_err synthPRNG_getGaussianNoise(double *pVal, synthPRNGCtx *pCtx) { 132 | synth_err rv; 133 | 134 | /* Sanitize the arguments */ 135 | SYNTH_ASSERT_ERR(pVal, SYNTH_BAD_PARAM_ERR); 136 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 137 | 138 | if (pCtx->type == NW_BOXMULLER) { 139 | struct stBoxMullerParams *pParams; 140 | 141 | pParams = &(pCtx->noiseParams.boxMuller); 142 | 143 | if (pParams->didGenerate) { 144 | *pVal = pParams->z1; 145 | pParams->didGenerate = 0; 146 | } 147 | else { 148 | double u1, u2; 149 | 150 | do { 151 | synthPRNG_getDouble(&u1, pCtx); 152 | synthPRNG_getDouble(&u2, pCtx); 153 | } while (u1 <= DBL_MIN); 154 | 155 | pParams->z0 = sqrt(-2.0 * log(u1)) * cos(TAU * u2); 156 | pParams->z1 = sqrt(-2.0 * log(u1)) * sin(TAU * u2); 157 | 158 | *pVal = pParams->z0; 159 | pParams->didGenerate = 1; 160 | } 161 | 162 | /* Convert it to the desired range */ 163 | *pVal = (*pVal) / 6.7; 164 | } 165 | else { 166 | SYNTH_ASSERT_ERR(0, SYNTH_FUNCTION_NOT_IMPLEMENTED); 167 | } 168 | 169 | rv = SYNTH_OK; 170 | __err: 171 | return rv; 172 | } 173 | 174 | -------------------------------------------------------------------------------- /samples/ld34.mml: -------------------------------------------------------------------------------- 1 | MML//=======================================// 2 | // Song written for 'MK-F417XX3' // 3 | // a game by GFM // 4 | // made for Ludum Dare #34 // 5 | //==========================================// 6 | 7 | t160 8 | 9 | //============================================ 10 | // Bass-line-1: 11 | // 25% square wave 12 | // play notes for 60% of its duration 13 | //-------------------------------------------- 14 | 15 | w2 o3 l4 k5 q60 h65 v(35, 20) > 16 | 17 | // -- 1 -> 4 ------------------------------- 18 | l1 19 | a 20 | < c > 21 | f 22 | g 23 | 24 | // -- 5 -> 8 ------------------------------- 25 | a2 a4. a8 26 | < c2 c4. c8> 27 | f2 f4. f8 28 | g2 g4. g8 29 | 30 | l4 31 | // -- 9 -> 10 ------------------------------- 32 | r g2 g 33 | r g g g 34 | 35 | $ // -- Repeat on 10 ------------------------- 36 | 37 | // -- 11 -> 22 ------------------------------- 38 | l4 39 | [a a a a 40 | < c c c c> 41 | f f f f 42 | g g g g]3 43 | 44 | // Go to a higher pitch (and tone) 45 | l1 46 | // -- 23 -> 26 ------------------------------- 47 | [a < 48 | c 49 | f 50 | g >] 51 | 52 | // -- 27 -> 30 ------------------------------- 53 | a+ < 54 | c+ 55 | f+ 56 | g+ > 57 | 58 | // -- 31 -> 34 ------------------------------- 59 | b < 60 | d 61 | g 62 | a > 63 | 64 | // First part in a higher tone 65 | 66 | // -- 35 -> 46 ------------------------------- 67 | l4 68 | [b b b b 69 | < d d d d> 70 | g g g g 71 | a a a a]3 72 | 73 | // Go back to the original pitch 74 | 75 | l1 76 | // -- 47 -> 50 ------------------------------- 77 | a+ < 78 | c+ 79 | f+ 80 | g+ > 81 | 82 | // -- 51 -> 54 ------------------------------- 83 | a < 84 | c 85 | f 86 | g > 87 | 88 | ; //========================================== 89 | 90 | //============================================ 91 | // Bass-line-2: 92 | // 25% square wave, 93 | // play notes for 60% of its duration 94 | //-------------------------------------------- 95 | 96 | w2 o3 l4 k5 q60 h65 v(30, 15) 97 | 98 | // -- 1 -> 8 ------------------------------- 99 | [r8 e2.. 100 | r8 g2.. 101 | r8 c2.. 102 | r8 d2..] 103 | 104 | l4 105 | // -- 9 -> 10 ------------------------------- 106 | r d2 d 107 | r d d d 108 | 109 | $ // -- Repeat on 10 ------------------------- 110 | 111 | l4 112 | // -- 11 -> 14 ------------------------------- 113 | r8 // All the following '^8' are extending the 114 | // previous note because of this 'desync' 115 | e e e e8 116 | ^8 g g g g8 117 | ^8 c c c c8 118 | ^8 d d d d8 119 | 120 | // -- 15 -> 18 ------------------------------- 121 | ^8 e e e e8 122 | ^8 g g g g8 123 | ^8 c c c c8 124 | ^8 d d d d8 125 | 126 | // -- 19 -> 22 ------------------------------- 127 | ^8 e e e e8 128 | ^8 g g g g8 129 | ^8 c c c c8 130 | ^8 d d d d8 // No extension to sync back with 131 | // bass-line-1 132 | 133 | // Go to a higher pitch (and tone) 134 | l1 135 | // -- 23 -> 26 ------------------------------- 136 | [e < 137 | g 138 | c 139 | d >] 140 | 141 | // -- 27 -> 30 ------------------------------- 142 | f < 143 | g+ 144 | c+ 145 | d+ > 146 | 147 | // -- 31 -> 34 ------------------------------- 148 | f+ < 149 | a 150 | d 151 | e > 152 | 153 | // First part in a higher tone 154 | 155 | l4 156 | // -- 35 -> 38 ------------------------------- 157 | r8 // All the following '^8' are extending the 158 | // previous note because of this 'desync' 159 | f+ f+ f+ f+8 160 | ^8 a a a a8 161 | ^8 d d d d8 162 | ^8 e e e e8 163 | 164 | // -- 39 -> 42 ------------------------------- 165 | ^8 f+ f+ f+ f+8 166 | ^8 a a a a8 167 | ^8 d d d d8 168 | ^8 e e e e8 169 | 170 | // -- 43 -> 46 ------------------------------- 171 | ^8 f+ f+ f+ f+8 172 | ^8 a a a a8 173 | ^8 d d d d8 174 | ^8 e e e e8 // No extension to sync back with 175 | // bass-line-1 176 | 177 | // Go back to the original pitch 178 | 179 | l1 180 | // -- 47 -> 50 ------------------------------- 181 | f < 182 | g+ 183 | c+ 184 | d+ > 185 | 186 | // -- 51 -> 54 ------------------------------- 187 | e < 188 | g 189 | c 190 | d > 191 | 192 | ; //========================================== 193 | 194 | //============================================ 195 | // Melody-line-1: 196 | // 50% square wave, 197 | // play notes for 75% of its duration 198 | //-------------------------------------------- 199 | 200 | w0 k3 q70 h80 o5 l8 v(40, 10) 201 | 202 | // -- 1 -> 8 ------------------------------- 203 | [r4 e2. 204 | r4 g2. 205 | r4 > a2. < 206 | r4 > b2. <] 207 | 208 | l4 209 | // -- 9 -> 10 ------------------------------- 210 | r > b2 b < 211 | r > b b b < 212 | 213 | $ // -- Repeat on 10 ------------------------- 214 | 215 | l8 216 | // -- 11 -> 14 ------------------------------- 217 | e c > a < e c > a < e a 218 | g e c g e c g c 219 | c > a f < c > a f < c > f < 220 | d > b g < d > b g < d > g < 221 | 222 | // -- 15 -> 16 ------------------------------- 223 | e e e e e c > a < e 224 | g g g e e e d e 225 | 226 | // -- 17 -> 18 ------------------------------- 227 | c4. c4. c > g < 228 | d1 229 | 230 | l2 231 | // -- 19 -> 22 ------------------------------- 232 | e c 233 | g e 234 | > a f 235 | b g < 236 | 237 | // Go to a higher pitch (and tone) 238 | l8 239 | // -- 23 -> 26 ------------------------------- 240 | e c > a < e c > a < e < a 241 | < c1 > 242 | < d1 > 243 | < c2 > a r4. > 244 | 245 | // -- 27 -> 30 ------------------------------- 246 | e4. > g4. < c4 247 | g2 d2 248 | a1 249 | < c4. > b4. a4 250 | 251 | // -- 31 -> 34 ------------------------------- 252 | f c+ > a+ < f c+ > a+ < f < a+ 253 | < c+1 > 254 | < d+1 > 255 | < c+2 > a+ r4. > 256 | 257 | // -- 35 -> 38 ------------------------------- 258 | f+4. > a4. < d4 259 | a2 e2 260 | b1 261 | < d4. c+4. > b4 262 | 263 | // First part in a higher tone 264 | 265 | // -- 39 -> 42 ------------------------------- 266 | f+ f+ f+ f+ f+ d > b < f+ 267 | a a a f+ f+ f+ e f+ 268 | d4. d4. d > a < 269 | e1 270 | 271 | // -- 43 -> 46 ------------------------------- 272 | f+ d > b < f+ d > b < f+ b 273 | a f+ d a f+ d a d 274 | d > b g < d > b g < d > g < 275 | e > > c+ < a < e > > c+ < a < e > a < 276 | 277 | // -- 47 -> 50 ------------------------------- 278 | l2 279 | f+ d 280 | a f+ 281 | > b g 282 | > c+ < a < 283 | 284 | // Go back to the original pitch 285 | l8 286 | // -- 51 -> 54 ------------------------------- 287 | f c+ > a+ < f c+ > a+ < f < a+ 288 | < c+1 > 289 | < d+1 > 290 | < c+2 > a+ r4. > 291 | 292 | // -- 55 -> 58 ------------------------------- 293 | e4. > g4. < c4 294 | g2 d2 295 | a1 296 | < c4. > b4. a4 297 | 298 | //============================================ 299 | 300 | -------------------------------------------------------------------------------- /tst/tst_countSamples.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple test to compile a song and, then, count how many samples are needed 3 | * for that song 4 | * 5 | * @file tst/tst_countSamples.c 6 | */ 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | /* Simple test song */ 15 | static char __song[] = "MML t90 l16 o5 e e8 e r c e r g4 > g4 <"; 16 | 17 | /** 18 | * Entry point 19 | * 20 | * @param [ in]argc Number of arguments 21 | * @param [ in]argv List of arguments 22 | * @return The exit code 23 | */ 24 | int main(int argc, char *argv[]) { 25 | char *pSrc; 26 | int freq, handle, i, isFile, len, num; 27 | synthCtx *pCtx; 28 | synth_err rv; 29 | 30 | /* Clean the context, so it's not freed on error */ 31 | pCtx = 0; 32 | 33 | /* Store the default frequency */ 34 | freq = 44100; 35 | isFile = 0; 36 | pSrc = 0; 37 | len = 0; 38 | /* Check argc/argv */ 39 | if (argc > 1) { 40 | int i; 41 | 42 | i = 1; 43 | while (i < argc) { 44 | #define IS_PARAM(l_cmd, s_cmd) \ 45 | if (strcmp(argv[i], l_cmd) == 0 || strcmp(argv[i], s_cmd) == 0) 46 | IS_PARAM("--string", "-s") { 47 | if (argc <= i + 1) { 48 | printf("Expected parameter but got nothing! Run " 49 | "'tst_countSampler --help' for usage!\n"); 50 | return 1; 51 | } 52 | 53 | /* Store the string and retrieve its length */ 54 | pSrc = argv[i + 1]; 55 | isFile = 0; 56 | len = strlen(argv[i + 1]); 57 | } 58 | IS_PARAM("--file", "-f") { 59 | if (argc <= i + 1) { 60 | printf("Expected parameter but got nothing! Run " 61 | "'tst_countSampler --help' for usage!\n"); 62 | return 1; 63 | } 64 | 65 | /* Store the filename */ 66 | pSrc = argv[i + 1]; 67 | isFile = 1; 68 | } 69 | IS_PARAM("--frequency", "-F") { 70 | if (argc <= i + 1) { 71 | printf("Expected parameter but got nothing! Run " 72 | "'tst_countSampler --help' for usage!\n"); 73 | return 1; 74 | } 75 | 76 | char *pNum; 77 | int tmp; 78 | 79 | pNum = argv[i + 1]; 80 | 81 | tmp = 0; 82 | 83 | while (*pNum != '\0') { 84 | tmp = tmp * 10 + (*pNum) - '0'; 85 | pNum++; 86 | } 87 | 88 | freq = tmp; 89 | } 90 | IS_PARAM("--help", "-h") { 91 | printf("A simple test for the c_synth library\n" 92 | "\n" 93 | "Usage: tst_countSamples [--string | -s \"the song\"] " 94 | "[--file | -f ]\n" 95 | " [--frequency | -F ] " 96 | "[--help | -h]\n" 97 | "\n" 98 | "Only one song can be compiled at a time, and this " 99 | "program simply checks if it \n" 100 | "compiles successfully or not (no output is " 101 | "generated).\n" 102 | "On error, however, this program does display the " 103 | "cause and position of the \n" 104 | "error.\n" 105 | "\n" 106 | "If no argument is passed, it will compile a simple " 107 | "test song.\n" 108 | "\n" 109 | "After compiling the song, the number of samples " 110 | "required by it will be counted\n"); 111 | 112 | return 0; 113 | } 114 | 115 | i += 2; 116 | #undef IS_PARAM 117 | } 118 | } 119 | 120 | /* Initialize it */ 121 | printf("Initialize the synthesizer...\n"); 122 | rv = synth_init(&pCtx, freq); 123 | SYNTH_ASSERT(rv == SYNTH_OK); 124 | 125 | /* Compile a song */ 126 | if (pSrc != 0) { 127 | if (isFile) { 128 | printf("Compiling song from file '%s'...\n", pSrc); 129 | rv = synth_compileSongFromFile(&handle, pCtx, pSrc); 130 | } 131 | else { 132 | printf("Compiling song '%s'...\n", pSrc); 133 | rv = synth_compileSongFromString(&handle, pCtx, pSrc, len); 134 | } 135 | } 136 | else { 137 | printf("Compiling static song '%s'...\n", __song); 138 | rv = synth_compileSongFromStringStatic(&handle, pCtx, __song); 139 | } 140 | 141 | if (rv != SYNTH_OK) { 142 | char *pError; 143 | synth_err irv; 144 | 145 | /* Retrieve and print the error */ 146 | irv = synth_getCompilerErrorString(&pError, pCtx); 147 | SYNTH_ASSERT_ERR(irv == SYNTH_OK, irv); 148 | 149 | printf("%s", pError); 150 | } 151 | else { 152 | printf("Song compiled successfully!\n"); 153 | } 154 | SYNTH_ASSERT(rv == SYNTH_OK); 155 | 156 | /* Get the number of tracks in the song */ 157 | printf("Retrieving the number of tracks in the song...\n"); 158 | rv = synth_getAudioTrackCount(&num, pCtx, handle); 159 | SYNTH_ASSERT(rv == SYNTH_OK); 160 | printf("Found %i tracks\n", num); 161 | 162 | /* Get the number of samples in each track */ 163 | printf("Counting the number of samples required by the song...\n"); 164 | i = 0; 165 | while (i < num) { 166 | int intro, len; 167 | 168 | rv = synth_getTrackIntroLength(&intro, pCtx, handle, i); 169 | SYNTH_ASSERT(rv == SYNTH_OK); 170 | rv = synth_getTrackLength(&len, pCtx, handle, i); 171 | SYNTH_ASSERT(rv == SYNTH_OK); 172 | 173 | printf("Track %i requires %i samples and loops at %i\n", i + 1, len, 174 | intro); 175 | i++; 176 | } 177 | 178 | rv = SYNTH_OK; 179 | __err: 180 | if (rv != SYNTH_OK) { 181 | printf("An error happened!\n"); 182 | } 183 | 184 | if (pCtx) { 185 | printf("Releasing resources used by the lib...\n"); 186 | synth_free(&pCtx); 187 | } 188 | 189 | printf("Exiting...\n"); 190 | return rv; 191 | } 192 | 193 | -------------------------------------------------------------------------------- /src/include/c_synth_internal/synth_note.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file src/include/synth_internal/synth_note.h 3 | * 4 | * Representation of a single note in a track 5 | */ 6 | #ifndef __SYNTH_NOTE_H__ 7 | #define __SYNTH_NOTE_H__ 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | /** 16 | * Retrieve a new note pointer, so it can be later initialized 17 | * 18 | * @param [out]ppNote The new note 19 | * @param [ in]pCtx The synthesizer context 20 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 21 | */ 22 | synth_err synthNote_init(synthNote **ppNote, synthCtx *pCtx); 23 | 24 | /** 25 | * Retrieve a new note pointer, already initialized as a loop 26 | * 27 | * @param [out]ppNote The new note 28 | * @param [ in]pCtx The synthesizer context 29 | * @param [ in]repeat How many times the loop should repeat 30 | * @param [ in]position Note to which the song should jump back, on loop 31 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 32 | */ 33 | synth_err synthNote_initLoop(synthNote **ppNote, synthCtx *pCtx, int repeat, 34 | int position); 35 | 36 | /** 37 | * Set the note panning 38 | * 39 | * Defines which channel, if any, should be louder; If the value is outside the 40 | * expected [0, 100] range, it's automatically clamped 41 | * 42 | * @param [ in]pNote The note 43 | * @param [ in]pan Panning level; 0 means completelly to the left and 100 44 | * means completelly to the right 45 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 46 | */ 47 | synth_err synthNote_setPan(synthNote *pNote, char pan); 48 | 49 | /** 50 | * Set the note octave 51 | * 52 | * Define higher the pitch, the highed the numeric representation; The value is 53 | * clamped to the range [1, 8] 54 | * 55 | * @param [ in]pNote The note 56 | * @param [ in]octave The octave 57 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 58 | */ 59 | synth_err synthNote_setOctave(synthNote *pNote, char octave); 60 | 61 | /** 62 | * Set the note wave 63 | * 64 | * If the wave isn't valid, it will be set to noise! 65 | * 66 | * @param [ in]pNote The note 67 | * @param [ in]wave The wave 68 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 69 | */ 70 | synth_err synthNote_setWave(synthNote *pNote, synth_wave wave); 71 | 72 | /** 73 | * Set the musical note 74 | * 75 | * @param [ in]pNote The note 76 | * @param [ in]note The musical note 77 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 78 | */ 79 | synth_err synthNote_setNote(synthNote *pNote, synth_note note); 80 | 81 | /** 82 | * Set the note duration 83 | * 84 | * NOTE: The duration is stored in samples 85 | * 86 | * @param [ in]pNote The note 87 | * @param [ in]pCtx The synthesizer context 88 | * @param [ in]duration Bitfield for the duration. Each bit represents a 89 | * fraction of the duration; 90 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 91 | */ 92 | synth_err synthNote_setDuration(synthNote *pNote, synthCtx *pCtx, int duration); 93 | 94 | /** 95 | * Set the characteristics of the note's duration 96 | * 97 | * NOTE: This parameter must be set after the duration 98 | * 99 | * All values must be in the range [0, 100]. The attack is campled to the range 100 | * [0, keyoff] and the release is campled to the range [keyoff, 100]. Although 101 | * the parameter express the percentage of the note's duration, the value is 102 | * stored in samples. 103 | * 104 | * @param [ in]pNote The note 105 | * @param [ in]attack The percentage of the note duration before it reaches 106 | * its full amplitude 107 | * @param [ in]keyoff The percentage of the note duration before it's released 108 | * @param [ in]release The percentage of the note duration before it halts 109 | * completely 110 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 111 | */ 112 | synth_err synthNote_setKeyoff(synthNote *pNote, int attack, int keyoff, 113 | int release); 114 | 115 | /** 116 | * Set the volume envelop 117 | * 118 | * @param [ in]pNote The note 119 | * @param [ in]volume The volume 120 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 121 | */ 122 | synth_err synthNote_setVolume(synthNote *pNote, int volume); 123 | 124 | /** 125 | * Check if the note is a loop point 126 | * 127 | * @param [ in]pNote The note 128 | * @return SYNTH_TRUE, SYNTH_FALSE 129 | */ 130 | synth_bool synthNote_isLoop(synthNote *pNote); 131 | 132 | /** 133 | * Retrieve the note duration, in binary fixed point notation 134 | * 135 | * @param [out]pVal The duration 136 | * @param [ in]pNote The note 137 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 138 | */ 139 | synth_err synthNote_getDuration(int *pVal, synthNote *pNote); 140 | 141 | /** 142 | * Retrieve the panning of the note, where 0 means completely on the left 143 | * channel and 100 means completely on the right channel 144 | * 145 | * @param [out]pVal The panning 146 | * @param [ in]pNote The note 147 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 148 | */ 149 | synth_err synthNote_getPan(char *pVal, synthNote *pNote); 150 | 151 | /** 152 | * Retrieve the number of times this loop should repeat 153 | * 154 | * @param [out]pVal The repeat count 155 | * @param [ in]pNote The note 156 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 157 | */ 158 | synth_err synthNote_getRepeat(int *pVal, synthNote *pNote); 159 | 160 | /** 161 | * Retrieve the position, in the track, to which it should jump on loop 162 | * 163 | * @param [out]pVal The repeat position 164 | * @param [ in]pNote The note 165 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 166 | */ 167 | synth_err synthNote_getJumpPosition(int *pVal, synthNote *pNote); 168 | 169 | /** 170 | * Render a note into a buffer 171 | * 172 | * The buffer must have at least 'synthNote_getDuration' bytes time the number 173 | * of bytes required by the mode 174 | * 175 | * @param [ in]pBuf Buffer that will be filled with the track 176 | * @param [ in]pNote The note 177 | * @param [ in]pCtx The synthesizer context 178 | * @param [ in]mode Desired mode for the wave 179 | * @param [ in]synthFreq Synthesizer's frequency 180 | * @param [ in]duration The note's length in samples 181 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 182 | */ 183 | synth_err synthNote_render(char *pBuf, synthNote *pNote, synthCtx *pCtx, 184 | synthBufMode mode, int synthFreq, int duration); 185 | 186 | #endif /* __SYNTH_NOTE_H__ */ 187 | 188 | -------------------------------------------------------------------------------- /src/synth_volume.c: -------------------------------------------------------------------------------- 1 | /** 2 | * A 'volume' represents a function on the time domain, which controls each the 3 | * volume for each sample on a note; For simplicity (on the lib's side), 4 | * whenever a new volume is created it can never be deleted; However, if two 5 | * notes shares the same function, they will point to the same 'volume object' 6 | * 7 | * Volumes are stored in a simple array, since they are only searched and 8 | * created on compilation time; So, even if lots of volumes are used and they 9 | * slow down the compilation, there will be no side effects while rendering the 10 | * song 11 | * 12 | * @file src/synth_volume.c 13 | */ 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | /** 24 | * Alloc and initialize a new volume 25 | * 26 | * @param [out]ppVol The new volume 27 | * @param [ in]pCtx The synthesizer context 28 | * @return SYNTH_OK, SYNTH_MEM_ERR 29 | */ 30 | static synth_err synthVolume_init(synthVolume **ppVol, synthCtx *pCtx) { 31 | synth_err rv; 32 | 33 | /* Make sure there's enough space for another volume */ 34 | SYNTH_ASSERT_ERR(pCtx->volumes.max == 0 || 35 | pCtx->volumes.used < pCtx->volumes.max, SYNTH_MEM_ERR); 36 | 37 | /* Check if the buffer must be expanded and do so */ 38 | if (pCtx->volumes.used >= pCtx->volumes.len) { 39 | /* 'Double' the current buffer; Note that this will never be called if 40 | * the context was pre-alloc'ed, since 'max' will be set; The '+1' is 41 | * for the first volume, in which len will be 0 */ 42 | pCtx->volumes.buf.pVolumes = (synthVolume*)realloc( 43 | pCtx->volumes.buf.pVolumes, (1 + pCtx->volumes.len * 2) * 44 | sizeof(synthVolume)); 45 | SYNTH_ASSERT_ERR(pCtx->volumes.buf.pVolumes, SYNTH_MEM_ERR); 46 | /* Clear only the new part of the buffer */ 47 | memset(&(pCtx->volumes.buf.pVolumes[pCtx->volumes.used]), 0x0, 48 | (1 + pCtx->volumes.len) * sizeof(synthVolume)); 49 | /* Actually increase the buffer length */ 50 | pCtx->volumes.len += 1 + pCtx->volumes.len; 51 | } 52 | 53 | /* Retrieve the volume to be used */ 54 | *ppVol = &(pCtx->volumes.buf.pVolumes[pCtx->volumes.used]); 55 | pCtx->volumes.used++; 56 | rv = SYNTH_OK; 57 | __err: 58 | return rv; 59 | } 60 | 61 | /** 62 | * Retrieve a constant volume 63 | * 64 | * If the required volume isn't found, it will be instantiated and returned 65 | * 66 | * @param [out]pVol The index of the volume 67 | * @param [ in]pCtx The synthesizer context 68 | * @param [ in]amp The requested amplitude (in the range [0, 128]) 69 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 70 | */ 71 | synth_err synthVolume_getConst(int *pVol, synthCtx *pCtx, int amp) { 72 | int i; 73 | synth_err rv; 74 | 75 | /* Sanitize the arguments */ 76 | SYNTH_ASSERT_ERR(pVol, SYNTH_BAD_PARAM_ERR); 77 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 78 | 79 | /* Clamp the note to the valid range */ 80 | if (amp < 0) { 81 | amp = 0; 82 | } 83 | else if (amp > 128) { 84 | amp = 128; 85 | } 86 | 87 | /* Increase the amplitude to a 16 bits value */ 88 | amp <<= 8; 89 | 90 | /* Clean the return, so we now if anything was found */ 91 | *pVol = 0; 92 | 93 | /* Search for the requested volume through the existing ones */ 94 | i = 0; 95 | while (i < pCtx->volumes.used) { 96 | if ((pCtx->volumes.buf.pVolumes[i].ini == 97 | pCtx->volumes.buf.pVolumes[i].fin) && 98 | (pCtx->volumes.buf.pVolumes[i].ini == amp)) { 99 | /* If a volume matched, simply return it */ 100 | *pVol = i; 101 | break; 102 | } 103 | i++; 104 | } 105 | 106 | /* If the volume wasn't found, create a new one */ 107 | if (*pVol == 0) { 108 | synthVolume *pVolume; 109 | 110 | rv = synthVolume_init(&pVolume, pCtx); 111 | SYNTH_ASSERT(rv == SYNTH_OK); 112 | 113 | /* Set both values to the same, since this is a constant volume */ 114 | pVolume->ini = amp; 115 | pVolume->fin = amp; 116 | 117 | /* Retrieve the volume's index */ 118 | *pVol = pCtx->volumes.used - 1; 119 | } 120 | 121 | rv = SYNTH_OK; 122 | __err: 123 | return rv; 124 | } 125 | 126 | /** 127 | * Retrieve a linear volume 128 | * 129 | * If the required volume isn't found, it will be instantiated and returned 130 | * 131 | * @param [out]pVol The index of the volume 132 | * @param [ in]pCtx The synthesizer context 133 | * @param [ in]ini The initial amplitude (in the range [0, 255]) 134 | * @param [ in]fin The final amplitude (in the range [0, 255]) 135 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 136 | */ 137 | synth_err synthVolume_getLinear(int *pVol, synthCtx *pCtx, int ini, int fin) { 138 | int i; 139 | synth_err rv; 140 | 141 | /* Sanitize the arguments */ 142 | SYNTH_ASSERT_ERR(pVol, SYNTH_BAD_PARAM_ERR); 143 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 144 | 145 | /* Clamp the note to the valid range */ 146 | if (ini < 0) { 147 | ini = 0; 148 | } 149 | else if (ini > 128) { 150 | ini = 128; 151 | } 152 | if (fin < 0) { 153 | fin = 0; 154 | } 155 | else if (fin > 128) { 156 | fin = 128; 157 | } 158 | 159 | /* Clean the return, so we now if anything was found */ 160 | *pVol = 0; 161 | 162 | /* Increase the amplitude to a 16 bits value */ 163 | ini <<= 8; 164 | fin <<= 8; 165 | 166 | /* Search for the requested volume through the existing ones */ 167 | i = 0; 168 | while (i < pCtx->volumes.used) { 169 | if (pCtx->volumes.buf.pVolumes[i].ini == ini && 170 | pCtx->volumes.buf.pVolumes[i].fin == fin) { 171 | /* If a volume matched, simply return it */ 172 | *pVol = i; 173 | break; 174 | } 175 | i++; 176 | } 177 | 178 | /* If the volume wasn't found, create a new one */ 179 | if (*pVol == 0) { 180 | synthVolume *pVolume; 181 | 182 | rv = synthVolume_init(&pVolume, pCtx); 183 | SYNTH_ASSERT(rv == SYNTH_OK); 184 | 185 | /* Set both values to the same, since this is a constant volume */ 186 | pVolume->ini = ini; 187 | pVolume->fin = fin; 188 | 189 | /* Retrieve the volume's index */ 190 | *pVol = pCtx->volumes.used - 1; 191 | } 192 | 193 | rv = SYNTH_OK; 194 | __err: 195 | return rv; 196 | } 197 | 198 | /** 199 | * Retrieve the volume at a given percentage of a note 200 | * 201 | * @param [out]pAmp The note's amplitude 202 | * @param [ in]pVol The volume 203 | * @param [ in]perc Percentage into the note (in the range [0, 1024)) 204 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 205 | */ 206 | synth_err synthVolume_getAmplitude(int *pAmp, synthVolume *pVol, int perc) { 207 | synth_err rv; 208 | 209 | /* Sanitize the arguments */ 210 | SYNTH_ASSERT_ERR(pAmp, SYNTH_BAD_PARAM_ERR); 211 | SYNTH_ASSERT_ERR(pVol, SYNTH_BAD_PARAM_ERR); 212 | 213 | /* Calculate the current amplitude */ 214 | *pAmp = (((pVol->ini * (1024 - perc)) + (pVol->fin * perc)) >> 10) & 0xffff; 215 | /* If the previous didn't work out (because of integer division), use the 216 | * following */ 217 | /* *pAmp = (pVol->ini + (pVol->fin - pVol->ini) * (perc / 1024.0f)) & 218 | * 0xff; */ 219 | 220 | rv = SYNTH_OK; 221 | __err: 222 | return rv; 223 | } 224 | 225 | -------------------------------------------------------------------------------- /tst/tst_renderTrack.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple test to render each track of a song 3 | * 4 | * @file tst/tst_renderTrack.c 5 | */ 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | /* Simple test song */ 15 | static char __song[] = "MML t90 l16 o5 e e8 e r c e r g4 > g4 <"; 16 | 17 | /** 18 | * Entry point 19 | * 20 | * @param [ in]argc Number of arguments 21 | * @param [ in]argv List of arguments 22 | * @return The exit code 23 | */ 24 | int main(int argc, char *argv[]) { 25 | char *pBuf, *pSrc; 26 | int bufLen, freq, handle, i, isFile, len, num; 27 | synthBufMode mode; 28 | synthCtx *pCtx; 29 | synth_err rv; 30 | 31 | /* Clean the context, so it's not freed on error */ 32 | pCtx = 0; 33 | pBuf = 0; 34 | bufLen = 0; 35 | 36 | /* Store the default frequency */ 37 | freq = 44100; 38 | /* Store the default mode */ 39 | mode = SYNTH_1CHAN_U8BITS; 40 | isFile = 0; 41 | pSrc = 0; 42 | len = 0; 43 | /* Check argc/argv */ 44 | if (argc > 1) { 45 | int i; 46 | 47 | i = 1; 48 | while (i < argc) { 49 | #define IS_PARAM(l_cmd, s_cmd) \ 50 | if (strcmp(argv[i], l_cmd) == 0 || strcmp(argv[i], s_cmd) == 0) 51 | IS_PARAM("--string", "-s") { 52 | if (argc <= i + 1) { 53 | printf("Expected parameter but got nothing! Run " 54 | "'tst_renderTrack --help' for usage!\n"); 55 | return 1; 56 | } 57 | 58 | /* Store the string and retrieve its length */ 59 | pSrc = argv[i + 1]; 60 | isFile = 0; 61 | len = strlen(argv[i + 1]); 62 | } 63 | IS_PARAM("--file", "-f") { 64 | if (argc <= i + 1) { 65 | printf("Expected parameter but got nothing! Run " 66 | "'tst_renderTrack --help' for usage!\n"); 67 | return 1; 68 | } 69 | 70 | /* Store the filename */ 71 | pSrc = argv[i + 1]; 72 | isFile = 1; 73 | } 74 | IS_PARAM("--frequency", "-F") { 75 | char *pNum; 76 | int tmp; 77 | 78 | if (argc <= i + 1) { 79 | printf("Expected parameter but got nothing! Run " 80 | "'tst_renderTrack --help' for usage!\n"); 81 | return 1; 82 | } 83 | 84 | pNum = argv[i + 1]; 85 | 86 | tmp = 0; 87 | 88 | while (*pNum != '\0') { 89 | tmp = tmp * 10 + (*pNum) - '0'; 90 | pNum++; 91 | } 92 | 93 | freq = tmp; 94 | } 95 | IS_PARAM("--mode", "-m") { 96 | char *pMode; 97 | 98 | if (argc <= i + 1) { 99 | printf("Expected parameter but got nothing! Run " 100 | "'tst_renderTrack --help' for usage!\n"); 101 | return 1; 102 | } 103 | 104 | pMode = argv[i + 1]; 105 | 106 | if (strcmp(pMode, "1chan-u8") == 0) { 107 | mode = SYNTH_1CHAN_U8BITS; 108 | } 109 | else if (strcmp(pMode, "1chan-8") == 0) { 110 | mode = SYNTH_1CHAN_8BITS; 111 | } 112 | else if (strcmp(pMode, "1chan-u16") == 0) { 113 | mode = SYNTH_1CHAN_U16BITS; 114 | } 115 | else if (strcmp(pMode, "1chan-16") == 0) { 116 | mode = SYNTH_1CHAN_16BITS; 117 | } 118 | else if (strcmp(pMode, "2chan-u8") == 0) { 119 | mode = SYNTH_2CHAN_U8BITS; 120 | } 121 | else if (strcmp(pMode, "2chan-8") == 0) { 122 | mode = SYNTH_2CHAN_8BITS; 123 | } 124 | else if (strcmp(pMode, "2chan-u16") == 0) { 125 | mode = SYNTH_2CHAN_U16BITS; 126 | } 127 | else if (strcmp(pMode, "2chan-16") == 0) { 128 | mode = SYNTH_2CHAN_16BITS; 129 | } 130 | else { 131 | printf("Invalid mode! Run 'tst_renderTrack --help' to " 132 | "check the usage!\n"); 133 | return 1; 134 | } 135 | } 136 | IS_PARAM("--help", "-h") { 137 | printf("A simple test for the c_synth library\n" 138 | "\n" 139 | "Usage: tst_renderTrack [--string | -s \"the song\"] " 140 | "[--file | -f ]\n" 141 | " [--frequency | -F ] " 142 | "[--mode | -m ] \n" 143 | " [--help | -h]\n" 144 | "\n" 145 | "Compiles a single song and then render each of its " 146 | "tracks.\n" 147 | "'' must be one of the following:\n" 148 | " 1chan-u8 : 1 channel, unsigned 8 bits samples\n" 149 | " 1chan-8 : 1 channel, signed 8 bits samples\n" 150 | " 1chan-u16: 1 channel, unsigned 16 bits samples\n" 151 | " 1chan-16 : 1 channel, signed 16 bits samples\n" 152 | " 2chan-u8 : 2 channel, unsigned 8 bits samples\n" 153 | " 2chan-8 : 2 channel, signed 8 bits samples\n" 154 | " 2chan-u16: 2 channel, unsigned 16 bits samples\n" 155 | " 2chan-16 : 2 channel, signed 16 bits samples\n" 156 | "\n" 157 | "If no argument is passed, it will compile a simple " 158 | "test song.\n"); 159 | return 0; 160 | } 161 | 162 | i += 2; 163 | #undef IS_PARAM 164 | } 165 | } 166 | 167 | /* Initialize it */ 168 | printf("Initialize the synthesizer...\n"); 169 | rv = synth_init(&pCtx, freq); 170 | SYNTH_ASSERT(rv == SYNTH_OK); 171 | 172 | /* Compile a song */ 173 | if (pSrc != 0) { 174 | if (isFile) { 175 | printf("Compiling song from file '%s'...\n", pSrc); 176 | rv = synth_compileSongFromFile(&handle, pCtx, pSrc); 177 | } 178 | else { 179 | printf("Compiling song '%s'...\n", pSrc); 180 | rv = synth_compileSongFromString(&handle, pCtx, pSrc, len); 181 | } 182 | } 183 | else { 184 | printf("Compiling static song '%s'...\n", __song); 185 | rv = synth_compileSongFromStringStatic(&handle, pCtx, __song); 186 | } 187 | 188 | if (rv != SYNTH_OK) { 189 | char *pError; 190 | synth_err irv; 191 | 192 | /* Retrieve and print the error */ 193 | irv = synth_getCompilerErrorString(&pError, pCtx); 194 | SYNTH_ASSERT_ERR(irv == SYNTH_OK, irv); 195 | 196 | printf("%s", pError); 197 | } 198 | else { 199 | printf("Song compiled successfully!\n"); 200 | } 201 | SYNTH_ASSERT(rv == SYNTH_OK); 202 | 203 | /* Get the number of tracks in the song */ 204 | printf("Retrieving the number of tracks in the song...\n"); 205 | rv = synth_getAudioTrackCount(&num, pCtx, handle); 206 | SYNTH_ASSERT(rv == SYNTH_OK); 207 | printf("Found %i tracks\n", num); 208 | 209 | /* Get the number of samples in each track */ 210 | printf("Rendering each of the song's tracks...\n"); 211 | i = 0; 212 | while (i < num) { 213 | int intro, len, reqLen; 214 | 215 | /* Retrieve the length of the track, in samples */ 216 | rv = synth_getTrackIntroLength(&intro, pCtx, handle, i); 217 | SYNTH_ASSERT(rv == SYNTH_OK); 218 | rv = synth_getTrackLength(&len, pCtx, handle, i); 219 | SYNTH_ASSERT(rv == SYNTH_OK); 220 | 221 | printf("Track %i requires %i samples and loops at %i\n", i + 1, len, 222 | intro); 223 | 224 | /* Retrieve the number of bytes required */ 225 | reqLen = len; 226 | if (mode & SYNTH_16BITS) { 227 | reqLen *= 2; 228 | } 229 | if (mode & SYNTH_2CHAN) { 230 | reqLen *= 2; 231 | } 232 | 233 | printf("Track %i requires %i bytes (%i KB, %i MB)\n", i + 1, reqLen, 234 | reqLen >> 10, reqLen >> 20); 235 | 236 | /* Check if the buffer must be expanded... */ 237 | if (bufLen < reqLen) { 238 | printf("Expanding the buffer from %i bytes to %i bytes...\n", 239 | bufLen, reqLen); 240 | 241 | pBuf = (char*)realloc(pBuf, reqLen); 242 | SYNTH_ASSERT_ERR(pBuf, SYNTH_MEM_ERR); 243 | 244 | bufLen = reqLen; 245 | } 246 | 247 | /* Render the track */ 248 | rv = synth_renderTrack(pBuf, pCtx, handle, i, mode); 249 | SYNTH_ASSERT(rv == SYNTH_OK); 250 | 251 | i++; 252 | } 253 | 254 | rv = SYNTH_OK; 255 | __err: 256 | if (rv != SYNTH_OK) { 257 | printf("An error happened!\n"); 258 | } 259 | 260 | if (pCtx) { 261 | printf("Releasing resources used by the lib...\n"); 262 | synth_free(&pCtx); 263 | } 264 | 265 | if (pBuf) { 266 | free(pBuf); 267 | } 268 | 269 | printf("Exiting...\n"); 270 | return rv; 271 | } 272 | 273 | -------------------------------------------------------------------------------- /include/c_synth/synth.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file include/synth/synth.h 3 | */ 4 | #ifndef __SYNTHCTX_STRUCT__ 5 | #define __SYNTHCTX_STRUCT__ 6 | 7 | /** 'Export' the synthCtx struct */ 8 | typedef struct stSynthCtx synthCtx; 9 | 10 | #endif /* __SYNTHCTX_STRUCT__ */ 11 | 12 | #ifndef __SYNTHBUFMODE_ENUM__ 13 | #define __SYNTHBUFMODE_ENUM__ 14 | 15 | /* Define the renderable types of buffers */ 16 | /* TODO Check if 8 bits waves can be signed */ 17 | enum enSynthBufMode { 18 | /* Configurable bits */ 19 | SYNTH_8BITS = 0x0001, 20 | SYNTH_16BITS = 0x0002, 21 | SYNTH_1CHAN = 0x0010, 22 | SYNTH_2CHAN = 0x0020, 23 | SYNTH_UNSIGNED = 0x0100, 24 | SYNTH_SIGNED = 0x0200, 25 | /* Pre-defined types */ 26 | SYNTH_1CHAN_U8BITS = SYNTH_8BITS | SYNTH_1CHAN | SYNTH_UNSIGNED, 27 | SYNTH_1CHAN_8BITS = SYNTH_8BITS | SYNTH_1CHAN | SYNTH_SIGNED, 28 | SYNTH_1CHAN_U16BITS = SYNTH_16BITS | SYNTH_1CHAN | SYNTH_UNSIGNED, 29 | SYNTH_1CHAN_16BITS = SYNTH_16BITS | SYNTH_1CHAN | SYNTH_SIGNED, 30 | SYNTH_2CHAN_U8BITS = SYNTH_8BITS | SYNTH_2CHAN | SYNTH_UNSIGNED, 31 | SYNTH_2CHAN_8BITS = SYNTH_8BITS | SYNTH_2CHAN | SYNTH_SIGNED, 32 | SYNTH_2CHAN_U16BITS = SYNTH_16BITS | SYNTH_2CHAN | SYNTH_UNSIGNED, 33 | SYNTH_2CHAN_16BITS = SYNTH_16BITS | SYNTH_2CHAN | SYNTH_SIGNED, 34 | /* Mask to check that the mode is valid */ 35 | SYNTH_VALID_MODE_MASK = 0x0333 36 | }; 37 | 38 | /* Export the buffer mode enum */ 39 | typedef enum enSynthBufMode synthBufMode; 40 | 41 | #endif /* __SYNTHBUFMODE_ENUM__ */ 42 | 43 | #ifndef __SYNTH_H__ 44 | #define __SYNTH_H__ 45 | 46 | #include 47 | 48 | /** 49 | * Retrieve the total size for a context 50 | * 51 | * This allows an application to alloc it however it wants; In memory constraint 52 | * environments, it might be desired not to use dynamic memory, so this function 53 | * call can determined how much memory would be required for a context with some 54 | * restrictions 55 | * 56 | * @param [out]pSize The size of the context struct in bytes 57 | * @param [ in]maxSongs How many songs can be compiled at the same time 58 | * @param [ in]maxTracks How many tracks can be used through all songs 59 | * @param [ in]maxNotes How many notes can be used through all tracks 60 | * @param [ in]maxVolumes How many volumes can be used through all tracks 61 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 62 | */ 63 | synth_err synth_getStaticContextSize(int *pSize, int maxSongs, int maxTracks, 64 | int maxNotes, int maxVolumes); 65 | 66 | /** 67 | * Check how many bytes the context is currently using 68 | * 69 | * @param [out]pSize The size of the context struct in bytes 70 | * @param [ in]pCtx The synthesizer context 71 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 72 | */ 73 | synth_err synth_getContextSize(int *pSize, synthCtx *pCtx); 74 | 75 | /** 76 | * Initialize the synthesizer context from a previously alloc'ed memory from the 77 | * user 78 | * 79 | * This function call initializes a context that enforces restrictions over the 80 | * use of memory by the synthesizer; It won't alloc no extra memory, so it's 81 | * highly advised that the synthesizer is first tested using its dynamic 82 | * version, so the required memory to whatever is desired is calculated, before 83 | * trying to use this mode; 84 | * 85 | * @param [out]ppCtx The new synthesizer context 86 | * @param [ in]pMem 'synth_getContextSize' bytes or NULL, if the library 87 | * should alloc the structure however it wants 88 | * @param [ in]freq Synthesizer frequency, in samples per seconds 89 | * @param [ in]maxSongs How many songs can be compiled at the same time 90 | * @param [ in]maxTracks How many tracks can be used through all songs 91 | * @param [ in]maxNotes How many notes can be used through all tracks 92 | * @param [ in]maxVolumes How many volumes can be used through all tracks 93 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 94 | */ 95 | synth_err synth_initStatic(synthCtx **ppCtx, void *pMem, int freq, int maxSongs, 96 | int maxTracks, int maxNotes, int maxVolumes); 97 | 98 | /** 99 | * Alloc and initialize the synthesizer 100 | * 101 | * @param [out]ppCtx The new synthesizer context 102 | * @param [ in]freq Synthesizer frequency, in samples per seconds 103 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 104 | */ 105 | synth_err synth_init(synthCtx **ppCtx, int freq); 106 | 107 | /** 108 | * Release any of the submodules in use and then release any other memory 109 | * alloc'ed by the library 110 | * 111 | * @param [ in]ppCtx The synthesizer context that will be dealloc'ed 112 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 113 | */ 114 | synth_err synth_free(synthCtx **ppCtx); 115 | 116 | /** 117 | * Parse a file into a compiled song. The file must have been opened as a 118 | * SDL_RWops file 119 | * 120 | * @param [out]pHandle Handle of the loaded song 121 | * @param [ in]pCtx The synthesizer context 122 | * @param [ in]pFile The SDL_RWops file 123 | */ 124 | synth_err synth_compileSongFromSDL_RWops(int *pHandle, synthCtx *pCtx, 125 | void *pFile); 126 | 127 | /** 128 | * Parse a file into a compiled song 129 | * 130 | * The compiled song can later be used to playback the audio, get its samples 131 | * (i.e., buffer the whole song) or to export it to WAVE or OGG 132 | * 133 | * @param [out]pHandle Handle of the loaded song 134 | * @param [ in]pCtx The synthesizer context 135 | * @param [ in]pFilename File with the song's MML 136 | * @param SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR, ... 137 | */ 138 | synth_err synth_compileSongFromFile(int *pHandle, synthCtx *pCtx, 139 | char *pFilename); 140 | 141 | /** 142 | * Parse a string into a compiled song 143 | * 144 | * The compiled song can later be used to playback the audio, get its samples 145 | * (i.e., buffer the whole song) or to export it to WAVE or OGG 146 | * 147 | * @param [out]pHandle Handle of the loaded song 148 | * @param [ in]pCtx The synthesizer context 149 | * @param [ in]pString Song's MML 150 | * @param [ in]length The string's length (must contain the NULL-terminator!) 151 | * @param SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR, ... 152 | */ 153 | synth_err synth_compileSongFromString(int *pHandle, synthCtx *pCtx, 154 | char *pString, int length); 155 | 156 | #define synth_compileSongFromStringStatic(pHandle, pCtx, pString) \ 157 | synth_compileSongFromString(pHandle, pCtx, pString, sizeof(pString)) 158 | 159 | /** 160 | * Return a string representing the compiler error raised 161 | * 162 | * This string is statically allocated and mustn't be freed by user 163 | * 164 | * @param [out]ppError The error string 165 | * @param [ in]pCtx The synthesizer context 166 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_NO_ERRORS 167 | */ 168 | synth_err synth_getCompilerErrorString(char **ppError, synthCtx *pCtx); 169 | 170 | /** 171 | * Return the number of tracks in a song 172 | * 173 | * @param [out]pNum The number of tracks 174 | * @param [ in]pCtx The synthesizer context 175 | * @param [ in]handle Handle of the audio 176 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_INVALID_INDEX 177 | */ 178 | synth_err synth_getAudioTrackCount(int *pNum, synthCtx *pCtx, int handle); 179 | 180 | /** 181 | * Retrieve the number of samples in a track 182 | * 183 | * @param [out]pLen The length of the track in samples 184 | * @param [ in]pCtx The synthesizer context 185 | * @param [ in]handle Handle of the audio 186 | * @param [ in]track Track index 187 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_INVALID_INDEX 188 | */ 189 | synth_err synth_getTrackLength(int *pLen, synthCtx *pCtx, int handle, 190 | int track); 191 | 192 | /** 193 | * Retrieve the number of samples until a track's loop point 194 | * 195 | * @param [out]pLen The length of the track's intro 196 | * @param [ in]pCtx The synthesizer context 197 | * @param [ in]handle Handle of the audio 198 | * @param [ in]track The track 199 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 200 | */ 201 | synth_err synth_getTrackIntroLength(int *pLen, synthCtx *pCtx, int handle, 202 | int track); 203 | 204 | /** 205 | * Check whether a track is loopable 206 | * 207 | * @param [out]pVal 1 if it does loop, 0 otherwise 208 | * @param [ in]pCtx The synthesizer context 209 | * @param [ in]handle Handle of the audio 210 | * @param [ in]pTrack The track 211 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_INVALID_INDEX 212 | */ 213 | synth_err synth_isTrackLoopable(int *pVal, synthCtx *pCtx, int handle, 214 | int track); 215 | 216 | /** 217 | * Render a track into a buffer 218 | * 219 | * The buffer must be prepared by the caller, and it must have 220 | * 'synth_getTrackLength' bytes times the number of bytes per samples 221 | * 222 | * @param [ in]pBuf Buffer that will be filled with the track 223 | * @param [ in]pCtx The synthesizer context 224 | * @param [ in]handle Handle of the audio 225 | * @param [ in]pTrack The track 226 | * @param [ in]mode Desired mode for the wave 227 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 228 | */ 229 | synth_err synth_renderTrack(char *pBuf, synthCtx *pCtx, int handle, int track, 230 | synthBufMode mode); 231 | 232 | /** 233 | * Check whether a song can loop nicely in a single iteration 234 | * 235 | * @param [ in]pCtx The synthesizer context 236 | * @param [ in]handle Handle of the audio 237 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_INVALID_INDEX, 238 | * SYNTH_COMPLEX_LOOPPOINT, SYNTH_NOT_LOOPABLE 239 | */ 240 | synth_err synth_canSongLoop(synthCtx *pCtx, int handle); 241 | 242 | /** 243 | * Retrieve the length, in samples, of the longest track in a song 244 | * 245 | * The song is checked for a single iteration loop. If that's impossible, the 246 | * function will exit with an error 247 | * 248 | * @param [out]pLen The length of the track's intro 249 | * @param [ in]pCtx The synthesizer context 250 | * @param [ in]handle Handle of the audio 251 | * @param [ in]pTrack The track 252 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_INVALID_INDEX, 253 | * SYNTH_COMPLEX_LOOPPOINT 254 | */ 255 | synth_err synth_getSongLength(int *pLen, synthCtx *pCtx, int handle); 256 | 257 | /** 258 | * Retrieve the number of samples until a song's loop point 259 | * 260 | * This functions expect all tracks to loop at the same point, so it will fail 261 | * if this isn't possible in a single iteration of the longest track 262 | * 263 | * @param [out]pLen The length of the track's intro 264 | * @param [ in]pCtx The synthesizer context 265 | * @param [ in]handle Handle of the audio 266 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_INVALID_INDEX, 267 | * SYNTH_COMPLEX_LOOPPOINT, SYNTH_NOT_LOOPABLE 268 | */ 269 | synth_err synth_getSongIntroLength(int *pLen, synthCtx *pCtx, int handle); 270 | 271 | /** 272 | * Render all of a song's tracks and accumulate 'em in a single buffer 273 | * 274 | * A temporary buffer is necessary in order to render each track; If the same 275 | * buffer were to be used, the previously rendered data would be lost (when 276 | * accumulating the tracks on the destination buffer), so this situation is 277 | * checked and is actually an error 278 | * 279 | * @param [ in]pBuf Buffer that will be filled with the song 280 | * @param [ in]pCtx The synthesizer context 281 | * @param [ in]handle Handle of the audio 282 | * @param [ in]mode Desired mode for the song 283 | * @param [ in]pTmp Temporary buffer that will be filled with each track 284 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_INVALID_INDEX, 285 | * SYNTH_COMPLEX_LOOPPOINT 286 | */ 287 | synth_err synth_renderSong(char *pBuf, synthCtx *pCtx, int handle, 288 | synthBufMode mode, char *pTmp); 289 | 290 | #endif /* __SYNTH_H__ */ 291 | 292 | -------------------------------------------------------------------------------- /src/synth_audio.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file src/synth_audio.c 3 | */ 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | /** 19 | * Initialize a new audio, so a song can be compiled into it 20 | * 21 | * @param [out]pAudio Object that will be filled with the compiled song 22 | * @param [ in]pCtx The synthesizer context 23 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 24 | */ 25 | synth_err synthAudio_init(synthAudio **ppAudio, synthCtx *pCtx) { 26 | synth_err rv; 27 | 28 | /* Sanitize the arguments */ 29 | SYNTH_ASSERT_ERR(ppAudio, SYNTH_BAD_PARAM_ERR); 30 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 31 | /* Make sure there's enough space for another song */ 32 | SYNTH_ASSERT_ERR(pCtx->songs.max == 0 || pCtx->songs.used < pCtx->songs.max, 33 | SYNTH_MEM_ERR); 34 | 35 | /* Retrieve the audio to be used */ 36 | if (pCtx->songs.used >= pCtx->songs.len) { 37 | /* 'Double' the current buffer; Note that this will never be called if 38 | * the context was pre-alloc'ed, since 'max' will be set; The '+1' is 39 | * for the first audio, in which len will be 0 */ 40 | pCtx->songs.buf.pAudios = (synthAudio*)realloc(pCtx->songs.buf.pAudios, 41 | (1 + pCtx->songs.len * 2) * sizeof(synthAudio)); 42 | SYNTH_ASSERT_ERR(pCtx->songs.buf.pAudios, SYNTH_MEM_ERR); 43 | /* Clear only the new part of the buffer */ 44 | memset(&(pCtx->songs.buf.pAudios[pCtx->songs.used]), 0x0, 45 | (1 + pCtx->songs.len) * sizeof(synthAudio)); 46 | /* Actually increase the buffer length */ 47 | pCtx->songs.len += 1 + pCtx->songs.len; 48 | } 49 | 50 | /* Set the default BPM */ 51 | pCtx->songs.buf.pAudios[pCtx->songs.used].bpm = 60; 52 | /* Set the time signature to a whole note ('brevissima'); This should work 53 | * for any simple time signature (1/4, 2/4, 4/4 etc) */ 54 | pCtx->songs.buf.pAudios[pCtx->songs.used].timeSignature = 1 << 6; 55 | 56 | *ppAudio = &(pCtx->songs.buf.pAudios[pCtx->songs.used]); 57 | pCtx->songs.used++; 58 | rv = SYNTH_OK; 59 | __err: 60 | return rv; 61 | } 62 | 63 | /** 64 | * Compile a MML audio SDL_RWops into an object 65 | * 66 | * @param [ in]pAudio Object that will be filled with the compiled song 67 | * @param [ in]pCtx The synthesizer context 68 | * @param [ in]pFile File with the song's MML 69 | */ 70 | synth_err synthAudio_compileSDL_RWops(synthAudio *pAudio, synthCtx *pCtx, 71 | void *pFile) { 72 | #if defined(USE_SDL2) 73 | synth_err rv; 74 | 75 | /* Sanitize the arguments */ 76 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 77 | SYNTH_ASSERT_ERR(pAudio, SYNTH_BAD_PARAM_ERR); 78 | SYNTH_ASSERT_ERR(pFile, SYNTH_BAD_PARAM_ERR); 79 | 80 | /* Clear the audio */ 81 | memset(pAudio, 0x0, sizeof(synthAudio)); 82 | 83 | /* Init parser */ 84 | rv = synthLexer_initFromSDL_RWops(&(pCtx->lexCtx), pFile); 85 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 86 | rv = synthParser_init(&(pCtx->parserCtx), pCtx); 87 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 88 | 89 | /* Parse the audio */ 90 | rv = synthParser_getAudio(&(pCtx->parserCtx), pCtx, pAudio); 91 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 92 | 93 | rv = SYNTH_OK; 94 | __err: 95 | /* Clear the lexer, so any open file is closed */ 96 | synthLexer_clear(&(pCtx->lexCtx)); 97 | 98 | return rv; 99 | #else 100 | return SYNTH_FUNCTION_NOT_IMPLEMENTED; 101 | #endif 102 | } 103 | 104 | /** 105 | * Compile a MML audio file into an object 106 | * 107 | * @param [ in]pAudio Object that will be filled with the compiled song 108 | * @param [ in]pCtx The synthesizer context 109 | * @param [ in]pFilename File with the song's MML 110 | */ 111 | synth_err synthAudio_compileFile(synthAudio *pAudio, synthCtx *pCtx, 112 | char *pFilename) { 113 | synth_err rv; 114 | 115 | /* Sanitize the arguments */ 116 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 117 | SYNTH_ASSERT_ERR(pAudio, SYNTH_BAD_PARAM_ERR); 118 | SYNTH_ASSERT_ERR(pFilename, SYNTH_BAD_PARAM_ERR); 119 | /* The file is checked for existance before, so no need to do it again */ 120 | 121 | /* Clear the audio */ 122 | memset(pAudio, 0x0, sizeof(synthAudio)); 123 | 124 | /* Init parser */ 125 | rv = synthLexer_initFromFile(&(pCtx->lexCtx), pFilename); 126 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 127 | rv = synthParser_init(&(pCtx->parserCtx), pCtx); 128 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 129 | 130 | /* Parse the audio */ 131 | rv = synthParser_getAudio(&(pCtx->parserCtx), pCtx, pAudio); 132 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 133 | 134 | rv = SYNTH_OK; 135 | __err: 136 | /* Clear the lexer, so any open file is closed */ 137 | synthLexer_clear(&(pCtx->lexCtx)); 138 | 139 | return rv; 140 | } 141 | 142 | /** 143 | * Compile a MML audio string into a object 144 | * 145 | * @param [ in]pAudio Object that will be filled with the compiled song 146 | * @param [ in]pCtx The synthesizer context 147 | * @param [ in]pString The MML song 148 | * @param [ in]len The MML song's length 149 | */ 150 | synth_err synthAudio_compileString(synthAudio *pAudio, synthCtx *pCtx, 151 | char *pString, int len) { 152 | synth_err rv; 153 | 154 | /* Sanitize the arguments */ 155 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 156 | SYNTH_ASSERT_ERR(pAudio, SYNTH_BAD_PARAM_ERR); 157 | SYNTH_ASSERT_ERR(pString, SYNTH_BAD_PARAM_ERR); 158 | SYNTH_ASSERT_ERR(len > 0, SYNTH_BAD_PARAM_ERR); 159 | 160 | /* Clear the audio */ 161 | memset(pAudio, 0x0, sizeof(synthAudio)); 162 | 163 | /* Init parser */ 164 | rv = synthLexer_initFromString(&(pCtx->lexCtx), pString, len); 165 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 166 | rv = synthParser_init(&(pCtx->parserCtx), pCtx); 167 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 168 | 169 | /* Parse the audio */ 170 | rv = synthParser_getAudio(&(pCtx->parserCtx), pCtx, pAudio); 171 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 172 | 173 | rv = SYNTH_OK; 174 | __err: 175 | 176 | return rv; 177 | } 178 | 179 | /** 180 | * Return the audio BPM 181 | * 182 | * @param [out]pBpm The BPM 183 | * @param [ in]pAudio The audio 184 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 185 | */ 186 | synth_err synthAudio_getBpm(int *pBpm, synthAudio *pAudio) { 187 | synth_err rv; 188 | 189 | /* Sanitize the arguments */ 190 | SYNTH_ASSERT_ERR(pBpm, SYNTH_BAD_PARAM_ERR); 191 | SYNTH_ASSERT_ERR(pAudio, SYNTH_BAD_PARAM_ERR); 192 | 193 | *pBpm = pAudio->bpm; 194 | 195 | rv = SYNTH_OK; 196 | __err: 197 | return rv; 198 | } 199 | 200 | /** 201 | * Return the audio time signature 202 | * 203 | * @param [out]pTime The time signature 204 | * @param [ in]pAudio The audio 205 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 206 | */ 207 | synth_err synthAudio_getTimeSignature(int *pTime, synthAudio *pAudio) { 208 | synth_err rv; 209 | 210 | /* Sanitize the arguments */ 211 | SYNTH_ASSERT_ERR(pTime, SYNTH_BAD_PARAM_ERR); 212 | SYNTH_ASSERT_ERR(pAudio, SYNTH_BAD_PARAM_ERR); 213 | 214 | *pTime = pAudio->timeSignature; 215 | 216 | rv = SYNTH_OK; 217 | __err: 218 | return rv; 219 | } 220 | 221 | /** 222 | * Return the number of tracks in a song 223 | * 224 | * @param [out]pNum The number of tracks 225 | * @param [ in]pAudio The audio 226 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 227 | */ 228 | synth_err synthAudio_getTrackCount(int *pNum, synthAudio *pAudio) { 229 | synth_err rv; 230 | 231 | /* Sanitize the arguments */ 232 | SYNTH_ASSERT_ERR(pNum, SYNTH_BAD_PARAM_ERR); 233 | SYNTH_ASSERT_ERR(pAudio, SYNTH_BAD_PARAM_ERR); 234 | 235 | *pNum = pAudio->num; 236 | 237 | rv = SYNTH_OK; 238 | __err: 239 | return rv; 240 | } 241 | 242 | /** 243 | * Retrieve the number of samples in a track 244 | * 245 | * @param [out]pLen The length of the track in samples 246 | * @param [ in]pAudio The audio 247 | * @param [ in]pCtx The synthesizer context 248 | * @param [ in]track Track index 249 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_INVALID_INDEX 250 | */ 251 | synth_err synthAudio_getTrackLength(int *pLen, synthAudio *pAudio, 252 | synthCtx *pCtx, int track) { 253 | synth_err rv; 254 | 255 | /* Sanitize the arguments */ 256 | SYNTH_ASSERT_ERR(pLen, SYNTH_BAD_PARAM_ERR); 257 | SYNTH_ASSERT_ERR(pAudio, SYNTH_BAD_PARAM_ERR); 258 | /* Check that the track is valid */ 259 | SYNTH_ASSERT_ERR(track < pAudio->num, SYNTH_INVALID_INDEX); 260 | 261 | /* Make sure the renderer is at a compass start */ 262 | rv = synthRenderer_resetPosition(&(pCtx->renderCtx)); 263 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 264 | 265 | rv = synthTrack_getLength(pLen, 266 | &(pCtx->tracks.buf.pTracks[pAudio->tracksIndex + track]), pCtx); 267 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 268 | 269 | rv = SYNTH_OK; 270 | __err: 271 | return rv; 272 | } 273 | 274 | /** 275 | * Retrieve the number of samples until a track's loop point 276 | * 277 | * @param [out]pLen The length of the track's intro 278 | * @param [ in]pAudio The audio 279 | * @param [ in]pCtx The synthesizer context 280 | * @param [ in]track The track 281 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 282 | */ 283 | synth_err synthAudio_getTrackIntroLength(int *pLen, synthAudio *pAudio, 284 | synthCtx *pCtx, int track) { 285 | synth_err rv; 286 | 287 | /* Sanitize the arguments */ 288 | SYNTH_ASSERT_ERR(pLen, SYNTH_BAD_PARAM_ERR); 289 | SYNTH_ASSERT_ERR(pAudio, SYNTH_BAD_PARAM_ERR); 290 | /* Check that the track is valid */ 291 | SYNTH_ASSERT_ERR(track < pAudio->num, SYNTH_INVALID_INDEX); 292 | 293 | /* Make sure the renderer is at a compass start */ 294 | rv = synthRenderer_resetPosition(&(pCtx->renderCtx)); 295 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 296 | 297 | rv = synthTrack_getIntroLength(pLen, 298 | &(pCtx->tracks.buf.pTracks[pAudio->tracksIndex + track]), pCtx); 299 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 300 | 301 | rv = SYNTH_OK; 302 | __err: 303 | return rv; 304 | } 305 | 306 | /** 307 | * Retrieve whether a track is loopable or not 308 | * 309 | * @param [ in]pAudio The audio 310 | * @param [ in]pCtx The synthesizer context 311 | * @param [ in]track The track 312 | * @return SYNTH_TRUE, SYNTH_FALSE 313 | */ 314 | synth_bool synthAudio_isTrackLoopable(synthAudio *pAudio, synthCtx *pCtx, 315 | int track) { 316 | if (pAudio && track < pAudio->num) { 317 | return synthTrack_isLoopable( 318 | &(pCtx->tracks.buf.pTracks[pAudio->tracksIndex + track])); 319 | } 320 | return SYNTH_FALSE; 321 | } 322 | 323 | /** 324 | * Render a track into a buffer 325 | * 326 | * The buffer must be prepared by the caller, and it must have 327 | * 'synth_getTrackLength' bytes times the number of bytes per samples 328 | * 329 | * @param [ in]pBuf Buffer that will be filled with the track 330 | * @param [ in]pAudio The audio 331 | * @param [ in]pCtx The synthesizer context 332 | * @param [ in]pTrack The track 333 | * @param [ in]mode Desired mode for the wave 334 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 335 | */ 336 | synth_err synthAudio_renderTrack(char *pBuf, synthAudio *pAudio, synthCtx *pCtx, 337 | int track, synthBufMode mode) { 338 | synth_err rv; 339 | 340 | /* Sanitize the arguments */ 341 | SYNTH_ASSERT_ERR(pBuf, SYNTH_BAD_PARAM_ERR); 342 | SYNTH_ASSERT_ERR(pAudio, SYNTH_BAD_PARAM_ERR); 343 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 344 | /* Check that the track is valid */ 345 | SYNTH_ASSERT_ERR(track < pAudio->num, SYNTH_INVALID_INDEX); 346 | 347 | /* Make sure the renderer is at a compass start */ 348 | rv = synthRenderer_resetPosition(&(pCtx->renderCtx)); 349 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 350 | 351 | rv = synthTrack_render(pBuf, 352 | &(pCtx->tracks.buf.pTracks[pAudio->tracksIndex + track]), pCtx, 353 | mode); 354 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 355 | 356 | rv = SYNTH_OK; 357 | __err: 358 | return rv; 359 | } 360 | 361 | -------------------------------------------------------------------------------- /src/include/c_synth_internal/synth_types.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Define all types used within the library, but not 'publicly exported' to the 3 | * user 4 | * 5 | * @file src/include/synth_internal/synth_types.h 6 | */ 7 | #ifndef __SYNTH_INTERNAL_TYPES_H__ 8 | #define __SYNTH_INTERNAL_TYPES_H__ 9 | 10 | /* Required because of a FILE* */ 11 | #include 12 | 13 | #if defined(USE_SDL2) 14 | # include 15 | #endif 16 | 17 | /* First, define the name (i.e., typedef) of every type */ 18 | 19 | # ifndef __SYNTHAUDIO_STRUCT__ 20 | # define __SYNTHAUDIO_STRUCT__ 21 | typedef struct stSynthAudio synthAudio; 22 | # endif /* __SYNTHAUDIO_STRUCT__ */ 23 | # ifndef __SYNTHBUFFER_UNION__ 24 | # define __SYNTHBUFFER_UNION__ 25 | typedef union unSynthBuffer synthBuffer; 26 | # endif /* __SYNTHBUFFER_UNION__ */ 27 | # ifndef __SYNTHCTX_STRUCT__ 28 | # define __SYNTHCTX_STRUCT__ 29 | typedef struct stSynthCtx synthCtx; 30 | # endif /* __SYNTHCTX_STRUCT__ */ 31 | # ifndef __SYNTHLEXCTX_STRUCT__ 32 | # define __SYNTHLEXCTX_STRUCT__ 33 | typedef struct stSynthLexCtx synthLexCtx; 34 | # endif /* __SYNTHLEXCTX_STRUCT__ */ 35 | # ifndef __SYNTHLIST_STRUCT__ 36 | # define __SYNTHLIST_STRUCT__ 37 | typedef struct stSynthList synthList; 38 | # endif /* __SYNTHLIST_STRUCT__ */ 39 | # ifndef __SYNTHNOTE_STRUCT__ 40 | # define __SYNTHNOTE_STRUCT__ 41 | typedef struct stSynthNote synthNote; 42 | # endif /* __SYNTHNOTE_STRUCT__ */ 43 | # ifndef __SYNTHPARSERCTX_STRUCT__ 44 | # define __SYNTHPARSERCTX_STRUCT__ 45 | typedef struct stSynthParserCtx synthParserCtx; 46 | # endif /* __SYNTHPARSERCTX_STRUCT__ */ 47 | # ifndef __SYNTHRENDERERCTX_STRUCT__ 48 | # define __SYNTHRENDERERCTX_STRUCT__ 49 | typedef struct stSynthRendererCtx synthRendererCtx; 50 | # endif /* __SYNTHRENDERERCTX_STRUCT__ */ 51 | # ifndef __SYNTHPRNG_STRUCT__ 52 | # define __SYNTHPRNG_STRUCT__ 53 | typedef struct stSynthPRNGCtx synthPRNGCtx; 54 | # endif /* __SYNTHPRNG_STRUCTRUCT__ */ 55 | # ifndef __SYNTHSOURCE_UNION__ 56 | # define __SYNTHSOURCE_UNION__ 57 | typedef union unSynthSource synthSource; 58 | # endif /* __SYNTHSOURCE_UNION__ */ 59 | # ifndef __SYNTHSOURCETYPE_ENUM__ 60 | # define __SYNTHSOURCETYPE_ENUM__ 61 | typedef enum enSynthSourceType synthSourceType; 62 | # endif /* __SYNTHSOURCETYPE_ENUM__ */ 63 | # ifndef __SYNTHSTRING_STRUCT__ 64 | # define __SYNTHSTRING_STRUCT__ 65 | typedef struct stSynthString synthString; 66 | # endif /* __SYNTHSTRING_STRUCT__ */ 67 | # ifndef __SYNTHTRACK_STRUCT__ 68 | # define __SYNTHTRACK_STRUCT__ 69 | typedef struct stSynthTrack synthTrack; 70 | # endif /* __SYNTHTRACK_STRUCT__ */ 71 | # ifndef __SYNTHVOLUME_STRUCT__ 72 | # define __SYNTHVOLUME_STRUCT__ 73 | typedef struct stSynthVolume synthVolume; 74 | # endif /* __SYNTHVOLUME_STRUCT__ */ 75 | # ifndef __SYNTHBOOL_ENUM__ 76 | # define __SYNTHBOOL_ENUM__ 77 | typedef enum enSynthBool synth_bool; 78 | # endif /* __SYNTHBOOL_ENUM__ */ 79 | # ifndef __SYNTHWAVE_ENUM__ 80 | # define __SYNTHWAVE_ENUM__ 81 | typedef enum enSynthWave synth_wave; 82 | # endif /* __SYNTHWAVE_ENUM__ */ 83 | # ifndef __SYNTHNOTE_ENUM__ 84 | # define __SYNTHNOTE_ENUM__ 85 | typedef enum enSynthNote synth_note; 86 | # endif /* __SYNTHNOTE_ENUM__ */ 87 | # ifndef __SYNTHTOKEN_ENUM__ 88 | # define __SYNTHTOKEN_ENUM__ 89 | typedef enum enSynthToken synth_token; 90 | # endif /* __SYNTHTOKEN_ENUM__ */ 91 | 92 | /* Define every enumeration */ 93 | 94 | /* Simple boolean value */ 95 | enum enSynthBool { 96 | SYNTH_TRUE = 0, 97 | SYNTH_FALSE 98 | }; 99 | 100 | /* Wave types for a note */ 101 | enum enSynthWave { 102 | W_SQUARE = 0, 103 | W_PULSE_12_5, 104 | W_PULSE_25, 105 | W_PULSE_75, 106 | W_TRIANGLE, 107 | W_NOISE, 108 | W_NOISE_SQUARE, 109 | W_NOISE_12_5, 110 | W_NOISE_25, 111 | W_NOISE_75, 112 | W_NOISE_TRIANGLE, 113 | SYNTH_MAX_WAVE 114 | }; 115 | 116 | /* Musical notes */ 117 | enum enSynthNote { 118 | N_CB = 0, /* Required for decreasing octave */ 119 | N_C, 120 | N_CS, 121 | N_D, 122 | N_DS, 123 | N_E, 124 | N_F, 125 | N_FS, 126 | N_G, 127 | N_GS, 128 | N_A, 129 | N_AS, 130 | N_B, 131 | N_BS, /* Required for increasing octave */ 132 | N_REST, 133 | N_LOOP 134 | }; 135 | 136 | /* Tokens used on a song's compilation */ 137 | enum enSynthToken { 138 | T_MML = 0, 139 | T_SET_BPM, 140 | T_SET_DURATION, 141 | T_SET_OCTAVE, 142 | T_SET_REL_OCTAVE, 143 | T_SET_LOOPPOINT, 144 | T_END_OF_TRACK, 145 | T_SET_VOLUME, 146 | T_SET_ATTACK, 147 | T_SET_KEYOFF, 148 | T_SET_RELEASE, 149 | T_SET_PAN, 150 | T_SET_LOOP_START, 151 | T_SET_LOOP_END, 152 | T_SET_WAVE, 153 | T_OPEN_BRACKET, 154 | T_CLOSE_BRACKET, 155 | T_NOTE, 156 | T_DURATION, 157 | T_NUMBER, 158 | T_COMMA, 159 | T_DONE, 160 | T_EXTEND, 161 | TK_MAX 162 | }; 163 | 164 | /* Now, define every struct/union; Note that the order may be relevant, as there 165 | * are statically alloc'ed stuff into the structs; Obviously, in case of 166 | * pointers, the order becomes irrelevant */ 167 | 168 | /** Define a simple static string for parsing */ 169 | struct stSynthString { 170 | /** Total length of the string */ 171 | int len; 172 | /** Current position on the string */ 173 | int pos; 174 | /** Pointer to the static (and NULL-terminated) string */ 175 | char *pStr; 176 | }; 177 | 178 | /** Define a source for a MML audio, which can either be a file or a string */ 179 | union unSynthSource { 180 | #if defined(USE_SDL2) 181 | /** SDL's SDL_RWops, so it works on mobile! */ 182 | SDL_RWops *sdl; 183 | #endif 184 | /** A file */ 185 | FILE *file; 186 | /** A static string, with its current position and length */ 187 | synthString str; 188 | }; 189 | 190 | /** Defines all posible input types for the lexer */ 191 | enum enSynthSourceType { 192 | SST_NONE = 0, 193 | SST_FILE, 194 | SST_STR, 195 | SST_SDL, 196 | SST_MAX 197 | }; 198 | 199 | /** Define the context for the lexer */ 200 | struct stSynthLexCtx { 201 | /** Last read character */ 202 | char lastChar; 203 | /** Current input type */ 204 | synthSourceType type; 205 | /** Current line on the stream */ 206 | int line; 207 | /** Position inside the current line */ 208 | int linePos; 209 | /** Token read on the privous getToken call */ 210 | synth_token lastToken; 211 | /** Integer value gotten when reading a token */ 212 | int ivalue; 213 | /** MML's source; either a file descriptor or a string */ 214 | synthSource source; 215 | }; 216 | 217 | /** Define the context for the parser */ 218 | struct stSynthParserCtx { 219 | /** Expected token (only valid on error) */ 220 | synth_token expected; 221 | /** Gotten token (only valid on error) */ 222 | synth_token gotten; 223 | /** Whether an error occured or note */ 224 | synth_bool errorFlag; 225 | /** Which error code was raised */ 226 | synth_err errorCode; 227 | /** Current octave */ 228 | int octave; 229 | /** Default duration (when not specified) */ 230 | int duration; 231 | /** Index of the default volume */ 232 | int volume; 233 | /** Current attack */ 234 | int attack; 235 | /** Current keyoff */ 236 | int keyoff; 237 | /** Current release */ 238 | int release; 239 | /** Current pan */ 240 | int pan; 241 | /** Compass' time signature in binary fixed point notation */ 242 | int timeSignature; 243 | /** Length of the current compass in binary fixed point notation */ 244 | int curCompassLength; 245 | /** Current wave */ 246 | synth_wave wave; 247 | }; 248 | 249 | /** Struct with data about the currently rendering song/track */ 250 | struct stSynthRendererCtx { 251 | /** Number of samples per compass */ 252 | int samplesPerCompass; 253 | /** Current length of the compass in samples */ 254 | int curCompassLength; 255 | /** Audio time signature */ 256 | int timeSignature; 257 | /** Current position within the compass */ 258 | int curCompassPosition; 259 | }; 260 | 261 | /** Defines the types of noise wave generators */ 262 | enum enNoiseWaveType { 263 | NW_NONE = 0, 264 | NW_BOXMULLER, 265 | NW_ZIGGURAT 266 | }; 267 | 268 | /** Struct with the static parameters required by the Box-Muller algorithm */ 269 | struct stBoxMullerParams { 270 | /** A point, generated on the previous iteration */ 271 | double z0; 272 | /** The other point, generated on the previous iteration */ 273 | double z1; 274 | /** Whether the points where generated on the previous iteration */ 275 | int didGenerate; 276 | }; 277 | 278 | /** Struct with the static parameters required by the ziggurat algorithm */ 279 | struct stZigguratParams { 280 | /* TODO */ 281 | int nil; 282 | }; 283 | 284 | /** Parameter specific to algorithms used by the PRNG */ 285 | union stPRNGParams { 286 | struct stBoxMullerParams boxMuller; 287 | struct stZigguratParams ziggurat; 288 | }; 289 | 290 | /** Current context used by the pseudo random number generator */ 291 | struct stSynthPRNGCtx { 292 | /* Whether it was initialized */ 293 | int isInit; 294 | /** Value used by the PRNG formula */ 295 | unsigned int a; 296 | /** Value used by the PRNG formula */ 297 | unsigned int c; 298 | /** Latest seed */ 299 | unsigned int seed; 300 | /* The current noise wave generator */ 301 | enum enNoiseWaveType type; 302 | /** Params used by the current generator algorithm */ 303 | union stPRNGParams noiseParams; 304 | }; 305 | 306 | /** Union with all possible buffers (i.e., list of items) */ 307 | union unSynthBuffer { 308 | /* Points to an array of audios */ 309 | synthAudio *pAudios; 310 | /* Points to an array of notes */ 311 | synthNote *pNotes; 312 | /* Points to an array of tracks */ 313 | synthTrack *pTracks; 314 | /* Points to an array of volumes */ 315 | synthVolume *pVolumes; 316 | }; 317 | 318 | /** A generic list of a buffer */ 319 | struct stSynthList { 320 | /** How many itens may this list may hold, at most */ 321 | int max; 322 | /** How big is the list; Useful if there's no limit */ 323 | int len; 324 | /** How many itens are currently in use */ 325 | int used; 326 | /* TODO Add a map of used items? */ 327 | /** The actual list of itens */ 328 | synthBuffer buf; 329 | }; 330 | 331 | /* Define the main context */ 332 | struct stSynthCtx { 333 | /** 334 | * Whether the context was alloc'ed by the library (and, thus, must be 335 | * freed) or if it was alloc'ed by the user 336 | */ 337 | int autoAlloced; 338 | /** Synthesizer frequency in samples per second */ 339 | int frequency; 340 | /** List of songs */ 341 | synthList songs; 342 | /** List of tracks */ 343 | synthList tracks; 344 | /** List of notes */ 345 | synthList notes; 346 | /** List of volumes */ 347 | synthList volumes; 348 | /** Lexer context */ 349 | synthLexCtx lexCtx; 350 | /** Parser context */ 351 | synthParserCtx parserCtx; 352 | /** Pseudo-random number generator context */ 353 | synthPRNGCtx prngCtx; 354 | /** Keep track of whatever is being rendered */ 355 | synthRendererCtx renderCtx; 356 | }; 357 | 358 | /** Define an audio, which is simply an aggregation of tracks */ 359 | struct stSynthAudio { 360 | /** 361 | * Index to the first track in the synthesizer context; An offset was 362 | * chosen, in place of a pointer, because the base pointer might change, if 363 | * dynamic allocations are allowed; So, since the main context should always 364 | * be available (i.e., passed as parameter to the function), this seemed 365 | * like the cleanest way to do this 366 | */ 367 | int tracksIndex; 368 | /** How many tracks the song has */ 369 | int num; 370 | /** Song's 'speed' in beats-per-minute */ 371 | int bpm; 372 | /** Song's time signature */ 373 | int timeSignature; 374 | }; 375 | 376 | /** Define a track, which is almost simply a sequence of notes */ 377 | struct stSynthTrack { 378 | /** Cached length of the track, in samples */ 379 | int cachedLength; 380 | /** Cached loop of the track, in samples */ 381 | int cachedLoopPoint; 382 | /** Start point for repeating or -1, if shouldn't loop */ 383 | int loopPoint; 384 | /** 385 | * Index to the first note in the synthesizer context; An offset was 386 | * chosen, in place of a pointer, because the base pointer might change, if 387 | * dynamic allocations are allowed; So, since the main context should always 388 | * be available (i.e., passed as parameter to the function), this seemed 389 | * like the cleanest way to do this 390 | */ 391 | int notesIndex; 392 | /** Number of notes in this track */ 393 | int num; 394 | }; 395 | 396 | struct stSynthNote { 397 | /** 398 | * Value between 0 and 100, where 0 means only left channel and 100 means 399 | * only right channel 400 | */ 401 | char pan; 402 | /** Octave at which the note should play, from 1 to 8 */ 403 | char octave; 404 | /** 405 | * Duration of the note in samples (depends on the sample rate). 406 | * If type is N_loop, represent how many times should repeat. 407 | */ 408 | int len; 409 | /** 410 | * Note's duration in binary fixed point notation; It uses 6 bits for the 411 | * fractional part 412 | */ 413 | int duration; 414 | /** Cached duration of the note in samples */ 415 | int samplesDuration; 416 | /** Only used if type is N_loop; Represents note to which should jump. */ 417 | int jumpPosition; 418 | /** Time, in samples, until the note reaches its maximum amplitude */ 419 | int attack; 420 | /** After how many samples should the note be muted */ 421 | int keyoff; 422 | /** Time, in samples, until the note halts completely */ 423 | int release; 424 | /** Only used if type is N_loop; how many times has already looped */ 425 | int numIterations; 426 | /** Wave type to be synthesized */ 427 | synth_wave wave; 428 | /** Musical note to be played */ 429 | synth_note note; 430 | /** Index to either a value between 0x0 and 0xff or a envelop */ 431 | int volume; 432 | }; 433 | 434 | /** Define a simple note envelop */ 435 | struct stSynthVolume { 436 | /** Initial volume */ 437 | int ini; 438 | /** Final volume */ 439 | int fin; 440 | }; 441 | 442 | #endif /* __SYNTH_INTERNAL_TYPES_H__ */ 443 | 444 | -------------------------------------------------------------------------------- /src/synth_track.c: -------------------------------------------------------------------------------- 1 | /** 2 | * A sequence of notes 3 | * 4 | * @file src/synth_track.c 5 | */ 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | /** 19 | * Retrieve a new track (alloc it as possible and necessary) 20 | * 21 | * @param [out]ppTrack The new track 22 | * @param [ in]pCtx The synthesizer context 23 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, SYNTH_MEM_ERR 24 | */ 25 | synth_err synthTrack_init(synthTrack **ppTrack, synthCtx *pCtx) { 26 | synth_err rv; 27 | 28 | /* Sanitize the arguments */ 29 | SYNTH_ASSERT_ERR(ppTrack, SYNTH_BAD_PARAM_ERR); 30 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 31 | 32 | /* Make sure there's enough space for another track */ 33 | SYNTH_ASSERT_ERR(pCtx->tracks.max == 0 || 34 | pCtx->tracks.used < pCtx->tracks.max, SYNTH_MEM_ERR); 35 | 36 | /* Expand the array as necessary */ 37 | if (pCtx->tracks.used >= pCtx->tracks.len) { 38 | /* 'Double' the current buffer; Note that this will never be called if 39 | * the context was pre-alloc'ed, since 'max' will be set; The '+1' is 40 | * for the first audio, in which len will be 0 */ 41 | pCtx->tracks.buf.pTracks = (synthTrack*)realloc( 42 | pCtx->tracks.buf.pTracks, (1 + pCtx->tracks.len * 2) * 43 | sizeof(synthTrack)); 44 | SYNTH_ASSERT_ERR(pCtx->tracks.buf.pTracks, SYNTH_MEM_ERR); 45 | /* Clear only the new part of the buffer */ 46 | memset(&(pCtx->tracks.buf.pTracks[pCtx->tracks.used]), 0x0, 47 | (1 + pCtx->tracks.len) * sizeof(synthTrack)); 48 | /* Actually increase the buffer length */ 49 | pCtx->tracks.len += 1 + pCtx->tracks.len; 50 | } 51 | 52 | /* Retrieve the next track */ 53 | *ppTrack = &(pCtx->tracks.buf.pTracks[pCtx->tracks.used]); 54 | pCtx->tracks.used++; 55 | 56 | /* Initialize the track as not being looped and without any notes */ 57 | (*ppTrack)->loopPoint = -1; 58 | (*ppTrack)->notesIndex = pCtx->notes.used; 59 | 60 | rv = SYNTH_OK; 61 | __err: 62 | return rv; 63 | } 64 | 65 | /* !!!WARNING!!! This functions calls 'synthTrack_countSample', which in turn 66 | * MAY call this function back. Since the song is parsed from a file or from a 67 | * string it's guaranteed to end and there should be no infinite loops. 68 | * 69 | * However, beware that "bad things may happen"... */ 70 | static synth_err synthTrack_getLoopLength(int *pLen, synthTrack *pTrack, 71 | synthCtx *pCtx, int pos); 72 | 73 | /** 74 | * Loop through some notes and accumulate their samples 75 | * 76 | * This was mostly done to avoid repeating the loop count, thus all 77 | * verifications are done in previous calls 78 | * 79 | * Also, don't forget that the loop is done in inverse order, from the last note 80 | * to the first. So, initialPos must always be greater than finalPosition 81 | * 82 | * @param [out]pLen The length of the track in samples 83 | * @param [ in]pTrack The track 84 | * @param [ in]pCtx The synthesizer context 85 | * @param [ in]initalPos Initial position (inclusive) 86 | * @param [ in]finalPos Final position (inclusive) 87 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 88 | */ 89 | static synth_err synthTrack_countSample(int *pLen, synthTrack *pTrack, 90 | synthCtx *pCtx, int initialPos, int finalPosition) { 91 | int i, len; 92 | synthNote *pNote; 93 | synth_err rv; 94 | 95 | len = 0; 96 | 97 | /* Simply loop though all notes */ 98 | i = initialPos; 99 | while (i >= finalPosition) { 100 | int tmp; 101 | 102 | /* Retrieve the current note */ 103 | pNote = &(pCtx->notes.buf.pNotes[pTrack->notesIndex + i]); 104 | 105 | /* Check if note is a loop */ 106 | if (synthNote_isLoop(pNote) == SYNTH_TRUE) { 107 | int pos; 108 | 109 | /* Retrieve the length of the loop, in samples (already taking into 110 | * account the number of repetitions) */ 111 | rv = synthTrack_getLoopLength(&tmp, pTrack, pCtx, i); 112 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 113 | 114 | /* Get the loop destination */ 115 | rv = synthNote_getJumpPosition(&pos, pNote); 116 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 117 | /* Update the curent note accordingly */ 118 | i = pos; 119 | 120 | len += tmp; 121 | } 122 | else { 123 | /* Accumulate the note duration */ 124 | rv = synthRenderer_getNoteLengthAndUpdate(&tmp, &(pCtx->renderCtx), 125 | pNote); 126 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 127 | 128 | len += tmp; 129 | } 130 | 131 | i--; 132 | } 133 | 134 | *pLen = len; 135 | rv = SYNTH_OK; 136 | __err: 137 | return rv; 138 | } 139 | 140 | /** 141 | * Count how many samples there are in a loop 142 | * 143 | * Since loops are recursive, this function may also be called recursively 144 | * 145 | * @param [out]pLen The length of the track in samples 146 | * @param [ in]pTrack The track 147 | * @param [ in]pCtx The synthesizer context 148 | * @param [ in]pos Position of the loop note 149 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 150 | */ 151 | static synth_err synthTrack_getLoopLength(int *pLen, synthTrack *pTrack, 152 | synthCtx *pCtx, int pos) { 153 | int jumpPosition, repeatCount; 154 | synthNote *pNote; 155 | synth_err rv; 156 | 157 | /* Retrieve the current note */ 158 | pNote = &(pCtx->notes.buf.pNotes[pTrack->notesIndex + pos]); 159 | 160 | /* Retrieve the loop attributes */ 161 | rv = synthNote_getRepeat(&repeatCount, pNote); 162 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 163 | rv = synthNote_getJumpPosition(&jumpPosition, pNote); 164 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 165 | 166 | /* Loop through all notes and calculate the total length */ 167 | rv = synthTrack_countSample(pLen, pTrack, pCtx, pos - 1, jumpPosition); 168 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 169 | 170 | *pLen *= repeatCount; 171 | rv = SYNTH_OK; 172 | __err: 173 | return rv; 174 | } 175 | 176 | /** 177 | * Retrieve the number of samples in a track 178 | * 179 | * @param [out]pLen The length of the track in samples 180 | * @param [ in]pTrack The track 181 | * @param [ in]pCtx The synthesizer context 182 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 183 | */ 184 | synth_err synthTrack_getLength(int *pLen, synthTrack *pTrack, synthCtx *pCtx) { 185 | synth_err rv; 186 | 187 | /* Sanitize the arguments */ 188 | SYNTH_ASSERT_ERR(pLen, SYNTH_BAD_PARAM_ERR); 189 | SYNTH_ASSERT_ERR(pTrack, SYNTH_BAD_PARAM_ERR); 190 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 191 | 192 | /* Check if the value has already been calculated */ 193 | if (pTrack->cachedLength == 0) { 194 | int len; 195 | 196 | /* Count from the last note so we can recursivelly calculate all loops 197 | * lengths */ 198 | rv = synthTrack_countSample(&len, pTrack, pCtx, pTrack->num - 1, 0); 199 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 200 | 201 | /* Cached the length so it can be used later */ 202 | pTrack->cachedLength = len; 203 | } 204 | 205 | /* Retrieve the cached length */ 206 | *pLen = pTrack->cachedLength; 207 | 208 | rv = SYNTH_OK; 209 | __err: 210 | return rv; 211 | } 212 | 213 | /** 214 | * Retrieve the number of samples until a track's loop point 215 | * 216 | * @param [out]pLen The length of the track's intro 217 | * @param [ in]pTrack The track 218 | * @param [ in]pCtx The synthesizer context 219 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 220 | */ 221 | synth_err synthTrack_getIntroLength(int *pLen, synthTrack *pTrack, 222 | synthCtx *pCtx) { 223 | synth_err rv; 224 | 225 | /* Sanitize the arguments */ 226 | SYNTH_ASSERT_ERR(pLen, SYNTH_BAD_PARAM_ERR); 227 | SYNTH_ASSERT_ERR(pTrack, SYNTH_BAD_PARAM_ERR); 228 | SYNTH_ASSERT_ERR(pCtx, SYNTH_BAD_PARAM_ERR); 229 | 230 | /* Check if the value needs to be calculated */ 231 | if (pTrack->cachedLoopPoint == 0) { 232 | int len; 233 | 234 | /* Count how many samples there are from the loop point to the song 235 | * start */ 236 | if (pTrack->loopPoint != -1) { 237 | rv = synthTrack_countSample(&len, pTrack, pCtx, 238 | pTrack->loopPoint - 1, 0); 239 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 240 | } 241 | else { 242 | len = 0; 243 | } 244 | 245 | /* Cache the value so it can be used later */ 246 | pTrack->cachedLoopPoint = len; 247 | } 248 | 249 | /* Retrieve the cached value */ 250 | *pLen = pTrack->cachedLoopPoint; 251 | 252 | rv = SYNTH_OK; 253 | __err: 254 | return rv; 255 | } 256 | 257 | /** 258 | * Retrieve whether a track is loopable or not 259 | * 260 | * @param [ in]pTrack The track 261 | * @return SYNTH_TRUE, SYNTH_FALSE 262 | */ 263 | synth_bool synthTrack_isLoopable(synthTrack *pTrack) { 264 | if (pTrack && pTrack->loopPoint != -1) { 265 | return SYNTH_TRUE; 266 | } 267 | return SYNTH_FALSE; 268 | } 269 | 270 | /** 271 | * Renders a sequence of notes 272 | * 273 | * Note that the track is rendered in inverse order, from the last to the first 274 | * note. Therefore, 'i' must be greater than 'dst' (though it isn't checked!!) 275 | * 276 | * @param [out]pBytes How many bytes were rendered (so the buffer's position 277 | * can be updated accordingly) 278 | * @param [ in]pBuf End of the buffer to be filled with the rendered sequence 279 | * @param [ in]pTrack The track 280 | * @param [ in]pCtx The synthesizer context 281 | * @param [ in]mode Current rendering mode 282 | * @param [ in]i Current position into the sequence of notes 283 | * @param [ in]dst Last note to be rendered 284 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR, ... 285 | */ 286 | static synth_err synthTrack_renderSequence(int *pBytes, char *pBuf, 287 | synthTrack *pTrack, synthCtx *pCtx, synthBufMode mode, int i, int dst) { 288 | int bytes; 289 | synth_err rv; 290 | 291 | bytes = 0; 292 | 293 | while (i >= dst) { 294 | synthNote *pNote; 295 | 296 | /* Retrieve the current note */ 297 | pNote = &(pCtx->notes.buf.pNotes[pTrack->notesIndex + i]); 298 | 299 | /* Check if it's a loop or a common note */ 300 | if (synthNote_isLoop(pNote) == SYNTH_TRUE) { 301 | int count, jumpPosition, repeatCount, tmpBytes; 302 | 303 | /* Get the loop parameters */ 304 | rv = synthNote_getRepeat(&repeatCount, pNote); 305 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 306 | rv = synthNote_getJumpPosition(&jumpPosition, pNote); 307 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 308 | 309 | /* Render the loop and any sub-loops */ 310 | rv = synthTrack_renderSequence(&tmpBytes, pBuf, pTrack, pCtx, mode, 311 | i - 1, jumpPosition); 312 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 313 | 314 | /* Move the buffer back as many bytes as were rendered */ 315 | pBuf -= tmpBytes; 316 | 317 | /* Copy the sequence back into the buffer as many times as 318 | * necessary */ 319 | count = 1; 320 | while (count < repeatCount) { 321 | memcpy(pBuf - tmpBytes, pBuf, tmpBytes); 322 | count++; 323 | /* Move the buffer back before the sequence */ 324 | pBuf -= tmpBytes; 325 | } 326 | 327 | /* Update the number of bytes rendered */ 328 | bytes += tmpBytes * repeatCount; 329 | 330 | /* Place the buffer as if it just rendered the last note on the 331 | * loop (it will be decreased afterward */ 332 | i = jumpPosition; 333 | } 334 | else { 335 | int duration, durationSamples; 336 | 337 | /* Get the note's duration in samples */ 338 | rv = synthRenderer_getNoteLengthAndUpdate(&durationSamples, 339 | &(pCtx->renderCtx), pNote); 340 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 341 | 342 | duration = durationSamples; 343 | /* Convert the number of samples into bytes */ 344 | if (mode & SYNTH_16BITS) { 345 | duration *= 2; 346 | } 347 | if (mode & SYNTH_2CHAN) { 348 | duration *= 2; 349 | } 350 | 351 | /* Place the buffer at the start of the note */ 352 | pBuf -= duration; 353 | 354 | /* Render the current note */ 355 | rv = synthNote_render(pBuf, pNote, pCtx, mode, pCtx->frequency, 356 | durationSamples); 357 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 358 | 359 | /* Update the amount of bytes rendered */ 360 | bytes += duration; 361 | } 362 | 363 | i--; 364 | } 365 | 366 | *pBytes = bytes; 367 | rv = SYNTH_OK; 368 | __err: 369 | return rv; 370 | } 371 | 372 | /** 373 | * Render a full track into a buffer 374 | * 375 | * The buffer must be prepared by the caller, and it must have 376 | * 'synth_getTrackLength' bytes times the number of bytes per samples 377 | * 378 | * @param [ in]pBuf Buffer that will be filled with the track 379 | * @param [ in]pTrack The track 380 | * @param [ in]pCtx The synthesizer context 381 | * @param [ in]mode Desired mode for the wave 382 | * @return SYNTH_OK, SYNTH_BAD_PARAM_ERR 383 | */ 384 | synth_err synthTrack_render(char *pBuf, synthTrack *pTrack, synthCtx *pCtx, 385 | synthBufMode mode) { 386 | int len, tmp; 387 | synth_err rv; 388 | 389 | /* Retrieve the track's duration in samples */ 390 | rv = synthTrack_getLength(&len, pTrack, pCtx); 391 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 392 | 393 | /* Convert the number of samples into bytes */ 394 | if (mode & SYNTH_16BITS) { 395 | len *= 2; 396 | } 397 | if (mode & SYNTH_2CHAN) { 398 | len *= 2; 399 | } 400 | 401 | /* Place the buffer at its expected end */ 402 | pBuf += len; 403 | 404 | /* Loop through all notes and render 'em */ 405 | rv = synthTrack_renderSequence(&tmp, pBuf, pTrack, pCtx, mode, 406 | pTrack->num - 1, 0); 407 | SYNTH_ASSERT_ERR(rv == SYNTH_OK, rv); 408 | 409 | rv = SYNTH_OK; 410 | __err: 411 | return rv; 412 | } 413 | 414 | -------------------------------------------------------------------------------- /tst/tst_playMultipleSongsSDL2.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple test to render multiple songs and play 'em all (using SDL2 as the 3 | * backend) 4 | * 5 | * @file tst/tst_playMultipleSongsSDL2.c 6 | */ 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | /* Structure to hold every track pointer as well as how many samples/bytes were 18 | * played */ 19 | struct stSharedData { 20 | /** Array with tracks' data */ 21 | char **ppBufs; 22 | /** Length of each track */ 23 | int *pBufLens; 24 | /** Number of tracks */ 25 | int numBufs; 26 | /** Number of played samples/bytes */ 27 | int pos; 28 | /** Current mode being played */ 29 | synthBufMode mode; 30 | }; 31 | 32 | static void audioCallback(void *pArg, Uint8 *pStream, int len) { 33 | int i; 34 | struct stSharedData *pData; 35 | synthBufMode mode; 36 | 37 | /* Retrieve the data */ 38 | pData = (struct stSharedData*)pArg; 39 | mode = pData->mode; 40 | 41 | /* Clear the buffer (so there's no extra noise) */ 42 | memset(pStream, 0x0, len); 43 | 44 | /* Mix each track data */ 45 | i = 0; 46 | while (i < pData->numBufs) { 47 | char *pBuf; 48 | int j, bufLen; 49 | 50 | /* Retrieve the current buffer */ 51 | pBuf = pData->ppBufs[i]; 52 | bufLen = pData->pBufLens[i]; 53 | 54 | /* Already increase the counter, so we can loop early */ 55 | i++; 56 | if (pData->pos >= bufLen) { 57 | continue; 58 | } 59 | 60 | /* Loop through the track according with the mode */ 61 | j = 0; 62 | while (j < len) { 63 | if (pData->pos + j >= bufLen) { 64 | break; 65 | } 66 | 67 | if ((mode & SYNTH_1CHAN) && (mode & SYNTH_8BITS) && 68 | (mode & SYNTH_UNSIGNED)) { 69 | Uint8 chan, dst; 70 | 71 | /* Retrieve the current stream and the channel data */ 72 | chan = pBuf[pData->pos + j] & 0x00ff; 73 | dst = pStream[j] & 0x00ff; 74 | 75 | /* 'Mix' both */ 76 | dst += chan; 77 | 78 | /* Return it to the stream */ 79 | pStream[j] = dst & 0x00ff; 80 | 81 | j++; 82 | } 83 | else if ((mode & SYNTH_1CHAN) && (mode & SYNTH_8BITS) && 84 | (mode & SYNTH_SIGNED)) { 85 | Sint8 chan, dst; 86 | 87 | /* Retrieve the current stream and the channel data */ 88 | chan = pBuf[pData->pos + j] & 0x00ff; 89 | dst = pStream[j] & 0x00ff; 90 | 91 | /* 'Mix' both */ 92 | dst += chan; 93 | 94 | /* Return it to the stream */ 95 | pStream[j] = dst & 0x00ff; 96 | 97 | j++; 98 | } 99 | else if ((mode & SYNTH_1CHAN) && (mode & SYNTH_16BITS) && 100 | (mode & SYNTH_UNSIGNED)) { 101 | Uint16 chan, dst; 102 | 103 | /* Retrieve the current stream and the channel data */ 104 | chan = (pBuf[pData->pos + j] & 0x00ff) | 105 | ((pBuf[pData->pos + j + 1] << 8) & 0xff00); 106 | dst = (pStream[j] & 0x00ff) | ((pStream[j + 1] << 8) & 0xff00); 107 | 108 | /* 'Mix' both */ 109 | dst += chan; 110 | 111 | /* Return it to the stream */ 112 | pStream[j] = dst & 0x00ff; 113 | pStream[j + 1] = (dst >> 8) & 0x00ff; 114 | 115 | j += 2; 116 | } 117 | else if ((mode & SYNTH_1CHAN) && (mode & SYNTH_16BITS) && 118 | (mode & SYNTH_SIGNED)) { 119 | Sint16 chan, dst; 120 | 121 | /* Retrieve the current stream and the channel data */ 122 | chan = (pBuf[pData->pos + j] & 0x00ff) | 123 | ((pBuf[pData->pos + j + 1] << 8) & 0xff00); 124 | dst = (pStream[j] & 0x00ff) | ((pStream[j + 1] << 8) & 0xff00); 125 | 126 | /* 'Mix' both */ 127 | dst += chan; 128 | 129 | /* Return it to the stream */ 130 | pStream[j] = dst & 0x00ff; 131 | pStream[j + 1] = (dst >> 8) & 0x00ff; 132 | 133 | j += 2; 134 | } 135 | else if ((mode & SYNTH_2CHAN) && (mode & SYNTH_8BITS) && 136 | (mode & SYNTH_UNSIGNED)) { 137 | Uint8 chan1, chan2, dst1, dst2; 138 | 139 | /* Retrieve the current stream and the channel data */ 140 | chan1 = pBuf[pData->pos + j] & 0x00ff; 141 | chan2 = pBuf[pData->pos + j + 1] & 0x00ff; 142 | dst1 = pStream[j] & 0x00ff; 143 | dst2 = pStream[j + 1] & 0x00ff; 144 | 145 | /* 'Mix' both */ 146 | dst1 += chan1; 147 | dst2 += chan2; 148 | 149 | /* Return it to the stream */ 150 | pStream[j] = dst1 & 0x00ff; 151 | pStream[j + 1] = dst2 & 0x00ff; 152 | 153 | j += 2; 154 | } 155 | else if ((mode & SYNTH_2CHAN) && (mode & SYNTH_8BITS) && 156 | (mode & SYNTH_SIGNED)) { 157 | Sint8 chan1, chan2, dst1, dst2; 158 | 159 | /* Retrieve the current stream and the channel data */ 160 | chan1 = pBuf[pData->pos + j] & 0x00ff; 161 | chan2 = pBuf[pData->pos + j + 1] & 0x00ff; 162 | dst1 = pStream[j] & 0x00ff; 163 | dst2 = pStream[j + 1] & 0x00ff; 164 | 165 | /* 'Mix' both */ 166 | dst1 += chan1; 167 | dst2 += chan2; 168 | 169 | /* Return it to the stream */ 170 | pStream[j] = dst1 & 0x00ff; 171 | pStream[j + 1] = dst2 & 0x00ff; 172 | 173 | j += 2; 174 | } 175 | else if ((mode & SYNTH_2CHAN) && (mode & SYNTH_16BITS) && 176 | (mode & SYNTH_UNSIGNED)) { 177 | Uint16 chan1, chan2, dst1, dst2; 178 | 179 | /* Retrieve the current stream and the channel data */ 180 | chan1 = (pBuf[pData->pos + j] & 0x00ff) | 181 | ((pBuf[pData->pos + j + 1] << 8) & 0xff00); 182 | chan2 = (pBuf[pData->pos + j + 2] & 0x00ff) | 183 | ((pBuf[pData->pos + j + 3] << 8) & 0xff00); 184 | dst1 = (pStream[j] & 0x00ff) | 185 | ((pStream[j + 1] << 8) & 0xff00); 186 | dst2 = (pStream[j + 2] & 0x00ff) | 187 | ((pStream[j + 3] << 8) & 0xff00); 188 | 189 | /* 'Mix' both */ 190 | dst1 += chan1; 191 | dst2 += chan2; 192 | 193 | /* Return it to the stream */ 194 | pStream[j] = dst1 & 0x00ff; 195 | pStream[j + 1] = (dst1 >> 8) & 0x00ff; 196 | pStream[j + 2] = dst2 & 0x00ff; 197 | pStream[j + 3] = (dst2 >> 8) & 0x00ff; 198 | 199 | j += 4; 200 | } 201 | else if ((mode & SYNTH_2CHAN) && (mode & SYNTH_16BITS) && 202 | (mode & SYNTH_SIGNED)) { 203 | Sint16 chan1, chan2, dst1, dst2; 204 | 205 | /* Retrieve the current stream and the channel data */ 206 | chan1 = (pBuf[pData->pos + j] & 0x00ff) | 207 | ((pBuf[pData->pos + j + 1] << 8) & 0xff00); 208 | chan2 = (pBuf[pData->pos + j + 2] & 0x00ff) | 209 | ((pBuf[pData->pos + j + 3] << 8) & 0xff00); 210 | dst1 = (pStream[j] & 0x00ff) | 211 | ((pStream[j + 1] << 8) & 0xff00); 212 | dst2 = (pStream[j + 2] & 0x00ff) | 213 | ((pStream[j + 3] << 8) & 0xff00); 214 | 215 | /* 'Mix' both */ 216 | dst1 += chan1; 217 | dst2 += chan2; 218 | 219 | /* Return it to the stream */ 220 | pStream[j] = dst1 & 0x00ff; 221 | pStream[j + 1] = (dst1 >> 8) & 0x00ff; 222 | pStream[j + 2] = dst2 & 0x00ff; 223 | pStream[j + 3] = (dst2 >> 8) & 0x00ff; 224 | 225 | j += 4; 226 | } 227 | } 228 | } 229 | 230 | pData->pos += len; 231 | } 232 | 233 | /** 234 | * Entry point 235 | * 236 | * @param [ in]argc Number of arguments 237 | * @param [ in]argv List of arguments 238 | * @return The exit code 239 | */ 240 | int main(int argc, char *argv[]) { 241 | char **ppBufs; 242 | int didInitSDL, freq, i, irv, j, numSongs, maxLen, *pBufLens; 243 | SDL_AudioDeviceID dev; 244 | SDL_AudioSpec wanted, specs; 245 | struct stSharedData data; 246 | synthBufMode mode; 247 | synthCtx *pCtx; 248 | synth_err rv; 249 | 250 | /* Clean the context, so it's not freed on error */ 251 | didInitSDL = 0; 252 | pCtx = 0; 253 | ppBufs = 0; 254 | pBufLens = 0; 255 | dev = 0; 256 | 257 | /* Store the default frequency */ 258 | freq = 44100; 259 | /* Store the default mode */ 260 | mode = SYNTH_1CHAN_U8BITS; 261 | numSongs = 0; 262 | /* Check argc/argv */ 263 | if (argc > 1) { 264 | int i; 265 | 266 | i = 1; 267 | while (i < argc) { 268 | #define IS_PARAM(l_cmd, s_cmd) \ 269 | if (strcmp(argv[i], l_cmd) == 0 || strcmp(argv[i], s_cmd) == 0) 270 | IS_PARAM("--file", "-f") { 271 | if (argc <= i + 1) { 272 | printf("Expected parameter but got nothing! Run " 273 | "'tst_playSongSDL2 --help' for usage!\n"); 274 | return 1; 275 | } 276 | /* Count how many songs there are */ 277 | numSongs++; 278 | } 279 | IS_PARAM("--frequency", "-F") { 280 | char *pNum; 281 | int tmp; 282 | 283 | if (argc <= i + 1) { 284 | printf("Expected parameter but got nothing! Run " 285 | "'tst_playSongSDL2 --help' for usage!\n"); 286 | return 1; 287 | } 288 | 289 | pNum = argv[i + 1]; 290 | 291 | tmp = 0; 292 | 293 | while (*pNum != '\0') { 294 | tmp = tmp * 10 + (*pNum) - '0'; 295 | pNum++; 296 | } 297 | 298 | freq = tmp; 299 | } 300 | IS_PARAM("--mode", "-m") { 301 | char *pMode; 302 | 303 | if (argc <= i + 1) { 304 | printf("Expected parameter but got nothing! Run " 305 | "'tst_playSongSDL2 --help' for usage!\n"); 306 | return 1; 307 | } 308 | 309 | pMode = argv[i + 1]; 310 | 311 | if (strcmp(pMode, "1chan-u8") == 0) { 312 | mode = SYNTH_1CHAN_U8BITS; 313 | } 314 | else if (strcmp(pMode, "1chan-8") == 0) { 315 | mode = SYNTH_1CHAN_8BITS; 316 | } 317 | else if (strcmp(pMode, "1chan-u16") == 0) { 318 | mode = SYNTH_1CHAN_U16BITS; 319 | } 320 | else if (strcmp(pMode, "1chan-16") == 0) { 321 | mode = SYNTH_1CHAN_16BITS; 322 | } 323 | else if (strcmp(pMode, "2chan-u8") == 0) { 324 | mode = SYNTH_2CHAN_U8BITS; 325 | } 326 | else if (strcmp(pMode, "2chan-8") == 0) { 327 | mode = SYNTH_2CHAN_8BITS; 328 | } 329 | else if (strcmp(pMode, "2chan-u16") == 0) { 330 | mode = SYNTH_2CHAN_U16BITS; 331 | } 332 | else if (strcmp(pMode, "2chan-16") == 0) { 333 | mode = SYNTH_2CHAN_16BITS; 334 | } 335 | else { 336 | printf("Invalid mode! Run 'tst_playSongSDL2 --help' to " 337 | "check the usage!\n"); 338 | return 1; 339 | } 340 | } 341 | IS_PARAM("--help", "-h") { 342 | printf("A simple test for the c_synth library\n" 343 | "\n" 344 | "Usage: tst_playSongSDL2 [--string | -s \"the song\"] " 345 | "[--file | -f ]\n" 346 | " [--frequency | -F ] " 347 | "[--mode | -m ]\n" 348 | " [--help | -h]\n" 349 | "\n" 350 | "Compiles a single song and the plays it once.\n" 351 | "\n" 352 | "Note that this test ignores a song loop point and " 353 | "tracks with different\n" 354 | "lengths.\n" 355 | "\n" 356 | "'' must be one of the following:\n" 357 | " 1chan-u8 : 1 channel, unsigned 8 bits samples\n" 358 | " 1chan-8 : 1 channel, signed 8 bits samples\n" 359 | " 1chan-u16: 1 channel, unsigned 16 bits samples\n" 360 | " 1chan-16 : 1 channel, signed 16 bits samples\n" 361 | " 2chan-u8 : 2 channel, unsigned 8 bits samples\n" 362 | " 2chan-8 : 2 channel, signed 8 bits samples\n" 363 | " 2chan-u16: 2 channel, unsigned 16 bits samples\n" 364 | " 2chan-16 : 2 channel, signed 16 bits samples\n" 365 | "\n" 366 | "If no argument is passed, it will compile a simple " 367 | "test song.\n"); 368 | return 0; 369 | } 370 | 371 | i += 2; 372 | #undef IS_PARAM 373 | } 374 | } 375 | 376 | /* Check that there is more than one song */ 377 | SYNTH_ASSERT_ERR(numSongs > 1, SYNTH_BAD_PARAM_ERR); 378 | 379 | /* Initialize the synthesizer */ 380 | printf("Initialize the synthesizer...\n"); 381 | rv = synth_init(&pCtx, freq); 382 | SYNTH_ASSERT(rv == SYNTH_OK); 383 | 384 | /* Create arrays for storing the buffers and lengths of each song */ 385 | ppBufs = (char**)malloc(sizeof(char*) * numSongs); 386 | SYNTH_ASSERT_ERR(ppBufs, SYNTH_MEM_ERR); 387 | memset(ppBufs, 0x0, sizeof(char*) * numSongs); 388 | 389 | pBufLens = (int*)malloc(sizeof(int) * numSongs); 390 | SYNTH_ASSERT_ERR(pBufLens, SYNTH_MEM_ERR); 391 | 392 | /* Compile every song */ 393 | i = 0; 394 | j = 0; 395 | maxLen = 0; 396 | while (i < argc) { 397 | char *pBuf, *pSrc; 398 | int handle, intro, len, numTracks, reqLen; 399 | 400 | if (strcmp(argv[i], "--file") != 0 && strcmp(argv[i], "-f") != 0) { 401 | i++; 402 | continue; 403 | } 404 | 405 | /* Retrieve the filename */ 406 | pSrc = argv[i + 1]; 407 | 408 | printf("Compiling song from file '%s'...\n", pSrc); 409 | rv = synth_compileSongFromFile(&handle, pCtx, pSrc); 410 | if (rv != SYNTH_OK) { 411 | char *pError; 412 | synth_err irv; 413 | 414 | /* Retrieve and print the error */ 415 | irv = synth_getCompilerErrorString(&pError, pCtx); 416 | SYNTH_ASSERT_ERR(irv == SYNTH_OK, irv); 417 | 418 | printf("%s", pError); 419 | } 420 | else { 421 | printf("Song compiled successfully into handle %i!\n", handle); 422 | } 423 | SYNTH_ASSERT(rv == SYNTH_OK); 424 | 425 | /* Make sure there is only one track in the songs */ 426 | printf("Checking number of tracks in the song...\n"); 427 | rv = synth_getAudioTrackCount(&numTracks, pCtx, handle); 428 | SYNTH_ASSERT(rv == SYNTH_OK); 429 | if (numTracks != 1) { 430 | printf("Found more than one track in song '%s'!\n Aborting...\n", 431 | pSrc); 432 | SYNTH_ASSERT_ERR(0, SYNTH_BAD_PARAM_ERR); 433 | } 434 | 435 | /* Get the number of samples in the track */ 436 | rv = synth_getTrackIntroLength(&intro, pCtx, handle, 0 /* track */); 437 | SYNTH_ASSERT(rv == SYNTH_OK); 438 | rv = synth_getTrackLength(&len, pCtx, handle, 0 /* track */); 439 | SYNTH_ASSERT(rv == SYNTH_OK); 440 | printf("Song %i requires %i samples and loops at %i\n", handle, len, 441 | intro); 442 | 443 | /* Find the longest track, in samples */ 444 | if (len > maxLen) { 445 | maxLen = len; 446 | } 447 | 448 | /* Retrieve the number of bytes required */ 449 | reqLen = len; 450 | if (mode & SYNTH_16BITS) { 451 | reqLen *= 2; 452 | } 453 | if (mode & SYNTH_2CHAN) { 454 | reqLen *= 2; 455 | } 456 | 457 | printf("Song %i requires %i bytes (%i KB, %i MB)\n", handle, reqLen, 458 | reqLen >> 10, reqLen >> 20); 459 | 460 | /* Alloc the track's buffer... */ 461 | pBuf = (char*)malloc(reqLen); 462 | SYNTH_ASSERT_ERR(pBuf, SYNTH_MEM_ERR); 463 | 464 | /* Store the buffer's pointer and length to play it later */ 465 | ppBufs[j] = pBuf; 466 | pBufLens[j] = reqLen; 467 | 468 | /* Render the track */ 469 | rv = synth_renderTrack(pBuf, pCtx, handle, 0 /* track */, mode); 470 | SYNTH_ASSERT(rv == SYNTH_OK); 471 | 472 | /* Go to the next parameter */ 473 | i += 2; 474 | j++; 475 | } 476 | 477 | /* Set the data object, so the tracks may be sent to the audio callback */ 478 | data.ppBufs = ppBufs; 479 | data.pBufLens = pBufLens; 480 | data.numBufs = numSongs; 481 | data.pos = 0; 482 | data.mode = mode; 483 | 484 | /* Initialize SDL so it can play the song */ 485 | irv = SDL_Init(SDL_INIT_AUDIO); 486 | SYNTH_ASSERT_ERR(irv >= 0, SYNTH_INTERNAL_ERR); 487 | didInitSDL = 1; 488 | 489 | /* Set the audio specs according to the requested ones */ 490 | wanted.freq = freq; 491 | wanted.samples = 4096; 492 | 493 | if (mode & SYNTH_1CHAN) { 494 | wanted.channels = 1; 495 | } 496 | else if (mode & SYNTH_2CHAN) { 497 | wanted.channels = 2; 498 | } 499 | 500 | wanted.samples *= wanted.channels; 501 | 502 | if ((mode & SYNTH_8BITS) && (mode & SYNTH_UNSIGNED)) { 503 | wanted.format = AUDIO_U8; 504 | } 505 | else if ((mode & SYNTH_8BITS) && (mode & SYNTH_SIGNED)) { 506 | wanted.format = AUDIO_S8; 507 | } 508 | else if ((mode & SYNTH_16BITS) && (mode & SYNTH_UNSIGNED)) { 509 | wanted.format = AUDIO_U16LSB; 510 | } 511 | else if ((mode & SYNTH_16BITS) && (mode & SYNTH_SIGNED)) { 512 | wanted.format = AUDIO_S16LSB; 513 | } 514 | 515 | wanted.callback = audioCallback; 516 | wanted.userdata = (void*)&data; 517 | 518 | /* Open the device, so the song may be played */ 519 | dev = SDL_OpenAudioDevice(0, 0, &wanted, &specs, 0); 520 | SYNTH_ASSERT_ERR(dev != 0, SYNTH_INTERNAL_ERR); 521 | 522 | /* Play the song */ 523 | SDL_PauseAudioDevice(dev, 0); 524 | 525 | /* Wait long enough, until the audio is played */ 526 | printf("Sleeping for %ums, while the song plays...\n", 527 | (unsigned int)maxLen * 1000 / freq + 1); 528 | SDL_Delay((unsigned int)maxLen * 1000 / freq + 1); 529 | 530 | rv = SYNTH_OK; 531 | __err: 532 | if (rv != SYNTH_OK) { 533 | printf("An error happened!\n"); 534 | } 535 | 536 | if (dev != 0) { 537 | SDL_PauseAudioDevice(dev, 1); 538 | SDL_CloseAudioDevice(dev); 539 | } 540 | 541 | if (didInitSDL) { 542 | SDL_Quit(); 543 | } 544 | 545 | if (pCtx) { 546 | printf("Releasing resources used by the lib...\n"); 547 | synth_free(&pCtx); 548 | } 549 | 550 | /* Release every buffer */ 551 | if (ppBufs) { 552 | int i; 553 | 554 | i = 0; 555 | while (i < numSongs) { 556 | if (ppBufs[i]) { 557 | free(ppBufs[i]); 558 | } 559 | 560 | i++; 561 | } 562 | free(ppBufs); 563 | } 564 | 565 | if (pBufLens) { 566 | free(pBufLens); 567 | } 568 | 569 | printf("Exiting...\n"); 570 | return rv; 571 | } 572 | 573 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #=============================================================================== 2 | # Override-able variables: 3 | # 4 | # CC -- Compiler 5 | # AR -- Archiver (generate the static lib) 6 | # STRIP -- Strip (discard symbols) 7 | # OS -- Target operating system (in {Linux, Win, emscript}) 8 | # ARCH -- Target architecture (in {i686, x86_64}) 9 | # CFLAGS -- Base compiler flags (to which more flags are appended) 10 | # PREFIX -- Base directory where the lib will be installed 11 | # LIBPATH -- Directory where the shared library will be installed 12 | # HEADERPATH -- Directory where the headers will be installed 13 | #------------------------------------------------------------------------------- 14 | #------------------------------------------------------------------------------- 15 | # Cross-compiling instructions: 16 | # 17 | # Cross-compiling from Linux to Windows may be done by setting the following 18 | # variables: 'CC', 'AR', 'STRIP', 'OS', 'ARCH'. Setting 'OS' to 'Win' avoids 19 | # some overriding done when compiling natively on Windows through MinGW. 20 | # 21 | # The destination for the 'install' command may be chosen either by setting the 22 | # 'PREFIX' var or by manually setting 'LIBPATH' and 'HEADERPATH'. The later may 23 | # be interesting if you want to install both i686 and x86_64 libraries (since 24 | # this Makefile don't follow any default recomendation). 25 | #=============================================================================== 26 | 27 | #=============================================================================== 28 | # Define every object required by compilation 29 | #=============================================================================== 30 | OBJS = $(OBJDIR)/synth.o \ 31 | $(OBJDIR)/synth_audio.o \ 32 | $(OBJDIR)/synth_lexer.o \ 33 | $(OBJDIR)/synth_note.o \ 34 | $(OBJDIR)/synth_parser.o \ 35 | $(OBJDIR)/synth_prng.o \ 36 | $(OBJDIR)/synth_renderer.o \ 37 | $(OBJDIR)/synth_track.o \ 38 | $(OBJDIR)/synth_volume.o 39 | #=============================================================================== 40 | 41 | #============================================================================== 42 | # Select which compiler to use (either gcc or emcc) 43 | #============================================================================== 44 | ifneq (, $(findstring emscript, $(MAKECMDGOALS))) 45 | CC := emcc 46 | endif 47 | CC ?= gcc 48 | AR ?= ar 49 | STRIP ?= strip 50 | # Set DEBUG as the default mode 51 | ifneq ($(RELEASE), yes) 52 | RELEASE := no 53 | DEBUG := yes 54 | endif 55 | #============================================================================== 56 | 57 | #============================================================================== 58 | # Clear the suffixes' default rule, since there's an explicit one 59 | #============================================================================== 60 | .SUFFIXES: 61 | #============================================================================== 62 | 63 | #============================================================================== 64 | # Define all targets that doesn't match its generated file 65 | #============================================================================== 66 | .PHONY: emscript fast fast_all install install_shared install_shared_win \ 67 | install_shared_x install_static install_static_win install_static_x \ 68 | uninstall uninstall_win uninstall_x clean emscript_clean distclean 69 | #============================================================================== 70 | 71 | #============================================================================== 72 | # Define compilation target 73 | #============================================================================== 74 | TARGET := libCSynth 75 | LIBNAME := lCSynth 76 | MAJOR_VERSION := 1 77 | MINOR_VERSION := 0 78 | REV_VERSION := 2 79 | # If the DEBUG flag was set, generate another binary (so it doesn't collide 80 | # with the release one) 81 | ifeq ($(DEBUG), yes) 82 | TARGET := $(TARGET)_dbg 83 | endif 84 | #============================================================================== 85 | 86 | #=============================================================================== 87 | # Set OS flag 88 | #=============================================================================== 89 | UNAME := $(shell uname) 90 | OS ?= $(UNAME) 91 | ifneq (, $(findstring Windows_NT, $(UNAME))) 92 | OS := Win 93 | UNAME := Win 94 | endif 95 | ifneq (, $(findstring MINGW, $(UNAME))) 96 | OS := Win 97 | UNAME := Win 98 | endif 99 | ifneq (, $(findstring MSYS, $(UNAME))) 100 | OS := Win 101 | UNAME := Win 102 | endif 103 | ifeq ($(CC), emcc) 104 | OS := emscript 105 | endif 106 | #=============================================================================== 107 | 108 | #=============================================================================== 109 | # Define CFLAGS (compiler flags) 110 | #=============================================================================== 111 | # Add all warnings and default include path 112 | CFLAGS := $(CFLAGS) -Wall -I"./include" -I"./src/include" 113 | # Add architecture flag 114 | ARCH ?= $(shell uname -m) 115 | ifeq ($(OS), emscript) 116 | CFLAGS := $(CFLAGS) -I"$(EMSCRIPTEN)/system/include/" -m32 117 | else 118 | ifeq ($(ARCH), x86_64) 119 | CFLAGS := $(CFLAGS) -m64 120 | else 121 | CFLAGS := $(CFLAGS) -m32 122 | endif 123 | endif 124 | # Add debug flags 125 | ifeq ($(OS), emscript) 126 | CFLAGS := $(CFLAGS) -O2 127 | else 128 | ifneq ($(RELEASE), yes) 129 | CFLAGS := $(CFLAGS) -g -O0 130 | else 131 | CFLAGS := $(CFLAGS) -O3 132 | endif 133 | endif 134 | # Set flags required by OS 135 | ifeq ($(UNAME), Win) 136 | CFLAGS := $(CFLAGS) -I"/d/windows/mingw/include" 137 | endif 138 | ifneq ($(OS), Win) 139 | CFLAGS := $(CFLAGS) -fPIC 140 | endif 141 | # Set the current compiler 142 | ifeq ($(OS), emscript) 143 | CFLAGS := $(CFLAGS) -DEMCC 144 | endif 145 | #=============================================================================== 146 | 147 | #=============================================================================== 148 | # Define LDFLAGS (linker flags) 149 | #=============================================================================== 150 | SDL_LDFLAGS := -lSDL2 151 | 152 | ifeq ($(OS), Win) 153 | LDFLAGS := $(LDFLAGS) -lmingw32 154 | SDL_LDFLAGS := -lSDL2main -lSDL2 155 | else 156 | LDFLAGS := $(LDFLAGS) -lm 157 | endif 158 | #=============================================================================== 159 | 160 | #=============================================================================== 161 | # Define where source files can be found and where objects and binary are output 162 | #=============================================================================== 163 | VPATH := src:tst 164 | ifeq ($(RELEASE), yes) 165 | OBJDIR := obj/release/$(OS) 166 | BINDIR := bin/release/$(OS) 167 | else 168 | OBJDIR := obj/debug/$(OS) 169 | BINDIR := bin/debug/$(OS) 170 | endif 171 | TESTDIR := tst 172 | 173 | PREFIX ?= /usr 174 | LIBPATH ?= $(PREFIX)/lib/c_synth 175 | HEADERPATH ?= $(PREFIX)/include/c_synth 176 | #=============================================================================== 177 | 178 | #============================================================================== 179 | # Automatically look up for tests and compile them 180 | #============================================================================== 181 | TEST_SRC := $(wildcard $(TESTDIR)/tst_*.c) 182 | TEST_OBJS := $(TEST_SRC:$(TESTDIR)/%.c=$(OBJDIR)/%.o) 183 | TEST_BIN := $(addprefix $(BINDIR)/, $(TEST_SRC:$(TESTDIR)/%.c=%$(BIN_EXT))) 184 | #============================================================================== 185 | 186 | #============================================================================== 187 | # Make sure the test's object files aren't automatically deleted 188 | #============================================================================== 189 | .SECONDARY: $(TEST_OBJS) 190 | #============================================================================== 191 | 192 | #============================================================================== 193 | # Make the objects list constant (and the icon, if any) 194 | #============================================================================== 195 | OBJS := $(OBJS) 196 | #============================================================================== 197 | 198 | #============================================================================== 199 | # Set shared library's extension 200 | #============================================================================== 201 | ifeq ($(OS), Win) 202 | SO ?= dll 203 | MJV ?= $(SO) 204 | MNV ?= $(SO) 205 | else 206 | SO ?= so 207 | MJV ?= $(SO).$(MAJOR_VERSION) 208 | MNV ?= $(SO).$(MAJOR_VERSION).$(MINOR_VERSION).$(REV_VERSION) 209 | endif 210 | #============================================================================== 211 | 212 | #============================================================================== 213 | # Ensure debug build isn't stripped 214 | #============================================================================== 215 | ifneq ($(RELEASE), yes) 216 | STRIP := touch 217 | endif 218 | #============================================================================== 219 | 220 | #============================================================================== 221 | # Get the number of cores for fun stuff 222 | #============================================================================== 223 | ifeq ($(UNAME), Win) 224 | CORES := 1 225 | else 226 | CORES := $$(($(shell nproc) * 2)) 227 | endif 228 | #============================================================================== 229 | 230 | #=============================================================================== 231 | # Define default compilation rule 232 | #=============================================================================== 233 | all: static shared tests 234 | #=============================================================================== 235 | 236 | #============================================================================== 237 | # Rule for building a object file for emscript 238 | #============================================================================== 239 | emscript: $(BINDIR)/$(TARGET).bc 240 | #=============================================================================== 241 | 242 | #============================================================================== 243 | # Rule for cleaning emscript build... It's required to modify the CC 244 | #============================================================================== 245 | emscript_clean: clean 246 | #=============================================================================== 247 | 248 | #============================================================================== 249 | # Build a emscript (LLVM) binary, to be used when compiling for HTML5 250 | #============================================================================== 251 | $(BINDIR)/$(TARGET).bc: MKDIRS $(OBJS) 252 | $(CC) -o $@ $(CFLAGS) $(OBJS) 253 | #============================================================================== 254 | 255 | #============================================================================== 256 | # Rule for building the static lib 257 | #============================================================================== 258 | static: MKDIRS $(BINDIR)/$(TARGET).a 259 | #============================================================================== 260 | 261 | #============================================================================== 262 | # Rule for building the shared libs 263 | #============================================================================== 264 | shared: MKDIRS $(BINDIR)/$(TARGET).$(SO) 265 | #============================================================================== 266 | 267 | #============================================================================== 268 | # Rule for building tests 269 | #============================================================================== 270 | tests: MKDIRS shared $(TEST_BIN) 271 | #============================================================================== 272 | 273 | #============================================================================== 274 | # Rule for installing the library 275 | #============================================================================== 276 | ifeq ($(UNAME), Win) 277 | install: install_shared_win install_static_win 278 | install_shared: install_shared_win 279 | install_static: install_static_win 280 | else 281 | install: install_shared_x install_static_x 282 | install_shared: install_shared_x 283 | install_static: install_static_x 284 | endif 285 | 286 | install_shared_win: shared 287 | # Create destiny directories 288 | mkdir -p /c/c_synth/lib/ 289 | mkdir -p /c/c_synth/include/c_synth 290 | # Copy the headers 291 | cp -rf ./include/c_synth/* /c/c_synth/include/c_synth 292 | # Copy the lib 293 | cp -rf $(BINDIR)/$(TARGET).dll /c/c_synth/lib/ 294 | 295 | install_static_win: static 296 | # Create destiny directories 297 | mkdir -p /c/c_synth/lib/ 298 | mkdir -p /c/c_synth/include/c_synth 299 | # Copy the headers 300 | cp -rf ./include/c_synth/* /c/c_synth/include/c_synth 301 | # Copy the lib 302 | cp -rf $(BINDIR)/$(TARGET).a /c/c_synth/lib/ 303 | 304 | install_shared_x: shared 305 | # Create destiny directories 306 | mkdir -p $(LIBPATH) 307 | mkdir -p $(HEADERPATH) 308 | # Copy the headers 309 | cp -rf ./include/c_synth/* $(HEADERPATH) 310 | # Copy every shared lib (normal, optmized and debug) 311 | cp -f $(BINDIR)/$(TARGET)*.$(MNV) $(LIBPATH) 312 | # -P = don't follow sym-link 313 | cp -fP $(BINDIR)/$(TARGET)*.$(MJV) $(LIBPATH) 314 | cp -fP $(BINDIR)/$(TARGET)*.$(SO) $(LIBPATH) 315 | # Make the lib be automatically found 316 | echo "$(LIBPATH)" > /etc/ld.so.conf.d/c_synth.conf 317 | ldconfig 318 | 319 | install_static_x: static 320 | # Create destiny directories 321 | mkdir -p $(LIBPATH) 322 | mkdir -p $(HEADERPATH) 323 | # Copy the headers 324 | cp -rf ./include/c_synth/* $(HEADERPATH) 325 | # Copy the static lib 326 | cp -f $(BINDIR)/$(TARGET)*.a $(LIBPATH) 327 | #============================================================================== 328 | 329 | #============================================================================== 330 | # Rule for uninstalling the library 331 | #============================================================================== 332 | ifeq ($(UNAME), Win) 333 | uninstall: uninstall_win 334 | else 335 | uninstall: uninstall_x 336 | endif 337 | 338 | uninstall_win: 339 | # Remove the libraries (account for different versions) 340 | rm -f /c/c_synth/lib/$(TARGET)_dbg.* 341 | rm -f /c/c_synth/lib/$(TARGET).* 342 | # Remove the headers 343 | rm -rf /c/c_synth/include/* 344 | # Remove its directories 345 | rmdir /c/c_synth/lib/ 346 | rmdir /c/c_synth/include/ 347 | rmdir /c/c_synth/ 348 | 349 | uninstall_x: 350 | # Remove the libraries (account for different versions) 351 | rm -f $(LIBPATH)/$(TARGET)_dbg.* 352 | rm -f $(LIBPATH)/$(TARGET).* 353 | # Remove the headers 354 | rm -rf $(HEADERPATH)/* 355 | # Remove its directories 356 | rmdir $(LIBPATH) 357 | rmdir $(HEADERPATH) 358 | # Remove the lib from the default path 359 | rm /etc/ld.so.conf.d/c_synth.conf 360 | # Update the paths 361 | ldconfig 362 | #============================================================================== 363 | 364 | #============================================================================== 365 | # Rule for actually building the static library 366 | #============================================================================== 367 | $(BINDIR)/$(TARGET).a: $(OBJS) 368 | rm -f $(BINDIR)/$(TARGET).a 369 | $(AR) -cvq $(BINDIR)/$(TARGET).a $(OBJS) 370 | #============================================================================== 371 | 372 | #============================================================================== 373 | # Rule for actually building the shared library 374 | #============================================================================== 375 | # Windows DLL 376 | $(BINDIR)/$(TARGET).dll: $(OBJS) 377 | rm -f $@ 378 | $(CC) -shared -Wl,-soname,$(TARGET).dll -Wl,-export-all-symbols $(CFLAGS) \ 379 | -o $@ $(OBJS) $(LDFLAGS) 380 | $(STRIP) $@ 381 | 382 | # Linux 383 | $(BINDIR)/$(TARGET).so: $(BINDIR)/$(TARGET).$(MJV) 384 | rm -f $(BINDIR)/$(TARGET).$(SO) 385 | cd $(BINDIR); ln -f -s $(TARGET).$(MJV) $(TARGET).$(SO) 386 | 387 | ifneq ($(SO), $(MJV)) 388 | $(BINDIR)/$(TARGET).$(MJV): $(BINDIR)/$(TARGET).$(MNV) 389 | rm -f $(BINDIR)/$(TARGET).$(MJV) 390 | cd $(BINDIR); ln -f -s $(TARGET).$(MNV) $(TARGET).$(MJV) 391 | endif 392 | 393 | ifneq ($(SO), $(MNV)) 394 | $(BINDIR)/$(TARGET).$(MNV): $(OBJS) 395 | $(CC) -shared -Wl,-soname,$(TARGET).$(MJV) -Wl,-export-dynamic \ 396 | $(CFLAGS) -o $(BINDIR)/$(TARGET).$(MNV) $(OBJS) $(LDFLAGS) 397 | $(STRIP) $@ 398 | endif 399 | 400 | # Mac OS X 401 | $(BINDIR)/$(TARGET).dylib: $(OBJS) 402 | $(CC) -dynamiclib $(CFLAGS) -o $(BINDIR)/$(TARGET).dylib $(OBJS) 403 | $(STRIP) $@ 404 | #============================================================================== 405 | 406 | #============================================================================== 407 | # Rule for compiling test binaries that uses SDL2 as its backend (those are 408 | # prefixed by 'tst_' and suffixed by 'SDL2') 409 | #============================================================================== 410 | $(BINDIR)/tst_%SDL2$(BIN_EXT): $(OBJDIR)/tst_%SDL2.o 411 | $(CC) $(CFLAGS) -o $@ $< -L$(BINDIR) $(LDFLAGS) -$(LIBNAME)_dbg $(SDL_LDFLAGS) 412 | #============================================================================== 413 | 414 | #============================================================================== 415 | # Rule for compiling a test binary (it's prefixed by 'tst_') 416 | #============================================================================== 417 | $(BINDIR)/tst_%$(BIN_EXT): $(OBJDIR)/tst_%.o 418 | $(CC) $(CFLAGS) -o $@ $< -L$(BINDIR) $(LDFLAGS) -$(LIBNAME)_dbg 419 | #============================================================================== 420 | 421 | #============================================================================== 422 | # Rule for compiling any .c in its object 423 | #============================================================================== 424 | $(OBJDIR)/%.o: %.c 425 | $(CC) $(CFLAGS) -o $@ -c $< 426 | #============================================================================== 427 | 428 | #============================================================================== 429 | # Rule for creating every directory 430 | #============================================================================== 431 | MKDIRS: | $(OBJDIR) 432 | #============================================================================== 433 | 434 | #============================================================================== 435 | # Build everything as fast as possible (and using as many cores/threads as 436 | # possible) 437 | #============================================================================== 438 | fast: 439 | make -j $(CORES) static shared 440 | #============================================================================== 441 | 442 | #============================================================================== 443 | # Build everything as fast as possible (and using as many cores/threads as 444 | # possible) 445 | #============================================================================== 446 | fast_all: 447 | make -j $(CORES) static shared && make -j $(CORES) 448 | #============================================================================== 449 | 450 | #============================================================================== 451 | # Rule for actually creating every directory 452 | #============================================================================== 453 | $(OBJDIR): 454 | mkdir -p $(OBJDIR) 455 | mkdir -p $(BINDIR) 456 | #============================================================================== 457 | 458 | #============================================================================== 459 | # Removes all built objects (use emscript_clean to clear the emscript stuff) 460 | #============================================================================== 461 | clean: 462 | rm -f $(OBJS) $(BINDIR)/$(TARGET).a $(BINDIR)/* 463 | rm -rf $(OBJDIR) $(BINDIR) 464 | #============================================================================== 465 | 466 | #============================================================================== 467 | # Remove all built objects and target directories 468 | #============================================================================== 469 | distclean: 470 | make emscript_clean DEBUG=yes 471 | make emscript_clean RELEASE=yes 472 | make clean DEBUG=yes 473 | make clean RELEASE=yes 474 | #============================================================================== 475 | 476 | -------------------------------------------------------------------------------- /tst/tst_playSongSDL2.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple test to render a song and play it (using SDL2 as the backend) 3 | * 4 | * @file tst/tst_playSongSDL2.c 5 | */ 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | /* Structure to hold every track pointer as well as how many samples/bytes were 17 | * played */ 18 | struct stSharedData { 19 | /** Array with tracks' data */ 20 | char **ppBufs; 21 | /** Length of each track */ 22 | int *pBufLens; 23 | /** Number of tracks */ 24 | int numBufs; 25 | /** Number of played samples/bytes */ 26 | int pos; 27 | /** Current mode being played */ 28 | synthBufMode mode; 29 | }; 30 | 31 | static void audioCallback(void *pArg, Uint8 *pStream, int len) { 32 | int i; 33 | struct stSharedData *pData; 34 | synthBufMode mode; 35 | 36 | /* Retrieve the data */ 37 | pData = (struct stSharedData*)pArg; 38 | mode = pData->mode; 39 | 40 | /* Clear the buffer (so there's no extra noise) */ 41 | memset(pStream, 0x0, len); 42 | 43 | /* Mix each track data */ 44 | i = 0; 45 | while (i < pData->numBufs) { 46 | char *pBuf; 47 | int j, bufLen; 48 | 49 | /* Retrieve the current buffer */ 50 | pBuf = pData->ppBufs[i]; 51 | bufLen = pData->pBufLens[i]; 52 | 53 | /* Already increase the counter, so we can loop early */ 54 | i++; 55 | if (pData->pos >= bufLen) { 56 | continue; 57 | } 58 | 59 | /* Loop through the track according with the mode */ 60 | j = 0; 61 | while (j < len) { 62 | if (pData->pos + j >= bufLen) { 63 | break; 64 | } 65 | 66 | if ((mode & SYNTH_1CHAN) && (mode & SYNTH_8BITS) && 67 | (mode & SYNTH_UNSIGNED)) { 68 | Uint8 chan, dst; 69 | 70 | /* Retrieve the current stream and the channel data */ 71 | chan = pBuf[pData->pos + j] & 0x00ff; 72 | dst = pStream[j] & 0x00ff; 73 | 74 | /* 'Mix' both */ 75 | dst += chan; 76 | 77 | /* Return it to the stream */ 78 | pStream[j] = dst & 0x00ff; 79 | 80 | j++; 81 | } 82 | else if ((mode & SYNTH_1CHAN) && (mode & SYNTH_8BITS) && 83 | (mode & SYNTH_SIGNED)) { 84 | Sint8 chan, dst; 85 | 86 | /* Retrieve the current stream and the channel data */ 87 | chan = pBuf[pData->pos + j] & 0x00ff; 88 | dst = pStream[j] & 0x00ff; 89 | 90 | /* 'Mix' both */ 91 | dst += chan; 92 | 93 | /* Return it to the stream */ 94 | pStream[j] = dst & 0x00ff; 95 | 96 | j++; 97 | } 98 | else if ((mode & SYNTH_1CHAN) && (mode & SYNTH_16BITS) && 99 | (mode & SYNTH_UNSIGNED)) { 100 | Uint16 chan, dst; 101 | 102 | /* Retrieve the current stream and the channel data */ 103 | chan = (pBuf[pData->pos + j] & 0x00ff) | 104 | ((pBuf[pData->pos + j + 1] << 8) & 0xff00); 105 | dst = (pStream[j] & 0x00ff) | ((pStream[j + 1] << 8) & 0xff00); 106 | 107 | /* 'Mix' both */ 108 | dst += chan; 109 | 110 | /* Return it to the stream */ 111 | pStream[j] = dst & 0x00ff; 112 | pStream[j + 1] = (dst >> 8) & 0x00ff; 113 | 114 | j += 2; 115 | } 116 | else if ((mode & SYNTH_1CHAN) && (mode & SYNTH_16BITS) && 117 | (mode & SYNTH_SIGNED)) { 118 | Sint16 chan, dst; 119 | 120 | /* Retrieve the current stream and the channel data */ 121 | chan = (pBuf[pData->pos + j] & 0x00ff) | 122 | ((pBuf[pData->pos + j + 1] << 8) & 0xff00); 123 | dst = (pStream[j] & 0x00ff) | ((pStream[j + 1] << 8) & 0xff00); 124 | 125 | /* 'Mix' both */ 126 | dst += chan; 127 | 128 | /* Return it to the stream */ 129 | pStream[j] = dst & 0x00ff; 130 | pStream[j + 1] = (dst >> 8) & 0x00ff; 131 | 132 | j += 2; 133 | } 134 | else if ((mode & SYNTH_2CHAN) && (mode & SYNTH_8BITS) && 135 | (mode & SYNTH_UNSIGNED)) { 136 | Uint8 chan1, chan2, dst1, dst2; 137 | 138 | /* Retrieve the current stream and the channel data */ 139 | chan1 = pBuf[pData->pos + j] & 0x00ff; 140 | chan2 = pBuf[pData->pos + j + 1] & 0x00ff; 141 | dst1 = pStream[j] & 0x00ff; 142 | dst2 = pStream[j + 1] & 0x00ff; 143 | 144 | /* 'Mix' both */ 145 | dst1 += chan1; 146 | dst2 += chan2; 147 | 148 | /* Return it to the stream */ 149 | pStream[j] = dst1 & 0x00ff; 150 | pStream[j + 1] = dst2 & 0x00ff; 151 | 152 | j += 2; 153 | } 154 | else if ((mode & SYNTH_2CHAN) && (mode & SYNTH_8BITS) && 155 | (mode & SYNTH_SIGNED)) { 156 | Sint8 chan1, chan2, dst1, dst2; 157 | 158 | /* Retrieve the current stream and the channel data */ 159 | chan1 = pBuf[pData->pos + j] & 0x00ff; 160 | chan2 = pBuf[pData->pos + j + 1] & 0x00ff; 161 | dst1 = pStream[j] & 0x00ff; 162 | dst2 = pStream[j + 1] & 0x00ff; 163 | 164 | /* 'Mix' both */ 165 | dst1 += chan1; 166 | dst2 += chan2; 167 | 168 | /* Return it to the stream */ 169 | pStream[j] = dst1 & 0x00ff; 170 | pStream[j + 1] = dst2 & 0x00ff; 171 | 172 | j += 2; 173 | } 174 | else if ((mode & SYNTH_2CHAN) && (mode & SYNTH_16BITS) && 175 | (mode & SYNTH_UNSIGNED)) { 176 | Uint16 chan1, chan2, dst1, dst2; 177 | 178 | /* Retrieve the current stream and the channel data */ 179 | chan1 = (pBuf[pData->pos + j] & 0x00ff) | 180 | ((pBuf[pData->pos + j + 1] << 8) & 0xff00); 181 | chan2 = (pBuf[pData->pos + j + 2] & 0x00ff) | 182 | ((pBuf[pData->pos + j + 3] << 8) & 0xff00); 183 | dst1 = (pStream[j] & 0x00ff) | 184 | ((pStream[j + 1] << 8) & 0xff00); 185 | dst2 = (pStream[j + 2] & 0x00ff) | 186 | ((pStream[j + 3] << 8) & 0xff00); 187 | 188 | /* 'Mix' both */ 189 | dst1 += chan1; 190 | dst2 += chan2; 191 | 192 | /* Return it to the stream */ 193 | pStream[j] = dst1 & 0x00ff; 194 | pStream[j + 1] = (dst1 >> 8) & 0x00ff; 195 | pStream[j + 2] = dst2 & 0x00ff; 196 | pStream[j + 3] = (dst2 >> 8) & 0x00ff; 197 | 198 | j += 4; 199 | } 200 | else if ((mode & SYNTH_2CHAN) && (mode & SYNTH_16BITS) && 201 | (mode & SYNTH_SIGNED)) { 202 | Sint16 chan1, chan2, dst1, dst2; 203 | 204 | /* Retrieve the current stream and the channel data */ 205 | chan1 = (pBuf[pData->pos + j] & 0x00ff) | 206 | ((pBuf[pData->pos + j + 1] << 8) & 0xff00); 207 | chan2 = (pBuf[pData->pos + j + 2] & 0x00ff) | 208 | ((pBuf[pData->pos + j + 3] << 8) & 0xff00); 209 | dst1 = (pStream[j] & 0x00ff) | 210 | ((pStream[j + 1] << 8) & 0xff00); 211 | dst2 = (pStream[j + 2] & 0x00ff) | 212 | ((pStream[j + 3] << 8) & 0xff00); 213 | 214 | /* 'Mix' both */ 215 | dst1 += chan1; 216 | dst2 += chan2; 217 | 218 | /* Return it to the stream */ 219 | pStream[j] = dst1 & 0x00ff; 220 | pStream[j + 1] = (dst1 >> 8) & 0x00ff; 221 | pStream[j + 2] = dst2 & 0x00ff; 222 | pStream[j + 3] = (dst2 >> 8) & 0x00ff; 223 | 224 | j += 4; 225 | } 226 | } 227 | } 228 | 229 | pData->pos += len; 230 | } 231 | 232 | /* Simple test song */ 233 | static char __song[] = "MML t90 l16 o5 e e8 e r c e r g4 > g4 <"; 234 | 235 | /** 236 | * Entry point 237 | * 238 | * @param [ in]argc Number of arguments 239 | * @param [ in]argv List of arguments 240 | * @return The exit code 241 | */ 242 | int main(int argc, char *argv[]) { 243 | char **ppBufs, *pSrc; 244 | int didInitSDL, freq, handle, i, isFile, irv, len, numTracks, maxLen, 245 | *pBufLens; 246 | SDL_AudioDeviceID dev; 247 | SDL_AudioSpec wanted, specs; 248 | struct stSharedData data; 249 | synthBufMode mode; 250 | synthCtx *pCtx; 251 | synth_err rv; 252 | 253 | /* Clean the context, so it's not freed on error */ 254 | didInitSDL = 0; 255 | pCtx = 0; 256 | ppBufs = 0; 257 | pBufLens = 0; 258 | dev = 0; 259 | 260 | /* Store the default frequency */ 261 | freq = 44100; 262 | /* Store the default mode */ 263 | mode = SYNTH_1CHAN_U8BITS; 264 | isFile = 0; 265 | pSrc = 0; 266 | len = 0; 267 | /* Check argc/argv */ 268 | if (argc > 1) { 269 | int i; 270 | 271 | i = 1; 272 | while (i < argc) { 273 | #define IS_PARAM(l_cmd, s_cmd) \ 274 | if (strcmp(argv[i], l_cmd) == 0 || strcmp(argv[i], s_cmd) == 0) 275 | IS_PARAM("--string", "-s") { 276 | if (argc <= i + 1) { 277 | printf("Expected parameter but got nothing! Run " 278 | "'tst_playSongSDL2 --help' for usage!\n"); 279 | return 1; 280 | } 281 | 282 | /* Store the string and retrieve its length */ 283 | pSrc = argv[i + 1]; 284 | isFile = 0; 285 | len = strlen(argv[i + 1]); 286 | } 287 | IS_PARAM("--file", "-f") { 288 | if (argc <= i + 1) { 289 | printf("Expected parameter but got nothing! Run " 290 | "'tst_playSongSDL2 --help' for usage!\n"); 291 | return 1; 292 | } 293 | 294 | /* Store the filename */ 295 | pSrc = argv[i + 1]; 296 | isFile = 1; 297 | } 298 | IS_PARAM("--frequency", "-F") { 299 | char *pNum; 300 | int tmp; 301 | 302 | if (argc <= i + 1) { 303 | printf("Expected parameter but got nothing! Run " 304 | "'tst_playSongSDL2 --help' for usage!\n"); 305 | return 1; 306 | } 307 | 308 | pNum = argv[i + 1]; 309 | 310 | tmp = 0; 311 | 312 | while (*pNum != '\0') { 313 | tmp = tmp * 10 + (*pNum) - '0'; 314 | pNum++; 315 | } 316 | 317 | freq = tmp; 318 | } 319 | IS_PARAM("--mode", "-m") { 320 | char *pMode; 321 | 322 | if (argc <= i + 1) { 323 | printf("Expected parameter but got nothing! Run " 324 | "'tst_playSongSDL2 --help' for usage!\n"); 325 | return 1; 326 | } 327 | 328 | pMode = argv[i + 1]; 329 | 330 | if (strcmp(pMode, "1chan-u8") == 0) { 331 | mode = SYNTH_1CHAN_U8BITS; 332 | } 333 | else if (strcmp(pMode, "1chan-8") == 0) { 334 | mode = SYNTH_1CHAN_8BITS; 335 | } 336 | else if (strcmp(pMode, "1chan-u16") == 0) { 337 | mode = SYNTH_1CHAN_U16BITS; 338 | } 339 | else if (strcmp(pMode, "1chan-16") == 0) { 340 | mode = SYNTH_1CHAN_16BITS; 341 | } 342 | else if (strcmp(pMode, "2chan-u8") == 0) { 343 | mode = SYNTH_2CHAN_U8BITS; 344 | } 345 | else if (strcmp(pMode, "2chan-8") == 0) { 346 | mode = SYNTH_2CHAN_8BITS; 347 | } 348 | else if (strcmp(pMode, "2chan-u16") == 0) { 349 | mode = SYNTH_2CHAN_U16BITS; 350 | } 351 | else if (strcmp(pMode, "2chan-16") == 0) { 352 | mode = SYNTH_2CHAN_16BITS; 353 | } 354 | else { 355 | printf("Invalid mode! Run 'tst_playSongSDL2 --help' to " 356 | "check the usage!\n"); 357 | return 1; 358 | } 359 | } 360 | IS_PARAM("--help", "-h") { 361 | printf("A simple test for the c_synth library\n" 362 | "\n" 363 | "Usage: tst_playSongSDL2 [--string | -s \"the song\"] " 364 | "[--file | -f ]\n" 365 | " [--frequency | -F ] " 366 | "[--mode | -m ]\n" 367 | " [--help | -h]\n" 368 | "\n" 369 | "Compiles a single song and the plays it once.\n" 370 | "\n" 371 | "Note that this test ignores a song loop point and " 372 | "tracks with different\n" 373 | "lengths.\n" 374 | "\n" 375 | "'' must be one of the following:\n" 376 | " 1chan-u8 : 1 channel, unsigned 8 bits samples\n" 377 | " 1chan-8 : 1 channel, signed 8 bits samples\n" 378 | " 1chan-u16: 1 channel, unsigned 16 bits samples\n" 379 | " 1chan-16 : 1 channel, signed 16 bits samples\n" 380 | " 2chan-u8 : 2 channel, unsigned 8 bits samples\n" 381 | " 2chan-8 : 2 channel, signed 8 bits samples\n" 382 | " 2chan-u16: 2 channel, unsigned 16 bits samples\n" 383 | " 2chan-16 : 2 channel, signed 16 bits samples\n" 384 | "\n" 385 | "If no argument is passed, it will compile a simple " 386 | "test song.\n"); 387 | return 0; 388 | } 389 | 390 | i += 2; 391 | #undef IS_PARAM 392 | } 393 | } 394 | 395 | /* Initialize it */ 396 | printf("Initialize the synthesizer...\n"); 397 | rv = synth_init(&pCtx, freq); 398 | SYNTH_ASSERT(rv == SYNTH_OK); 399 | 400 | /* Compile a song */ 401 | if (pSrc != 0) { 402 | if (isFile) { 403 | printf("Compiling song from file '%s'...\n", pSrc); 404 | rv = synth_compileSongFromFile(&handle, pCtx, pSrc); 405 | } 406 | else { 407 | printf("Compiling song '%s'...\n", pSrc); 408 | rv = synth_compileSongFromString(&handle, pCtx, pSrc, len); 409 | } 410 | } 411 | else { 412 | printf("Compiling static song '%s'...\n", __song); 413 | rv = synth_compileSongFromStringStatic(&handle, pCtx, __song); 414 | } 415 | 416 | if (rv != SYNTH_OK) { 417 | char *pError; 418 | synth_err irv; 419 | 420 | /* Retrieve and print the error */ 421 | irv = synth_getCompilerErrorString(&pError, pCtx); 422 | SYNTH_ASSERT_ERR(irv == SYNTH_OK, irv); 423 | 424 | printf("%s", pError); 425 | } 426 | else { 427 | printf("Song compiled successfully!\n"); 428 | } 429 | SYNTH_ASSERT(rv == SYNTH_OK); 430 | 431 | /* Get the number of tracks in the song */ 432 | printf("Retrieving the number of tracks in the song...\n"); 433 | rv = synth_getAudioTrackCount(&numTracks, pCtx, handle); 434 | SYNTH_ASSERT(rv == SYNTH_OK); 435 | printf("Found %i tracks\n", numTracks); 436 | 437 | /* Create arrays for storing the buffers and lengths of each song */ 438 | ppBufs = (char**)malloc(sizeof(char*) * numTracks); 439 | SYNTH_ASSERT_ERR(ppBufs, SYNTH_MEM_ERR); 440 | memset(ppBufs, 0x0, sizeof(char*) * numTracks); 441 | 442 | pBufLens = (int*)malloc(sizeof(int) * numTracks); 443 | SYNTH_ASSERT_ERR(pBufLens, SYNTH_MEM_ERR); 444 | 445 | /* Get the number of samples in each track */ 446 | printf("Rendering each of the song's tracks...\n"); 447 | i = 0; 448 | maxLen = 0; 449 | while (i < numTracks) { 450 | char *pBuf; 451 | int intro, len, reqLen; 452 | 453 | /* Retrieve the length of the track, in samples */ 454 | rv = synth_getTrackIntroLength(&intro, pCtx, handle, i); 455 | SYNTH_ASSERT(rv == SYNTH_OK); 456 | rv = synth_getTrackLength(&len, pCtx, handle, i); 457 | SYNTH_ASSERT(rv == SYNTH_OK); 458 | 459 | printf("Track %i requires %i samples and loops at %i\n", i + 1, len, 460 | intro); 461 | 462 | /* Find the longest track, in samples */ 463 | if (len > maxLen) { 464 | maxLen = len; 465 | } 466 | 467 | /* Retrieve the number of bytes required */ 468 | reqLen = len; 469 | if (mode & SYNTH_16BITS) { 470 | reqLen *= 2; 471 | } 472 | if (mode & SYNTH_2CHAN) { 473 | reqLen *= 2; 474 | } 475 | 476 | printf("Track %i requires %i bytes (%i KB, %i MB)\n", i + 1, reqLen, 477 | reqLen >> 10, reqLen >> 20); 478 | 479 | /* Alloc the track's buffer... */ 480 | pBuf = (char*)malloc(reqLen); 481 | SYNTH_ASSERT_ERR(pBuf, SYNTH_MEM_ERR); 482 | 483 | /* Store the buffer's pointer and length to play it later */ 484 | ppBufs[i] = pBuf; 485 | pBufLens[i] = reqLen; 486 | 487 | /* Render the track */ 488 | rv = synth_renderTrack(pBuf, pCtx, handle, i, mode); 489 | SYNTH_ASSERT(rv == SYNTH_OK); 490 | 491 | i++; 492 | } 493 | 494 | /* Set the data object, so the tracks may be sent to the audio callback */ 495 | data.ppBufs = ppBufs; 496 | data.pBufLens = pBufLens; 497 | data.numBufs = numTracks; 498 | data.pos = 0; 499 | data.mode = mode; 500 | 501 | /* Initialize SDL so it can play the song */ 502 | irv = SDL_Init(SDL_INIT_AUDIO); 503 | SYNTH_ASSERT_ERR(irv >= 0, SYNTH_INTERNAL_ERR); 504 | didInitSDL = 1; 505 | 506 | /* Set the audio specs according to the requested ones */ 507 | wanted.freq = freq; 508 | wanted.samples = 4096; 509 | 510 | if (mode & SYNTH_1CHAN) { 511 | wanted.channels = 1; 512 | } 513 | else if (mode & SYNTH_2CHAN) { 514 | wanted.channels = 2; 515 | } 516 | 517 | wanted.samples *= wanted.channels; 518 | 519 | if ((mode & SYNTH_8BITS) && (mode & SYNTH_UNSIGNED)) { 520 | wanted.format = AUDIO_U8; 521 | } 522 | else if ((mode & SYNTH_8BITS) && (mode & SYNTH_SIGNED)) { 523 | wanted.format = AUDIO_S8; 524 | } 525 | else if ((mode & SYNTH_16BITS) && (mode & SYNTH_UNSIGNED)) { 526 | wanted.format = AUDIO_U16LSB; 527 | } 528 | else if ((mode & SYNTH_16BITS) && (mode & SYNTH_SIGNED)) { 529 | wanted.format = AUDIO_S16LSB; 530 | } 531 | 532 | wanted.callback = audioCallback; 533 | wanted.userdata = (void*)&data; 534 | 535 | /* Open the device, so the song may be played */ 536 | dev = SDL_OpenAudioDevice(0, 0, &wanted, &specs, 0); 537 | SYNTH_ASSERT_ERR(dev != 0, SYNTH_INTERNAL_ERR); 538 | 539 | /* Play the song */ 540 | SDL_PauseAudioDevice(dev, 0); 541 | 542 | /* Wait long enough, until the audio is played */ 543 | printf("Sleeping for %ums, while the song plays...\n", 544 | (unsigned int)maxLen * 1000 / freq + 1); 545 | SDL_Delay((unsigned int)maxLen * 1000 / freq + 1); 546 | 547 | rv = SYNTH_OK; 548 | __err: 549 | if (rv != SYNTH_OK) { 550 | printf("An error happened!\n"); 551 | } 552 | 553 | if (dev != 0) { 554 | SDL_PauseAudioDevice(dev, 1); 555 | SDL_CloseAudioDevice(dev); 556 | } 557 | 558 | if (didInitSDL) { 559 | SDL_Quit(); 560 | } 561 | 562 | if (pCtx) { 563 | printf("Releasing resources used by the lib...\n"); 564 | synth_free(&pCtx); 565 | } 566 | 567 | /* Release every buffer */ 568 | if (ppBufs) { 569 | int i; 570 | 571 | i = 0; 572 | while (i < numTracks) { 573 | if (ppBufs[i]) { 574 | free(ppBufs[i]); 575 | } 576 | 577 | i++; 578 | } 579 | free(ppBufs); 580 | } 581 | 582 | if (pBufLens) { 583 | free(pBufLens); 584 | } 585 | 586 | printf("Exiting...\n"); 587 | return rv; 588 | } 589 | 590 | --------------------------------------------------------------------------------