Skip to content

Instantly share code, notes, and snippets.

@hydren
Last active May 13, 2022 12:11
Show Gist options
  • Save hydren/68ae34d596fca69bd196c6fed4210c72 to your computer and use it in GitHub Desktop.
Save hydren/68ae34d596fca69bd196c6fed4210c72 to your computer and use it in GitHub Desktop.
SDL_mixer sound pitching effect on chunks C example (as library)
/*
* custom_mix_pitch.c
*
* Created on: 10 jan 2018
* Author: Carlos Faruolo
*/
#include "custom_mix_pitch.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
static Uint16 format_sample_size(Uint16 format) { return (format & 0xFF) / 8; }
/* Get chunk time length (in ms) given its size and current audio format */
int Custom_Mix_ComputeChunkLengthMillisec(int chunkSize)
{
Uint16 fmt;
int freq, chancnt;
Mix_QuerySpec(&freq, &fmt, &chancnt);
/* bytes / samplesize == sample points */
const Uint32 points = chunkSize / format_sample_size(fmt);
/* sample points / channels == sample frames */
const Uint32 frames = (points / chancnt);
/* (sample frames * 1000) / frequency == play length, in ms */
return ((frames * 1000) / freq);
}
/* custom handler object to control which part of the Mix_Chunk's audio data will be played, with which pitch-related modifications. */
typedef struct Custom_Mix_PlaybackSpeedEffectHandler
{
const Mix_Chunk* chunk;
const float* speed; /* ptr to the desired playback speed */
float position; /* current position of the sound, in ms */
int altered; /* false if this playback has never been pitched. */
// read-only!
int loop; /* whether this is a looped playback */
int duration; /* the duration of the sound, in ms */
int chunk_size; /* the size of the sound, as a number of indexes (or sample points). thinks of this as a array size when using the proper array type (instead of just Uint8*). */
int self_halt; /* flags whether playback should be halted by this callback when playback is finished */
} Custom_Mix_PlaybackSpeedEffectHandler;
/* "Constructor" for Custom_Mix_PlaybackSpeedEffectHandler */
Custom_Mix_PlaybackSpeedEffectHandler* Custom_Mix_CreatePlaybackSpeedEffectHandler(const Mix_Chunk* chunk, const float* speed, int loop, int self_halt)
{
Uint16 fmt;
Mix_QuerySpec(NULL, &fmt, NULL);
Custom_Mix_PlaybackSpeedEffectHandler* handler = malloc(sizeof(Custom_Mix_PlaybackSpeedEffectHandler));
handler->chunk = chunk;
handler->speed = speed;
handler->position = 0;
handler->altered = 0;
handler->loop = loop;
handler->duration = Custom_Mix_ComputeChunkLengthMillisec(chunk->alen);
handler->chunk_size = chunk->alen / format_sample_size(fmt);
handler->self_halt = self_halt;
return handler;
}
/* implementation of Uint8 version of the callback */
#define Custom_Mix_PlaybackSpeedEffectFuncCallback Custom_Mix_PlaybackSpeedEffectFuncCallbackUint8
#define AUDIO_FORMAT_TYPE Uint8
#include "custom_mix_pitch_callback.h"
#undef Custom_Mix_PlaybackSpeedEffectFuncCallback
#undef AUDIO_FORMAT_TYPE
/* implementation of Sint8 version of the callback */
#define Custom_Mix_PlaybackSpeedEffectFuncCallback Custom_Mix_PlaybackSpeedEffectFuncCallbackSint8
#define AUDIO_FORMAT_TYPE Sint8
#include "custom_mix_pitch_callback.h"
#undef Custom_Mix_PlaybackSpeedEffectFuncCallback
#undef AUDIO_FORMAT_TYPE
/* implementation of Uint16 version of the callback */
#define Custom_Mix_PlaybackSpeedEffectFuncCallback Custom_Mix_PlaybackSpeedEffectFuncCallbackUint16
#define AUDIO_FORMAT_TYPE Uint16
#include "custom_mix_pitch_callback.h"
#undef Custom_Mix_PlaybackSpeedEffectFuncCallback
#undef AUDIO_FORMAT_TYPE
/* implementation of Sint16 version of the callback */
#define Custom_Mix_PlaybackSpeedEffectFuncCallback Custom_Mix_PlaybackSpeedEffectFuncCallbackSint16
#define AUDIO_FORMAT_TYPE Sint16
#include "custom_mix_pitch_callback.h"
#undef Custom_Mix_PlaybackSpeedEffectFuncCallback
#undef AUDIO_FORMAT_TYPE
/* implementation of Sint32 version of the callback */
#define Custom_Mix_PlaybackSpeedEffectFuncCallback Custom_Mix_PlaybackSpeedEffectFuncCallbackSint32
#define AUDIO_FORMAT_TYPE Sint32
#include "custom_mix_pitch_callback.h"
#undef Custom_Mix_PlaybackSpeedEffectFuncCallback
#undef AUDIO_FORMAT_TYPE
/* implementation of Float version of the callback */
#define Custom_Mix_PlaybackSpeedEffectFuncCallback Custom_Mix_PlaybackSpeedEffectFuncCallbackFloat
#define AUDIO_FORMAT_TYPE float
#include "custom_mix_pitch_callback.h"
#undef Custom_Mix_PlaybackSpeedEffectFuncCallback
#undef AUDIO_FORMAT_TYPE
/* Mix_EffectDone_t callback that deletes the handler at the end of the effect usage (handler passed via userData) */
void Custom_Mix_PlaybackSpeedEffectDoneCallback(int channel, void *userData)
{
free(userData);
}
/* Register a proper playback speed effect handler for this channel according to the current audio format. Effect valid for the current (or next) playback only. */
void Custom_Mix_RegisterPlaybackSpeedEffect(int channel, Mix_Chunk* chunk, float* speed, int loop, int self_halt)
{
Uint16 fmt;
Mix_QuerySpec(NULL, &fmt, NULL);
Mix_EffectFunc_t effect_func_callback;
/* select the register function for the current audio format and register the effect using the compatible handlers
xxx is it correct to behave the same way to all S16 and U16 formats? Should we create case statements for AUDIO_S16SYS, AUDIO_S16LSB, AUDIO_S16MSB, etc, individually? */
switch(fmt)
{
case AUDIO_U8: effect_func_callback = Custom_Mix_PlaybackSpeedEffectFuncCallbackUint8; break;
case AUDIO_S8: effect_func_callback = Custom_Mix_PlaybackSpeedEffectFuncCallbackSint8; break;
case AUDIO_U16: effect_func_callback = Custom_Mix_PlaybackSpeedEffectFuncCallbackUint16; break;
default:
case AUDIO_S16: effect_func_callback = Custom_Mix_PlaybackSpeedEffectFuncCallbackSint16; break;
case AUDIO_S32: effect_func_callback = Custom_Mix_PlaybackSpeedEffectFuncCallbackSint32; break;
case AUDIO_F32: effect_func_callback = Custom_Mix_PlaybackSpeedEffectFuncCallbackFloat; break;
}
Mix_RegisterEffect(channel, effect_func_callback, Custom_Mix_PlaybackSpeedEffectDoneCallback, Custom_Mix_CreatePlaybackSpeedEffectHandler(chunk, speed, loop, self_halt));
}
/*
* custom_mix_pitch.h
*
* Created on: 10 jan 2018
* Author: Carlos Faruolo
*/
#ifndef CUSTOM_MIX_PITCH_H_
#define CUSTOM_MIX_PITCH_H_
#include <SDL2/SDL_mixer.h>
/* Get chunk time length (in ms) given its size and current audio format */
int Custom_Mix_ComputeChunkLengthMillisec(int chunk_size);
/* Register a proper playback speed effect handler for this channel according to the current audio format. Effect valid for the current (or next) playback only. */
void Custom_Mix_RegisterPlaybackSpeedEffect(int channel, Mix_Chunk* chunk, float* speed, int loop, int self_halt);
#endif /* CUSTOM_MIX_PITCH_H_ */
/*
* custom_mix_pitch_callback.h
*
* Created on: 10 de jan de 2018
* Author: carlosfaruolo
*/
// Mix_EffectFunc_t callback that redirects to handler method (handler passed via user_data)
// Processing function to be able to change chunk speed/pitch.
// AUDIO_FORMAT_TYPE depends on the current audio format (queryable via Mix_QuerySpec)
void Custom_Mix_PlaybackSpeedEffectFuncCallback(int mix_channel, void* stream, int length, void* user_data)
{
int audio_frequency, audio_channel_count;
Mix_QuerySpec(&audio_frequency, NULL, &audio_channel_count);
Custom_Mix_PlaybackSpeedEffectHandler* handler = (Custom_Mix_PlaybackSpeedEffectHandler*) user_data;
const AUDIO_FORMAT_TYPE* chunk_data = (AUDIO_FORMAT_TYPE*) handler->chunk->abuf;
AUDIO_FORMAT_TYPE* buffer = (AUDIO_FORMAT_TYPE*) stream;
const int buffer_size = length / sizeof(AUDIO_FORMAT_TYPE); // buffer size (as array)
const float speed_factor = *(handler->speed); // take a "snapshot" of speed factor
// if there is still sound to be played
if(handler->position < handler->duration || handler->loop)
{
const float delta = 1000.0 / audio_frequency, // normal duration of each sample
vdelta = delta * speed_factor; // virtual stretched duration, scaled by 'speedFactor'
// if playback is unaltered and pitch is required (for the first time)
if(!handler->altered && speed_factor != 1.0f)
handler->altered = 1; // flags playback modification and proceed to the pitch routine
if(handler->altered) // if unaltered, this pitch routine is skipped
{
for(int i = 0; i < buffer_size; i += audio_channel_count)
{
const int j = i / audio_channel_count; // j goes from 0 to size/channelCount, incremented 1 by 1
const float x = handler->position + j * vdelta; // get "virtual" index. its corresponding value will be interpolated.
const int k = floor(x / delta); // get left index to interpolate from original chunk data (right index will be this plus 1)
const float prop = (x / delta) - k; // get the proportion of the right value (left will be 1.0 minus this)
// const float prop2 = prop * prop; // cache the square of the proportion (needed only for cubic interpolation)
// usually just 2 channels: 0 (left) and 1 (right), but who knows...
for(int c = 0; c < audio_channel_count; c++)
{
// check if k will be within bounds
if(k * audio_channel_count + audio_channel_count - 1 < handler->chunk_size || handler->loop)
{
AUDIO_FORMAT_TYPE v0 = chunk_data[( k * audio_channel_count + c) % handler->chunk_size],
// v_ = chunk_data[((k-1) * audio_channel_count + c) % handler->chunk_size],
// v2 = chunk_data[((k+2) * audio_channel_count + c) % handler->chunk_size],
v1 = chunk_data[((k+1) * audio_channel_count + c) % handler->chunk_size];
// put interpolated value on 'data'
// buffer[i + c] = (1 - prop) * v0 + prop * v1; // linear interpolation
buffer[i + c] = v0 + prop * (v1 - v0); // linear interpolation (single-multiplication version)
// buffer[i + c] = v0 + 0.5f * prop * ((prop - 3) * v0 - (prop - 2) * 2 * v1 + (prop - 1) * v2); // quadratic interpolation
// buffer[i + c] = v0 + (prop / 6) * ((3 * prop - prop2 - 2) * v_ + (prop2 - 2 * prop - 1) * 3 * v0 + (prop - prop2 + 2) * 3 * v1 + (prop2 - 1) * v2); // cubic interpolation
// buffer[i + c] = v0 + 0.5f * prop * ((2 * prop2 - 3 * prop - 1) * (v0 - v1) + (prop2 - 2 * prop + 1) * (v0 - v_) + (prop2 - prop) * (v2 - v2)); // cubic spline interpolation
}
else // if k will be out of bounds (chunk bounds), it means we already finished; thus, we'll pass silence
{
buffer[i + c] = 0;
}
}
}
}
// update position
handler->position += (buffer_size / audio_channel_count) * vdelta;
// reset position if looping
if(handler->loop) while(handler->position > handler->duration)
handler->position -= handler->duration;
}
else // if we already played the whole sound but finished earlier than expected by SDL_mixer (due to faster playback speed)
{
// set silence on the buffer since Mix_HaltChannel() poops out some of it for a few ms.
for(int i = 0; i < buffer_size; i++)
buffer[i] = 0;
if(handler->self_halt)
Mix_HaltChannel(mix_channel); // XXX unsafe call, since it locks audio; but no safer solution was found yet...
}
}
/*
* main.c
*
* Created on: 10 jan 2018
* Author: Carlos Faruolo
*/
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#include "SDL2/SDL.h"
#include "SDL2/SDL_mixer.h"
#include "custom_mix_pitch.h"
/* example
run the executable passing an filename of a sound file that SDL_mixer is able to open (ogg, wav, ...) */
int main(int argc, char** argv)
{
if(argc < 2) { puts("Missing argument."); return 0; }
SDL_Init(SDL_INIT_AUDIO);
Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, 4096);
Mix_AllocateChannels(MIX_CHANNELS);
float speed = 1.0;
Mix_Chunk* chunk = Mix_LoadWAV(argv[1]);
if(chunk != NULL)
{
const int channel = Mix_PlayChannelTimed(-1, chunk, -1, 8000);
Custom_Mix_RegisterPlaybackSpeedEffect(channel, chunk, &speed, 1, 0);
puts("Looping for 8 seconds, changing the pitch dynamically...\n");
/* loop for 8 seconds, changing the pitch dynamically */
while(SDL_GetTicks() < 8000)
speed = 1 + 0.25*sin(0.001*SDL_GetTicks());
puts("Finished.");
}
else
puts("No data.");
Mix_FreeChunk(chunk);
Mix_CloseAudio();
Mix_Quit();
SDL_Quit();
return EXIT_SUCCESS;
}
@jasonjk192
Copy link

Hi, can I use your custom mix pitch library for a project I am working on?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment