From 9639b14e1bbfb9065d788dd6341527c93ad55e88 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sun, 17 Apr 2016 14:48:20 +0200 Subject: Reduce PCM buffer size for Android to avoid stalls --- src/audio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 260f6778..75383385 100644 --- a/src/audio.c +++ b/src/audio.c @@ -57,8 +57,8 @@ //---------------------------------------------------------------------------------- #define MUSIC_STREAM_BUFFERS 2 -#if defined(PLATFORM_RPI) - // NOTE: On RPI should be lower to avoid frame-stalls +#if defined(PLATFORM_RPI) || defined(PLATFORM_ANDROID) + // NOTE: On RPI and Android should be lower to avoid frame-stalls #define MUSIC_BUFFER_SIZE 4096*2 // PCM data buffer (short) - 16Kb (RPI) #else // NOTE: On HTML5 (emscripten) this is allocated on heap, by default it's only 16MB!...just take care... -- cgit v1.2.3 From ec2cbaa5eb5c4bca059c5592932746369e1e293d Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sun, 24 Apr 2016 15:25:48 -0700 Subject: Added proto version of jar_xm This is an early draft, needs lots of work. Still need to figure out way to calculate total length of song. This is hard because xm tracks stream out zeros when done, only position in track can be found. Position does not give any direct value of how much more time is left. I think that by setting the loop count to 1 and seeking until the end I can total up the number of samples and come up with a length. --- src/audio.c | 81 +- src/jar_xm.h | 2647 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2712 insertions(+), 16 deletions(-) create mode 100644 src/jar_xm.h (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 75383385..9baa2222 100644 --- a/src/audio.c +++ b/src/audio.c @@ -42,6 +42,8 @@ #include // Required for strcmp() #include // Used for .WAV loading +#include "jar_xm.h" // For playing .xm files + #if defined(AUDIO_STANDALONE) #include // Used for functions with variable number of parameters (TraceLog()) #else @@ -73,6 +75,7 @@ // NOTE: Anything longer than ~10 seconds should be streamed... typedef struct Music { stb_vorbis *stream; + jar_xm_context_t *chipctx; // Stores jar_xm context ALuint buffers[MUSIC_STREAM_BUFFERS]; ALuint source; @@ -82,7 +85,7 @@ typedef struct Music { int sampleRate; int totalSamplesLeft; bool loop; - + bool chipTune; // True if chiptune is loaded } Music; #if defined(AUDIO_STANDALONE) @@ -564,6 +567,22 @@ void PlayMusicStream(char *fileName) currentMusic.totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels; } } + else if (strcmp(GetExtension(fileName),"xm") == 0) + { + currentMusic.chipTune = true; + currentMusic.channels = 2; + currentMusic.sampleRate = 48000; + currentMusic.loop = true; + + // only stereo/float is supported for xm + if(info.channels == 2 && !jar_xm_create_context_from_file(¤tMusic.chipctx, currentMusic.sampleRate, fileName)) + { + currentMusic.format = AL_FORMAT_STEREO_FLOAT32; + jar_xm_set_max_loop_count(currentMusic.chipctx, 0); //infinite number of loops + //currentMusic.totalSamplesLeft = ; // Unsure of how to calculate this + musicEnabled = true; + } + } else TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName); } @@ -572,14 +591,19 @@ void StopMusicStream(void) { if (musicEnabled) { - alSourceStop(currentMusic.source); - - EmptyMusicStream(); // Empty music buffers - - alDeleteSources(1, ¤tMusic.source); - alDeleteBuffers(2, currentMusic.buffers); - - stb_vorbis_close(currentMusic.stream); + alSourceStop(currentMusic.source); + EmptyMusicStream(); // Empty music buffers + alDeleteSources(1, ¤tMusic.source); + alDeleteBuffers(2, currentMusic.buffers); + + if (currentMusic.chipTune) + { + jar_xm_free_context(currentMusic.chipctx); + } + else + { + stb_vorbis_close(currentMusic.stream); + } } musicEnabled = false; @@ -633,7 +657,15 @@ void SetMusicVolume(float volume) // Get current music time length (in seconds) float GetMusicTimeLength(void) { - float totalSeconds = stb_vorbis_stream_length_in_seconds(currentMusic.stream); + float totalSeconds; + if (currentMusic.chipTune) + { + //totalSeconds = (float)samples; // Need to figure out how toget this + } + else + { + totalSeconds = stb_vorbis_stream_length_in_seconds(currentMusic.stream); + } return totalSeconds; } @@ -641,11 +673,20 @@ float GetMusicTimeLength(void) // Get current music time played (in seconds) float GetMusicTimePlayed(void) { - int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels; - - int samplesPlayed = totalSamples - currentMusic.totalSamplesLeft; - - float secondsPlayed = (float)samplesPlayed / (currentMusic.sampleRate * currentMusic.channels); + float secondsPlayed; + if (currentMusic.chipTune) + { + uint64_t* samples; + jar_xm_get_position(currentMusic.chipctx, NULL, NULL, NULL, samples); // Unsure if this is the desired value + secondsPlayed = (float)samples; + } + else + { + int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels; + int samplesPlayed = totalSamples - currentMusic.totalSamplesLeft; + secondsPlayed = (float)samplesPlayed / (currentMusic.sampleRate * currentMusic.channels); + } + return secondsPlayed; } @@ -668,7 +709,15 @@ static bool BufferMusicStream(ALuint buffer) { while (size < MUSIC_BUFFER_SIZE) { - streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic.stream, currentMusic.channels, pcm + size, MUSIC_BUFFER_SIZE - size); + if (currentMusic.chipTune) + { + jar_xm_generate_samples(currentMusic.chipctx, pcm + size, (MUSIC_BUFFER_SIZE - size)/2); + streamedBytes = (MUSIC_BUFFER_SIZE - size)/2; // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. + } + else + { + streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic.stream, currentMusic.channels, pcm + size, MUSIC_BUFFER_SIZE - size); + } if (streamedBytes > 0) size += (streamedBytes*currentMusic.channels); else break; diff --git a/src/jar_xm.h b/src/jar_xm.h new file mode 100644 index 00000000..2f102cf8 --- /dev/null +++ b/src/jar_xm.h @@ -0,0 +1,2647 @@ +// jar_xm.h - v0.01 - public domain - Joshua Reisenauer, MAR 2016 +// +// HISTORY: +// +// v0.01 2016-02-22 Setup +// +// +// USAGE: +// +// In ONE source file, put: +// +// #define JAR_XM_IMPLEMENTATION +// #include "jar_xm.h" +// +// Other source files should just include jar_xm.h +// +// SAMPLE CODE: +// +// jar_xm_context_t *musicptr; +// float musicBuffer[48000 / 60]; +// int intro_load(void) +// { +// jar_xm_create_context_from_file(&musicptr, 48000, "Song.XM"); +// return 1; +// } +// int intro_unload(void) +// { +// jar_xm_free_context(musicptr); +// return 1; +// } +// int intro_tick(long counter) +// { +// jar_xm_generate_samples(musicptr, musicBuffer, (48000 / 60) / 2); +// if(IsKeyDown(KEY_ENTER)) +// return 1; +// return 0; +// } +// +// +// LISCENSE - FOR LIBXM: +// +// Author: Romain "Artefact2" Dalmaso +// Contributor: Dan Spencer +// Repackaged into jar_xm.h By: Joshua Adam Reisenauer +// This program is free software. It comes without any warranty, to the +// extent permitted by applicable law. You can redistribute it and/or +// modify it under the terms of the Do What The Fuck You Want To Public +// License, Version 2, as published by Sam Hocevar. See +// http://sam.zoy.org/wtfpl/COPYING for more details. + +#ifndef INCLUDE_JAR_XM_H +#define INCLUDE_JAR_XM_H + +#define JAR_XM_DEBUG 0 +#define JAR_XM_LINEAR_INTERPOLATION 1 // speed increase with decrease in quality +#define JAR_XM_DEFENSIVE 1 +#define JAR_XM_RAMPING 1 + +#include +#include +#include +#include +#include +#include + +//------------------------------------------------------------------------------- +#ifdef __cplusplus +extern "C" { +#endif + +struct jar_xm_context_s; +typedef struct jar_xm_context_s jar_xm_context_t; + +/** Create a XM context. + * + * @param moddata the contents of the module + * @param rate play rate in Hz, recommended value of 48000 + * + * @returns 0 on success + * @returns 1 if module data is not sane + * @returns 2 if memory allocation failed + * @returns 3 unable to open input file + * @returns 4 fseek() failed + * @returns 5 fread() failed + * @returns 6 unkown error + * + * @deprecated This function is unsafe! + * @see jar_xm_create_context_safe() + */ +int jar_xm_create_context_from_file(jar_xm_context_t** ctx, uint32_t rate, const char* filename); + +/** Create a XM context. + * + * @param moddata the contents of the module + * @param rate play rate in Hz, recommended value of 48000 + * + * @returns 0 on success + * @returns 1 if module data is not sane + * @returns 2 if memory allocation failed + * + * @deprecated This function is unsafe! + * @see jar_xm_create_context_safe() + */ +int jar_xm_create_context(jar_xm_context_t**, const char* moddata, uint32_t rate); + +/** Create a XM context. + * + * @param moddata the contents of the module + * @param moddata_length the length of the contents of the module, in bytes + * @param rate play rate in Hz, recommended value of 48000 + * + * @returns 0 on success + * @returns 1 if module data is not sane + * @returns 2 if memory allocation failed + */ +int jar_xm_create_context_safe(jar_xm_context_t**, const char* moddata, size_t moddata_length, uint32_t rate); + +/** Free a XM context created by jar_xm_create_context(). */ +void jar_xm_free_context(jar_xm_context_t*); + +/** Play the module and put the sound samples in an output buffer. + * + * @param output buffer of 2*numsamples elements + * @param numsamples number of samples to generate + */ +void jar_xm_generate_samples(jar_xm_context_t*, float* output, size_t numsamples); + +/** Play the module, resample from 32 bit to 16 bit, and put the sound samples in an output buffer. + * + * @param output buffer of 2*numsamples elements + * @param numsamples number of samples to generate + */ +void jar_xm_generate_samples_16bit(jar_xm_context_t* ctx, short* output, size_t numsamples) +{ + float* musicBuffer = malloc((2*numsamples)*sizeof(float)); + short* musicBuffer2 = malloc((2*numsamples)*sizeof(short)); + + jar_xm_generate_samples(ctx, musicBuffer, numsamples); + + int x; + for(x=0;x<2*numsamples;x++) + musicBuffer2[x] = musicBuffer[x] * SHRT_MAX; + + memcpy(output, musicBuffer2, (2*numsamples)*sizeof(short)); + free(musicBuffer); + free(musicBuffer2); +} + +/** Play the module, resample from 32 bit to 8 bit, and put the sound samples in an output buffer. + * + * @param output buffer of 2*numsamples elements + * @param numsamples number of samples to generate + */ +void jar_xm_generate_samples_8bit(jar_xm_context_t* ctx, char* output, size_t numsamples) +{ + float* musicBuffer = malloc((2*numsamples)*sizeof(float)); + char* musicBuffer2 = malloc((2*numsamples)*sizeof(char)); + + jar_xm_generate_samples(ctx, musicBuffer, numsamples); + + int x; + for(x=0;x<2*numsamples;x++) + musicBuffer2[x] = musicBuffer[x] * CHAR_MAX; + + memcpy(output, musicBuffer2, (2*numsamples)*sizeof(char)); + free(musicBuffer); + free(musicBuffer2); +} + + + +/** Set the maximum number of times a module can loop. After the + * specified number of loops, calls to jar_xm_generate_samples will only + * generate silence. You can control the current number of loops with + * jar_xm_get_loop_count(). + * + * @param loopcnt maximum number of loops. Use 0 to loop + * indefinitely. */ +void jar_xm_set_max_loop_count(jar_xm_context_t*, uint8_t loopcnt); + +/** Get the loop count of the currently playing module. This value is + * 0 when the module is still playing, 1 when the module has looped + * once, etc. */ +uint8_t jar_xm_get_loop_count(jar_xm_context_t*); + + + +/** Mute or unmute a channel. + * + * @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...). + * + * @return whether the channel was muted. + */ +bool jar_xm_mute_channel(jar_xm_context_t*, uint16_t, bool); + +/** Mute or unmute an instrument. + * + * @note Instrument numbers go from 1 to + * jar_xm_get_number_of_instruments(...). + * + * @return whether the instrument was muted. + */ +bool jar_xm_mute_instrument(jar_xm_context_t*, uint16_t, bool); + + + +/** Get the module name as a NUL-terminated string. */ +const char* jar_xm_get_module_name(jar_xm_context_t*); + +/** Get the tracker name as a NUL-terminated string. */ +const char* jar_xm_get_tracker_name(jar_xm_context_t*); + + + +/** Get the number of channels. */ +uint16_t jar_xm_get_number_of_channels(jar_xm_context_t*); + +/** Get the module length (in patterns). */ +uint16_t jar_xm_get_module_length(jar_xm_context_t*); + +/** Get the number of patterns. */ +uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t*); + +/** Get the number of rows of a pattern. + * + * @note Pattern numbers go from 0 to + * jar_xm_get_number_of_patterns(...)-1. + */ +uint16_t jar_xm_get_number_of_rows(jar_xm_context_t*, uint16_t); + +/** Get the number of instruments. */ +uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t*); + +/** Get the number of samples of an instrument. + * + * @note Instrument numbers go from 1 to + * jar_xm_get_number_of_instruments(...). + */ +uint16_t jar_xm_get_number_of_samples(jar_xm_context_t*, uint16_t); + + + +/** Get the current module speed. + * + * @param bpm will receive the current BPM + * @param tempo will receive the current tempo (ticks per line) + */ +void jar_xm_get_playing_speed(jar_xm_context_t*, uint16_t* bpm, uint16_t* tempo); + +/** Get the current position in the module being played. + * + * @param pattern_index if not NULL, will receive the current pattern + * index in the POT (pattern order table) + * + * @param pattern if not NULL, will receive the current pattern number + * + * @param row if not NULL, will receive the current row + * + * @param samples if not NULL, will receive the total number of + * generated samples (divide by sample rate to get seconds of + * generated audio) + */ +void jar_xm_get_position(jar_xm_context_t*, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples); + +/** Get the latest time (in number of generated samples) when a + * particular instrument was triggered in any channel. + * + * @note Instrument numbers go from 1 to + * jar_xm_get_number_of_instruments(...). + */ +uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t*, uint16_t); + +/** Get the latest time (in number of generated samples) when a + * particular sample was triggered in any channel. + * + * @note Instrument numbers go from 1 to + * jar_xm_get_number_of_instruments(...). + * + * @note Sample numbers go from 0 to + * jar_xm_get_nubmer_of_samples(...,instr)-1. + */ +uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t*, uint16_t instr, uint16_t sample); + +/** Get the latest time (in number of generated samples) when any + * instrument was triggered in a given channel. + * + * @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...). + */ +uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t*, uint16_t); + +#ifdef __cplusplus +} +#endif +//------------------------------------------------------------------------------- + + + + + + +//Function Definitions----------------------------------------------------------- +#ifdef JAR_XM_IMPLEMENTATION + +#include +#include + +#if JAR_XM_DEBUG +#include +#define DEBUG(fmt, ...) do { \ + fprintf(stderr, "%s(): " fmt "\n", __func__, __VA_ARGS__); \ + fflush(stderr); \ + } while(0) +#else +#define DEBUG(...) +#endif + +#if jar_xm_BIG_ENDIAN +#error "Big endian platforms are not yet supported, sorry" +/* Make sure the compiler stops, even if #error is ignored */ +extern int __fail[-1]; +#endif + +/* ----- XM constants ----- */ + +#define SAMPLE_NAME_LENGTH 22 +#define INSTRUMENT_NAME_LENGTH 22 +#define MODULE_NAME_LENGTH 20 +#define TRACKER_NAME_LENGTH 20 +#define PATTERN_ORDER_TABLE_LENGTH 256 +#define NUM_NOTES 96 +#define NUM_ENVELOPE_POINTS 12 +#define MAX_NUM_ROWS 256 + +#if JAR_XM_RAMPING +#define jar_xm_SAMPLE_RAMPING_POINTS 0x20 +#endif + +/* ----- Data types ----- */ + +enum jar_xm_waveform_type_e { + jar_xm_SINE_WAVEFORM = 0, + jar_xm_RAMP_DOWN_WAVEFORM = 1, + jar_xm_SQUARE_WAVEFORM = 2, + jar_xm_RANDOM_WAVEFORM = 3, + jar_xm_RAMP_UP_WAVEFORM = 4, +}; +typedef enum jar_xm_waveform_type_e jar_xm_waveform_type_t; + +enum jar_xm_loop_type_e { + jar_xm_NO_LOOP, + jar_xm_FORWARD_LOOP, + jar_xm_PING_PONG_LOOP, +}; +typedef enum jar_xm_loop_type_e jar_xm_loop_type_t; + +enum jar_xm_frequency_type_e { + jar_xm_LINEAR_FREQUENCIES, + jar_xm_AMIGA_FREQUENCIES, +}; +typedef enum jar_xm_frequency_type_e jar_xm_frequency_type_t; + +struct jar_xm_envelope_point_s { + uint16_t frame; + uint16_t value; +}; +typedef struct jar_xm_envelope_point_s jar_xm_envelope_point_t; + +struct jar_xm_envelope_s { + jar_xm_envelope_point_t points[NUM_ENVELOPE_POINTS]; + uint8_t num_points; + uint8_t sustain_point; + uint8_t loop_start_point; + uint8_t loop_end_point; + bool enabled; + bool sustain_enabled; + bool loop_enabled; +}; +typedef struct jar_xm_envelope_s jar_xm_envelope_t; + +struct jar_xm_sample_s { + char name[SAMPLE_NAME_LENGTH + 1]; + int8_t bits; /* Either 8 or 16 */ + + uint32_t length; + uint32_t loop_start; + uint32_t loop_length; + uint32_t loop_end; + float volume; + int8_t finetune; + jar_xm_loop_type_t loop_type; + float panning; + int8_t relative_note; + uint64_t latest_trigger; + + float* data; + }; + typedef struct jar_xm_sample_s jar_xm_sample_t; + + struct jar_xm_instrument_s { + char name[INSTRUMENT_NAME_LENGTH + 1]; + uint16_t num_samples; + uint8_t sample_of_notes[NUM_NOTES]; + jar_xm_envelope_t volume_envelope; + jar_xm_envelope_t panning_envelope; + jar_xm_waveform_type_t vibrato_type; + uint8_t vibrato_sweep; + uint8_t vibrato_depth; + uint8_t vibrato_rate; + uint16_t volume_fadeout; + uint64_t latest_trigger; + bool muted; + + jar_xm_sample_t* samples; + }; + typedef struct jar_xm_instrument_s jar_xm_instrument_t; + + struct jar_xm_pattern_slot_s { + uint8_t note; /* 1-96, 97 = Key Off note */ + uint8_t instrument; /* 1-128 */ + uint8_t volume_column; + uint8_t effect_type; + uint8_t effect_param; + }; + typedef struct jar_xm_pattern_slot_s jar_xm_pattern_slot_t; + + struct jar_xm_pattern_s { + uint16_t num_rows; + jar_xm_pattern_slot_t* slots; /* Array of size num_rows * num_channels */ + }; + typedef struct jar_xm_pattern_s jar_xm_pattern_t; + + struct jar_xm_module_s { + char name[MODULE_NAME_LENGTH + 1]; + char trackername[TRACKER_NAME_LENGTH + 1]; + uint16_t length; + uint16_t restart_position; + uint16_t num_channels; + uint16_t num_patterns; + uint16_t num_instruments; + jar_xm_frequency_type_t frequency_type; + uint8_t pattern_table[PATTERN_ORDER_TABLE_LENGTH]; + + jar_xm_pattern_t* patterns; + jar_xm_instrument_t* instruments; /* Instrument 1 has index 0, + * instrument 2 has index 1, etc. */ + }; + typedef struct jar_xm_module_s jar_xm_module_t; + + struct jar_xm_channel_context_s { + float note; + float orig_note; /* The original note before effect modifications, as read in the pattern. */ + jar_xm_instrument_t* instrument; /* Could be NULL */ + jar_xm_sample_t* sample; /* Could be NULL */ + jar_xm_pattern_slot_t* current; + + float sample_position; + float period; + float frequency; + float step; + bool ping; /* For ping-pong samples: true is -->, false is <-- */ + + float volume; /* Ideally between 0 (muted) and 1 (loudest) */ + float panning; /* Between 0 (left) and 1 (right); 0.5 is centered */ + + uint16_t autovibrato_ticks; + + bool sustained; + float fadeout_volume; + float volume_envelope_volume; + float panning_envelope_panning; + uint16_t volume_envelope_frame_count; + uint16_t panning_envelope_frame_count; + + float autovibrato_note_offset; + + bool arp_in_progress; + uint8_t arp_note_offset; + uint8_t volume_slide_param; + uint8_t fine_volume_slide_param; + uint8_t global_volume_slide_param; + uint8_t panning_slide_param; + uint8_t portamento_up_param; + uint8_t portamento_down_param; + uint8_t fine_portamento_up_param; + uint8_t fine_portamento_down_param; + uint8_t extra_fine_portamento_up_param; + uint8_t extra_fine_portamento_down_param; + uint8_t tone_portamento_param; + float tone_portamento_target_period; + uint8_t multi_retrig_param; + uint8_t note_delay_param; + uint8_t pattern_loop_origin; /* Where to restart a E6y loop */ + uint8_t pattern_loop_count; /* How many loop passes have been done */ + bool vibrato_in_progress; + jar_xm_waveform_type_t vibrato_waveform; + bool vibrato_waveform_retrigger; /* True if a new note retriggers the waveform */ + uint8_t vibrato_param; + uint16_t vibrato_ticks; /* Position in the waveform */ + float vibrato_note_offset; + jar_xm_waveform_type_t tremolo_waveform; + bool tremolo_waveform_retrigger; + uint8_t tremolo_param; + uint8_t tremolo_ticks; + float tremolo_volume; + uint8_t tremor_param; + bool tremor_on; + + uint64_t latest_trigger; + bool muted; + +#if JAR_XM_RAMPING + /* These values are updated at the end of each tick, to save + * a couple of float operations on every generated sample. */ + float target_panning; + float target_volume; + + unsigned long frame_count; + float end_of_previous_sample[jar_xm_SAMPLE_RAMPING_POINTS]; +#endif + + float actual_panning; + float actual_volume; + }; + typedef struct jar_xm_channel_context_s jar_xm_channel_context_t; + + struct jar_xm_context_s { + void* allocated_memory; + jar_xm_module_t module; + uint32_t rate; + + uint16_t tempo; + uint16_t bpm; + float global_volume; + float amplification; + +#if JAR_XM_RAMPING + /* How much is a channel final volume allowed to change per + * sample; this is used to avoid abrubt volume changes which + * manifest as "clicks" in the generated sound. */ + float volume_ramp; + float panning_ramp; /* Same for panning. */ +#endif + + uint8_t current_table_index; + uint8_t current_row; + uint16_t current_tick; /* Can go below 255, with high tempo and a pattern delay */ + float remaining_samples_in_tick; + uint64_t generated_samples; + + bool position_jump; + bool pattern_break; + uint8_t jump_dest; + uint8_t jump_row; + + /* Extra ticks to be played before going to the next row - + * Used for EEy effect */ + uint16_t extra_ticks; + + uint8_t* row_loop_count; /* Array of size MAX_NUM_ROWS * module_length */ + uint8_t loop_count; + uint8_t max_loop_count; + + jar_xm_channel_context_t* channels; +}; + +/* ----- Internal API ----- */ + +#if JAR_XM_DEFENSIVE + +/** Check the module data for errors/inconsistencies. + * + * @returns 0 if everything looks OK. Module should be safe to load. + */ +int jar_xm_check_sanity_preload(const char*, size_t); + +/** Check a loaded module for errors/inconsistencies. + * + * @returns 0 if everything looks OK. + */ +int jar_xm_check_sanity_postload(jar_xm_context_t*); + +#endif + +/** Get the number of bytes needed to store the module data in a + * dynamically allocated blank context. + * + * Things that are dynamically allocated: + * - sample data + * - sample structures in instruments + * - pattern data + * - row loop count arrays + * - pattern structures in module + * - instrument structures in module + * - channel contexts + * - context structure itself + + * @returns 0 if everything looks OK. + */ +size_t jar_xm_get_memory_needed_for_context(const char*, size_t); + +/** Populate the context from module data. + * + * @returns pointer to the memory pool + */ +char* jar_xm_load_module(jar_xm_context_t*, const char*, size_t, char*); + +int jar_xm_create_context(jar_xm_context_t** ctxp, const char* moddata, uint32_t rate) { + return jar_xm_create_context_safe(ctxp, moddata, SIZE_MAX, rate); +} + +int jar_xm_create_context_safe(jar_xm_context_t** ctxp, const char* moddata, size_t moddata_length, uint32_t rate) { +#if JAR_XM_DEFENSIVE + int ret; +#endif + size_t bytes_needed; + char* mempool; + jar_xm_context_t* ctx; + +#if JAR_XM_DEFENSIVE + if((ret = jar_xm_check_sanity_preload(moddata, moddata_length))) { + DEBUG("jar_xm_check_sanity_preload() returned %i, module is not safe to load", ret); + return 1; + } +#endif + + bytes_needed = jar_xm_get_memory_needed_for_context(moddata, moddata_length); + mempool = malloc(bytes_needed); + if(mempool == NULL && bytes_needed > 0) { + /* malloc() failed, trouble ahead */ + DEBUG("call to malloc() failed, returned %p", (void*)mempool); + return 2; + } + + /* Initialize most of the fields to 0, 0.f, NULL or false depending on type */ + memset(mempool, 0, bytes_needed); + + ctx = (*ctxp = (jar_xm_context_t*)mempool); + ctx->allocated_memory = mempool; /* Keep original pointer for free() */ + mempool += sizeof(jar_xm_context_t); + + ctx->rate = rate; + mempool = jar_xm_load_module(ctx, moddata, moddata_length, mempool); + + ctx->channels = (jar_xm_channel_context_t*)mempool; + mempool += ctx->module.num_channels * sizeof(jar_xm_channel_context_t); + + ctx->global_volume = 1.f; + ctx->amplification = .25f; /* XXX: some bad modules may still clip. Find out something better. */ + +#if JAR_XM_RAMPING + ctx->volume_ramp = (1.f / 128.f); + ctx->panning_ramp = (1.f / 128.f); +#endif + + for(uint8_t i = 0; i < ctx->module.num_channels; ++i) { + jar_xm_channel_context_t* ch = ctx->channels + i; + + ch->ping = true; + ch->vibrato_waveform = jar_xm_SINE_WAVEFORM; + ch->vibrato_waveform_retrigger = true; + ch->tremolo_waveform = jar_xm_SINE_WAVEFORM; + ch->tremolo_waveform_retrigger = true; + + ch->volume = ch->volume_envelope_volume = ch->fadeout_volume = 1.0f; + ch->panning = ch->panning_envelope_panning = .5f; + ch->actual_volume = .0f; + ch->actual_panning = .5f; + } + + ctx->row_loop_count = (uint8_t*)mempool; + mempool += MAX_NUM_ROWS * sizeof(uint8_t); + +#if JAR_XM_DEFENSIVE + if((ret = jar_xm_check_sanity_postload(ctx))) { + DEBUG("jar_xm_check_sanity_postload() returned %i, module is not safe to play", ret); + jar_xm_free_context(ctx); + return 1; + } +#endif + + return 0; +} + +void jar_xm_free_context(jar_xm_context_t* context) { + free(context->allocated_memory); +} + +void jar_xm_set_max_loop_count(jar_xm_context_t* context, uint8_t loopcnt) { + context->max_loop_count = loopcnt; +} + +uint8_t jar_xm_get_loop_count(jar_xm_context_t* context) { + return context->loop_count; +} + + + +bool jar_xm_mute_channel(jar_xm_context_t* ctx, uint16_t channel, bool mute) { + bool old = ctx->channels[channel - 1].muted; + ctx->channels[channel - 1].muted = mute; + return old; +} + +bool jar_xm_mute_instrument(jar_xm_context_t* ctx, uint16_t instr, bool mute) { + bool old = ctx->module.instruments[instr - 1].muted; + ctx->module.instruments[instr - 1].muted = mute; + return old; +} + + + +const char* jar_xm_get_module_name(jar_xm_context_t* ctx) { + return ctx->module.name; +} + +const char* jar_xm_get_tracker_name(jar_xm_context_t* ctx) { + return ctx->module.trackername; +} + + + +uint16_t jar_xm_get_number_of_channels(jar_xm_context_t* ctx) { + return ctx->module.num_channels; +} + +uint16_t jar_xm_get_module_length(jar_xm_context_t* ctx) { + return ctx->module.length; +} + +uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t* ctx) { + return ctx->module.num_patterns; +} + +uint16_t jar_xm_get_number_of_rows(jar_xm_context_t* ctx, uint16_t pattern) { + return ctx->module.patterns[pattern].num_rows; +} + +uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t* ctx) { + return ctx->module.num_instruments; +} + +uint16_t jar_xm_get_number_of_samples(jar_xm_context_t* ctx, uint16_t instrument) { + return ctx->module.instruments[instrument - 1].num_samples; +} + + + +void jar_xm_get_playing_speed(jar_xm_context_t* ctx, uint16_t* bpm, uint16_t* tempo) { + if(bpm) *bpm = ctx->bpm; + if(tempo) *tempo = ctx->tempo; +} + +void jar_xm_get_position(jar_xm_context_t* ctx, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples) { + if(pattern_index) *pattern_index = ctx->current_table_index; + if(pattern) *pattern = ctx->module.pattern_table[ctx->current_table_index]; + if(row) *row = ctx->current_row; + if(samples) *samples = ctx->generated_samples; +} + +uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t* ctx, uint16_t instr) { + return ctx->module.instruments[instr - 1].latest_trigger; +} + +uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t* ctx, uint16_t instr, uint16_t sample) { + return ctx->module.instruments[instr - 1].samples[sample].latest_trigger; +} + +uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t* ctx, uint16_t chn) { + return ctx->channels[chn - 1].latest_trigger; +} + +/* .xm files are little-endian. (XXX: Are they really?) */ + +/* Bounded reader macros. + * If we attempt to read the buffer out-of-bounds, pretend that the buffer is + * infinitely padded with zeroes. + */ +#define READ_U8(offset) (((offset) < moddata_length) ? (*(uint8_t*)(moddata + (offset))) : 0) +#define READ_U16(offset) ((uint16_t)READ_U8(offset) | ((uint16_t)READ_U8((offset) + 1) << 8)) +#define READ_U32(offset) ((uint32_t)READ_U16(offset) | ((uint32_t)READ_U16((offset) + 2) << 16)) +#define READ_MEMCPY(ptr, offset, length) memcpy_pad(ptr, length, moddata, moddata_length, offset) + +static inline void memcpy_pad(void* dst, size_t dst_len, const void* src, size_t src_len, size_t offset) { + uint8_t* dst_c = dst; + const uint8_t* src_c = src; + + /* how many bytes can be copied without overrunning `src` */ + size_t copy_bytes = (src_len >= offset) ? (src_len - offset) : 0; + copy_bytes = copy_bytes > dst_len ? dst_len : copy_bytes; + + memcpy(dst_c, src_c + offset, copy_bytes); + /* padded bytes */ + memset(dst_c + copy_bytes, 0, dst_len - copy_bytes); +} + +#if JAR_XM_DEFENSIVE + +int jar_xm_check_sanity_preload(const char* module, size_t module_length) { + if(module_length < 60) { + return 4; + } + + if(memcmp("Extended Module: ", module, 17) != 0) { + return 1; + } + + if(module[37] != 0x1A) { + return 2; + } + + if(module[59] != 0x01 || module[58] != 0x04) { + /* Not XM 1.04 */ + return 3; + } + + return 0; +} + +int jar_xm_check_sanity_postload(jar_xm_context_t* ctx) { + /* @todo: plenty of stuff to do here… */ + + /* Check the POT */ + for(uint8_t i = 0; i < ctx->module.length; ++i) { + if(ctx->module.pattern_table[i] >= ctx->module.num_patterns) { + if(i+1 == ctx->module.length && ctx->module.length > 1) { + /* Cheap fix */ + --ctx->module.length; + DEBUG("trimming invalid POT at pos %X", i); + } else { + DEBUG("module has invalid POT, pos %X references nonexistent pattern %X", + i, + ctx->module.pattern_table[i]); + return 1; + } + } + } + + return 0; +} + +#endif + +size_t jar_xm_get_memory_needed_for_context(const char* moddata, size_t moddata_length) { + size_t memory_needed = 0; + size_t offset = 60; /* Skip the first header */ + uint16_t num_channels; + uint16_t num_patterns; + uint16_t num_instruments; + + /* Read the module header */ + + num_channels = READ_U16(offset + 8); + num_channels = READ_U16(offset + 8); + + num_patterns = READ_U16(offset + 10); + memory_needed += num_patterns * sizeof(jar_xm_pattern_t); + + num_instruments = READ_U16(offset + 12); + memory_needed += num_instruments * sizeof(jar_xm_instrument_t); + + memory_needed += MAX_NUM_ROWS * READ_U16(offset + 4) * sizeof(uint8_t); /* Module length */ + + /* Header size */ + offset += READ_U32(offset); + + /* Read pattern headers */ + for(uint16_t i = 0; i < num_patterns; ++i) { + uint16_t num_rows; + + num_rows = READ_U16(offset + 5); + memory_needed += num_rows * num_channels * sizeof(jar_xm_pattern_slot_t); + + /* Pattern header length + packed pattern data size */ + offset += READ_U32(offset) + READ_U16(offset + 7); + } + + /* Read instrument headers */ + for(uint16_t i = 0; i < num_instruments; ++i) { + uint16_t num_samples; + uint32_t sample_header_size = 0; + uint32_t sample_size_aggregate = 0; + + num_samples = READ_U16(offset + 27); + memory_needed += num_samples * sizeof(jar_xm_sample_t); + + if(num_samples > 0) { + sample_header_size = READ_U32(offset + 29); + } + + /* Instrument header size */ + offset += READ_U32(offset); + + for(uint16_t j = 0; j < num_samples; ++j) { + uint32_t sample_size; + uint8_t flags; + + sample_size = READ_U32(offset); + flags = READ_U8(offset + 14); + sample_size_aggregate += sample_size; + + if(flags & (1 << 4)) { + /* 16 bit sample */ + memory_needed += sample_size * (sizeof(float) >> 1); + } else { + /* 8 bit sample */ + memory_needed += sample_size * sizeof(float); + } + + offset += sample_header_size; + } + + offset += sample_size_aggregate; + } + + memory_needed += num_channels * sizeof(jar_xm_channel_context_t); + memory_needed += sizeof(jar_xm_context_t); + + return memory_needed; +} + +char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t moddata_length, char* mempool) { + size_t offset = 0; + jar_xm_module_t* mod = &(ctx->module); + + /* Read XM header */ + READ_MEMCPY(mod->name, offset + 17, MODULE_NAME_LENGTH); + READ_MEMCPY(mod->trackername, offset + 38, TRACKER_NAME_LENGTH); + offset += 60; + + /* Read module header */ + uint32_t header_size = READ_U32(offset); + + mod->length = READ_U16(offset + 4); + mod->restart_position = READ_U16(offset + 6); + mod->num_channels = READ_U16(offset + 8); + mod->num_patterns = READ_U16(offset + 10); + mod->num_instruments = READ_U16(offset + 12); + + mod->patterns = (jar_xm_pattern_t*)mempool; + mempool += mod->num_patterns * sizeof(jar_xm_pattern_t); + + mod->instruments = (jar_xm_instrument_t*)mempool; + mempool += mod->num_instruments * sizeof(jar_xm_instrument_t); + + uint16_t flags = READ_U32(offset + 14); + mod->frequency_type = (flags & (1 << 0)) ? jar_xm_LINEAR_FREQUENCIES : jar_xm_AMIGA_FREQUENCIES; + + ctx->tempo = READ_U16(offset + 16); + ctx->bpm = READ_U16(offset + 18); + + READ_MEMCPY(mod->pattern_table, offset + 20, PATTERN_ORDER_TABLE_LENGTH); + offset += header_size; + + /* Read patterns */ + for(uint16_t i = 0; i < mod->num_patterns; ++i) { + uint16_t packed_patterndata_size = READ_U16(offset + 7); + jar_xm_pattern_t* pat = mod->patterns + i; + + pat->num_rows = READ_U16(offset + 5); + + pat->slots = (jar_xm_pattern_slot_t*)mempool; + mempool += mod->num_channels * pat->num_rows * sizeof(jar_xm_pattern_slot_t); + + /* Pattern header length */ + offset += READ_U32(offset); + + if(packed_patterndata_size == 0) { + /* No pattern data is present */ + memset(pat->slots, 0, sizeof(jar_xm_pattern_slot_t) * pat->num_rows * mod->num_channels); + } else { + /* This isn't your typical for loop */ + for(uint16_t j = 0, k = 0; j < packed_patterndata_size; ++k) { + uint8_t note = READ_U8(offset + j); + jar_xm_pattern_slot_t* slot = pat->slots + k; + + if(note & (1 << 7)) { + /* MSB is set, this is a compressed packet */ + ++j; + + if(note & (1 << 0)) { + /* Note follows */ + slot->note = READ_U8(offset + j); + ++j; + } else { + slot->note = 0; + } + + if(note & (1 << 1)) { + /* Instrument follows */ + slot->instrument = READ_U8(offset + j); + ++j; + } else { + slot->instrument = 0; + } + + if(note & (1 << 2)) { + /* Volume column follows */ + slot->volume_column = READ_U8(offset + j); + ++j; + } else { + slot->volume_column = 0; + } + + if(note & (1 << 3)) { + /* Effect follows */ + slot->effect_type = READ_U8(offset + j); + ++j; + } else { + slot->effect_type = 0; + } + + if(note & (1 << 4)) { + /* Effect parameter follows */ + slot->effect_param = READ_U8(offset + j); + ++j; + } else { + slot->effect_param = 0; + } + } else { + /* Uncompressed packet */ + slot->note = note; + slot->instrument = READ_U8(offset + j + 1); + slot->volume_column = READ_U8(offset + j + 2); + slot->effect_type = READ_U8(offset + j + 3); + slot->effect_param = READ_U8(offset + j + 4); + j += 5; + } + } + } + + offset += packed_patterndata_size; + } + + /* Read instruments */ + for(uint16_t i = 0; i < ctx->module.num_instruments; ++i) { + uint32_t sample_header_size = 0; + jar_xm_instrument_t* instr = mod->instruments + i; + + READ_MEMCPY(instr->name, offset + 4, INSTRUMENT_NAME_LENGTH); + instr->num_samples = READ_U16(offset + 27); + + if(instr->num_samples > 0) { + /* Read extra header properties */ + sample_header_size = READ_U32(offset + 29); + READ_MEMCPY(instr->sample_of_notes, offset + 33, NUM_NOTES); + + instr->volume_envelope.num_points = READ_U8(offset + 225); + instr->panning_envelope.num_points = READ_U8(offset + 226); + + for(uint8_t j = 0; j < instr->volume_envelope.num_points; ++j) { + instr->volume_envelope.points[j].frame = READ_U16(offset + 129 + 4 * j); + instr->volume_envelope.points[j].value = READ_U16(offset + 129 + 4 * j + 2); + } + + for(uint8_t j = 0; j < instr->panning_envelope.num_points; ++j) { + instr->panning_envelope.points[j].frame = READ_U16(offset + 177 + 4 * j); + instr->panning_envelope.points[j].value = READ_U16(offset + 177 + 4 * j + 2); + } + + instr->volume_envelope.sustain_point = READ_U8(offset + 227); + instr->volume_envelope.loop_start_point = READ_U8(offset + 228); + instr->volume_envelope.loop_end_point = READ_U8(offset + 229); + + instr->panning_envelope.sustain_point = READ_U8(offset + 230); + instr->panning_envelope.loop_start_point = READ_U8(offset + 231); + instr->panning_envelope.loop_end_point = READ_U8(offset + 232); + + uint8_t flags = READ_U8(offset + 233); + instr->volume_envelope.enabled = flags & (1 << 0); + instr->volume_envelope.sustain_enabled = flags & (1 << 1); + instr->volume_envelope.loop_enabled = flags & (1 << 2); + + flags = READ_U8(offset + 234); + instr->panning_envelope.enabled = flags & (1 << 0); + instr->panning_envelope.sustain_enabled = flags & (1 << 1); + instr->panning_envelope.loop_enabled = flags & (1 << 2); + + instr->vibrato_type = READ_U8(offset + 235); + if(instr->vibrato_type == 2) { + instr->vibrato_type = 1; + } else if(instr->vibrato_type == 1) { + instr->vibrato_type = 2; + } + instr->vibrato_sweep = READ_U8(offset + 236); + instr->vibrato_depth = READ_U8(offset + 237); + instr->vibrato_rate = READ_U8(offset + 238); + instr->volume_fadeout = READ_U16(offset + 239); + + instr->samples = (jar_xm_sample_t*)mempool; + mempool += instr->num_samples * sizeof(jar_xm_sample_t); + } else { + instr->samples = NULL; + } + + /* Instrument header size */ + offset += READ_U32(offset); + + for(uint16_t j = 0; j < instr->num_samples; ++j) { + /* Read sample header */ + jar_xm_sample_t* sample = instr->samples + j; + + sample->length = READ_U32(offset); + sample->loop_start = READ_U32(offset + 4); + sample->loop_length = READ_U32(offset + 8); + sample->loop_end = sample->loop_start + sample->loop_length; + sample->volume = (float)READ_U8(offset + 12) / (float)0x40; + sample->finetune = (int8_t)READ_U8(offset + 13); + + uint8_t flags = READ_U8(offset + 14); + if((flags & 3) == 0) { + sample->loop_type = jar_xm_NO_LOOP; + } else if((flags & 3) == 1) { + sample->loop_type = jar_xm_FORWARD_LOOP; + } else { + sample->loop_type = jar_xm_PING_PONG_LOOP; + } + + sample->bits = (flags & (1 << 4)) ? 16 : 8; + + sample->panning = (float)READ_U8(offset + 15) / (float)0xFF; + sample->relative_note = (int8_t)READ_U8(offset + 16); + READ_MEMCPY(sample->name, 18, SAMPLE_NAME_LENGTH); + sample->data = (float*)mempool; + + if(sample->bits == 16) { + /* 16 bit sample */ + mempool += sample->length * (sizeof(float) >> 1); + sample->loop_start >>= 1; + sample->loop_length >>= 1; + sample->loop_end >>= 1; + sample->length >>= 1; + } else { + /* 8 bit sample */ + mempool += sample->length * sizeof(float); + } + + offset += sample_header_size; + } + + for(uint16_t j = 0; j < instr->num_samples; ++j) { + /* Read sample data */ + jar_xm_sample_t* sample = instr->samples + j; + uint32_t length = sample->length; + + if(sample->bits == 16) { + int16_t v = 0; + for(uint32_t k = 0; k < length; ++k) { + v = v + (int16_t)READ_U16(offset + (k << 1)); + sample->data[k] = (float)v / (float)(1 << 15); + } + offset += sample->length << 1; + } else { + int8_t v = 0; + for(uint32_t k = 0; k < length; ++k) { + v = v + (int8_t)READ_U8(offset + k); + sample->data[k] = (float)v / (float)(1 << 7); + } + offset += sample->length; + } + } + } + + return mempool; +} + +//------------------------------------------------------------------------------- +//THE FOLLOWING IS FOR PLAYING +//------------------------------------------------------------------------------- + +/* ----- Static functions ----- */ + +static float jar_xm_waveform(jar_xm_waveform_type_t, uint8_t); +static void jar_xm_autovibrato(jar_xm_context_t*, jar_xm_channel_context_t*); +static void jar_xm_vibrato(jar_xm_context_t*, jar_xm_channel_context_t*, uint8_t, uint16_t); +static void jar_xm_tremolo(jar_xm_context_t*, jar_xm_channel_context_t*, uint8_t, uint16_t); +static void jar_xm_arpeggio(jar_xm_context_t*, jar_xm_channel_context_t*, uint8_t, uint16_t); +static void jar_xm_tone_portamento(jar_xm_context_t*, jar_xm_channel_context_t*); +static void jar_xm_pitch_slide(jar_xm_context_t*, jar_xm_channel_context_t*, float); +static void jar_xm_panning_slide(jar_xm_channel_context_t*, uint8_t); +static void jar_xm_volume_slide(jar_xm_channel_context_t*, uint8_t); + +static float jar_xm_envelope_lerp(jar_xm_envelope_point_t*, jar_xm_envelope_point_t*, uint16_t); +static void jar_xm_envelope_tick(jar_xm_channel_context_t*, jar_xm_envelope_t*, uint16_t*, float*); +static void jar_xm_envelopes(jar_xm_channel_context_t*); + +static float jar_xm_linear_period(float); +static float jar_xm_linear_frequency(float); +static float jar_xm_amiga_period(float); +static float jar_xm_amiga_frequency(float); +static float jar_xm_period(jar_xm_context_t*, float); +static float jar_xm_frequency(jar_xm_context_t*, float, float); +static void jar_xm_update_frequency(jar_xm_context_t*, jar_xm_channel_context_t*); + +static void jar_xm_handle_note_and_instrument(jar_xm_context_t*, jar_xm_channel_context_t*, jar_xm_pattern_slot_t*); +static void jar_xm_trigger_note(jar_xm_context_t*, jar_xm_channel_context_t*, unsigned int flags); +static void jar_xm_cut_note(jar_xm_channel_context_t*); +static void jar_xm_key_off(jar_xm_channel_context_t*); + +static void jar_xm_post_pattern_change(jar_xm_context_t*); +static void jar_xm_row(jar_xm_context_t*); +static void jar_xm_tick(jar_xm_context_t*); + +static float jar_xm_next_of_sample(jar_xm_channel_context_t*); +static void jar_xm_sample(jar_xm_context_t*, float*, float*); + +/* ----- Other oddities ----- */ + +#define jar_xm_TRIGGER_KEEP_VOLUME (1 << 0) +#define jar_xm_TRIGGER_KEEP_PERIOD (1 << 1) +#define jar_xm_TRIGGER_KEEP_SAMPLE_POSITION (1 << 2) + +static const uint16_t amiga_frequencies[] = { + 1712, 1616, 1525, 1440, /* C-2, C#2, D-2, D#2 */ + 1357, 1281, 1209, 1141, /* E-2, F-2, F#2, G-2 */ + 1077, 1017, 961, 907, /* G#2, A-2, A#2, B-2 */ + 856, /* C-3 */ +}; + +static const float multi_retrig_add[] = { + 0.f, -1.f, -2.f, -4.f, /* 0, 1, 2, 3 */ + -8.f, -16.f, 0.f, 0.f, /* 4, 5, 6, 7 */ + 0.f, 1.f, 2.f, 4.f, /* 8, 9, A, B */ + 8.f, 16.f, 0.f, 0.f /* C, D, E, F */ +}; + +static const float multi_retrig_multiply[] = { + 1.f, 1.f, 1.f, 1.f, /* 0, 1, 2, 3 */ + 1.f, 1.f, .6666667f, .5f, /* 4, 5, 6, 7 */ + 1.f, 1.f, 1.f, 1.f, /* 8, 9, A, B */ + 1.f, 1.f, 1.5f, 2.f /* C, D, E, F */ +}; + +#define jar_xm_CLAMP_UP1F(vol, limit) do { \ + if((vol) > (limit)) (vol) = (limit); \ + } while(0) +#define jar_xm_CLAMP_UP(vol) jar_xm_CLAMP_UP1F((vol), 1.f) + +#define jar_xm_CLAMP_DOWN1F(vol, limit) do { \ + if((vol) < (limit)) (vol) = (limit); \ + } while(0) +#define jar_xm_CLAMP_DOWN(vol) jar_xm_CLAMP_DOWN1F((vol), .0f) + +#define jar_xm_CLAMP2F(vol, up, down) do { \ + if((vol) > (up)) (vol) = (up); \ + else if((vol) < (down)) (vol) = (down); \ + } while(0) +#define jar_xm_CLAMP(vol) jar_xm_CLAMP2F((vol), 1.f, .0f) + +#define jar_xm_SLIDE_TOWARDS(val, goal, incr) do { \ + if((val) > (goal)) { \ + (val) -= (incr); \ + jar_xm_CLAMP_DOWN1F((val), (goal)); \ + } else if((val) < (goal)) { \ + (val) += (incr); \ + jar_xm_CLAMP_UP1F((val), (goal)); \ + } \ + } while(0) + +#define jar_xm_LERP(u, v, t) ((u) + (t) * ((v) - (u))) +#define jar_xm_INVERSE_LERP(u, v, lerp) (((lerp) - (u)) / ((v) - (u))) + +#define HAS_TONE_PORTAMENTO(s) ((s)->effect_type == 3 \ + || (s)->effect_type == 5 \ + || ((s)->volume_column >> 4) == 0xF) +#define HAS_ARPEGGIO(s) ((s)->effect_type == 0 \ + && (s)->effect_param != 0) +#define HAS_VIBRATO(s) ((s)->effect_type == 4 \ + || (s)->effect_param == 6 \ + || ((s)->volume_column >> 4) == 0xB) +#define NOTE_IS_VALID(n) ((n) > 0 && (n) < 97) + +/* ----- Function definitions ----- */ + +static float jar_xm_waveform(jar_xm_waveform_type_t waveform, uint8_t step) { + static unsigned int next_rand = 24492; + step %= 0x40; + + switch(waveform) { + + case jar_xm_SINE_WAVEFORM: + /* Why not use a table? For saving space, and because there's + * very very little actual performance gain. */ + return -sinf(2.f * 3.141592f * (float)step / (float)0x40); + + case jar_xm_RAMP_DOWN_WAVEFORM: + /* Ramp down: 1.0f when step = 0; -1.0f when step = 0x40 */ + return (float)(0x20 - step) / 0x20; + + case jar_xm_SQUARE_WAVEFORM: + /* Square with a 50% duty */ + return (step >= 0x20) ? 1.f : -1.f; + + case jar_xm_RANDOM_WAVEFORM: + /* Use the POSIX.1-2001 example, just to be deterministic + * across different machines */ + next_rand = next_rand * 1103515245 + 12345; + return (float)((next_rand >> 16) & 0x7FFF) / (float)0x4000 - 1.f; + + case jar_xm_RAMP_UP_WAVEFORM: + /* Ramp up: -1.f when step = 0; 1.f when step = 0x40 */ + return (float)(step - 0x20) / 0x20; + + default: + break; + + } + + return .0f; +} + +static void jar_xm_autovibrato(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch) { + if(ch->instrument == NULL || ch->instrument->vibrato_depth == 0) return; + jar_xm_instrument_t* instr = ch->instrument; + float sweep = 1.f; + + if(ch->autovibrato_ticks < instr->vibrato_sweep) { + /* No idea if this is correct, but it sounds close enough… */ + sweep = jar_xm_LERP(0.f, 1.f, (float)ch->autovibrato_ticks / (float)instr->vibrato_sweep); + } + + unsigned int step = ((ch->autovibrato_ticks++) * instr->vibrato_rate) >> 2; + ch->autovibrato_note_offset = .25f * jar_xm_waveform(instr->vibrato_type, step) + * (float)instr->vibrato_depth / (float)0xF * sweep; + jar_xm_update_frequency(ctx, ch); +} + +static void jar_xm_vibrato(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, uint8_t param, uint16_t pos) { + unsigned int step = pos * (param >> 4); + ch->vibrato_note_offset = + 2.f + * jar_xm_waveform(ch->vibrato_waveform, step) + * (float)(param & 0x0F) / (float)0xF; + jar_xm_update_frequency(ctx, ch); +} + +static void jar_xm_tremolo(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, uint8_t param, uint16_t pos) { + unsigned int step = pos * (param >> 4); + /* Not so sure about this, it sounds correct by ear compared with + * MilkyTracker, but it could come from other bugs */ + ch->tremolo_volume = -1.f * jar_xm_waveform(ch->tremolo_waveform, step) + * (float)(param & 0x0F) / (float)0xF; +} + +static void jar_xm_arpeggio(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, uint8_t param, uint16_t tick) { + switch(tick % 3) { + case 0: + ch->arp_in_progress = false; + ch->arp_note_offset = 0; + break; + case 2: + ch->arp_in_progress = true; + ch->arp_note_offset = param >> 4; + break; + case 1: + ch->arp_in_progress = true; + ch->arp_note_offset = param & 0x0F; + break; + } + + jar_xm_update_frequency(ctx, ch); +} + +static void jar_xm_tone_portamento(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch) { + /* 3xx called without a note, wait until we get an actual + * target note. */ + if(ch->tone_portamento_target_period == 0.f) return; + + if(ch->period != ch->tone_portamento_target_period) { + jar_xm_SLIDE_TOWARDS(ch->period, + ch->tone_portamento_target_period, + (ctx->module.frequency_type == jar_xm_LINEAR_FREQUENCIES ? + 4.f : 1.f) * ch->tone_portamento_param + ); + jar_xm_update_frequency(ctx, ch); + } +} + +static void jar_xm_pitch_slide(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, float period_offset) { + /* Don't ask about the 4.f coefficient. I found mention of it + * nowhere. Found by ear™. */ + if(ctx->module.frequency_type == jar_xm_LINEAR_FREQUENCIES) { + period_offset *= 4.f; + } + + ch->period += period_offset; + jar_xm_CLAMP_DOWN(ch->period); + /* XXX: upper bound of period ? */ + + jar_xm_update_frequency(ctx, ch); +} + +static void jar_xm_panning_slide(jar_xm_channel_context_t* ch, uint8_t rawval) { + float f; + + if((rawval & 0xF0) && (rawval & 0x0F)) { + /* Illegal state */ + return; + } + + if(rawval & 0xF0) { + /* Slide right */ + f = (float)(rawval >> 4) / (float)0xFF; + ch->panning += f; + jar_xm_CLAMP_UP(ch->panning); + } else { + /* Slide left */ + f = (float)(rawval & 0x0F) / (float)0xFF; + ch->panning -= f; + jar_xm_CLAMP_DOWN(ch->panning); + } +} + +static void jar_xm_volume_slide(jar_xm_channel_context_t* ch, uint8_t rawval) { + float f; + + if((rawval & 0xF0) && (rawval & 0x0F)) { + /* Illegal state */ + return; + } + + if(rawval & 0xF0) { + /* Slide up */ + f = (float)(rawval >> 4) / (float)0x40; + ch->volume += f; + jar_xm_CLAMP_UP(ch->volume); + } else { + /* Slide down */ + f = (float)(rawval & 0x0F) / (float)0x40; + ch->volume -= f; + jar_xm_CLAMP_DOWN(ch->volume); + } +} + +static float jar_xm_envelope_lerp(jar_xm_envelope_point_t* restrict a, jar_xm_envelope_point_t* restrict b, uint16_t pos) { + /* Linear interpolation between two envelope points */ + if(pos <= a->frame) return a->value; + else if(pos >= b->frame) return b->value; + else { + float p = (float)(pos - a->frame) / (float)(b->frame - a->frame); + return a->value * (1 - p) + b->value * p; + } +} + +static void jar_xm_post_pattern_change(jar_xm_context_t* ctx) { + /* Loop if necessary */ + if(ctx->current_table_index >= ctx->module.length) { + ctx->current_table_index = ctx->module.restart_position; + } +} + +static float jar_xm_linear_period(float note) { + return 7680.f - note * 64.f; +} + +static float jar_xm_linear_frequency(float period) { + return 8363.f * powf(2.f, (4608.f - period) / 768.f); +} + +static float jar_xm_amiga_period(float note) { + unsigned int intnote = note; + uint8_t a = intnote % 12; + int8_t octave = note / 12.f - 2; + uint16_t p1 = amiga_frequencies[a], p2 = amiga_frequencies[a + 1]; + + if(octave > 0) { + p1 >>= octave; + p2 >>= octave; + } else if(octave < 0) { + p1 <<= (-octave); + p2 <<= (-octave); + } + + return jar_xm_LERP(p1, p2, note - intnote); +} + +static float jar_xm_amiga_frequency(float period) { + if(period == .0f) return .0f; + + /* This is the PAL value. No reason to choose this one over the + * NTSC value. */ + return 7093789.2f / (period * 2.f); +} + +static float jar_xm_period(jar_xm_context_t* ctx, float note) { + switch(ctx->module.frequency_type) { + case jar_xm_LINEAR_FREQUENCIES: + return jar_xm_linear_period(note); + case jar_xm_AMIGA_FREQUENCIES: + return jar_xm_amiga_period(note); + } + return .0f; +} + +static float jar_xm_frequency(jar_xm_context_t* ctx, float period, float note_offset) { + uint8_t a; + int8_t octave; + float note; + uint16_t p1, p2; + + switch(ctx->module.frequency_type) { + + case jar_xm_LINEAR_FREQUENCIES: + return jar_xm_linear_frequency(period - 64.f * note_offset); + + case jar_xm_AMIGA_FREQUENCIES: + if(note_offset == 0) { + /* A chance to escape from insanity */ + return jar_xm_amiga_frequency(period); + } + + /* FIXME: this is very crappy at best */ + a = octave = 0; + + /* Find the octave of the current period */ + if(period > amiga_frequencies[0]) { + --octave; + while(period > (amiga_frequencies[0] << (-octave))) --octave; + } else if(period < amiga_frequencies[12]) { + ++octave; + while(period < (amiga_frequencies[12] >> octave)) ++octave; + } + + /* Find the smallest note closest to the current period */ + for(uint8_t i = 0; i < 12; ++i) { + p1 = amiga_frequencies[i], p2 = amiga_frequencies[i + 1]; + + if(octave > 0) { + p1 >>= octave; + p2 >>= octave; + } else if(octave < 0) { + p1 <<= (-octave); + p2 <<= (-octave); + } + + if(p2 <= period && period <= p1) { + a = i; + break; + } + } + + if(JAR_XM_DEBUG && (p1 < period || p2 > period)) { + DEBUG("%i <= %f <= %i should hold but doesn't, this is a bug", p2, period, p1); + } + + note = 12.f * (octave + 2) + a + jar_xm_INVERSE_LERP(p1, p2, period); + + return jar_xm_amiga_frequency(jar_xm_amiga_period(note + note_offset)); + + } + + return .0f; +} + +static void jar_xm_update_frequency(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch) { + ch->frequency = jar_xm_frequency( + ctx, ch->period, + (ch->arp_note_offset > 0 ? ch->arp_note_offset : ( + ch->vibrato_note_offset + ch->autovibrato_note_offset + )) + ); + ch->step = ch->frequency / ctx->rate; +} + +static void jar_xm_handle_note_and_instrument(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, + jar_xm_pattern_slot_t* s) { + if(s->instrument > 0) { + if(HAS_TONE_PORTAMENTO(ch->current) && ch->instrument != NULL && ch->sample != NULL) { + /* Tone portamento in effect, unclear stuff happens */ + jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_PERIOD | jar_xm_TRIGGER_KEEP_SAMPLE_POSITION); + } else if(s->instrument > ctx->module.num_instruments) { + /* Invalid instrument, Cut current note */ + jar_xm_cut_note(ch); + ch->instrument = NULL; + ch->sample = NULL; + } else { + ch->instrument = ctx->module.instruments + (s->instrument - 1); + if(s->note == 0 && ch->sample != NULL) { + /* Ghost instrument, trigger note */ + /* Sample position is kept, but envelopes are reset */ + jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_SAMPLE_POSITION); + } + } + } + + if(NOTE_IS_VALID(s->note)) { + /* Yes, the real note number is s->note -1. Try finding + * THAT in any of the specs! :-) */ + + jar_xm_instrument_t* instr = ch->instrument; + + if(HAS_TONE_PORTAMENTO(ch->current) && instr != NULL && ch->sample != NULL) { + /* Tone portamento in effect */ + ch->note = s->note + ch->sample->relative_note + ch->sample->finetune / 128.f - 1.f; + ch->tone_portamento_target_period = jar_xm_period(ctx, ch->note); + } else if(instr == NULL || ch->instrument->num_samples == 0) { + /* Bad instrument */ + jar_xm_cut_note(ch); + } else { + if(instr->sample_of_notes[s->note - 1] < instr->num_samples) { +#if JAR_XM_RAMPING + for(unsigned int z = 0; z < jar_xm_SAMPLE_RAMPING_POINTS; ++z) { + ch->end_of_previous_sample[z] = jar_xm_next_of_sample(ch); + } + ch->frame_count = 0; +#endif + ch->sample = instr->samples + instr->sample_of_notes[s->note - 1]; + ch->orig_note = ch->note = s->note + ch->sample->relative_note + + ch->sample->finetune / 128.f - 1.f; + if(s->instrument > 0) { + jar_xm_trigger_note(ctx, ch, 0); + } else { + /* Ghost note: keep old volume */ + jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_VOLUME); + } + } else { + /* Bad sample */ + jar_xm_cut_note(ch); + } + } + } else if(s->note == 97) { + /* Key Off */ + jar_xm_key_off(ch); + } + + switch(s->volume_column >> 4) { + + case 0x5: + if(s->volume_column > 0x50) break; + case 0x1: + case 0x2: + case 0x3: + case 0x4: + /* Set volume */ + ch->volume = (float)(s->volume_column - 0x10) / (float)0x40; + break; + + case 0x8: /* Fine volume slide down */ + jar_xm_volume_slide(ch, s->volume_column & 0x0F); + break; + + case 0x9: /* Fine volume slide up */ + jar_xm_volume_slide(ch, s->volume_column << 4); + break; + + case 0xA: /* Set vibrato speed */ + ch->vibrato_param = (ch->vibrato_param & 0x0F) | ((s->volume_column & 0x0F) << 4); + break; + + case 0xC: /* Set panning */ + ch->panning = (float)( + ((s->volume_column & 0x0F) << 4) | (s->volume_column & 0x0F) + ) / (float)0xFF; + break; + + case 0xF: /* Tone portamento */ + if(s->volume_column & 0x0F) { + ch->tone_portamento_param = ((s->volume_column & 0x0F) << 4) + | (s->volume_column & 0x0F); + } + break; + + default: + break; + + } + + switch(s->effect_type) { + + case 1: /* 1xx: Portamento up */ + if(s->effect_param > 0) { + ch->portamento_up_param = s->effect_param; + } + break; + + case 2: /* 2xx: Portamento down */ + if(s->effect_param > 0) { + ch->portamento_down_param = s->effect_param; + } + break; + + case 3: /* 3xx: Tone portamento */ + if(s->effect_param > 0) { + ch->tone_portamento_param = s->effect_param; + } + break; + + case 4: /* 4xy: Vibrato */ + if(s->effect_param & 0x0F) { + /* Set vibrato depth */ + ch->vibrato_param = (ch->vibrato_param & 0xF0) | (s->effect_param & 0x0F); + } + if(s->effect_param >> 4) { + /* Set vibrato speed */ + ch->vibrato_param = (s->effect_param & 0xF0) | (ch->vibrato_param & 0x0F); + } + break; + + case 5: /* 5xy: Tone portamento + Volume slide */ + if(s->effect_param > 0) { + ch->volume_slide_param = s->effect_param; + } + break; + + case 6: /* 6xy: Vibrato + Volume slide */ + if(s->effect_param > 0) { + ch->volume_slide_param = s->effect_param; + } + break; + + case 7: /* 7xy: Tremolo */ + if(s->effect_param & 0x0F) { + /* Set tremolo depth */ + ch->tremolo_param = (ch->tremolo_param & 0xF0) | (s->effect_param & 0x0F); + } + if(s->effect_param >> 4) { + /* Set tremolo speed */ + ch->tremolo_param = (s->effect_param & 0xF0) | (ch->tremolo_param & 0x0F); + } + break; + + case 8: /* 8xx: Set panning */ + ch->panning = (float)s->effect_param / (float)0xFF; + break; + + case 9: /* 9xx: Sample offset */ + if(ch->sample != NULL && NOTE_IS_VALID(s->note)) { + uint32_t final_offset = s->effect_param << (ch->sample->bits == 16 ? 7 : 8); + if(final_offset >= ch->sample->length) { + /* Pretend the sample dosen't loop and is done playing */ + ch->sample_position = -1; + break; + } + ch->sample_position = final_offset; + } + break; + + case 0xA: /* Axy: Volume slide */ + if(s->effect_param > 0) { + ch->volume_slide_param = s->effect_param; + } + break; + + case 0xB: /* Bxx: Position jump */ + if(s->effect_param < ctx->module.length) { + ctx->position_jump = true; + ctx->jump_dest = s->effect_param; + } + break; + + case 0xC: /* Cxx: Set volume */ + ch->volume = (float)((s->effect_param > 0x40) + ? 0x40 : s->effect_param) / (float)0x40; + break; + + case 0xD: /* Dxx: Pattern break */ + /* Jump after playing this line */ + ctx->pattern_break = true; + ctx->jump_row = (s->effect_param >> 4) * 10 + (s->effect_param & 0x0F); + break; + + case 0xE: /* EXy: Extended command */ + switch(s->effect_param >> 4) { + + case 1: /* E1y: Fine portamento up */ + if(s->effect_param & 0x0F) { + ch->fine_portamento_up_param = s->effect_param & 0x0F; + } + jar_xm_pitch_slide(ctx, ch, -ch->fine_portamento_up_param); + break; + + case 2: /* E2y: Fine portamento down */ + if(s->effect_param & 0x0F) { + ch->fine_portamento_down_param = s->effect_param & 0x0F; + } + jar_xm_pitch_slide(ctx, ch, ch->fine_portamento_down_param); + break; + + case 4: /* E4y: Set vibrato control */ + ch->vibrato_waveform = s->effect_param & 3; + ch->vibrato_waveform_retrigger = !((s->effect_param >> 2) & 1); + break; + + case 5: /* E5y: Set finetune */ + if(NOTE_IS_VALID(ch->current->note) && ch->sample != NULL) { + ch->note = ch->current->note + ch->sample->relative_note + + (float)(((s->effect_param & 0x0F) - 8) << 4) / 128.f - 1.f; + ch->period = jar_xm_period(ctx, ch->note); + jar_xm_update_frequency(ctx, ch); + } + break; + + case 6: /* E6y: Pattern loop */ + if(s->effect_param & 0x0F) { + if((s->effect_param & 0x0F) == ch->pattern_loop_count) { + /* Loop is over */ + ch->pattern_loop_count = 0; + break; + } + + /* Jump to the beginning of the loop */ + ch->pattern_loop_count++; + ctx->position_jump = true; + ctx->jump_row = ch->pattern_loop_origin; + ctx->jump_dest = ctx->current_table_index; + } else { + /* Set loop start point */ + ch->pattern_loop_origin = ctx->current_row; + /* Replicate FT2 E60 bug */ + ctx->jump_row = ch->pattern_loop_origin; + } + break; + + case 7: /* E7y: Set tremolo control */ + ch->tremolo_waveform = s->effect_param & 3; + ch->tremolo_waveform_retrigger = !((s->effect_param >> 2) & 1); + break; + + case 0xA: /* EAy: Fine volume slide up */ + if(s->effect_param & 0x0F) { + ch->fine_volume_slide_param = s->effect_param & 0x0F; + } + jar_xm_volume_slide(ch, ch->fine_volume_slide_param << 4); + break; + + case 0xB: /* EBy: Fine volume slide down */ + if(s->effect_param & 0x0F) { + ch->fine_volume_slide_param = s->effect_param & 0x0F; + } + jar_xm_volume_slide(ch, ch->fine_volume_slide_param); + break; + + case 0xD: /* EDy: Note delay */ + /* XXX: figure this out better. EDx triggers + * the note even when there no note and no + * instrument. But ED0 acts like like a ghost + * note, EDx (x ≠ 0) does not. */ + if(s->note == 0 && s->instrument == 0) { + unsigned int flags = jar_xm_TRIGGER_KEEP_VOLUME; + + if(ch->current->effect_param & 0x0F) { + ch->note = ch->orig_note; + jar_xm_trigger_note(ctx, ch, flags); + } else { + jar_xm_trigger_note( + ctx, ch, + flags + | jar_xm_TRIGGER_KEEP_PERIOD + | jar_xm_TRIGGER_KEEP_SAMPLE_POSITION + ); + } + } + break; + + case 0xE: /* EEy: Pattern delay */ + ctx->extra_ticks = (ch->current->effect_param & 0x0F) * ctx->tempo; + break; + + default: + break; + + } + break; + + case 0xF: /* Fxx: Set tempo/BPM */ + if(s->effect_param > 0) { + if(s->effect_param <= 0x1F) { + ctx->tempo = s->effect_param; + } else { + ctx->bpm = s->effect_param; + } + } + break; + + case 16: /* Gxx: Set global volume */ + ctx->global_volume = (float)((s->effect_param > 0x40) + ? 0x40 : s->effect_param) / (float)0x40; + break; + + case 17: /* Hxy: Global volume slide */ + if(s->effect_param > 0) { + ch->global_volume_slide_param = s->effect_param; + } + break; + + case 21: /* Lxx: Set envelope position */ + ch->volume_envelope_frame_count = s->effect_param; + ch->panning_envelope_frame_count = s->effect_param; + break; + + case 25: /* Pxy: Panning slide */ + if(s->effect_param > 0) { + ch->panning_slide_param = s->effect_param; + } + break; + + case 27: /* Rxy: Multi retrig note */ + if(s->effect_param > 0) { + if((s->effect_param >> 4) == 0) { + /* Keep previous x value */ + ch->multi_retrig_param = (ch->multi_retrig_param & 0xF0) | (s->effect_param & 0x0F); + } else { + ch->multi_retrig_param = s->effect_param; + } + } + break; + + case 29: /* Txy: Tremor */ + if(s->effect_param > 0) { + /* Tremor x and y params do not appear to be separately + * kept in memory, unlike Rxy */ + ch->tremor_param = s->effect_param; + } + break; + + case 33: /* Xxy: Extra stuff */ + switch(s->effect_param >> 4) { + + case 1: /* X1y: Extra fine portamento up */ + if(s->effect_param & 0x0F) { + ch->extra_fine_portamento_up_param = s->effect_param & 0x0F; + } + jar_xm_pitch_slide(ctx, ch, -1.0f * ch->extra_fine_portamento_up_param); + break; + + case 2: /* X2y: Extra fine portamento down */ + if(s->effect_param & 0x0F) { + ch->extra_fine_portamento_down_param = s->effect_param & 0x0F; + } + jar_xm_pitch_slide(ctx, ch, ch->extra_fine_portamento_down_param); + break; + + default: + break; + + } + break; + + default: + break; + + } +} + +static void jar_xm_trigger_note(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, unsigned int flags) { + if(!(flags & jar_xm_TRIGGER_KEEP_SAMPLE_POSITION)) { + ch->sample_position = 0.f; + ch->ping = true; + } + + if(ch->sample != NULL) { + if(!(flags & jar_xm_TRIGGER_KEEP_VOLUME)) { + ch->volume = ch->sample->volume; + } + + ch->panning = ch->sample->panning; + } + + ch->sustained = true; + ch->fadeout_volume = ch->volume_envelope_volume = 1.0f; + ch->panning_envelope_panning = .5f; + ch->volume_envelope_frame_count = ch->panning_envelope_frame_count = 0; + ch->vibrato_note_offset = 0.f; + ch->tremolo_volume = 0.f; + ch->tremor_on = false; + + ch->autovibrato_ticks = 0; + + if(ch->vibrato_waveform_retrigger) { + ch->vibrato_ticks = 0; /* XXX: should the waveform itself also + * be reset to sine? */ + } + if(ch->tremolo_waveform_retrigger) { + ch->tremolo_ticks = 0; + } + + if(!(flags & jar_xm_TRIGGER_KEEP_PERIOD)) { + ch->period = jar_xm_period(ctx, ch->note); + jar_xm_update_frequency(ctx, ch); + } + + ch->latest_trigger = ctx->generated_samples; + if(ch->instrument != NULL) { + ch->instrument->latest_trigger = ctx->generated_samples; + } + if(ch->sample != NULL) { + ch->sample->latest_trigger = ctx->generated_samples; + } +} + +static void jar_xm_cut_note(jar_xm_channel_context_t* ch) { + /* NB: this is not the same as Key Off */ + ch->volume = .0f; +} + +static void jar_xm_key_off(jar_xm_channel_context_t* ch) { + /* Key Off */ + ch->sustained = false; + + /* If no volume envelope is used, also cut the note */ + if(ch->instrument == NULL || !ch->instrument->volume_envelope.enabled) { + jar_xm_cut_note(ch); + } +} + +static void jar_xm_row(jar_xm_context_t* ctx) { + if(ctx->position_jump) { + ctx->current_table_index = ctx->jump_dest; + ctx->current_row = ctx->jump_row; + ctx->position_jump = false; + ctx->pattern_break = false; + ctx->jump_row = 0; + jar_xm_post_pattern_change(ctx); + } else if(ctx->pattern_break) { + ctx->current_table_index++; + ctx->current_row = ctx->jump_row; + ctx->pattern_break = false; + ctx->jump_row = 0; + jar_xm_post_pattern_change(ctx); + } + + jar_xm_pattern_t* cur = ctx->module.patterns + ctx->module.pattern_table[ctx->current_table_index]; + bool in_a_loop = false; + + /* Read notes… */ + for(uint8_t i = 0; i < ctx->module.num_channels; ++i) { + jar_xm_pattern_slot_t* s = cur->slots + ctx->current_row * ctx->module.num_channels + i; + jar_xm_channel_context_t* ch = ctx->channels + i; + + ch->current = s; + + if(s->effect_type != 0xE || s->effect_param >> 4 != 0xD) { + jar_xm_handle_note_and_instrument(ctx, ch, s); + } else { + ch->note_delay_param = s->effect_param & 0x0F; + } + + if(!in_a_loop && ch->pattern_loop_count > 0) { + in_a_loop = true; + } + } + + if(!in_a_loop) { + /* No E6y loop is in effect (or we are in the first pass) */ + ctx->loop_count = (ctx->row_loop_count[MAX_NUM_ROWS * ctx->current_table_index + ctx->current_row]++); + } + + ctx->current_row++; /* Since this is an uint8, this line can + * increment from 255 to 0, in which case it + * is still necessary to go the next + * pattern. */ + if(!ctx->position_jump && !ctx->pattern_break && + (ctx->current_row >= cur->num_rows || ctx->current_row == 0)) { + ctx->current_table_index++; + ctx->current_row = ctx->jump_row; /* This will be 0 most of + * the time, except when E60 + * is used */ + ctx->jump_row = 0; + jar_xm_post_pattern_change(ctx); + } +} + +static void jar_xm_envelope_tick(jar_xm_channel_context_t* ch, + jar_xm_envelope_t* env, + uint16_t* counter, + float* outval) { + if(env->num_points < 2) { + /* Don't really know what to do… */ + if(env->num_points == 1) { + /* XXX I am pulling this out of my ass */ + *outval = (float)env->points[0].value / (float)0x40; + if(*outval > 1) { + *outval = 1; + } + } + + return; + } else { + uint8_t j; + + if(env->loop_enabled) { + uint16_t loop_start = env->points[env->loop_start_point].frame; + uint16_t loop_end = env->points[env->loop_end_point].frame; + uint16_t loop_length = loop_end - loop_start; + + if(*counter >= loop_end) { + *counter -= loop_length; + } + } + + for(j = 0; j < (env->num_points - 2); ++j) { + if(env->points[j].frame <= *counter && + env->points[j+1].frame >= *counter) { + break; + } + } + + *outval = jar_xm_envelope_lerp(env->points + j, env->points + j + 1, *counter) / (float)0x40; + + /* Make sure it is safe to increment frame count */ + if(!ch->sustained || !env->sustain_enabled || + *counter != env->points[env->sustain_point].frame) { + (*counter)++; + } + } +} + +static void jar_xm_envelopes(jar_xm_channel_context_t* ch) { + if(ch->instrument != NULL) { + if(ch->instrument->volume_envelope.enabled) { + if(!ch->sustained) { + ch->fadeout_volume -= (float)ch->instrument->volume_fadeout / 65536.f; + jar_xm_CLAMP_DOWN(ch->fadeout_volume); + } + + jar_xm_envelope_tick(ch, + &(ch->instrument->volume_envelope), + &(ch->volume_envelope_frame_count), + &(ch->volume_envelope_volume)); + } + + if(ch->instrument->panning_envelope.enabled) { + jar_xm_envelope_tick(ch, + &(ch->instrument->panning_envelope), + &(ch->panning_envelope_frame_count), + &(ch->panning_envelope_panning)); + } + } +} + +static void jar_xm_tick(jar_xm_context_t* ctx) { + if(ctx->current_tick == 0) { + jar_xm_row(ctx); + } + + for(uint8_t i = 0; i < ctx->module.num_channels; ++i) { + jar_xm_channel_context_t* ch = ctx->channels + i; + + jar_xm_envelopes(ch); + jar_xm_autovibrato(ctx, ch); + + if(ch->arp_in_progress && !HAS_ARPEGGIO(ch->current)) { + ch->arp_in_progress = false; + ch->arp_note_offset = 0; + jar_xm_update_frequency(ctx, ch); + } + if(ch->vibrato_in_progress && !HAS_VIBRATO(ch->current)) { + ch->vibrato_in_progress = false; + ch->vibrato_note_offset = 0.f; + jar_xm_update_frequency(ctx, ch); + } + + switch(ch->current->volume_column >> 4) { + + case 0x6: /* Volume slide down */ + if(ctx->current_tick == 0) break; + jar_xm_volume_slide(ch, ch->current->volume_column & 0x0F); + break; + + case 0x7: /* Volume slide up */ + if(ctx->current_tick == 0) break; + jar_xm_volume_slide(ch, ch->current->volume_column << 4); + break; + + case 0xB: /* Vibrato */ + if(ctx->current_tick == 0) break; + ch->vibrato_in_progress = false; + jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++); + break; + + case 0xD: /* Panning slide left */ + if(ctx->current_tick == 0) break; + jar_xm_panning_slide(ch, ch->current->volume_column & 0x0F); + break; + + case 0xE: /* Panning slide right */ + if(ctx->current_tick == 0) break; + jar_xm_panning_slide(ch, ch->current->volume_column << 4); + break; + + case 0xF: /* Tone portamento */ + if(ctx->current_tick == 0) break; + jar_xm_tone_portamento(ctx, ch); + break; + + default: + break; + + } + + switch(ch->current->effect_type) { + + case 0: /* 0xy: Arpeggio */ + if(ch->current->effect_param > 0) { + char arp_offset = ctx->tempo % 3; + switch(arp_offset) { + case 2: /* 0 -> x -> 0 -> y -> x -> … */ + if(ctx->current_tick == 1) { + ch->arp_in_progress = true; + ch->arp_note_offset = ch->current->effect_param >> 4; + jar_xm_update_frequency(ctx, ch); + break; + } + /* No break here, this is intended */ + case 1: /* 0 -> 0 -> y -> x -> … */ + if(ctx->current_tick == 0) { + ch->arp_in_progress = false; + ch->arp_note_offset = 0; + jar_xm_update_frequency(ctx, ch); + break; + } + /* No break here, this is intended */ + case 0: /* 0 -> y -> x -> … */ + jar_xm_arpeggio(ctx, ch, ch->current->effect_param, ctx->current_tick - arp_offset); + default: + break; + } + } + break; + + case 1: /* 1xx: Portamento up */ + if(ctx->current_tick == 0) break; + jar_xm_pitch_slide(ctx, ch, -ch->portamento_up_param); + break; + + case 2: /* 2xx: Portamento down */ + if(ctx->current_tick == 0) break; + jar_xm_pitch_slide(ctx, ch, ch->portamento_down_param); + break; + + case 3: /* 3xx: Tone portamento */ + if(ctx->current_tick == 0) break; + jar_xm_tone_portamento(ctx, ch); + break; + + case 4: /* 4xy: Vibrato */ + if(ctx->current_tick == 0) break; + ch->vibrato_in_progress = true; + jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++); + break; + + case 5: /* 5xy: Tone portamento + Volume slide */ + if(ctx->current_tick == 0) break; + jar_xm_tone_portamento(ctx, ch); + jar_xm_volume_slide(ch, ch->volume_slide_param); + break; + + case 6: /* 6xy: Vibrato + Volume slide */ + if(ctx->current_tick == 0) break; + ch->vibrato_in_progress = true; + jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++); + jar_xm_volume_slide(ch, ch->volume_slide_param); + break; + + case 7: /* 7xy: Tremolo */ + if(ctx->current_tick == 0) break; + jar_xm_tremolo(ctx, ch, ch->tremolo_param, ch->tremolo_ticks++); + break; + + case 0xA: /* Axy: Volume slide */ + if(ctx->current_tick == 0) break; + jar_xm_volume_slide(ch, ch->volume_slide_param); + break; + + case 0xE: /* EXy: Extended command */ + switch(ch->current->effect_param >> 4) { + + case 0x9: /* E9y: Retrigger note */ + if(ctx->current_tick != 0 && ch->current->effect_param & 0x0F) { + if(!(ctx->current_tick % (ch->current->effect_param & 0x0F))) { + jar_xm_trigger_note(ctx, ch, 0); + jar_xm_envelopes(ch); + } + } + break; + + case 0xC: /* ECy: Note cut */ + if((ch->current->effect_param & 0x0F) == ctx->current_tick) { + jar_xm_cut_note(ch); + } + break; + + case 0xD: /* EDy: Note delay */ + if(ch->note_delay_param == ctx->current_tick) { + jar_xm_handle_note_and_instrument(ctx, ch, ch->current); + jar_xm_envelopes(ch); + } + break; + + default: + break; + + } + break; + + case 17: /* Hxy: Global volume slide */ + if(ctx->current_tick == 0) break; + if((ch->global_volume_slide_param & 0xF0) && + (ch->global_volume_slide_param & 0x0F)) { + /* Illegal state */ + break; + } + if(ch->global_volume_slide_param & 0xF0) { + /* Global slide up */ + float f = (float)(ch->global_volume_slide_param >> 4) / (float)0x40; + ctx->global_volume += f; + jar_xm_CLAMP_UP(ctx->global_volume); + } else { + /* Global slide down */ + float f = (float)(ch->global_volume_slide_param & 0x0F) / (float)0x40; + ctx->global_volume -= f; + jar_xm_CLAMP_DOWN(ctx->global_volume); + } + break; + + case 20: /* Kxx: Key off */ + /* Most documentations will tell you the parameter has no + * use. Don't be fooled. */ + if(ctx->current_tick == ch->current->effect_param) { + jar_xm_key_off(ch); + } + break; + + case 25: /* Pxy: Panning slide */ + if(ctx->current_tick == 0) break; + jar_xm_panning_slide(ch, ch->panning_slide_param); + break; + + case 27: /* Rxy: Multi retrig note */ + if(ctx->current_tick == 0) break; + if(((ch->multi_retrig_param) & 0x0F) == 0) break; + if((ctx->current_tick % (ch->multi_retrig_param & 0x0F)) == 0) { + float v = ch->volume * multi_retrig_multiply[ch->multi_retrig_param >> 4] + + multi_retrig_add[ch->multi_retrig_param >> 4]; + jar_xm_CLAMP(v); + jar_xm_trigger_note(ctx, ch, 0); + ch->volume = v; + } + break; + + case 29: /* Txy: Tremor */ + if(ctx->current_tick == 0) break; + ch->tremor_on = ( + (ctx->current_tick - 1) % ((ch->tremor_param >> 4) + (ch->tremor_param & 0x0F) + 2) + > + (ch->tremor_param >> 4) + ); + break; + + default: + break; + + } + + float panning, volume; + + panning = ch->panning + + (ch->panning_envelope_panning - .5f) * (.5f - fabsf(ch->panning - .5f)) * 2.0f; + + if(ch->tremor_on) { + volume = .0f; + } else { + volume = ch->volume + ch->tremolo_volume; + jar_xm_CLAMP(volume); + volume *= ch->fadeout_volume * ch->volume_envelope_volume; + } + +#if JAR_XM_RAMPING + ch->target_panning = panning; + ch->target_volume = volume; +#else + ch->actual_panning = panning; + ch->actual_volume = volume; +#endif + } + + ctx->current_tick++; + if(ctx->current_tick >= ctx->tempo + ctx->extra_ticks) { + ctx->current_tick = 0; + ctx->extra_ticks = 0; + } + + /* FT2 manual says number of ticks / second = BPM * 0.4 */ + ctx->remaining_samples_in_tick += (float)ctx->rate / ((float)ctx->bpm * 0.4f); +} + +static float jar_xm_next_of_sample(jar_xm_channel_context_t* ch) { + if(ch->instrument == NULL || ch->sample == NULL || ch->sample_position < 0) { +#if JAR_XM_RAMPING + if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) { + return jar_xm_LERP(ch->end_of_previous_sample[ch->frame_count], .0f, + (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + } +#endif + return .0f; + } + if(ch->sample->length == 0) { + return .0f; + } + + float u, v, t; + uint32_t a, b; + a = (uint32_t)ch->sample_position; /* This cast is fine, + * sample_position will not + * go above integer + * ranges */ + if(JAR_XM_LINEAR_INTERPOLATION) { + b = a + 1; + t = ch->sample_position - a; /* Cheaper than fmodf(., 1.f) */ + } + u = ch->sample->data[a]; + + switch(ch->sample->loop_type) { + + case jar_xm_NO_LOOP: + if(JAR_XM_LINEAR_INTERPOLATION) { + v = (b < ch->sample->length) ? ch->sample->data[b] : .0f; + } + ch->sample_position += ch->step; + if(ch->sample_position >= ch->sample->length) { + ch->sample_position = -1; + } + break; + + case jar_xm_FORWARD_LOOP: + if(JAR_XM_LINEAR_INTERPOLATION) { + v = ch->sample->data[ + (b == ch->sample->loop_end) ? ch->sample->loop_start : b + ]; + } + ch->sample_position += ch->step; + while(ch->sample_position >= ch->sample->loop_end) { + ch->sample_position -= ch->sample->loop_length; + } + break; + + case jar_xm_PING_PONG_LOOP: + if(ch->ping) { + ch->sample_position += ch->step; + } else { + ch->sample_position -= ch->step; + } + /* XXX: this may not work for very tight ping-pong loops + * (ie switches direction more than once per sample */ + if(ch->ping) { + if(JAR_XM_LINEAR_INTERPOLATION) { + v = (b >= ch->sample->loop_end) ? ch->sample->data[a] : ch->sample->data[b]; + } + if(ch->sample_position >= ch->sample->loop_end) { + ch->ping = false; + ch->sample_position = (ch->sample->loop_end << 1) - ch->sample_position; + } + /* sanity checking */ + if(ch->sample_position >= ch->sample->length) { + ch->ping = false; + ch->sample_position -= ch->sample->length - 1; + } + } else { + if(JAR_XM_LINEAR_INTERPOLATION) { + v = u; + u = (b == 1 || b - 2 <= ch->sample->loop_start) ? ch->sample->data[a] : ch->sample->data[b - 2]; + } + if(ch->sample_position <= ch->sample->loop_start) { + ch->ping = true; + ch->sample_position = (ch->sample->loop_start << 1) - ch->sample_position; + } + /* sanity checking */ + if(ch->sample_position <= .0f) { + ch->ping = true; + ch->sample_position = .0f; + } + } + break; + + default: + v = .0f; + break; + } + + float endval = JAR_XM_LINEAR_INTERPOLATION ? jar_xm_LERP(u, v, t) : u; + +#if JAR_XM_RAMPING + if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) { + /* Smoothly transition between old and new sample. */ + return jar_xm_LERP(ch->end_of_previous_sample[ch->frame_count], endval, + (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + } +#endif + + return endval; +} + +static void jar_xm_sample(jar_xm_context_t* ctx, float* left, float* right) { + if(ctx->remaining_samples_in_tick <= 0) { + jar_xm_tick(ctx); + } + ctx->remaining_samples_in_tick--; + + *left = 0.f; + *right = 0.f; + + if(ctx->max_loop_count > 0 && ctx->loop_count >= ctx->max_loop_count) { + return; + } + + for(uint8_t i = 0; i < ctx->module.num_channels; ++i) { + jar_xm_channel_context_t* ch = ctx->channels + i; + + if(ch->instrument == NULL || ch->sample == NULL || ch->sample_position < 0) { + continue; + } + + const float fval = jar_xm_next_of_sample(ch); + + if(!ch->muted && !ch->instrument->muted) { + *left += fval * ch->actual_volume * (1.f - ch->actual_panning); + *right += fval * ch->actual_volume * ch->actual_panning; + } + +#if JAR_XM_RAMPING + ch->frame_count++; + jar_xm_SLIDE_TOWARDS(ch->actual_volume, ch->target_volume, ctx->volume_ramp); + jar_xm_SLIDE_TOWARDS(ch->actual_panning, ch->target_panning, ctx->panning_ramp); +#endif + } + + const float fgvol = ctx->global_volume * ctx->amplification; + *left *= fgvol; + *right *= fgvol; + +#if JAR_XM_DEBUG + if(fabs(*left) > 1 || fabs(*right) > 1) { + DEBUG("clipping frame: %f %f, this is a bad module or a libxm bug", *left, *right); + } +#endif +} + +void jar_xm_generate_samples(jar_xm_context_t* ctx, float* output, size_t numsamples) { + if(ctx && output) { + ctx->generated_samples += numsamples; + for(size_t i = 0; i < numsamples; i++) { + jar_xm_sample(ctx, output + (2 * i), output + (2 * i + 1)); + } + } +} + + + + + + + +//-------------------------------------------- +//FILE LOADER - TODO - NEEDS TO BE CLEANED UP +//-------------------------------------------- + + + +#undef DEBUG +#define DEBUG(...) do { \ + fprintf(stderr, __VA_ARGS__); \ + fflush(stderr); \ + } while(0) + +#define DEBUG_ERR(...) do { \ + fprintf(stderr, __VA_ARGS__); \ + fflush(stderr); \ + } while(0) + +#define FATAL(...) do { \ + fprintf(stderr, __VA_ARGS__); \ + fflush(stderr); \ + exit(1); \ + } while(0) + +#define FATAL_ERR(...) do { \ + fprintf(stderr, __VA_ARGS__); \ + fflush(stderr); \ + exit(1); \ + } while(0) + + +int jar_xm_create_context_from_file(jar_xm_context_t** ctx, uint32_t rate, const char* filename) { + FILE* xmf; + int size; + + xmf = fopen(filename, "rb"); + if(xmf == NULL) { + DEBUG_ERR("Could not open input file"); + *ctx = NULL; + return 3; + } + + fseek(xmf, 0, SEEK_END); + size = ftell(xmf); + rewind(xmf); + if(size == -1) { + fclose(xmf); + DEBUG_ERR("fseek() failed"); + *ctx = NULL; + return 4; + } + + char* data = malloc(size + 1); + if(fread(data, 1, size, xmf) < size) { + fclose(xmf); + DEBUG_ERR("fread() failed"); + *ctx = NULL; + return 5; + } + + fclose(xmf); + + switch(jar_xm_create_context_safe(ctx, data, size, rate)) { + case 0: + break; + + case 1: + DEBUG("could not create context: module is not sane\n"); + *ctx = NULL; + return 1; + break; + + case 2: + FATAL("could not create context: malloc failed\n"); + return 2; + break; + + default: + FATAL("could not create context: unknown error\n"); + return 6; + break; + + } + + return 0; +} + + + + +#endif//end of JAR_XM_IMPLEMENTATION +//------------------------------------------------------------------------------- + + + + +#endif//end of INCLUDE_JAR_XM_H -- cgit v1.2.3 From cb05c51911fdc885c8e91e51cbaa2ab32e114c7b Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sun, 24 Apr 2016 18:18:18 -0700 Subject: tabs to spaces fix --- src/audio.c | 124 ++++++++++++++++++++++++++++++------------------------------ 1 file changed, 62 insertions(+), 62 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 9baa2222..e5bd6819 100644 --- a/src/audio.c +++ b/src/audio.c @@ -75,7 +75,7 @@ // NOTE: Anything longer than ~10 seconds should be streamed... typedef struct Music { stb_vorbis *stream; - jar_xm_context_t *chipctx; // Stores jar_xm context + jar_xm_context_t *chipctx; // Stores jar_xm context ALuint buffers[MUSIC_STREAM_BUFFERS]; ALuint source; @@ -85,7 +85,7 @@ typedef struct Music { int sampleRate; int totalSamplesLeft; bool loop; - bool chipTune; // True if chiptune is loaded + bool chipTune; // True if chiptune is loaded } Music; #if defined(AUDIO_STANDALONE) @@ -567,22 +567,22 @@ void PlayMusicStream(char *fileName) currentMusic.totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels; } } - else if (strcmp(GetExtension(fileName),"xm") == 0) - { - currentMusic.chipTune = true; - currentMusic.channels = 2; - currentMusic.sampleRate = 48000; - currentMusic.loop = true; - - // only stereo/float is supported for xm - if(info.channels == 2 && !jar_xm_create_context_from_file(¤tMusic.chipctx, currentMusic.sampleRate, fileName)) - { - currentMusic.format = AL_FORMAT_STEREO_FLOAT32; - jar_xm_set_max_loop_count(currentMusic.chipctx, 0); //infinite number of loops - //currentMusic.totalSamplesLeft = ; // Unsure of how to calculate this - musicEnabled = true; - } - } + else if (strcmp(GetExtension(fileName),"xm") == 0) + { + currentMusic.chipTune = true; + currentMusic.channels = 2; + currentMusic.sampleRate = 48000; + currentMusic.loop = true; + + // only stereo/float is supported for xm + if(info.channels == 2 && !jar_xm_create_context_from_file(¤tMusic.chipctx, currentMusic.sampleRate, fileName)) + { + currentMusic.format = AL_FORMAT_STEREO_FLOAT32; + jar_xm_set_max_loop_count(currentMusic.chipctx, 0); //infinite number of loops + //currentMusic.totalSamplesLeft = ; // Unsure of how to calculate this + musicEnabled = true; + } + } else TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName); } @@ -591,19 +591,19 @@ void StopMusicStream(void) { if (musicEnabled) { - alSourceStop(currentMusic.source); - EmptyMusicStream(); // Empty music buffers - alDeleteSources(1, ¤tMusic.source); - alDeleteBuffers(2, currentMusic.buffers); - - if (currentMusic.chipTune) - { - jar_xm_free_context(currentMusic.chipctx); - } - else - { - stb_vorbis_close(currentMusic.stream); - } + alSourceStop(currentMusic.source); + EmptyMusicStream(); // Empty music buffers + alDeleteSources(1, ¤tMusic.source); + alDeleteBuffers(2, currentMusic.buffers); + + if (currentMusic.chipTune) + { + jar_xm_free_context(currentMusic.chipctx); + } + else + { + stb_vorbis_close(currentMusic.stream); + } } musicEnabled = false; @@ -657,15 +657,15 @@ void SetMusicVolume(float volume) // Get current music time length (in seconds) float GetMusicTimeLength(void) { - float totalSeconds; - if (currentMusic.chipTune) - { - //totalSeconds = (float)samples; // Need to figure out how toget this - } - else - { - totalSeconds = stb_vorbis_stream_length_in_seconds(currentMusic.stream); - } + float totalSeconds; + if (currentMusic.chipTune) + { + //totalSeconds = (float)samples; // Need to figure out how toget this + } + else + { + totalSeconds = stb_vorbis_stream_length_in_seconds(currentMusic.stream); + } return totalSeconds; } @@ -673,19 +673,19 @@ float GetMusicTimeLength(void) // Get current music time played (in seconds) float GetMusicTimePlayed(void) { - float secondsPlayed; - if (currentMusic.chipTune) - { - uint64_t* samples; - jar_xm_get_position(currentMusic.chipctx, NULL, NULL, NULL, samples); // Unsure if this is the desired value - secondsPlayed = (float)samples; - } - else - { - int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels; - int samplesPlayed = totalSamples - currentMusic.totalSamplesLeft; - secondsPlayed = (float)samplesPlayed / (currentMusic.sampleRate * currentMusic.channels); - } + float secondsPlayed; + if (currentMusic.chipTune) + { + uint64_t* samples; + jar_xm_get_position(currentMusic.chipctx, NULL, NULL, NULL, samples); // Unsure if this is the desired value + secondsPlayed = (float)samples; + } + else + { + int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels; + int samplesPlayed = totalSamples - currentMusic.totalSamplesLeft; + secondsPlayed = (float)samplesPlayed / (currentMusic.sampleRate * currentMusic.channels); + } return secondsPlayed; @@ -709,15 +709,15 @@ static bool BufferMusicStream(ALuint buffer) { while (size < MUSIC_BUFFER_SIZE) { - if (currentMusic.chipTune) - { - jar_xm_generate_samples(currentMusic.chipctx, pcm + size, (MUSIC_BUFFER_SIZE - size)/2); - streamedBytes = (MUSIC_BUFFER_SIZE - size)/2; // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. - } - else - { - streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic.stream, currentMusic.channels, pcm + size, MUSIC_BUFFER_SIZE - size); - } + if (currentMusic.chipTune) + { + jar_xm_generate_samples(currentMusic.chipctx, pcm + size, (MUSIC_BUFFER_SIZE - size)/2); + streamedBytes = (MUSIC_BUFFER_SIZE - size)/2; // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. + } + else + { + streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic.stream, currentMusic.channels, pcm + size, MUSIC_BUFFER_SIZE - size); + } if (streamedBytes > 0) size += (streamedBytes*currentMusic.channels); else break; -- cgit v1.2.3 From 1c370f5a179ab956d90d80e5a3b566ab14027557 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sun, 24 Apr 2016 22:00:40 -0700 Subject: cleaned up calculations --- src/audio.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index e5bd6819..b8657336 100644 --- a/src/audio.c +++ b/src/audio.c @@ -676,9 +676,9 @@ float GetMusicTimePlayed(void) float secondsPlayed; if (currentMusic.chipTune) { - uint64_t* samples; - jar_xm_get_position(currentMusic.chipctx, NULL, NULL, NULL, samples); // Unsure if this is the desired value - secondsPlayed = (float)samples; + uint64_t samples; + jar_xm_get_position(currentMusic.chipctx, NULL, NULL, NULL, &samples); // Unsure if this is the desired value + secondsPlayed = (float)samples / (currentMusic.sampleRate * currentMusic.channels); } else { @@ -711,7 +711,7 @@ static bool BufferMusicStream(ALuint buffer) { if (currentMusic.chipTune) { - jar_xm_generate_samples(currentMusic.chipctx, pcm + size, (MUSIC_BUFFER_SIZE - size)/2); + jar_xm_generate_samples(currentMusic.chipctx, pcm + size, (MUSIC_BUFFER_SIZE - size) / 2); streamedBytes = (MUSIC_BUFFER_SIZE - size)/2; // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. } else -- cgit v1.2.3 From 89a84a621bb5bd3a6f17ebc788e143c2874cd55b Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sun, 24 Apr 2016 22:04:31 -0700 Subject: implement --- src/audio.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index b8657336..93b83599 100644 --- a/src/audio.c +++ b/src/audio.c @@ -42,6 +42,7 @@ #include // Required for strcmp() #include // Used for .WAV loading +#define JAR_XM_IMPLEMENTATION #include "jar_xm.h" // For playing .xm files #if defined(AUDIO_STANDALONE) -- cgit v1.2.3 From f12754b01f107913b722c80f0d957bdcdfb68c81 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Mon, 25 Apr 2016 18:40:19 -0700 Subject: quick fix Boolean errors --- src/audio.c | 20 ++++++++++---------- src/jar_xm.h | 3 ++- src/raylib.h | 3 ++- 3 files changed, 14 insertions(+), 12 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 93b83599..5953cd40 100644 --- a/src/audio.c +++ b/src/audio.c @@ -42,9 +42,6 @@ #include // Required for strcmp() #include // Used for .WAV loading -#define JAR_XM_IMPLEMENTATION -#include "jar_xm.h" // For playing .xm files - #if defined(AUDIO_STANDALONE) #include // Used for functions with variable number of parameters (TraceLog()) #else @@ -53,7 +50,10 @@ #endif //#define STB_VORBIS_HEADER_ONLY -#include "stb_vorbis.h" // OGG loading functions +#include "stb_vorbis.h" // OGG loading functions + +#define JAR_XM_IMPLEMENTATION +#include "jar_xm.h" // For playing .xm files //---------------------------------------------------------------------------------- // Defines and Macros @@ -576,11 +576,11 @@ void PlayMusicStream(char *fileName) currentMusic.loop = true; // only stereo/float is supported for xm - if(info.channels == 2 && !jar_xm_create_context_from_file(¤tMusic.chipctx, currentMusic.sampleRate, fileName)) + if(!jar_xm_create_context_from_file(¤tMusic.chipctx, currentMusic.sampleRate, fileName)) { - currentMusic.format = AL_FORMAT_STEREO_FLOAT32; - jar_xm_set_max_loop_count(currentMusic.chipctx, 0); //infinite number of loops - //currentMusic.totalSamplesLeft = ; // Unsure of how to calculate this + currentMusic.format = AL_FORMAT_STEREO16; // AL_FORMAT_STEREO_FLOAT32; + jar_xm_set_max_loop_count(currentMusic.chipctx, 0); // infinite number of loops + currentMusic.totalSamplesLeft = jar_xm_get_remaining_samples(currentMusic.chipctx); musicEnabled = true; } } @@ -712,8 +712,8 @@ static bool BufferMusicStream(ALuint buffer) { if (currentMusic.chipTune) { - jar_xm_generate_samples(currentMusic.chipctx, pcm + size, (MUSIC_BUFFER_SIZE - size) / 2); - streamedBytes = (MUSIC_BUFFER_SIZE - size)/2; // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. + jar_xm_generate_samples_16bit(currentMusic.chipctx, pcm + size, (MUSIC_BUFFER_SIZE - size) / 2); + streamedBytes = (MUSIC_BUFFER_SIZE - size) * 2; // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. } else { diff --git a/src/jar_xm.h b/src/jar_xm.h index 4fda948b..062b88da 100644 --- a/src/jar_xm.h +++ b/src/jar_xm.h @@ -59,10 +59,11 @@ #include #include #include -#include #include #include + + //------------------------------------------------------------------------------- #ifdef __cplusplus extern "C" { diff --git a/src/raylib.h b/src/raylib.h index 0c80b957..de157087 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -261,7 +261,8 @@ //---------------------------------------------------------------------------------- #ifndef __cplusplus // Boolean type -typedef enum { false, true } bool; +#include +//typedef enum { false, true } bool; #endif // byte type -- cgit v1.2.3 From 04d9deac92dc9dc041ee31b0d9535d0d535d4040 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Mon, 25 Apr 2016 20:05:03 -0700 Subject: setting up openal --- src/audio.c | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 5953cd40..b46c2c20 100644 --- a/src/audio.c +++ b/src/audio.c @@ -85,6 +85,7 @@ typedef struct Music { int channels; int sampleRate; int totalSamplesLeft; + float totalLengthSeconds; bool loop; bool chipTune; // True if chiptune is loaded } Music; @@ -575,13 +576,26 @@ void PlayMusicStream(char *fileName) currentMusic.sampleRate = 48000; currentMusic.loop = true; - // only stereo/float is supported for xm + // only stereo is supported for xm if(!jar_xm_create_context_from_file(¤tMusic.chipctx, currentMusic.sampleRate, fileName)) { currentMusic.format = AL_FORMAT_STEREO16; // AL_FORMAT_STEREO_FLOAT32; jar_xm_set_max_loop_count(currentMusic.chipctx, 0); // infinite number of loops currentMusic.totalSamplesLeft = jar_xm_get_remaining_samples(currentMusic.chipctx); + currentMusic.totalLengthSeconds = currentMusic.totalSamplesLeft / (currentMusic.sampleRate * currentMusic.channels); musicEnabled = true; + + // Set up OpenAL + alGenSources(1, ¤tMusic.source); + alSourcef(currentMusic.source, AL_PITCH, 1); + alSourcef(currentMusic.source, AL_GAIN, 1); + alSource3f(currentMusic.source, AL_POSITION, 0, 0, 0); + alSource3f(currentMusic.source, AL_VELOCITY, 0, 0, 0); + alGenBuffers(2, currentMusic.buffers); + BufferMusicStream(currentMusic.buffers[0]); + BufferMusicStream(currentMusic.buffers[1]); + alSourceQueueBuffers(currentMusic.source, 2, currentMusic.buffers); + alSourcePlay(currentMusic.source); } } else TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName); @@ -661,7 +675,7 @@ float GetMusicTimeLength(void) float totalSeconds; if (currentMusic.chipTune) { - //totalSeconds = (float)samples; // Need to figure out how toget this + totalSeconds = currentMusic.totalLengthSeconds; // Not sure if this is the correct value } else { @@ -678,8 +692,8 @@ float GetMusicTimePlayed(void) if (currentMusic.chipTune) { uint64_t samples; - jar_xm_get_position(currentMusic.chipctx, NULL, NULL, NULL, &samples); // Unsure if this is the desired value - secondsPlayed = (float)samples / (currentMusic.sampleRate * currentMusic.channels); + jar_xm_get_position(currentMusic.chipctx, NULL, NULL, NULL, &samples); + secondsPlayed = (float)samples / (currentMusic.sampleRate * currentMusic.channels); // Not sure if this is the correct value } else { @@ -710,10 +724,11 @@ static bool BufferMusicStream(ALuint buffer) { while (size < MUSIC_BUFFER_SIZE) { - if (currentMusic.chipTune) + if (currentMusic.chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. { - jar_xm_generate_samples_16bit(currentMusic.chipctx, pcm + size, (MUSIC_BUFFER_SIZE - size) / 2); - streamedBytes = (MUSIC_BUFFER_SIZE - size) * 2; // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. + int readlen = (MUSIC_BUFFER_SIZE - size) / 2; + jar_xm_generate_samples_16bit(currentMusic.chipctx, pcm + size, readlen); // reads 2*readlen shorts and moves them to buffer+size memory location + streamedBytes = readlen * 4; // Not sure if this is what it needs } else { @@ -781,9 +796,15 @@ void UpdateMusicStream(void) // If no more data to stream, restart music (if loop) if ((!active) && (currentMusic.loop)) { - stb_vorbis_seek_start(currentMusic.stream); - currentMusic.totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic.stream)*currentMusic.channels; - + if(currentMusic.chipTune) + { + currentMusic.totalSamplesLeft = jar_xm_get_remaining_samples(currentMusic.chipctx); + } + else + { + stb_vorbis_seek_start(currentMusic.stream); + currentMusic.totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic.stream)*currentMusic.channels; + } active = BufferMusicStream(buffer); } -- cgit v1.2.3 From 3104d3d6cd3c29b6e240198628110b21882a8fb0 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Mon, 25 Apr 2016 22:18:49 -0700 Subject: small fix for streaming There is still an issue where audio will cut off after a brief moment --- src/audio.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index b46c2c20..9472cef6 100644 --- a/src/audio.c +++ b/src/audio.c @@ -567,10 +567,15 @@ void PlayMusicStream(char *fileName) // NOTE: Regularly, we must check if a buffer has been processed and refill it: UpdateMusicStream() currentMusic.totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels; + currentMusic.totalLengthSeconds = stb_vorbis_stream_length_in_seconds(currentMusic.stream); } } else if (strcmp(GetExtension(fileName),"xm") == 0) { + // Stop current music, clean buffers, unload current stream + StopMusicStream(); + + // new song settings for xm chiptune currentMusic.chipTune = true; currentMusic.channels = 2; currentMusic.sampleRate = 48000; @@ -714,39 +719,37 @@ float GetMusicTimePlayed(void) static bool BufferMusicStream(ALuint buffer) { short pcm[MUSIC_BUFFER_SIZE]; - + int size = 0; // Total size of data steamed (in bytes) - int streamedBytes = 0; // Bytes of data obtained in one samples get - + int streamedBytes = 0; // samples of data obtained, channels are not included in calculation bool active = true; // We can get more data from stream (not finished) if (musicEnabled) { - while (size < MUSIC_BUFFER_SIZE) + if (currentMusic.chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. { - if (currentMusic.chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. - { - int readlen = (MUSIC_BUFFER_SIZE - size) / 2; - jar_xm_generate_samples_16bit(currentMusic.chipctx, pcm + size, readlen); // reads 2*readlen shorts and moves them to buffer+size memory location - streamedBytes = readlen * 4; // Not sure if this is what it needs - } - else + int readlen = MUSIC_BUFFER_SIZE / 2; + jar_xm_generate_samples_16bit(currentMusic.chipctx, pcm, readlen); // reads 2*readlen shorts and moves them to buffer+size memory location + size += readlen * currentMusic.channels; // Not sure if this is what it needs + } + else + { + while (size < MUSIC_BUFFER_SIZE) { streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic.stream, currentMusic.channels, pcm + size, MUSIC_BUFFER_SIZE - size); + if (streamedBytes > 0) size += (streamedBytes*currentMusic.channels); + else break; } - - if (streamedBytes > 0) size += (streamedBytes*currentMusic.channels); - else break; } - - //TraceLog(DEBUG, "Streaming music data to buffer. Bytes streamed: %i", size); + TraceLog(DEBUG, "Streaming music data to buffer. Bytes streamed: %i", size); } if (size > 0) { alBufferData(buffer, currentMusic.format, pcm, size*sizeof(short), currentMusic.sampleRate); - currentMusic.totalSamplesLeft -= size; + + if(currentMusic.totalSamplesLeft <= 0) active = false; // end if no more samples left } else { -- cgit v1.2.3 From 299ae7a4bdcddc31281b1e2d0cd2df476755fb79 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Tue, 26 Apr 2016 16:50:07 -0700 Subject: new trace logs and optimizations --- src/audio.c | 14 ++++++++++---- src/jar_xm.h | 30 +++++++++++++----------------- 2 files changed, 23 insertions(+), 21 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 9472cef6..2b8c6d48 100644 --- a/src/audio.c +++ b/src/audio.c @@ -584,12 +584,15 @@ void PlayMusicStream(char *fileName) // only stereo is supported for xm if(!jar_xm_create_context_from_file(¤tMusic.chipctx, currentMusic.sampleRate, fileName)) { - currentMusic.format = AL_FORMAT_STEREO16; // AL_FORMAT_STEREO_FLOAT32; + currentMusic.format = AL_FORMAT_STEREO16; jar_xm_set_max_loop_count(currentMusic.chipctx, 0); // infinite number of loops currentMusic.totalSamplesLeft = jar_xm_get_remaining_samples(currentMusic.chipctx); - currentMusic.totalLengthSeconds = currentMusic.totalSamplesLeft / (currentMusic.sampleRate * currentMusic.channels); + currentMusic.totalLengthSeconds = ((float)currentMusic.totalSamplesLeft) / ((float)currentMusic.sampleRate); musicEnabled = true; + TraceLog(INFO, "[%s] XM number of samples: %i", fileName, currentMusic.totalSamplesLeft); + TraceLog(INFO, "[%s] XM track length: %11.6f sec", fileName, currentMusic.totalLengthSeconds); + // Set up OpenAL alGenSources(1, ¤tMusic.source); alSourcef(currentMusic.source, AL_PITCH, 1); @@ -601,7 +604,10 @@ void PlayMusicStream(char *fileName) BufferMusicStream(currentMusic.buffers[1]); alSourceQueueBuffers(currentMusic.source, 2, currentMusic.buffers); alSourcePlay(currentMusic.source); + + // NOTE: Regularly, we must check if a buffer has been processed and refill it: UpdateMusicStream() } + else TraceLog(WARNING, "[%s] XM file could not be opened", fileName); } else TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName); } @@ -680,7 +686,7 @@ float GetMusicTimeLength(void) float totalSeconds; if (currentMusic.chipTune) { - totalSeconds = currentMusic.totalLengthSeconds; // Not sure if this is the correct value + totalSeconds = currentMusic.totalLengthSeconds; } else { @@ -801,7 +807,7 @@ void UpdateMusicStream(void) { if(currentMusic.chipTune) { - currentMusic.totalSamplesLeft = jar_xm_get_remaining_samples(currentMusic.chipctx); + currentMusic.totalSamplesLeft = currentMusic.totalLengthSeconds * currentMusic.sampleRate; } else { diff --git a/src/jar_xm.h b/src/jar_xm.h index 062b88da..f9ddb511 100644 --- a/src/jar_xm.h +++ b/src/jar_xm.h @@ -121,51 +121,47 @@ void jar_xm_free_context(jar_xm_context_t*); /** Play the module and put the sound samples in an output buffer. * - * @param output buffer of 2*numsamples elements + * @param output buffer of 2*numsamples elements (A left and right value for each sample) * @param numsamples number of samples to generate */ void jar_xm_generate_samples(jar_xm_context_t*, float* output, size_t numsamples); /** Play the module, resample from 32 bit to 16 bit, and put the sound samples in an output buffer. * - * @param output buffer of 2*numsamples elements + * @param output buffer of 2*numsamples elements (A left and right value for each sample) * @param numsamples number of samples to generate */ void jar_xm_generate_samples_16bit(jar_xm_context_t* ctx, short* output, size_t numsamples) { float* musicBuffer = malloc((2*numsamples)*sizeof(float)); - short* musicBuffer2 = malloc((2*numsamples)*sizeof(short)); - jar_xm_generate_samples(ctx, musicBuffer, numsamples); - int x; - for(x=0;x<2*numsamples;x++) - musicBuffer2[x] = musicBuffer[x] * SHRT_MAX; + if(output){ + int x; + for(x=0;x<2*numsamples;x++) + output[x] = musicBuffer[x] * SHRT_MAX; + } - memcpy(output, musicBuffer2, (2*numsamples)*sizeof(short)); free(musicBuffer); - free(musicBuffer2); } /** Play the module, resample from 32 bit to 8 bit, and put the sound samples in an output buffer. * - * @param output buffer of 2*numsamples elements + * @param output buffer of 2*numsamples elements (A left and right value for each sample) * @param numsamples number of samples to generate */ void jar_xm_generate_samples_8bit(jar_xm_context_t* ctx, char* output, size_t numsamples) { float* musicBuffer = malloc((2*numsamples)*sizeof(float)); - char* musicBuffer2 = malloc((2*numsamples)*sizeof(char)); - jar_xm_generate_samples(ctx, musicBuffer, numsamples); - int x; - for(x=0;x<2*numsamples;x++) - musicBuffer2[x] = musicBuffer[x] * CHAR_MAX; + if(output){ + int x; + for(x=0;x<2*numsamples;x++) + output[x] = musicBuffer[x] * CHAR_MAX; + } - memcpy(output, musicBuffer2, (2*numsamples)*sizeof(char)); free(musicBuffer); - free(musicBuffer2); } -- cgit v1.2.3 From f707c1ca468ef3243808ec87a0b6a891f6c6a130 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Wed, 27 Apr 2016 00:02:11 -0700 Subject: this should work --- src/audio.c | 2 +- src/raylib.h | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 2b8c6d48..0be257d9 100644 --- a/src/audio.c +++ b/src/audio.c @@ -820,7 +820,7 @@ void UpdateMusicStream(void) // Add refilled buffer to queue again... don't let the music stop! alSourceQueueBuffers(currentMusic.source, 1, &buffer); - if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Ogg playing, error buffering data..."); + if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Error buffering data..."); processed--; } diff --git a/src/raylib.h b/src/raylib.h index de157087..699fbbdb 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -261,8 +261,9 @@ //---------------------------------------------------------------------------------- #ifndef __cplusplus // Boolean type -#include -//typedef enum { false, true } bool; + #ifndef true + typedef enum { false, true } bool; + #endif #endif // byte type -- cgit v1.2.3 From 91f1f324c0ee3bd57ed778b39fd54d97330dba32 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Fri, 29 Apr 2016 23:00:12 -0700 Subject: First stage of audio API update Look over changes and give feedback please. --- src/audio.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- src/audio.h | 25 +++++++++++++++++++++++-- src/raylib.h | 19 +++++++++++++++++++ 3 files changed, 92 insertions(+), 4 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 0be257d9..ededf4ae 100644 --- a/src/audio.c +++ b/src/audio.c @@ -97,9 +97,10 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- +static bool mixChannelsActive_g[4]; // What mix channels are currently active static bool musicEnabled = false; -static Music currentMusic; // Current music loaded - // NOTE: Only one music file playing at a time +static Music currentMusic; // Current music loaded + // NOTE: Only one music file playing at a time //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -164,6 +165,53 @@ void CloseAudioDevice(void) alcCloseDevice(device); } +// True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet +bool AudioDeviceReady(void) +{ + ALCcontext *context = alcGetCurrentContext(); + if (context == NULL) return false; + else{ + ALCdevice *device = alcGetContextsDevice(context); + if (device == NULL) return false; + else return true; + } +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Custom audio output +//---------------------------------------------------------------------------------- + +// Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing +// The mix_t is what mix channel you want to operate on, mixA->mixD are the ones available. Each mix channel can only be used one at a time. +// exmple usage is InitAudioContext(48000, 16, mixA, stereo); +AudioContext* InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, mix_t mixChannel, channel_t channels) +{ + if(!AudioDeviceReady()) InitAudioDevice(); + else StopMusicStream(); + + if(!mixChannelsActive_g[mixChannel]){ + AudioContext *ac = malloc(sizeof(AudioContext)); + ac->sampleRate = sampleRate; + ac->bitsPerSample = bitsPerSample; + ac->mixChannel = mixChannel; + ac->channels = channels; + mixChannelsActive_g[mixChannel] = true; + return ac; + } + return NULL; +} + +// Frees buffer in audio context +void CloseAudioContext(AudioContext *ctx) +{ + if(ctx){ + mixChannelsActive_g[ctx->mixChannel] = false; + free(ctx); + } +} + + + //---------------------------------------------------------------------------------- // Module Functions Definition - Sounds loading and playing (.WAV) //---------------------------------------------------------------------------------- diff --git a/src/audio.h b/src/audio.h index ed156532..401edc3e 100644 --- a/src/audio.h +++ b/src/audio.h @@ -41,8 +41,12 @@ //---------------------------------------------------------------------------------- #ifndef __cplusplus // Boolean type -typedef enum { false, true } bool; + #ifndef true + typedef enum { false, true } bool; + #endif #endif +typedef enum { silence, mono, stereo } channel_t; +typedef enum { mixA, mixB, mixC, mixD } mix_t; // Used for mixing/muxing up to four diferent audio streams // Sound source type typedef struct Sound { @@ -59,6 +63,16 @@ typedef struct Wave { short channels; } Wave; +// Audio Context, used to create custom audio streams that are not bound to a sound file. There can be +// no more than 4 concurrent audio contexts in use. This is due to each active context being tied to +// a dedicated mix channel. +typedef struct AudioContext { + unsigned short sampleRate; // default is 48000 + unsigned char bitsPerSample; // 16 is default + mix_t mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream + channel_t channels; // 1=mono, 2=stereo +} AudioContext; + #ifdef __cplusplus extern "C" { // Prevents name mangling of functions #endif @@ -73,6 +87,13 @@ extern "C" { // Prevents name mangling of functions //---------------------------------------------------------------------------------- void InitAudioDevice(void); // Initialize audio device and context void CloseAudioDevice(void); // Close the audio device and context (and music stream) +bool AudioDeviceReady(void); // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet + +// Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing +// The mix_t is what mix channel you want to operate on, mixA->mixD are the ones available. Each mix channel can only be used one at a time. +// exmple usage is InitAudioContext(48000, 16, mixA, stereo); +AudioContext* InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, mix_t mixChannel, channel_t channels); +void CloseAudioContext(AudioContext *ctx); // Frees audio context Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data @@ -92,7 +113,7 @@ void PauseMusicStream(void); // Pause music p void ResumeMusicStream(void); // Resume playing paused music bool MusicIsPlaying(void); // Check if music is playing void SetMusicVolume(float volume); // Set volume for music (1.0 is max level) -float GetMusicTimeLength(void); // Get current music time length (in seconds) +float GetMusicTimeLength(void); // Get music time length (in seconds) float GetMusicTimePlayed(void); // Get current music time played (in seconds) #ifdef __cplusplus diff --git a/src/raylib.h b/src/raylib.h index 699fbbdb..1721c009 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -265,6 +265,8 @@ typedef enum { false, true } bool; #endif #endif +typedef enum { silence, mono, stereo } channel_t; +typedef enum { mixA, mixB, mixC, mixD } mix_t; // Used for mixing/muxing up to four diferent audio streams // byte type typedef unsigned char byte; @@ -446,6 +448,16 @@ typedef struct Wave { short channels; } Wave; +// Audio Context, used to create custom audio streams that are not bound to a sound file. There can be +// no more than 4 concurrent audio contexts in use. This is due to each active context being tied to +// a dedicated mix channel. +typedef struct AudioContext { + unsigned short sampleRate; // default is 48000 + unsigned char bitsPerSample; // 16 is default + mix_t mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream + channel_t channels; // 1=mono, 2=stereo +} AudioContext; + // Texture formats // NOTE: Support depends on OpenGL version and platform typedef enum { @@ -863,6 +875,13 @@ void DrawPhysicObjectInfo(PhysicObject *pObj, Vector2 position, int fontSize); //------------------------------------------------------------------------------------ void InitAudioDevice(void); // Initialize audio device and context void CloseAudioDevice(void); // Close the audio device and context (and music stream) +bool AudioDeviceReady(void); // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet + +// Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing +// The mix_t is what mix channel you want to operate on, mixA->mixD are the ones available. Each mix channel can only be used one at a time. +// exmple usage is InitAudioContext(48000, 16, mixA, stereo); +AudioContext* InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, mix_t mixChannel, channel_t channels); +void CloseAudioContext(AudioContext *ctx); // Frees audio context Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data -- cgit v1.2.3 From 5f1e8b827822ded6d24d71f75e1f1d0d301dbb2d Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Fri, 29 Apr 2016 23:43:21 -0700 Subject: hide struct from user Hiding the struct from user should protect from accidentally modifying the mix channel. This could cause serious errors down the road. --- src/audio.c | 37 +++++++++++++++++++++++++++---------- src/audio.h | 12 ++++-------- src/raylib.h | 12 ++++-------- 3 files changed, 35 insertions(+), 26 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index ededf4ae..2c0ed3de 100644 --- a/src/audio.c +++ b/src/audio.c @@ -90,6 +90,16 @@ typedef struct Music { bool chipTune; // True if chiptune is loaded } Music; +// Audio Context, used to create custom audio streams that are not bound to a sound file. There can be +// no more than 4 concurrent audio contexts in use. This is due to each active context being tied to +// a dedicated mix channel. +typedef struct AudioContext_t { + unsigned short sampleRate; // default is 48000 + unsigned char bitsPerSample; // 16 is default + mix_t mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream + channel_t channels; // 1=mono, 2=stereo +} AudioContext_t; + #if defined(AUDIO_STANDALONE) typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; #endif @@ -97,10 +107,10 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static bool mixChannelsActive_g[4]; // What mix channels are currently active +static AudioContext_t* mixChannelsActive_g[4]; // What mix channels are currently active static bool musicEnabled = false; -static Music currentMusic; // Current music loaded - // NOTE: Only one music file playing at a time +static Music currentMusic; // Current music loaded + // NOTE: Only one music file playing at a time //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -184,32 +194,39 @@ bool AudioDeviceReady(void) // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mix_t is what mix channel you want to operate on, mixA->mixD are the ones available. Each mix channel can only be used one at a time. // exmple usage is InitAudioContext(48000, 16, mixA, stereo); -AudioContext* InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, mix_t mixChannel, channel_t channels) +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, mix_t mixChannel, channel_t channels) { if(!AudioDeviceReady()) InitAudioDevice(); else StopMusicStream(); if(!mixChannelsActive_g[mixChannel]){ - AudioContext *ac = malloc(sizeof(AudioContext)); + AudioContext_t *ac = malloc(sizeof(AudioContext_t)); ac->sampleRate = sampleRate; ac->bitsPerSample = bitsPerSample; ac->mixChannel = mixChannel; ac->channels = channels; - mixChannelsActive_g[mixChannel] = true; + mixChannelsActive_g[mixChannel] = ac; return ac; } return NULL; } // Frees buffer in audio context -void CloseAudioContext(AudioContext *ctx) +void CloseAudioContext(AudioContext ctx) { - if(ctx){ - mixChannelsActive_g[ctx->mixChannel] = false; - free(ctx); + AudioContext_t *context = (AudioContext_t*)ctx; + if(context){ + mixChannelsActive_g[context->mixChannel] = NULL; + free(context); } } +// Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in +void UpdateAudioContext(AudioContext ctx, void *data, unsigned short *dataLength) +{ + ; +} + //---------------------------------------------------------------------------------- diff --git a/src/audio.h b/src/audio.h index 401edc3e..cadf2bc1 100644 --- a/src/audio.h +++ b/src/audio.h @@ -66,12 +66,7 @@ typedef struct Wave { // Audio Context, used to create custom audio streams that are not bound to a sound file. There can be // no more than 4 concurrent audio contexts in use. This is due to each active context being tied to // a dedicated mix channel. -typedef struct AudioContext { - unsigned short sampleRate; // default is 48000 - unsigned char bitsPerSample; // 16 is default - mix_t mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream - channel_t channels; // 1=mono, 2=stereo -} AudioContext; +typedef void* AudioContext; #ifdef __cplusplus extern "C" { // Prevents name mangling of functions @@ -92,8 +87,9 @@ bool AudioDeviceReady(void); // True if call // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mix_t is what mix channel you want to operate on, mixA->mixD are the ones available. Each mix channel can only be used one at a time. // exmple usage is InitAudioContext(48000, 16, mixA, stereo); -AudioContext* InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, mix_t mixChannel, channel_t channels); -void CloseAudioContext(AudioContext *ctx); // Frees audio context +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, mix_t mixChannel, channel_t channels); +void CloseAudioContext(AudioContext ctx); // Frees audio context +void UpdateAudioContext(AudioContext ctx, void *data, unsigned short *dataLength); // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data diff --git a/src/raylib.h b/src/raylib.h index 1721c009..5b7b34fd 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -451,12 +451,7 @@ typedef struct Wave { // Audio Context, used to create custom audio streams that are not bound to a sound file. There can be // no more than 4 concurrent audio contexts in use. This is due to each active context being tied to // a dedicated mix channel. -typedef struct AudioContext { - unsigned short sampleRate; // default is 48000 - unsigned char bitsPerSample; // 16 is default - mix_t mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream - channel_t channels; // 1=mono, 2=stereo -} AudioContext; +typedef void* AudioContext; // Texture formats // NOTE: Support depends on OpenGL version and platform @@ -880,8 +875,9 @@ bool AudioDeviceReady(void); // True if call // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mix_t is what mix channel you want to operate on, mixA->mixD are the ones available. Each mix channel can only be used one at a time. // exmple usage is InitAudioContext(48000, 16, mixA, stereo); -AudioContext* InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, mix_t mixChannel, channel_t channels); -void CloseAudioContext(AudioContext *ctx); // Frees audio context +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, mix_t mixChannel, channel_t channels); +void CloseAudioContext(AudioContext ctx); // Frees audio context +void UpdateAudioContext(AudioContext ctx, void *data, unsigned short *dataLength); // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data -- cgit v1.2.3 From a1038f61b60ad21c936af783facc91fe01607be3 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sat, 30 Apr 2016 15:41:46 -0700 Subject: BPS type added to ensure consistency --- src/audio.c | 42 ++++++++++++++++++++++++++++++++++++++---- src/audio.h | 7 ++++--- src/raylib.h | 7 ++++--- 3 files changed, 46 insertions(+), 10 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 2c0ed3de..9d1aa014 100644 --- a/src/audio.c +++ b/src/audio.c @@ -95,9 +95,12 @@ typedef struct Music { // a dedicated mix channel. typedef struct AudioContext_t { unsigned short sampleRate; // default is 48000 - unsigned char bitsPerSample; // 16 is default + BPS bitsPerSample; // 16 is default mix_t mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream channel_t channels; // 1=mono, 2=stereo + ALenum alFormat; // openAL format specifier + ALuint alSource; // openAL source + ALuint alBuffer[2]; // openAL sample buffer } AudioContext_t; #if defined(AUDIO_STANDALONE) @@ -193,8 +196,8 @@ bool AudioDeviceReady(void) // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mix_t is what mix channel you want to operate on, mixA->mixD are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, 16, mixA, stereo); -AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, mix_t mixChannel, channel_t channels) +// exmple usage is InitAudioContext(48000, sixteenBPS, mixA, stereo); +AudioContext InitAudioContext(unsigned short sampleRate, BPS bitsPerSample, mix_t mixChannel, channel_t channels) { if(!AudioDeviceReady()) InitAudioDevice(); else StopMusicStream(); @@ -206,6 +209,30 @@ AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSa ac->mixChannel = mixChannel; ac->channels = channels; mixChannelsActive_g[mixChannel] = ac; + + // setup openAL format + if (channels == mono) + { + if (bitsPerSample == eightBPS ) ac->alFormat = AL_FORMAT_MONO8; + else if (bitsPerSample == sixteenBPS) ac->alFormat = AL_FORMAT_MONO16; + } + else if (channels == stereo) + { + if (bitsPerSample == eightBPS ) ac->alFormat = AL_FORMAT_STEREO8; + else if (bitsPerSample == sixteenBPS) ac->alFormat = AL_FORMAT_STEREO16; + } + + // Create an audio source + alGenSources(1, &ac->alSource); + alSourcef(ac->alSource, AL_PITCH, 1); + alSourcef(ac->alSource, AL_GAIN, 1); + alSource3f(ac->alSource, AL_POSITION, 0, 0, 0); + alSource3f(ac->alSource, AL_VELOCITY, 0, 0, 0); + + // Create Buffer + alGenBuffers(2, &ac->alBuffer); + + return ac; } return NULL; @@ -216,15 +243,22 @@ void CloseAudioContext(AudioContext ctx) { AudioContext_t *context = (AudioContext_t*)ctx; if(context){ + alDeleteSources(1, &context->alSource); + alDeleteBuffers(2, &context->alBuffer); mixChannelsActive_g[context->mixChannel] = NULL; free(context); + ctx = NULL; } } // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in void UpdateAudioContext(AudioContext ctx, void *data, unsigned short *dataLength) { - ; + AudioContext_t *context = (AudioContext_t*)ctx; + if(!musicEnabled && context && mixChannelsActive_g[context->mixChannel] == context) + { + ; + } } diff --git a/src/audio.h b/src/audio.h index cadf2bc1..28fe4b3c 100644 --- a/src/audio.h +++ b/src/audio.h @@ -45,8 +45,9 @@ typedef enum { false, true } bool; #endif #endif -typedef enum { silence, mono, stereo } channel_t; -typedef enum { mixA, mixB, mixC, mixD } mix_t; // Used for mixing/muxing up to four diferent audio streams +typedef enum { silence, mono, stereo } channel_t; // number of audio sources per sample +typedef enum { mixA, mixB, mixC, mixD } mix_t; // Used for mixing/muxing up to four diferent audio samples +typedef enum { eightBPS, sixteenBPS } BPS; // Either 8 or 16 bit quality samples // Sound source type typedef struct Sound { @@ -87,7 +88,7 @@ bool AudioDeviceReady(void); // True if call // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mix_t is what mix channel you want to operate on, mixA->mixD are the ones available. Each mix channel can only be used one at a time. // exmple usage is InitAudioContext(48000, 16, mixA, stereo); -AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, mix_t mixChannel, channel_t channels); +AudioContext InitAudioContext(unsigned short sampleRate, BPS bitsPerSample, mix_t mixChannel, channel_t channels); void CloseAudioContext(AudioContext ctx); // Frees audio context void UpdateAudioContext(AudioContext ctx, void *data, unsigned short *dataLength); // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in diff --git a/src/raylib.h b/src/raylib.h index 5b7b34fd..83f58a51 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -265,8 +265,9 @@ typedef enum { false, true } bool; #endif #endif -typedef enum { silence, mono, stereo } channel_t; -typedef enum { mixA, mixB, mixC, mixD } mix_t; // Used for mixing/muxing up to four diferent audio streams +typedef enum { silence, mono, stereo } channel_t; // number of audio sources per sample +typedef enum { mixA, mixB, mixC, mixD } mix_t; // Used for mixing/muxing up to four diferent audio samples +typedef enum { eightBPS, sixteenBPS } BPS; // Either 8 or 16 bit quality samples // byte type typedef unsigned char byte; @@ -875,7 +876,7 @@ bool AudioDeviceReady(void); // True if call // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mix_t is what mix channel you want to operate on, mixA->mixD are the ones available. Each mix channel can only be used one at a time. // exmple usage is InitAudioContext(48000, 16, mixA, stereo); -AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, mix_t mixChannel, channel_t channels); +AudioContext InitAudioContext(unsigned short sampleRate, BPS bitsPerSample, mix_t mixChannel, channel_t channels); void CloseAudioContext(AudioContext ctx); // Frees audio context void UpdateAudioContext(AudioContext ctx, void *data, unsigned short *dataLength); // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in -- cgit v1.2.3 From 34e5fcf47e99deecc9954fd848130c61119e2e93 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sat, 30 Apr 2016 16:05:43 -0700 Subject: removed enums --- src/audio.c | 36 +++++++++++++++++++----------------- src/audio.h | 13 +++++-------- src/raylib.h | 13 +++++-------- 3 files changed, 29 insertions(+), 33 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 9d1aa014..8a8ca4ef 100644 --- a/src/audio.c +++ b/src/audio.c @@ -59,6 +59,7 @@ // Defines and Macros //---------------------------------------------------------------------------------- #define MUSIC_STREAM_BUFFERS 2 +#define MAX_AUDIO_CONTEXTS 4 #if defined(PLATFORM_RPI) || defined(PLATFORM_ANDROID) // NOTE: On RPI and Android should be lower to avoid frame-stalls @@ -95,9 +96,9 @@ typedef struct Music { // a dedicated mix channel. typedef struct AudioContext_t { unsigned short sampleRate; // default is 48000 - BPS bitsPerSample; // 16 is default - mix_t mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream - channel_t channels; // 1=mono, 2=stereo + unsigned char bitsPerSample; // 16 is default + unsigned char mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream + unsigned char channels; // 1=mono, 2=stereo ALenum alFormat; // openAL format specifier ALuint alSource; // openAL source ALuint alBuffer[2]; // openAL sample buffer @@ -110,10 +111,10 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static AudioContext_t* mixChannelsActive_g[4]; // What mix channels are currently active +static AudioContext_t* mixChannelsActive_g[MAX_AUDIO_CONTEXTS]; // What mix channels are currently active static bool musicEnabled = false; -static Music currentMusic; // Current music loaded - // NOTE: Only one music file playing at a time +static Music currentMusic; // Current music loaded + // NOTE: Only one music file playing at a time //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -179,7 +180,7 @@ void CloseAudioDevice(void) } // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet -bool AudioDeviceReady(void) +bool IsAudioDeviceReady(void) { ALCcontext *context = alcGetCurrentContext(); if (context == NULL) return false; @@ -195,11 +196,12 @@ bool AudioDeviceReady(void) //---------------------------------------------------------------------------------- // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing -// The mix_t is what mix channel you want to operate on, mixA->mixD are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, sixteenBPS, mixA, stereo); -AudioContext InitAudioContext(unsigned short sampleRate, BPS bitsPerSample, mix_t mixChannel, channel_t channels) +// The mixChannel is what mix channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. +// exmple usage is InitAudioContext(48000, 16, 0, 2); // stereo, mixchannel 1, 16bit, 48khz +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, unsigned char mixChannel, unsigned char channels) { - if(!AudioDeviceReady()) InitAudioDevice(); + if(mixChannel > MAX_AUDIO_CONTEXTS) return NULL; + if(!IsAudioDeviceReady()) InitAudioDevice(); else StopMusicStream(); if(!mixChannelsActive_g[mixChannel]){ @@ -211,15 +213,15 @@ AudioContext InitAudioContext(unsigned short sampleRate, BPS bitsPerSample, mix_ mixChannelsActive_g[mixChannel] = ac; // setup openAL format - if (channels == mono) + if (channels == 1) { - if (bitsPerSample == eightBPS ) ac->alFormat = AL_FORMAT_MONO8; - else if (bitsPerSample == sixteenBPS) ac->alFormat = AL_FORMAT_MONO16; + if (bitsPerSample == 8 ) ac->alFormat = AL_FORMAT_MONO8; + else if (bitsPerSample == 16) ac->alFormat = AL_FORMAT_MONO16; } - else if (channels == stereo) + else if (channels == 2) { - if (bitsPerSample == eightBPS ) ac->alFormat = AL_FORMAT_STEREO8; - else if (bitsPerSample == sixteenBPS) ac->alFormat = AL_FORMAT_STEREO16; + if (bitsPerSample == 8 ) ac->alFormat = AL_FORMAT_STEREO8; + else if (bitsPerSample == 16) ac->alFormat = AL_FORMAT_STEREO16; } // Create an audio source diff --git a/src/audio.h b/src/audio.h index 28fe4b3c..9c681044 100644 --- a/src/audio.h +++ b/src/audio.h @@ -45,9 +45,6 @@ typedef enum { false, true } bool; #endif #endif -typedef enum { silence, mono, stereo } channel_t; // number of audio sources per sample -typedef enum { mixA, mixB, mixC, mixD } mix_t; // Used for mixing/muxing up to four diferent audio samples -typedef enum { eightBPS, sixteenBPS } BPS; // Either 8 or 16 bit quality samples // Sound source type typedef struct Sound { @@ -83,13 +80,13 @@ extern "C" { // Prevents name mangling of functions //---------------------------------------------------------------------------------- void InitAudioDevice(void); // Initialize audio device and context void CloseAudioDevice(void); // Close the audio device and context (and music stream) -bool AudioDeviceReady(void); // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet +bool IsAudioDeviceReady(void); // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing -// The mix_t is what mix channel you want to operate on, mixA->mixD are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, 16, mixA, stereo); -AudioContext InitAudioContext(unsigned short sampleRate, BPS bitsPerSample, mix_t mixChannel, channel_t channels); -void CloseAudioContext(AudioContext ctx); // Frees audio context +// The mixChannel is what mix channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. +// exmple usage is InitAudioContext(48000, 16, 0, 2); // stereo, mixchannel 1, 16bit, 48khz +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, unsigned char mixChannel, unsigned char channels); +void CloseAudioContext(AudioContext ctx); // Frees audio context void UpdateAudioContext(AudioContext ctx, void *data, unsigned short *dataLength); // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in Sound LoadSound(char *fileName); // Load sound to memory diff --git a/src/raylib.h b/src/raylib.h index 83f58a51..8390d176 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -265,9 +265,6 @@ typedef enum { false, true } bool; #endif #endif -typedef enum { silence, mono, stereo } channel_t; // number of audio sources per sample -typedef enum { mixA, mixB, mixC, mixD } mix_t; // Used for mixing/muxing up to four diferent audio samples -typedef enum { eightBPS, sixteenBPS } BPS; // Either 8 or 16 bit quality samples // byte type typedef unsigned char byte; @@ -871,13 +868,13 @@ void DrawPhysicObjectInfo(PhysicObject *pObj, Vector2 position, int fontSize); //------------------------------------------------------------------------------------ void InitAudioDevice(void); // Initialize audio device and context void CloseAudioDevice(void); // Close the audio device and context (and music stream) -bool AudioDeviceReady(void); // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet +bool IsAudioDeviceReady(void); // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing -// The mix_t is what mix channel you want to operate on, mixA->mixD are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, 16, mixA, stereo); -AudioContext InitAudioContext(unsigned short sampleRate, BPS bitsPerSample, mix_t mixChannel, channel_t channels); -void CloseAudioContext(AudioContext ctx); // Frees audio context +// The mixChannel is what mix channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. +// exmple usage is InitAudioContext(48000, 16, 0, 2); // stereo, mixchannel 1, 16bit, 48khz +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, unsigned char mixChannel, unsigned char channels); +void CloseAudioContext(AudioContext ctx); // Frees audio context void UpdateAudioContext(AudioContext ctx, void *data, unsigned short *dataLength); // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in Sound LoadSound(char *fileName); // Load sound to memory -- cgit v1.2.3 From 17732fa9c493bffb8e05cb5db45d998d2b151cb9 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 2 May 2016 00:38:01 +0200 Subject: Corrected warning with array --- src/audio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 8a8ca4ef..39d1ee8b 100644 --- a/src/audio.c +++ b/src/audio.c @@ -232,7 +232,7 @@ AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSa alSource3f(ac->alSource, AL_VELOCITY, 0, 0, 0); // Create Buffer - alGenBuffers(2, &ac->alBuffer); + alGenBuffers(2, ac->alBuffer); return ac; @@ -246,7 +246,7 @@ void CloseAudioContext(AudioContext ctx) AudioContext_t *context = (AudioContext_t*)ctx; if(context){ alDeleteSources(1, &context->alSource); - alDeleteBuffers(2, &context->alBuffer); + alDeleteBuffers(2, context->alBuffer); mixChannelsActive_g[context->mixChannel] = NULL; free(context); ctx = NULL; -- cgit v1.2.3 From a2a3d3aeb60dd117b02fc8c7c85ec428175a5f83 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sun, 1 May 2016 18:53:40 -0700 Subject: new silence generator --- src/audio.c | 74 ++++++++++++++++++++++++++++++++++++++++++------------------ src/audio.h | 7 +++--- src/raylib.h | 7 +++--- 3 files changed, 60 insertions(+), 28 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 8a8ca4ef..7100a6c1 100644 --- a/src/audio.c +++ b/src/audio.c @@ -37,6 +37,7 @@ #include "AL/al.h" // OpenAL basic header #include "AL/alc.h" // OpenAL context header (like OpenGL, OpenAL requires a context to work) +#include "AL/alext.h" // extensions for other format types #include // Declares malloc() and free() for memory management #include // Required for strcmp() @@ -93,12 +94,10 @@ typedef struct Music { // Audio Context, used to create custom audio streams that are not bound to a sound file. There can be // no more than 4 concurrent audio contexts in use. This is due to each active context being tied to -// a dedicated mix channel. +// a dedicated mix channel. All audio is 32bit floating point in stereo. typedef struct AudioContext_t { unsigned short sampleRate; // default is 48000 - unsigned char bitsPerSample; // 16 is default unsigned char mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream - unsigned char channels; // 1=mono, 2=stereo ALenum alFormat; // openAL format specifier ALuint alSource; // openAL source ALuint alBuffer[2]; // openAL sample buffer @@ -126,6 +125,9 @@ static void UnloadWave(Wave wave); // Unload wave data static bool BufferMusicStream(ALuint buffer); // Fill music buffers with data static void EmptyMusicStream(void); // Empty music buffers +// fill buffer with zeros +static void FillAlBufferWithSilence(AudioContext_t *ac, ALuint buffer); + #if defined(AUDIO_STANDALONE) const char *GetExtension(const char *fileName); // Get the extension for a filename void TraceLog(int msgType, const char *text, ...); // Outputs a trace log message (INFO, ERROR, WARNING) @@ -197,8 +199,9 @@ bool IsAudioDeviceReady(void) // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mixChannel is what mix channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, 16, 0, 2); // stereo, mixchannel 1, 16bit, 48khz -AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, unsigned char mixChannel, unsigned char channels) +// exmple usage is InitAudioContext(48000, 0); // mixchannel 1, 48khz +// all samples are floating point stereo by default +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel) { if(mixChannel > MAX_AUDIO_CONTEXTS) return NULL; if(!IsAudioDeviceReady()) InitAudioDevice(); @@ -207,22 +210,11 @@ AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSa if(!mixChannelsActive_g[mixChannel]){ AudioContext_t *ac = malloc(sizeof(AudioContext_t)); ac->sampleRate = sampleRate; - ac->bitsPerSample = bitsPerSample; ac->mixChannel = mixChannel; - ac->channels = channels; mixChannelsActive_g[mixChannel] = ac; // setup openAL format - if (channels == 1) - { - if (bitsPerSample == 8 ) ac->alFormat = AL_FORMAT_MONO8; - else if (bitsPerSample == 16) ac->alFormat = AL_FORMAT_MONO16; - } - else if (channels == 2) - { - if (bitsPerSample == 8 ) ac->alFormat = AL_FORMAT_STEREO8; - else if (bitsPerSample == 16) ac->alFormat = AL_FORMAT_STEREO16; - } + ac->alFormat = AL_FORMAT_STEREO_FLOAT32; // Create an audio source alGenSources(1, &ac->alSource); @@ -232,7 +224,14 @@ AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSa alSource3f(ac->alSource, AL_VELOCITY, 0, 0, 0); // Create Buffer - alGenBuffers(2, &ac->alBuffer); + alGenBuffers(2, ac->alBuffer); + + //fill buffers + FillAlBufferWithSilence(ac, ac->alBuffer[0]); + FillAlBufferWithSilence(ac, ac->alBuffer[1]); + alSourceQueueBuffers(ac->alSource, 2, ac->alBuffer); + alSourcei(ac->alSource, AL_LOOPING, AL_FALSE); // this could cause errors + alSourcePlay(ac->alSource); return ac; @@ -245,8 +244,20 @@ void CloseAudioContext(AudioContext ctx) { AudioContext_t *context = (AudioContext_t*)ctx; if(context){ + alSourceStop(context->alSource); + + //flush out all queued buffers + ALuint buffer = 0; + int queued = 0; + alGetSourcei(context->alSource, AL_BUFFERS_QUEUED, &queued); + while (queued > 0) + { + alSourceUnqueueBuffers(context->alSource, 1, &buffer); + queued--; + } + alDeleteSources(1, &context->alSource); - alDeleteBuffers(2, &context->alBuffer); + alDeleteBuffers(2, context->alBuffer); mixChannelsActive_g[context->mixChannel] = NULL; free(context); ctx = NULL; @@ -254,15 +265,34 @@ void CloseAudioContext(AudioContext ctx) } // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in -void UpdateAudioContext(AudioContext ctx, void *data, unsigned short *dataLength) +// Call "UpdateAudioContext(ctx, NULL, 0)" every game tick if you want to pause the audio +void UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength) { AudioContext_t *context = (AudioContext_t*)ctx; - if(!musicEnabled && context && mixChannelsActive_g[context->mixChannel] == context) + if (context && mixChannelsActive_g[context->mixChannel] == context) { - ; + ALint processed = 0; + ALuint buffer = 0; + alGetSourcei(context->alSource, AL_BUFFERS_PROCESSED, &processed); // Get the number of already processed buffers (if any) + + if (!data || !dataLength)// play silence + while (processed > 0) + { + alSourceUnqueueBuffers(context->alSource, 1, &buffer); + FillAlBufferWithSilence(context, buffer); + alSourceQueueBuffers(context->alSource, 1, &buffer); + processed--; + } } } +// fill buffer with zeros +static void FillAlBufferWithSilence(AudioContext_t *ac, ALuint buffer) +{ + float pcm[MUSIC_BUFFER_SIZE] = {0.f}; + alBufferData(buffer, ac->alFormat, pcm, MUSIC_BUFFER_SIZE*sizeof(float), ac->sampleRate); +} + //---------------------------------------------------------------------------------- diff --git a/src/audio.h b/src/audio.h index 9c681044..9037a843 100644 --- a/src/audio.h +++ b/src/audio.h @@ -84,10 +84,11 @@ bool IsAudioDeviceReady(void); // True if call // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mixChannel is what mix channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, 16, 0, 2); // stereo, mixchannel 1, 16bit, 48khz -AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, unsigned char mixChannel, unsigned char channels); +// exmple usage is InitAudioContext(48000, 0); // mixchannel 1, 48khz +// all samples are floating point stereo by default +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel); void CloseAudioContext(AudioContext ctx); // Frees audio context -void UpdateAudioContext(AudioContext ctx, void *data, unsigned short *dataLength); // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in +void UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data diff --git a/src/raylib.h b/src/raylib.h index 8390d176..f4ea875e 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -872,10 +872,11 @@ bool IsAudioDeviceReady(void); // True if call // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mixChannel is what mix channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, 16, 0, 2); // stereo, mixchannel 1, 16bit, 48khz -AudioContext InitAudioContext(unsigned short sampleRate, unsigned char bitsPerSample, unsigned char mixChannel, unsigned char channels); +// exmple usage is InitAudioContext(48000, 0); // mixchannel 1, 48khz +// all samples are floating point stereo by default +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel); void CloseAudioContext(AudioContext ctx); // Frees audio context -void UpdateAudioContext(AudioContext ctx, void *data, unsigned short *dataLength); // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in +void UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data -- cgit v1.2.3 From 790bc7280685d714ddce3e1ee0afa11cee0c5e06 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sun, 1 May 2016 23:07:02 -0700 Subject: bool return for failed update --- src/audio.c | 9 +++++++-- src/audio.h | 2 +- src/raylib.h | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 7100a6c1..7b42b089 100644 --- a/src/audio.c +++ b/src/audio.c @@ -256,6 +256,7 @@ void CloseAudioContext(AudioContext ctx) queued--; } + //delete source and buffers alDeleteSources(1, &context->alSource); alDeleteBuffers(2, context->alBuffer); mixChannelsActive_g[context->mixChannel] = NULL; @@ -266,7 +267,8 @@ void CloseAudioContext(AudioContext ctx) // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in // Call "UpdateAudioContext(ctx, NULL, 0)" every game tick if you want to pause the audio -void UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength) +// Returns true if data was pushed onto queue, otherwise if queue is full then no data is added and false is returned +bool UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength) { AudioContext_t *context = (AudioContext_t*)ctx; if (context && mixChannelsActive_g[context->mixChannel] == context) @@ -274,7 +276,9 @@ void UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength ALint processed = 0; ALuint buffer = 0; alGetSourcei(context->alSource, AL_BUFFERS_PROCESSED, &processed); // Get the number of already processed buffers (if any) - + + if(!processed) return false;//nothing to process, queue is still full + if (!data || !dataLength)// play silence while (processed > 0) { @@ -283,6 +287,7 @@ void UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength alSourceQueueBuffers(context->alSource, 1, &buffer); processed--; } + return true; } } diff --git a/src/audio.h b/src/audio.h index 9037a843..4a198c59 100644 --- a/src/audio.h +++ b/src/audio.h @@ -88,7 +88,7 @@ bool IsAudioDeviceReady(void); // True if call // all samples are floating point stereo by default AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel); void CloseAudioContext(AudioContext ctx); // Frees audio context -void UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played +bool UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data diff --git a/src/raylib.h b/src/raylib.h index ade581d3..9c5a8258 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -874,7 +874,7 @@ bool IsAudioDeviceReady(void); // True if call // all samples are floating point stereo by default AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel); void CloseAudioContext(AudioContext ctx); // Frees audio context -void UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played +bool UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data -- cgit v1.2.3 From 9ef0240e99e7e6a626fdb8fee4f8a81eea21f3e2 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Mon, 2 May 2016 01:24:24 -0700 Subject: resamples added Ease of use considered in api and channels are more convenient as unsigned char type. --- src/audio.c | 49 +++++++++++++++++++++++++++++++++++++++++++------ src/audio.h | 6 +++--- src/raylib.h | 6 +++--- 3 files changed, 49 insertions(+), 12 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 7b42b089..d935b6c7 100644 --- a/src/audio.c +++ b/src/audio.c @@ -97,6 +97,7 @@ typedef struct Music { // a dedicated mix channel. All audio is 32bit floating point in stereo. typedef struct AudioContext_t { unsigned short sampleRate; // default is 48000 + unsigned char channels; // 1=mono,2=stereo unsigned char mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream ALenum alFormat; // openAL format specifier ALuint alSource; // openAL source @@ -125,8 +126,9 @@ static void UnloadWave(Wave wave); // Unload wave data static bool BufferMusicStream(ALuint buffer); // Fill music buffers with data static void EmptyMusicStream(void); // Empty music buffers -// fill buffer with zeros -static void FillAlBufferWithSilence(AudioContext_t *ac, ALuint buffer); +static void FillAlBufferWithSilence(AudioContext_t *ac, ALuint buffer);// fill buffer with zeros +static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // pass two arrays of the same legnth in +static void ResampleByteToFloat(char *chars, float *floats, unsigned short len); // pass two arrays of same length in #if defined(AUDIO_STANDALONE) const char *GetExtension(const char *fileName); // Get the extension for a filename @@ -199,9 +201,9 @@ bool IsAudioDeviceReady(void) // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mixChannel is what mix channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, 0); // mixchannel 1, 48khz -// all samples are floating point stereo by default -AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel) +// exmple usage is InitAudioContext(48000, 0, 2); // mixchannel 1, 48khz, stereo +// all samples are floating point by default +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels) { if(mixChannel > MAX_AUDIO_CONTEXTS) return NULL; if(!IsAudioDeviceReady()) InitAudioDevice(); @@ -210,11 +212,15 @@ AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChanne if(!mixChannelsActive_g[mixChannel]){ AudioContext_t *ac = malloc(sizeof(AudioContext_t)); ac->sampleRate = sampleRate; + ac->channels = channels; ac->mixChannel = mixChannel; mixChannelsActive_g[mixChannel] = ac; // setup openAL format - ac->alFormat = AL_FORMAT_STEREO_FLOAT32; + if(channels == 1) + ac->alFormat = AL_FORMAT_MONO_FLOAT32; + else + ac->alFormat = AL_FORMAT_STEREO_FLOAT32; // Create an audio source alGenSources(1, &ac->alSource); @@ -289,6 +295,7 @@ bool UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength } return true; } + return false; } // fill buffer with zeros @@ -298,6 +305,36 @@ static void FillAlBufferWithSilence(AudioContext_t *ac, ALuint buffer) alBufferData(buffer, ac->alFormat, pcm, MUSIC_BUFFER_SIZE*sizeof(float), ac->sampleRate); } +// example usage: +// short sh[3] = {1,2,3};float fl[3]; +// ResampleShortToFloat(sh,fl,3); +static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len) +{ + int x; + for(x=0;x Date: Mon, 2 May 2016 14:37:00 -0700 Subject: number remaining buffer transfer for updateAudioContext updateAudioContext is almost done --- src/audio.c | 39 +++++++++++++++++++++++++++++++-------- src/audio.h | 2 +- src/raylib.h | 2 +- 3 files changed, 33 insertions(+), 10 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index d935b6c7..ff4f2858 100644 --- a/src/audio.c +++ b/src/audio.c @@ -273,17 +273,20 @@ void CloseAudioContext(AudioContext ctx) // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in // Call "UpdateAudioContext(ctx, NULL, 0)" every game tick if you want to pause the audio -// Returns true if data was pushed onto queue, otherwise if queue is full then no data is added and false is returned -bool UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength) +// Returns number of floats that where processed +unsigned short UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength) { + unsigned short numberProcessed = 0; + unsigned short numberRemaining = dataLength; AudioContext_t *context = (AudioContext_t*)ctx; + if (context && mixChannelsActive_g[context->mixChannel] == context) { ALint processed = 0; ALuint buffer = 0; alGetSourcei(context->alSource, AL_BUFFERS_PROCESSED, &processed); // Get the number of already processed buffers (if any) - if(!processed) return false;//nothing to process, queue is still full + if(!processed) return 0;//nothing to process, queue is still full if (!data || !dataLength)// play silence while (processed > 0) @@ -292,17 +295,37 @@ bool UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength FillAlBufferWithSilence(context, buffer); alSourceQueueBuffers(context->alSource, 1, &buffer); processed--; + numberProcessed+=MUSIC_BUFFER_SIZE; + } + if(numberRemaining)// buffer data stream in increments of MUSIC_BUFFER_SIZE + while (processed > 0) + { + alSourceUnqueueBuffers(context->alSource, 1, &buffer); + if(numberRemaining >= MUSIC_BUFFER_SIZE) + { + float pcm[MUSIC_BUFFER_SIZE]; + memcpy(pcm, &data[numberProcessed], MUSIC_BUFFER_SIZE); + alBufferData(buffer, context->alFormat, pcm, MUSIC_BUFFER_SIZE*sizeof(float), context->sampleRate); + alSourceQueueBuffers(context->alSource, 1, &buffer); + numberProcessed+=MUSIC_BUFFER_SIZE; + numberRemaining-=MUSIC_BUFFER_SIZE; + } + else // less than MUSIC_BUFFER_SIZE number of samples left to buffer, the remaining data is padded with zeros + { + float pcm[MUSIC_BUFFER_SIZE] = {0.f}; // pad with zeros + } + + processed--; } - return true; } - return false; + return numberProcessed; } // fill buffer with zeros -static void FillAlBufferWithSilence(AudioContext_t *ac, ALuint buffer) +static void FillAlBufferWithSilence(AudioContext_t *context, ALuint buffer) { float pcm[MUSIC_BUFFER_SIZE] = {0.f}; - alBufferData(buffer, ac->alFormat, pcm, MUSIC_BUFFER_SIZE*sizeof(float), ac->sampleRate); + alBufferData(buffer, context->alFormat, pcm, MUSIC_BUFFER_SIZE*sizeof(float), context->sampleRate); } // example usage: @@ -322,7 +345,7 @@ static void ResampleShortToFloat(short *shorts, float *floats, unsigned short le // example usage: // char ch[3] = {1,2,3};float fl[3]; -// ResampleShortToFloat(ch,fl,3); +// ResampleByteToFloat(ch,fl,3); static void ResampleByteToFloat(char *chars, float *floats, unsigned short len) { int x; diff --git a/src/audio.h b/src/audio.h index d723ef1b..2b36b3f0 100644 --- a/src/audio.h +++ b/src/audio.h @@ -88,7 +88,7 @@ bool IsAudioDeviceReady(void); // True if call // all samples are floating point by default AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels); void CloseAudioContext(AudioContext ctx); // Frees audio context -bool UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played +unsigned short UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data diff --git a/src/raylib.h b/src/raylib.h index 3fa20116..5c727b61 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -874,7 +874,7 @@ bool IsAudioDeviceReady(void); // True if call // all samples are floating point by default AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels); void CloseAudioContext(AudioContext ctx); // Frees audio context -bool UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played +unsigned short UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data -- cgit v1.2.3 From 9d09ada33b26704a7f5d285d2f0d115e41df41bf Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Mon, 2 May 2016 21:59:55 -0700 Subject: new boolean floatingPoint option Now floating point is either on or off. This simplifies the use of 16bit vs float. --- src/audio.c | 132 +++++++++++++++++++++++++++++++++++++---------------------- src/audio.h | 7 ++-- src/raylib.h | 7 ++-- 3 files changed, 88 insertions(+), 58 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index ff4f2858..dc063796 100644 --- a/src/audio.c +++ b/src/audio.c @@ -59,15 +59,17 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define MUSIC_STREAM_BUFFERS 2 -#define MAX_AUDIO_CONTEXTS 4 +#define MAX_STREAM_BUFFERS 2 +#define MAX_AUDIO_CONTEXTS 4 // Number of open AL sources #if defined(PLATFORM_RPI) || defined(PLATFORM_ANDROID) // NOTE: On RPI and Android should be lower to avoid frame-stalls - #define MUSIC_BUFFER_SIZE 4096*2 // PCM data buffer (short) - 16Kb (RPI) + #define MUSIC_BUFFER_SIZE_SHORT 4096*2 // PCM data buffer (short) - 16Kb (RPI) + #define MUSIC_BUFFER_SIZE_FLOAT 4096 // PCM data buffer (float) - 16Kb (RPI) #else // NOTE: On HTML5 (emscripten) this is allocated on heap, by default it's only 16MB!...just take care... - #define MUSIC_BUFFER_SIZE 4096*8 // PCM data buffer (short) - 64Kb + #define MUSIC_BUFFER_SIZE_SHORT 4096*8 // PCM data buffer (short) - 64Kb + #define MUSIC_BUFFER_SIZE_FLOAT 4096*4 // PCM data buffer (float) - 64Kb #endif //---------------------------------------------------------------------------------- @@ -80,7 +82,7 @@ typedef struct Music { stb_vorbis *stream; jar_xm_context_t *chipctx; // Stores jar_xm context - ALuint buffers[MUSIC_STREAM_BUFFERS]; + ALuint buffers[MAX_STREAM_BUFFERS]; ALuint source; ALenum format; @@ -96,12 +98,13 @@ typedef struct Music { // no more than 4 concurrent audio contexts in use. This is due to each active context being tied to // a dedicated mix channel. All audio is 32bit floating point in stereo. typedef struct AudioContext_t { - unsigned short sampleRate; // default is 48000 - unsigned char channels; // 1=mono,2=stereo - unsigned char mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream - ALenum alFormat; // openAL format specifier - ALuint alSource; // openAL source - ALuint alBuffer[2]; // openAL sample buffer + unsigned short sampleRate; // default is 48000 + unsigned char channels; // 1=mono,2=stereo + unsigned char mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream + bool floatingPoint; // if false then the short datatype is used instead + ALenum alFormat; // openAL format specifier + ALuint alSource; // openAL source + ALuint alBuffer[MAX_STREAM_BUFFERS]; // openAL sample buffer } AudioContext_t; #if defined(AUDIO_STANDALONE) @@ -126,7 +129,7 @@ static void UnloadWave(Wave wave); // Unload wave data static bool BufferMusicStream(ALuint buffer); // Fill music buffers with data static void EmptyMusicStream(void); // Empty music buffers -static void FillAlBufferWithSilence(AudioContext_t *ac, ALuint buffer);// fill buffer with zeros +static unsigned short FillAlBufferWithSilence(AudioContext_t *context, ALuint buffer);// fill buffer with zeros, returns number processed static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // pass two arrays of the same legnth in static void ResampleByteToFloat(char *chars, float *floats, unsigned short len); // pass two arrays of same length in @@ -201,11 +204,10 @@ bool IsAudioDeviceReady(void) // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mixChannel is what mix channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, 0, 2); // mixchannel 1, 48khz, stereo -// all samples are floating point by default -AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels) +// exmple usage is InitAudioContext(48000, 0, 2, true); // mixchannel 1, 48khz, stereo, floating point +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint) { - if(mixChannel > MAX_AUDIO_CONTEXTS) return NULL; + if(mixChannel >= MAX_AUDIO_CONTEXTS) return NULL; if(!IsAudioDeviceReady()) InitAudioDevice(); else StopMusicStream(); @@ -214,13 +216,24 @@ AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChanne ac->sampleRate = sampleRate; ac->channels = channels; ac->mixChannel = mixChannel; + ac->floatingPoint = floatingPoint; mixChannelsActive_g[mixChannel] = ac; // setup openAL format if(channels == 1) - ac->alFormat = AL_FORMAT_MONO_FLOAT32; - else - ac->alFormat = AL_FORMAT_STEREO_FLOAT32; + { + if(floatingPoint) + ac->alFormat = AL_FORMAT_MONO_FLOAT32; + else + ac->alFormat = AL_FORMAT_MONO16; + } + else if(channels == 2) + { + if(floatingPoint) + ac->alFormat = AL_FORMAT_STEREO_FLOAT32; + else + ac->alFormat = AL_FORMAT_STEREO16; + } // Create an audio source alGenSources(1, &ac->alSource); @@ -230,16 +243,17 @@ AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChanne alSource3f(ac->alSource, AL_VELOCITY, 0, 0, 0); // Create Buffer - alGenBuffers(2, ac->alBuffer); + alGenBuffers(MAX_STREAM_BUFFERS, ac->alBuffer); //fill buffers - FillAlBufferWithSilence(ac, ac->alBuffer[0]); - FillAlBufferWithSilence(ac, ac->alBuffer[1]); - alSourceQueueBuffers(ac->alSource, 2, ac->alBuffer); + int x; + for(x=0;xalBuffer[x]); + + alSourceQueueBuffers(ac->alSource, MAX_STREAM_BUFFERS, ac->alBuffer); alSourcei(ac->alSource, AL_LOOPING, AL_FALSE); // this could cause errors alSourcePlay(ac->alSource); - return ac; } return NULL; @@ -264,20 +278,22 @@ void CloseAudioContext(AudioContext ctx) //delete source and buffers alDeleteSources(1, &context->alSource); - alDeleteBuffers(2, context->alBuffer); + alDeleteBuffers(MAX_STREAM_BUFFERS, context->alBuffer); mixChannelsActive_g[context->mixChannel] = NULL; free(context); ctx = NULL; } } -// Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in -// Call "UpdateAudioContext(ctx, NULL, 0)" every game tick if you want to pause the audio -// Returns number of floats that where processed -unsigned short UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength) +// Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in. +// Call "UpdateAudioContext(ctx, NULL, 0)" every game tick if you want to pause the audio. +// @Returns number of samples that where processed. +// All data streams should be of a length that is evenly divisible by MUSIC_BUFFER_SIZE, +// otherwise the remaining data will not be pushed. +unsigned short UpdateAudioContext(AudioContext ctx, void *data, unsigned short numberElements) { unsigned short numberProcessed = 0; - unsigned short numberRemaining = dataLength; + unsigned short numberRemaining = numberElements; AudioContext_t *context = (AudioContext_t*)ctx; if (context && mixChannelsActive_g[context->mixChannel] == context) @@ -288,44 +304,60 @@ unsigned short UpdateAudioContext(AudioContext ctx, float *data, unsigned short if(!processed) return 0;//nothing to process, queue is still full - if (!data || !dataLength)// play silence + if (!data || !numberElements)// play silence + { while (processed > 0) { alSourceUnqueueBuffers(context->alSource, 1, &buffer); - FillAlBufferWithSilence(context, buffer); + numberProcessed += FillAlBufferWithSilence(context, buffer); alSourceQueueBuffers(context->alSource, 1, &buffer); processed--; - numberProcessed+=MUSIC_BUFFER_SIZE; } + } if(numberRemaining)// buffer data stream in increments of MUSIC_BUFFER_SIZE + { while (processed > 0) { - alSourceUnqueueBuffers(context->alSource, 1, &buffer); - if(numberRemaining >= MUSIC_BUFFER_SIZE) + if(context->floatingPoint && numberRemaining >= MUSIC_BUFFER_SIZE_FLOAT) // process float buffers { - float pcm[MUSIC_BUFFER_SIZE]; - memcpy(pcm, &data[numberProcessed], MUSIC_BUFFER_SIZE); - alBufferData(buffer, context->alFormat, pcm, MUSIC_BUFFER_SIZE*sizeof(float), context->sampleRate); + float *ptr = (float*)data; + alSourceUnqueueBuffers(context->alSource, 1, &buffer); + alBufferData(buffer, context->alFormat, &ptr[numberProcessed], MUSIC_BUFFER_SIZE_FLOAT*sizeof(float), context->sampleRate); alSourceQueueBuffers(context->alSource, 1, &buffer); - numberProcessed+=MUSIC_BUFFER_SIZE; - numberRemaining-=MUSIC_BUFFER_SIZE; + numberProcessed+=MUSIC_BUFFER_SIZE_FLOAT; + numberRemaining-=MUSIC_BUFFER_SIZE_FLOAT; } - else // less than MUSIC_BUFFER_SIZE number of samples left to buffer, the remaining data is padded with zeros + else if(!context->floatingPoint && numberRemaining >= MUSIC_BUFFER_SIZE_SHORT) // process short buffers { - float pcm[MUSIC_BUFFER_SIZE] = {0.f}; // pad with zeros + short *ptr = (short*)data; + alSourceUnqueueBuffers(context->alSource, 1, &buffer); + alBufferData(buffer, context->alFormat, &ptr[numberProcessed], MUSIC_BUFFER_SIZE_SHORT*sizeof(short), context->sampleRate); + alSourceQueueBuffers(context->alSource, 1, &buffer); + numberProcessed+=MUSIC_BUFFER_SIZE_SHORT; + numberRemaining-=MUSIC_BUFFER_SIZE_SHORT; } processed--; } + } } return numberProcessed; } -// fill buffer with zeros -static void FillAlBufferWithSilence(AudioContext_t *context, ALuint buffer) +// fill buffer with zeros, returns number processed +static unsigned short FillAlBufferWithSilence(AudioContext_t *context, ALuint buffer) { - float pcm[MUSIC_BUFFER_SIZE] = {0.f}; - alBufferData(buffer, context->alFormat, pcm, MUSIC_BUFFER_SIZE*sizeof(float), context->sampleRate); + if(context->floatingPoint){ + float pcm[MUSIC_BUFFER_SIZE_FLOAT] = {0.f}; + alBufferData(buffer, context->alFormat, pcm, MUSIC_BUFFER_SIZE_FLOAT*sizeof(float), context->sampleRate); + return MUSIC_BUFFER_SIZE_FLOAT; + } + else + { + short pcm[MUSIC_BUFFER_SIZE_SHORT] = {0}; + alBufferData(buffer, context->alFormat, pcm, MUSIC_BUFFER_SIZE_SHORT*sizeof(short), context->sampleRate); + return MUSIC_BUFFER_SIZE_SHORT; + } } // example usage: @@ -920,7 +952,7 @@ float GetMusicTimePlayed(void) // Fill music buffers with new data from music stream static bool BufferMusicStream(ALuint buffer) { - short pcm[MUSIC_BUFFER_SIZE]; + short pcm[MUSIC_BUFFER_SIZE_SHORT]; int size = 0; // Total size of data steamed (in bytes) int streamedBytes = 0; // samples of data obtained, channels are not included in calculation @@ -930,15 +962,15 @@ static bool BufferMusicStream(ALuint buffer) { if (currentMusic.chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. { - int readlen = MUSIC_BUFFER_SIZE / 2; + int readlen = MUSIC_BUFFER_SIZE_SHORT / 2; jar_xm_generate_samples_16bit(currentMusic.chipctx, pcm, readlen); // reads 2*readlen shorts and moves them to buffer+size memory location size += readlen * currentMusic.channels; // Not sure if this is what it needs } else { - while (size < MUSIC_BUFFER_SIZE) + while (size < MUSIC_BUFFER_SIZE_SHORT) { - streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic.stream, currentMusic.channels, pcm + size, MUSIC_BUFFER_SIZE - size); + streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic.stream, currentMusic.channels, pcm + size, MUSIC_BUFFER_SIZE_SHORT - size); if (streamedBytes > 0) size += (streamedBytes*currentMusic.channels); else break; } diff --git a/src/audio.h b/src/audio.h index 2b36b3f0..9d6fa5d0 100644 --- a/src/audio.h +++ b/src/audio.h @@ -84,11 +84,10 @@ bool IsAudioDeviceReady(void); // True if call // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mixChannel is what mix channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, 0, 2); // mixchannel 1, 48khz, stereo -// all samples are floating point by default -AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels); +// exmple usage is InitAudioContext(48000, 0, 2, true); // mixchannel 1, 48khz, stereo, floating point +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint); void CloseAudioContext(AudioContext ctx); // Frees audio context -unsigned short UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played +unsigned short UpdateAudioContext(AudioContext ctx, void *data, unsigned short numberElements); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data diff --git a/src/raylib.h b/src/raylib.h index 5c727b61..c9ba5b38 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -870,11 +870,10 @@ bool IsAudioDeviceReady(void); // True if call // Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing // The mixChannel is what mix channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, 0, 2); // mixchannel 1, 48khz, stereo -// all samples are floating point by default -AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels); +// exmple usage is InitAudioContext(48000, 0, 2, true); // mixchannel 1, 48khz, stereo, floating point +AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint); void CloseAudioContext(AudioContext ctx); // Frees audio context -unsigned short UpdateAudioContext(AudioContext ctx, float *data, unsigned short dataLength); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played +unsigned short UpdateAudioContext(AudioContext ctx, void *data, unsigned short numberElements); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data -- cgit v1.2.3 From d6feeb14ffc11e54a82f51dbbe899f720d6997e8 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Tue, 3 May 2016 02:52:45 -0700 Subject: pause on no data --- src/audio.c | 31 +++++++++++++++---------------- src/easings.h | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 16 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index dc063796..a46aa00a 100644 --- a/src/audio.c +++ b/src/audio.c @@ -114,11 +114,10 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static AudioContext_t* mixChannelsActive_g[MAX_AUDIO_CONTEXTS]; // What mix channels are currently active +static AudioContext_t* mixChannelsActive_g[MAX_AUDIO_CONTEXTS]; // What mix channels are currently active static bool musicEnabled = false; static Music currentMusic; // Current music loaded // NOTE: Only one music file playing at a time - //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- @@ -286,34 +285,34 @@ void CloseAudioContext(AudioContext ctx) } // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in. -// Call "UpdateAudioContext(ctx, NULL, 0)" every game tick if you want to pause the audio. +// Call "UpdateAudioContext(ctx, NULL, 0)" if you want to pause the audio. // @Returns number of samples that where processed. // All data streams should be of a length that is evenly divisible by MUSIC_BUFFER_SIZE, // otherwise the remaining data will not be pushed. unsigned short UpdateAudioContext(AudioContext ctx, void *data, unsigned short numberElements) { - unsigned short numberProcessed = 0; - unsigned short numberRemaining = numberElements; AudioContext_t *context = (AudioContext_t*)ctx; + if(context && context->channels == 2 && numberElements % 2 != 0) return 0; // when there is two channels there must be an even number of samples + + if (!data || !numberElements) alSourcePause(context->alSource); // pauses audio until data is given + else{ // restart audio otherwise + ALint state; + alGetSourcei(context->alSource, AL_SOURCE_STATE, &state); + if (state != AL_PLAYING) alSourcePlay(context->alSource); + } + if (context && mixChannelsActive_g[context->mixChannel] == context) { ALint processed = 0; ALuint buffer = 0; - alGetSourcei(context->alSource, AL_BUFFERS_PROCESSED, &processed); // Get the number of already processed buffers (if any) + unsigned short numberProcessed = 0; + unsigned short numberRemaining = numberElements; + + alGetSourcei(context->alSource, AL_BUFFERS_PROCESSED, &processed); // Get the number of already processed buffers (if any) if(!processed) return 0;//nothing to process, queue is still full - if (!data || !numberElements)// play silence - { - while (processed > 0) - { - alSourceUnqueueBuffers(context->alSource, 1, &buffer); - numberProcessed += FillAlBufferWithSilence(context, buffer); - alSourceQueueBuffers(context->alSource, 1, &buffer); - processed--; - } - } if(numberRemaining)// buffer data stream in increments of MUSIC_BUFFER_SIZE { while (processed > 0) diff --git a/src/easings.h b/src/easings.h index a198be4d..e1e5465a 100644 --- a/src/easings.h +++ b/src/easings.h @@ -7,6 +7,23 @@ * This header uses: * #define EASINGS_STATIC_INLINE // Inlines all functions code, so it runs faster. * // This requires lots of memory on system. +* How to use: +* The four inputs t,b,c,d are defined as follows: +* t = current time in milliseconds +* b = starting position in only one dimension [X || Y || Z] your choice +* c = the total change in value of b that needs to occur +* d = total time it should take to complete +* +* Example: +* float speed = 1.f; +* float currentTime = 0.f; +* float currentPos[2] = {0,0}; +* float newPos[2] = {1,1}; +* float tempPosition[2] = currentPos;//x,y positions +* while(currentPos[0] < newPos[0]) +* currentPos[0] = EaseSineIn(currentTime, tempPosition[0], tempPosition[0]-newPos[0], speed); +* currentPos[1] = EaseSineIn(currentTime, tempPosition[1], tempPosition[1]-newPos[0], speed); +* currentTime += diffTime(); * * A port of Robert Penner's easing equations to C (http://robertpenner.com/easing/) * -- cgit v1.2.3 From 5f73850fa675530d6933d85a6d80684106beff69 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Tue, 3 May 2016 18:04:21 +0200 Subject: Renamed functions for consistency --- src/audio.c | 4 ++-- src/audio.h | 4 ++-- src/raylib.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 39d1ee8b..09c91785 100644 --- a/src/audio.c +++ b/src/audio.c @@ -585,7 +585,7 @@ void StopSound(Sound sound) } // Check if a sound is playing -bool SoundIsPlaying(Sound sound) +bool IsSoundPlaying(Sound sound) { bool playing = false; ALint state; @@ -764,7 +764,7 @@ void ResumeMusicStream(void) } // Check if music is playing -bool MusicIsPlaying(void) +bool IsMusicPlaying(void) { bool playing = false; ALint state; diff --git a/src/audio.h b/src/audio.h index 9c681044..73f60ab1 100644 --- a/src/audio.h +++ b/src/audio.h @@ -96,7 +96,7 @@ void UnloadSound(Sound sound); // Unload sound void PlaySound(Sound sound); // Play a sound void PauseSound(Sound sound); // Pause a sound void StopSound(Sound sound); // Stop playing a sound -bool SoundIsPlaying(Sound sound); // Check if a sound is currently playing +bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) @@ -105,7 +105,7 @@ void UpdateMusicStream(void); // Updates buffe void StopMusicStream(void); // Stop music playing (close stream) void PauseMusicStream(void); // Pause music playing void ResumeMusicStream(void); // Resume playing paused music -bool MusicIsPlaying(void); // Check if music is playing +bool IsMusicPlaying(void); // Check if music is playing void SetMusicVolume(float volume); // Set volume for music (1.0 is max level) float GetMusicTimeLength(void); // Get music time length (in seconds) float GetMusicTimePlayed(void); // Get current music time played (in seconds) diff --git a/src/raylib.h b/src/raylib.h index 337b9813..bc98181b 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -882,7 +882,7 @@ void UnloadSound(Sound sound); // Unload sound void PlaySound(Sound sound); // Play a sound void PauseSound(Sound sound); // Pause a sound void StopSound(Sound sound); // Stop playing a sound -bool SoundIsPlaying(Sound sound); // Check if a sound is currently playing +bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) @@ -891,7 +891,7 @@ void UpdateMusicStream(void); // Updates buffe void StopMusicStream(void); // Stop music playing (close stream) void PauseMusicStream(void); // Pause music playing void ResumeMusicStream(void); // Resume playing paused music -bool MusicIsPlaying(void); // Check if music is playing +bool IsMusicPlaying(void); // Check if music is playing void SetMusicVolume(float volume); // Set volume for music (1.0 is max level) float GetMusicTimeLength(void); // Get current music time length (in seconds) float GetMusicTimePlayed(void); // Get current music time played (in seconds) -- cgit v1.2.3 From b7f8e97b0384ae37bff110a2ef42cc42cfa09228 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Tue, 10 May 2016 01:54:20 -0700 Subject: final fix for audiocontext system now it works --- src/audio.c | 74 ++++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 23 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 8f6431f6..fbf53df6 100644 --- a/src/audio.c +++ b/src/audio.c @@ -102,6 +102,7 @@ typedef struct AudioContext_t { unsigned char channels; // 1=mono,2=stereo unsigned char mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream bool floatingPoint; // if false then the short datatype is used instead + bool playing; ALenum alFormat; // openAL format specifier ALuint alSource; // openAL source ALuint alBuffer[MAX_STREAM_BUFFERS]; // openAL sample buffer @@ -211,7 +212,7 @@ AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChanne else StopMusicStream(); if(!mixChannelsActive_g[mixChannel]){ - AudioContext_t *ac = malloc(sizeof(AudioContext_t)); + AudioContext_t *ac = (AudioContext_t*)malloc(sizeof(AudioContext_t)); ac->sampleRate = sampleRate; ac->channels = channels; ac->mixChannel = mixChannel; @@ -250,8 +251,8 @@ AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChanne FillAlBufferWithSilence(ac, ac->alBuffer[x]); alSourceQueueBuffers(ac->alSource, MAX_STREAM_BUFFERS, ac->alBuffer); - alSourcei(ac->alSource, AL_LOOPING, AL_FALSE); // this could cause errors alSourcePlay(ac->alSource); + ac->playing = true; return ac; } @@ -264,6 +265,7 @@ void CloseAudioContext(AudioContext ctx) AudioContext_t *context = (AudioContext_t*)ctx; if(context){ alSourceStop(context->alSource); + context->playing = false; //flush out all queued buffers ALuint buffer = 0; @@ -287,22 +289,29 @@ void CloseAudioContext(AudioContext ctx) // Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in. // Call "UpdateAudioContext(ctx, NULL, 0)" if you want to pause the audio. // @Returns number of samples that where processed. -// All data streams should be of a length that is evenly divisible by MUSIC_BUFFER_SIZE, -// otherwise the remaining data will not be pushed. unsigned short UpdateAudioContext(AudioContext ctx, void *data, unsigned short numberElements) { AudioContext_t *context = (AudioContext_t*)ctx; - if(context && context->channels == 2 && numberElements % 2 != 0) return 0; // when there is two channels there must be an even number of samples + if(!context || (context->channels == 2 && numberElements % 2 != 0)) return 0; // when there is two channels there must be an even number of samples - if (!data || !numberElements) alSourcePause(context->alSource); // pauses audio until data is given - else{ // restart audio otherwise + if (!data || !numberElements) + { // pauses audio until data is given + alSourcePause(context->alSource); + context->playing = false; + return 0; + } + else + { // restart audio otherwise ALint state; alGetSourcei(context->alSource, AL_SOURCE_STATE, &state); - if (state != AL_PLAYING) alSourcePlay(context->alSource); + if (state != AL_PLAYING){ + alSourcePlay(context->alSource); + context->playing = true; + } } - if (context && mixChannelsActive_g[context->mixChannel] == context) + if (context && context->playing && mixChannelsActive_g[context->mixChannel] == context) { ALint processed = 0; ALuint buffer = 0; @@ -311,36 +320,55 @@ unsigned short UpdateAudioContext(AudioContext ctx, void *data, unsigned short n alGetSourcei(context->alSource, AL_BUFFERS_PROCESSED, &processed); // Get the number of already processed buffers (if any) - if(!processed) return 0;//nothing to process, queue is still full + if(!processed) return 0; // nothing to process, queue is still full + - if(numberRemaining)// buffer data stream in increments of MUSIC_BUFFER_SIZE + while (processed > 0) { - while (processed > 0) + if(context->floatingPoint) // process float buffers { - if(context->floatingPoint && numberRemaining >= MUSIC_BUFFER_SIZE_FLOAT) // process float buffers + float *ptr = (float*)data; + alSourceUnqueueBuffers(context->alSource, 1, &buffer); + if(numberRemaining >= MUSIC_BUFFER_SIZE_FLOAT) { - float *ptr = (float*)data; - alSourceUnqueueBuffers(context->alSource, 1, &buffer); alBufferData(buffer, context->alFormat, &ptr[numberProcessed], MUSIC_BUFFER_SIZE_FLOAT*sizeof(float), context->sampleRate); - alSourceQueueBuffers(context->alSource, 1, &buffer); numberProcessed+=MUSIC_BUFFER_SIZE_FLOAT; numberRemaining-=MUSIC_BUFFER_SIZE_FLOAT; } - else if(!context->floatingPoint && numberRemaining >= MUSIC_BUFFER_SIZE_SHORT) // process short buffers + else { - short *ptr = (short*)data; - alSourceUnqueueBuffers(context->alSource, 1, &buffer); - alBufferData(buffer, context->alFormat, &ptr[numberProcessed], MUSIC_BUFFER_SIZE_SHORT*sizeof(short), context->sampleRate); - alSourceQueueBuffers(context->alSource, 1, &buffer); + alBufferData(buffer, context->alFormat, &ptr[numberProcessed], numberRemaining*sizeof(float), context->sampleRate); + numberProcessed+=numberRemaining; + numberRemaining=0; + } + alSourceQueueBuffers(context->alSource, 1, &buffer); + processed--; + } + else if(!context->floatingPoint) // process short buffers + { + short *ptr = (short*)data; + alSourceUnqueueBuffers(context->alSource, 1, &buffer); + if(numberRemaining >= MUSIC_BUFFER_SIZE_SHORT) + { + alBufferData(buffer, context->alFormat, &ptr[numberProcessed], MUSIC_BUFFER_SIZE_FLOAT*sizeof(short), context->sampleRate); numberProcessed+=MUSIC_BUFFER_SIZE_SHORT; numberRemaining-=MUSIC_BUFFER_SIZE_SHORT; } - + else + { + alBufferData(buffer, context->alFormat, &ptr[numberProcessed], numberRemaining*sizeof(short), context->sampleRate); + numberProcessed+=numberRemaining; + numberRemaining=0; + } + alSourceQueueBuffers(context->alSource, 1, &buffer); processed--; } + else + break; } + return numberProcessed; } - return numberProcessed; + return 0; } // fill buffer with zeros, returns number processed -- cgit v1.2.3 From 6db44500b75097a581587cba15ce703963d2ced8 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Wed, 11 May 2016 00:37:10 -0700 Subject: adding multiple music streams --- src/audio.c | 181 ++++++++++++++++++++++++++--------------------------------- src/audio.h | 9 +-- src/raylib.h | 9 +-- 3 files changed, 91 insertions(+), 108 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index fbf53df6..39be4eeb 100644 --- a/src/audio.c +++ b/src/audio.c @@ -61,6 +61,7 @@ //---------------------------------------------------------------------------------- #define MAX_STREAM_BUFFERS 2 #define MAX_AUDIO_CONTEXTS 4 // Number of open AL sources +#define MAX_MUSIC_STREAMS 2 #if defined(PLATFORM_RPI) || defined(PLATFORM_ANDROID) // NOTE: On RPI and Android should be lower to avoid frame-stalls @@ -81,13 +82,8 @@ typedef struct Music { stb_vorbis *stream; jar_xm_context_t *chipctx; // Stores jar_xm context - - ALuint buffers[MAX_STREAM_BUFFERS]; - ALuint source; - ALenum format; - - int channels; - int sampleRate; + AudioContext_t *ctx; // audio context + int totalSamplesLeft; float totalLengthSeconds; bool loop; @@ -117,8 +113,8 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; //---------------------------------------------------------------------------------- static AudioContext_t* mixChannelsActive_g[MAX_AUDIO_CONTEXTS]; // What mix channels are currently active static bool musicEnabled = false; -static Music currentMusic; // Current music loaded - // NOTE: Only one music file playing at a time +static Music currentMusic[2]; // Current music loaded, up to two can play at the same time + //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- @@ -769,151 +765,129 @@ void SetSoundPitch(Sound sound, float pitch) // Start music playing (open stream) void PlayMusicStream(char *fileName) { + int musicIndex; + int mixIndex; + for(musicIndex = 0; musicIndex < MAX_MUSIC_STREAMS; musicIndex++) // find empty music slot + { + if(currentMusic[musicIndex].stream == NULL && !currentMusic[musicIndex].chipTune) break; + else if(musicIndex = MAX_MUSIC_STREAMS - 1) return; + } + for(mixIndex = 0; mixIndex < MAX_AUDIO_CONTEXTS; mixIndex++) // find empty mix channel slot + { + if(mixChannelsActive_g[mixIndex] == NULL) break; + else if(musicIndex = MAX_AUDIO_CONTEXTS - 1) return; + } + + if (strcmp(GetExtension(fileName),"ogg") == 0) { - // Stop current music, clean buffers, unload current stream - StopMusicStream(); - // Open audio stream - currentMusic.stream = stb_vorbis_open_filename(fileName, NULL, NULL); + currentMusic[musicIndex].stream = stb_vorbis_open_filename(fileName, NULL, NULL); - if (currentMusic.stream == NULL) + if (currentMusic[musicIndex].stream == NULL) { TraceLog(WARNING, "[%s] OGG audio file could not be opened", fileName); } else { // Get file info - stb_vorbis_info info = stb_vorbis_get_info(currentMusic.stream); - - currentMusic.channels = info.channels; - currentMusic.sampleRate = info.sample_rate; + stb_vorbis_info info = stb_vorbis_get_info(currentMusic[musicIndex].stream); TraceLog(INFO, "[%s] Ogg sample rate: %i", fileName, info.sample_rate); TraceLog(INFO, "[%s] Ogg channels: %i", fileName, info.channels); TraceLog(DEBUG, "[%s] Temp memory required: %i", fileName, info.temp_memory_required); - if (info.channels == 2) currentMusic.format = AL_FORMAT_STEREO16; - else currentMusic.format = AL_FORMAT_MONO16; + if (info.channels == 2){ + currentMusic[musicIndex].ctx = InitAudioContext(info.sample_rate, mixIndex, 2, false); + } + else{ + currentMusic[musicIndex].ctx = InitAudioContext(info.sample_rate, mixIndex, 1, false); + } - currentMusic.loop = true; // We loop by default + currentMusic[musicIndex].loop = true; // We loop by default musicEnabled = true; - // Create an audio source - alGenSources(1, ¤tMusic.source); // Generate pointer to audio source - - alSourcef(currentMusic.source, AL_PITCH, 1); - alSourcef(currentMusic.source, AL_GAIN, 1); - alSource3f(currentMusic.source, AL_POSITION, 0, 0, 0); - alSource3f(currentMusic.source, AL_VELOCITY, 0, 0, 0); - //alSourcei(currentMusic.source, AL_LOOPING, AL_TRUE); // ERROR: Buffers do not queue! - - // Generate two OpenAL buffers - alGenBuffers(2, currentMusic.buffers); - - // Fill buffers with music... - BufferMusicStream(currentMusic.buffers[0]); - BufferMusicStream(currentMusic.buffers[1]); - - // Queue buffers and start playing - alSourceQueueBuffers(currentMusic.source, 2, currentMusic.buffers); - alSourcePlay(currentMusic.source); - - // NOTE: Regularly, we must check if a buffer has been processed and refill it: UpdateMusicStream() - - currentMusic.totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels; - currentMusic.totalLengthSeconds = stb_vorbis_stream_length_in_seconds(currentMusic.stream); + currentMusic[musicIndex].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[musicIndex].stream) * currentMusic[musicIndex].channels; + currentMusic[musicIndex].totalLengthSeconds = stb_vorbis_stream_length_in_seconds(currentMusic[musicIndex].stream); } } else if (strcmp(GetExtension(fileName),"xm") == 0) { - // Stop current music, clean buffers, unload current stream - StopMusicStream(); - - // new song settings for xm chiptune - currentMusic.chipTune = true; - currentMusic.channels = 2; - currentMusic.sampleRate = 48000; - currentMusic.loop = true; - // only stereo is supported for xm - if(!jar_xm_create_context_from_file(¤tMusic.chipctx, currentMusic.sampleRate, fileName)) + if(!jar_xm_create_context_from_file(¤tMusic[musicIndex].chipctx, 48000, fileName)) { - currentMusic.format = AL_FORMAT_STEREO16; - jar_xm_set_max_loop_count(currentMusic.chipctx, 0); // infinite number of loops - currentMusic.totalSamplesLeft = jar_xm_get_remaining_samples(currentMusic.chipctx); - currentMusic.totalLengthSeconds = ((float)currentMusic.totalSamplesLeft) / ((float)currentMusic.sampleRate); + currentMusic[musicIndex].chipTune = true; + currentMusic[musicIndex].loop = true; + jar_xm_set_max_loop_count(currentMusic[musicIndex].chipctx, 0); // infinite number of loops + currentMusic[musicIndex].totalSamplesLeft = jar_xm_get_remaining_samples(currentMusic[musicIndex].chipctx); + currentMusic[musicIndex].totalLengthSeconds = ((float)currentMusic[musicIndex].totalSamplesLeft) / ((float)currentMusic[musicIndex].sampleRate); musicEnabled = true; - TraceLog(INFO, "[%s] XM number of samples: %i", fileName, currentMusic.totalSamplesLeft); - TraceLog(INFO, "[%s] XM track length: %11.6f sec", fileName, currentMusic.totalLengthSeconds); - - // Set up OpenAL - alGenSources(1, ¤tMusic.source); - alSourcef(currentMusic.source, AL_PITCH, 1); - alSourcef(currentMusic.source, AL_GAIN, 1); - alSource3f(currentMusic.source, AL_POSITION, 0, 0, 0); - alSource3f(currentMusic.source, AL_VELOCITY, 0, 0, 0); - alGenBuffers(2, currentMusic.buffers); - BufferMusicStream(currentMusic.buffers[0]); - BufferMusicStream(currentMusic.buffers[1]); - alSourceQueueBuffers(currentMusic.source, 2, currentMusic.buffers); - alSourcePlay(currentMusic.source); + TraceLog(INFO, "[%s] XM number of samples: %i", fileName, currentMusic[musicIndex].totalSamplesLeft); + TraceLog(INFO, "[%s] XM track length: %11.6f sec", fileName, currentMusic[musicIndex].totalLengthSeconds); - // NOTE: Regularly, we must check if a buffer has been processed and refill it: UpdateMusicStream() + currentMusic[musicIndex].ctx = InitAudioContext(48000, mixIndex, 2, true); } else TraceLog(WARNING, "[%s] XM file could not be opened", fileName); } else TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName); } -// Stop music playing (close stream) -void StopMusicStream(void) +// Stop music playing for individual music index of currentMusic array (close stream) +void StopMusicStream(int index) { - if (musicEnabled) + if (index < MAX_MUSIC_STREAMS && currentMusic[index].ctx) { - alSourceStop(currentMusic.source); - EmptyMusicStream(); // Empty music buffers - alDeleteSources(1, ¤tMusic.source); - alDeleteBuffers(2, currentMusic.buffers); + CloseAudioContext(currentMusic[index].ctx); - if (currentMusic.chipTune) + if (currentMusic[index].chipTune) { - jar_xm_free_context(currentMusic.chipctx); + jar_xm_free_context(currentMusic[index].chipctx); } else { - stb_vorbis_close(currentMusic.stream); + stb_vorbis_close(currentMusic[index].stream); } + + if(!getMusicStreamCount()) musicEnabled = false; } +} - musicEnabled = false; +//get number of music channels active at this time, this does not mean they are playing +int getMusicStreamCount(void) +{ + int musicCount; + for(int musicIndex = 0; musicIndex < MAX_MUSIC_STREAMS; musicIndex++) // find empty music slot + if(currentMusic[musicIndex].stream != NULL || currentMusic[musicIndex].chipTune) musicCount++; + + return musicCount; } // Pause music playing -void PauseMusicStream(void) +void PauseMusicStream(int index) { // Pause music stream if music available! if (musicEnabled) { TraceLog(INFO, "Pausing music stream"); - alSourcePause(currentMusic.source); + UpdateAudioContext(currentMusic[index].ctx, NULL, 0); // pushing null data auto pauses stream musicEnabled = false; } } // Resume music playing -void ResumeMusicStream(void) +void ResumeMusicStream(int index) { // Resume music playing... if music available! ALenum state; - alGetSourcei(currentMusic.source, AL_SOURCE_STATE, &state); - - if (state == AL_PAUSED) - { - TraceLog(INFO, "Resuming music stream"); - alSourcePlay(currentMusic.source); - musicEnabled = true; + if(currentMusic[musicIndex].ctx){ + alGetSourcei(currentMusic[musicIndex].ctx->alSource, AL_SOURCE_STATE, &state); + if (state == AL_PAUSED) + { + TraceLog(INFO, "Resuming music stream"); + alSourcePlay(currentMusic[musicIndex].ctx->alSource); + musicEnabled = true; + } } } @@ -922,17 +896,24 @@ bool IsMusicPlaying(void) { bool playing = false; ALint state; - - alGetSourcei(currentMusic.source, AL_SOURCE_STATE, &state); - if (state == AL_PLAYING) playing = true; + + for(int musicIndex = 0; musicIndex < MAX_MUSIC_STREAMS; musicIndex++) + { + if(currentMusic[musicIndex].ctx){ + alGetSourcei(currentMusic[musicIndex].ctx->alSource, AL_SOURCE_STATE, &state); + if (state == AL_PLAYING) playing = true; + } + } return playing; } // Set volume for music -void SetMusicVolume(float volume) +void SetMusicVolume(int index, float volume) { - alSourcef(currentMusic.source, AL_GAIN, volume); + if(currentMusic[musicIndex].ctx){ + alSourcef(currentMusic[musicIndex].ctx->alSource, AL_GAIN, volume); + } } // Get current music time length (in seconds) diff --git a/src/audio.h b/src/audio.h index afd881b7..a1602bd9 100644 --- a/src/audio.h +++ b/src/audio.h @@ -102,13 +102,14 @@ void SetSoundPitch(Sound sound, float pitch); // Set pitch for void PlayMusicStream(char *fileName); // Start music playing (open stream) void UpdateMusicStream(void); // Updates buffers for music streaming -void StopMusicStream(void); // Stop music playing (close stream) -void PauseMusicStream(void); // Pause music playing -void ResumeMusicStream(void); // Resume playing paused music +void StopMusicStream(int index); // Stop music playing (close stream) +void PauseMusicStream(int index); // Pause music playing +void ResumeMusicStream(int index); // Resume playing paused music bool IsMusicPlaying(void); // Check if music is playing -void SetMusicVolume(float volume); // Set volume for music (1.0 is max level) +void SetMusicVolume(int index, float volume); // Set volume for music (1.0 is max level) float GetMusicTimeLength(void); // Get music time length (in seconds) float GetMusicTimePlayed(void); // Get current music time played (in seconds) +int getMusicStreamCount(void); #ifdef __cplusplus } diff --git a/src/raylib.h b/src/raylib.h index d464c8e9..0c49a085 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -893,13 +893,14 @@ void SetSoundPitch(Sound sound, float pitch); // Set pitch for void PlayMusicStream(char *fileName); // Start music playing (open stream) void UpdateMusicStream(void); // Updates buffers for music streaming -void StopMusicStream(void); // Stop music playing (close stream) -void PauseMusicStream(void); // Pause music playing -void ResumeMusicStream(void); // Resume playing paused music +void StopMusicStream(int index); // Stop music playing (close stream) +void PauseMusicStream(int index); // Pause music playing +void ResumeMusicStream(int index); // Resume playing paused music bool IsMusicPlaying(void); // Check if music is playing -void SetMusicVolume(float volume); // Set volume for music (1.0 is max level) +void SetMusicVolume(int index, float volume); // Set volume for music (1.0 is max level) float GetMusicTimeLength(void); // Get current music time length (in seconds) float GetMusicTimePlayed(void); // Get current music time played (in seconds) +int getMusicStreamCount(void); #ifdef __cplusplus } -- cgit v1.2.3 From ad3d270c429844462f3fd62fa12257760fac24b5 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Wed, 11 May 2016 18:14:59 -0700 Subject: added set pitch for music streams --- src/audio.c | 40 ++++++++++++++++++++++------------------ src/audio.h | 3 ++- src/raylib.h | 3 ++- 3 files changed, 26 insertions(+), 20 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 39be4eeb..f89be8bf 100644 --- a/src/audio.c +++ b/src/audio.c @@ -113,7 +113,7 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; //---------------------------------------------------------------------------------- static AudioContext_t* mixChannelsActive_g[MAX_AUDIO_CONTEXTS]; // What mix channels are currently active static bool musicEnabled = false; -static Music currentMusic[2]; // Current music loaded, up to two can play at the same time +static Music currentMusic[MAX_MUSIC_STREAMS]; // Current music loaded, up to two can play at the same time //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -797,18 +797,18 @@ void PlayMusicStream(char *fileName) TraceLog(INFO, "[%s] Ogg channels: %i", fileName, info.channels); TraceLog(DEBUG, "[%s] Temp memory required: %i", fileName, info.temp_memory_required); + currentMusic[musicIndex].loop = true; // We loop by default + musicEnabled = true; + + currentMusic[musicIndex].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[musicIndex].stream) * info.channels; + currentMusic[musicIndex].totalLengthSeconds = stb_vorbis_stream_length_in_seconds(currentMusic[musicIndex].stream); + if (info.channels == 2){ currentMusic[musicIndex].ctx = InitAudioContext(info.sample_rate, mixIndex, 2, false); } else{ currentMusic[musicIndex].ctx = InitAudioContext(info.sample_rate, mixIndex, 1, false); } - - currentMusic[musicIndex].loop = true; // We loop by default - musicEnabled = true; - - currentMusic[musicIndex].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[musicIndex].stream) * currentMusic[musicIndex].channels; - currentMusic[musicIndex].totalLengthSeconds = stb_vorbis_stream_length_in_seconds(currentMusic[musicIndex].stream); } } else if (strcmp(GetExtension(fileName),"xm") == 0) @@ -820,7 +820,7 @@ void PlayMusicStream(char *fileName) currentMusic[musicIndex].loop = true; jar_xm_set_max_loop_count(currentMusic[musicIndex].chipctx, 0); // infinite number of loops currentMusic[musicIndex].totalSamplesLeft = jar_xm_get_remaining_samples(currentMusic[musicIndex].chipctx); - currentMusic[musicIndex].totalLengthSeconds = ((float)currentMusic[musicIndex].totalSamplesLeft) / ((float)currentMusic[musicIndex].sampleRate); + currentMusic[musicIndex].totalLengthSeconds = ((float)currentMusic[musicIndex].totalSamplesLeft) / 48000.f; musicEnabled = true; TraceLog(INFO, "[%s] XM number of samples: %i", fileName, currentMusic[musicIndex].totalSamplesLeft); @@ -891,18 +891,15 @@ void ResumeMusicStream(int index) } } -// Check if music is playing -bool IsMusicPlaying(void) +// Check if any music is playing +bool IsMusicPlaying(int index) { bool playing = false; ALint state; - for(int musicIndex = 0; musicIndex < MAX_MUSIC_STREAMS; musicIndex++) - { - if(currentMusic[musicIndex].ctx){ - alGetSourcei(currentMusic[musicIndex].ctx->alSource, AL_SOURCE_STATE, &state); - if (state == AL_PLAYING) playing = true; - } + if(index < MAX_MUSIC_STREAMS && currentMusic[index].ctx){ + alGetSourcei(currentMusic[index].ctx->alSource, AL_SOURCE_STATE, &state); + if (state == AL_PLAYING) playing = true; } return playing; @@ -911,8 +908,15 @@ bool IsMusicPlaying(void) // Set volume for music void SetMusicVolume(int index, float volume) { - if(currentMusic[musicIndex].ctx){ - alSourcef(currentMusic[musicIndex].ctx->alSource, AL_GAIN, volume); + if(index < MAX_MUSIC_STREAMS && currentMusic[index].ctx){ + alSourcef(currentMusic[index].ctx->alSource, AL_GAIN, volume); + } +} + +void SetMusicPitch(int index, float pitch) +{ + if(index < MAX_MUSIC_STREAMS && currentMusic[index].ctx){ + alSourcef(currentMusic[index].ctx->alSource, AL_PITCH, pitch); } } diff --git a/src/audio.h b/src/audio.h index a1602bd9..fef85b4f 100644 --- a/src/audio.h +++ b/src/audio.h @@ -105,11 +105,12 @@ void UpdateMusicStream(void); // Updates buffe void StopMusicStream(int index); // Stop music playing (close stream) void PauseMusicStream(int index); // Pause music playing void ResumeMusicStream(int index); // Resume playing paused music -bool IsMusicPlaying(void); // Check if music is playing +bool IsMusicPlaying(int index); // Check if music is playing void SetMusicVolume(int index, float volume); // Set volume for music (1.0 is max level) float GetMusicTimeLength(void); // Get music time length (in seconds) float GetMusicTimePlayed(void); // Get current music time played (in seconds) int getMusicStreamCount(void); +void SetMusicPitch(int index, float pitch); #ifdef __cplusplus } diff --git a/src/raylib.h b/src/raylib.h index 0c49a085..b9390d31 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -896,11 +896,12 @@ void UpdateMusicStream(void); // Updates buffe void StopMusicStream(int index); // Stop music playing (close stream) void PauseMusicStream(int index); // Pause music playing void ResumeMusicStream(int index); // Resume playing paused music -bool IsMusicPlaying(void); // Check if music is playing +bool IsMusicPlaying(int index); // Check if music is playing void SetMusicVolume(int index, float volume); // Set volume for music (1.0 is max level) float GetMusicTimeLength(void); // Get current music time length (in seconds) float GetMusicTimePlayed(void); // Get current music time played (in seconds) int getMusicStreamCount(void); +void SetMusicPitch(int index, float pitch); #ifdef __cplusplus } -- cgit v1.2.3 From 9737c58054d5d0cc636fca0c998b31a69d501970 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Wed, 11 May 2016 20:15:37 -0700 Subject: PlayMusicStream now uses index --- src/audio.c | 29 +++++++++++++++++++---------- src/audio.h | 2 +- src/raylib.h | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index f89be8bf..6a8d251e 100644 --- a/src/audio.c +++ b/src/audio.c @@ -763,19 +763,18 @@ void SetSoundPitch(Sound sound, float pitch) //---------------------------------------------------------------------------------- // Start music playing (open stream) -void PlayMusicStream(char *fileName) +// returns 0 on success +int PlayMusicStream(int musicIndex, char *fileName) { - int musicIndex; + int musicIndex = index; int mixIndex; - for(musicIndex = 0; musicIndex < MAX_MUSIC_STREAMS; musicIndex++) // find empty music slot - { - if(currentMusic[musicIndex].stream == NULL && !currentMusic[musicIndex].chipTune) break; - else if(musicIndex = MAX_MUSIC_STREAMS - 1) return; - } + + if(currentMusic[musicIndex].stream != NULL || currentMusic[musicIndex].chipTune) return 1; // error + for(mixIndex = 0; mixIndex < MAX_AUDIO_CONTEXTS; mixIndex++) // find empty mix channel slot { if(mixChannelsActive_g[mixIndex] == NULL) break; - else if(musicIndex = MAX_AUDIO_CONTEXTS - 1) return; + else if(musicIndex = MAX_AUDIO_CONTEXTS - 1) return 2; // error } @@ -787,6 +786,7 @@ void PlayMusicStream(char *fileName) if (currentMusic[musicIndex].stream == NULL) { TraceLog(WARNING, "[%s] OGG audio file could not be opened", fileName); + return 3; // error } else { @@ -828,9 +828,18 @@ void PlayMusicStream(char *fileName) currentMusic[musicIndex].ctx = InitAudioContext(48000, mixIndex, 2, true); } - else TraceLog(WARNING, "[%s] XM file could not be opened", fileName); + else + { + TraceLog(WARNING, "[%s] XM file could not be opened", fileName); + return 4; // error + } + } + else + { + TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName); + return 5; // error } - else TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName); + return 0; // normal return } // Stop music playing for individual music index of currentMusic array (close stream) diff --git a/src/audio.h b/src/audio.h index fef85b4f..d09c4acc 100644 --- a/src/audio.h +++ b/src/audio.h @@ -100,7 +100,7 @@ bool IsSoundPlaying(Sound sound); // Check if a so void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) -void PlayMusicStream(char *fileName); // Start music playing (open stream) +int PlayMusicStream(int musicIndex, char *fileName); // Start music playing (open stream) void UpdateMusicStream(void); // Updates buffers for music streaming void StopMusicStream(int index); // Stop music playing (close stream) void PauseMusicStream(int index); // Pause music playing diff --git a/src/raylib.h b/src/raylib.h index a6507906..cb17aa78 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -894,7 +894,7 @@ bool IsSoundPlaying(Sound sound); // Check if a so void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) -void PlayMusicStream(char *fileName); // Start music playing (open stream) +int PlayMusicStream(int musicIndex, char *fileName); // Start music playing (open stream) void UpdateMusicStream(void); // Updates buffers for music streaming void StopMusicStream(int index); // Stop music playing (close stream) void PauseMusicStream(int index); // Pause music playing -- cgit v1.2.3 From f0ada8c40d7887654d05c62f980a0a5473c1d8a7 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Wed, 11 May 2016 22:37:53 -0700 Subject: apply index to remaining functions --- src/audio.c | 108 +++++++++++++++++++++++++++++++---------------------------- src/audio.h | 6 ++-- src/raylib.h | 6 ++-- 3 files changed, 62 insertions(+), 58 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 6a8d251e..94729566 100644 --- a/src/audio.c +++ b/src/audio.c @@ -118,12 +118,12 @@ static Music currentMusic[MAX_MUSIC_STREAMS]; // Current //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- -static Wave LoadWAV(const char *fileName); // Load WAV file -static Wave LoadOGG(char *fileName); // Load OGG file -static void UnloadWave(Wave wave); // Unload wave data +static Wave LoadWAV(const char *fileName); // Load WAV file +static Wave LoadOGG(char *fileName); // Load OGG file +static void UnloadWave(Wave wave); // Unload wave data -static bool BufferMusicStream(ALuint buffer); // Fill music buffers with data -static void EmptyMusicStream(void); // Empty music buffers +static bool BufferMusicStream(int index, ALuint buffer); // Fill music buffers with data +static void EmptyMusicStream(int index); // Empty music buffers static unsigned short FillAlBufferWithSilence(AudioContext_t *context, ALuint buffer);// fill buffer with zeros, returns number processed static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // pass two arrays of the same legnth in @@ -766,7 +766,6 @@ void SetSoundPitch(Sound sound, float pitch) // returns 0 on success int PlayMusicStream(int musicIndex, char *fileName) { - int musicIndex = index; int mixIndex; if(currentMusic[musicIndex].stream != NULL || currentMusic[musicIndex].chipTune) return 1; // error @@ -930,36 +929,36 @@ void SetMusicPitch(int index, float pitch) } // Get current music time length (in seconds) -float GetMusicTimeLength(void) +float GetMusicTimeLength(int index) { float totalSeconds; - if (currentMusic.chipTune) + if (currentMusic[index].chipTune) { - totalSeconds = currentMusic.totalLengthSeconds; + totalSeconds = currentMusic[index].totalLengthSeconds; } else { - totalSeconds = stb_vorbis_stream_length_in_seconds(currentMusic.stream); + totalSeconds = stb_vorbis_stream_length_in_seconds(currentMusic[index].stream); } return totalSeconds; } // Get current music time played (in seconds) -float GetMusicTimePlayed(void) +float GetMusicTimePlayed(int index) { float secondsPlayed; - if (currentMusic.chipTune) + if (currentMusic[index].chipTune) { uint64_t samples; - jar_xm_get_position(currentMusic.chipctx, NULL, NULL, NULL, &samples); - secondsPlayed = (float)samples / (currentMusic.sampleRate * currentMusic.channels); // Not sure if this is the correct value + jar_xm_get_position(currentMusic[index].chipctx, NULL, NULL, NULL, &samples); + secondsPlayed = (float)samples / (48000 * currentMusic[index].ctx->channels); // Not sure if this is the correct value } else { - int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels; - int samplesPlayed = totalSamples - currentMusic.totalSamplesLeft; - secondsPlayed = (float)samplesPlayed / (currentMusic.sampleRate * currentMusic.channels); + int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic[index].stream) * currentMusic[index].ctx->channels; + int samplesPlayed = totalSamples - currentMusic[index].totalSamplesLeft; + secondsPlayed = (float)samplesPlayed / (currentMusic[index].ctx->sampleRate * currentMusic[index].ctx->channels); } @@ -971,9 +970,10 @@ float GetMusicTimePlayed(void) //---------------------------------------------------------------------------------- // Fill music buffers with new data from music stream -static bool BufferMusicStream(ALuint buffer) +static bool BufferMusicStream(int index, ALuint buffer) { short pcm[MUSIC_BUFFER_SIZE_SHORT]; + float pcmf[MUSIC_BUFFER_SIZE_FLOAT]; int size = 0; // Total size of data steamed (in bytes) int streamedBytes = 0; // samples of data obtained, channels are not included in calculation @@ -981,93 +981,97 @@ static bool BufferMusicStream(ALuint buffer) if (musicEnabled) { - if (currentMusic.chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. + if (currentMusic[index].chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. { - int readlen = MUSIC_BUFFER_SIZE_SHORT / 2; - jar_xm_generate_samples_16bit(currentMusic.chipctx, pcm, readlen); // reads 2*readlen shorts and moves them to buffer+size memory location - size += readlen * currentMusic.channels; // Not sure if this is what it needs + int readlen = MUSIC_BUFFER_SIZE_FLOAT / 2; + jar_xm_generate_samples(currentMusic[index].chipctx, pcmf, readlen); // reads 2*readlen shorts and moves them to buffer+size memory location + size += readlen * currentMusic[index].ctx->channels; // Not sure if this is what it needs + + alBufferData(buffer, currentMusic[index].ctx->alFormat, pcmf, size*sizeof(float), 48000); + currentMusic[index].totalSamplesLeft -= size; + if(currentMusic[index].totalSamplesLeft <= 0) active = false; } else { while (size < MUSIC_BUFFER_SIZE_SHORT) { - streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic.stream, currentMusic.channels, pcm + size, MUSIC_BUFFER_SIZE_SHORT - size); - if (streamedBytes > 0) size += (streamedBytes*currentMusic.channels); + streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic[index].stream, currentMusic[index].ctx->channels, pcm + size, MUSIC_BUFFER_SIZE_SHORT - size); + if (streamedBytes > 0) size += (streamedBytes*currentMusic[index].ctx->channels); else break; } + + if (size > 0) + { + alBufferData(buffer, currentMusic[index].ctx->alFormat, pcm, size*sizeof(short), currentMusic[index].ctx->sampleRate); + currentMusic[index].totalSamplesLeft -= size; + + if(currentMusic[index].totalSamplesLeft <= 0) active = false; // end if no more samples left + } + else + { + active = false; + TraceLog(WARNING, "No more data obtained from stream"); + } } TraceLog(DEBUG, "Streaming music data to buffer. Bytes streamed: %i", size); } - if (size > 0) - { - alBufferData(buffer, currentMusic.format, pcm, size*sizeof(short), currentMusic.sampleRate); - currentMusic.totalSamplesLeft -= size; - - if(currentMusic.totalSamplesLeft <= 0) active = false; // end if no more samples left - } - else - { - active = false; - TraceLog(WARNING, "No more data obtained from stream"); - } - return active; } // Empty music buffers -static void EmptyMusicStream(void) +static void EmptyMusicStream(int index) { ALuint buffer = 0; int queued = 0; - alGetSourcei(currentMusic.source, AL_BUFFERS_QUEUED, &queued); + alGetSourcei(currentMusic[index].source, AL_BUFFERS_QUEUED, &queued); while (queued > 0) { - alSourceUnqueueBuffers(currentMusic.source, 1, &buffer); + alSourceUnqueueBuffers(currentMusic[index].source, 1, &buffer); queued--; } } // Update (re-fill) music buffers if data already processed -void UpdateMusicStream(void) +void UpdateMusicStream(int index) { ALuint buffer = 0; ALint processed = 0; bool active = true; - if (musicEnabled) + if (index < MAX_MUSIC_STREAMS && musicEnabled) { // Get the number of already processed buffers (if any) - alGetSourcei(currentMusic.source, AL_BUFFERS_PROCESSED, &processed); + alGetSourcei(currentMusic[index].source, AL_BUFFERS_PROCESSED, &processed); while (processed > 0) { // Recover processed buffer for refill - alSourceUnqueueBuffers(currentMusic.source, 1, &buffer); + alSourceUnqueueBuffers(currentMusic[index].source, 1, &buffer); // Refill buffer active = BufferMusicStream(buffer); // If no more data to stream, restart music (if loop) - if ((!active) && (currentMusic.loop)) + if ((!active) && (currentMusic[index].loop)) { - if(currentMusic.chipTune) + if(currentMusic[index].chipTune) { - currentMusic.totalSamplesLeft = currentMusic.totalLengthSeconds * currentMusic.sampleRate; + currentMusic[index].totalSamplesLeft = currentMusic[index].totalLengthSeconds * currentMusic[index].ctx->sampleRate; } else { - stb_vorbis_seek_start(currentMusic.stream); - currentMusic.totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic.stream)*currentMusic.channels; + stb_vorbis_seek_start(currentMusic[index].stream); + currentMusic[index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[index].stream)*currentMusic[index].ctx->channels; } active = BufferMusicStream(buffer); } // Add refilled buffer to queue again... don't let the music stop! - alSourceQueueBuffers(currentMusic.source, 1, &buffer); + alSourceQueueBuffers(currentMusic[index].source, 1, &buffer); if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Error buffering data..."); @@ -1075,9 +1079,9 @@ void UpdateMusicStream(void) } ALenum state; - alGetSourcei(currentMusic.source, AL_SOURCE_STATE, &state); + alGetSourcei(currentMusic[index].source, AL_SOURCE_STATE, &state); - if ((state != AL_PLAYING) && active) alSourcePlay(currentMusic.source); + if ((state != AL_PLAYING) && active) alSourcePlay(currentMusic[index].source); if (!active) StopMusicStream(); } diff --git a/src/audio.h b/src/audio.h index d09c4acc..63c1c136 100644 --- a/src/audio.h +++ b/src/audio.h @@ -101,14 +101,14 @@ void SetSoundVolume(Sound sound, float volume); // Set volume fo void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) int PlayMusicStream(int musicIndex, char *fileName); // Start music playing (open stream) -void UpdateMusicStream(void); // Updates buffers for music streaming +void UpdateMusicStream(int index); // Updates buffers for music streaming void StopMusicStream(int index); // Stop music playing (close stream) void PauseMusicStream(int index); // Pause music playing void ResumeMusicStream(int index); // Resume playing paused music bool IsMusicPlaying(int index); // Check if music is playing void SetMusicVolume(int index, float volume); // Set volume for music (1.0 is max level) -float GetMusicTimeLength(void); // Get music time length (in seconds) -float GetMusicTimePlayed(void); // Get current music time played (in seconds) +float GetMusicTimeLength(int index); // Get music time length (in seconds) +float GetMusicTimePlayed(int index); // Get current music time played (in seconds) int getMusicStreamCount(void); void SetMusicPitch(int index, float pitch); diff --git a/src/raylib.h b/src/raylib.h index cb17aa78..05c945f7 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -895,14 +895,14 @@ void SetSoundVolume(Sound sound, float volume); // Set volume fo void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) int PlayMusicStream(int musicIndex, char *fileName); // Start music playing (open stream) -void UpdateMusicStream(void); // Updates buffers for music streaming +void UpdateMusicStream(int index); // Updates buffers for music streaming void StopMusicStream(int index); // Stop music playing (close stream) void PauseMusicStream(int index); // Pause music playing void ResumeMusicStream(int index); // Resume playing paused music bool IsMusicPlaying(int index); // Check if music is playing void SetMusicVolume(int index, float volume); // Set volume for music (1.0 is max level) -float GetMusicTimeLength(void); // Get current music time length (in seconds) -float GetMusicTimePlayed(void); // Get current music time played (in seconds) +float GetMusicTimeLength(int index); // Get current music time length (in seconds) +float GetMusicTimePlayed(int index); // Get current music time played (in seconds) int getMusicStreamCount(void); void SetMusicPitch(int index, float pitch); -- cgit v1.2.3 From 83dbc076507b7025aa0a1315e409ff46f75c1e0b Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Thu, 12 May 2016 16:02:23 -0700 Subject: buffering of music now uses update audio context --- src/audio.c | 94 ++++++++++++++++++++--------------------------------------- src/easings.h | 10 +++---- 2 files changed, 37 insertions(+), 67 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 94729566..cc964cd1 100644 --- a/src/audio.c +++ b/src/audio.c @@ -118,12 +118,12 @@ static Music currentMusic[MAX_MUSIC_STREAMS]; // Current //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- -static Wave LoadWAV(const char *fileName); // Load WAV file -static Wave LoadOGG(char *fileName); // Load OGG file -static void UnloadWave(Wave wave); // Unload wave data +static Wave LoadWAV(const char *fileName); // Load WAV file +static Wave LoadOGG(char *fileName); // Load OGG file +static void UnloadWave(Wave wave); // Unload wave data -static bool BufferMusicStream(int index, ALuint buffer); // Fill music buffers with data -static void EmptyMusicStream(int index); // Empty music buffers +static bool BufferMusicStream(int index); // Fill music buffers with data +static void EmptyMusicStream(int index); // Empty music buffers static unsigned short FillAlBufferWithSilence(AudioContext_t *context, ALuint buffer);// fill buffer with zeros, returns number processed static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // pass two arrays of the same legnth in @@ -970,7 +970,7 @@ float GetMusicTimePlayed(int index) //---------------------------------------------------------------------------------- // Fill music buffers with new data from music stream -static bool BufferMusicStream(int index, ALuint buffer) +static bool BufferMusicStream(int index) { short pcm[MUSIC_BUFFER_SIZE_SHORT]; float pcmf[MUSIC_BUFFER_SIZE_FLOAT]; @@ -985,33 +985,17 @@ static bool BufferMusicStream(int index, ALuint buffer) { int readlen = MUSIC_BUFFER_SIZE_FLOAT / 2; jar_xm_generate_samples(currentMusic[index].chipctx, pcmf, readlen); // reads 2*readlen shorts and moves them to buffer+size memory location + UpdateAudioContext(currentMusic[index].ctx, pcmf, MUSIC_BUFFER_SIZE_FLOAT); size += readlen * currentMusic[index].ctx->channels; // Not sure if this is what it needs - - alBufferData(buffer, currentMusic[index].ctx->alFormat, pcmf, size*sizeof(float), 48000); currentMusic[index].totalSamplesLeft -= size; if(currentMusic[index].totalSamplesLeft <= 0) active = false; } else { - while (size < MUSIC_BUFFER_SIZE_SHORT) - { - streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic[index].stream, currentMusic[index].ctx->channels, pcm + size, MUSIC_BUFFER_SIZE_SHORT - size); - if (streamedBytes > 0) size += (streamedBytes*currentMusic[index].ctx->channels); - else break; - } - - if (size > 0) - { - alBufferData(buffer, currentMusic[index].ctx->alFormat, pcm, size*sizeof(short), currentMusic[index].ctx->sampleRate); - currentMusic[index].totalSamplesLeft -= size; - - if(currentMusic[index].totalSamplesLeft <= 0) active = false; // end if no more samples left - } - else - { - active = false; - TraceLog(WARNING, "No more data obtained from stream"); - } + streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic[index].stream, currentMusic[index].ctx->channels, pcm, MUSIC_BUFFER_SIZE_SHORT); + UpdateAudioContext(currentMusic[index].ctx, pcm, MUSIC_BUFFER_SIZE_SHORT); + currentMusic[index].totalSamplesLeft -= MUSIC_BUFFER_SIZE_SHORT; + if(currentMusic[index].totalSamplesLeft <= 0) active = false; } TraceLog(DEBUG, "Streaming music data to buffer. Bytes streamed: %i", size); } @@ -1038,53 +1022,39 @@ static void EmptyMusicStream(int index) // Update (re-fill) music buffers if data already processed void UpdateMusicStream(int index) { - ALuint buffer = 0; - ALint processed = 0; + ALenum state; bool active = true; if (index < MAX_MUSIC_STREAMS && musicEnabled) { - // Get the number of already processed buffers (if any) - alGetSourcei(currentMusic[index].source, AL_BUFFERS_PROCESSED, &processed); - - while (processed > 0) + active = BufferMusicStream(index); + + if ((!active) && (currentMusic[index].loop)) { - // Recover processed buffer for refill - alSourceUnqueueBuffers(currentMusic[index].source, 1, &buffer); - - // Refill buffer - active = BufferMusicStream(buffer); - - // If no more data to stream, restart music (if loop) - if ((!active) && (currentMusic[index].loop)) + if(currentMusic[index].chipTune) { - if(currentMusic[index].chipTune) - { - currentMusic[index].totalSamplesLeft = currentMusic[index].totalLengthSeconds * currentMusic[index].ctx->sampleRate; - } - else - { - stb_vorbis_seek_start(currentMusic[index].stream); - currentMusic[index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[index].stream)*currentMusic[index].ctx->channels; - } - active = BufferMusicStream(buffer); + currentMusic[index].totalSamplesLeft = currentMusic[index].totalLengthSeconds * currentMusic[index].ctx->sampleRate; } + else + { + stb_vorbis_seek_start(currentMusic[index].stream); + currentMusic[index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[index].stream)*currentMusic[index].ctx->channels; + } + active = BufferMusicStream(index); + } + - // Add refilled buffer to queue again... don't let the music stop! - alSourceQueueBuffers(currentMusic[index].source, 1, &buffer); - - if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Error buffering data..."); + if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Error buffering data..."); - processed--; - } + processed--; + } - ALenum state; - alGetSourcei(currentMusic[index].source, AL_SOURCE_STATE, &state); + + alGetSourcei(currentMusic[index].source, AL_SOURCE_STATE, &state); - if ((state != AL_PLAYING) && active) alSourcePlay(currentMusic[index].source); + if ((state != AL_PLAYING) && active) alSourcePlay(currentMusic[index].source); - if (!active) StopMusicStream(); - } + if (!active) StopMusicStream(); } // Load WAV file into Wave structure diff --git a/src/easings.h b/src/easings.h index e1e5465a..a8178f4a 100644 --- a/src/easings.h +++ b/src/easings.h @@ -18,11 +18,11 @@ * float speed = 1.f; * float currentTime = 0.f; * float currentPos[2] = {0,0}; -* float newPos[2] = {1,1}; -* float tempPosition[2] = currentPos;//x,y positions -* while(currentPos[0] < newPos[0]) -* currentPos[0] = EaseSineIn(currentTime, tempPosition[0], tempPosition[0]-newPos[0], speed); -* currentPos[1] = EaseSineIn(currentTime, tempPosition[1], tempPosition[1]-newPos[0], speed); +* float finalPos[2] = {1,1}; +* float startPosition[2] = currentPos;//x,y positions +* while(currentPos[0] < finalPos[0]) +* currentPos[0] = EaseSineIn(currentTime, startPosition[0], startPosition[0]-finalPos[0], speed); +* currentPos[1] = EaseSineIn(currentTime, startPosition[1], startPosition[1]-finalPos[0], speed); * currentTime += diffTime(); * * A port of Robert Penner's easing equations to C (http://robertpenner.com/easing/) -- cgit v1.2.3 From 5107a2dc40330623f61205ee70f067d24afb0af8 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Thu, 12 May 2016 21:14:02 -0700 Subject: bug fixes --- src/audio.c | 69 +++++++++++++++++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 34 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index cc964cd1..d19d3306 100644 --- a/src/audio.c +++ b/src/audio.c @@ -77,19 +77,6 @@ // Types and Structures Definition //---------------------------------------------------------------------------------- -// Music type (file streaming from memory) -// NOTE: Anything longer than ~10 seconds should be streamed... -typedef struct Music { - stb_vorbis *stream; - jar_xm_context_t *chipctx; // Stores jar_xm context - AudioContext_t *ctx; // audio context - - int totalSamplesLeft; - float totalLengthSeconds; - bool loop; - bool chipTune; // True if chiptune is loaded -} Music; - // Audio Context, used to create custom audio streams that are not bound to a sound file. There can be // no more than 4 concurrent audio contexts in use. This is due to each active context being tied to // a dedicated mix channel. All audio is 32bit floating point in stereo. @@ -104,6 +91,19 @@ typedef struct AudioContext_t { ALuint alBuffer[MAX_STREAM_BUFFERS]; // openAL sample buffer } AudioContext_t; +// Music type (file streaming from memory) +// NOTE: Anything longer than ~10 seconds should be streamed... +typedef struct Music { + stb_vorbis *stream; + jar_xm_context_t *chipctx; // Stores jar_xm context + AudioContext_t *ctx; // audio context + + int totalSamplesLeft; + float totalLengthSeconds; + bool loop; + bool chipTune; // True if chiptune is loaded +} Music; + #if defined(AUDIO_STANDALONE) typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; #endif @@ -168,7 +168,10 @@ void InitAudioDevice(void) // Close the audio device for the current context, and destroys the context void CloseAudioDevice(void) { - StopMusicStream(); // Stop music streaming and close current stream + for(int index=0; index= MAX_AUDIO_CONTEXTS) return NULL; if(!IsAudioDeviceReady()) InitAudioDevice(); - else StopMusicStream(); if(!mixChannelsActive_g[mixChannel]){ AudioContext_t *ac = (AudioContext_t*)malloc(sizeof(AudioContext_t)); @@ -776,7 +778,6 @@ int PlayMusicStream(int musicIndex, char *fileName) else if(musicIndex = MAX_AUDIO_CONTEXTS - 1) return 2; // error } - if (strcmp(GetExtension(fileName),"ogg") == 0) { // Open audio stream @@ -888,12 +889,12 @@ void ResumeMusicStream(int index) { // Resume music playing... if music available! ALenum state; - if(currentMusic[musicIndex].ctx){ - alGetSourcei(currentMusic[musicIndex].ctx->alSource, AL_SOURCE_STATE, &state); + if(currentMusic[index].ctx){ + alGetSourcei(currentMusic[index].ctx->alSource, AL_SOURCE_STATE, &state); if (state == AL_PAUSED) { TraceLog(INFO, "Resuming music stream"); - alSourcePlay(currentMusic[musicIndex].ctx->alSource); + alSourcePlay(currentMusic[index].ctx->alSource); musicEnabled = true; } } @@ -976,9 +977,8 @@ static bool BufferMusicStream(int index) float pcmf[MUSIC_BUFFER_SIZE_FLOAT]; int size = 0; // Total size of data steamed (in bytes) - int streamedBytes = 0; // samples of data obtained, channels are not included in calculation bool active = true; // We can get more data from stream (not finished) - + if (musicEnabled) { if (currentMusic[index].chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. @@ -992,12 +992,12 @@ static bool BufferMusicStream(int index) } else { - streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic[index].stream, currentMusic[index].ctx->channels, pcm, MUSIC_BUFFER_SIZE_SHORT); - UpdateAudioContext(currentMusic[index].ctx, pcm, MUSIC_BUFFER_SIZE_SHORT); - currentMusic[index].totalSamplesLeft -= MUSIC_BUFFER_SIZE_SHORT; + int streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic[index].stream, currentMusic[index].ctx->channels, pcm, MUSIC_BUFFER_SIZE_SHORT); + UpdateAudioContext(currentMusic[index].ctx, pcm, streamedBytes); + currentMusic[index].totalSamplesLeft -= streamedBytes*currentMusic[index].ctx->channels; if(currentMusic[index].totalSamplesLeft <= 0) active = false; } - TraceLog(DEBUG, "Streaming music data to buffer. Bytes streamed: %i", size); + // TraceLog(DEBUG, "Streaming music data to buffer. Bytes streamed: %i", size); } return active; @@ -1009,11 +1009,11 @@ static void EmptyMusicStream(int index) ALuint buffer = 0; int queued = 0; - alGetSourcei(currentMusic[index].source, AL_BUFFERS_QUEUED, &queued); + alGetSourcei(currentMusic[index].ctx->alSource, AL_BUFFERS_QUEUED, &queued); while (queued > 0) { - alSourceUnqueueBuffers(currentMusic[index].source, 1, &buffer); + alSourceUnqueueBuffers(currentMusic[index].ctx->alSource, 1, &buffer); queued--; } @@ -1045,16 +1045,17 @@ void UpdateMusicStream(int index) if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Error buffering data..."); + + alGetSourcei(currentMusic[index].ctx->alSource, AL_SOURCE_STATE, &state); - processed--; - } - - - alGetSourcei(currentMusic[index].source, AL_SOURCE_STATE, &state); + if ((state != AL_PLAYING) && active) alSourcePlay(currentMusic[index].ctx->alSource); - if ((state != AL_PLAYING) && active) alSourcePlay(currentMusic[index].source); + if (!active) StopMusicStream(index); + + } + else + return; - if (!active) StopMusicStream(); } // Load WAV file into Wave structure -- cgit v1.2.3 From ea4b5552c2b4ff8a907dd1fefc084317187af168 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sat, 14 May 2016 00:25:40 -0700 Subject: corrected typos --- src/audio.c | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index d19d3306..c53d6318 100644 --- a/src/audio.c +++ b/src/audio.c @@ -85,7 +85,7 @@ typedef struct AudioContext_t { unsigned char channels; // 1=mono,2=stereo unsigned char mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream bool floatingPoint; // if false then the short datatype is used instead - bool playing; + bool playing; // false if paused ALenum alFormat; // openAL format specifier ALuint alSource; // openAL source ALuint alBuffer[MAX_STREAM_BUFFERS]; // openAL sample buffer @@ -165,13 +165,14 @@ void InitAudioDevice(void) alListener3f(AL_ORIENTATION, 0, 0, -1); } -// Close the audio device for the current context, and destroys the context +// Close the audio device for all contexts void CloseAudioDevice(void) { for(int index=0; indexplaying = true; currentMusic[musicIndex].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[musicIndex].stream) * info.channels; currentMusic[musicIndex].totalLengthSeconds = stb_vorbis_stream_length_in_seconds(currentMusic[musicIndex].stream); @@ -822,6 +824,7 @@ int PlayMusicStream(int musicIndex, char *fileName) currentMusic[musicIndex].totalSamplesLeft = jar_xm_get_remaining_samples(currentMusic[musicIndex].chipctx); currentMusic[musicIndex].totalLengthSeconds = ((float)currentMusic[musicIndex].totalSamplesLeft) / 48000.f; musicEnabled = true; + currentMusic[musicIndex].ctx->playing = true; TraceLog(INFO, "[%s] XM number of samples: %i", fileName, currentMusic[musicIndex].totalSamplesLeft); TraceLog(INFO, "[%s] XM track length: %11.6f sec", fileName, currentMusic[musicIndex].totalLengthSeconds); @@ -859,16 +862,21 @@ void StopMusicStream(int index) } if(!getMusicStreamCount()) musicEnabled = false; + if(currentMusic[index].stream || currentMusic[index].chipctx) + { + currentMusic[index].stream = NULL; + currentMusic[index].chipctx = NULL; + } } } //get number of music channels active at this time, this does not mean they are playing int getMusicStreamCount(void) { - int musicCount; + int musicCount = 0; for(int musicIndex = 0; musicIndex < MAX_MUSIC_STREAMS; musicIndex++) // find empty music slot if(currentMusic[musicIndex].stream != NULL || currentMusic[musicIndex].chipTune) musicCount++; - + return musicCount; } @@ -876,11 +884,11 @@ int getMusicStreamCount(void) void PauseMusicStream(int index) { // Pause music stream if music available! - if (musicEnabled) + if (currentMusic[index].ctx && musicEnabled) { TraceLog(INFO, "Pausing music stream"); - UpdateAudioContext(currentMusic[index].ctx, NULL, 0); // pushing null data auto pauses stream - musicEnabled = false; + alSourcePause(currentMusic[index].ctx->alSource); + currentMusic[index].ctx->playing = false; } } @@ -895,7 +903,7 @@ void ResumeMusicStream(int index) { TraceLog(INFO, "Resuming music stream"); alSourcePlay(currentMusic[index].ctx->alSource); - musicEnabled = true; + currentMusic[index].ctx->playing = true; } } } @@ -981,13 +989,17 @@ static bool BufferMusicStream(int index) if (musicEnabled) { + if(!currentMusic[index].ctx->playing) + { + UpdateAudioContext(currentMusic[index].ctx, NULL, 0); + return false; + } + if (currentMusic[index].chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. { - int readlen = MUSIC_BUFFER_SIZE_FLOAT / 2; - jar_xm_generate_samples(currentMusic[index].chipctx, pcmf, readlen); // reads 2*readlen shorts and moves them to buffer+size memory location + jar_xm_generate_samples(currentMusic[index].chipctx, pcmf, MUSIC_BUFFER_SIZE_FLOAT / 2); // reads 2*readlen shorts and moves them to buffer+size memory location UpdateAudioContext(currentMusic[index].ctx, pcmf, MUSIC_BUFFER_SIZE_FLOAT); - size += readlen * currentMusic[index].ctx->channels; // Not sure if this is what it needs - currentMusic[index].totalSamplesLeft -= size; + currentMusic[index].totalSamplesLeft -= MUSIC_BUFFER_SIZE_FLOAT; if(currentMusic[index].totalSamplesLeft <= 0) active = false; } else @@ -997,7 +1009,7 @@ static bool BufferMusicStream(int index) currentMusic[index].totalSamplesLeft -= streamedBytes*currentMusic[index].ctx->channels; if(currentMusic[index].totalSamplesLeft <= 0) active = false; } - // TraceLog(DEBUG, "Streaming music data to buffer. Bytes streamed: %i", size); + TraceLog(DEBUG, "Buffering index:%i, chiptune:%i", index, (int)currentMusic[index].chipTune); } return active; -- cgit v1.2.3 From 8c5d403dda7a7070dc58204bb10539b3646f183b Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sat, 14 May 2016 15:26:17 -0700 Subject: new function to check if music stream is ready _g naming convention for globals, new error exit numbers. --- src/audio.c | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index c53d6318..0fd1bb13 100644 --- a/src/audio.c +++ b/src/audio.c @@ -112,7 +112,7 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; // Global Variables Definition //---------------------------------------------------------------------------------- static AudioContext_t* mixChannelsActive_g[MAX_AUDIO_CONTEXTS]; // What mix channels are currently active -static bool musicEnabled = false; +static bool musicEnabled_g = false; static Music currentMusic[MAX_MUSIC_STREAMS]; // Current music loaded, up to two can play at the same time //---------------------------------------------------------------------------------- @@ -126,8 +126,9 @@ static bool BufferMusicStream(int index); // Fill music buffers with da static void EmptyMusicStream(int index); // Empty music buffers static unsigned short FillAlBufferWithSilence(AudioContext_t *context, ALuint buffer);// fill buffer with zeros, returns number processed -static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // pass two arrays of the same legnth in -static void ResampleByteToFloat(char *chars, float *floats, unsigned short len); // pass two arrays of same length in +static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // pass two arrays of the same legnth in +static void ResampleByteToFloat(char *chars, float *floats, unsigned short len); // pass two arrays of same length in +static bool isMusicStreamReady(int index); // Checks if music buffer is ready to be refilled #if defined(AUDIO_STANDALONE) const char *GetExtension(const char *fileName); // Get the extension for a filename @@ -799,18 +800,21 @@ int PlayMusicStream(int musicIndex, char *fileName) TraceLog(DEBUG, "[%s] Temp memory required: %i", fileName, info.temp_memory_required); currentMusic[musicIndex].loop = true; // We loop by default - musicEnabled = true; - currentMusic[musicIndex].ctx->playing = true; + musicEnabled_g = true; + currentMusic[musicIndex].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[musicIndex].stream) * info.channels; currentMusic[musicIndex].totalLengthSeconds = stb_vorbis_stream_length_in_seconds(currentMusic[musicIndex].stream); if (info.channels == 2){ currentMusic[musicIndex].ctx = InitAudioContext(info.sample_rate, mixIndex, 2, false); + currentMusic[musicIndex].ctx->playing = true; } else{ currentMusic[musicIndex].ctx = InitAudioContext(info.sample_rate, mixIndex, 1, false); + currentMusic[musicIndex].ctx->playing = true; } + if(!currentMusic[musicIndex].ctx) return 4; // error } } else if (strcmp(GetExtension(fileName),"xm") == 0) @@ -823,24 +827,25 @@ int PlayMusicStream(int musicIndex, char *fileName) jar_xm_set_max_loop_count(currentMusic[musicIndex].chipctx, 0); // infinite number of loops currentMusic[musicIndex].totalSamplesLeft = jar_xm_get_remaining_samples(currentMusic[musicIndex].chipctx); currentMusic[musicIndex].totalLengthSeconds = ((float)currentMusic[musicIndex].totalSamplesLeft) / 48000.f; - musicEnabled = true; - currentMusic[musicIndex].ctx->playing = true; + musicEnabled_g = true; TraceLog(INFO, "[%s] XM number of samples: %i", fileName, currentMusic[musicIndex].totalSamplesLeft); TraceLog(INFO, "[%s] XM track length: %11.6f sec", fileName, currentMusic[musicIndex].totalLengthSeconds); currentMusic[musicIndex].ctx = InitAudioContext(48000, mixIndex, 2, true); + if(!currentMusic[musicIndex].ctx) return 5; // error + currentMusic[musicIndex].ctx->playing = true; } else { TraceLog(WARNING, "[%s] XM file could not be opened", fileName); - return 4; // error + return 6; // error } } else { TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName); - return 5; // error + return 7; // error } return 0; // normal return } @@ -861,7 +866,7 @@ void StopMusicStream(int index) stb_vorbis_close(currentMusic[index].stream); } - if(!getMusicStreamCount()) musicEnabled = false; + if(!getMusicStreamCount()) musicEnabled_g = false; if(currentMusic[index].stream || currentMusic[index].chipctx) { currentMusic[index].stream = NULL; @@ -884,7 +889,7 @@ int getMusicStreamCount(void) void PauseMusicStream(int index) { // Pause music stream if music available! - if (currentMusic[index].ctx && musicEnabled) + if (currentMusic[index].ctx && musicEnabled_g) { TraceLog(INFO, "Pausing music stream"); alSourcePause(currentMusic[index].ctx->alSource); @@ -987,7 +992,7 @@ static bool BufferMusicStream(int index) int size = 0; // Total size of data steamed (in bytes) bool active = true; // We can get more data from stream (not finished) - if (musicEnabled) + if (musicEnabled_g) { if(!currentMusic[index].ctx->playing) { @@ -1031,13 +1036,25 @@ static void EmptyMusicStream(int index) } } +//determine if a music stream is ready to be written to +static bool isMusicStreamReady(int index) +{ + ALint processed = 0; + alGetSourcei(currentMusic[index].ctx->alSource, AL_BUFFERS_PROCESSED, &processed); + + if(processed) + return true; + + return false; +} + // Update (re-fill) music buffers if data already processed void UpdateMusicStream(int index) { ALenum state; bool active = true; - if (index < MAX_MUSIC_STREAMS && musicEnabled) + if (index < MAX_MUSIC_STREAMS && musicEnabled_g && isMusicStreamReady(index)) { active = BufferMusicStream(index); -- cgit v1.2.3 From d38d7a1bedadf7c4824c9bb1d4a9ecd3f0935b9c Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sat, 14 May 2016 16:30:32 -0700 Subject: clean up on buffering and preconditions --- src/audio.c | 89 ++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 50 insertions(+), 39 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 0fd1bb13..30e2343f 100644 --- a/src/audio.c +++ b/src/audio.c @@ -889,7 +889,7 @@ int getMusicStreamCount(void) void PauseMusicStream(int index) { // Pause music stream if music available! - if (currentMusic[index].ctx && musicEnabled_g) + if (index < MAX_MUSIC_STREAMS && currentMusic[index].ctx && musicEnabled_g) { TraceLog(INFO, "Pausing music stream"); alSourcePause(currentMusic[index].ctx->alSource); @@ -902,7 +902,7 @@ void ResumeMusicStream(int index) { // Resume music playing... if music available! ALenum state; - if(currentMusic[index].ctx){ + if(index < MAX_MUSIC_STREAMS && currentMusic[index].ctx){ alGetSourcei(currentMusic[index].ctx->alSource, AL_SOURCE_STATE, &state); if (state == AL_PAUSED) { @@ -962,17 +962,20 @@ float GetMusicTimeLength(int index) float GetMusicTimePlayed(int index) { float secondsPlayed; - if (currentMusic[index].chipTune) - { - uint64_t samples; - jar_xm_get_position(currentMusic[index].chipctx, NULL, NULL, NULL, &samples); - secondsPlayed = (float)samples / (48000 * currentMusic[index].ctx->channels); // Not sure if this is the correct value - } - else + if(index < MAX_MUSIC_STREAMS && currentMusic[index].ctx) { - int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic[index].stream) * currentMusic[index].ctx->channels; - int samplesPlayed = totalSamples - currentMusic[index].totalSamplesLeft; - secondsPlayed = (float)samplesPlayed / (currentMusic[index].ctx->sampleRate * currentMusic[index].ctx->channels); + if (currentMusic[index].chipTune) + { + uint64_t samples; + jar_xm_get_position(currentMusic[index].chipctx, NULL, NULL, NULL, &samples); + secondsPlayed = (float)samples / (48000 * currentMusic[index].ctx->channels); // Not sure if this is the correct value + } + else + { + int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic[index].stream) * currentMusic[index].ctx->channels; + int samplesPlayed = totalSamples - currentMusic[index].totalSamplesLeft; + secondsPlayed = (float)samplesPlayed / (currentMusic[index].ctx->sampleRate * currentMusic[index].ctx->channels); + } } @@ -989,33 +992,42 @@ static bool BufferMusicStream(int index) short pcm[MUSIC_BUFFER_SIZE_SHORT]; float pcmf[MUSIC_BUFFER_SIZE_FLOAT]; - int size = 0; // Total size of data steamed (in bytes) + int size = 0; // Total size of data steamed in L+R samples bool active = true; // We can get more data from stream (not finished) - if (musicEnabled_g) + + if(!currentMusic[index].ctx->playing && currentMusic[index].totalSamplesLeft > 0) { - if(!currentMusic[index].ctx->playing) - { - UpdateAudioContext(currentMusic[index].ctx, NULL, 0); - return false; - } - - if (currentMusic[index].chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. - { - jar_xm_generate_samples(currentMusic[index].chipctx, pcmf, MUSIC_BUFFER_SIZE_FLOAT / 2); // reads 2*readlen shorts and moves them to buffer+size memory location - UpdateAudioContext(currentMusic[index].ctx, pcmf, MUSIC_BUFFER_SIZE_FLOAT); - currentMusic[index].totalSamplesLeft -= MUSIC_BUFFER_SIZE_FLOAT; - if(currentMusic[index].totalSamplesLeft <= 0) active = false; - } + UpdateAudioContext(currentMusic[index].ctx, NULL, 0); + return true; // it is still active, only it is paused + } + + + if (currentMusic[index].chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. + { + if(currentMusic[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_FLOAT / 2) + size = MUSIC_BUFFER_SIZE_FLOAT / 2; else - { - int streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic[index].stream, currentMusic[index].ctx->channels, pcm, MUSIC_BUFFER_SIZE_SHORT); - UpdateAudioContext(currentMusic[index].ctx, pcm, streamedBytes); - currentMusic[index].totalSamplesLeft -= streamedBytes*currentMusic[index].ctx->channels; - if(currentMusic[index].totalSamplesLeft <= 0) active = false; - } - TraceLog(DEBUG, "Buffering index:%i, chiptune:%i", index, (int)currentMusic[index].chipTune); + size = currentMusic[index].totalSamplesLeft / 2; + + jar_xm_generate_samples(currentMusic[index].chipctx, pcmf, size); // reads 2*readlen shorts and moves them to buffer+size memory location + UpdateAudioContext(currentMusic[index].ctx, pcmf, size * 2); + currentMusic[index].totalSamplesLeft -= size * 2; } + else + { + if(currentMusic[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_SHORT) + size = MUSIC_BUFFER_SIZE_SHORT; + else + size = currentMusic[index].totalSamplesLeft; + + int streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic[index].stream, currentMusic[index].ctx->channels, pcm, size); + UpdateAudioContext(currentMusic[index].ctx, pcm, streamedBytes * currentMusic[index].ctx->channels); + currentMusic[index].totalSamplesLeft -= streamedBytes * currentMusic[index].ctx->channels; + } + + TraceLog(DEBUG, "Buffering index:%i, chiptune:%i", index, (int)currentMusic[index].chipTune); + if(currentMusic[index].totalSamplesLeft <= 0) active = false; return active; } @@ -1042,8 +1054,7 @@ static bool isMusicStreamReady(int index) ALint processed = 0; alGetSourcei(currentMusic[index].ctx->alSource, AL_BUFFERS_PROCESSED, &processed); - if(processed) - return true; + if(processed) return true; return false; } @@ -1054,11 +1065,11 @@ void UpdateMusicStream(int index) ALenum state; bool active = true; - if (index < MAX_MUSIC_STREAMS && musicEnabled_g && isMusicStreamReady(index)) + if (index < MAX_MUSIC_STREAMS && musicEnabled_g && currentMusic[index].ctx && isMusicStreamReady(index)) { active = BufferMusicStream(index); - if ((!active) && (currentMusic[index].loop)) + if (!active && currentMusic[index].loop && currentMusic[index].ctx->playing) { if(currentMusic[index].chipTune) { @@ -1067,7 +1078,7 @@ void UpdateMusicStream(int index) else { stb_vorbis_seek_start(currentMusic[index].stream); - currentMusic[index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[index].stream)*currentMusic[index].ctx->channels; + currentMusic[index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[index].stream) * currentMusic[index].ctx->channels; } active = BufferMusicStream(index); } -- cgit v1.2.3 From 86fbf4fd8f0cd5301259b2115125c45c9872c02a Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sun, 15 May 2016 02:09:57 -0700 Subject: logic bug fix --- src/audio.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 30e2343f..cc5ca1a2 100644 --- a/src/audio.c +++ b/src/audio.c @@ -996,10 +996,10 @@ static bool BufferMusicStream(int index) bool active = true; // We can get more data from stream (not finished) - if(!currentMusic[index].ctx->playing && currentMusic[index].totalSamplesLeft > 0) + if (!currentMusic[index].ctx->playing && currentMusic[index].totalSamplesLeft > 0) { UpdateAudioContext(currentMusic[index].ctx, NULL, 0); - return true; // it is still active, only it is paused + return true; // it is still active but it is paused } @@ -1071,7 +1071,7 @@ void UpdateMusicStream(int index) if (!active && currentMusic[index].loop && currentMusic[index].ctx->playing) { - if(currentMusic[index].chipTune) + if (currentMusic[index].chipTune) { currentMusic[index].totalSamplesLeft = currentMusic[index].totalLengthSeconds * currentMusic[index].ctx->sampleRate; } @@ -1080,7 +1080,7 @@ void UpdateMusicStream(int index) stb_vorbis_seek_start(currentMusic[index].stream); currentMusic[index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[index].stream) * currentMusic[index].ctx->channels; } - active = BufferMusicStream(index); + active = true; } @@ -1088,7 +1088,7 @@ void UpdateMusicStream(int index) alGetSourcei(currentMusic[index].ctx->alSource, AL_SOURCE_STATE, &state); - if ((state != AL_PLAYING) && active) alSourcePlay(currentMusic[index].ctx->alSource); + if (state != AL_PLAYING && active && currentMusic[index].ctx->playing) alSourcePlay(currentMusic[index].ctx->alSource); if (!active) StopMusicStream(index); -- cgit v1.2.3 From 76ff4d220ee735b8b86bd4dae776665cf68e4fb4 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Sun, 15 May 2016 19:37:15 -0700 Subject: renamed everything so it is obvious what it does --- src/audio.c | 380 ++++++++++++++++++++++++++++------------------------------- src/audio.h | 15 +-- src/raylib.h | 15 +-- 3 files changed, 190 insertions(+), 220 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index cc5ca1a2..584d3ad1 100644 --- a/src/audio.c +++ b/src/audio.c @@ -77,10 +77,10 @@ // Types and Structures Definition //---------------------------------------------------------------------------------- -// Audio Context, used to create custom audio streams that are not bound to a sound file. There can be -// no more than 4 concurrent audio contexts in use. This is due to each active context being tied to -// a dedicated mix channel. All audio is 32bit floating point in stereo. -typedef struct AudioContext_t { +// Used to create custom audio streams that are not bound to a specific file. There can be +// no more than 4 concurrent mixchannels in use. This is due to each active mixc being tied to +// a dedicated mix channel. +typedef struct MixChannel_t { unsigned short sampleRate; // default is 48000 unsigned char channels; // 1=mono,2=stereo unsigned char mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream @@ -89,14 +89,14 @@ typedef struct AudioContext_t { ALenum alFormat; // openAL format specifier ALuint alSource; // openAL source ALuint alBuffer[MAX_STREAM_BUFFERS]; // openAL sample buffer -} AudioContext_t; +} MixChannel_t; // Music type (file streaming from memory) -// NOTE: Anything longer than ~10 seconds should be streamed... +// NOTE: Anything longer than ~10 seconds should be streamed into a mix channel... typedef struct Music { stb_vorbis *stream; - jar_xm_context_t *chipctx; // Stores jar_xm context - AudioContext_t *ctx; // audio context + jar_xm_context_t *chipctx; // Stores jar_xm mixc + MixChannel_t *mixc; // mix channel int totalSamplesLeft; float totalLengthSeconds; @@ -111,9 +111,9 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static AudioContext_t* mixChannelsActive_g[MAX_AUDIO_CONTEXTS]; // What mix channels are currently active +static MixChannel_t* mixChannelsActive_g[MAX_AUDIO_CONTEXTS]; // What mix channels are currently active static bool musicEnabled_g = false; -static Music currentMusic[MAX_MUSIC_STREAMS]; // Current music loaded, up to two can play at the same time +static Music currentMusic[MAX_MUSIC_STREAMS]; // Current music loaded, up to two can play at the same time //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -122,13 +122,17 @@ static Wave LoadWAV(const char *fileName); // Load WAV file static Wave LoadOGG(char *fileName); // Load OGG file static void UnloadWave(Wave wave); // Unload wave data -static bool BufferMusicStream(int index); // Fill music buffers with data -static void EmptyMusicStream(int index); // Empty music buffers +static bool BufferMusicStream(int index, int numBuffers); // Fill music buffers with data +static void EmptyMusicStream(int index); // Empty music buffers -static unsigned short FillAlBufferWithSilence(AudioContext_t *context, ALuint buffer);// fill buffer with zeros, returns number processed -static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // pass two arrays of the same legnth in -static void ResampleByteToFloat(char *chars, float *floats, unsigned short len); // pass two arrays of same length in -static bool isMusicStreamReady(int index); // Checks if music buffer is ready to be refilled + +static MixChannel_t* InitMixChannel(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint); // For streaming into mix channels. +static void CloseMixChannel(MixChannel_t* mixc); // Frees mix channel +static unsigned short BufferMixChannel(MixChannel_t* mixc, void *data, int numberElements); // Pushes more audio data into mixc mix channel, if NULL is passed it pauses +static unsigned short FillAlBufferWithSilence(MixChannel_t *mixc, ALuint buffer); // Fill buffer with zeros, returns number processed +static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // Pass two arrays of the same legnth in +static void ResampleByteToFloat(char *chars, float *floats, unsigned short len); // Pass two arrays of same length in +static int IsMusicStreamReadyForBuffering(int index); // Checks if music buffer is ready to be refilled #if defined(AUDIO_STANDALONE) const char *GetExtension(const char *fileName); // Get the extension for a filename @@ -139,7 +143,7 @@ void TraceLog(int msgType, const char *text, ...); // Outputs a trace log messa // Module Functions Definition - Audio Device initialization and Closing //---------------------------------------------------------------------------------- -// Initialize audio device and context +// Initialize audio device and mixc void InitAudioDevice(void) { // Open and initialize a device with default settings @@ -155,7 +159,7 @@ void InitAudioDevice(void) alcCloseDevice(device); - TraceLog(ERROR, "Could not setup audio context"); + TraceLog(ERROR, "Could not setup mix channel"); } TraceLog(INFO, "Audio device and context initialized successfully: %s", alcGetString(device, ALC_DEVICE_SPECIFIER)); @@ -171,14 +175,14 @@ void CloseAudioDevice(void) { for(int index=0; index= MAX_AUDIO_CONTEXTS) return NULL; if(!IsAudioDeviceReady()) InitAudioDevice(); if(!mixChannelsActive_g[mixChannel]){ - AudioContext_t *ac = (AudioContext_t*)malloc(sizeof(AudioContext_t)); - ac->sampleRate = sampleRate; - ac->channels = channels; - ac->mixChannel = mixChannel; - ac->floatingPoint = floatingPoint; - mixChannelsActive_g[mixChannel] = ac; + MixChannel_t *mixc = (MixChannel_t*)malloc(sizeof(MixChannel_t)); + mixc->sampleRate = sampleRate; + mixc->channels = channels; + mixc->mixChannel = mixChannel; + mixc->floatingPoint = floatingPoint; + mixChannelsActive_g[mixChannel] = mixc; // setup openAL format if(channels == 1) { if(floatingPoint) - ac->alFormat = AL_FORMAT_MONO_FLOAT32; + mixc->alFormat = AL_FORMAT_MONO_FLOAT32; else - ac->alFormat = AL_FORMAT_MONO16; + mixc->alFormat = AL_FORMAT_MONO16; } else if(channels == 2) { if(floatingPoint) - ac->alFormat = AL_FORMAT_STEREO_FLOAT32; + mixc->alFormat = AL_FORMAT_STEREO_FLOAT32; else - ac->alFormat = AL_FORMAT_STEREO16; + mixc->alFormat = AL_FORMAT_STEREO16; } // Create an audio source - alGenSources(1, &ac->alSource); - alSourcef(ac->alSource, AL_PITCH, 1); - alSourcef(ac->alSource, AL_GAIN, 1); - alSource3f(ac->alSource, AL_POSITION, 0, 0, 0); - alSource3f(ac->alSource, AL_VELOCITY, 0, 0, 0); + alGenSources(1, &mixc->alSource); + alSourcef(mixc->alSource, AL_PITCH, 1); + alSourcef(mixc->alSource, AL_GAIN, 1); + alSource3f(mixc->alSource, AL_POSITION, 0, 0, 0); + alSource3f(mixc->alSource, AL_VELOCITY, 0, 0, 0); // Create Buffer - alGenBuffers(MAX_STREAM_BUFFERS, ac->alBuffer); + alGenBuffers(MAX_STREAM_BUFFERS, mixc->alBuffer); //fill buffers int x; for(x=0;xalBuffer[x]); + FillAlBufferWithSilence(mixc, mixc->alBuffer[x]); - alSourceQueueBuffers(ac->alSource, MAX_STREAM_BUFFERS, ac->alBuffer); - alSourcePlay(ac->alSource); - ac->playing = true; + alSourceQueueBuffers(mixc->alSource, MAX_STREAM_BUFFERS, mixc->alBuffer); + mixc->playing = true; + alSourcePlay(mixc->alSource); - return ac; + return mixc; } return NULL; } -// Frees buffer in audio context -void CloseAudioContext(AudioContext ctx) +// Frees buffer in mix channel +static void CloseMixChannel(MixChannel_t* mixc) { - AudioContext_t *context = (AudioContext_t*)ctx; - if(context){ - alSourceStop(context->alSource); - context->playing = false; + if(mixc){ + alSourceStop(mixc->alSource); + mixc->playing = false; //flush out all queued buffers ALuint buffer = 0; int queued = 0; - alGetSourcei(context->alSource, AL_BUFFERS_QUEUED, &queued); + alGetSourcei(mixc->alSource, AL_BUFFERS_QUEUED, &queued); while (queued > 0) { - alSourceUnqueueBuffers(context->alSource, 1, &buffer); + alSourceUnqueueBuffers(mixc->alSource, 1, &buffer); queued--; } //delete source and buffers - alDeleteSources(1, &context->alSource); - alDeleteBuffers(MAX_STREAM_BUFFERS, context->alBuffer); - mixChannelsActive_g[context->mixChannel] = NULL; - free(context); - ctx = NULL; + alDeleteSources(1, &mixc->alSource); + alDeleteBuffers(MAX_STREAM_BUFFERS, mixc->alBuffer); + mixChannelsActive_g[mixc->mixChannel] = NULL; + free(mixc); + mixc = NULL; } } -// Pushes more audio data into context mix channel, if none are ever pushed then zeros are fed in. -// Call "UpdateAudioContext(ctx, NULL, 0)" if you want to pause the audio. +// Pushes more audio data into mixc mix channel, only one buffer per call +// Call "BufferMixChannel(mixc, NULL, 0)" if you want to pause the audio. // @Returns number of samples that where processed. -unsigned short UpdateAudioContext(AudioContext ctx, void *data, unsigned short numberElements) +static unsigned short BufferMixChannel(MixChannel_t* mixc, void *data, int numberElements) { - AudioContext_t *context = (AudioContext_t*)ctx; - - if(!context || (context->channels == 2 && numberElements % 2 != 0)) return 0; // when there is two channels there must be an even number of samples + if(!mixc || mixChannelsActive_g[mixc->mixChannel] != mixc) return 0; // when there is two channels there must be an even number of samples if (!data || !numberElements) { // pauses audio until data is given - alSourcePause(context->alSource); - context->playing = false; + if(mixc->playing){ + alSourcePause(mixc->alSource); + mixc->playing = false; + } return 0; } - else + else if(!mixc->playing) { // restart audio otherwise - ALint state; - alGetSourcei(context->alSource, AL_SOURCE_STATE, &state); - if (state != AL_PLAYING){ - alSourcePlay(context->alSource); - context->playing = true; - } + alSourcePlay(mixc->alSource); + mixc->playing = true; } - if (context && context->playing && mixChannelsActive_g[context->mixChannel] == context) + + ALuint buffer = 0; + + alSourceUnqueueBuffers(mixc->alSource, 1, &buffer); + if(!buffer) return 0; + if(mixc->floatingPoint) // process float buffers { - ALint processed = 0; - ALuint buffer = 0; - unsigned short numberProcessed = 0; - unsigned short numberRemaining = numberElements; - - - alGetSourcei(context->alSource, AL_BUFFERS_PROCESSED, &processed); // Get the number of already processed buffers (if any) - if(!processed) return 0; // nothing to process, queue is still full - - - while (processed > 0) - { - if(context->floatingPoint) // process float buffers - { - float *ptr = (float*)data; - alSourceUnqueueBuffers(context->alSource, 1, &buffer); - if(numberRemaining >= MUSIC_BUFFER_SIZE_FLOAT) - { - alBufferData(buffer, context->alFormat, &ptr[numberProcessed], MUSIC_BUFFER_SIZE_FLOAT*sizeof(float), context->sampleRate); - numberProcessed+=MUSIC_BUFFER_SIZE_FLOAT; - numberRemaining-=MUSIC_BUFFER_SIZE_FLOAT; - } - else - { - alBufferData(buffer, context->alFormat, &ptr[numberProcessed], numberRemaining*sizeof(float), context->sampleRate); - numberProcessed+=numberRemaining; - numberRemaining=0; - } - alSourceQueueBuffers(context->alSource, 1, &buffer); - processed--; - } - else if(!context->floatingPoint) // process short buffers - { - short *ptr = (short*)data; - alSourceUnqueueBuffers(context->alSource, 1, &buffer); - if(numberRemaining >= MUSIC_BUFFER_SIZE_SHORT) - { - alBufferData(buffer, context->alFormat, &ptr[numberProcessed], MUSIC_BUFFER_SIZE_FLOAT*sizeof(short), context->sampleRate); - numberProcessed+=MUSIC_BUFFER_SIZE_SHORT; - numberRemaining-=MUSIC_BUFFER_SIZE_SHORT; - } - else - { - alBufferData(buffer, context->alFormat, &ptr[numberProcessed], numberRemaining*sizeof(short), context->sampleRate); - numberProcessed+=numberRemaining; - numberRemaining=0; - } - alSourceQueueBuffers(context->alSource, 1, &buffer); - processed--; - } - else - break; - } - return numberProcessed; + float *ptr = (float*)data; + alBufferData(buffer, mixc->alFormat, ptr, numberElements*sizeof(float), mixc->sampleRate); + } + else // process short buffers + { + short *ptr = (short*)data; + alBufferData(buffer, mixc->alFormat, ptr, numberElements*sizeof(short), mixc->sampleRate); } - return 0; + alSourceQueueBuffers(mixc->alSource, 1, &buffer); + + return numberElements; } // fill buffer with zeros, returns number processed -static unsigned short FillAlBufferWithSilence(AudioContext_t *context, ALuint buffer) +static unsigned short FillAlBufferWithSilence(MixChannel_t *mixc, ALuint buffer) { - if(context->floatingPoint){ + if(mixc->floatingPoint){ float pcm[MUSIC_BUFFER_SIZE_FLOAT] = {0.f}; - alBufferData(buffer, context->alFormat, pcm, MUSIC_BUFFER_SIZE_FLOAT*sizeof(float), context->sampleRate); + alBufferData(buffer, mixc->alFormat, pcm, MUSIC_BUFFER_SIZE_FLOAT*sizeof(float), mixc->sampleRate); return MUSIC_BUFFER_SIZE_FLOAT; } else { short pcm[MUSIC_BUFFER_SIZE_SHORT] = {0}; - alBufferData(buffer, context->alFormat, pcm, MUSIC_BUFFER_SIZE_SHORT*sizeof(short), context->sampleRate); + alBufferData(buffer, mixc->alFormat, pcm, MUSIC_BUFFER_SIZE_SHORT*sizeof(short), mixc->sampleRate); return MUSIC_BUFFER_SIZE_SHORT; } } @@ -417,6 +376,28 @@ static void ResampleByteToFloat(char *chars, float *floats, unsigned short len) } } +// used to output raw audio streams, returns negative numbers on error +RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint) +{ + int mixIndex; + for(mixIndex = 0; mixIndex < MAX_AUDIO_CONTEXTS; mixIndex++) // find empty mix channel slot + { + if(mixChannelsActive_g[mixIndex] == NULL) break; + else if(mixIndex = MAX_AUDIO_CONTEXTS - 1) return -1; // error + } + + if(InitMixChannel(sampleRate, mixIndex, channels, floatingPoint)) + return mixIndex; + else + return -2; // error +} + +void CloseRawAudioContext(RawAudioContext ctx) +{ + if(mixChannelsActive_g[ctx]) + CloseMixChannel(mixChannelsActive_g[ctx]); +} + //---------------------------------------------------------------------------------- @@ -807,14 +788,14 @@ int PlayMusicStream(int musicIndex, char *fileName) currentMusic[musicIndex].totalLengthSeconds = stb_vorbis_stream_length_in_seconds(currentMusic[musicIndex].stream); if (info.channels == 2){ - currentMusic[musicIndex].ctx = InitAudioContext(info.sample_rate, mixIndex, 2, false); - currentMusic[musicIndex].ctx->playing = true; + currentMusic[musicIndex].mixc = InitMixChannel(info.sample_rate, mixIndex, 2, false); + currentMusic[musicIndex].mixc->playing = true; } else{ - currentMusic[musicIndex].ctx = InitAudioContext(info.sample_rate, mixIndex, 1, false); - currentMusic[musicIndex].ctx->playing = true; + currentMusic[musicIndex].mixc = InitMixChannel(info.sample_rate, mixIndex, 1, false); + currentMusic[musicIndex].mixc->playing = true; } - if(!currentMusic[musicIndex].ctx) return 4; // error + if(!currentMusic[musicIndex].mixc) return 4; // error } } else if (strcmp(GetExtension(fileName),"xm") == 0) @@ -832,9 +813,9 @@ int PlayMusicStream(int musicIndex, char *fileName) TraceLog(INFO, "[%s] XM number of samples: %i", fileName, currentMusic[musicIndex].totalSamplesLeft); TraceLog(INFO, "[%s] XM track length: %11.6f sec", fileName, currentMusic[musicIndex].totalLengthSeconds); - currentMusic[musicIndex].ctx = InitAudioContext(48000, mixIndex, 2, true); - if(!currentMusic[musicIndex].ctx) return 5; // error - currentMusic[musicIndex].ctx->playing = true; + currentMusic[musicIndex].mixc = InitMixChannel(48000, mixIndex, 2, false); + if(!currentMusic[musicIndex].mixc) return 5; // error + currentMusic[musicIndex].mixc->playing = true; } else { @@ -853,9 +834,9 @@ int PlayMusicStream(int musicIndex, char *fileName) // Stop music playing for individual music index of currentMusic array (close stream) void StopMusicStream(int index) { - if (index < MAX_MUSIC_STREAMS && currentMusic[index].ctx) + if (index < MAX_MUSIC_STREAMS && currentMusic[index].mixc) { - CloseAudioContext(currentMusic[index].ctx); + CloseMixChannel(currentMusic[index].mixc); if (currentMusic[index].chipTune) { @@ -889,11 +870,11 @@ int getMusicStreamCount(void) void PauseMusicStream(int index) { // Pause music stream if music available! - if (index < MAX_MUSIC_STREAMS && currentMusic[index].ctx && musicEnabled_g) + if (index < MAX_MUSIC_STREAMS && currentMusic[index].mixc && musicEnabled_g) { TraceLog(INFO, "Pausing music stream"); - alSourcePause(currentMusic[index].ctx->alSource); - currentMusic[index].ctx->playing = false; + alSourcePause(currentMusic[index].mixc->alSource); + currentMusic[index].mixc->playing = false; } } @@ -902,13 +883,13 @@ void ResumeMusicStream(int index) { // Resume music playing... if music available! ALenum state; - if(index < MAX_MUSIC_STREAMS && currentMusic[index].ctx){ - alGetSourcei(currentMusic[index].ctx->alSource, AL_SOURCE_STATE, &state); + if(index < MAX_MUSIC_STREAMS && currentMusic[index].mixc){ + alGetSourcei(currentMusic[index].mixc->alSource, AL_SOURCE_STATE, &state); if (state == AL_PAUSED) { TraceLog(INFO, "Resuming music stream"); - alSourcePlay(currentMusic[index].ctx->alSource); - currentMusic[index].ctx->playing = true; + alSourcePlay(currentMusic[index].mixc->alSource); + currentMusic[index].mixc->playing = true; } } } @@ -919,8 +900,8 @@ bool IsMusicPlaying(int index) bool playing = false; ALint state; - if(index < MAX_MUSIC_STREAMS && currentMusic[index].ctx){ - alGetSourcei(currentMusic[index].ctx->alSource, AL_SOURCE_STATE, &state); + if(index < MAX_MUSIC_STREAMS && currentMusic[index].mixc){ + alGetSourcei(currentMusic[index].mixc->alSource, AL_SOURCE_STATE, &state); if (state == AL_PLAYING) playing = true; } @@ -930,15 +911,15 @@ bool IsMusicPlaying(int index) // Set volume for music void SetMusicVolume(int index, float volume) { - if(index < MAX_MUSIC_STREAMS && currentMusic[index].ctx){ - alSourcef(currentMusic[index].ctx->alSource, AL_GAIN, volume); + if(index < MAX_MUSIC_STREAMS && currentMusic[index].mixc){ + alSourcef(currentMusic[index].mixc->alSource, AL_GAIN, volume); } } void SetMusicPitch(int index, float pitch) { - if(index < MAX_MUSIC_STREAMS && currentMusic[index].ctx){ - alSourcef(currentMusic[index].ctx->alSource, AL_PITCH, pitch); + if(index < MAX_MUSIC_STREAMS && currentMusic[index].mixc){ + alSourcef(currentMusic[index].mixc->alSource, AL_PITCH, pitch); } } @@ -962,19 +943,19 @@ float GetMusicTimeLength(int index) float GetMusicTimePlayed(int index) { float secondsPlayed; - if(index < MAX_MUSIC_STREAMS && currentMusic[index].ctx) + if(index < MAX_MUSIC_STREAMS && currentMusic[index].mixc) { if (currentMusic[index].chipTune) { uint64_t samples; jar_xm_get_position(currentMusic[index].chipctx, NULL, NULL, NULL, &samples); - secondsPlayed = (float)samples / (48000 * currentMusic[index].ctx->channels); // Not sure if this is the correct value + secondsPlayed = (float)samples / (48000 * currentMusic[index].mixc->channels); // Not sure if this is the correct value } else { - int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic[index].stream) * currentMusic[index].ctx->channels; + int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic[index].stream) * currentMusic[index].mixc->channels; int samplesPlayed = totalSamples - currentMusic[index].totalSamplesLeft; - secondsPlayed = (float)samplesPlayed / (currentMusic[index].ctx->sampleRate * currentMusic[index].ctx->channels); + secondsPlayed = (float)samplesPlayed / (currentMusic[index].mixc->sampleRate * currentMusic[index].mixc->channels); } } @@ -987,32 +968,32 @@ float GetMusicTimePlayed(int index) //---------------------------------------------------------------------------------- // Fill music buffers with new data from music stream -static bool BufferMusicStream(int index) +static bool BufferMusicStream(int index, int numBuffers) { short pcm[MUSIC_BUFFER_SIZE_SHORT]; float pcmf[MUSIC_BUFFER_SIZE_FLOAT]; - int size = 0; // Total size of data steamed in L+R samples + int size = 0; // Total size of data steamed in L+R samples for xm floats, individual L or R for ogg shorts bool active = true; // We can get more data from stream (not finished) - - - if (!currentMusic[index].ctx->playing && currentMusic[index].totalSamplesLeft > 0) - { - UpdateAudioContext(currentMusic[index].ctx, NULL, 0); - return true; // it is still active but it is paused - } - if (currentMusic[index].chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. { - if(currentMusic[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_FLOAT / 2) - size = MUSIC_BUFFER_SIZE_FLOAT / 2; + if(currentMusic[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_SHORT) + size = MUSIC_BUFFER_SIZE_SHORT / 2; else size = currentMusic[index].totalSamplesLeft / 2; - - jar_xm_generate_samples(currentMusic[index].chipctx, pcmf, size); // reads 2*readlen shorts and moves them to buffer+size memory location - UpdateAudioContext(currentMusic[index].ctx, pcmf, size * 2); - currentMusic[index].totalSamplesLeft -= size * 2; + + for(int x=0; xchannels, pcm, size); - UpdateAudioContext(currentMusic[index].ctx, pcm, streamedBytes * currentMusic[index].ctx->channels); - currentMusic[index].totalSamplesLeft -= streamedBytes * currentMusic[index].ctx->channels; + for(int x=0; xchannels, pcm, size); + BufferMixChannel(currentMusic[index].mixc, pcm, streamedBytes * currentMusic[index].mixc->channels); + currentMusic[index].totalSamplesLeft -= streamedBytes * currentMusic[index].mixc->channels; + if(currentMusic[index].totalSamplesLeft <= 0) + { + active = false; + break; + } + } } - - TraceLog(DEBUG, "Buffering index:%i, chiptune:%i", index, (int)currentMusic[index].chipTune); - if(currentMusic[index].totalSamplesLeft <= 0) active = false; return active; } @@ -1038,25 +1024,22 @@ static void EmptyMusicStream(int index) ALuint buffer = 0; int queued = 0; - alGetSourcei(currentMusic[index].ctx->alSource, AL_BUFFERS_QUEUED, &queued); + alGetSourcei(currentMusic[index].mixc->alSource, AL_BUFFERS_QUEUED, &queued); while (queued > 0) { - alSourceUnqueueBuffers(currentMusic[index].ctx->alSource, 1, &buffer); + alSourceUnqueueBuffers(currentMusic[index].mixc->alSource, 1, &buffer); queued--; } } //determine if a music stream is ready to be written to -static bool isMusicStreamReady(int index) +static int IsMusicStreamReadyForBuffering(int index) { ALint processed = 0; - alGetSourcei(currentMusic[index].ctx->alSource, AL_BUFFERS_PROCESSED, &processed); - - if(processed) return true; - - return false; + alGetSourcei(currentMusic[index].mixc->alSource, AL_BUFFERS_PROCESSED, &processed); + return processed; } // Update (re-fill) music buffers if data already processed @@ -1064,21 +1047,22 @@ void UpdateMusicStream(int index) { ALenum state; bool active = true; - - if (index < MAX_MUSIC_STREAMS && musicEnabled_g && currentMusic[index].ctx && isMusicStreamReady(index)) + int numBuffers = IsMusicStreamReadyForBuffering(index); + + if (currentMusic[index].mixc->playing && index < MAX_MUSIC_STREAMS && musicEnabled_g && currentMusic[index].mixc && numBuffers) { - active = BufferMusicStream(index); + active = BufferMusicStream(index, numBuffers); - if (!active && currentMusic[index].loop && currentMusic[index].ctx->playing) + if (!active && currentMusic[index].loop) { if (currentMusic[index].chipTune) { - currentMusic[index].totalSamplesLeft = currentMusic[index].totalLengthSeconds * currentMusic[index].ctx->sampleRate; + currentMusic[index].totalSamplesLeft = currentMusic[index].totalLengthSeconds * 48000; } else { stb_vorbis_seek_start(currentMusic[index].stream); - currentMusic[index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[index].stream) * currentMusic[index].ctx->channels; + currentMusic[index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic[index].stream) * currentMusic[index].mixc->channels; } active = true; } @@ -1086,9 +1070,9 @@ void UpdateMusicStream(int index) if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Error buffering data..."); - alGetSourcei(currentMusic[index].ctx->alSource, AL_SOURCE_STATE, &state); + alGetSourcei(currentMusic[index].mixc->alSource, AL_SOURCE_STATE, &state); - if (state != AL_PLAYING && active && currentMusic[index].ctx->playing) alSourcePlay(currentMusic[index].ctx->alSource); + if (state != AL_PLAYING && active) alSourcePlay(currentMusic[index].mixc->alSource); if (!active) StopMusicStream(index); diff --git a/src/audio.h b/src/audio.h index 63c1c136..d3276bf6 100644 --- a/src/audio.h +++ b/src/audio.h @@ -61,10 +61,7 @@ typedef struct Wave { short channels; } Wave; -// Audio Context, used to create custom audio streams that are not bound to a sound file. There can be -// no more than 4 concurrent audio contexts in use. This is due to each active context being tied to -// a dedicated mix channel. -typedef void* AudioContext; +typedef int RawAudioContext; #ifdef __cplusplus extern "C" { // Prevents name mangling of functions @@ -82,13 +79,6 @@ void InitAudioDevice(void); // Initialize au void CloseAudioDevice(void); // Close the audio device and context (and music stream) bool IsAudioDeviceReady(void); // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet -// Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing -// The mixChannel is what mix channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, 0, 2, true); // mixchannel 1, 48khz, stereo, floating point -AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint); -void CloseAudioContext(AudioContext ctx); // Frees audio context -unsigned short UpdateAudioContext(AudioContext ctx, void *data, unsigned short numberElements); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played - Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data Sound LoadSoundFromRES(const char *rresName, int resId); // Load sound to memory from rRES file (raylib Resource) @@ -112,6 +102,9 @@ float GetMusicTimePlayed(int index); // Get current m int getMusicStreamCount(void); void SetMusicPitch(int index, float pitch); +RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint); +void CloseRawAudioContext(RawAudioContext ctx); + #ifdef __cplusplus } #endif diff --git a/src/raylib.h b/src/raylib.h index ea9fbfcb..6efde710 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -451,10 +451,7 @@ typedef struct Wave { short channels; } Wave; -// Audio Context, used to create custom audio streams that are not bound to a sound file. There can be -// no more than 4 concurrent audio contexts in use. This is due to each active context being tied to -// a dedicated mix channel. -typedef void* AudioContext; +typedef int RawAudioContext; // Texture formats // NOTE: Support depends on OpenGL version and platform @@ -876,13 +873,6 @@ void InitAudioDevice(void); // Initialize au void CloseAudioDevice(void); // Close the audio device and context (and music stream) bool IsAudioDeviceReady(void); // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet -// Audio contexts are for outputing custom audio waveforms, This will shut down any other sound sources currently playing -// The mixChannel is what mix channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitAudioContext(48000, 0, 2, true); // mixchannel 1, 48khz, stereo, floating point -AudioContext InitAudioContext(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint); -void CloseAudioContext(AudioContext ctx); // Frees audio context -unsigned short UpdateAudioContext(AudioContext ctx, void *data, unsigned short numberElements); // Pushes more audio data into context mix channel, if NULL is passed to data then zeros are played - Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data Sound LoadSoundFromRES(const char *rresName, int resId); // Load sound to memory from rRES file (raylib Resource) @@ -906,6 +896,9 @@ float GetMusicTimePlayed(int index); // Get current m int getMusicStreamCount(void); void SetMusicPitch(int index, float pitch); +RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint); // used to output raw audio streams, returns negative numbers on error +void CloseRawAudioContext(RawAudioContext ctx); + #ifdef __cplusplus } #endif -- cgit v1.2.3 From b10425492ac692d7c53001290e3d0b678df634b0 Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Thu, 19 May 2016 15:22:12 -0700 Subject: name correction --- src/audio.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 584d3ad1..6d8f876c 100644 --- a/src/audio.c +++ b/src/audio.c @@ -59,9 +59,9 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define MAX_STREAM_BUFFERS 2 -#define MAX_AUDIO_CONTEXTS 4 // Number of open AL sources -#define MAX_MUSIC_STREAMS 2 +#define MAX_STREAM_BUFFERS 2 // Number of buffers for each alSource +#define MAX_MIX_CHANNELS 4 // Number of open AL sources +#define MAX_MUSIC_STREAMS 2 // Number of simultanious music sources #if defined(PLATFORM_RPI) || defined(PLATFORM_ANDROID) // NOTE: On RPI and Android should be lower to avoid frame-stalls @@ -96,12 +96,12 @@ typedef struct MixChannel_t { typedef struct Music { stb_vorbis *stream; jar_xm_context_t *chipctx; // Stores jar_xm mixc - MixChannel_t *mixc; // mix channel + MixChannel_t *mixc; // mix channel int totalSamplesLeft; float totalLengthSeconds; bool loop; - bool chipTune; // True if chiptune is loaded + bool chipTune; // True if chiptune is loaded } Music; #if defined(AUDIO_STANDALONE) @@ -111,7 +111,7 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static MixChannel_t* mixChannelsActive_g[MAX_AUDIO_CONTEXTS]; // What mix channels are currently active +static MixChannel_t* mixChannelsActive_g[MAX_MIX_CHANNELS]; // What mix channels are currently active static bool musicEnabled_g = false; static Music currentMusic[MAX_MUSIC_STREAMS]; // Current music loaded, up to two can play at the same time @@ -123,7 +123,7 @@ static Wave LoadOGG(char *fileName); // Load OGG file static void UnloadWave(Wave wave); // Unload wave data static bool BufferMusicStream(int index, int numBuffers); // Fill music buffers with data -static void EmptyMusicStream(int index); // Empty music buffers +static void EmptyMusicStream(int index); // Empty music buffers static MixChannel_t* InitMixChannel(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint); // For streaming into mix channels. @@ -212,7 +212,7 @@ bool IsAudioDeviceReady(void) // exmple usage is InitMixChannel(48000, 0, 2, true); // mixchannel 1, 48khz, stereo, floating point static MixChannel_t* InitMixChannel(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint) { - if(mixChannel >= MAX_AUDIO_CONTEXTS) return NULL; + if(mixChannel >= MAX_MIX_CHANNELS) return NULL; if(!IsAudioDeviceReady()) InitAudioDevice(); if(!mixChannelsActive_g[mixChannel]){ @@ -380,10 +380,10 @@ static void ResampleByteToFloat(char *chars, float *floats, unsigned short len) RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint) { int mixIndex; - for(mixIndex = 0; mixIndex < MAX_AUDIO_CONTEXTS; mixIndex++) // find empty mix channel slot + for(mixIndex = 0; mixIndex < MAX_MIX_CHANNELS; mixIndex++) // find empty mix channel slot { if(mixChannelsActive_g[mixIndex] == NULL) break; - else if(mixIndex = MAX_AUDIO_CONTEXTS - 1) return -1; // error + else if(mixIndex = MAX_MIX_CHANNELS - 1) return -1; // error } if(InitMixChannel(sampleRate, mixIndex, channels, floatingPoint)) @@ -755,10 +755,10 @@ int PlayMusicStream(int musicIndex, char *fileName) if(currentMusic[musicIndex].stream || currentMusic[musicIndex].chipctx) return 1; // error - for(mixIndex = 0; mixIndex < MAX_AUDIO_CONTEXTS; mixIndex++) // find empty mix channel slot + for(mixIndex = 0; mixIndex < MAX_MIX_CHANNELS; mixIndex++) // find empty mix channel slot { if(mixChannelsActive_g[mixIndex] == NULL) break; - else if(mixIndex = MAX_AUDIO_CONTEXTS - 1) return 2; // error + else if(mixIndex = MAX_MIX_CHANNELS - 1) return 2; // error } if (strcmp(GetExtension(fileName),"ogg") == 0) -- cgit v1.2.3 From 41c5f3a0178027e9b74e563ab102f603a53e35bf Mon Sep 17 00:00:00 2001 From: Joshua Reisenauer Date: Thu, 19 May 2016 20:44:09 -0700 Subject: Buffer for raw audio --- src/audio.c | 22 ++++++++++++++++++---- src/audio.h | 4 ++++ src/raylib.h | 6 +++++- 3 files changed, 27 insertions(+), 5 deletions(-) (limited to 'src/audio.c') diff --git a/src/audio.c b/src/audio.c index 6d8f876c..43e8be14 100644 --- a/src/audio.c +++ b/src/audio.c @@ -128,8 +128,8 @@ static void EmptyMusicStream(int index); // Empty music buffers static MixChannel_t* InitMixChannel(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint); // For streaming into mix channels. static void CloseMixChannel(MixChannel_t* mixc); // Frees mix channel -static unsigned short BufferMixChannel(MixChannel_t* mixc, void *data, int numberElements); // Pushes more audio data into mixc mix channel, if NULL is passed it pauses -static unsigned short FillAlBufferWithSilence(MixChannel_t *mixc, ALuint buffer); // Fill buffer with zeros, returns number processed +static int BufferMixChannel(MixChannel_t* mixc, void *data, int numberElements); // Pushes more audio data into mixc mix channel, if NULL is passed it pauses +static int FillAlBufferWithSilence(MixChannel_t *mixc, ALuint buffer); // Fill buffer with zeros, returns number processed static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // Pass two arrays of the same legnth in static void ResampleByteToFloat(char *chars, float *floats, unsigned short len); // Pass two arrays of same length in static int IsMusicStreamReadyForBuffering(int index); // Checks if music buffer is ready to be refilled @@ -292,7 +292,7 @@ static void CloseMixChannel(MixChannel_t* mixc) // Pushes more audio data into mixc mix channel, only one buffer per call // Call "BufferMixChannel(mixc, NULL, 0)" if you want to pause the audio. // @Returns number of samples that where processed. -static unsigned short BufferMixChannel(MixChannel_t* mixc, void *data, int numberElements) +static int BufferMixChannel(MixChannel_t* mixc, void *data, int numberElements) { if(!mixc || mixChannelsActive_g[mixc->mixChannel] != mixc) return 0; // when there is two channels there must be an even number of samples @@ -331,7 +331,7 @@ static unsigned short BufferMixChannel(MixChannel_t* mixc, void *data, int numbe } // fill buffer with zeros, returns number processed -static unsigned short FillAlBufferWithSilence(MixChannel_t *mixc, ALuint buffer) +static int FillAlBufferWithSilence(MixChannel_t *mixc, ALuint buffer) { if(mixc->floatingPoint){ float pcm[MUSIC_BUFFER_SIZE_FLOAT] = {0.f}; @@ -377,6 +377,7 @@ static void ResampleByteToFloat(char *chars, float *floats, unsigned short len) } // used to output raw audio streams, returns negative numbers on error +// if floating point is false the data size is 16bit short, otherwise it is float 32bit RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint) { int mixIndex; @@ -398,6 +399,19 @@ void CloseRawAudioContext(RawAudioContext ctx) CloseMixChannel(mixChannelsActive_g[ctx]); } +int BufferRawAudioContext(RawAudioContext ctx, void *data, int numberElements) +{ + int numBuffered = 0; + if(ctx >= 0) + { + MixChannel_t* mixc = mixChannelsActive_g[ctx]; + numBuffered = BufferMixChannel(mixc, data, numberElements); + } + return numBuffered; +} + + + //---------------------------------------------------------------------------------- diff --git a/src/audio.h b/src/audio.h index d3276bf6..1140a60a 100644 --- a/src/audio.h +++ b/src/audio.h @@ -102,8 +102,12 @@ float GetMusicTimePlayed(int index); // Get current m int getMusicStreamCount(void); void SetMusicPitch(int index, float pitch); +// used to output raw audio streams, returns negative numbers on error +// if floating point is false the data size is 16bit short, otherwise it is float 32bit RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint); + void CloseRawAudioContext(RawAudioContext ctx); +int BufferRawAudioContext(RawAudioContext ctx, void *data, int numberElements); // returns number of elements buffered #ifdef __cplusplus } diff --git a/src/raylib.h b/src/raylib.h index 6efde710..986dc7bf 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -896,8 +896,12 @@ float GetMusicTimePlayed(int index); // Get current m int getMusicStreamCount(void); void SetMusicPitch(int index, float pitch); -RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint); // used to output raw audio streams, returns negative numbers on error +// used to output raw audio streams, returns negative numbers on error +// if floating point is false the data size is 16bit short, otherwise it is float 32bit +RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint); + void CloseRawAudioContext(RawAudioContext ctx); +int BufferRawAudioContext(RawAudioContext ctx, void *data, int numberElements); // returns number of elements buffered #ifdef __cplusplus } -- cgit v1.2.3