├── CMakeLists.txt ├── LICENCE.txt ├── README.md ├── clownresampler.c ├── clownresampler.h ├── examples ├── .gitignore ├── CMakeLists.txt ├── high-level.c ├── libraries │ ├── dr_mp3.h │ └── miniaudio.h └── low-level.c └── tests ├── .gitignore ├── CMakeLists.txt ├── dr_flac.h ├── test-high-level.c ├── test-low-level.c ├── test.flac ├── test1 ├── test2 ├── test3 └── test4 /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | get_property(CLOWNRESAMPLER_INCLUDE_GUARD GLOBAL PROPERTY CLOWNRESAMPLER_INCLUDE_GUARD) 2 | if(NOT DEFINED CLOWNRESAMPLER_INCLUDE_GUARD) 3 | set_property(GLOBAL PROPERTY CLOWNRESAMPLER_INCLUDE_GUARD ON) 4 | 5 | cmake_minimum_required(VERSION 3.0...3.12) 6 | 7 | project(clownresampler LANGUAGES C) 8 | 9 | add_library(clownresampler STATIC "clownresampler.c" "clownresampler.h") 10 | 11 | endif() 12 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Clownacy 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 7 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 8 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 9 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 10 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 11 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 12 | PERFORMANCE OF THIS SOFTWARE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clownresampler 2 | 3 | This is a single-file library for resampling audio. It is written in C89 and 4 | licensed under the terms of the Zero-Clause BSD licence. 5 | 6 | In particular, this library implements a windowed-sinc resampler, using a 7 | Lanczos window. 8 | -------------------------------------------------------------------------------- /clownresampler.c: -------------------------------------------------------------------------------- 1 | #define CLOWNRESAMPLER_IMPLEMENTATION 2 | #include "clownresampler.h" 3 | -------------------------------------------------------------------------------- /clownresampler.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022-2023 Clownacy 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | /* 17 | clownresampler 18 | 19 | This is a single-file library for resampling audio. It is written in C89 and 20 | licensed under the terms of the Zero-Clause BSD licence. 21 | 22 | In particular, this library implements a windowed-sinc resampler, using a 23 | Lanczos window. 24 | 25 | https://github.com/Clownacy/clownresampler 26 | */ 27 | 28 | /* 29 | Contents: 30 | - 1. Examples 31 | - 2. Configuration 32 | - 3. Header & Documentation 33 | - 4. Implementation 34 | */ 35 | 36 | /* 1. Examples */ 37 | 38 | #if 0 39 | /* 40 | This demonstrates use of clownresampler's low-level API. 41 | 42 | The low-level API is ideal for when the entirety of the input data is available 43 | at once, whereas the high-level API is ideal for when the input data is 44 | streamed piece by piece. 45 | */ 46 | 47 | #include 48 | #include 49 | #include 50 | 51 | #define MINIAUDIO_IMPLEMENTATION 52 | #define MA_NO_DECODING 53 | #define MA_NO_ENCODING 54 | #define MA_NO_GENERATION 55 | #define MA_NO_ENGINE 56 | #define MA_NO_NODE_GRAPH 57 | #define MA_API static 58 | #include "libraries/miniaudio.h" /* v0.11.9 */ 59 | 60 | #define DR_MP3_IMPLEMENTATION 61 | #define DR_MP3_ONLY_MP3 62 | #define DRMP3_API static 63 | #include "libraries/dr_mp3.h" /* v0.6.33 */ 64 | 65 | #define CLOWNRESAMPLER_IMPLEMENTATION 66 | #define CLOWNRESAMPLER_STATIC 67 | #define CLOWNRESAMPLER_NO_HIGH_LEVEL_API /* We do not need the high-level API. */ 68 | #include "../clownresampler.h" 69 | 70 | static ClownResampler_Precomputed precomputed; 71 | static ClownResampler_LowLevel_State resampler; 72 | static unsigned int total_channels; 73 | static drmp3_int16 *resampler_input_buffer; 74 | static size_t resampler_input_buffer_total_frames; 75 | static size_t resampler_input_buffer_frames_remaining; 76 | 77 | typedef struct ResamplerCallbackData 78 | { 79 | ma_int16 *output_pointer; 80 | ma_uint32 output_buffer_frames_remaining; 81 | } ResamplerCallbackData; 82 | 83 | static cc_bool ResamplerOutputCallback(void *user_data, const cc_s32f *frame, cc_u8f total_samples) 84 | { 85 | ResamplerCallbackData* const callback_data = (ResamplerCallbackData*)user_data; 86 | 87 | cc_u8f i; 88 | 89 | /* Output the frame. */ 90 | for (i = 0; i < total_samples; ++i) 91 | { 92 | cc_s32f sample; 93 | 94 | sample = frame[i]; 95 | 96 | /* Clamp the sample to 16-bit. */ 97 | if (sample > 0x7FFF) 98 | sample = 0x7FFF; 99 | else if (sample < -0x7FFF) 100 | sample = -0x7FFF; 101 | 102 | /* Push the sample to the output buffer. */ 103 | *callback_data->output_pointer++ = (ma_int16)sample; 104 | } 105 | 106 | /* Signal whether there is more room in the output buffer. */ 107 | return --callback_data->output_buffer_frames_remaining != 0; 108 | } 109 | 110 | static void AudioCallback(ma_device *device, void *output, const void *input, ma_uint32 frame_count) 111 | { 112 | ResamplerCallbackData callback_data; 113 | 114 | (void)device; 115 | (void)input; 116 | 117 | callback_data.output_pointer = (ma_int16*)output; 118 | callback_data.output_buffer_frames_remaining = frame_count; 119 | 120 | /* Resample the decoded audio data. */ 121 | ClownResampler_LowLevel_Resample(&resampler, &precomputed, &resampler_input_buffer[(resampler_input_buffer_total_frames - resampler_input_buffer_frames_remaining) * total_channels], &resampler_input_buffer_frames_remaining, ResamplerOutputCallback, &callback_data); 122 | 123 | /* If there are no more samples left, then fill the remaining space in the buffer with 0. */ 124 | memset(callback_data.output_pointer, 0, callback_data.output_buffer_frames_remaining * total_channels * sizeof(ma_int16)); 125 | } 126 | 127 | int main(int argc, char **argv) 128 | { 129 | int exit_code; 130 | drmp3 mp3_decoder; 131 | 132 | exit_code = EXIT_FAILURE; 133 | 134 | if (argc < 2) 135 | { 136 | fputs("Pass the path to an MP3 file as an argument.\n", stderr); 137 | } 138 | else 139 | { 140 | if (!drmp3_init_file(&mp3_decoder, argv[1], NULL)) 141 | { 142 | fputs("Failed to initialise MP3 decoder.\n", stderr); 143 | } 144 | else 145 | { 146 | /******************************/ 147 | /* Initialise audio playback. */ 148 | /******************************/ 149 | ma_device_config miniaudio_config; 150 | ma_device miniaudio_device; 151 | 152 | miniaudio_config = ma_device_config_init(ma_device_type_playback); 153 | miniaudio_config.playback.format = ma_format_s16; 154 | miniaudio_config.playback.channels = mp3_decoder.channels; 155 | miniaudio_config.sampleRate = 0; /* Use whatever sample rate the playback device wants. */ 156 | miniaudio_config.dataCallback = AudioCallback; 157 | miniaudio_config.pUserData = NULL; 158 | 159 | if (ma_device_init(NULL, &miniaudio_config, &miniaudio_device) != MA_SUCCESS) 160 | { 161 | drmp3_uninit(&mp3_decoder); 162 | fputs("Failed to initialise playback device.\n", stderr); 163 | } 164 | else 165 | { 166 | /*****************************************/ 167 | /* Finished initialising audio playback. */ 168 | /*****************************************/ 169 | 170 | const size_t size_of_frame = mp3_decoder.channels * sizeof(drmp3_int16); 171 | 172 | size_t total_mp3_pcm_frames; 173 | 174 | total_mp3_pcm_frames = drmp3_get_pcm_frame_count(&mp3_decoder); 175 | total_channels = mp3_decoder.channels; 176 | 177 | /* Inform the user of the input and output sample rates. */ 178 | fprintf(stderr, "MP3 Sample Rate: %lu\n", (unsigned long)mp3_decoder.sampleRate); 179 | fprintf(stderr, "Playback Sample Rate: %lu\n", (unsigned long)miniaudio_device.sampleRate); 180 | fflush(stderr); 181 | 182 | /******************************/ 183 | /* Initialise clownresampler. */ 184 | /******************************/ 185 | 186 | /* Precompute the Lanczos kernel. */ 187 | ClownResampler_Precompute(&precomputed); 188 | 189 | /* Create a resampler that converts from the sample rate of the MP3 to the sample rate of the playback device. */ 190 | /* The low-pass filter is set to 44100Hz since that should allow all human-perceivable frequencies through. */ 191 | ClownResampler_LowLevel_Init(&resampler, mp3_decoder.channels, mp3_decoder.sampleRate, miniaudio_device.sampleRate, 44100); 192 | 193 | /*****************************************/ 194 | /* Finished initialising clownresampler. */ 195 | /*****************************************/ 196 | 197 | /*****************************************/ 198 | /* Set up clownresampler's input buffer. */ 199 | /*****************************************/ 200 | 201 | /* Create a buffer to hold the decoded PCM data. */ 202 | /* clownresampler's low-level API requires that this buffer have padding at its beginning and end. */ 203 | resampler_input_buffer = (drmp3_int16*)malloc((resampler.lowest_level.integer_stretched_kernel_radius * 2 + total_mp3_pcm_frames) * size_of_frame); 204 | 205 | if (resampler_input_buffer == NULL) 206 | { 207 | drmp3_uninit(&mp3_decoder); 208 | fputs("Failed to allocate memory for resampler input buffer.\n", stderr); 209 | } 210 | else 211 | { 212 | /* Set the padding samples at the start to 0. */ 213 | memset(&resampler_input_buffer[0], 0, resampler.lowest_level.integer_stretched_kernel_radius * size_of_frame); 214 | 215 | /* Decode the MP3 to the input buffer. */ 216 | drmp3_read_pcm_frames_s16(&mp3_decoder, total_mp3_pcm_frames, &resampler_input_buffer[resampler.lowest_level.integer_stretched_kernel_radius * total_channels]); 217 | drmp3_uninit(&mp3_decoder); 218 | 219 | /* Set the padding samples at the end to 0. */ 220 | memset(&resampler_input_buffer[(resampler.lowest_level.integer_stretched_kernel_radius + total_mp3_pcm_frames) * total_channels], 0, resampler.lowest_level.integer_stretched_kernel_radius * size_of_frame); 221 | 222 | /* Initialise some variables that will be used by the audio callback. */ 223 | resampler_input_buffer_total_frames = resampler_input_buffer_frames_remaining = total_mp3_pcm_frames; 224 | 225 | /*****************************************************/ 226 | /* Finished setting up the resampler's input buffer. */ 227 | /*****************************************************/ 228 | 229 | /* Begin playback. */ 230 | ma_device_start(&miniaudio_device); 231 | 232 | /* Wait for input from the user before terminating the program. */ 233 | fgetc(stdin); 234 | 235 | ma_device_stop(&miniaudio_device); 236 | 237 | free(resampler_input_buffer); 238 | 239 | exit_code = EXIT_SUCCESS; 240 | } 241 | 242 | ma_device_uninit(&miniaudio_device); 243 | } 244 | } 245 | } 246 | 247 | return exit_code; 248 | } 249 | #endif 250 | 251 | #if 0 252 | /* 253 | This demonstrates use of clownresampler's high-level API. 254 | 255 | The low-level API is ideal for when the entirety of the input data is available 256 | at once, whereas the high-level API is ideal for when the input data is 257 | streamed piece by piece. 258 | */ 259 | 260 | #include 261 | #include 262 | #include 263 | 264 | #define MINIAUDIO_IMPLEMENTATION 265 | #define MA_NO_DECODING 266 | #define MA_NO_ENCODING 267 | #define MA_NO_GENERATION 268 | #define MA_NO_ENGINE 269 | #define MA_NO_NODE_GRAPH 270 | #define MA_API static 271 | #include "libraries/miniaudio.h" /* v0.11.9 */ 272 | 273 | #define DR_MP3_IMPLEMENTATION 274 | #define DR_MP3_ONLY_MP3 275 | #define DRMP3_API static 276 | #include "libraries/dr_mp3.h" /* v0.6.33 */ 277 | 278 | #define CLOWNRESAMPLER_IMPLEMENTATION 279 | #define CLOWNRESAMPLER_STATIC 280 | #include "../clownresampler.h" 281 | 282 | static ClownResampler_Precomputed precomputed; 283 | static ClownResampler_HighLevel_State resampler; 284 | static drmp3 mp3_decoder; 285 | static unsigned int total_channels; 286 | 287 | typedef struct ResamplerCallbackData 288 | { 289 | ma_int16 *output_pointer; 290 | ma_uint32 output_buffer_frames_remaining; 291 | } ResamplerCallbackData; 292 | 293 | static size_t ResamplerInputCallback(void *user_data, cc_s16l *buffer, size_t total_frames) 294 | { 295 | (void)user_data; 296 | 297 | /* Obtain samples from the MP3 file. */ 298 | return drmp3_read_pcm_frames_s16(&mp3_decoder, total_frames, buffer); 299 | } 300 | 301 | static cc_bool ResamplerOutputCallback(void *user_data, const cc_s32f *frame, cc_u8f total_samples) 302 | { 303 | ResamplerCallbackData* const callback_data = (ResamplerCallbackData*)user_data; 304 | 305 | cc_u8f i; 306 | 307 | /* Output the frame. */ 308 | for (i = 0; i < total_samples; ++i) 309 | { 310 | cc_s32f sample; 311 | 312 | sample = frame[i]; 313 | 314 | /* Clamp the sample to 16-bit. */ 315 | if (sample > 0x7FFF) 316 | sample = 0x7FFF; 317 | else if (sample < -0x7FFF) 318 | sample = -0x7FFF; 319 | 320 | /* Push the sample to the output buffer. */ 321 | *callback_data->output_pointer++ = (ma_int16)sample; 322 | } 323 | 324 | /* Signal whether there is more room in the output buffer. */ 325 | return --callback_data->output_buffer_frames_remaining != 0; 326 | } 327 | 328 | static void AudioCallback(ma_device *device, void *output, const void *input, ma_uint32 frame_count) 329 | { 330 | ResamplerCallbackData callback_data; 331 | 332 | (void)device; 333 | (void)input; 334 | 335 | callback_data.output_pointer = (ma_int16*)output; 336 | callback_data.output_buffer_frames_remaining = frame_count; 337 | 338 | /* Resample the decoded audio data. */ 339 | ClownResampler_HighLevel_Resample(&resampler, &precomputed, ResamplerInputCallback, ResamplerOutputCallback, &callback_data); 340 | 341 | /* If there are no more samples left, then fill the remaining space in the buffer with 0. */ 342 | memset(callback_data.output_pointer, 0, callback_data.output_buffer_frames_remaining * total_channels * sizeof(ma_int16)); 343 | } 344 | 345 | int main(int argc, char **argv) 346 | { 347 | int exit_code; 348 | 349 | exit_code = EXIT_FAILURE; 350 | 351 | if (argc < 2) 352 | { 353 | fputs("Pass the path to an MP3 file as an argument.\n", stderr); 354 | } 355 | else 356 | { 357 | if (!drmp3_init_file(&mp3_decoder, argv[1], NULL)) 358 | { 359 | fputs("Failed to initialise MP3 decoder.\n", stderr); 360 | } 361 | else 362 | { 363 | /******************************/ 364 | /* Initialise audio playback. */ 365 | /******************************/ 366 | ma_device_config miniaudio_config; 367 | ma_device miniaudio_device; 368 | 369 | miniaudio_config = ma_device_config_init(ma_device_type_playback); 370 | miniaudio_config.playback.format = ma_format_s16; 371 | miniaudio_config.playback.channels = mp3_decoder.channels; 372 | miniaudio_config.sampleRate = 0; /* Use whatever sample rate the playback device wants. */ 373 | miniaudio_config.dataCallback = AudioCallback; 374 | miniaudio_config.pUserData = NULL; 375 | 376 | if (ma_device_init(NULL, &miniaudio_config, &miniaudio_device) != MA_SUCCESS) 377 | { 378 | fputs("Failed to initialise playback device.\n", stderr); 379 | } 380 | else 381 | { 382 | /*****************************************/ 383 | /* Finished initialising audio playback. */ 384 | /*****************************************/ 385 | 386 | /* Inform the user of the input and output sample rates. */ 387 | fprintf(stderr, "MP3 Sample Rate: %lu\n", (unsigned long)mp3_decoder.sampleRate); 388 | fprintf(stderr, "Playback Sample Rate: %lu\n", (unsigned long)miniaudio_device.sampleRate); 389 | fflush(stderr); 390 | 391 | /******************************/ 392 | /* Initialise clownresampler. */ 393 | /******************************/ 394 | 395 | /* Precompute the Lanczos kernel. */ 396 | ClownResampler_Precompute(&precomputed); 397 | 398 | /* Create a resampler that converts from the sample rate of the MP3 to the sample rate of the playback device. */ 399 | /* The low-pass filter is set to 44100Hz since that should allow all human-perceivable frequencies through. */ 400 | ClownResampler_HighLevel_Init(&resampler, mp3_decoder.channels, mp3_decoder.sampleRate, miniaudio_device.sampleRate, 44100); 401 | 402 | /*****************************************/ 403 | /* Finished initialising clownresampler. */ 404 | /*****************************************/ 405 | 406 | total_channels = mp3_decoder.channels; 407 | 408 | /* Begin playback. */ 409 | ma_device_start(&miniaudio_device); 410 | 411 | /* Wait for input from the user before terminating the program. */ 412 | fgetc(stdin); 413 | 414 | ma_device_uninit(&miniaudio_device); 415 | 416 | exit_code = EXIT_SUCCESS; 417 | } 418 | 419 | drmp3_uninit(&mp3_decoder); 420 | } 421 | } 422 | 423 | return exit_code; 424 | } 425 | #endif 426 | 427 | #ifndef CLOWNRESAMPLER_GUARD_MISC 428 | #define CLOWNRESAMPLER_GUARD_MISC 429 | 430 | 431 | /* 2. Configuration */ 432 | 433 | /* Define 'CLOWNRESAMPLER_STATIC' to limit the visibility of public functions. */ 434 | /* Alternatively, define 'CLOWNRESAMPLER_API' to control the qualifiers applied to the public functions. */ 435 | #ifndef CLOWNRESAMPLER_API 436 | #ifdef CLOWNRESAMPLER_STATIC 437 | #define CLOWNRESAMPLER_API static 438 | #else 439 | #define CLOWNRESAMPLER_API 440 | #endif 441 | #endif 442 | 443 | /* Controls the number of 'lobes' of the windowed sinc function. 444 | A higher number results in better audio, but is more expensive. */ 445 | #ifndef CLOWNRESAMPLER_KERNEL_RADIUS 446 | #define CLOWNRESAMPLER_KERNEL_RADIUS 3 447 | #endif 448 | 449 | /* How many samples to render per lobe for the pre-computed Lanczos kernel. 450 | Higher numbers produce a higher-quality Lanczos kernel, but cause it to take 451 | up more memory and cache. */ 452 | #ifndef CLOWNRESAMPLER_KERNEL_RESOLUTION 453 | #define CLOWNRESAMPLER_KERNEL_RESOLUTION 0x400 /* 1024 samples per lobe should be more than good enough */ 454 | #endif 455 | 456 | /* The maximum number of channels supported by the resampler. 457 | This will likely be removed in the future. */ 458 | #ifndef CLOWNRESAMPLER_MAXIMUM_CHANNELS 459 | #define CLOWNRESAMPLER_MAXIMUM_CHANNELS 16 /* As stb_vorbis says, this should be enough for pretty much everyone. */ 460 | #endif 461 | 462 | /* Disables the low-level API. */ 463 | /*#define CLOWNRESAMPLER_NO_LOW_LEVEL_API*/ 464 | 465 | /* Disables the high-level API. */ 466 | /*#define CLOWNRESAMPLER_NO_HIGH_LEVEL_API*/ 467 | 468 | /* Disables the ClownResampler_HighLevel_Adjust function. */ 469 | /*#define CLOWNRESAMPLER_NO_HIGH_LEVEL_ADJUST*/ 470 | 471 | /* Disables the ClownResampler_HighLevel_ResampleEnd function. */ 472 | /*#define CLOWNRESAMPLER_NO_HIGH_LEVEL_RESAMPLE_END*/ 473 | 474 | 475 | /* 3. Header & Documentation */ 476 | 477 | #include 478 | 479 | /* Integer types. */ 480 | #ifndef CC_INTEGERS_DEFINED 481 | #define CC_INTEGERS_DEFINED 482 | 483 | #if defined(CC_USE_C99_INTEGERS) 484 | /* Use C99's/C++11's better integer types if available. */ 485 | #include 486 | 487 | typedef int_least8_t cc_s8l; 488 | typedef int_least16_t cc_s16l; 489 | typedef int_least32_t cc_s32l; 490 | 491 | typedef uint_least8_t cc_u8l; 492 | typedef uint_least16_t cc_u16l; 493 | typedef uint_least32_t cc_u32l; 494 | 495 | typedef int_fast8_t cc_s8f; 496 | typedef int_fast16_t cc_s16f; 497 | typedef int_fast32_t cc_s32f; 498 | 499 | typedef uint_fast8_t cc_u8f; 500 | typedef uint_fast16_t cc_u16f; 501 | typedef uint_fast32_t cc_u32f; 502 | 503 | #define CC_PRIdLEAST8 PRIdLEAST8 504 | #define CC_PRIiLEAST8 PRIiLEAST8 505 | #define CC_PRIuLEAST8 PRIuLEAST8 506 | #define CC_PRIoLEAST8 PRIoLEAST8 507 | #define CC_PRIxLEAST8 PRIxLEAST8 508 | #define CC_PRIXLEAST8 PRIXLEAST8 509 | 510 | #define CC_PRIdLEAST16 PRIdLEAST16 511 | #define CC_PRIiLEAST16 PRIiLEAST16 512 | #define CC_PRIuLEAST16 PRIuLEAST16 513 | #define CC_PRIoLEAST16 PRIoLEAST16 514 | #define CC_PRIxLEAST16 PRIxLEAST16 515 | #define CC_PRIXLEAST16 PRIXLEAST16 516 | 517 | #define CC_PRIdLEAST32 PRIdLEAST32 518 | #define CC_PRIiLEAST32 PRIiLEAST32 519 | #define CC_PRIuLEAST32 PRIuLEAST32 520 | #define CC_PRIoLEAST32 PRIoLEAST32 521 | #define CC_PRIxLEAST32 PRIxLEAST32 522 | #define CC_PRIXLEAST32 PRIXLEAST32 523 | 524 | #define CC_PRIdFAST8 PRIdFAST8 525 | #define CC_PRIiFAST8 PRIiFAST8 526 | #define CC_PRIuFAST8 PRIuFAST8 527 | #define CC_PRIoFAST8 PRIoFAST8 528 | #define CC_PRIxFAST8 PRIxFAST8 529 | #define CC_PRIXFAST8 PRIXFAST8 530 | 531 | #define CC_PRIdFAST16 PRIdFAST16 532 | #define CC_PRIiFAST16 PRIiFAST16 533 | #define CC_PRIuFAST16 PRIuFAST16 534 | #define CC_PRIoFAST16 PRIoFAST16 535 | #define CC_PRIxFAST16 PRIxFAST16 536 | #define CC_PRIXFAST16 PRIXFAST16 537 | 538 | #define CC_PRIdFAST32 PRIdFAST32 539 | #define CC_PRIiFAST32 PRIiFAST32 540 | #define CC_PRIuFAST32 PRIuFAST32 541 | #define CC_PRIoFAST32 PRIoFAST32 542 | #define CC_PRIxFAST32 PRIxFAST32 543 | #define CC_PRIXFAST32 PRIXFAST32 544 | #else 545 | /* Fall back on C89's/C++98's dumb types. */ 546 | typedef signed char cc_s8l; 547 | typedef signed short cc_s16l; 548 | typedef signed long cc_s32l; 549 | 550 | typedef unsigned char cc_u8l; 551 | typedef unsigned short cc_u16l; 552 | typedef unsigned long cc_u32l; 553 | 554 | typedef signed int cc_s8f; 555 | typedef signed int cc_s16f; 556 | typedef signed long cc_s32f; 557 | 558 | typedef unsigned int cc_u8f; 559 | typedef unsigned int cc_u16f; 560 | typedef unsigned long cc_u32f; 561 | 562 | #define CC_PRIdLEAST8 "%d" 563 | #define CC_PRIiLEAST8 "%i" 564 | #define CC_PRIuLEAST8 "%u" 565 | #define CC_PRIoLEAST8 "%o" 566 | #define CC_PRIxLEAST8 "%x" 567 | #define CC_PRIXLEAST8 "%X" 568 | 569 | #define CC_PRIdLEAST16 "%d" 570 | #define CC_PRIiLEAST16 "%i" 571 | #define CC_PRIuLEAST16 "%u" 572 | #define CC_PRIoLEAST16 "%o" 573 | #define CC_PRIxLEAST16 "%x" 574 | #define CC_PRIXLEAST16 "%X" 575 | 576 | #define CC_PRIdLEAST32 "%ld" 577 | #define CC_PRIiLEAST32 "%li" 578 | #define CC_PRIuLEAST32 "%lu" 579 | #define CC_PRIoLEAST32 "%lo" 580 | #define CC_PRIxLEAST32 "%lx" 581 | #define CC_PRIXLEAST32 "%lX" 582 | 583 | #define CC_PRIdFAST8 "%d" 584 | #define CC_PRIiFAST8 "%i" 585 | #define CC_PRIuFAST8 "%u" 586 | #define CC_PRIoFAST8 "%o" 587 | #define CC_PRIxFAST8 "%x" 588 | #define CC_PRIXFAST8 "%X" 589 | 590 | #define CC_PRIdFAST16 "%d" 591 | #define CC_PRIiFAST16 "%i" 592 | #define CC_PRIuFAST16 "%u" 593 | #define CC_PRIoFAST16 "%o" 594 | #define CC_PRIxFAST16 "%x" 595 | #define CC_PRIXFAST16 "%X" 596 | 597 | #define CC_PRIdFAST32 "%ld" 598 | #define CC_PRIiFAST32 "%li" 599 | #define CC_PRIuFAST32 "%lu" 600 | #define CC_PRIoFAST32 "%lo" 601 | #define CC_PRIxFAST32 "%lx" 602 | #define CC_PRIXFAST32 "%lX" 603 | #endif 604 | 605 | /* Boolean. */ 606 | typedef cc_u8l cc_bool; 607 | enum 608 | { 609 | cc_false = 0, 610 | cc_true = 1 611 | }; 612 | 613 | #endif 614 | 615 | #define CLOWNRESAMPLER_COUNT_OF(x) (sizeof(x) / sizeof(*(x))) 616 | #define CLOWNRESAMPLER_MIN(a, b) ((a) < (b) ? (a) : (b)) 617 | #define CLOWNRESAMPLER_MAX(a, b) ((a) > (b) ? (a) : (b)) 618 | #define CLOWNRESAMPLER_CLAMP(min, max, x) (CLOWNRESAMPLER_MAX((min), CLOWNRESAMPLER_MIN((max), (x)))) 619 | 620 | #define CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE (1L << 16) /* For 16.16. This is good because it reduces multiplications and divisions to mere bit-shifts. */ 621 | #define CLOWNRESAMPLER_TO_FIXED_POINT_FROM_INTEGER(x) ((x) * CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE) 622 | #define CLOWNRESAMPLER_TO_INTEGER_FROM_FIXED_POINT_FLOOR(x) ((x) / CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE) 623 | #define CLOWNRESAMPLER_TO_INTEGER_FROM_FIXED_POINT_ROUND(x) (((x) + (CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE / 2)) / CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE) 624 | #define CLOWNRESAMPLER_TO_INTEGER_FROM_FIXED_POINT_CEILING(x) (((x) + (CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE - 1)) / CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE) 625 | #define CLOWNRESAMPLER_FIXED_POINT_MULTIPLY(a, b) ((a) * (b) / CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE) 626 | 627 | typedef struct ClownResampler_Precomputed 628 | { 629 | cc_s32l lanczos_kernel_table[CLOWNRESAMPLER_KERNEL_RADIUS * 2 * CLOWNRESAMPLER_KERNEL_RESOLUTION]; 630 | } ClownResampler_Precomputed; 631 | 632 | typedef struct ClownResampler_LowestLevel_Configuration 633 | { 634 | size_t stretched_kernel_radius; /* 16.16 fixed point. */ 635 | size_t integer_stretched_kernel_radius; 636 | size_t stretched_kernel_radius_delta; /* 16.16 fixed point. */ 637 | size_t kernel_step_size; 638 | } ClownResampler_LowestLevel_Configuration; 639 | 640 | typedef struct ClownResampler_LowLevel_State 641 | { 642 | ClownResampler_LowestLevel_Configuration lowest_level; 643 | 644 | cc_u8f channels; 645 | size_t position_integer; 646 | cc_u32f position_fractional; /* 16.16 fixed point. */ 647 | cc_u32f increment; /* 16.16 fixed point. */ 648 | } ClownResampler_LowLevel_State; 649 | 650 | typedef struct ClownResampler_HighLevel_State 651 | { 652 | ClownResampler_LowLevel_State low_level; 653 | 654 | cc_s16l input_buffer[0x1000]; /* TODO: This should be dynamically allocated in accordance with the kernel radius... */ 655 | cc_s16l *input_buffer_start; 656 | cc_s16l *input_buffer_end; 657 | size_t maximum_integer_stretched_kernel_radius; 658 | size_t leading_padding_frames_needed, trailing_padding_frames_remaining; 659 | } ClownResampler_HighLevel_State; 660 | 661 | typedef size_t (*ClownResampler_InputCallback)(void *user_data, cc_s16l *buffer, size_t total_frames); 662 | typedef cc_bool (*ClownResampler_OutputCallback)(void *user_data, const cc_s32f *frame, cc_u8f total_samples); 663 | 664 | #endif /* CLOWNRESAMPLER_GUARD_MISC */ 665 | 666 | #if !defined(CLOWNRESAMPLER_STATIC) || defined(CLOWNRESAMPLER_IMPLEMENTATION) 667 | 668 | #ifdef __cplusplus 669 | extern "C" { 670 | #endif 671 | 672 | #ifndef CLOWNRESAMPLER_GUARD_FUNCTION_DECLARATIONS 673 | #define CLOWNRESAMPLER_GUARD_FUNCTION_DECLARATIONS 674 | /* Common API. 675 | This API is used for both the low-level and high-level APIs. */ 676 | 677 | /* Precomputes some data to improve the performance of the resampler. 678 | Multiple resamplers can use the same 'ClownResampler_Precomputed'. 679 | The output of this function is always the same, so if you want to avoid 680 | calling this function, then you could dump the contents of the struct and 681 | then insert a const 'ClownResampler_Precomputed' in your source code. */ 682 | CLOWNRESAMPLER_API void ClownResampler_Precompute(ClownResampler_Precomputed *precomputed); 683 | 684 | 685 | 686 | /* Lowest-level API. */ 687 | CLOWNRESAMPLER_API cc_bool ClownResampler_LowestLevel_Configure(ClownResampler_LowestLevel_Configuration *configuration, cc_u32f input_sample_rate, cc_u32f output_sample_rate, cc_u32f low_pass_filter_sample_rate); 688 | CLOWNRESAMPLER_API void ClownResampler_LowestLevel_Resample(const ClownResampler_LowestLevel_Configuration *configuration, const ClownResampler_Precomputed *precomputed, cc_s32f *output_frame, cc_u8f channels, const cc_s16l *input_buffer, size_t position_integer, cc_u32f position_fractional); 689 | 690 | #endif /* CLOWNRESAMPLER_GUARD_FUNCTION_DECLARATIONS */ 691 | 692 | 693 | 694 | #ifndef CLOWNRESAMPLER_NO_LOW_LEVEL_API 695 | /* Low-level API. 696 | This API has lower overhead, but is more difficult to use, requiring that 697 | audio be pre-processed before resampling. 698 | Do NOT mix low-level API calls with high-level API calls for the same 699 | resampler! */ 700 | 701 | 702 | /* Initialises a low-level resampler. This function must be called before the 703 | state is passed to any other functions. The input and output sample rates do 704 | not actually have to match the sample rates being used - they just need to 705 | provide the ratio between the two (for example, 1 and 2 works just as well 706 | as 22050 and 44100). Remember that a sample rate is double the frequency. 707 | The 'channels' parameter must not be larger than 708 | CLOWNRESAMPLER_MAXIMUM_CHANNELS. 709 | 710 | Returns 'cc_false' on failure, and 'cc_true' otherwise. */ 711 | CLOWNRESAMPLER_API cc_bool ClownResampler_LowLevel_Init(ClownResampler_LowLevel_State *resampler, cc_u8f channels, cc_u32f input_sample_rate, cc_u32f output_sample_rate, cc_u32f low_pass_filter_sample_rate); 712 | 713 | /* Adjusts properties of the resampler. The input and output sample rates do 714 | not actually have to match the sample rates being used - they just need to 715 | provide the ratio between the two (for example, 1 and 2 works just as well 716 | as 22050 and 44100). Remember that a sample rate is double the frequency. 717 | 718 | Returns 'cc_false' on failure, and 'cc_true' otherwise. */ 719 | CLOWNRESAMPLER_API cc_bool ClownResampler_LowLevel_Adjust(ClownResampler_LowLevel_State *resampler, cc_u32f input_sample_rate, cc_u32f output_sample_rate, cc_u32f low_pass_filter_sample_rate); 720 | 721 | /* Resamples (pre-processed) audio. The 'total_input_frames' and 722 | 'total_output_frames' parameters measure the size of their respective 723 | buffers in frames, not samples nor bytes. 724 | 725 | The input buffer must be specially pre-processed, so that it is padded with 726 | extra frames at the beginning and end. This is needed as the resampler will 727 | unavoidably read past the beginning and the end of the audio data. The 728 | specific number of frames needed at the beginning and end can be found in 729 | the 'resampler->integer_stretched_kernel_radius' variable. If the audio you 730 | are resampling is a chunk of a larger piece of audio, then the 'padding' at 731 | the beginning and end must be the frames from before and after said chunk 732 | of audio, otherwise these frames should just be 0. Note that these padding 733 | frames must not be counted by the 'total_input_frames' parameter. 734 | 735 | 'output_callback' is a callback for outputting a single completed frame. 736 | 'frame' points to a series of samples corresponding a frame of audio. 737 | 'total_samples' is the number of samples in the frame, which will always 738 | match the number of channels that was passed to 739 | 'ClownResampler_LowLevel_Init'. Must return 0 if no more frames are needed, 740 | in which case this function terminates. The 'user_data' parameter is the 741 | same as the 'user_data' parameter of this function. 742 | 743 | After this function returns, the 'total_input_frames' parameter will 744 | contain the number of frames in the input buffer that were not processed. 745 | 746 | This function will return 'cc_true' if it terminated because it ran out of 747 | input samples, or 'cc_false' if it terminated because the callback returned 748 | 0. */ 749 | CLOWNRESAMPLER_API cc_bool ClownResampler_LowLevel_Resample(ClownResampler_LowLevel_State *resampler, const ClownResampler_Precomputed *precomputed, const cc_s16l *input_buffer, size_t *total_input_frames, ClownResampler_OutputCallback output_callback, const void *user_data); 750 | #endif /* CLOWNRESAMPLER_NO_LOW_LEVEL_API */ 751 | 752 | 753 | 754 | #ifndef CLOWNRESAMPLER_NO_HIGH_LEVEL_API 755 | /* High-level API. 756 | This API has more overhead, but is easier to use. 757 | Do NOT mix high-level API calls with low-level API calls for the same 758 | resampler! */ 759 | 760 | 761 | /* Initialises a high-level resampler. This function must be called before the 762 | state is passed to any other functions. The input and output sample rates do 763 | not actually have to match the sample rates being used - they just need to 764 | provide the ratio between the two (for example, 1 and 2 works just as well 765 | as 22050 and 44100). Remember that a sample rate is double the frequency. 766 | The 'channels' parameter must not be larger than 767 | CLOWNRESAMPLER_MAXIMUM_CHANNELS. 768 | 769 | Returns 'cc_false' on failure, and 'cc_true' otherwise. */ 770 | CLOWNRESAMPLER_API cc_bool ClownResampler_HighLevel_Init(ClownResampler_HighLevel_State *resampler, cc_u8f channels, cc_u32f input_sample_rate, cc_u32f output_sample_rate, cc_u32f low_pass_filter_sample_rate); 771 | 772 | /* Resamples audio. This function returns when either the output buffer is 773 | full, or the input callback stops providing frames. 774 | 775 | This function will return 'cc_true' if it terminated because the input 776 | callback returned 0, or 'cc_false' if it terminated because the output 777 | callback returned 0. 778 | 779 | The parameters are as follows: 780 | 781 | 'resampler' 782 | 783 | A pointer to a state struct that was previously initialised with the 784 | 'ClownResampler_HighLevel_Init' function. 785 | 786 | 787 | 'output_buffer' 788 | 789 | A pointer to a buffer which the resampled audio will be written to. 790 | The size of the audio buffer will be specified by the 'total_output_frames' 791 | variable. 792 | 793 | 794 | 'total_output_frames' 795 | 796 | The size of the buffer specified by the 'output_buffer' parameter. The size 797 | is measured in frames, not samples nor bytes. 798 | 799 | 800 | 'input_callback' 801 | 802 | A callback for retrieving frames of the input audio. The callback must 803 | write frames to the buffer pointed to by the 'buffer' parameter. The 804 | 'total_frames' parameter specifies the maximum number of frames that can be 805 | written to the buffer. The callback must return the number of frames that 806 | were written to the buffer. If the callback returns 0, then this function 807 | terminates. The 'user_data' parameter is the same as the 'user_data' 808 | parameter of this function. 809 | 810 | 811 | 'output_callback' 812 | 813 | 814 | A callback for outputting a single completed frame. 'frame' points to a 815 | series of samples corresponding a frame of audio. 'total_samples' is the 816 | number of samples in the frame, which will always match the number of 817 | channels that was passed to 'ClownResampler_HighLevel_Init'. Must return 0 818 | if no more frames are needed, in which case this function terminates. The 819 | 'user_data' parameter is the same as the 'user_data' parameter of this 820 | function. 821 | 822 | 823 | 'user_data' 824 | An arbitrary pointer that is passed to the callback functions. */ 825 | CLOWNRESAMPLER_API cc_bool ClownResampler_HighLevel_Resample(ClownResampler_HighLevel_State *resampler, const ClownResampler_Precomputed *precomputed, ClownResampler_InputCallback input_callback, ClownResampler_OutputCallback output_callback, const void *user_data); 826 | #endif /* CLOWNRESAMPLER_NO_HIGH_LEVEL_API */ 827 | 828 | #if !defined(CLOWNRESAMPLER_NO_HIGH_LEVEL_ADJUST) && !defined(CLOWNRESAMPLER_NO_HIGH_LEVEL_API) 829 | /* Adjusts properties of the resampler. The input and output sample rates do 830 | not actually have to match the sample rates being used - they just need to 831 | provide the ratio between the two (for example, 1 and 2 works just as well 832 | as 22050 and 44100). Remember that a sample rate is double the frequency. 833 | 834 | Unlike in the low-level API, when the input sample rate is higher than the 835 | output sample rate, the ratio between the two MUST NOT be wider than that 836 | of the rates passed to the 'ClownResampler_HighLevel_Init' function. 837 | 838 | Returns 'cc_false' on failure, and 'cc_true' otherwise. */ 839 | CLOWNRESAMPLER_API cc_bool ClownResampler_HighLevel_Adjust(ClownResampler_HighLevel_State *resampler, cc_u32f input_sample_rate, cc_u32f output_sample_rate, cc_u32f low_pass_filter_sample_rate); 840 | #endif /* CLOWNRESAMPLER_NO_HIGH_LEVEL_ADJUST */ 841 | 842 | #if !defined(CLOWNRESAMPLER_NO_HIGH_LEVEL_RESAMPLE_END) && !defined(CLOWNRESAMPLER_NO_HIGH_LEVEL_API) 843 | /* This is to be used after the final call to 844 | 'ClownResampler_HighLevel_Resample', to output the last few samples. 845 | 846 | Returns 'cc_true' when the final sample has been output. */ 847 | CLOWNRESAMPLER_API cc_bool ClownResampler_HighLevel_ResampleEnd(ClownResampler_HighLevel_State *resampler, const ClownResampler_Precomputed *precomputed, ClownResampler_OutputCallback output_callback, const void *user_data); 848 | #endif /* CLOWNRESAMPLER_NO_HIGH_LEVEL_RESAMPLE_END */ 849 | 850 | #ifdef __cplusplus 851 | } 852 | #endif 853 | 854 | #endif /* !defined(CLOWNRESAMPLER_STATIC) || defined(CLOWNRESAMPLER_IMPLEMENTATION) */ 855 | 856 | 857 | /* 4. Implementation */ 858 | 859 | #ifdef CLOWNRESAMPLER_IMPLEMENTATION 860 | 861 | #ifndef CLOWNRESAMPLER_GUARD_FUNCTION_DEFINITIONS 862 | #define CLOWNRESAMPLER_GUARD_FUNCTION_DEFINITIONS 863 | 864 | /* These can be used to provide your own C standard library functions. */ 865 | #ifndef CLOWNRESAMPLER_ASSERT 866 | #include 867 | #define CLOWNRESAMPLER_ASSERT assert 868 | #endif 869 | 870 | #ifndef CLOWNRESAMPLER_FABS 871 | #include 872 | #define CLOWNRESAMPLER_FABS fabs 873 | #endif 874 | 875 | #ifndef CLOWNRESAMPLER_SIN 876 | #include 877 | #define CLOWNRESAMPLER_SIN sin 878 | #endif 879 | 880 | #ifndef CLOWNRESAMPLER_ZERO 881 | #include 882 | #define CLOWNRESAMPLER_ZERO(buffer, size) memset(buffer, 0, size) 883 | #endif 884 | 885 | #ifndef CLOWNRESAMPLER_MEMMOVE 886 | #include 887 | #define CLOWNRESAMPLER_MEMMOVE memmove 888 | #endif 889 | 890 | #include 891 | 892 | static double ClownResampler_LanczosKernel(const double x) 893 | { 894 | const double kernel_radius = (double)CLOWNRESAMPLER_KERNEL_RADIUS; 895 | 896 | const double x_times_pi = x * 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679; /* 100 digits should be good enough. */ 897 | const double x_times_pi_divided_by_radius = x_times_pi / kernel_radius; 898 | 899 | /*CLOWNRESAMPLER_ASSERT(x != 0.0);*/ 900 | if (x == 0.0) 901 | return 1.0; 902 | 903 | CLOWNRESAMPLER_ASSERT(CLOWNRESAMPLER_FABS(x) <= kernel_radius); 904 | /*if (CLOWNRESAMPLER_FABS(x) > kernel_radius) 905 | return 0.0f*/ 906 | 907 | return (CLOWNRESAMPLER_SIN(x_times_pi) * CLOWNRESAMPLER_SIN(x_times_pi_divided_by_radius)) / (x_times_pi * x_times_pi_divided_by_radius); 908 | } 909 | 910 | 911 | /* Common API */ 912 | 913 | static cc_u32f ClownResampler_CalculateRatio(const cc_u32f a, const cc_u32f b) 914 | { 915 | /* HAHAHA, I NEVER THOUGHT LONG DIVISION WOULD ACTUALLY COME IN HANDY! */ 916 | cc_u32f upper, middle, lower, result; 917 | 918 | /* A hack to prevent crashes when either sample rate is 0. */ 919 | if (a == 0 || b == 0) 920 | return 0xFFFFFFFF; 921 | 922 | /* As well as splitting the number into chunks of CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE 923 | size, this sneakily also multiplies it by CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE. */ 924 | upper = a / CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE; 925 | middle = a % CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE; 926 | lower = 0; 927 | 928 | /* Perform long division. */ 929 | middle |= upper % b * CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE; 930 | upper /= b; 931 | 932 | lower |= middle % b * CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE; 933 | middle /= b; 934 | 935 | /*even_lower |= lower % b * CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE;*/ /* Nothing to feed the remainder into... */ 936 | lower /= b; 937 | 938 | /* Detect overflow. */ 939 | if (upper != 0 || middle >= CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE) 940 | return 0xFFFFFFFF; 941 | 942 | /* Merge the chunks back together. */ 943 | result = 0; 944 | /*result += upper * ((cc_u32f)CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE * CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE);*/ 945 | result += middle * CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE; 946 | result += lower; 947 | 948 | /* Detect underflow. */ 949 | if (result == 0) 950 | return 1; 951 | 952 | return result; 953 | } 954 | 955 | CLOWNRESAMPLER_API void ClownResampler_Precompute(ClownResampler_Precomputed* const precomputed) 956 | { 957 | size_t i; 958 | 959 | for (i = 0; i < CLOWNRESAMPLER_COUNT_OF(precomputed->lanczos_kernel_table); ++i) 960 | precomputed->lanczos_kernel_table[i] = (cc_s32l)CLOWNRESAMPLER_TO_FIXED_POINT_FROM_INTEGER(ClownResampler_LanczosKernel(((double)i / (double)CLOWNRESAMPLER_COUNT_OF(precomputed->lanczos_kernel_table) * 2.0 - 1.0) * (double)CLOWNRESAMPLER_KERNEL_RADIUS)); 961 | } 962 | 963 | CLOWNRESAMPLER_API cc_bool ClownResampler_LowestLevel_Configure(ClownResampler_LowestLevel_Configuration* const configuration, const cc_u32f input_sample_rate, const cc_u32f output_sample_rate, const cc_u32f low_pass_filter_sample_rate) 964 | { 965 | /* Determine the kernel scale. This is used to apply a low-pass filter. Not only is this something that the user may 966 | explicitly request, but it is needed when downsampling to avoid artefacts. */ 967 | /* Note that we do not ever want the kernel to be squished, but rather only stretched. */ 968 | const cc_u32f actual_low_pass_sample_rate = CLOWNRESAMPLER_MIN(input_sample_rate, CLOWNRESAMPLER_MIN(output_sample_rate, low_pass_filter_sample_rate)); 969 | const cc_u32f kernel_scale = ClownResampler_CalculateRatio(input_sample_rate, actual_low_pass_sample_rate); 970 | const cc_u32f inverse_kernel_scale = ClownResampler_CalculateRatio(actual_low_pass_sample_rate, input_sample_rate); 971 | 972 | /* Bail on crazy ratios. */ 973 | /* TODO: Maybe use better ratio logic to raise this limit? */ 974 | if (kernel_scale >= CLOWNRESAMPLER_TO_FIXED_POINT_FROM_INTEGER(0x1000)) 975 | return cc_false; 976 | 977 | configuration->stretched_kernel_radius = CLOWNRESAMPLER_KERNEL_RADIUS * kernel_scale; 978 | configuration->integer_stretched_kernel_radius = CLOWNRESAMPLER_TO_INTEGER_FROM_FIXED_POINT_CEILING(configuration->stretched_kernel_radius); 979 | configuration->stretched_kernel_radius_delta = CLOWNRESAMPLER_TO_FIXED_POINT_FROM_INTEGER(configuration->integer_stretched_kernel_radius) - configuration->stretched_kernel_radius; 980 | CLOWNRESAMPLER_ASSERT(configuration->stretched_kernel_radius_delta < CLOWNRESAMPLER_TO_FIXED_POINT_FROM_INTEGER(1)); 981 | configuration->kernel_step_size = CLOWNRESAMPLER_FIXED_POINT_MULTIPLY(CLOWNRESAMPLER_KERNEL_RESOLUTION, inverse_kernel_scale); 982 | 983 | return cc_true; 984 | } 985 | 986 | CLOWNRESAMPLER_API void ClownResampler_LowestLevel_Resample(const ClownResampler_LowestLevel_Configuration* const configuration, const ClownResampler_Precomputed* const precomputed, cc_s32f* const output_frame, const cc_u8f channels, const cc_s16l* const input_buffer, const size_t position_integer, const cc_u32f position_fractional) 987 | { 988 | cc_u8f current_channel; 989 | size_t sample_index, kernel_index; 990 | cc_s32f sample_normaliser; 991 | 992 | /* Calculate the bounds of the kernel convolution. */ 993 | const size_t min_relative = CLOWNRESAMPLER_TO_INTEGER_FROM_FIXED_POINT_CEILING(position_fractional + configuration->stretched_kernel_radius_delta); 994 | const size_t max_relative = CLOWNRESAMPLER_TO_INTEGER_FROM_FIXED_POINT_FLOOR(position_fractional + configuration->stretched_kernel_radius); 995 | const size_t min = (position_integer + min_relative) * channels; 996 | const size_t max = (position_integer + configuration->integer_stretched_kernel_radius + max_relative) * channels; 997 | 998 | /* Yes, I know this line is insane. 999 | It is essentially a simplified and fixed-point version of this: 1000 | const size_t kernel_start = (size_t)(configuration->kernel_step_size * ((float)(min / channels) - position_if_it_were_a_float)); */ 1001 | const size_t kernel_start = CLOWNRESAMPLER_FIXED_POINT_MULTIPLY(configuration->kernel_step_size, (CLOWNRESAMPLER_TO_FIXED_POINT_FROM_INTEGER(min_relative) - position_fractional)); 1002 | 1003 | CLOWNRESAMPLER_ASSERT(min_relative <= configuration->integer_stretched_kernel_radius); 1004 | CLOWNRESAMPLER_ASSERT(max_relative <= configuration->integer_stretched_kernel_radius); 1005 | 1006 | sample_normaliser = 0; 1007 | 1008 | for (sample_index = min, kernel_index = kernel_start; sample_index < max; sample_index += channels, kernel_index += configuration->kernel_step_size) 1009 | { 1010 | cc_s32f kernel_value; 1011 | 1012 | CLOWNRESAMPLER_ASSERT(kernel_index < CLOWNRESAMPLER_COUNT_OF(precomputed->lanczos_kernel_table)); 1013 | 1014 | /* The distance between the frames being output and the frames being read is the parameter to the Lanczos kernel. */ 1015 | kernel_value = (cc_s32f)precomputed->lanczos_kernel_table[kernel_index]; 1016 | sample_normaliser += kernel_value; 1017 | 1018 | /* Modulate the samples with the kernel and add them to the accumulators. */ 1019 | for (current_channel = 0; current_channel < channels; ++current_channel) 1020 | output_frame[current_channel] += CLOWNRESAMPLER_FIXED_POINT_MULTIPLY((cc_s32f)input_buffer[sample_index + current_channel], kernel_value); 1021 | } 1022 | 1023 | /* Compute 17.15 fixed-point reciprocal. */ 1024 | /* TODO: Anything but this; divisions are slow! */ 1025 | sample_normaliser = 0x80000000 / sample_normaliser; 1026 | 1027 | /* Normalise the samples. */ 1028 | for (current_channel = 0; current_channel < channels; ++current_channel) 1029 | { 1030 | /* Note that we use a 17.15 version of CLOWNRESAMPLER_FIXED_POINT_MULTIPLY here. 1031 | This is because, if we used a 16.16 normaliser, then there's a chance that the result 1032 | of the multiplication would overflow, causing popping. */ 1033 | output_frame[current_channel] = (output_frame[current_channel] * sample_normaliser) / (1 << 15); 1034 | } 1035 | } 1036 | 1037 | #endif /* CLOWNRESAMPLER_GUARD_FUNCTION_DEFINITIONS */ 1038 | 1039 | #ifndef CLOWNRESAMPLER_NO_LOW_LEVEL_API 1040 | #define CLOWNRESAMPLER_NO_LOW_LEVEL_API 1041 | 1042 | /* Low-Level API */ 1043 | 1044 | CLOWNRESAMPLER_API cc_bool ClownResampler_LowLevel_Init(ClownResampler_LowLevel_State* const resampler, const cc_u8f channels, const cc_u32f input_sample_rate, const cc_u32f output_sample_rate, const cc_u32f low_pass_filter_sample_rate) 1045 | { 1046 | resampler->channels = channels; 1047 | resampler->position_integer = 0; 1048 | resampler->position_fractional = 0; 1049 | return ClownResampler_LowLevel_Adjust(resampler, input_sample_rate, output_sample_rate, low_pass_filter_sample_rate); 1050 | } 1051 | 1052 | CLOWNRESAMPLER_API cc_bool ClownResampler_LowLevel_Adjust(ClownResampler_LowLevel_State* const resampler, const cc_u32f input_sample_rate, const cc_u32f output_sample_rate, const cc_u32f low_pass_filter_sample_rate) 1053 | { 1054 | resampler->increment = ClownResampler_CalculateRatio(input_sample_rate, output_sample_rate); 1055 | return ClownResampler_LowestLevel_Configure(&resampler->lowest_level, input_sample_rate, output_sample_rate, low_pass_filter_sample_rate); 1056 | } 1057 | 1058 | CLOWNRESAMPLER_API cc_bool ClownResampler_LowLevel_Resample(ClownResampler_LowLevel_State* const resampler, const ClownResampler_Precomputed* const precomputed, const cc_s16l* const input_buffer, size_t* const total_input_frames, const ClownResampler_OutputCallback output_callback, const void* const user_data) 1059 | { 1060 | for (;;) 1061 | { 1062 | /* Check if we have reached the end of the input buffer. */ 1063 | if (resampler->position_integer >= *total_input_frames) 1064 | { 1065 | resampler->position_integer -= *total_input_frames; 1066 | *total_input_frames = 0; 1067 | return cc_true; 1068 | } 1069 | else 1070 | { 1071 | cc_s32f samples[CLOWNRESAMPLER_MAXIMUM_CHANNELS] = {0}; /* Sample accumulators. */ 1072 | 1073 | ClownResampler_LowestLevel_Resample(&resampler->lowest_level, precomputed, samples, resampler->channels, input_buffer, resampler->position_integer, resampler->position_fractional); 1074 | 1075 | /* Increment input buffer position. */ 1076 | resampler->position_fractional += resampler->increment; 1077 | resampler->position_integer += CLOWNRESAMPLER_TO_INTEGER_FROM_FIXED_POINT_FLOOR(resampler->position_fractional); 1078 | resampler->position_fractional %= CLOWNRESAMPLER_FIXED_POINT_FRACTIONAL_SIZE; 1079 | 1080 | /* Output the samples. */ 1081 | if (!output_callback((void*)user_data, samples, resampler->channels)) 1082 | { 1083 | /* We've reached the end of the output buffer. */ 1084 | const size_t delta = CLOWNRESAMPLER_MIN(resampler->position_integer, *total_input_frames); 1085 | 1086 | *total_input_frames -= delta; 1087 | resampler->position_integer -= delta; 1088 | return cc_false; 1089 | } 1090 | } 1091 | } 1092 | } 1093 | 1094 | #endif /* CLOWNRESAMPLER_NO_LOW_LEVEL_API */ 1095 | 1096 | #ifndef CLOWNRESAMPLER_NO_HIGH_LEVEL_API 1097 | #define CLOWNRESAMPLER_NO_HIGH_LEVEL_API 1098 | 1099 | /* High-Level API */ 1100 | 1101 | CLOWNRESAMPLER_API cc_bool ClownResampler_HighLevel_Init(ClownResampler_HighLevel_State* const resampler, const cc_u8f channels, const cc_u32f input_sample_rate, const cc_u32f output_sample_rate, const cc_u32f low_pass_filter_sample_rate) 1102 | { 1103 | if (channels > CLOWNRESAMPLER_MAXIMUM_CHANNELS) 1104 | return cc_false; 1105 | 1106 | if (!ClownResampler_LowLevel_Init(&resampler->low_level, channels, input_sample_rate, output_sample_rate, low_pass_filter_sample_rate)) 1107 | return cc_false; 1108 | 1109 | resampler->maximum_integer_stretched_kernel_radius = resampler->leading_padding_frames_needed = resampler->trailing_padding_frames_remaining = resampler->low_level.lowest_level.integer_stretched_kernel_radius; 1110 | 1111 | /* Blank the width of the kernel's left side to zero, since there will not be previous data to occupy it yet. */ 1112 | CLOWNRESAMPLER_ZERO(resampler->input_buffer, resampler->maximum_integer_stretched_kernel_radius * resampler->low_level.channels * sizeof(*resampler->input_buffer)); 1113 | 1114 | /* Initialise the pointers to point to the middle of the first (and newly-initialised) kernel. */ 1115 | resampler->input_buffer_start = resampler->input_buffer_end = resampler->input_buffer + resampler->maximum_integer_stretched_kernel_radius * resampler->low_level.channels; 1116 | 1117 | return cc_true; 1118 | } 1119 | 1120 | CLOWNRESAMPLER_API cc_bool ClownResampler_HighLevel_Resample(ClownResampler_HighLevel_State* const resampler, const ClownResampler_Precomputed* const precomputed, const ClownResampler_InputCallback input_callback, const ClownResampler_OutputCallback output_callback, const void* const user_data) 1121 | { 1122 | cc_bool reached_end_of_output_buffer = cc_false; 1123 | 1124 | const size_t maximum_radius_in_samples = resampler->maximum_integer_stretched_kernel_radius * resampler->low_level.channels; 1125 | const size_t double_maximum_radius_in_samples = maximum_radius_in_samples * 2; 1126 | 1127 | while (resampler->leading_padding_frames_needed != 0) 1128 | { 1129 | cc_s16l* const buffer = &resampler->input_buffer[double_maximum_radius_in_samples - resampler->leading_padding_frames_needed * resampler->low_level.channels]; 1130 | const size_t frames_read = input_callback((void*)user_data, buffer, resampler->leading_padding_frames_needed); 1131 | 1132 | if (frames_read == 0) 1133 | return cc_true; 1134 | 1135 | resampler->leading_padding_frames_needed -= frames_read; 1136 | } 1137 | 1138 | do 1139 | { 1140 | /* If the input buffer is empty, refill it. */ 1141 | if (resampler->input_buffer_start == resampler->input_buffer_end) 1142 | { 1143 | /* It is hard to explain this step-by-step, but essentially there is a trick that we do here: 1144 | in order to avoid the resampler reading frames outside of the buffer, we have 'deadzones' 1145 | at each end of the buffer. When a new batch of frames is needed, the second deadzone is 1146 | copied over the first one, and the second is overwritten by the end of the new frames. */ 1147 | 1148 | /* Move the end of the last batch of data to the start of the buffer */ 1149 | /* (memcpy will not work here since the copy may overlap). */ 1150 | CLOWNRESAMPLER_MEMMOVE(resampler->input_buffer, resampler->input_buffer_end - maximum_radius_in_samples, double_maximum_radius_in_samples * sizeof(*resampler->input_buffer)); 1151 | 1152 | /* Obtain input frames (note that the new frames start after the frames we just copied). */ 1153 | resampler->input_buffer_start = resampler->input_buffer + maximum_radius_in_samples; 1154 | resampler->input_buffer_end = resampler->input_buffer_start + input_callback((void*)user_data, resampler->input_buffer + double_maximum_radius_in_samples, (CLOWNRESAMPLER_COUNT_OF(resampler->input_buffer) - double_maximum_radius_in_samples) / resampler->low_level.channels) * resampler->low_level.channels; 1155 | 1156 | /* If the callback returns 0, then we must have reached the end of the input data, so quit. */ 1157 | if (resampler->input_buffer_start == resampler->input_buffer_end) 1158 | return cc_true; 1159 | } 1160 | 1161 | /* Call the actual resampler. */ 1162 | { 1163 | size_t input_frames; 1164 | 1165 | const size_t radius_in_samples = resampler->low_level.lowest_level.integer_stretched_kernel_radius * resampler->low_level.channels; 1166 | 1167 | input_frames = (resampler->input_buffer_end - resampler->input_buffer_start) / resampler->low_level.channels; 1168 | reached_end_of_output_buffer = ClownResampler_LowLevel_Resample(&resampler->low_level, precomputed, resampler->input_buffer_start - radius_in_samples, &input_frames, output_callback, user_data) == 0; 1169 | 1170 | /* Increment input and output pointers. */ 1171 | resampler->input_buffer_start = resampler->input_buffer_end - input_frames * resampler->low_level.channels; 1172 | } 1173 | } while (!reached_end_of_output_buffer); 1174 | 1175 | return cc_false; 1176 | } 1177 | 1178 | #endif /* CLOWNRESAMPLER_NO_HIGH_LEVEL_API */ 1179 | 1180 | #if !defined(CLOWNRESAMPLER_NO_HIGH_LEVEL_ADJUST) && !defined(CLOWNRESAMPLER_NO_HIGH_LEVEL_API) 1181 | #define CLOWNRESAMPLER_NO_HIGH_LEVEL_ADJUST 1182 | 1183 | CLOWNRESAMPLER_API cc_bool ClownResampler_HighLevel_Adjust(ClownResampler_HighLevel_State* const resampler, const cc_u32f input_sample_rate, const cc_u32f output_sample_rate, const cc_u32f low_pass_filter_sample_rate) 1184 | { 1185 | /* TODO: Cache the inputs to `ClownResampler_LowLevel_Adjust` instead. */ 1186 | const ClownResampler_LowLevel_State state_backup = resampler->low_level; 1187 | 1188 | if (!ClownResampler_LowLevel_Adjust(&resampler->low_level, input_sample_rate, output_sample_rate, low_pass_filter_sample_rate)) 1189 | { 1190 | resampler->low_level = state_backup; 1191 | return cc_false; 1192 | } 1193 | 1194 | /* Fail if the ratio is too large. See the warning in this function's documentation for more information. */ 1195 | if (resampler->low_level.lowest_level.integer_stretched_kernel_radius > resampler->maximum_integer_stretched_kernel_radius) 1196 | { 1197 | resampler->low_level = state_backup; 1198 | return cc_false; 1199 | } 1200 | 1201 | /* Freak-out if the ratio is so high that the kernel radius would exceed the size of the input buffer. */ 1202 | if (resampler->low_level.lowest_level.integer_stretched_kernel_radius * 2 >= CLOWNRESAMPLER_COUNT_OF(resampler->input_buffer) / resampler->low_level.channels) 1203 | { 1204 | resampler->low_level = state_backup; 1205 | return cc_false; 1206 | } 1207 | 1208 | return cc_true; 1209 | } 1210 | 1211 | #endif /* CLOWNRESAMPLER_NO_HIGH_LEVEL_ADJUST */ 1212 | 1213 | #if !defined(CLOWNRESAMPLER_NO_HIGH_LEVEL_RESAMPLE_END) && !defined(CLOWNRESAMPLER_NO_HIGH_LEVEL_API) 1214 | #define CLOWNRESAMPLER_NO_HIGH_LEVEL_RESAMPLE_END 1215 | 1216 | typedef struct ClownResampler_CallbackWrapperData 1217 | { 1218 | ClownResampler_HighLevel_State *resampler; 1219 | ClownResampler_OutputCallback output_callback; 1220 | void *user_data; 1221 | } ClownResampler_CallbackWrapperData; 1222 | 1223 | static size_t ClownResampler_PaddingCallback(void* const user_data, cc_s16l* const buffer, const size_t total_frames) 1224 | { 1225 | const ClownResampler_CallbackWrapperData* const data = (ClownResampler_CallbackWrapperData*)user_data; 1226 | const size_t frames_to_do = CLOWNRESAMPLER_MIN(total_frames, data->resampler->trailing_padding_frames_remaining); 1227 | 1228 | CLOWNRESAMPLER_ZERO(buffer, frames_to_do * data->resampler->low_level.channels * sizeof(*buffer)); 1229 | 1230 | data->resampler->trailing_padding_frames_remaining -= frames_to_do; 1231 | 1232 | return frames_to_do; 1233 | } 1234 | 1235 | static cc_bool ClownResampler_OutputCallbackWrapper(void* const user_data, const cc_s32f* const frame, const cc_u8f total_samples) 1236 | { 1237 | const ClownResampler_CallbackWrapperData* const data = (ClownResampler_CallbackWrapperData*)user_data; 1238 | 1239 | return data->output_callback(data->user_data, frame, total_samples); 1240 | } 1241 | 1242 | CLOWNRESAMPLER_API cc_bool ClownResampler_HighLevel_ResampleEnd(ClownResampler_HighLevel_State* const resampler, const ClownResampler_Precomputed* const precomputed, const ClownResampler_OutputCallback output_callback, const void* const user_data) 1243 | { 1244 | ClownResampler_CallbackWrapperData data; 1245 | data.resampler = resampler; 1246 | data.output_callback = output_callback; 1247 | data.user_data = (void*)user_data; 1248 | 1249 | return ClownResampler_HighLevel_Resample(resampler, precomputed, ClownResampler_PaddingCallback, ClownResampler_OutputCallbackWrapper, &data); 1250 | } 1251 | 1252 | #endif /* CLOWNRESAMPLER_NO_HIGH_LEVEL_RESAMPLE_END */ 1253 | 1254 | #endif /* CLOWNRESAMPLER_IMPLEMENTATION */ 1255 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | # Typical CMake build directory. 2 | /build 3 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | 3 | project(clownresampler_examples LANGUAGES C) 4 | 5 | add_executable(low-level "low-level.c") 6 | 7 | set_target_properties(low-level PROPERTIES 8 | C_STANDARD 90 9 | C_STANDARD_REQUIRED NO 10 | C_STANDARD_EXTENSIONS YES 11 | ) 12 | 13 | find_library(MATH_LIBRARY m) 14 | 15 | if(MATH_LIBRARY) 16 | target_link_libraries(low-level PRIVATE ${MATH_LIBRARY}) 17 | endif() 18 | 19 | add_executable(high-level "high-level.c") 20 | 21 | set_target_properties(high-level PROPERTIES 22 | C_STANDARD 90 23 | C_STANDARD_REQUIRED NO 24 | C_STANDARD_EXTENSIONS YES 25 | ) 26 | 27 | if(MATH_LIBRARY) 28 | target_link_libraries(high-level PRIVATE ${MATH_LIBRARY}) 29 | endif() 30 | -------------------------------------------------------------------------------- /examples/high-level.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022 Clownacy 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | /* 17 | This demonstrates use of clownresampler's high-level API. 18 | 19 | The low-level API is ideal for when the entirety of the input data is available 20 | at once, whereas the high-level API is ideal for when the input data is 21 | streamed piece by piece. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #define MINIAUDIO_IMPLEMENTATION 29 | #define MA_NO_DECODING 30 | #define MA_NO_ENCODING 31 | #define MA_NO_GENERATION 32 | #define MA_NO_ENGINE 33 | #define MA_NO_NODE_GRAPH 34 | #define MA_API static 35 | #include "libraries/miniaudio.h" /* v0.11.9 */ 36 | 37 | #define DR_MP3_IMPLEMENTATION 38 | #define DR_MP3_ONLY_MP3 39 | #define DRMP3_API static 40 | #include "libraries/dr_mp3.h" /* v0.6.33 */ 41 | 42 | #define CLOWNRESAMPLER_IMPLEMENTATION 43 | #define CLOWNRESAMPLER_STATIC 44 | #include "../clownresampler.h" 45 | 46 | static ClownResampler_Precomputed precomputed; 47 | static ClownResampler_HighLevel_State resampler; 48 | static drmp3 mp3_decoder; 49 | static unsigned int total_channels; 50 | 51 | typedef struct ResamplerCallbackData 52 | { 53 | ma_int16 *output_pointer; 54 | ma_uint32 output_buffer_frames_remaining; 55 | } ResamplerCallbackData; 56 | 57 | static size_t ResamplerInputCallback(void *user_data, cc_s16l *buffer, size_t total_frames) 58 | { 59 | (void)user_data; 60 | 61 | /* Obtain samples from the MP3 file. */ 62 | return drmp3_read_pcm_frames_s16(&mp3_decoder, total_frames, buffer); 63 | } 64 | 65 | static cc_bool ResamplerOutputCallback(void *user_data, const cc_s32f *frame, cc_u8f total_samples) 66 | { 67 | ResamplerCallbackData* const callback_data = (ResamplerCallbackData*)user_data; 68 | 69 | cc_u8f i; 70 | 71 | /* Output the frame. */ 72 | for (i = 0; i < total_samples; ++i) 73 | { 74 | cc_s32f sample; 75 | 76 | sample = frame[i]; 77 | 78 | /* Clamp the sample to 16-bit. */ 79 | if (sample > 0x7FFF) 80 | sample = 0x7FFF; 81 | else if (sample < -0x7FFF) 82 | sample = -0x7FFF; 83 | 84 | /* Push the sample to the output buffer. */ 85 | *callback_data->output_pointer++ = (ma_int16)sample; 86 | } 87 | 88 | /* Signal whether there is more room in the output buffer. */ 89 | return --callback_data->output_buffer_frames_remaining != 0; 90 | } 91 | 92 | static void AudioCallback(ma_device *device, void *output, const void *input, ma_uint32 frame_count) 93 | { 94 | ResamplerCallbackData callback_data; 95 | 96 | (void)device; 97 | (void)input; 98 | 99 | callback_data.output_pointer = (ma_int16*)output; 100 | callback_data.output_buffer_frames_remaining = frame_count; 101 | 102 | /* Resample the decoded audio data. */ 103 | ClownResampler_HighLevel_Resample(&resampler, &precomputed, ResamplerInputCallback, ResamplerOutputCallback, &callback_data); 104 | 105 | /* If there are no more samples left, then fill the remaining space in the buffer with 0. */ 106 | memset(callback_data.output_pointer, 0, callback_data.output_buffer_frames_remaining * total_channels * sizeof(ma_int16)); 107 | } 108 | 109 | int main(int argc, char **argv) 110 | { 111 | int exit_code; 112 | 113 | exit_code = EXIT_FAILURE; 114 | 115 | if (argc < 2) 116 | { 117 | fputs("Pass the path to an MP3 file as an argument.\n", stderr); 118 | } 119 | else 120 | { 121 | if (!drmp3_init_file(&mp3_decoder, argv[1], NULL)) 122 | { 123 | fputs("Failed to initialise MP3 decoder.\n", stderr); 124 | } 125 | else 126 | { 127 | /******************************/ 128 | /* Initialise audio playback. */ 129 | /******************************/ 130 | ma_device_config miniaudio_config; 131 | ma_device miniaudio_device; 132 | 133 | miniaudio_config = ma_device_config_init(ma_device_type_playback); 134 | miniaudio_config.playback.format = ma_format_s16; 135 | miniaudio_config.playback.channels = mp3_decoder.channels; 136 | miniaudio_config.sampleRate = 0; /* Use whatever sample rate the playback device wants. */ 137 | miniaudio_config.dataCallback = AudioCallback; 138 | miniaudio_config.pUserData = NULL; 139 | 140 | if (ma_device_init(NULL, &miniaudio_config, &miniaudio_device) != MA_SUCCESS) 141 | { 142 | fputs("Failed to initialise playback device.\n", stderr); 143 | } 144 | else 145 | { 146 | /*****************************************/ 147 | /* Finished initialising audio playback. */ 148 | /*****************************************/ 149 | 150 | /* Inform the user of the input and output sample rates. */ 151 | fprintf(stderr, "MP3 Sample Rate: %lu\n", (unsigned long)mp3_decoder.sampleRate); 152 | fprintf(stderr, "Playback Sample Rate: %lu\n", (unsigned long)miniaudio_device.sampleRate); 153 | fflush(stderr); 154 | 155 | /******************************/ 156 | /* Initialise clownresampler. */ 157 | /******************************/ 158 | 159 | /* Precompute the Lanczos kernel. */ 160 | ClownResampler_Precompute(&precomputed); 161 | 162 | /* Create a resampler that converts from the sample rate of the MP3 to the sample rate of the playback device. */ 163 | /* The low-pass filter is set to 44100Hz since that should allow all human-perceivable frequencies through. */ 164 | ClownResampler_HighLevel_Init(&resampler, mp3_decoder.channels, mp3_decoder.sampleRate, miniaudio_device.sampleRate, 44100); 165 | 166 | /*****************************************/ 167 | /* Finished initialising clownresampler. */ 168 | /*****************************************/ 169 | 170 | total_channels = mp3_decoder.channels; 171 | 172 | /* Begin playback. */ 173 | ma_device_start(&miniaudio_device); 174 | 175 | /* Wait for input from the user before terminating the program. */ 176 | fgetc(stdin); 177 | 178 | ma_device_uninit(&miniaudio_device); 179 | 180 | exit_code = EXIT_SUCCESS; 181 | } 182 | 183 | drmp3_uninit(&mp3_decoder); 184 | } 185 | } 186 | 187 | return exit_code; 188 | } 189 | -------------------------------------------------------------------------------- /examples/low-level.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022 Clownacy 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | /* 17 | This demonstrates use of clownresampler's low-level API. 18 | 19 | The low-level API is ideal for when the entirety of the input data is available 20 | at once, whereas the high-level API is ideal for when the input data is 21 | streamed piece by piece. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #define MINIAUDIO_IMPLEMENTATION 29 | #define MA_NO_DECODING 30 | #define MA_NO_ENCODING 31 | #define MA_NO_GENERATION 32 | #define MA_NO_ENGINE 33 | #define MA_NO_NODE_GRAPH 34 | #define MA_API static 35 | #include "libraries/miniaudio.h" /* v0.11.9 */ 36 | 37 | #define DR_MP3_IMPLEMENTATION 38 | #define DR_MP3_ONLY_MP3 39 | #define DRMP3_API static 40 | #include "libraries/dr_mp3.h" /* v0.6.33 */ 41 | 42 | #define CLOWNRESAMPLER_IMPLEMENTATION 43 | #define CLOWNRESAMPLER_STATIC 44 | #define CLOWNRESAMPLER_NO_HIGH_LEVEL_API /* We don't need the high-level API. */ 45 | #include "../clownresampler.h" 46 | 47 | static ClownResampler_Precomputed precomputed; 48 | static ClownResampler_LowLevel_State resampler; 49 | static unsigned int total_channels; 50 | static drmp3_int16 *resampler_input_buffer; 51 | static size_t resampler_input_buffer_total_frames; 52 | static size_t resampler_input_buffer_frames_remaining; 53 | 54 | typedef struct ResamplerCallbackData 55 | { 56 | ma_int16 *output_pointer; 57 | ma_uint32 output_buffer_frames_remaining; 58 | } ResamplerCallbackData; 59 | 60 | static cc_bool ResamplerOutputCallback(void *user_data, const cc_s32f *frame, cc_u8f total_samples) 61 | { 62 | ResamplerCallbackData* const callback_data = (ResamplerCallbackData*)user_data; 63 | 64 | cc_u8f i; 65 | 66 | /* Output the frame. */ 67 | for (i = 0; i < total_samples; ++i) 68 | { 69 | cc_s32f sample; 70 | 71 | sample = frame[i]; 72 | 73 | /* Clamp the sample to 16-bit. */ 74 | if (sample > 0x7FFF) 75 | sample = 0x7FFF; 76 | else if (sample < -0x7FFF) 77 | sample = -0x7FFF; 78 | 79 | /* Push the sample to the output buffer. */ 80 | *callback_data->output_pointer++ = (ma_int16)sample; 81 | } 82 | 83 | /* Signal whether there is more room in the output buffer. */ 84 | return --callback_data->output_buffer_frames_remaining != 0; 85 | } 86 | 87 | static void AudioCallback(ma_device *device, void *output, const void *input, ma_uint32 frame_count) 88 | { 89 | ResamplerCallbackData callback_data; 90 | 91 | (void)device; 92 | (void)input; 93 | 94 | callback_data.output_pointer = (ma_int16*)output; 95 | callback_data.output_buffer_frames_remaining = frame_count; 96 | 97 | /* Resample the decoded audio data. */ 98 | ClownResampler_LowLevel_Resample(&resampler, &precomputed, &resampler_input_buffer[(resampler_input_buffer_total_frames - resampler_input_buffer_frames_remaining) * total_channels], &resampler_input_buffer_frames_remaining, ResamplerOutputCallback, &callback_data); 99 | 100 | /* If there are no more samples left, then fill the remaining space in the buffer with 0. */ 101 | memset(callback_data.output_pointer, 0, callback_data.output_buffer_frames_remaining * total_channels * sizeof(ma_int16)); 102 | } 103 | 104 | int main(int argc, char **argv) 105 | { 106 | int exit_code; 107 | drmp3 mp3_decoder; 108 | 109 | exit_code = EXIT_FAILURE; 110 | 111 | if (argc < 2) 112 | { 113 | fputs("Pass the path to an MP3 file as an argument.\n", stderr); 114 | } 115 | else 116 | { 117 | if (!drmp3_init_file(&mp3_decoder, argv[1], NULL)) 118 | { 119 | fputs("Failed to initialise MP3 decoder.\n", stderr); 120 | } 121 | else 122 | { 123 | /******************************/ 124 | /* Initialise audio playback. */ 125 | /******************************/ 126 | ma_device_config miniaudio_config; 127 | ma_device miniaudio_device; 128 | 129 | miniaudio_config = ma_device_config_init(ma_device_type_playback); 130 | miniaudio_config.playback.format = ma_format_s16; 131 | miniaudio_config.playback.channels = mp3_decoder.channels; 132 | miniaudio_config.sampleRate = 0; /* Use whatever sample rate the playback device wants. */ 133 | miniaudio_config.dataCallback = AudioCallback; 134 | miniaudio_config.pUserData = NULL; 135 | 136 | if (ma_device_init(NULL, &miniaudio_config, &miniaudio_device) != MA_SUCCESS) 137 | { 138 | drmp3_uninit(&mp3_decoder); 139 | fputs("Failed to initialise playback device.\n", stderr); 140 | } 141 | else 142 | { 143 | /*****************************************/ 144 | /* Finished initialising audio playback. */ 145 | /*****************************************/ 146 | 147 | const size_t size_of_frame = mp3_decoder.channels * sizeof(drmp3_int16); 148 | 149 | size_t total_mp3_pcm_frames; 150 | 151 | total_mp3_pcm_frames = drmp3_get_pcm_frame_count(&mp3_decoder); 152 | total_channels = mp3_decoder.channels; 153 | 154 | /* Inform the user of the input and output sample rates. */ 155 | fprintf(stderr, "MP3 Sample Rate: %lu\n", (unsigned long)mp3_decoder.sampleRate); 156 | fprintf(stderr, "Playback Sample Rate: %lu\n", (unsigned long)miniaudio_device.sampleRate); 157 | fflush(stderr); 158 | 159 | /******************************/ 160 | /* Initialise clownresampler. */ 161 | /******************************/ 162 | 163 | /* Precompute the Lanczos kernel. */ 164 | ClownResampler_Precompute(&precomputed); 165 | 166 | /* Create a resampler that converts from the sample rate of the MP3 to the sample rate of the playback device. */ 167 | /* The low-pass filter is set to 44100Hz since that should allow all human-perceivable frequencies through. */ 168 | ClownResampler_LowLevel_Init(&resampler, mp3_decoder.channels, mp3_decoder.sampleRate, miniaudio_device.sampleRate, 44100); 169 | 170 | /*****************************************/ 171 | /* Finished initialising clownresampler. */ 172 | /*****************************************/ 173 | 174 | /*****************************************/ 175 | /* Set up clownresampler's input buffer. */ 176 | /*****************************************/ 177 | 178 | /* Create a buffer to hold the decoded PCM data. */ 179 | /* clownresampler's low-level API requires that this buffer have padding at its beginning and end. */ 180 | resampler_input_buffer = (drmp3_int16*)malloc((resampler.integer_stretched_kernel_radius * 2 + total_mp3_pcm_frames) * size_of_frame); 181 | 182 | if (resampler_input_buffer == NULL) 183 | { 184 | drmp3_uninit(&mp3_decoder); 185 | fputs("Failed to allocate memory for resampler input buffer.\n", stderr); 186 | } 187 | else 188 | { 189 | /* Set the padding samples at the start to 0. */ 190 | memset(&resampler_input_buffer[0], 0, resampler.integer_stretched_kernel_radius * size_of_frame); 191 | 192 | /* Decode the MP3 to the input buffer. */ 193 | drmp3_read_pcm_frames_s16(&mp3_decoder, total_mp3_pcm_frames, &resampler_input_buffer[resampler.integer_stretched_kernel_radius * total_channels]); 194 | drmp3_uninit(&mp3_decoder); 195 | 196 | /* Set the padding samples at the end to 0. */ 197 | memset(&resampler_input_buffer[(resampler.integer_stretched_kernel_radius + total_mp3_pcm_frames) * total_channels], 0, resampler.integer_stretched_kernel_radius * size_of_frame); 198 | 199 | /* Initialise some variables that will be used by the audio callback. */ 200 | resampler_input_buffer_total_frames = resampler_input_buffer_frames_remaining = total_mp3_pcm_frames; 201 | 202 | /*****************************************************/ 203 | /* Finished setting up the resampler's input buffer. */ 204 | /*****************************************************/ 205 | 206 | /* Begin playback. */ 207 | ma_device_start(&miniaudio_device); 208 | 209 | /* Wait for input from the user before terminating the program. */ 210 | fgetc(stdin); 211 | 212 | ma_device_stop(&miniaudio_device); 213 | 214 | free(resampler_input_buffer); 215 | 216 | exit_code = EXIT_SUCCESS; 217 | } 218 | 219 | ma_device_uninit(&miniaudio_device); 220 | } 221 | } 222 | } 223 | 224 | return exit_code; 225 | } 226 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | # Typical CMake build directory. 2 | /build 3 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0...3.12) 2 | 3 | project(clownresampler-test LANGUAGES C) 4 | 5 | find_library(MATH_LIBRARY m) 6 | 7 | add_executable(test-high-level "test-high-level.c" "dr_flac.h") 8 | 9 | if(MATH_LIBRARY) 10 | target_link_libraries(test-high-level PRIVATE ${MATH_LIBRARY}) 11 | endif() 12 | 13 | add_executable(test-low-level "test-low-level.c" "dr_flac.h") 14 | 15 | if(MATH_LIBRARY) 16 | target_link_libraries(test-low-level PRIVATE ${MATH_LIBRARY}) 17 | endif() 18 | 19 | ######### 20 | # Tests # 21 | ######### 22 | 23 | enable_testing() 24 | 25 | add_test(NAME high-test1 COMMAND test-high-level "${CMAKE_CURRENT_SOURCE_DIR}/test.flac" "test-output" 8000 44100 44100) 26 | add_test(NAME high-test1_compare COMMAND ${CMAKE_COMMAND} -E compare_files "${CMAKE_CURRENT_SOURCE_DIR}/test1" "test-output") 27 | 28 | add_test(NAME high-test2 COMMAND test-high-level "${CMAKE_CURRENT_SOURCE_DIR}/test.flac" "test-output" 8000 44100 8000) 29 | add_test(NAME high-test2_compare COMMAND ${CMAKE_COMMAND} -E compare_files "${CMAKE_CURRENT_SOURCE_DIR}/test2" "test-output") 30 | 31 | add_test(NAME high-test3 COMMAND test-high-level "${CMAKE_CURRENT_SOURCE_DIR}/test.flac" "test-output" 44100 8000 44100) 32 | add_test(NAME high-test3_compare COMMAND ${CMAKE_COMMAND} -E compare_files "${CMAKE_CURRENT_SOURCE_DIR}/test3" "test-output") 33 | 34 | add_test(NAME high-test4 COMMAND test-high-level "${CMAKE_CURRENT_SOURCE_DIR}/test.flac" "test-output" 44100 8000 8000) 35 | add_test(NAME high-test4_compare COMMAND ${CMAKE_COMMAND} -E compare_files "${CMAKE_CURRENT_SOURCE_DIR}/test4" "test-output") 36 | 37 | add_test(NAME low-test1 COMMAND test-low-level "${CMAKE_CURRENT_SOURCE_DIR}/test.flac" "test-output" 8000 44100 44100) 38 | add_test(NAME low-test1_compare COMMAND ${CMAKE_COMMAND} -E compare_files "${CMAKE_CURRENT_SOURCE_DIR}/test1" "test-output") 39 | 40 | add_test(NAME low-test2 COMMAND test-low-level "${CMAKE_CURRENT_SOURCE_DIR}/test.flac" "test-output" 8000 44100 8000) 41 | add_test(NAME low-test2_compare COMMAND ${CMAKE_COMMAND} -E compare_files "${CMAKE_CURRENT_SOURCE_DIR}/test2" "test-output") 42 | 43 | add_test(NAME low-test3 COMMAND test-low-level "${CMAKE_CURRENT_SOURCE_DIR}/test.flac" "test-output" 44100 8000 44100) 44 | add_test(NAME low-test3_compare COMMAND ${CMAKE_COMMAND} -E compare_files "${CMAKE_CURRENT_SOURCE_DIR}/test3" "test-output") 45 | 46 | add_test(NAME low-test4 COMMAND test-low-level "${CMAKE_CURRENT_SOURCE_DIR}/test.flac" "test-output" 44100 8000 8000) 47 | add_test(NAME low-test4_compare COMMAND ${CMAKE_COMMAND} -E compare_files "${CMAKE_CURRENT_SOURCE_DIR}/test4" "test-output") 48 | -------------------------------------------------------------------------------- /tests/test-high-level.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022-2023 Clownacy 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #define DR_FLAC_IMPLEMENTATION 21 | #define DR_FLAC_NO_OGG 22 | #define DRFLAC_API static 23 | #include "dr_flac.h" 24 | 25 | #define CLOWNRESAMPLER_IMPLEMENTATION 26 | #define CLOWNRESAMPLER_STATIC 27 | #include "../clownresampler.h" 28 | 29 | static ClownResampler_Precomputed precomputed; 30 | static ClownResampler_HighLevel_State resampler; 31 | static drflac *flac_decoder; 32 | 33 | static size_t ResamplerInputCallback(void *user_data, cc_s16l *buffer, size_t total_frames) 34 | { 35 | (void)user_data; 36 | 37 | /* Obtain samples from the FLAC file. */ 38 | return drflac_read_pcm_frames_s16(flac_decoder, total_frames, buffer); 39 | } 40 | 41 | static cc_bool ResamplerOutputCallback(void *user_data, const cc_s32f *frame, cc_u8f total_samples) 42 | { 43 | FILE* const output_file = (FILE*)user_data; 44 | 45 | cc_u8f i; 46 | 47 | /* Output the frame. */ 48 | for (i = 0; i < total_samples; ++i) 49 | { 50 | unsigned int j; 51 | unsigned char bytes[4]; 52 | 53 | for (j = 0; j < 4; ++j) 54 | bytes[j] = (frame[i] >> (8 * j)) & 0xFF; 55 | 56 | fwrite(bytes, 1, sizeof(bytes), output_file); 57 | } 58 | 59 | return cc_true; 60 | } 61 | 62 | int main(int argc, char **argv) 63 | { 64 | int exit_code; 65 | 66 | exit_code = EXIT_FAILURE; 67 | 68 | if (argc < 6) 69 | { 70 | fprintf(stderr, "Usage: %s [path to input file] [path to output file] [input sample rate] [output sample rate] [low-pass filter sample rate]\n", argv[0]); 71 | } 72 | else 73 | { 74 | char *input_sample_rate_end; 75 | char *output_sample_rate_end; 76 | char *low_pass_sample_rate_end; 77 | 78 | const unsigned long input_sample_rate = strtoul(argv[3], &input_sample_rate_end, 0); 79 | const unsigned long output_sample_rate = strtoul(argv[4], &output_sample_rate_end, 0); 80 | const unsigned long low_pass_sample_rate = strtoul(argv[5], &low_pass_sample_rate_end, 0); 81 | 82 | if (input_sample_rate_end < argv[3] + strlen(argv[3]) 83 | || output_sample_rate_end < argv[4] + strlen(argv[4]) 84 | || low_pass_sample_rate_end < argv[5] + strlen(argv[5]) 85 | ) 86 | { 87 | fputs("Sample rate arguments were invalid.\n", stderr); 88 | } 89 | else 90 | { 91 | FILE* const output_file = fopen(argv[2], "wb"); 92 | 93 | if (output_file == NULL) 94 | { 95 | fputs("Failed to open output file for writing.\n", stderr); 96 | } 97 | else 98 | { 99 | flac_decoder = drflac_open_file(argv[1], NULL); 100 | 101 | if (flac_decoder == NULL) 102 | { 103 | fputs("Failed to initialise FLAC decoder.\n", stderr); 104 | } 105 | else 106 | { 107 | /* Inform the user of the input and output sample rates. */ 108 | fprintf(stderr, "FLAC Sample Rate: %lu\n", (unsigned long)flac_decoder->sampleRate); 109 | fflush(stderr); 110 | 111 | /******************************/ 112 | /* Initialise clownresampler. */ 113 | /******************************/ 114 | 115 | /* Precompute the Lanczos kernel. */ 116 | ClownResampler_Precompute(&precomputed); 117 | 118 | /* Create a resampler that converts from the sample rate of the FLAC file to the sample rate of the playback device. */ 119 | ClownResampler_HighLevel_Init(&resampler, flac_decoder->channels, input_sample_rate, output_sample_rate, low_pass_sample_rate); 120 | 121 | /*****************************************/ 122 | /* Finished initialising clownresampler. */ 123 | /*****************************************/ 124 | 125 | /* Resample the decoded audio data. */ 126 | ClownResampler_HighLevel_Resample(&resampler, &precomputed, ResamplerInputCallback, ResamplerOutputCallback, output_file); 127 | ClownResampler_HighLevel_ResampleEnd(&resampler, &precomputed, ResamplerOutputCallback, output_file); 128 | 129 | drflac_close(flac_decoder); 130 | 131 | exit_code = EXIT_SUCCESS; 132 | } 133 | 134 | fclose(output_file); 135 | } 136 | } 137 | } 138 | 139 | return exit_code; 140 | } 141 | -------------------------------------------------------------------------------- /tests/test-low-level.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2022-2023 Clownacy 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #define DR_FLAC_IMPLEMENTATION 21 | #define DR_FLAC_NO_OGG 22 | #define DRFLAC_API static 23 | #include "dr_flac.h" 24 | 25 | #define CLOWNRESAMPLER_IMPLEMENTATION 26 | #define CLOWNRESAMPLER_STATIC 27 | #define CLOWNRESAMPLER_NO_HIGH_LEVEL_API /* We don't need the high-level API. */ 28 | #include "../clownresampler.h" 29 | 30 | static ClownResampler_Precomputed precomputed; 31 | static ClownResampler_LowLevel_State resampler; 32 | static drflac_int16 *resampler_input_buffer; 33 | 34 | static cc_bool ResamplerOutputCallback(void *user_data, const cc_s32f *frame, cc_u8f total_samples) 35 | { 36 | FILE* const output_file = (FILE*)user_data; 37 | 38 | cc_u8f i; 39 | 40 | /* Output the frame. */ 41 | for (i = 0; i < total_samples; ++i) 42 | { 43 | unsigned int j; 44 | unsigned char bytes[4]; 45 | 46 | for (j = 0; j < 4; ++j) 47 | bytes[j] = (frame[i] >> (8 * j)) & 0xFF; 48 | 49 | fwrite(bytes, 1, sizeof(bytes), output_file); 50 | } 51 | 52 | return cc_true; 53 | } 54 | 55 | int main(int argc, char **argv) 56 | { 57 | int exit_code; 58 | drflac *flac_decoder; 59 | 60 | exit_code = EXIT_FAILURE; 61 | 62 | if (argc < 6) 63 | { 64 | fprintf(stderr, "Usage: %s [path to input file] [path to output file] [input sample rate] [output sample rate] [low-pass filter sample rate]\n", argv[0]); 65 | } 66 | else 67 | { 68 | char *input_sample_rate_end; 69 | char *output_sample_rate_end; 70 | char *low_pass_sample_rate_end; 71 | 72 | const unsigned long input_sample_rate = strtoul(argv[3], &input_sample_rate_end, 0); 73 | const unsigned long output_sample_rate = strtoul(argv[4], &output_sample_rate_end, 0); 74 | const unsigned long low_pass_sample_rate = strtoul(argv[5], &low_pass_sample_rate_end, 0); 75 | 76 | if (input_sample_rate_end < argv[3] + strlen(argv[3]) 77 | || output_sample_rate_end < argv[4] + strlen(argv[4]) 78 | || low_pass_sample_rate_end < argv[5] + strlen(argv[5]) 79 | ) 80 | { 81 | fputs("Sample rate arguments were invalid.\n", stderr); 82 | } 83 | else 84 | { 85 | FILE* const output_file = fopen(argv[2], "wb"); 86 | 87 | if (output_file == NULL) 88 | { 89 | fputs("Failed to open output file for writing.\n", stderr); 90 | } 91 | else 92 | { 93 | flac_decoder = drflac_open_file(argv[1], NULL); 94 | 95 | if (flac_decoder == NULL) 96 | { 97 | fputs("Failed to initialise FLAC decoder.\n", stderr); 98 | } 99 | else 100 | { 101 | const size_t size_of_frame = flac_decoder->channels * sizeof(drflac_int16); 102 | const size_t total_channels = flac_decoder->channels; 103 | 104 | size_t total_flac_pcm_frames; 105 | 106 | total_flac_pcm_frames = flac_decoder->totalPCMFrameCount; 107 | 108 | /* Inform the user of the input and output sample rates. */ 109 | fprintf(stderr, "FLAC Sample Rate: %lu\n", (unsigned long)flac_decoder->sampleRate); 110 | fflush(stderr); 111 | 112 | /******************************/ 113 | /* Initialise clownresampler. */ 114 | /******************************/ 115 | 116 | /* Precompute the Lanczos kernel. */ 117 | ClownResampler_Precompute(&precomputed); 118 | 119 | /* Create a resampler that converts from the sample rate of the FLAC to the sample rate of the playback device. */ 120 | /* The low-pass filter is set to 44100Hz since that should allow all human-perceivable frequencies through. */ 121 | ClownResampler_LowLevel_Init(&resampler, flac_decoder->channels, input_sample_rate, output_sample_rate, low_pass_sample_rate); 122 | 123 | /*****************************************/ 124 | /* Finished initialising clownresampler. */ 125 | /*****************************************/ 126 | 127 | /*****************************************/ 128 | /* Set up clownresampler's input buffer. */ 129 | /*****************************************/ 130 | 131 | /* Create a buffer to hold the decoded PCM data. */ 132 | /* clownresampler's low-level API requires that this buffer have padding at its beginning and end. */ 133 | resampler_input_buffer = (drflac_int16*)malloc((resampler.lowest_level.integer_stretched_kernel_radius * 2 + total_flac_pcm_frames) * size_of_frame); 134 | 135 | if (resampler_input_buffer == NULL) 136 | { 137 | drflac_close(flac_decoder); 138 | fputs("Failed to allocate memory for resampler input buffer.\n", stderr); 139 | } 140 | else 141 | { 142 | size_t resampler_input_buffer_frames_remaining; 143 | 144 | /* Set the padding samples at the start to 0. */ 145 | memset(&resampler_input_buffer[0], 0, resampler.lowest_level.integer_stretched_kernel_radius * size_of_frame); 146 | 147 | /* Decode the FLAC to the input buffer. */ 148 | drflac_read_pcm_frames_s16(flac_decoder, total_flac_pcm_frames, &resampler_input_buffer[resampler.lowest_level.integer_stretched_kernel_radius * total_channels]); 149 | drflac_close(flac_decoder); 150 | 151 | /* Set the padding samples at the end to 0. */ 152 | memset(&resampler_input_buffer[(resampler.lowest_level.integer_stretched_kernel_radius + total_flac_pcm_frames) * total_channels], 0, resampler.lowest_level.integer_stretched_kernel_radius * size_of_frame); 153 | 154 | /*****************************************************/ 155 | /* Finished setting up the resampler's input buffer. */ 156 | /*****************************************************/ 157 | 158 | resampler_input_buffer_frames_remaining = total_flac_pcm_frames; 159 | ClownResampler_LowLevel_Resample(&resampler, &precomputed, resampler_input_buffer, &resampler_input_buffer_frames_remaining, ResamplerOutputCallback, output_file); 160 | 161 | free(resampler_input_buffer); 162 | 163 | exit_code = EXIT_SUCCESS; 164 | } 165 | } 166 | 167 | fclose(output_file); 168 | } 169 | } 170 | } 171 | 172 | return exit_code; 173 | } 174 | -------------------------------------------------------------------------------- /tests/test.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clownacy/clownresampler/bf9669ff7f6293e5de554b5fe45a072e3623d5e1/tests/test.flac -------------------------------------------------------------------------------- /tests/test1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clownacy/clownresampler/bf9669ff7f6293e5de554b5fe45a072e3623d5e1/tests/test1 -------------------------------------------------------------------------------- /tests/test2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clownacy/clownresampler/bf9669ff7f6293e5de554b5fe45a072e3623d5e1/tests/test2 -------------------------------------------------------------------------------- /tests/test3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clownacy/clownresampler/bf9669ff7f6293e5de554b5fe45a072e3623d5e1/tests/test3 -------------------------------------------------------------------------------- /tests/test4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Clownacy/clownresampler/bf9669ff7f6293e5de554b5fe45a072e3623d5e1/tests/test4 --------------------------------------------------------------------------------