diff --git a/7DRL.vcxproj b/7DRL.vcxproj index 667cf56..72d71c6 100644 --- a/7DRL.vcxproj +++ b/7DRL.vcxproj @@ -69,7 +69,9 @@ - + + $(IncludePath) + Level3 @@ -84,7 +86,13 @@ Disabled true true + C:\Users\Usuario\Source\Repos\7DRL\src\soloud\include;D:\Programming\SDL2\include;D:\Programming\libtcod-1.15.1-x86_64-msvc\include + WITH_SDL2;_CRT_SECURE_NO_WARNINGS;_MBCS;%(PreprocessorDefinitions) + + SDL2.lib;libtcod.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + D:\Programming\SDL2\lib\x64;D:\Programming\libtcod-1.15.1-x86_64-msvc + @@ -108,13 +116,106 @@ true true true + D:\Programming\libtcod-1.15.1-x86_64-msvc\include; true true + D:\Programming\SDL2\lib\x64;D:\Programming\libtcod-1.15.1-x86_64-msvc;%(AdditionalLibraryDirectories) + SDL2.lib;libtcod.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/7DRL.vcxproj.filters b/7DRL.vcxproj.filters index 4863ddb..78dd21e 100644 --- a/7DRL.vcxproj.filters +++ b/7DRL.vcxproj.filters @@ -14,4 +14,264 @@ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + \ No newline at end of file diff --git a/SDL2.dll b/SDL2.dll new file mode 100644 index 0000000..6cf858c Binary files /dev/null and b/SDL2.dll differ diff --git a/click.wav b/click.wav new file mode 100644 index 0000000..833ee01 Binary files /dev/null and b/click.wav differ diff --git a/libtcod.dll b/libtcod.dll new file mode 100644 index 0000000..c52972e Binary files /dev/null and b/libtcod.dll differ diff --git a/ping.wav b/ping.wav new file mode 100644 index 0000000..a8fcbec Binary files /dev/null and b/ping.wav differ diff --git a/rc11.png b/rc11.png new file mode 100644 index 0000000..320e4f6 Binary files /dev/null and b/rc11.png differ diff --git a/src/Drawing.h b/src/Drawing.h new file mode 100644 index 0000000..6657dfe --- /dev/null +++ b/src/Drawing.h @@ -0,0 +1,114 @@ +#pragma once +#include "libtcod.h" +#include "soloud_wav.h" +#include "defines.h" + +static SoLoud::Wav* click = nullptr; + +class Drawing +{ +public: + + + static void draw_window(TCODConsole* target, int x0, int y0, int w, int h, const std::string& title, + bool clear_inside = false) + { + TCODConsole::root->setDefaultForeground(TCODColor::grey); + + for (int x = x0; x < x0 + w; x++) + { + target->putChar(x, y0, TCOD_CHAR_DHLINE); + target->putChar(x, y0 + h, TCOD_CHAR_DHLINE); + } + + for (int y = y0; y < y0 + h; y++) + { + target->putChar(x0, y, TCOD_CHAR_DVLINE); + target->putChar(x0 + w, y, TCOD_CHAR_DVLINE); + } + + target->putChar(x0, y0, TCOD_CHAR_DNW); + target->putChar(x0 + w, y0, TCOD_CHAR_DNE); + target->putChar(x0, y0 + h, TCOD_CHAR_DSW); + target->putChar(x0 + w, y0 + h, TCOD_CHAR_DSE); + + int tw = title.size(); + int tx = x0 + (w - tw) / 2; + + target->putChar(tx - 1, y0, 185); + target->putChar(tx + tw, y0, 204); + + TCODConsole::root->setDefaultForeground(TCODColor::white); + target->printf(tx, y0, title.c_str()); + + if (clear_inside) + { + for (int x = x0 + 1; x < x0 + w; x++) + { + for (int y = y0 + 1; y < y0 + h; y++) + { + target->setChar(x, y, ' '); + target->setCharForeground(x, y, TCODColor::white); + target->setCharBackground(x, y, TCODColor::black); + } + } + } + } + + // Only changes foregrounds + // Returns true if changed + static bool draw_switch(TCODConsole* target, int x, int y, int rx, int ry, bool* on, const std::string& title) + { + if (click == nullptr) + { + click = new SoLoud::Wav(); + click->load("click.wav"); + } + // LED + target->setChar(x, y, '('); + target->setChar(x+2, y, ')'); + target->setChar(x + 1, y, 254); + + // Indicators + target->setChar(x, y + 2, '0'); + target->setChar(x + 2, y + 2, '1'); + + // Lever + target->setChar(x, y + 1, TCOD_CHAR_DHLINE); + target->setChar(x + 1, y + 1, TCOD_CHAR_DHLINE); + target->setChar(x + 2, y + 1, TCOD_CHAR_DHLINE); + + if (*on) + { + target->setChar(x + 2, y + 1, 'O'); + } + else + { + target->setChar(x, y + 1, 'O'); + } + + if (*on) + { + target->setCharForeground(x + 1, y, TCODColor::lightRed); + } + else + { + target->setCharForeground(x + 1, y, TCODColor::grey); + } + + int tw = title.size(); + int tx = (x + 1) - (tw / 2); + target->printf(tx, y - 1, title.c_str()); + + TCOD_mouse_t pos = TCODMouse::getStatus(); + + if (pos.cx - rx >= x && pos.cx - rx <= x + 2 && pos.cy - ry == y + 1 && pos.lbutton_pressed) + { + *on = !(*on); + g_soloud->play(*click); + return true; + } + + return false; + } +}; \ No newline at end of file diff --git a/src/Help.h b/src/Help.h new file mode 100644 index 0000000..26370ae --- /dev/null +++ b/src/Help.h @@ -0,0 +1,27 @@ +#pragma once +#include + + +class Help +{ +public: + + static constexpr const char* help_main = "\ +-=Main screen=-\n\n\ +F11 - Open/Close this menu\n\ +ESC - Close Menus\n\ +Space - Pause\n\ +\n\n\ +This window shows contextual help depending on your currently open screen\n\ +To open other screens, click on any of the machines in the submarine. \ +If there is nobody operating the machine, click on a crewmember, rightclick \ +the machine, and select \"move\".\n\n\ +-------------------------------------\n\ +What should I do?\n\n\ +Wait for orders from central command, you will receive them on the radio\n\n\ +-------------------------------------\n\ +How can I interact with the crew?\n\n\ +All orders are done via rightclicking, you can find further help on the \"Crewmember\" menu \ +that can be opened on the rightclick dropdown"; +}; + diff --git a/src/Main.cpp b/src/Main.cpp new file mode 100644 index 0000000..24ab4c6 --- /dev/null +++ b/src/Main.cpp @@ -0,0 +1,49 @@ +#include "libtcod.hpp" +#include "defines.h" + +#include "Status.h" +#include "flight/FlightScene.h" + +#include "soloud/include/soloud.h" +#include "soloud/include/soloud_wav.h" + +SoLoud::Soloud* g_soloud; + +int main(int argc, char** argv) +{ + + TCODConsole::setCustomFont("rc11.png", TCOD_FONT_LAYOUT_ASCII_INROW); + TCODConsole::initRoot(WIDTH, HEIGHT, "7DRL", false, TCOD_RENDERER_GLSL); + + + g_soloud = new SoLoud::Soloud; + g_soloud->init(); + + Status status; + FlightScene flight_scene(&status); + + TCODSystem::setFps(60); + + while (!TCODConsole::isWindowClosed()) + { + TCOD_key_t key; + TCOD_mouse_t mouse; + TCODSystem::checkForEvent(TCOD_EVENT_KEY_PRESS | TCOD_EVENT_MOUSE, &key, &mouse); + if (key.vk != TCODK_NONE) + { + flight_scene.turn(key); + } + + flight_scene.update(TCODSystem::getLastFrameLength()); + + + + TCODConsole::root->clear(); + flight_scene.render(); + TCODConsole::flush(); + } + + g_soloud->deinit(); + + return 0; +} diff --git a/src/Status.cpp b/src/Status.cpp new file mode 100644 index 0000000..cdadfa1 --- /dev/null +++ b/src/Status.cpp @@ -0,0 +1,27 @@ +#include "Status.h" + + + +void Status::draw(int x0, int y0, int x1, int y1) +{ + int id = 0; + for (int y = y1 - 1; y > y0; y--) + { + int i = strings.size() - id - 1; + if (i >= 0 && strings.size() > 0) + { + TCODConsole::root->printf(x0, y, strings[i].c_str()); + } + + id++; + } +} + +Status::Status() +{ +} + + +Status::~Status() +{ +} diff --git a/src/Status.h b/src/Status.h new file mode 100644 index 0000000..9e0b535 --- /dev/null +++ b/src/Status.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +class Status +{ +public: + + std::vector strings; + + // x1 is ignored, we are always full width + void draw(int x0, int y0, int x1, int y1); + + Status(); + ~Status(); +}; + diff --git a/src/defines.h b/src/defines.h new file mode 100644 index 0000000..b070e19 --- /dev/null +++ b/src/defines.h @@ -0,0 +1,7 @@ +#pragma once +#include "soloud.h" + +#define WIDTH 90 +#define HEIGHT 70 + +extern SoLoud::Soloud* g_soloud; \ No newline at end of file diff --git a/src/flight/FlightMap.cpp b/src/flight/FlightMap.cpp new file mode 100644 index 0000000..47b7c8c --- /dev/null +++ b/src/flight/FlightMap.cpp @@ -0,0 +1,100 @@ +#include "FlightMap.h" + + + + +FlightMap::FlightMap(int width, int height, size_t seed) : vmap(width, height) +{ + this->width = width; + this->height = height; + + tiles.resize(width * height); + + // Cavern style map + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + tiles[y * width + x].wall = true; + } + } + + TCODRandom rng = TCODRandom(seed); + + int dx = width / 2, dy = height / 2; + int open = 0; + int target_open = rng.getFloat(0.3, 0.5) * width * height; + int max_steps = 100000; + int steps = 0; + + while (open < target_open && steps < max_steps) + { + if (tiles[dy * width + dx].wall) + { + tiles[dy * width + dx].wall = false; + open++; + } + + if (rng.getFloat(0.0, 1.0) >= 0.5) + { + if (rng.getFloat(0.0, 1.0) >= 0.5) + { + dx++; + } + else + { + dx--; + } + } + else + { + if (rng.getFloat(0.0, 1.0) >= 0.5) + { + dy++; + } + else + { + dy--; + } + } + + if (dx < 1) + { + dx = 1; + } + + if (dx >= width - 1) + { + dx = width - 2; + } + + if (dy < 1) + { + dy = 1; + } + + if (dy >= height - 1) + { + dy = height - 2; + } + + + + steps++; + } + + // TODO: Generate some bases or tunnels + + // Generate the visibility map + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + vmap.setProperties(x, y, !tiles[y * width + x].wall, !tiles[y * width + x].wall); + } + } +} + +FlightMap::~FlightMap() +{ +} diff --git a/src/flight/FlightMap.h b/src/flight/FlightMap.h new file mode 100644 index 0000000..5c9f52a --- /dev/null +++ b/src/flight/FlightMap.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include + +struct MapTile +{ + bool wall; +}; + +class FlightMap +{ +public: + + int width, height; + std::vector tiles; + + TCODMap vmap; + + FlightMap(int width, int height, size_t seed); + ~FlightMap(); +}; + diff --git a/src/flight/FlightScene.cpp b/src/flight/FlightScene.cpp new file mode 100644 index 0000000..201442f --- /dev/null +++ b/src/flight/FlightScene.cpp @@ -0,0 +1,115 @@ +#include "FlightScene.h" + + + +void FlightScene::turn(TCOD_key_t key) +{ + + if (open_window == SONAR) + { + + if (key.vk == TCODK_UP) + { + py--; + } + + if (key.vk == TCODK_DOWN) + { + py++; + } + + if (key.vk == TCODK_RIGHT) + { + px++; + } + + if (key.vk == TCODK_LEFT) + { + px--; + } + + sonar.draw_world(px, py, map); + } + + if (key.vk == TCODK_F11) + { + help_open = !help_open; + } +} + +void FlightScene::update(float dt) +{ + sonar.update(dt); +} + +void FlightScene::render() +{ + TCODConsole::blit(&spaceship, 0, 0, + spaceship.getWidth(), spaceship.getHeight(), TCODConsole::root, sonar.console.getWidth(), 0); + + + + status->draw(1, sonar.console.getHeight(), WIDTH - 1, HEIGHT - 1); + + // Draw frames + for (int i = 0; i < WIDTH; i++) + { + TCODConsole::root->setChar(i, 49, TCOD_CHAR_DHLINE); + } + + if (open_window == SONAR) + { + + int x0 = (TCODConsole::root->getWidth() - sonar.console.getWidth()) / 2; + int y0 = (TCODConsole::root->getHeight() - sonar.console.getHeight()) / 2; + + sonar.draw(x0, y0); + + + Drawing::draw_window(TCODConsole::root, x0 - 1, y0 - 1, sonar.console.getWidth(), sonar.console.getHeight(), "Sonar"); + + TCODConsole::blit(&sonar.console, 0, 0, + sonar.console.getWidth() - 1, sonar.console.getHeight() - 1, TCODConsole::root, x0, y0); + } + + if (help_open) + { + Drawing::draw_window(TCODConsole::root, 0, 0, 40, HEIGHT - 1, "Help", true); + + TCODConsole::root->setDefaultForeground(TCODColor::lightGrey); + TCODConsole::root->printRect(1, 1, 39, HEIGHT - 2, help_str.c_str()); + } + + + +} + + +FlightScene::FlightScene(Status* status) : + sonar(soloud), spaceship(WIDTH - 49, 49), map(map_size, map_size, 11234) +{ + underwater.load("underwater.wav"); + underwater.setLooping(true); + + this->soloud = g_soloud; + this->status = status; + + px = map_size / 2; + py = map_size / 2; + + sonar.sonar_active = true; + sonar.draw_world(px, py, map); + + + + soloud->play(underwater); + + open_window = SONAR; + + help_str = Help::help_main; +} + + +FlightScene::~FlightScene() +{ +} diff --git a/src/flight/FlightScene.h b/src/flight/FlightScene.h new file mode 100644 index 0000000..b2f47f9 --- /dev/null +++ b/src/flight/FlightScene.h @@ -0,0 +1,52 @@ +#pragma once +#include +#include "../defines.h" +#include "../Status.h" +#include "FlightMap.h" +#include "soloud.h" +#include "soloud_wav.h" +#include "Sonar.h" +#include "../Drawing.h" +#include "../Help.h" + + +constexpr int map_size = 512; + +enum Window +{ + NONE, + SONAR +}; + +class FlightScene +{ +public: + + SoLoud::Soloud* soloud; + + SoLoud::Wav underwater; + + FlightMap map; + + Window open_window; + + + Sonar sonar; + + bool help_open; + std::string help_str; + + int px, py; + + + TCODConsole spaceship; + Status* status; + + void turn(TCOD_key_t key); + void update(float dt); + void render(); + + FlightScene(Status* status); + ~FlightScene(); +}; + diff --git a/src/flight/Sonar.cpp b/src/flight/Sonar.cpp new file mode 100644 index 0000000..d0d21f2 --- /dev/null +++ b/src/flight/Sonar.cpp @@ -0,0 +1,127 @@ +#include "Sonar.h" + + + +void Sonar::update(float dt) +{ + sonar_radius += dt * 14.0f; + + if (!sonar_active) + { + sonar_radius = 0.0f; + } + + if (sonar_radius >= 40.0f) + { + sonar_radius = 0.0f; + g_soloud->play(ping); + } +} + +void Sonar::draw(int rx, int ry) +{ + // Fade old pixels + for (int x = 0; x < 49; x++) + { + for (int y = 0; y < 49; y++) + { + TCODColor cur = console.getCharForeground(x, y); + + cur.setValue(cur.getValue() * 0.984f); + + + console.setCharForeground(x, y, cur); + } + } + + + + for (int x = 0; x < 49; x++) + { + for (int y = 0; y < 49; y++) + { + float xf = (float)(x - 24.0f); + float yf = (float)(y - 24.0f); + float dist = sqrt(xf * xf + yf * yf); + if (abs(dist - sonar_radius) <= 2.0f) + { + console.setCharForeground(x, y, TCODColor(80, 255, 80)); + } + } + } + + + + console.setCharForeground(24, 24, TCODColor(255, 255, 255)); + console.setChar(24, 24, 127); + + if (Drawing::draw_switch(&console, 8, 51, rx, ry, &sonar_active, "Active Sonar") && sonar_active) + { + g_soloud->play(ping); + } +} + +void Sonar::draw_world(int px, int py, FlightMap& map) +{ + // Run a visibility algorithm and draw + map.vmap.computeFov(px, py, sonar_active ? 24 : 4, true, FOV_BASIC); + + for (int x = -24; x < 24; x++) + { + for (int y = -24; y < 24; y++) + { + int mx = x + px; + int my = y + py; + + int sx = x + 24; + int sy = y + 24; + + bool seen = true; + + if (mx < 0 || my < 0 || mx >= map.width || my >= map.height) + { + seen = false; + } + + if (!map.vmap.isInFov(mx, my)) + { + seen = false; + } + + if (seen) + { + if (map.tiles[my * map.width + mx].wall) + { + //console.setChar(sx, sy, 219); // Block + console.setChar(sx, sy, 178); + } + else + { + //radar.setChar(sx, sy, 249); + console.setChar(sx, sy, 176); // Dust style + } + + } + else + { + console.setChar(sx, sy, 250); + //radar.setChar(sx, sy, ' '); + } + + } + } +} + + + +Sonar::Sonar(SoLoud::Soloud * soloud) : console(49, 56) +{ + this->soloud = soloud; + ping.load("ping.wav"); +} + +Sonar::~Sonar() +{ + +} + diff --git a/src/flight/Sonar.h b/src/flight/Sonar.h new file mode 100644 index 0000000..3a8202b --- /dev/null +++ b/src/flight/Sonar.h @@ -0,0 +1,28 @@ +#pragma once + +#include "libtcod.h" +#include "soloud.h" +#include "soloud_wav.h" + +#include "FlightMap.h" +#include "../Drawing.h" + +class Sonar +{ +public: + + SoLoud::Soloud* soloud; + + TCODConsole console; + float sonar_radius; + bool sonar_active; + SoLoud::Wav ping; + + void update(float dt); + void draw(int rx, int ry); + void draw_world(int px, int py, FlightMap& map); + + Sonar(SoLoud::Soloud* soloud); + ~Sonar(); +}; + diff --git a/src/soloud/include/soloud.h b/src/soloud/include/soloud.h new file mode 100644 index 0000000..c759ee4 --- /dev/null +++ b/src/soloud/include/soloud.h @@ -0,0 +1,546 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_H +#define SOLOUD_H + +#include // rand +#include // sin + +#ifdef SOLOUD_NO_ASSERTS +#define SOLOUD_ASSERT(x) +#else +#ifdef _MSC_VER +#include // for sprintf in asserts +#define VC_EXTRALEAN +#define WIN32_LEAN_AND_MEAN +#include // only needed for OutputDebugStringA, should be solved somehow. +#define SOLOUD_ASSERT(x) if (!(x)) { char temp[200]; sprintf(temp, "%s(%d): assert(%s) failed.\n", __FILE__, __LINE__, #x); OutputDebugStringA(temp); __debugbreak(); } +#else +#include // assert +#define SOLOUD_ASSERT(x) assert(x) +#endif +#endif + +#ifdef WITH_SDL +#undef WITH_SDL2 +#undef WITH_SDL1 +#define WITH_SDL1 +#define WITH_SDL2 +#endif + +#ifdef WITH_SDL_STATIC +#undef WITH_SDL1_STATIC +#define WITH_SDL1_STATIC +#endif + +#ifndef M_PI +#define M_PI 3.14159265359 +#endif + +#if defined(_WIN32)||defined(_WIN64) +#define WINDOWS_VERSION +#endif + +#if !defined(DISABLE_SIMD) +#if defined(__x86_64__) || defined( _M_X64 ) || defined( __i386 ) || defined( _M_IX86 ) +#define SOLOUD_SSE_INTRINSICS +#endif +#endif + +#define SOLOUD_VERSION 201811 + +///////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////// +// Configuration defines + +// Maximum number of filters per stream +#define FILTERS_PER_STREAM 8 + +// Number of samples to process on one go +#define SAMPLE_GRANULARITY 512 + +// Maximum number of concurrent voices (hard limit is 4095) +#define VOICE_COUNT 1024 + +// Use linear resampler +#define RESAMPLER_LINEAR + +// 1)mono, 2)stereo 4)quad 6)5.1 8)7.1 +#define MAX_CHANNELS 8 + +// +///////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////// + +// Typedefs have to be made before the includes, as the +// includes depend on them. +namespace SoLoud +{ + class Soloud; + typedef void (*mutexCallFunction)(void *aMutexPtr); + typedef void (*soloudCallFunction)(Soloud *aSoloud); + typedef unsigned int result; + typedef unsigned int handle; + typedef double time; +}; + +namespace SoLoud +{ + // Class that handles aligned allocations to support vectorized operations + class AlignedFloatBuffer + { + public: + float *mData; // aligned pointer + unsigned char *mBasePtr; // raw allocated pointer (for delete) + int mFloats; // size of buffer (w/out padding) + + // ctor + AlignedFloatBuffer(); + // Allocate and align buffer + result init(unsigned int aFloats); + // Clear data to zero. + void clear(); + // dtor + ~AlignedFloatBuffer(); + }; + + // Lightweight class that handles small aligned buffer to support vectorized operations + class TinyAlignedFloatBuffer + { + public: + float *mData; // aligned pointer + unsigned char mActualData[sizeof(float) * 16 + 16]; + + // ctor + TinyAlignedFloatBuffer(); + }; +}; + +#include "soloud_filter.h" +#include "soloud_fader.h" +#include "soloud_audiosource.h" +#include "soloud_bus.h" +#include "soloud_queue.h" +#include "soloud_error.h" + +namespace SoLoud +{ + + // Soloud core class. + class Soloud + { + public: + // Back-end data; content is up to the back-end implementation. + void * mBackendData; + // Pointer for the audio thread mutex. + void * mAudioThreadMutex; + // Flag for when we're inside the mutex, used for debugging. + bool mInsideAudioThreadMutex; + // Called by SoLoud to shut down the back-end. If NULL, not called. Should be set by back-end. + soloudCallFunction mBackendCleanupFunc; + + // CTor + Soloud(); + // DTor + ~Soloud(); + + enum BACKENDS + { + AUTO = 0, + SDL1, + SDL2, + PORTAUDIO, + WINMM, + XAUDIO2, + WASAPI, + ALSA, + OSS, + OPENAL, + COREAUDIO, + OPENSLES, + VITA_HOMEBREW, + NULLDRIVER, + BACKEND_MAX, + }; + + enum FLAGS + { + // Use round-off clipper + CLIP_ROUNDOFF = 1, + ENABLE_VISUALIZATION = 2, + LEFT_HANDED_3D = 4 + }; + + // Initialize SoLoud. Must be called before SoLoud can be used. + result init(unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aBackend = Soloud::AUTO, unsigned int aSamplerate = Soloud::AUTO, unsigned int aBufferSize = Soloud::AUTO, unsigned int aChannels = 2); + + // Deinitialize SoLoud. Must be called before shutting down. + void deinit(); + + // Query SoLoud version number (should equal to SOLOUD_VERSION macro) + unsigned int getVersion() const; + + // Translate error number to an asciiz string + const char * getErrorString(result aErrorCode) const; + + // Returns current backend ID (BACKENDS enum) + unsigned int getBackendId(); + // Returns current backend string. May be NULL. + const char * getBackendString(); + // Returns current backend channel count (1 mono, 2 stereo, etc) + unsigned int getBackendChannels(); + // Returns current backend sample rate + unsigned int getBackendSamplerate(); + // Returns current backend buffer size + unsigned int getBackendBufferSize(); + + // Set speaker position in 3d space + result setSpeakerPosition(unsigned int aChannel, float aX, float aY, float aZ); + // Get speaker position in 3d space + result getSpeakerPosition(unsigned int aChannel, float &aX, float &aY, float &aZ); + + // Start playing a sound. Returns voice handle, which can be ignored or used to alter the playing sound's parameters. Negative volume means to use default. + handle play(AudioSource &aSound, float aVolume = -1.0f, float aPan = 0.0f, bool aPaused = 0, unsigned int aBus = 0); + // Start playing a sound delayed in relation to other sounds called via this function. Negative volume means to use default. + handle playClocked(time aSoundTime, AudioSource &aSound, float aVolume = -1.0f, float aPan = 0.0f, unsigned int aBus = 0); + // Start playing a 3d audio source + handle play3d(AudioSource &aSound, float aPosX, float aPosY, float aPosZ, float aVelX = 0.0f, float aVelY = 0.0f, float aVelZ = 0.0f, float aVolume = 1.0f, bool aPaused = 0, unsigned int aBus = 0); + // Start playing a 3d audio source, delayed in relation to other sounds called via this function. + handle play3dClocked(time aSoundTime, AudioSource &aSound, float aPosX, float aPosY, float aPosZ, float aVelX = 0.0f, float aVelY = 0.0f, float aVelZ = 0.0f, float aVolume = 1.0f, unsigned int aBus = 0); + // Start playing a sound without any panning. It will be played at full volume. + handle playBackground(AudioSource &aSound, float aVolume = -1.0f, bool aPaused = 0, unsigned int aBus = 0); + + // Seek the audio stream to certain point in time. Some streams can't seek backwards. Relative play speed affects time. + result seek(handle aVoiceHandle, time aSeconds); + // Stop the sound. + void stop(handle aVoiceHandle); + // Stop all voices. + void stopAll(); + // Stop all voices that play this sound source + void stopAudioSource(AudioSource &aSound); + // Count voices that play this audio source + int countAudioSource(AudioSource &aSound); + + // Set a live filter parameter. Use 0 for the global filters. + void setFilterParameter(handle aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId, float aValue); + // Get a live filter parameter. Use 0 for the global filters. + float getFilterParameter(handle aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId); + // Fade a live filter parameter. Use 0 for the global filters. + void fadeFilterParameter(handle aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId, float aTo, time aTime); + // Oscillate a live filter parameter. Use 0 for the global filters. + void oscillateFilterParameter(handle aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId, float aFrom, float aTo, time aTime); + + // Get current play time, in seconds. + time getStreamTime(handle aVoiceHandle); + // Get current sample position, in seconds. + time getStreamPosition(handle aVoiceHandle); + // Get current pause state. + bool getPause(handle aVoiceHandle); + // Get current volume. + float getVolume(handle aVoiceHandle); + // Get current overall volume (set volume * 3d volume) + float getOverallVolume(handle aVoiceHandle); + // Get current pan. + float getPan(handle aVoiceHandle); + // Get current sample rate. + float getSamplerate(handle aVoiceHandle); + // Get current voice protection state. + bool getProtectVoice(handle aVoiceHandle); + // Get the current number of busy voices. + unsigned int getActiveVoiceCount(); + // Get the current number of voices in SoLoud + unsigned int getVoiceCount(); + // Check if the handle is still valid, or if the sound has stopped. + bool isValidVoiceHandle(handle aVoiceHandle); + // Get current relative play speed. + float getRelativePlaySpeed(handle aVoiceHandle); + // Get current post-clip scaler value. + float getPostClipScaler() const; + // Get current global volume + float getGlobalVolume() const; + // Get current maximum active voice setting + unsigned int getMaxActiveVoiceCount() const; + // Query whether a voice is set to loop. + bool getLooping(handle aVoiceHandle); + // Get voice loop point value + time getLoopPoint(handle aVoiceHandle); + + // Set voice loop point value + void setLoopPoint(handle aVoiceHandle, time aLoopPoint); + // Set voice's loop state + void setLooping(handle aVoiceHandle, bool aLooping); + // Set current maximum active voice setting + result setMaxActiveVoiceCount(unsigned int aVoiceCount); + // Set behavior for inaudible sounds + void setInaudibleBehavior(handle aVoiceHandle, bool aMustTick, bool aKill); + // Set the global volume + void setGlobalVolume(float aVolume); + // Set the post clip scaler value + void setPostClipScaler(float aScaler); + // Set the pause state + void setPause(handle aVoiceHandle, bool aPause); + // Pause all voices + void setPauseAll(bool aPause); + // Set the relative play speed + result setRelativePlaySpeed(handle aVoiceHandle, float aSpeed); + // Set the voice protection state + void setProtectVoice(handle aVoiceHandle, bool aProtect); + // Set the sample rate + void setSamplerate(handle aVoiceHandle, float aSamplerate); + // Set panning value; -1 is left, 0 is center, 1 is right + void setPan(handle aVoiceHandle, float aPan); + // Set absolute left/right volumes + void setPanAbsolute(handle aVoiceHandle, float aLVolume, float aRVolume, float aLBVolume = 0, float aRBVolume = 0, float aCVolume = 0, float aSVolume = 0); + // Set overall volume + void setVolume(handle aVoiceHandle, float aVolume); + // Set delay, in samples, before starting to play samples. Calling this on a live sound will cause glitches. + void setDelaySamples(handle aVoiceHandle, unsigned int aSamples); + + // Set up volume fader + void fadeVolume(handle aVoiceHandle, float aTo, time aTime); + // Set up panning fader + void fadePan(handle aVoiceHandle, float aTo, time aTime); + // Set up relative play speed fader + void fadeRelativePlaySpeed(handle aVoiceHandle, float aTo, time aTime); + // Set up global volume fader + void fadeGlobalVolume(float aTo, time aTime); + // Schedule a stream to pause + void schedulePause(handle aVoiceHandle, time aTime); + // Schedule a stream to stop + void scheduleStop(handle aVoiceHandle, time aTime); + + // Set up volume oscillator + void oscillateVolume(handle aVoiceHandle, float aFrom, float aTo, time aTime); + // Set up panning oscillator + void oscillatePan(handle aVoiceHandle, float aFrom, float aTo, time aTime); + // Set up relative play speed oscillator + void oscillateRelativePlaySpeed(handle aVoiceHandle, float aFrom, float aTo, time aTime); + // Set up global volume oscillator + void oscillateGlobalVolume(float aFrom, float aTo, time aTime); + + // Set global filters. Set to NULL to clear the filter. + void setGlobalFilter(unsigned int aFilterId, Filter *aFilter); + + // Enable or disable visualization data gathering + void setVisualizationEnable(bool aEnable); + + // Calculate and get 256 floats of FFT data for visualization. Visualization has to be enabled before use. + float *calcFFT(); + + // Get 256 floats of wave data for visualization. Visualization has to be enabled before use. + float *getWave(); + + // Get approximate output volume for a channel for visualization. Visualization has to be enabled before use. + float getApproximateVolume(unsigned int aChannel); + + // Get current loop count. Returns 0 if handle is not valid. (All audio sources may not update loop count) + unsigned int getLoopCount(handle aVoiceHandle); + + // Get audiosource-specific information from a voice. + float getInfo(handle aVoiceHandle, unsigned int aInfoKey); + + // Create a voice group. Returns 0 if unable (out of voice groups / out of memory) + handle createVoiceGroup(); + // Destroy a voice group. + result destroyVoiceGroup(handle aVoiceGroupHandle); + // Add a voice handle to a voice group + result addVoiceToGroup(handle aVoiceGroupHandle, handle aVoiceHandle); + // Is this handle a valid voice group? + bool isVoiceGroup(handle aVoiceGroupHandle); + // Is this voice group empty? + bool isVoiceGroupEmpty(handle aVoiceGroupHandle); + + // Perform 3d audio parameter update + void update3dAudio(); + + // Set the speed of sound constant for doppler + result set3dSoundSpeed(float aSpeed); + // Get the current speed of sound constant for doppler + float get3dSoundSpeed(); + // Set 3d listener parameters + void set3dListenerParameters(float aPosX, float aPosY, float aPosZ, float aAtX, float aAtY, float aAtZ, float aUpX, float aUpY, float aUpZ, float aVelocityX = 0.0f, float aVelocityY = 0.0f, float aVelocityZ = 0.0f); + // Set 3d listener position + void set3dListenerPosition(float aPosX, float aPosY, float aPosZ); + // Set 3d listener "at" vector + void set3dListenerAt(float aAtX, float aAtY, float aAtZ); + // set 3d listener "up" vector + void set3dListenerUp(float aUpX, float aUpY, float aUpZ); + // Set 3d listener velocity + void set3dListenerVelocity(float aVelocityX, float aVelocityY, float aVelocityZ); + + // Set 3d audio source parameters + void set3dSourceParameters(handle aVoiceHandle, float aPosX, float aPosY, float aPosZ, float aVelocityX = 0.0f, float aVelocityY = 0.0f, float aVelocityZ = 0.0f); + // Set 3d audio source position + void set3dSourcePosition(handle aVoiceHandle, float aPosX, float aPosY, float aPosZ); + // Set 3d audio source velocity + void set3dSourceVelocity(handle aVoiceHandle, float aVelocityX, float aVelocityY, float aVelocityZ); + // Set 3d audio source min/max distance (distance < min means max volume) + void set3dSourceMinMaxDistance(handle aVoiceHandle, float aMinDistance, float aMaxDistance); + // Set 3d audio source attenuation parameters + void set3dSourceAttenuation(handle aVoiceHandle, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); + // Set 3d audio source doppler factor to reduce or enhance doppler effect. Default = 1.0 + void set3dSourceDopplerFactor(handle aVoiceHandle, float aDopplerFactor); + + // Rest of the stuff is used internally. + + // Returns mixed float samples in buffer. Called by the back-end, or user with null driver. + void mix(float *aBuffer, unsigned int aSamples); + // Returns mixed 16-bit signed integer samples in buffer. Called by the back-end, or user with null driver. + void mixSigned16(short *aBuffer, unsigned int aSamples); + public: + // Mix N samples * M channels. Called by other mix_ functions. + void mix_internal(unsigned int aSamples); + + // Handle rest of initialization (called from backend) + void postinit(unsigned int aSamplerate, unsigned int aBufferSize, unsigned int aFlags, unsigned int aChannels); + + // Update list of active voices + void calcActiveVoices(); + // Map resample buffers to active voices + void mapResampleBuffers(); + // Perform mixing for a specific bus + void mixBus(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize, float *aScratch, unsigned int aBus, float aSamplerate, unsigned int aChannels); + // Max. number of active voices. Busses and tickable inaudibles also count against this. + unsigned int mMaxActiveVoices; + // Highest voice in use so far + unsigned int mHighestVoice; + // Scratch buffer, used for resampling. + AlignedFloatBuffer mScratch; + // Current size of the scratch, in samples. + unsigned int mScratchSize; + // Amount of scratch needed. + unsigned int mScratchNeeded; + // Output scratch buffer, used in mix_(). + AlignedFloatBuffer mOutputScratch; + // Resampler buffers, two per active voice. + AlignedFloatBuffer *mResampleData; + // Owners of the resample data + AudioSourceInstance **mResampleDataOwner; + // Audio voices. + AudioSourceInstance *mVoice[VOICE_COUNT]; + // Output sample rate (not float) + unsigned int mSamplerate; + // Output channel count + unsigned int mChannels; + // Current backend ID + unsigned int mBackendID; + // Current backend string + const char * mBackendString; + // Maximum size of output buffer; used to calculate needed scratch. + unsigned int mBufferSize; + // Flags; see Soloud::FLAGS + unsigned int mFlags; + // Global volume. Applied before clipping. + float mGlobalVolume; + // Post-clip scaler. Applied after clipping. + float mPostClipScaler; + // Current play index. Used to create audio handles. + unsigned int mPlayIndex; + // Current sound source index. Used to create sound source IDs. + unsigned int mAudioSourceID; + // Fader for the global volume. + Fader mGlobalVolumeFader; + // Global stream time, for the global volume fader. + time mStreamTime; + // Last time seen by the playClocked call + time mLastClockedTime; + // Global filter + Filter *mFilter[FILTERS_PER_STREAM]; + // Global filter instance + FilterInstance *mFilterInstance[FILTERS_PER_STREAM]; + // Find a free voice, stopping the oldest if no free voice is found. + int findFreeVoice(); + // Converts handle to voice, if the handle is valid. Returns -1 if not. + int getVoiceFromHandle(handle aVoiceHandle) const; + // Converts voice + playindex into handle + handle getHandleFromVoice(unsigned int aVoice) const; + // Stop voice (not handle). + void stopVoice(unsigned int aVoice); + // Set voice (not handle) pan. + void setVoicePan(unsigned int aVoice, float aPan); + // Set voice (not handle) relative play speed. + result setVoiceRelativePlaySpeed(unsigned int aVoice, float aSpeed); + // Set voice (not handle) volume. + void setVoiceVolume(unsigned int aVoice, float aVolume); + // Set voice (not handle) pause state. + void setVoicePause(unsigned int aVoice, int aPause); + // Update overall volume from set and 3d volumes + void updateVoiceVolume(unsigned int aVoice); + // Update overall relative play speed from set and 3d speeds + void updateVoiceRelativePlaySpeed(unsigned int aVoice); + // Perform 3d audio calculation for array of voices + void update3dVoices(unsigned int *aVoiceList, unsigned int aVoiceCount); + // Clip the samples in the buffer + void clip(AlignedFloatBuffer &aBuffer, AlignedFloatBuffer &aDestBuffer, unsigned int aSamples, float aVolume0, float aVolume1); + // Approximate volume for channels. + float mVisualizationChannelVolume[MAX_CHANNELS]; + // Mono-mixed wave data for visualization and for visualization FFT input + float mVisualizationWaveData[256]; + // FFT output data + float mFFTData[256]; + // Snapshot of wave data for visualization + float mWaveData[256]; + + // 3d listener position + float m3dPosition[3]; + // 3d listener look-at + float m3dAt[3]; + // 3d listener up + float m3dUp[3]; + // 3d listener velocity + float m3dVelocity[3]; + // 3d speed of sound (for doppler) + float m3dSoundSpeed; + + // 3d position of speakers + float m3dSpeakerPosition[3 * MAX_CHANNELS]; + + // Data related to 3d processing, separate from AudioSource so we can do 3d calculations without audio mutex. + AudioSourceInstance3dData m3dData[VOICE_COUNT]; + + // For each voice group, first int is number of ints alocated. + unsigned int **mVoiceGroup; + unsigned int mVoiceGroupCount; + + // List of currently active voices + unsigned int mActiveVoice[VOICE_COUNT]; + // Number of currently active voices + unsigned int mActiveVoiceCount; + // Active voices list needs to be recalculated + bool mActiveVoiceDirty; + + // Remove all non-active voices from group + void trimVoiceGroup(handle aVoiceGroupHandle); + // Get pointer to the zero-terminated array of voice handles in a voice group + handle * voiceGroupHandleToArray(handle aVoiceGroupHandle) const; + + // Lock audio thread mutex. + void lockAudioMutex(); + // Unlock audio thread mutex. + void unlockAudioMutex(); + }; +}; + +#endif diff --git a/src/soloud/include/soloud_audiosource.h b/src/soloud/include/soloud_audiosource.h new file mode 100644 index 0000000..77e6156 --- /dev/null +++ b/src/soloud/include/soloud_audiosource.h @@ -0,0 +1,315 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_AUDIOSOURCE_H +#define SOLOUD_AUDIOSOURCE_H + +#include "soloud.h" +#include "soloud_fader.h" +#include "soloud_filter.h" + +namespace SoLoud +{ + class AudioSource; + class AudioSourceInstance; + class AudioSourceInstance3dData; + + class AudioCollider + { + public: + // Calculate volume multiplier. Assumed to return value between 0 and 1. + virtual float collide(Soloud *aSoloud, AudioSourceInstance3dData *aAudioInstance3dData, int aUserData) = 0; + }; + + class AudioAttenuator + { + public: + virtual float attenuate(float aDistance, float aMinDistance, float aMaxDistance, float aRolloffFactor) = 0; + }; + + class AudioSourceInstance3dData + { + public: + // ctor + AudioSourceInstance3dData(); + // Set settings from audiosource + void init(AudioSource &aSource); + // 3d position + float m3dPosition[3]; + // 3d velocity + float m3dVelocity[3]; + // 3d cone direction + /* + float m3dConeDirection[3]; + // 3d cone inner angle + float m3dConeInnerAngle; + // 3d cone outer angle + float m3dConeOuterAngle; + // 3d cone outer volume multiplier + float m3dConeOuterVolume; + */ + // 3d min distance + float m3dMinDistance; + // 3d max distance + float m3dMaxDistance; + // 3d attenuation rolloff factor + float m3dAttenuationRolloff; + // 3d attenuation model + unsigned int m3dAttenuationModel; + // 3d doppler factor + float m3dDopplerFactor; + // Pointer to a custom audio collider object + AudioCollider *mCollider; + // Pointer to a custom audio attenuator object + AudioAttenuator *mAttenuator; + // User data related to audio collider + int mColliderData; + + // Doppler sample rate multiplier + float mDopplerValue; + // Overall 3d volume + float m3dVolume; + // Channel volume + float mChannelVolume[MAX_CHANNELS]; + // Copy of flags + unsigned int mFlags; + // Latest handle for this voice + handle mHandle; + }; + + // Base class for audio instances + class AudioSourceInstance + { + public: + enum FLAGS + { + // This audio instance loops (if supported) + LOOPING = 1, + // This audio instance is protected - won't get stopped if we run out of voices + PROTECTED = 2, + // This audio instance is paused + PAUSED = 4, + // This audio instance is affected by 3d processing + PROCESS_3D = 8, + // This audio instance has listener-relative 3d coordinates + LISTENER_RELATIVE = 16, + // Currently inaudible + INAUDIBLE = 32, + // If inaudible, should be killed (default = don't kill kill) + INAUDIBLE_KILL = 64, + // If inaudible, should still be ticked (default = pause) + INAUDIBLE_TICK = 128 + }; + // Ctor + AudioSourceInstance(); + // Dtor + virtual ~AudioSourceInstance(); + // Play index; used to identify instances from handles + unsigned int mPlayIndex; + // Loop count + unsigned int mLoopCount; + // Flags; see AudioSourceInstance::FLAGS + unsigned int mFlags; + // Pan value, for getPan() + float mPan; + // Volume for each channel (panning) + float mChannelVolume[MAX_CHANNELS]; + // Set volume + float mSetVolume; + // Overall volume overall = set * 3d + float mOverallVolume; + // Base samplerate; samplerate = base samplerate * relative play speed + float mBaseSamplerate; + // Samplerate; samplerate = base samplerate * relative play speed + float mSamplerate; + // Number of channels this audio source produces + unsigned int mChannels; + // Relative play speed; samplerate = base samplerate * relative play speed + float mSetRelativePlaySpeed; + // Overall relative plays peed; overall = set * 3d + float mOverallRelativePlaySpeed; + // How long this stream has played, in seconds. + time mStreamTime; + // Position of this stream, in seconds. + time mStreamPosition; + // Fader for the audio panning + Fader mPanFader; + // Fader for the audio volume + Fader mVolumeFader; + // Fader for the relative play speed + Fader mRelativePlaySpeedFader; + // Fader used to schedule pausing of the stream + Fader mPauseScheduler; + // Fader used to schedule stopping of the stream + Fader mStopScheduler; + // Affected by some fader + int mActiveFader; + // Current channel volumes, used to ramp the volume changes to avoid clicks + float mCurrentChannelVolume[MAX_CHANNELS]; + // ID of the sound source that generated this instance + unsigned int mAudioSourceID; + // Handle of the bus this audio instance is playing on. 0 for root. + unsigned int mBusHandle; + // Filter pointer + FilterInstance *mFilter[FILTERS_PER_STREAM]; + // Initialize instance. Mostly internal use. + void init(AudioSource &aSource, int aPlayIndex); + // Buffers for the resampler + AlignedFloatBuffer *mResampleData[2]; + // Sub-sample playhead; 16.16 fixed point + unsigned int mSrcOffset; + // Samples left over from earlier pass + unsigned int mLeftoverSamples; + // Number of samples to delay streaming + unsigned int mDelaySamples; + // When looping, start playing from this time + time mLoopPoint; + + // Get N samples from the stream to the buffer. Report samples written. + virtual unsigned int getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize) = 0; + // Has the stream ended? + virtual bool hasEnded() = 0; + // Seek to certain place in the stream. Base implementation is generic "tape" seek (and slow). + virtual result seek(time aSeconds, float *mScratch, unsigned int mScratchSize); + // Rewind stream. Base implementation returns NOT_IMPLEMENTED, meaning it can't rewind. + virtual result rewind(); + // Get information. Returns 0 by default. + virtual float getInfo(unsigned int aInfoKey); + }; + + class Soloud; + + // Base class for audio sources + class AudioSource + { + public: + enum FLAGS + { + // The instances from this audio source should loop + SHOULD_LOOP = 1, + // Only one instance of this audio source should play at the same time + SINGLE_INSTANCE = 2, + // Visualization data gathering enabled. Only for busses. + VISUALIZATION_DATA = 4, + // Audio instances created from this source are affected by 3d processing + PROCESS_3D = 8, + // Audio instances created from this source have listener-relative 3d coordinates + LISTENER_RELATIVE = 16, + // Delay start of sound by the distance from listener + DISTANCE_DELAY = 32, + // If inaudible, should be killed (default) + INAUDIBLE_KILL = 64, + // If inaudible, should still be ticked (default = pause) + INAUDIBLE_TICK = 128 + }; + enum ATTENUATION_MODELS + { + // No attenuation + NO_ATTENUATION = 0, + // Inverse distance attenuation model + INVERSE_DISTANCE = 1, + // Linear distance attenuation model + LINEAR_DISTANCE = 2, + // Exponential distance attenuation model + EXPONENTIAL_DISTANCE = 3 + }; + + // Flags. See AudioSource::FLAGS + unsigned int mFlags; + // Base sample rate, used to initialize instances + float mBaseSamplerate; + // Default volume for created instances + float mVolume; + // Number of channels this audio source produces + unsigned int mChannels; + // Sound source ID. Assigned by SoLoud the first time it's played. + unsigned int mAudioSourceID; + // 3d min distance + float m3dMinDistance; + // 3d max distance + float m3dMaxDistance; + // 3d attenuation rolloff factor + float m3dAttenuationRolloff; + // 3d attenuation model + unsigned int m3dAttenuationModel; + // 3d doppler factor + float m3dDopplerFactor; + // Filter pointer + Filter *mFilter[FILTERS_PER_STREAM]; + // Pointer to the Soloud object. Needed to stop all instances in dtor. + Soloud *mSoloud; + // Pointer to a custom audio collider object + AudioCollider *mCollider; + // Pointer to custom attenuator object + AudioAttenuator *mAttenuator; + // User data related to audio collider + int mColliderData; + // When looping, start playing from this time + time mLoopPoint; + + // CTor + AudioSource(); + // Set default volume for instances + void setVolume(float aVolume); + // Set the looping of the instances created from this audio source + void setLooping(bool aLoop); + // Set whether only one instance of this sound should ever be playing at the same time + void setSingleInstance(bool aSingleInstance); + + // Set the minimum and maximum distances for 3d audio source (closer to min distance = max vol) + void set3dMinMaxDistance(float aMinDistance, float aMaxDistance); + // Set attenuation model and rolloff factor for 3d audio source + void set3dAttenuation(unsigned int aAttenuationModel, float aAttenuationRolloffFactor); + // Set doppler factor to reduce or enhance doppler effect, default = 1.0 + void set3dDopplerFactor(float aDopplerFactor); + // Set the coordinates for this audio source to be relative to listener's coordinates. + void set3dListenerRelative(bool aListenerRelative); + // Enable delaying the start of the sound based on the distance. + void set3dDistanceDelay(bool aDistanceDelay); + + // Set a custom 3d audio collider. Set to NULL to disable. + void set3dCollider(AudioCollider *aCollider, int aUserData = 0); + // Set a custom attenuator. Set to NULL to disable. + void set3dAttenuator(AudioAttenuator *aAttenuator); + + // Set behavior for inaudible sounds + void setInaudibleBehavior(bool aMustTick, bool aKill); + + // Set time to jump to when looping + void setLoopPoint(time aLoopPoint); + // Get current loop point value + time getLoopPoint(); + + // Set filter. Set to NULL to clear the filter. + virtual void setFilter(unsigned int aFilterId, Filter *aFilter); + // DTor + virtual ~AudioSource(); + // Create instance from the audio source. Called from within Soloud class. + virtual AudioSourceInstance *createInstance() = 0; + // Stop all instances of this audio source + void stop(); + }; +}; + +#endif diff --git a/src/soloud/include/soloud_bassboostfilter.h b/src/soloud/include/soloud_bassboostfilter.h new file mode 100644 index 0000000..1725184 --- /dev/null +++ b/src/soloud/include/soloud_bassboostfilter.h @@ -0,0 +1,63 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_BASSBOOSTFILTER_H +#define SOLOUD_BASSBOOSTFILTER_H + +#include "soloud.h" +#include "soloud_fftfilter.h" + +namespace SoLoud +{ + class BassboostFilter; + + class BassboostFilterInstance : public FFTFilterInstance + { + enum FILTERATTRIBUTE + { + WET = 0, + BOOST = 1 + }; + BassboostFilter *mParent; + public: + virtual void fftFilterChannel(float *aFFTBuffer, unsigned int aSamples, float aSamplerate, time aTime, unsigned int aChannel, unsigned int aChannels); + BassboostFilterInstance(BassboostFilter *aParent); + }; + + class BassboostFilter : public FFTFilter + { + public: + enum FILTERATTRIBUTE + { + WET = 0, + BOOST = 1 + }; + float mBoost; + result setParams(float aBoost); + virtual FilterInstance *createInstance(); + BassboostFilter(); + }; +} + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_biquadresonantfilter.h b/src/soloud/include/soloud_biquadresonantfilter.h new file mode 100644 index 0000000..e83e0cf --- /dev/null +++ b/src/soloud/include/soloud_biquadresonantfilter.h @@ -0,0 +1,91 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_BQRFILTER_H +#define SOLOUD_BQRFILTER_H + +#include "soloud.h" + +namespace SoLoud +{ + class BiquadResonantFilter; + + struct BQRStateData + { + float mY1, mY2, mX1, mX2; + }; + + class BiquadResonantFilterInstance : public FilterInstance + { + enum FILTERATTRIBUTE + { + WET = 0, + SAMPLERATE = 1, + FREQUENCY = 2, + RESONANCE = 3 + }; + + int mActive; + BQRStateData mState[2]; + float mA0, mA1, mA2, mB1, mB2; + int mDirty; + int mFilterType; + + BiquadResonantFilter *mParent; + void calcBQRParams(); + public: + virtual void filterChannel(float *aBuffer, unsigned int aSamples, float aSamplerate, time aTime, unsigned int aChannel, unsigned int aChannels); + virtual ~BiquadResonantFilterInstance(); + BiquadResonantFilterInstance(BiquadResonantFilter *aParent); + }; + + class BiquadResonantFilter : public Filter + { + public: + enum FILTERTYPE + { + NONE = 0, + LOWPASS = 1, + HIGHPASS = 2, + BANDPASS = 3 + }; + enum FILTERATTRIBUTE + { + WET = 0, + SAMPLERATE = 1, + FREQUENCY = 2, + RESONANCE = 3 + }; + int mFilterType; + float mSampleRate; + float mFrequency; + float mResonance; + virtual BiquadResonantFilterInstance *createInstance(); + BiquadResonantFilter(); + result setParams(int aType, float aSampleRate, float aFrequency, float aResonance); + virtual ~BiquadResonantFilter(); + }; +} + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_bus.h b/src/soloud/include/soloud_bus.h new file mode 100644 index 0000000..84d411a --- /dev/null +++ b/src/soloud/include/soloud_bus.h @@ -0,0 +1,91 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_BUS_H +#define SOLOUD_BUS_H + +#include "soloud.h" + +namespace SoLoud +{ + class Bus; + + class BusInstance : public AudioSourceInstance + { + Bus *mParent; + unsigned int mScratchSize; + AlignedFloatBuffer mScratch; + public: + // Approximate volume for channels. + float mVisualizationChannelVolume[MAX_CHANNELS]; + // Mono-mixed wave data for visualization and for visualization FFT input + float mVisualizationWaveData[256]; + + BusInstance(Bus *aParent); + virtual unsigned int getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize); + virtual bool hasEnded(); + virtual ~BusInstance(); + }; + + class Bus : public AudioSource + { + public: + Bus(); + virtual BusInstance *createInstance(); + // Set filter. Set to NULL to clear the filter. + virtual void setFilter(unsigned int aFilterId, Filter *aFilter); + // Play sound through the bus + handle play(AudioSource &aSound, float aVolume = 1.0f, float aPan = 0.0f, bool aPaused = 0); + // Play sound through the bus, delayed in relation to other sounds called via this function. + handle playClocked(time aSoundTime, AudioSource &aSound, float aVolume = 1.0f, float aPan = 0.0f); + // Start playing a 3d audio source through the bus + handle play3d(AudioSource &aSound, float aPosX, float aPosY, float aPosZ, float aVelX = 0.0f, float aVelY = 0.0f, float aVelZ = 0.0f, float aVolume = 1.0f, bool aPaused = 0); + // Start playing a 3d audio source through the bus, delayed in relation to other sounds called via this function. + handle play3dClocked(time aSoundTime, AudioSource &aSound, float aPosX, float aPosY, float aPosZ, float aVelX = 0.0f, float aVelY = 0.0f, float aVelZ = 0.0f, float aVolume = 1.0f); + // Set number of channels for the bus (default 2) + result setChannels(unsigned int aChannels); + // Enable or disable visualization data gathering + void setVisualizationEnable(bool aEnable); + + // Calculate and get 256 floats of FFT data for visualization. Visualization has to be enabled before use. + float *calcFFT(); + + // Get 256 floats of wave data for visualization. Visualization has to be enabled before use. + float *getWave(); + + // Get approximate volume for output channel for visualization. Visualization has to be enabled before use. + float getApproximateVolume(unsigned int aChannel); + public: + BusInstance *mInstance; + unsigned int mChannelHandle; + // FFT output data + float mFFTData[256]; + // Snapshot of wave data for visualization + float mWaveData[256]; + // Internal: find the bus' channel + void findBusHandle(); + }; +}; + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_c.h b/src/soloud/include/soloud_c.h new file mode 100644 index 0000000..914347e --- /dev/null +++ b/src/soloud/include/soloud_c.h @@ -0,0 +1,625 @@ +/* ************************************************** + * WARNING: this is a generated file. Do not edit. * + * Any edits will be overwritten by the generator. * + ************************************************** */ + +/* +SoLoud audio engine +Copyright (c) 2013-2016 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* SoLoud C-Api Code Generator (c)2013-2018 Jari Komppa http://iki.fi/sol/ */ + +#ifndef SOLOUD_C_H_INCLUDED +#define SOLOUD_C_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif +// Collected enumerations +enum SOLOUD_ENUMS +{ + SOLOUD_AUTO = 0, + SOLOUD_SDL1 = 1, + SOLOUD_SDL2 = 2, + SOLOUD_PORTAUDIO = 3, + SOLOUD_WINMM = 4, + SOLOUD_XAUDIO2 = 5, + SOLOUD_WASAPI = 6, + SOLOUD_ALSA = 7, + SOLOUD_OSS = 8, + SOLOUD_OPENAL = 9, + SOLOUD_COREAUDIO = 10, + SOLOUD_OPENSLES = 11, + SOLOUD_VITA_HOMEBREW = 12, + SOLOUD_NULLDRIVER = 13, + SOLOUD_BACKEND_MAX = 14, + SOLOUD_CLIP_ROUNDOFF = 1, + SOLOUD_ENABLE_VISUALIZATION = 2, + SOLOUD_LEFT_HANDED_3D = 4, + BASSBOOSTFILTER_WET = 0, + BASSBOOSTFILTER_BOOST = 1, + BIQUADRESONANTFILTER_NONE = 0, + BIQUADRESONANTFILTER_LOWPASS = 1, + BIQUADRESONANTFILTER_HIGHPASS = 2, + BIQUADRESONANTFILTER_BANDPASS = 3, + BIQUADRESONANTFILTER_WET = 0, + BIQUADRESONANTFILTER_SAMPLERATE = 1, + BIQUADRESONANTFILTER_FREQUENCY = 2, + BIQUADRESONANTFILTER_RESONANCE = 3, + FLANGERFILTER_WET = 0, + FLANGERFILTER_DELAY = 1, + FLANGERFILTER_FREQ = 2, + LOFIFILTER_WET = 0, + LOFIFILTER_SAMPLERATE = 1, + LOFIFILTER_BITDEPTH = 2, + MONOTONE_SQUARE = 0, + MONOTONE_SAW = 1, + MONOTONE_SIN = 2, + MONOTONE_SAWSIN = 3, + ROBOTIZEFILTER_WET = 0, + SFXR_COIN = 0, + SFXR_LASER = 1, + SFXR_EXPLOSION = 2, + SFXR_POWERUP = 3, + SFXR_HURT = 4, + SFXR_JUMP = 5, + SFXR_BLIP = 6, + SPEECH_KW_SAW = 0, + SPEECH_KW_TRIANGLE = 1, + SPEECH_KW_SIN = 2, + SPEECH_KW_SQUARE = 3, + SPEECH_KW_PULSE = 4, + SPEECH_KW_NOISE = 5, + SPEECH_KW_WARBLE = 6, + VIC_PAL = 0, + VIC_NTSC = 1, + VIC_BASS = 0, + VIC_ALTO = 1, + VIC_SOPRANO = 2, + VIC_NOISE = 3, + VIC_MAX_REGS = 4 +}; + +// Object handle typedefs +typedef void * AlignedFloatBuffer; +typedef void * TinyAlignedFloatBuffer; +typedef void * Soloud; +typedef void * AudioCollider; +typedef void * AudioAttenuator; +typedef void * AudioSource; +typedef void * BassboostFilter; +typedef void * BiquadResonantFilter; +typedef void * Bus; +typedef void * DCRemovalFilter; +typedef void * EchoFilter; +typedef void * Fader; +typedef void * FFTFilter; +typedef void * Filter; +typedef void * FlangerFilter; +typedef void * LofiFilter; +typedef void * Monotone; +typedef void * Openmpt; +typedef void * Queue; +typedef void * RobotizeFilter; +typedef void * Prg; +typedef void * Sfxr; +typedef void * Speech; +typedef void * TedSid; +typedef void * Vic; +typedef void * Vizsn; +typedef void * Wav; +typedef void * WaveShaperFilter; +typedef void * WavStream; +typedef void * File; + +/* + * Soloud + */ +void Soloud_destroy(Soloud * aSoloud); +Soloud * Soloud_create(); +int Soloud_init(Soloud * aSoloud); +int Soloud_initEx(Soloud * aSoloud, unsigned int aFlags /* = Soloud::CLIP_ROUNDOFF */, unsigned int aBackend /* = Soloud::AUTO */, unsigned int aSamplerate /* = Soloud::AUTO */, unsigned int aBufferSize /* = Soloud::AUTO */, unsigned int aChannels /* = 2 */); +void Soloud_deinit(Soloud * aSoloud); +unsigned int Soloud_getVersion(Soloud * aSoloud); +const char * Soloud_getErrorString(Soloud * aSoloud, int aErrorCode); +unsigned int Soloud_getBackendId(Soloud * aSoloud); +const char * Soloud_getBackendString(Soloud * aSoloud); +unsigned int Soloud_getBackendChannels(Soloud * aSoloud); +unsigned int Soloud_getBackendSamplerate(Soloud * aSoloud); +unsigned int Soloud_getBackendBufferSize(Soloud * aSoloud); +int Soloud_setSpeakerPosition(Soloud * aSoloud, unsigned int aChannel, float aX, float aY, float aZ); +int Soloud_getSpeakerPosition(Soloud * aSoloud, unsigned int aChannel, float * aX, float * aY, float * aZ); +unsigned int Soloud_play(Soloud * aSoloud, AudioSource * aSound); +unsigned int Soloud_playEx(Soloud * aSoloud, AudioSource * aSound, float aVolume /* = -1.0f */, float aPan /* = 0.0f */, int aPaused /* = 0 */, unsigned int aBus /* = 0 */); +unsigned int Soloud_playClocked(Soloud * aSoloud, double aSoundTime, AudioSource * aSound); +unsigned int Soloud_playClockedEx(Soloud * aSoloud, double aSoundTime, AudioSource * aSound, float aVolume /* = -1.0f */, float aPan /* = 0.0f */, unsigned int aBus /* = 0 */); +unsigned int Soloud_play3d(Soloud * aSoloud, AudioSource * aSound, float aPosX, float aPosY, float aPosZ); +unsigned int Soloud_play3dEx(Soloud * aSoloud, AudioSource * aSound, float aPosX, float aPosY, float aPosZ, float aVelX /* = 0.0f */, float aVelY /* = 0.0f */, float aVelZ /* = 0.0f */, float aVolume /* = 1.0f */, int aPaused /* = 0 */, unsigned int aBus /* = 0 */); +unsigned int Soloud_play3dClocked(Soloud * aSoloud, double aSoundTime, AudioSource * aSound, float aPosX, float aPosY, float aPosZ); +unsigned int Soloud_play3dClockedEx(Soloud * aSoloud, double aSoundTime, AudioSource * aSound, float aPosX, float aPosY, float aPosZ, float aVelX /* = 0.0f */, float aVelY /* = 0.0f */, float aVelZ /* = 0.0f */, float aVolume /* = 1.0f */, unsigned int aBus /* = 0 */); +unsigned int Soloud_playBackground(Soloud * aSoloud, AudioSource * aSound); +unsigned int Soloud_playBackgroundEx(Soloud * aSoloud, AudioSource * aSound, float aVolume /* = -1.0f */, int aPaused /* = 0 */, unsigned int aBus /* = 0 */); +int Soloud_seek(Soloud * aSoloud, unsigned int aVoiceHandle, double aSeconds); +void Soloud_stop(Soloud * aSoloud, unsigned int aVoiceHandle); +void Soloud_stopAll(Soloud * aSoloud); +void Soloud_stopAudioSource(Soloud * aSoloud, AudioSource * aSound); +int Soloud_countAudioSource(Soloud * aSoloud, AudioSource * aSound); +void Soloud_setFilterParameter(Soloud * aSoloud, unsigned int aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId, float aValue); +float Soloud_getFilterParameter(Soloud * aSoloud, unsigned int aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId); +void Soloud_fadeFilterParameter(Soloud * aSoloud, unsigned int aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId, float aTo, double aTime); +void Soloud_oscillateFilterParameter(Soloud * aSoloud, unsigned int aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId, float aFrom, float aTo, double aTime); +double Soloud_getStreamTime(Soloud * aSoloud, unsigned int aVoiceHandle); +double Soloud_getStreamPosition(Soloud * aSoloud, unsigned int aVoiceHandle); +int Soloud_getPause(Soloud * aSoloud, unsigned int aVoiceHandle); +float Soloud_getVolume(Soloud * aSoloud, unsigned int aVoiceHandle); +float Soloud_getOverallVolume(Soloud * aSoloud, unsigned int aVoiceHandle); +float Soloud_getPan(Soloud * aSoloud, unsigned int aVoiceHandle); +float Soloud_getSamplerate(Soloud * aSoloud, unsigned int aVoiceHandle); +int Soloud_getProtectVoice(Soloud * aSoloud, unsigned int aVoiceHandle); +unsigned int Soloud_getActiveVoiceCount(Soloud * aSoloud); +unsigned int Soloud_getVoiceCount(Soloud * aSoloud); +int Soloud_isValidVoiceHandle(Soloud * aSoloud, unsigned int aVoiceHandle); +float Soloud_getRelativePlaySpeed(Soloud * aSoloud, unsigned int aVoiceHandle); +float Soloud_getPostClipScaler(Soloud * aSoloud); +float Soloud_getGlobalVolume(Soloud * aSoloud); +unsigned int Soloud_getMaxActiveVoiceCount(Soloud * aSoloud); +int Soloud_getLooping(Soloud * aSoloud, unsigned int aVoiceHandle); +double Soloud_getLoopPoint(Soloud * aSoloud, unsigned int aVoiceHandle); +void Soloud_setLoopPoint(Soloud * aSoloud, unsigned int aVoiceHandle, double aLoopPoint); +void Soloud_setLooping(Soloud * aSoloud, unsigned int aVoiceHandle, int aLooping); +int Soloud_setMaxActiveVoiceCount(Soloud * aSoloud, unsigned int aVoiceCount); +void Soloud_setInaudibleBehavior(Soloud * aSoloud, unsigned int aVoiceHandle, int aMustTick, int aKill); +void Soloud_setGlobalVolume(Soloud * aSoloud, float aVolume); +void Soloud_setPostClipScaler(Soloud * aSoloud, float aScaler); +void Soloud_setPause(Soloud * aSoloud, unsigned int aVoiceHandle, int aPause); +void Soloud_setPauseAll(Soloud * aSoloud, int aPause); +int Soloud_setRelativePlaySpeed(Soloud * aSoloud, unsigned int aVoiceHandle, float aSpeed); +void Soloud_setProtectVoice(Soloud * aSoloud, unsigned int aVoiceHandle, int aProtect); +void Soloud_setSamplerate(Soloud * aSoloud, unsigned int aVoiceHandle, float aSamplerate); +void Soloud_setPan(Soloud * aSoloud, unsigned int aVoiceHandle, float aPan); +void Soloud_setPanAbsolute(Soloud * aSoloud, unsigned int aVoiceHandle, float aLVolume, float aRVolume); +void Soloud_setPanAbsoluteEx(Soloud * aSoloud, unsigned int aVoiceHandle, float aLVolume, float aRVolume, float aLBVolume /* = 0 */, float aRBVolume /* = 0 */, float aCVolume /* = 0 */, float aSVolume /* = 0 */); +void Soloud_setVolume(Soloud * aSoloud, unsigned int aVoiceHandle, float aVolume); +void Soloud_setDelaySamples(Soloud * aSoloud, unsigned int aVoiceHandle, unsigned int aSamples); +void Soloud_fadeVolume(Soloud * aSoloud, unsigned int aVoiceHandle, float aTo, double aTime); +void Soloud_fadePan(Soloud * aSoloud, unsigned int aVoiceHandle, float aTo, double aTime); +void Soloud_fadeRelativePlaySpeed(Soloud * aSoloud, unsigned int aVoiceHandle, float aTo, double aTime); +void Soloud_fadeGlobalVolume(Soloud * aSoloud, float aTo, double aTime); +void Soloud_schedulePause(Soloud * aSoloud, unsigned int aVoiceHandle, double aTime); +void Soloud_scheduleStop(Soloud * aSoloud, unsigned int aVoiceHandle, double aTime); +void Soloud_oscillateVolume(Soloud * aSoloud, unsigned int aVoiceHandle, float aFrom, float aTo, double aTime); +void Soloud_oscillatePan(Soloud * aSoloud, unsigned int aVoiceHandle, float aFrom, float aTo, double aTime); +void Soloud_oscillateRelativePlaySpeed(Soloud * aSoloud, unsigned int aVoiceHandle, float aFrom, float aTo, double aTime); +void Soloud_oscillateGlobalVolume(Soloud * aSoloud, float aFrom, float aTo, double aTime); +void Soloud_setGlobalFilter(Soloud * aSoloud, unsigned int aFilterId, Filter * aFilter); +void Soloud_setVisualizationEnable(Soloud * aSoloud, int aEnable); +float * Soloud_calcFFT(Soloud * aSoloud); +float * Soloud_getWave(Soloud * aSoloud); +float Soloud_getApproximateVolume(Soloud * aSoloud, unsigned int aChannel); +unsigned int Soloud_getLoopCount(Soloud * aSoloud, unsigned int aVoiceHandle); +float Soloud_getInfo(Soloud * aSoloud, unsigned int aVoiceHandle, unsigned int aInfoKey); +unsigned int Soloud_createVoiceGroup(Soloud * aSoloud); +int Soloud_destroyVoiceGroup(Soloud * aSoloud, unsigned int aVoiceGroupHandle); +int Soloud_addVoiceToGroup(Soloud * aSoloud, unsigned int aVoiceGroupHandle, unsigned int aVoiceHandle); +int Soloud_isVoiceGroup(Soloud * aSoloud, unsigned int aVoiceGroupHandle); +int Soloud_isVoiceGroupEmpty(Soloud * aSoloud, unsigned int aVoiceGroupHandle); +void Soloud_update3dAudio(Soloud * aSoloud); +int Soloud_set3dSoundSpeed(Soloud * aSoloud, float aSpeed); +float Soloud_get3dSoundSpeed(Soloud * aSoloud); +void Soloud_set3dListenerParameters(Soloud * aSoloud, float aPosX, float aPosY, float aPosZ, float aAtX, float aAtY, float aAtZ, float aUpX, float aUpY, float aUpZ); +void Soloud_set3dListenerParametersEx(Soloud * aSoloud, float aPosX, float aPosY, float aPosZ, float aAtX, float aAtY, float aAtZ, float aUpX, float aUpY, float aUpZ, float aVelocityX /* = 0.0f */, float aVelocityY /* = 0.0f */, float aVelocityZ /* = 0.0f */); +void Soloud_set3dListenerPosition(Soloud * aSoloud, float aPosX, float aPosY, float aPosZ); +void Soloud_set3dListenerAt(Soloud * aSoloud, float aAtX, float aAtY, float aAtZ); +void Soloud_set3dListenerUp(Soloud * aSoloud, float aUpX, float aUpY, float aUpZ); +void Soloud_set3dListenerVelocity(Soloud * aSoloud, float aVelocityX, float aVelocityY, float aVelocityZ); +void Soloud_set3dSourceParameters(Soloud * aSoloud, unsigned int aVoiceHandle, float aPosX, float aPosY, float aPosZ); +void Soloud_set3dSourceParametersEx(Soloud * aSoloud, unsigned int aVoiceHandle, float aPosX, float aPosY, float aPosZ, float aVelocityX /* = 0.0f */, float aVelocityY /* = 0.0f */, float aVelocityZ /* = 0.0f */); +void Soloud_set3dSourcePosition(Soloud * aSoloud, unsigned int aVoiceHandle, float aPosX, float aPosY, float aPosZ); +void Soloud_set3dSourceVelocity(Soloud * aSoloud, unsigned int aVoiceHandle, float aVelocityX, float aVelocityY, float aVelocityZ); +void Soloud_set3dSourceMinMaxDistance(Soloud * aSoloud, unsigned int aVoiceHandle, float aMinDistance, float aMaxDistance); +void Soloud_set3dSourceAttenuation(Soloud * aSoloud, unsigned int aVoiceHandle, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); +void Soloud_set3dSourceDopplerFactor(Soloud * aSoloud, unsigned int aVoiceHandle, float aDopplerFactor); +void Soloud_mix(Soloud * aSoloud, float * aBuffer, unsigned int aSamples); +void Soloud_mixSigned16(Soloud * aSoloud, short * aBuffer, unsigned int aSamples); + +/* + * AudioAttenuator + */ +void AudioAttenuator_destroy(AudioAttenuator * aAudioAttenuator); +float AudioAttenuator_attenuate(AudioAttenuator * aAudioAttenuator, float aDistance, float aMinDistance, float aMaxDistance, float aRolloffFactor); + +/* + * BassboostFilter + */ +void BassboostFilter_destroy(BassboostFilter * aBassboostFilter); +int BassboostFilter_setParams(BassboostFilter * aBassboostFilter, float aBoost); +BassboostFilter * BassboostFilter_create(); + +/* + * BiquadResonantFilter + */ +void BiquadResonantFilter_destroy(BiquadResonantFilter * aBiquadResonantFilter); +BiquadResonantFilter * BiquadResonantFilter_create(); +int BiquadResonantFilter_setParams(BiquadResonantFilter * aBiquadResonantFilter, int aType, float aSampleRate, float aFrequency, float aResonance); + +/* + * Bus + */ +void Bus_destroy(Bus * aBus); +Bus * Bus_create(); +void Bus_setFilter(Bus * aBus, unsigned int aFilterId, Filter * aFilter); +unsigned int Bus_play(Bus * aBus, AudioSource * aSound); +unsigned int Bus_playEx(Bus * aBus, AudioSource * aSound, float aVolume /* = 1.0f */, float aPan /* = 0.0f */, int aPaused /* = 0 */); +unsigned int Bus_playClocked(Bus * aBus, double aSoundTime, AudioSource * aSound); +unsigned int Bus_playClockedEx(Bus * aBus, double aSoundTime, AudioSource * aSound, float aVolume /* = 1.0f */, float aPan /* = 0.0f */); +unsigned int Bus_play3d(Bus * aBus, AudioSource * aSound, float aPosX, float aPosY, float aPosZ); +unsigned int Bus_play3dEx(Bus * aBus, AudioSource * aSound, float aPosX, float aPosY, float aPosZ, float aVelX /* = 0.0f */, float aVelY /* = 0.0f */, float aVelZ /* = 0.0f */, float aVolume /* = 1.0f */, int aPaused /* = 0 */); +unsigned int Bus_play3dClocked(Bus * aBus, double aSoundTime, AudioSource * aSound, float aPosX, float aPosY, float aPosZ); +unsigned int Bus_play3dClockedEx(Bus * aBus, double aSoundTime, AudioSource * aSound, float aPosX, float aPosY, float aPosZ, float aVelX /* = 0.0f */, float aVelY /* = 0.0f */, float aVelZ /* = 0.0f */, float aVolume /* = 1.0f */); +int Bus_setChannels(Bus * aBus, unsigned int aChannels); +void Bus_setVisualizationEnable(Bus * aBus, int aEnable); +float * Bus_calcFFT(Bus * aBus); +float * Bus_getWave(Bus * aBus); +float Bus_getApproximateVolume(Bus * aBus, unsigned int aChannel); +void Bus_setVolume(Bus * aBus, float aVolume); +void Bus_setLooping(Bus * aBus, int aLoop); +void Bus_set3dMinMaxDistance(Bus * aBus, float aMinDistance, float aMaxDistance); +void Bus_set3dAttenuation(Bus * aBus, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); +void Bus_set3dDopplerFactor(Bus * aBus, float aDopplerFactor); +void Bus_set3dListenerRelative(Bus * aBus, int aListenerRelative); +void Bus_set3dDistanceDelay(Bus * aBus, int aDistanceDelay); +void Bus_set3dCollider(Bus * aBus, AudioCollider * aCollider); +void Bus_set3dColliderEx(Bus * aBus, AudioCollider * aCollider, int aUserData /* = 0 */); +void Bus_set3dAttenuator(Bus * aBus, AudioAttenuator * aAttenuator); +void Bus_setInaudibleBehavior(Bus * aBus, int aMustTick, int aKill); +void Bus_setLoopPoint(Bus * aBus, double aLoopPoint); +double Bus_getLoopPoint(Bus * aBus); +void Bus_stop(Bus * aBus); + +/* + * DCRemovalFilter + */ +void DCRemovalFilter_destroy(DCRemovalFilter * aDCRemovalFilter); +DCRemovalFilter * DCRemovalFilter_create(); +int DCRemovalFilter_setParams(DCRemovalFilter * aDCRemovalFilter); +int DCRemovalFilter_setParamsEx(DCRemovalFilter * aDCRemovalFilter, float aLength /* = 0.1f */); + +/* + * EchoFilter + */ +void EchoFilter_destroy(EchoFilter * aEchoFilter); +EchoFilter * EchoFilter_create(); +int EchoFilter_setParams(EchoFilter * aEchoFilter, float aDelay); +int EchoFilter_setParamsEx(EchoFilter * aEchoFilter, float aDelay, float aDecay /* = 0.7f */, float aFilter /* = 0.0f */); + +/* + * FFTFilter + */ +void FFTFilter_destroy(FFTFilter * aFFTFilter); +FFTFilter * FFTFilter_create(); + +/* + * FlangerFilter + */ +void FlangerFilter_destroy(FlangerFilter * aFlangerFilter); +FlangerFilter * FlangerFilter_create(); +int FlangerFilter_setParams(FlangerFilter * aFlangerFilter, float aDelay, float aFreq); + +/* + * LofiFilter + */ +void LofiFilter_destroy(LofiFilter * aLofiFilter); +LofiFilter * LofiFilter_create(); +int LofiFilter_setParams(LofiFilter * aLofiFilter, float aSampleRate, float aBitdepth); + +/* + * Monotone + */ +void Monotone_destroy(Monotone * aMonotone); +Monotone * Monotone_create(); +int Monotone_setParams(Monotone * aMonotone, int aHardwareChannels); +int Monotone_setParamsEx(Monotone * aMonotone, int aHardwareChannels, int aWaveform /* = SQUARE */); +int Monotone_load(Monotone * aMonotone, const char * aFilename); +int Monotone_loadMem(Monotone * aMonotone, unsigned char * aMem, unsigned int aLength); +int Monotone_loadMemEx(Monotone * aMonotone, unsigned char * aMem, unsigned int aLength, int aCopy /* = false */, int aTakeOwnership /* = true */); +int Monotone_loadFile(Monotone * aMonotone, File * aFile); +void Monotone_setVolume(Monotone * aMonotone, float aVolume); +void Monotone_setLooping(Monotone * aMonotone, int aLoop); +void Monotone_set3dMinMaxDistance(Monotone * aMonotone, float aMinDistance, float aMaxDistance); +void Monotone_set3dAttenuation(Monotone * aMonotone, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); +void Monotone_set3dDopplerFactor(Monotone * aMonotone, float aDopplerFactor); +void Monotone_set3dListenerRelative(Monotone * aMonotone, int aListenerRelative); +void Monotone_set3dDistanceDelay(Monotone * aMonotone, int aDistanceDelay); +void Monotone_set3dCollider(Monotone * aMonotone, AudioCollider * aCollider); +void Monotone_set3dColliderEx(Monotone * aMonotone, AudioCollider * aCollider, int aUserData /* = 0 */); +void Monotone_set3dAttenuator(Monotone * aMonotone, AudioAttenuator * aAttenuator); +void Monotone_setInaudibleBehavior(Monotone * aMonotone, int aMustTick, int aKill); +void Monotone_setLoopPoint(Monotone * aMonotone, double aLoopPoint); +double Monotone_getLoopPoint(Monotone * aMonotone); +void Monotone_setFilter(Monotone * aMonotone, unsigned int aFilterId, Filter * aFilter); +void Monotone_stop(Monotone * aMonotone); + +/* + * Openmpt + */ +void Openmpt_destroy(Openmpt * aOpenmpt); +Openmpt * Openmpt_create(); +int Openmpt_load(Openmpt * aOpenmpt, const char * aFilename); +int Openmpt_loadMem(Openmpt * aOpenmpt, unsigned char * aMem, unsigned int aLength); +int Openmpt_loadMemEx(Openmpt * aOpenmpt, unsigned char * aMem, unsigned int aLength, int aCopy /* = false */, int aTakeOwnership /* = true */); +int Openmpt_loadFile(Openmpt * aOpenmpt, File * aFile); +void Openmpt_setVolume(Openmpt * aOpenmpt, float aVolume); +void Openmpt_setLooping(Openmpt * aOpenmpt, int aLoop); +void Openmpt_set3dMinMaxDistance(Openmpt * aOpenmpt, float aMinDistance, float aMaxDistance); +void Openmpt_set3dAttenuation(Openmpt * aOpenmpt, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); +void Openmpt_set3dDopplerFactor(Openmpt * aOpenmpt, float aDopplerFactor); +void Openmpt_set3dListenerRelative(Openmpt * aOpenmpt, int aListenerRelative); +void Openmpt_set3dDistanceDelay(Openmpt * aOpenmpt, int aDistanceDelay); +void Openmpt_set3dCollider(Openmpt * aOpenmpt, AudioCollider * aCollider); +void Openmpt_set3dColliderEx(Openmpt * aOpenmpt, AudioCollider * aCollider, int aUserData /* = 0 */); +void Openmpt_set3dAttenuator(Openmpt * aOpenmpt, AudioAttenuator * aAttenuator); +void Openmpt_setInaudibleBehavior(Openmpt * aOpenmpt, int aMustTick, int aKill); +void Openmpt_setLoopPoint(Openmpt * aOpenmpt, double aLoopPoint); +double Openmpt_getLoopPoint(Openmpt * aOpenmpt); +void Openmpt_setFilter(Openmpt * aOpenmpt, unsigned int aFilterId, Filter * aFilter); +void Openmpt_stop(Openmpt * aOpenmpt); + +/* + * Queue + */ +void Queue_destroy(Queue * aQueue); +Queue * Queue_create(); +int Queue_play(Queue * aQueue, AudioSource * aSound); +unsigned int Queue_getQueueCount(Queue * aQueue); +int Queue_isCurrentlyPlaying(Queue * aQueue, AudioSource * aSound); +int Queue_setParamsFromAudioSource(Queue * aQueue, AudioSource * aSound); +int Queue_setParams(Queue * aQueue, float aSamplerate); +int Queue_setParamsEx(Queue * aQueue, float aSamplerate, unsigned int aChannels /* = 2 */); +void Queue_setVolume(Queue * aQueue, float aVolume); +void Queue_setLooping(Queue * aQueue, int aLoop); +void Queue_set3dMinMaxDistance(Queue * aQueue, float aMinDistance, float aMaxDistance); +void Queue_set3dAttenuation(Queue * aQueue, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); +void Queue_set3dDopplerFactor(Queue * aQueue, float aDopplerFactor); +void Queue_set3dListenerRelative(Queue * aQueue, int aListenerRelative); +void Queue_set3dDistanceDelay(Queue * aQueue, int aDistanceDelay); +void Queue_set3dCollider(Queue * aQueue, AudioCollider * aCollider); +void Queue_set3dColliderEx(Queue * aQueue, AudioCollider * aCollider, int aUserData /* = 0 */); +void Queue_set3dAttenuator(Queue * aQueue, AudioAttenuator * aAttenuator); +void Queue_setInaudibleBehavior(Queue * aQueue, int aMustTick, int aKill); +void Queue_setLoopPoint(Queue * aQueue, double aLoopPoint); +double Queue_getLoopPoint(Queue * aQueue); +void Queue_setFilter(Queue * aQueue, unsigned int aFilterId, Filter * aFilter); +void Queue_stop(Queue * aQueue); + +/* + * RobotizeFilter + */ +void RobotizeFilter_destroy(RobotizeFilter * aRobotizeFilter); +RobotizeFilter * RobotizeFilter_create(); + +/* + * Prg + */ +void Prg_destroy(Prg * aPrg); +Prg * Prg_create(); +unsigned int Prg_rand(Prg * aPrg); +void Prg_srand(Prg * aPrg, int aSeed); + +/* + * Sfxr + */ +void Sfxr_destroy(Sfxr * aSfxr); +Sfxr * Sfxr_create(); +void Sfxr_resetParams(Sfxr * aSfxr); +int Sfxr_loadParams(Sfxr * aSfxr, const char * aFilename); +int Sfxr_loadParamsMem(Sfxr * aSfxr, unsigned char * aMem, unsigned int aLength); +int Sfxr_loadParamsMemEx(Sfxr * aSfxr, unsigned char * aMem, unsigned int aLength, int aCopy /* = false */, int aTakeOwnership /* = true */); +int Sfxr_loadParamsFile(Sfxr * aSfxr, File * aFile); +int Sfxr_loadPreset(Sfxr * aSfxr, int aPresetNo, int aRandSeed); +void Sfxr_setVolume(Sfxr * aSfxr, float aVolume); +void Sfxr_setLooping(Sfxr * aSfxr, int aLoop); +void Sfxr_set3dMinMaxDistance(Sfxr * aSfxr, float aMinDistance, float aMaxDistance); +void Sfxr_set3dAttenuation(Sfxr * aSfxr, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); +void Sfxr_set3dDopplerFactor(Sfxr * aSfxr, float aDopplerFactor); +void Sfxr_set3dListenerRelative(Sfxr * aSfxr, int aListenerRelative); +void Sfxr_set3dDistanceDelay(Sfxr * aSfxr, int aDistanceDelay); +void Sfxr_set3dCollider(Sfxr * aSfxr, AudioCollider * aCollider); +void Sfxr_set3dColliderEx(Sfxr * aSfxr, AudioCollider * aCollider, int aUserData /* = 0 */); +void Sfxr_set3dAttenuator(Sfxr * aSfxr, AudioAttenuator * aAttenuator); +void Sfxr_setInaudibleBehavior(Sfxr * aSfxr, int aMustTick, int aKill); +void Sfxr_setLoopPoint(Sfxr * aSfxr, double aLoopPoint); +double Sfxr_getLoopPoint(Sfxr * aSfxr); +void Sfxr_setFilter(Sfxr * aSfxr, unsigned int aFilterId, Filter * aFilter); +void Sfxr_stop(Sfxr * aSfxr); + +/* + * Speech + */ +void Speech_destroy(Speech * aSpeech); +Speech * Speech_create(); +int Speech_setText(Speech * aSpeech, const char * aText); +int Speech_setParams(Speech * aSpeech); +int Speech_setParamsEx(Speech * aSpeech, unsigned int aBaseFrequency /* = 1330 */, float aBaseSpeed /* = 10.0f */, float aBaseDeclination /* = 0.5f */, int aBaseWaveform /* = KW_TRIANGLE */); +void Speech_setVolume(Speech * aSpeech, float aVolume); +void Speech_setLooping(Speech * aSpeech, int aLoop); +void Speech_set3dMinMaxDistance(Speech * aSpeech, float aMinDistance, float aMaxDistance); +void Speech_set3dAttenuation(Speech * aSpeech, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); +void Speech_set3dDopplerFactor(Speech * aSpeech, float aDopplerFactor); +void Speech_set3dListenerRelative(Speech * aSpeech, int aListenerRelative); +void Speech_set3dDistanceDelay(Speech * aSpeech, int aDistanceDelay); +void Speech_set3dCollider(Speech * aSpeech, AudioCollider * aCollider); +void Speech_set3dColliderEx(Speech * aSpeech, AudioCollider * aCollider, int aUserData /* = 0 */); +void Speech_set3dAttenuator(Speech * aSpeech, AudioAttenuator * aAttenuator); +void Speech_setInaudibleBehavior(Speech * aSpeech, int aMustTick, int aKill); +void Speech_setLoopPoint(Speech * aSpeech, double aLoopPoint); +double Speech_getLoopPoint(Speech * aSpeech); +void Speech_setFilter(Speech * aSpeech, unsigned int aFilterId, Filter * aFilter); +void Speech_stop(Speech * aSpeech); + +/* + * TedSid + */ +void TedSid_destroy(TedSid * aTedSid); +TedSid * TedSid_create(); +int TedSid_load(TedSid * aTedSid, const char * aFilename); +int TedSid_loadToMem(TedSid * aTedSid, const char * aFilename); +int TedSid_loadMem(TedSid * aTedSid, unsigned char * aMem, unsigned int aLength); +int TedSid_loadMemEx(TedSid * aTedSid, unsigned char * aMem, unsigned int aLength, int aCopy /* = false */, int aTakeOwnership /* = true */); +int TedSid_loadFileToMem(TedSid * aTedSid, File * aFile); +int TedSid_loadFile(TedSid * aTedSid, File * aFile); +void TedSid_setVolume(TedSid * aTedSid, float aVolume); +void TedSid_setLooping(TedSid * aTedSid, int aLoop); +void TedSid_set3dMinMaxDistance(TedSid * aTedSid, float aMinDistance, float aMaxDistance); +void TedSid_set3dAttenuation(TedSid * aTedSid, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); +void TedSid_set3dDopplerFactor(TedSid * aTedSid, float aDopplerFactor); +void TedSid_set3dListenerRelative(TedSid * aTedSid, int aListenerRelative); +void TedSid_set3dDistanceDelay(TedSid * aTedSid, int aDistanceDelay); +void TedSid_set3dCollider(TedSid * aTedSid, AudioCollider * aCollider); +void TedSid_set3dColliderEx(TedSid * aTedSid, AudioCollider * aCollider, int aUserData /* = 0 */); +void TedSid_set3dAttenuator(TedSid * aTedSid, AudioAttenuator * aAttenuator); +void TedSid_setInaudibleBehavior(TedSid * aTedSid, int aMustTick, int aKill); +void TedSid_setLoopPoint(TedSid * aTedSid, double aLoopPoint); +double TedSid_getLoopPoint(TedSid * aTedSid); +void TedSid_setFilter(TedSid * aTedSid, unsigned int aFilterId, Filter * aFilter); +void TedSid_stop(TedSid * aTedSid); + +/* + * Vic + */ +void Vic_destroy(Vic * aVic); +Vic * Vic_create(); +void Vic_setModel(Vic * aVic, int model); +int Vic_getModel(Vic * aVic); +void Vic_setRegister(Vic * aVic, int reg, unsigned char value); +unsigned char Vic_getRegister(Vic * aVic, int reg); +void Vic_setVolume(Vic * aVic, float aVolume); +void Vic_setLooping(Vic * aVic, int aLoop); +void Vic_set3dMinMaxDistance(Vic * aVic, float aMinDistance, float aMaxDistance); +void Vic_set3dAttenuation(Vic * aVic, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); +void Vic_set3dDopplerFactor(Vic * aVic, float aDopplerFactor); +void Vic_set3dListenerRelative(Vic * aVic, int aListenerRelative); +void Vic_set3dDistanceDelay(Vic * aVic, int aDistanceDelay); +void Vic_set3dCollider(Vic * aVic, AudioCollider * aCollider); +void Vic_set3dColliderEx(Vic * aVic, AudioCollider * aCollider, int aUserData /* = 0 */); +void Vic_set3dAttenuator(Vic * aVic, AudioAttenuator * aAttenuator); +void Vic_setInaudibleBehavior(Vic * aVic, int aMustTick, int aKill); +void Vic_setLoopPoint(Vic * aVic, double aLoopPoint); +double Vic_getLoopPoint(Vic * aVic); +void Vic_setFilter(Vic * aVic, unsigned int aFilterId, Filter * aFilter); +void Vic_stop(Vic * aVic); + +/* + * Vizsn + */ +void Vizsn_destroy(Vizsn * aVizsn); +Vizsn * Vizsn_create(); +void Vizsn_setText(Vizsn * aVizsn, char * aText); +void Vizsn_setVolume(Vizsn * aVizsn, float aVolume); +void Vizsn_setLooping(Vizsn * aVizsn, int aLoop); +void Vizsn_set3dMinMaxDistance(Vizsn * aVizsn, float aMinDistance, float aMaxDistance); +void Vizsn_set3dAttenuation(Vizsn * aVizsn, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); +void Vizsn_set3dDopplerFactor(Vizsn * aVizsn, float aDopplerFactor); +void Vizsn_set3dListenerRelative(Vizsn * aVizsn, int aListenerRelative); +void Vizsn_set3dDistanceDelay(Vizsn * aVizsn, int aDistanceDelay); +void Vizsn_set3dCollider(Vizsn * aVizsn, AudioCollider * aCollider); +void Vizsn_set3dColliderEx(Vizsn * aVizsn, AudioCollider * aCollider, int aUserData /* = 0 */); +void Vizsn_set3dAttenuator(Vizsn * aVizsn, AudioAttenuator * aAttenuator); +void Vizsn_setInaudibleBehavior(Vizsn * aVizsn, int aMustTick, int aKill); +void Vizsn_setLoopPoint(Vizsn * aVizsn, double aLoopPoint); +double Vizsn_getLoopPoint(Vizsn * aVizsn); +void Vizsn_setFilter(Vizsn * aVizsn, unsigned int aFilterId, Filter * aFilter); +void Vizsn_stop(Vizsn * aVizsn); + +/* + * Wav + */ +void Wav_destroy(Wav * aWav); +Wav * Wav_create(); +int Wav_load(Wav * aWav, const char * aFilename); +int Wav_loadMem(Wav * aWav, unsigned char * aMem, unsigned int aLength); +int Wav_loadMemEx(Wav * aWav, unsigned char * aMem, unsigned int aLength, int aCopy /* = false */, int aTakeOwnership /* = true */); +int Wav_loadFile(Wav * aWav, File * aFile); +int Wav_loadRawWave8(Wav * aWav, unsigned char * aMem, unsigned int aLength); +int Wav_loadRawWave8Ex(Wav * aWav, unsigned char * aMem, unsigned int aLength, float aSamplerate /* = 44100.0f */, unsigned int aChannels /* = 1 */); +int Wav_loadRawWave16(Wav * aWav, short * aMem, unsigned int aLength); +int Wav_loadRawWave16Ex(Wav * aWav, short * aMem, unsigned int aLength, float aSamplerate /* = 44100.0f */, unsigned int aChannels /* = 1 */); +int Wav_loadRawWave(Wav * aWav, float * aMem, unsigned int aLength); +int Wav_loadRawWaveEx(Wav * aWav, float * aMem, unsigned int aLength, float aSamplerate /* = 44100.0f */, unsigned int aChannels /* = 1 */, int aCopy /* = false */, int aTakeOwnership /* = true */); +double Wav_getLength(Wav * aWav); +void Wav_setVolume(Wav * aWav, float aVolume); +void Wav_setLooping(Wav * aWav, int aLoop); +void Wav_set3dMinMaxDistance(Wav * aWav, float aMinDistance, float aMaxDistance); +void Wav_set3dAttenuation(Wav * aWav, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); +void Wav_set3dDopplerFactor(Wav * aWav, float aDopplerFactor); +void Wav_set3dListenerRelative(Wav * aWav, int aListenerRelative); +void Wav_set3dDistanceDelay(Wav * aWav, int aDistanceDelay); +void Wav_set3dCollider(Wav * aWav, AudioCollider * aCollider); +void Wav_set3dColliderEx(Wav * aWav, AudioCollider * aCollider, int aUserData /* = 0 */); +void Wav_set3dAttenuator(Wav * aWav, AudioAttenuator * aAttenuator); +void Wav_setInaudibleBehavior(Wav * aWav, int aMustTick, int aKill); +void Wav_setLoopPoint(Wav * aWav, double aLoopPoint); +double Wav_getLoopPoint(Wav * aWav); +void Wav_setFilter(Wav * aWav, unsigned int aFilterId, Filter * aFilter); +void Wav_stop(Wav * aWav); + +/* + * WaveShaperFilter + */ +void WaveShaperFilter_destroy(WaveShaperFilter * aWaveShaperFilter); +int WaveShaperFilter_setParams(WaveShaperFilter * aWaveShaperFilter, float aAmount); +int WaveShaperFilter_setParamsEx(WaveShaperFilter * aWaveShaperFilter, float aAmount, float aWet /* = 1.0f */); +WaveShaperFilter * WaveShaperFilter_create(); + +/* + * WavStream + */ +void WavStream_destroy(WavStream * aWavStream); +WavStream * WavStream_create(); +int WavStream_load(WavStream * aWavStream, const char * aFilename); +int WavStream_loadMem(WavStream * aWavStream, unsigned char * aData, unsigned int aDataLen); +int WavStream_loadMemEx(WavStream * aWavStream, unsigned char * aData, unsigned int aDataLen, int aCopy /* = false */, int aTakeOwnership /* = true */); +int WavStream_loadToMem(WavStream * aWavStream, const char * aFilename); +int WavStream_loadFile(WavStream * aWavStream, File * aFile); +int WavStream_loadFileToMem(WavStream * aWavStream, File * aFile); +double WavStream_getLength(WavStream * aWavStream); +void WavStream_setVolume(WavStream * aWavStream, float aVolume); +void WavStream_setLooping(WavStream * aWavStream, int aLoop); +void WavStream_set3dMinMaxDistance(WavStream * aWavStream, float aMinDistance, float aMaxDistance); +void WavStream_set3dAttenuation(WavStream * aWavStream, unsigned int aAttenuationModel, float aAttenuationRolloffFactor); +void WavStream_set3dDopplerFactor(WavStream * aWavStream, float aDopplerFactor); +void WavStream_set3dListenerRelative(WavStream * aWavStream, int aListenerRelative); +void WavStream_set3dDistanceDelay(WavStream * aWavStream, int aDistanceDelay); +void WavStream_set3dCollider(WavStream * aWavStream, AudioCollider * aCollider); +void WavStream_set3dColliderEx(WavStream * aWavStream, AudioCollider * aCollider, int aUserData /* = 0 */); +void WavStream_set3dAttenuator(WavStream * aWavStream, AudioAttenuator * aAttenuator); +void WavStream_setInaudibleBehavior(WavStream * aWavStream, int aMustTick, int aKill); +void WavStream_setLoopPoint(WavStream * aWavStream, double aLoopPoint); +double WavStream_getLoopPoint(WavStream * aWavStream); +void WavStream_setFilter(WavStream * aWavStream, unsigned int aFilterId, Filter * aFilter); +void WavStream_stop(WavStream * aWavStream); +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // SOLOUD_C_H_INCLUDED + diff --git a/src/soloud/include/soloud_dcremovalfilter.h b/src/soloud/include/soloud_dcremovalfilter.h new file mode 100644 index 0000000..293fb38 --- /dev/null +++ b/src/soloud/include/soloud_dcremovalfilter.h @@ -0,0 +1,58 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_DCREMOVAL_H +#define SOLOUD_DCREMOVAL_H + +#include "soloud.h" + +namespace SoLoud +{ + class DCRemovalFilter; + + class DCRemovalFilterInstance : public FilterInstance + { + float *mBuffer; + float *mTotals; + int mBufferLength; + DCRemovalFilter *mParent; + int mOffset; + + public: + virtual void filter(float *aBuffer, unsigned int aSamples, unsigned int aChannels, float aSamplerate, time aTime); + virtual ~DCRemovalFilterInstance(); + DCRemovalFilterInstance(DCRemovalFilter *aParent); + }; + + class DCRemovalFilter : public Filter + { + public: + float mLength; + virtual FilterInstance *createInstance(); + DCRemovalFilter(); + result setParams(float aLength = 0.1f); + }; +} + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_echofilter.h b/src/soloud/include/soloud_echofilter.h new file mode 100644 index 0000000..fcc4f8b --- /dev/null +++ b/src/soloud/include/soloud_echofilter.h @@ -0,0 +1,59 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_ECHOFILTER_H +#define SOLOUD_ECHOFILTER_H + +#include "soloud.h" + +namespace SoLoud +{ + class EchoFilter; + + class EchoFilterInstance : public FilterInstance + { + float *mBuffer; + int mBufferLength; + EchoFilter *mParent; + int mOffset; + + public: + virtual void filter(float *aBuffer, unsigned int aSamples, unsigned int aChannels, float aSamplerate, time aTime); + virtual ~EchoFilterInstance(); + EchoFilterInstance(EchoFilter *aParent); + }; + + class EchoFilter : public Filter + { + public: + float mDelay; + float mDecay; + float mFilter; + virtual FilterInstance *createInstance(); + EchoFilter(); + result setParams(float aDelay, float aDecay = 0.7f, float aFilter = 0.0f); + }; +} + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_error.h b/src/soloud/include/soloud_error.h new file mode 100644 index 0000000..1379f8e --- /dev/null +++ b/src/soloud/include/soloud_error.h @@ -0,0 +1,41 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#ifndef SOLOUD_ERROR_H +#define SOLOUD_ERROR_H + +namespace SoLoud +{ + enum SOLOUD_ERRORS + { + SO_NO_ERROR = 0, // No error + INVALID_PARAMETER = 1, // Some parameter is invalid + FILE_NOT_FOUND = 2, // File not found + FILE_LOAD_FAILED = 3, // File found, but could not be loaded + DLL_NOT_FOUND = 4, // DLL not found, or wrong DLL + OUT_OF_MEMORY = 5, // Out of memory + NOT_IMPLEMENTED = 6, // Feature not implemented + UNKNOWN_ERROR = 7 // Other error + }; +}; +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_fader.h b/src/soloud/include/soloud_fader.h new file mode 100644 index 0000000..bd1c93e --- /dev/null +++ b/src/soloud/include/soloud_fader.h @@ -0,0 +1,63 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_FADER_H +#define SOLOUD_FADER_H + +#include "soloud.h" + +namespace SoLoud +{ + // Helper class to process faders + class Fader + { + public: + // Value to fade from + float mFrom; + // Value to fade to + float mTo; + // Delta between from and to + float mDelta; + // Total time to fade + time mTime; + // Time fading started + time mStartTime; + // Time fading will end + time mEndTime; + // Current value. Used in case time rolls over. + float mCurrent; + // Active flag; 0 means disabled, 1 is active, 2 is LFO, -1 means was active, but stopped + int mActive; + // Ctor + Fader(); + // Set up LFO + void setLFO(float aFrom, float aTo, time aTime, time aStartTime); + // Set up fader + void set(float aFrom, float aTo, time aTime, time aStartTime); + // Get the current fading value + float get(time aCurrentTime); + }; +}; + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_fft.h b/src/soloud/include/soloud_fft.h new file mode 100644 index 0000000..dd54488 --- /dev/null +++ b/src/soloud/include/soloud_fft.h @@ -0,0 +1,51 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_FFT_H +#define SOLOUD_FFT_H + +#include "soloud.h" + +namespace SoLoud +{ + namespace FFT + { + // Perform 1024 unit FFT. Buffer must have 1024 floats, and will be overwritten + void fft1024(float *aBuffer); + + // Perform 256 unit FFT. Buffer must have 256 floats, and will be overwritten + void fft256(float *aBuffer); + + // Perform 256 unit IFFT. Buffer must have 256 floats, and will be overwritten + void ifft256(float *aBuffer); + + // Generic (slower) power of two FFT. Buffer is overwritten. + void fft(float *aBuffer, unsigned int aBufferLength); + + // Generic (slower) power of two IFFT. Buffer is overwritten. + void ifft(float *aBuffer, unsigned int aBufferLength); + }; +}; + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_fftfilter.h b/src/soloud/include/soloud_fftfilter.h new file mode 100644 index 0000000..751587b --- /dev/null +++ b/src/soloud/include/soloud_fftfilter.h @@ -0,0 +1,57 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_FFTFILTER_H +#define SOLOUD_FFTFILTER_H + +#include "soloud.h" + +namespace SoLoud +{ + class FFTFilter; + + class FFTFilterInstance : public FilterInstance + { + float *mTemp; + float *mInputBuffer; + float *mMixBuffer; + unsigned int mOffset[MAX_CHANNELS]; + FFTFilter *mParent; + public: + virtual void fftFilterChannel(float *aFFTBuffer, unsigned int aSamples, float aSamplerate, time aTime, unsigned int aChannel, unsigned int aChannels); + virtual void filterChannel(float *aBuffer, unsigned int aSamples, float aSamplerate, time aTime, unsigned int aChannel, unsigned int aChannels); + virtual ~FFTFilterInstance(); + FFTFilterInstance(FFTFilter *aParent); + FFTFilterInstance(); + }; + + class FFTFilter : public Filter + { + public: + virtual FilterInstance *createInstance(); + FFTFilter(); + }; +} + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_file.h b/src/soloud/include/soloud_file.h new file mode 100644 index 0000000..7667ada --- /dev/null +++ b/src/soloud/include/soloud_file.h @@ -0,0 +1,90 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_FILE_H +#define SOLOUD_FILE_H + +#include +#include "soloud.h" + +typedef void* Soloud_Filehack; + +namespace SoLoud +{ + class File + { + public: + virtual ~File() {} + unsigned int read8(); + unsigned int read16(); + unsigned int read32(); + virtual int eof() = 0; + virtual unsigned int read(unsigned char *aDst, unsigned int aBytes) = 0; + virtual unsigned int length() = 0; + virtual void seek(int aOffset) = 0; + virtual unsigned int pos() = 0; + virtual FILE * getFilePtr() { return 0; } + virtual unsigned char * getMemPtr() { return 0; } + }; + + class DiskFile : public File + { + public: + FILE *mFileHandle; + + virtual int eof(); + virtual unsigned int read(unsigned char *aDst, unsigned int aBytes); + virtual unsigned int length(); + virtual void seek(int aOffset); + virtual unsigned int pos(); + virtual ~DiskFile(); + DiskFile(); + DiskFile(FILE *fp); + result open(const char *aFilename); + virtual FILE * getFilePtr(); + }; + + class MemoryFile : public File + { + public: + unsigned char *mDataPtr; + unsigned int mDataLength; + unsigned int mOffset; + bool mDataOwned; + + virtual int eof(); + virtual unsigned int read(unsigned char *aDst, unsigned int aBytes); + virtual unsigned int length(); + virtual void seek(int aOffset); + virtual unsigned int pos(); + virtual unsigned char * getMemPtr(); + virtual ~MemoryFile(); + MemoryFile(); + result openMem(unsigned char *aData, unsigned int aDataLength, bool aCopy=false, bool aTakeOwnership=true); + result openToMem(const char *aFilename); + result openFileToMem(File *aFile); + }; +}; + +#endif diff --git a/src/soloud/include/soloud_file_hack_off.h b/src/soloud/include/soloud_file_hack_off.h new file mode 100644 index 0000000..57c2ebd --- /dev/null +++ b/src/soloud/include/soloud_file_hack_off.h @@ -0,0 +1,35 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +See soloud_file_hack_on.h +*/ + +#undef FILE +#undef fgetc +#undef fread +#undef fseek +#undef ftell +#undef fclose +#undef fopen diff --git a/src/soloud/include/soloud_file_hack_on.h b/src/soloud/include/soloud_file_hack_on.h new file mode 100644 index 0000000..2be8c54 --- /dev/null +++ b/src/soloud/include/soloud_file_hack_on.h @@ -0,0 +1,58 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +This is a "hack" header to fool third party code to use our File stuff instead +of stdio FILE* stuff. +You can use soloud_file_hack_off.h to undef the stuff defined here. +*/ + +#ifndef SEEK_SET +#error soloud_file_hack_on must be included after stdio, otherwise the #define hacks will break stdio. +#endif + +typedef void* Soloud_Filehack; + +#ifdef __cplusplus +extern "C" { +#endif + +extern int Soloud_Filehack_fgetc(Soloud_Filehack *f); +extern int Soloud_Filehack_fread(void *dst, int s, int c, Soloud_Filehack *f); +extern int Soloud_Filehack_fseek(Soloud_Filehack *f, int idx, int base); +extern int Soloud_Filehack_ftell(Soloud_Filehack *f); +extern int Soloud_Filehack_fclose(Soloud_Filehack *f); +extern Soloud_Filehack * Soloud_Filehack_fopen(const char *aFilename, char *aMode); + +#ifdef __cplusplus +} +#endif + +#define FILE Soloud_Filehack +#define fgetc Soloud_Filehack_fgetc +#define fread Soloud_Filehack_fread +#define fseek Soloud_Filehack_fseek +#define ftell Soloud_Filehack_ftell +#define fclose Soloud_Filehack_fclose +#define fopen Soloud_Filehack_fopen diff --git a/src/soloud/include/soloud_filter.h b/src/soloud/include/soloud_filter.h new file mode 100644 index 0000000..a87fc11 --- /dev/null +++ b/src/soloud/include/soloud_filter.h @@ -0,0 +1,64 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_FILTER_H +#define SOLOUD_FILTER_H + +#include "soloud.h" + +namespace SoLoud +{ + class Fader; + + class FilterInstance + { + public: + unsigned int mNumParams; + unsigned int mParamChanged; + float *mParam; + Fader *mParamFader; + + + FilterInstance(); + virtual result initParams(int aNumParams); + virtual void updateParams(time aTime); + virtual void filter(float *aBuffer, unsigned int aSamples, unsigned int aChannels, float aSamplerate, time aTime); + virtual void filterChannel(float *aBuffer, unsigned int aSamples, float aSamplerate, time aTime, unsigned int aChannel, unsigned int aChannels); + virtual float getFilterParameter(unsigned int aAttributeId); + virtual void setFilterParameter(unsigned int aAttributeId, float aValue); + virtual void fadeFilterParameter(unsigned int aAttributeId, float aTo, time aTime, time aStartTime); + virtual void oscillateFilterParameter(unsigned int aAttributeId, float aFrom, float aTo, time aTime, time aStartTime); + virtual ~FilterInstance(); + }; + + class Filter + { + public: + Filter(); + virtual FilterInstance *createInstance() = 0; + virtual ~Filter(); + }; +}; + +#endif diff --git a/src/soloud/include/soloud_flangerfilter.h b/src/soloud/include/soloud_flangerfilter.h new file mode 100644 index 0000000..e9a949d --- /dev/null +++ b/src/soloud/include/soloud_flangerfilter.h @@ -0,0 +1,65 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_FLANGERFILTER_H +#define SOLOUD_FLANGERFILTER_H + +#include "soloud.h" + +namespace SoLoud +{ + class FlangerFilter; + + class FlangerFilterInstance : public FilterInstance + { + float *mBuffer; + unsigned int mBufferLength; + FlangerFilter *mParent; + unsigned int mOffset; + double mIndex; + + public: + virtual void filter(float *aBuffer, unsigned int aSamples, unsigned int aChannels, float aSamplerate, time aTime); + virtual ~FlangerFilterInstance(); + FlangerFilterInstance(FlangerFilter *aParent); + }; + + class FlangerFilter : public Filter + { + public: + enum FILTERPARAMS + { + WET, + DELAY, + FREQ + }; + float mDelay; + float mFreq; + virtual FilterInstance *createInstance(); + FlangerFilter(); + result setParams(float aDelay, float aFreq); + }; +} + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_internal.h b/src/soloud/include/soloud_internal.h new file mode 100644 index 0000000..7502c11 --- /dev/null +++ b/src/soloud/include/soloud_internal.h @@ -0,0 +1,121 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_INTERNAL_H +#define SOLOUD_INTERNAL_H + +#include "soloud.h" + +namespace SoLoud +{ + // SDL1 back-end initialization call + result sdl1_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // SDL2 back-end initialization call + result sdl2_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // SDL1 "non-dynamic" back-end initialization call + result sdl1static_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // SDL2 "non-dynamic" back-end initialization call + result sdl2static_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // OpenAL back-end initialization call + result openal_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // Core Audio driver back-end initialization call + result coreaudio_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // OpenSL ES back-end initialization call + result opensles_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // PortAudio back-end initialization call + result portaudio_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // WinMM back-end initialization call + result winmm_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 4096, unsigned int aChannels = 2); + + // XAudio2 back-end initialization call + result xaudio2_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // WASAPI back-end initialization call + result wasapi_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 4096, unsigned int aChannels = 2); + + // OSS back-end initialization call + result oss_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // PS Vita homebrew back-end initialization call + result vita_homebrew_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // ALSA back-end initialization call + result alsa_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // null driver back-end initialization call + result null_init(SoLoud::Soloud *aSoloud, unsigned int aFlags = Soloud::CLIP_ROUNDOFF, unsigned int aSamplerate = 44100, unsigned int aBuffer = 2048, unsigned int aChannels = 2); + + // Deinterlace samples in a buffer. From 12121212 to 11112222 + void deinterlace_samples_float(const float *aSourceBuffer, float *aDestBuffer, unsigned int aSamples, unsigned int aChannels); + + // Interlace samples in a buffer. From 11112222 to 12121212 + void interlace_samples_float(const float *aSourceBuffer, float *aDestBuffer, unsigned int aSamples, unsigned int aChannels); + + // Convert to 16-bit and interlace samples in a buffer. From 11112222 to 12121212 + void interlace_samples_s16(const float *aSourceBuffer, short *aDestBuffer, unsigned int aSamples, unsigned int aChannels); +}; + +#define FOR_ALL_VOICES_PRE \ + handle *h_ = NULL; \ + handle th_[2] = { aVoiceHandle, 0 }; \ + lockAudioMutex(); \ + h_ = voiceGroupHandleToArray(aVoiceHandle); \ + if (h_ == NULL) h_ = th_; \ + while (*h_) \ + { \ + int ch = getVoiceFromHandle(*h_); \ + if (ch != -1) \ + { + +#define FOR_ALL_VOICES_POST \ + } \ + h_++; \ + } \ + unlockAudioMutex(); + +#define FOR_ALL_VOICES_PRE_3D \ + handle *h_ = NULL; \ + handle th_[2] = { aVoiceHandle, 0 }; \ + h_ = voiceGroupHandleToArray(aVoiceHandle); \ + if (h_ == NULL) h_ = th_; \ + while (*h_) \ + { \ + int ch = (*h_ & 0xfff) - 1; \ + if (ch != -1 && m3dData[ch].mHandle == *h_) \ + { + +#define FOR_ALL_VOICES_POST_3D \ + } \ + h_++; \ + } + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_lofifilter.h b/src/soloud/include/soloud_lofifilter.h new file mode 100644 index 0000000..99a6a3f --- /dev/null +++ b/src/soloud/include/soloud_lofifilter.h @@ -0,0 +1,75 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_LOFIFILTER_H +#define SOLOUD_LOFIFILTER_H + +#include "soloud.h" + +namespace SoLoud +{ + class LofiFilter; + + struct LofiChannelData + { + float mSample; + float mSamplesToSkip; + }; + + class LofiFilterInstance : public FilterInstance + { + enum FILTERPARAMS + { + WET, + SAMPLERATE, + BITDEPTH + }; + LofiChannelData mChannelData[2]; + + LofiFilter *mParent; + public: + virtual void filterChannel(float *aBuffer, unsigned int aSamples, float aSamplerate, time aTime, unsigned int aChannel, unsigned int aChannels); + virtual ~LofiFilterInstance(); + LofiFilterInstance(LofiFilter *aParent); + }; + + class LofiFilter : public Filter + { + public: + enum FILTERPARAMS + { + WET, + SAMPLERATE, + BITDEPTH + }; + float mSampleRate; + float mBitdepth; + virtual LofiFilterInstance *createInstance(); + LofiFilter(); + result setParams(float aSampleRate, float aBitdepth); + virtual ~LofiFilter(); + }; +} + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_monotone.h b/src/soloud/include/soloud_monotone.h new file mode 100644 index 0000000..bca1624 --- /dev/null +++ b/src/soloud/include/soloud_monotone.h @@ -0,0 +1,116 @@ +/* +MONOTONE module for SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef MONOTONE_H +#define MONOTONE_H + +#include "soloud.h" + +namespace SoLoud +{ + class Monotone; + class File; + + struct MonotoneSong + { + char *mTitle; + char *mComment; + unsigned char mVersion; // must be 1 + unsigned char mTotalPatterns; + unsigned char mTotalTracks; + unsigned char mCellSize; // must be 2 for version 1 + unsigned char mOrder[256]; + unsigned int *mPatternData; // 64 rows * mTotalPatterns * mTotalTracks + }; + + struct MonotoneChannel + { + int mEnabled; + int mActive; + int mFreq[3]; + int mPortamento; + int mArpCounter; + int mArp; + int mLastNote; + int mPortamentoToNote; + int mVibrato; + int mVibratoIndex; + int mVibratoDepth; + int mVibratoSpeed; + }; + + struct MonotoneHardwareChannel + { + int mEnabled; + float mSamplePos; + float mSamplePosInc; + }; + + class MonotoneInstance : public AudioSourceInstance + { + Monotone *mParent; + public: + MonotoneChannel mChannel[12]; + MonotoneHardwareChannel mOutput[12]; + int mNextChannel; + int mTempo; // ticks / row. Tick = 60hz. Default 4. + int mOrder; + int mRow; + int mSampleCount; + int mRowTick; + + MonotoneInstance(Monotone *aParent); + virtual unsigned int getAudio(float *aBuffer, unsigned int aSamples, unsigned int aBufferSize); + virtual bool hasEnded(); + }; + + class Monotone : public AudioSource + { + public: + enum MONOTONE_WAVEFORMS + { + SQUARE = 0, + SAW = 1, + SIN = 2, + SAWSIN = 3 + }; + + int mNotesHz[800]; + int mVibTable[32]; + int mHardwareChannels; + int mWaveform; + MonotoneSong mSong; + Monotone(); + ~Monotone(); + result setParams(int aHardwareChannels, int aWaveform = SQUARE); + result load(const char *aFilename); + result loadMem(unsigned char *aMem, unsigned int aLength, bool aCopy = false, bool aTakeOwnership = true); + result loadFile(File *aFile); + virtual AudioSourceInstance *createInstance(); + public: + void clear(); + }; +}; + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_openmpt.h b/src/soloud/include/soloud_openmpt.h new file mode 100644 index 0000000..9a8bd54 --- /dev/null +++ b/src/soloud/include/soloud_openmpt.h @@ -0,0 +1,62 @@ +/* +Openmpt module for SoLoud audio engine +Copyright (c) 2016 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef OPENMPT_H +#define OPENMPT_H + +#include "soloud.h" + +namespace SoLoud +{ + class Openmpt; + class File; + + class OpenmptInstance : public AudioSourceInstance + { + Openmpt *mParent; + void *mModfile; + int mPlaying; + + public: + OpenmptInstance(Openmpt *aParent); + virtual ~OpenmptInstance(); + virtual unsigned int getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize); + virtual bool hasEnded(); + }; + + class Openmpt : public AudioSource + { + public: + char *mData; + unsigned int mDataLen; + Openmpt(); + virtual ~Openmpt(); + result load(const char* aFilename); + result loadMem(unsigned char *aMem, unsigned int aLength, bool aCopy = false, bool aTakeOwnership = true); + result loadFile(File *aFile); + virtual AudioSourceInstance *createInstance(); + }; +}; + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_queue.h b/src/soloud/include/soloud_queue.h new file mode 100644 index 0000000..0491f56 --- /dev/null +++ b/src/soloud/include/soloud_queue.h @@ -0,0 +1,72 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_QUEUE_H +#define SOLOUD_QUEUE_H + +#include "soloud.h" + +#define SOLOUD_QUEUE_MAX 32 + +namespace SoLoud +{ + class Queue; + + class QueueInstance : public AudioSourceInstance + { + Queue *mParent; + public: + QueueInstance(Queue *aParent); + virtual unsigned int getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize); + virtual bool hasEnded(); + virtual ~QueueInstance(); + }; + + class Queue : public AudioSource + { + public: + Queue(); + virtual QueueInstance *createInstance(); + // Play sound through the queue + result play(AudioSource &aSound); + // Number of audio sources queued for replay + unsigned int getQueueCount(); + // Is this audio source currently playing? + bool isCurrentlyPlaying(AudioSource &aSound); + // Set params by reading them from an audio source + result setParamsFromAudioSource(AudioSource &aSound); + // Set params manually + result setParams(float aSamplerate, unsigned int aChannels = 2); + + public: + unsigned int mReadIndex, mWriteIndex, mCount; + AudioSourceInstance *mSource[SOLOUD_QUEUE_MAX]; + QueueInstance *mInstance; + handle mQueueHandle; + void findQueueHandle(); + + }; +}; + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_robotizefilter.h b/src/soloud/include/soloud_robotizefilter.h new file mode 100644 index 0000000..6b89cce --- /dev/null +++ b/src/soloud/include/soloud_robotizefilter.h @@ -0,0 +1,60 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_ROBOTIZEFILTER_H +#define SOLOUD_ROBOTIZEFILTER_H + +#include "soloud.h" +#include "soloud_fftfilter.h" + +namespace SoLoud +{ + class RobotizeFilter; + + class RobotizeFilterInstance : public FFTFilterInstance + { + enum FILTERATTRIBUTE + { + WET = 0 + }; + RobotizeFilter *mParent; + public: + virtual void fftFilterChannel(float *aFFTBuffer, unsigned int aSamples, float aSamplerate, time aTime, unsigned int aChannel, unsigned int aChannels); + RobotizeFilterInstance(RobotizeFilter *aParent); + }; + + class RobotizeFilter : public FFTFilter + { + public: + enum FILTERATTRIBUTE + { + WET = 0 + }; + float mBoost; + virtual FilterInstance *createInstance(); + RobotizeFilter(); + }; +} + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_sfxr.h b/src/soloud/include/soloud_sfxr.h new file mode 100644 index 0000000..169c6df --- /dev/null +++ b/src/soloud/include/soloud_sfxr.h @@ -0,0 +1,169 @@ +/* +SFXR module for SoLoud audio engine +Copyright (c) 2014 Jari Komppa +Based on code (c) by Tomas Pettersson, re-licensed under zlib by permission + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SFXR_H +#define SFXR_H + +#include "soloud.h" + +namespace SoLoud +{ + class File; + + class Prg + { + public: + // random generator + Prg(); + unsigned int state[16]; + unsigned int index; + unsigned int rand(); + void srand(int aSeed); + }; + + struct SfxrParams + { + int wave_type; + + float p_base_freq; + float p_freq_limit; + float p_freq_ramp; + float p_freq_dramp; + float p_duty; + float p_duty_ramp; + + float p_vib_strength; + float p_vib_speed; + float p_vib_delay; + + float p_env_attack; + float p_env_sustain; + float p_env_decay; + float p_env_punch; + + bool filter_on; + float p_lpf_resonance; + float p_lpf_freq; + float p_lpf_ramp; + float p_hpf_freq; + float p_hpf_ramp; + + float p_pha_offset; + float p_pha_ramp; + + float p_repeat_speed; + + float p_arp_speed; + float p_arp_mod; + + float master_vol; + + float sound_vol; + }; + + class Sfxr; + + class SfxrInstance : public AudioSourceInstance + { + Sfxr *mParent; + + Prg mRand; + SfxrParams mParams; + + bool playing_sample; + int phase; + double fperiod; + double fmaxperiod; + double fslide; + double fdslide; + int period; + float square_duty; + float square_slide; + int env_stage; + int env_time; + int env_length[3]; + float env_vol; + float fphase; + float fdphase; + int iphase; + float phaser_buffer[1024]; + int ipp; + float noise_buffer[32]; + float fltp; + float fltdp; + float fltw; + float fltw_d; + float fltdmp; + float fltphp; + float flthp; + float flthp_d; + float vib_phase; + float vib_speed; + float vib_amp; + int rep_time; + int rep_limit; + int arp_time; + int arp_limit; + double arp_mod; + + void resetSample(bool aRestart); + + public: + SfxrInstance(Sfxr *aParent); + virtual unsigned int getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize); + virtual bool hasEnded(); + }; + + class Sfxr : public AudioSource + { + public: + SfxrParams mParams; + + enum SFXR_PRESETS + { + COIN, + LASER, + EXPLOSION, + POWERUP, + HURT, + JUMP, + BLIP + }; + + Prg mRand; + + Sfxr(); + virtual ~Sfxr(); + void resetParams(); + result loadParams(const char* aFilename); + result loadParamsMem(unsigned char *aMem, unsigned int aLength, bool aCopy = false, bool aTakeOwnership = true); + result loadParamsFile(File *aFile); + + result loadPreset(int aPresetNo, int aRandSeed); + virtual AudioSourceInstance *createInstance(); + }; +}; + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_speech.h b/src/soloud/include/soloud_speech.h new file mode 100644 index 0000000..fa7436c --- /dev/null +++ b/src/soloud/include/soloud_speech.h @@ -0,0 +1,79 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#ifndef SOLOUD_SPEECH_H +#define SOLOUD_SPEECH_H + +#include "soloud.h" +#include "../src/audiosource/speech/darray.h" +#include "../src/audiosource/speech/klatt.h" +#include "../src/audiosource/speech/tts.h" + +namespace SoLoud +{ + class Speech; + + class Speech : public AudioSource + { + // copy of the enum in klatt.h for codegen purposes + enum KLATT_WAVEFORM + { + KW_SAW, + KW_TRIANGLE, + KW_SIN, + KW_SQUARE, + KW_PULSE, + KW_NOISE, + KW_WARBLE + }; + public: + int mBaseFrequency; + float mBaseSpeed; + float mBaseDeclination; + int mBaseWaveform; + int mFrames; + darray mElement; + Speech(); + result setText(const char *aText); + result setParams(unsigned int aBaseFrequency = 1330, float aBaseSpeed = 10.0f, float aBaseDeclination = 0.5f, int aBaseWaveform = KW_TRIANGLE); + virtual ~Speech(); + virtual AudioSourceInstance *createInstance(); + }; + + class SpeechInstance : public AudioSourceInstance + { + klatt mSynth; + Speech *mParent; + short *mSample; + int mSampleCount; + int mOffset; + public: + SpeechInstance(Speech *aParent); + virtual ~SpeechInstance(); + virtual unsigned int getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize); + virtual result rewind(); + virtual bool hasEnded(); + }; +}; + +#endif diff --git a/src/soloud/include/soloud_tedsid.h b/src/soloud/include/soloud_tedsid.h new file mode 100644 index 0000000..d238c51 --- /dev/null +++ b/src/soloud/include/soloud_tedsid.h @@ -0,0 +1,74 @@ +/* +TED/SID module for SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef TEDSID_H +#define TEDSID_H + +#include "soloud.h" + +class SIDsound; +class TED; + +namespace SoLoud +{ + class TedSid; + class File; + + class TedSidInstance : public AudioSourceInstance + { + TedSid *mParent; + SIDsound *mSID; + TED *mTED; + unsigned int mSampleCount; + int mNextReg; + int mNextVal; + int mRegValues[128]; + public: + + TedSidInstance(TedSid *aParent); + ~TedSidInstance(); + virtual unsigned int getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize); + virtual void tick(); + virtual bool hasEnded(); + virtual float getInfo(unsigned int aInfoKey); + }; + + class TedSid : public AudioSource + { + public: + File *mFile; + int mModel; + bool mFileOwned; + TedSid(); + ~TedSid(); + result load(const char *aFilename); + result loadToMem(const char *aFilename); + result loadMem(unsigned char *aMem, unsigned int aLength, bool aCopy = false, bool aTakeOwnership = true); + result loadFileToMem(File *aFile); + result loadFile(File *aFile); + virtual AudioSourceInstance *createInstance(); + }; +}; + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_thread.h b/src/soloud/include/soloud_thread.h new file mode 100644 index 0000000..7ad7c6a --- /dev/null +++ b/src/soloud/include/soloud_thread.h @@ -0,0 +1,83 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_THREAD_H +#define SOLOUD_THREAD_H + +#include "soloud.h" + +namespace SoLoud +{ + namespace Thread + { + typedef void (*threadFunction)(void *aParam); + + struct ThreadHandleData; + typedef ThreadHandleData* ThreadHandle; + + void * createMutex(); + void destroyMutex(void *aHandle); + void lockMutex(void *aHandle); + void unlockMutex(void *aHandle); + + ThreadHandle createThread(threadFunction aThreadFunction, void *aParameter); + + void sleep(int aMSec); + void wait(ThreadHandle aThreadHandle); + void release(ThreadHandle aThreadHandle); + +#define MAX_THREADPOOL_TASKS 1024 + + class PoolTask + { + public: + virtual void work() = 0; + }; + + class Pool + { + public: + // Initialize and run thread pool. For thread count 0, work is done at addWork call. + void init(int aThreadCount); + // Ctor, sets known state + Pool(); + // Dtor. Waits for the threads to finish. Work may be unfinished. + ~Pool(); + // Add work to work list. Object is not automatically deleted when work is done. + void addWork(PoolTask *aTask); + // Called from worker thread to get a new task. Returns null if no work available. + PoolTask *getWork(); + public: + int mThreadCount; // number of threads + ThreadHandle *mThread; // array of thread handles + void *mWorkMutex; // mutex to protect task array/maxtask + PoolTask *mTaskArray[MAX_THREADPOOL_TASKS]; // pointers to tasks + int mMaxTask; // how many tasks are pending + int mRobin; // cyclic counter, used to pick jobs for threads + volatile int mRunning; // running flag, used to flag threads to stop + }; + } +} + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_vic.h b/src/soloud/include/soloud_vic.h new file mode 100644 index 0000000..ef6b486 --- /dev/null +++ b/src/soloud/include/soloud_vic.h @@ -0,0 +1,108 @@ +/* +SoLoud audio engine +Copyright (c) 2015 Jari Komppa + +VIC 6560/6561 sound chip emulator +Copyright (c) 2015 Petri Hakkinen + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_VIC_H +#define SOLOUD_VIC_H + +#include "soloud.h" + +/* +A very bare bones emulator for Commodore VIC-20 sound chip. Supports both PAL and NTSC models. +Bass, alto and soprano should be quite close to original vic, noise probably not so. + +The first three channels (bass, alto and soprano) are square waveform generators with 7-bit frequency. +The highest bit of each oscillator register switches the oscillator on/off. +The fourth oscillator generates a noise waveform. + +VIC-20 does not have per channel volume control, only global volume, +which you can change by setting audio source's volume. + +To get that authentic moldy VIC-20 sound, the audio source should be coupled with a biquad resonant filter +with the following params: type = LOWPASS, sample rate = 44100, frequency = 1500, resonance = 2.0. +*/ + +namespace SoLoud +{ + class Vic; + + class VicInstance : public AudioSourceInstance + { + public: + VicInstance(Vic *aParent); + ~VicInstance(); + + virtual unsigned int getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize); + virtual bool hasEnded(); + + public: + Vic* m_parent; + unsigned int m_phase[4]; + unsigned int m_noisePos; + }; + + class Vic : public AudioSource + { + public: + // VIC model + enum + { + PAL = 0, + NTSC + }; + + // VIC sound registers + enum + { + BASS = 0, + ALTO, + SOPRANO, + NOISE, + MAX_REGS + }; + + Vic(); + + virtual ~Vic(); + + void setModel(int model); + + int getModel() const; + + void setRegister(int reg, unsigned char value); + + unsigned char getRegister(int reg); + + public: + virtual AudioSourceInstance *createInstance(); + int m_model; + float m_clocks[4]; // base clock frequencies for oscillators, dependent on VIC model + unsigned char m_regs[MAX_REGS]; + unsigned char m_noise[8192]; + }; +}; + +#endif diff --git a/src/soloud/include/soloud_vizsn.h b/src/soloud/include/soloud_vizsn.h new file mode 100644 index 0000000..35a229d --- /dev/null +++ b/src/soloud/include/soloud_vizsn.h @@ -0,0 +1,82 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +vizsn speech synthesizer (c) by Ville-Matias Heikkilä, +released under WTFPL, http://www.wtfpl.net/txt/copying/ +(in short, "do whatever you want to") + +Integration and changes to work with SoLoud by Jari Komppa, +released under same license. +*/ + +#ifndef SOLOUD_VIZSN_H +#define SOLOUD_VIZSN_H + +#include "soloud.h" + +namespace SoLoud +{ + class Vizsn; + + struct VizsnResonator + { + public: + float a, b, c, p1, p2; + + float resonate(float i); + float antiresonate(float i); + }; + + struct VizsnBank + { + VizsnResonator r[10]; + float pitch; + float frica, voice, aspir, bypas, breth; + }; + + class VizsnInstance : public AudioSourceInstance + { + public: + VizsnInstance(Vizsn *aParent); + ~VizsnInstance(); + + virtual unsigned int getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize); + virtual bool hasEnded(); + + public: + Vizsn *mParent; + VizsnBank mBank0, mBank1, mBank0to1; + int mNper, mNmod, mNopen; + int mEchobuf[1024], mPtr; + int mCurrentVoiceType; + float mPitch; + char *mS; + float mBuf[2048]; + unsigned int mBufwrite; + unsigned int mBufread; + float vcsrc(int aPitch, int aVoicetype); + float noisrc(); + float genwave(); + void setphone(VizsnBank *aB, char aP, float aPitch); + void slidePrepare(int aNumtix); + void slideTick(); + int mA; + int mB; + int mOrgv; + float mGlotlast; + }; + + class Vizsn : public AudioSource + { + public: + char *mText; + Vizsn(); + virtual ~Vizsn(); + void setText(char *aText); + public: + virtual AudioSourceInstance *createInstance(); + }; +}; + +#endif diff --git a/src/soloud/include/soloud_wav.h b/src/soloud/include/soloud_wav.h new file mode 100644 index 0000000..887c16b --- /dev/null +++ b/src/soloud/include/soloud_wav.h @@ -0,0 +1,74 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_WAV_H +#define SOLOUD_WAV_H + +#include "soloud.h" + +struct stb_vorbis; + +namespace SoLoud +{ + class Wav; + class File; + class MemoryFile; + + class WavInstance : public AudioSourceInstance + { + Wav *mParent; + unsigned int mOffset; + public: + WavInstance(Wav *aParent); + virtual unsigned int getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize); + virtual result rewind(); + virtual bool hasEnded(); + }; + + class Wav : public AudioSource + { + result loadwav(MemoryFile *aReader); + result loadogg(MemoryFile *aReader); + result loadmp3(MemoryFile *aReader); + result loadflac(MemoryFile *aReader); + result testAndLoadFile(MemoryFile *aReader); + public: + float *mData; + unsigned int mSampleCount; + + Wav(); + virtual ~Wav(); + result load(const char *aFilename); + result loadMem(unsigned char *aMem, unsigned int aLength, bool aCopy = false, bool aTakeOwnership = true); + result loadFile(File *aFile); + result loadRawWave8(unsigned char *aMem, unsigned int aLength, float aSamplerate = 44100.0f, unsigned int aChannels = 1); + result loadRawWave16(short *aMem, unsigned int aLength, float aSamplerate = 44100.0f, unsigned int aChannels = 1); + result loadRawWave(float *aMem, unsigned int aLength, float aSamplerate = 44100.0f, unsigned int aChannels = 1, bool aCopy = false, bool aTakeOwnership = true); + + virtual AudioSourceInstance *createInstance(); + time getLength(); + }; +}; + +#endif diff --git a/src/soloud/include/soloud_waveshaperfilter.h b/src/soloud/include/soloud_waveshaperfilter.h new file mode 100644 index 0000000..11348a1 --- /dev/null +++ b/src/soloud/include/soloud_waveshaperfilter.h @@ -0,0 +1,54 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_WAVESHAPERFILTER_H +#define SOLOUD_WAVESHAPERFILTER_H + +#include "soloud.h" + +namespace SoLoud +{ + class WaveShaperFilter; + + class WaveShaperFilterInstance : public FilterInstance + { + WaveShaperFilter *mParent; + public: + virtual void filterChannel(float *aBuffer, unsigned int aSamples, float aSamplerate, time aTime, unsigned int aChannel, unsigned int aChannels); + virtual ~WaveShaperFilterInstance(); + WaveShaperFilterInstance(WaveShaperFilter *aParent); + }; + + class WaveShaperFilter : public Filter + { + public: + float mAmount, mWet; + virtual WaveShaperFilterInstance *createInstance(); + result setParams(float aAmount, float aWet = 1.0f); + WaveShaperFilter(); + virtual ~WaveShaperFilter(); + }; +} + +#endif \ No newline at end of file diff --git a/src/soloud/include/soloud_wavstream.h b/src/soloud/include/soloud_wavstream.h new file mode 100644 index 0000000..ba7739c --- /dev/null +++ b/src/soloud/include/soloud_wavstream.h @@ -0,0 +1,106 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef SOLOUD_WAVSTREAM_H +#define SOLOUD_WAVSTREAM_H + +#include +#include "soloud.h" + +struct stb_vorbis; +#ifndef dr_flac_h +struct drflac; +#endif +#ifndef dr_mp3_h +struct drmp3; +#endif +#ifndef dr_wav_h +struct drwav; +#endif + +namespace SoLoud +{ + class WavStream; + class File; + + class WavStreamInstance : public AudioSourceInstance + { + WavStream *mParent; + unsigned int mOffset; + File *mFile; + union codec + { + stb_vorbis *mOgg; + drflac *mFlac; + drmp3 *mMp3; + drwav *mWav; + } mCodec; + unsigned int mOggFrameSize; + unsigned int mOggFrameOffset; + float **mOggOutputs; + public: + WavStreamInstance(WavStream *aParent); + virtual unsigned int getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize); + virtual result rewind(); + virtual bool hasEnded(); + virtual ~WavStreamInstance(); + }; + + enum WAVSTREAM_FILETYPE + { + WAVSTREAM_WAV = 0, + WAVSTREAM_OGG = 1, + WAVSTREAM_FLAC = 2, + WAVSTREAM_MP3 = 3 + }; + + class WavStream : public AudioSource + { + result loadwav(File *fp); + result loadogg(File *fp); + result loadflac(File *fp); + result loadmp3(File *fp); + public: + int mFiletype; + char *mFilename; + File *mMemFile; + File *mStreamFile; + unsigned int mSampleCount; + + WavStream(); + virtual ~WavStream(); + result load(const char *aFilename); + result loadMem(unsigned char *aData, unsigned int aDataLen, bool aCopy = false, bool aTakeOwnership = true); + result loadToMem(const char *aFilename); + result loadFile(File *aFile); + result loadFileToMem(File *aFile); + virtual AudioSourceInstance *createInstance(); + time getLength(); + + public: + result parse(File *aFile); + }; +}; + +#endif \ No newline at end of file diff --git a/src/soloud/src/audiosource/monotone/soloud_monotone.cpp b/src/soloud/src/audiosource/monotone/soloud_monotone.cpp new file mode 100644 index 0000000..ee2593d --- /dev/null +++ b/src/soloud/src/audiosource/monotone/soloud_monotone.cpp @@ -0,0 +1,476 @@ +/* +MONOTONE module for SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include +#include +#include +#include "soloud_monotone.h" +#include "soloud_file.h" + +namespace SoLoud +{ + + MonotoneInstance::MonotoneInstance(Monotone *aParent) + { + mParent = aParent; + mOrder = 0; + mRow = 0; + mTempo = 4; + mSampleCount = 0; + mNextChannel = 0; + mRowTick = 0; + int i; + for (i = 0; i < 12; i++) + { + mOutput[i].mSamplePos = 0; + mOutput[i].mSamplePosInc = 0; + mOutput[i].mEnabled = i < mParent->mHardwareChannels && i < mParent->mSong.mTotalTracks; + mChannel[i].mEnabled = i < mParent->mSong.mTotalTracks; + mChannel[i].mActive = 0; + mChannel[i].mArpCounter = 0; + mChannel[i].mLastNote = 0; + mChannel[i].mPortamentoToNote = 0; + mChannel[i].mArp = 0; + mChannel[i].mVibrato = 0; + mChannel[i].mVibratoIndex = 0; + mChannel[i].mVibratoDepth = 1; + mChannel[i].mVibratoSpeed = 1; + } + } + + unsigned int MonotoneInstance::getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize) + { + int samplesPerTick = (int)floor(mSamplerate / 60); + unsigned int i; + for (i = 0; i < 12; i++) + { + mOutput[i].mEnabled = i < (unsigned int)mParent->mHardwareChannels && i < (unsigned int)mParent->mSong.mTotalTracks; + } + for (i = 0; i < aSamplesToRead; i++) + { + if ((mSampleCount % samplesPerTick) == 0) + { + // new tick + mRowTick++; + if (mRowTick >= mTempo) + { + mRowTick = 0; + // Process row + int patternjump = mOrder + 1; + int rowjump = 0; + int dojump = 0; + int pattern = mParent->mSong.mOrder[mOrder]; + int j; + for (j = 0; j < mParent->mSong.mTotalTracks; j++) + { + unsigned int d = mParent->mSong.mPatternData[(pattern * 64 + mRow) * mParent->mSong.mTotalTracks + j]; + unsigned int note = (d >> 9) & 127; + unsigned int effect = (d >> 6) & 7; + unsigned int effectdata = (d)& 63; + unsigned int effectdata1 = (d >> 3) & 7; + unsigned int effectdata2 = (d >> 0) & 7; + + // by default, effects are off, and have to be set on every row. + mChannel[j].mPortamento = 0; + mChannel[j].mArp = 0; + mChannel[j].mVibrato = 0; + + int oldhz = mChannel[j].mFreq[0]; + + if (note == 127) + { + // noteEnd + mChannel[j].mActive = 0; + mChannel[j].mFreq[0] = 0; + mChannel[j].mFreq[1] = 0; + mChannel[j].mFreq[2] = 0; + mChannel[j].mPortamento = 0; + mChannel[j].mLastNote = 0; + } + else + if (note != 0) + { + mChannel[j].mActive = 1; + mChannel[j].mFreq[0] = mParent->mNotesHz[note * 8]; + mChannel[j].mFreq[1] = mChannel[j].mFreq[0]; + mChannel[j].mFreq[2] = mChannel[j].mFreq[0]; + mChannel[j].mPortamento = 0; + mChannel[j].mLastNote = note; + mChannel[j].mVibratoIndex = 0; + } + else + if (note == 0) + { + note = mChannel[j].mLastNote; + } + + switch (effect) + { + case 0x0: + // arp + mChannel[j].mFreq[1] = mParent->mNotesHz[(note + effectdata1) * 8]; + mChannel[j].mFreq[2] = mParent->mNotesHz[(note + effectdata2) * 8]; + if (effectdata1 || effectdata2) + mChannel[j].mArp = 1; + break; + case 0x1: + // portamento up + mChannel[j].mPortamento = effectdata; + break; + case 0x2: + // portamento down + mChannel[j].mPortamento = -(signed)effectdata; + break; + case 0x3: + // portamento to note + mChannel[j].mPortamentoToNote = mParent->mNotesHz[note * 8]; + if (oldhz != mChannel[j].mPortamentoToNote) + { + mChannel[j].mFreq[0] = oldhz; + mChannel[j].mPortamento = effectdata; + if (oldhz > mChannel[j].mPortamentoToNote) + mChannel[j].mPortamento *= -1; + } + else + { + mChannel[j].mPortamentoToNote = 0; + } + break; + case 0x4: + // vibrato + mChannel[j].mVibrato = 1; + if (effectdata2 != 0) mChannel[j].mVibratoDepth = effectdata2; + if (effectdata1 != 0) mChannel[j].mVibratoSpeed = effectdata1; + break; + case 0x5: + // pattern jump + patternjump = effectdata; + dojump = 1; + break; + case 0x6: + // row jump + rowjump = effectdata; + dojump = 1; + break; + case 0x7: + // set speed + mTempo = effectdata; + break; + } + } + + mRow++; + + if (dojump) + { + mRow = rowjump; + mOrder = patternjump; + } + + if (mRow == 64) + { + mRow = 0; + mOrder++; + if (mParent->mSong.mOrder[mOrder] == 0xff) + mOrder = 0; + } + } + + int j; + + // per tick events + for (j = 0; j < mParent->mSong.mTotalTracks; j++) + { + if (mChannel[j].mActive) + { + if (mChannel[j].mVibrato) + { + mChannel[j].mFreq[0] = mParent->mNotesHz[mChannel[j].mLastNote * 8 + (mParent->mVibTable[mChannel[j].mVibratoIndex] * mChannel[j].mVibratoDepth) / 64]; + mChannel[j].mVibratoIndex += mChannel[j].mVibratoSpeed; + mChannel[j].mVibratoIndex %= 32; + } + if (mChannel[j].mPortamento && mRowTick != 0) + { + mChannel[j].mFreq[0] += mChannel[j].mPortamento; + if (mChannel[j].mPortamentoToNote) + { + if ((mChannel[j].mPortamento > 0 && mChannel[j].mFreq[0] >= mChannel[j].mPortamentoToNote) || + (mChannel[j].mPortamento < 0 && mChannel[j].mFreq[0] <= mChannel[j].mPortamentoToNote)) + { + mChannel[j].mFreq[0] = mChannel[j].mPortamentoToNote; + mChannel[j].mPortamentoToNote = 0; + } + } + } + } + } + + // Channel fill + + int gotit = 0; + int tries = 0; + + for (j = 0; j < mParent->mHardwareChannels; j++) + mOutput[j].mSamplePosInc = 0; + + while (gotit < mParent->mHardwareChannels && tries < mParent->mSong.mTotalTracks) + { + if (mChannel[mNextChannel].mActive) + { + if (mChannel[mNextChannel].mArp) + { + mOutput[gotit].mSamplePosInc = 1.0f / (mSamplerate / mChannel[mNextChannel].mFreq[mChannel[mNextChannel].mArpCounter]); + mChannel[mNextChannel].mArpCounter++; + mChannel[mNextChannel].mArpCounter %= 3; + } + else + { + mOutput[gotit].mSamplePosInc = 1.0f / (mSamplerate / mChannel[mNextChannel].mFreq[0]); + } + gotit++; + } + mNextChannel++; + mNextChannel %= mParent->mSong.mTotalTracks; + tries++; + } + } + + aBuffer[i] = 0; + int j; + switch (mParent->mWaveform) + { + case Monotone::SAW: + for (j = 0; j < 12; j++) + { + if (mOutput[j].mEnabled) + { + float bleh = mOutput[j].mSamplePos + mOutput[j].mSamplePosInc; + mOutput[j].mSamplePos = bleh - (long)bleh; + // saw: + aBuffer[i] += ((mOutput[j].mSamplePos) - 0.5f) * 0.5f; + } + } + break; + case Monotone::SIN: + for (j = 0; j < 12; j++) + { + if (mOutput[j].mEnabled) + { + float bleh = mOutput[j].mSamplePos + mOutput[j].mSamplePosInc; + mOutput[j].mSamplePos = bleh - (long)bleh; + // sin: + aBuffer[i] += (float)sin(mOutput[j].mSamplePos * M_PI * 2) * 0.5f; + } + } + break; + case Monotone::SAWSIN: + for (j = 0; j < 12; j++) + { + if (mOutput[j].mEnabled) + { + float bleh = mOutput[j].mSamplePos + mOutput[j].mSamplePosInc; + mOutput[j].mSamplePos = bleh - (long)bleh; + // sawsin: + bleh = ((mOutput[j].mSamplePos) - 0.5f); + bleh *= (float)sin(mOutput[j].mSamplePos * M_PI * 2); + aBuffer[i] += bleh; + } + } + break; + case Monotone::SQUARE: + default: + for (j = 0; j < 12; j++) + { + if (mOutput[j].mEnabled) + { + float bleh = mOutput[j].mSamplePos + mOutput[j].mSamplePosInc; + mOutput[j].mSamplePos = bleh - (long)bleh; + // square: + aBuffer[i] += (mOutput[j].mSamplePos > 0.5f) ? 0.25f : -0.25f; + } + } + break; + } + + mSampleCount++; + } + return aSamplesToRead; + } + + bool MonotoneInstance::hasEnded() + { + return 0; + } + + Monotone::Monotone() + { + int i; + float temphz = 27.5f; + int IBO = 12; // Intervals Between Octaves + int IBN = 8; // Intervals Between Notes + float interval = 1.00724641222f;//exp(ln(2)/(IBO*IBN)); + int maxnote = 3 + (8 * IBO) + 1; + + mNotesHz[0] = 440; + mNotesHz[1 * IBN] = (int)floor(temphz + 0.5f); + + for (i = (1 * IBN) - 1; i > 1; i--) + { + temphz = temphz / interval; + if (temphz < 19) temphz = 19; // orig limitation, we could go lower though + mNotesHz[i] = (int)floor(temphz + 0.5f); + } + temphz = 27.5f; + for (i = (1 * IBN) + 1; i < maxnote * IBN; i++) + { + temphz = temphz * interval; + mNotesHz[i] = (int)floor(temphz + 0.5f); + } + + for (i = 0; i < 32; i++) + mVibTable[i] = (int)floor(0.5 + 64 * sin(i * M_PI / 32 * 2)); + + mSong.mTitle = 0; + mSong.mComment = 0; + mSong.mPatternData = 0; + + mBaseSamplerate = 44100; + mChannels = 1; + + mHardwareChannels = 1; + mWaveform = SQUARE; + } + + void Monotone::clear() + { + stop(); + + delete[] mSong.mTitle; + delete[] mSong.mComment; + delete[] mSong.mPatternData; + + mSong.mTitle = 0; + mSong.mComment = 0; + mSong.mPatternData = 0; + } + + Monotone::~Monotone() + { + stop(); + clear(); + } + + static char * mystrdup(const char *src) + { + int len = (int)strlen(src); + char * res = new char[len + 1]; + memcpy(res, src, len); + res[len] = 0; + return res; + } + + result Monotone::setParams(int aHardwareChannels, int aWaveform) + { + if (aHardwareChannels <= 0 || aWaveform < 0) + return INVALID_PARAMETER; + mHardwareChannels = aHardwareChannels; + mWaveform = aWaveform; + return SO_NO_ERROR; + } + + result Monotone::loadMem(unsigned char *aMem, unsigned int aLength, bool aCopy, bool aTakeOwnership) + { + MemoryFile mf; + int res = mf.openMem(aMem, aLength, aCopy, aTakeOwnership); + if (res != SO_NO_ERROR) + return res; + return loadFile(&mf); + } + + result Monotone::load(const char *aFilename) + { + DiskFile df; + int res = df.open(aFilename); + if (res != SO_NO_ERROR) + return res; + return loadFile(&df); + } + + result Monotone::loadFile(File *aFile) + { + if (aFile == NULL) + return INVALID_PARAMETER; + clear(); + int i; + unsigned char temp[200]; + aFile->read(temp, 9); + char magic[] = "\bMONOTONE"; + for (i = 0; i < 9; i++) + { + if (temp[i] != magic[i]) + { + return FILE_LOAD_FAILED; + } + } + aFile->read(temp, 41); + temp[temp[0] + 1] = 0; // pascal -> asciiz: pascal strings have length as first byte + mSong.mTitle = mystrdup((char*)temp + 1); + aFile->read(temp, 41); + temp[temp[0] + 1] = 0; // pascal -> asciiz: pascal strings have length as first byte + mSong.mComment = mystrdup((char*)temp + 1); + aFile->read(temp, 4); + mSong.mVersion = temp[0]; + mSong.mTotalPatterns = temp[1]; + mSong.mTotalTracks = temp[2]; + mSong.mCellSize = temp[3]; + if (mSong.mVersion != 1 || mSong.mCellSize != 2) + { + return FILE_LOAD_FAILED; + } + aFile->read(mSong.mOrder, 256); + int totalnotes = 64 * mSong.mTotalPatterns * mSong.mTotalTracks; + mSong.mPatternData = new unsigned int[totalnotes]; + for (i = 0; i < totalnotes; i++) + { + aFile->read(temp, 2); + unsigned int datavalue = temp[0] | (temp[1] << 8); + mSong.mPatternData[i] = datavalue; + //unsigned int note = (datavalue >> 9) & 127; + //unsigned int effect = (datavalue >> 6) & 7; + //unsigned int effectdata = (datavalue)& 63; + //unsigned int effectdata1 = (datavalue >> 3) & 7; + //unsigned int effectdata2 = (datavalue >> 0) & 7; + } + + return SO_NO_ERROR; + } + + + AudioSourceInstance * Monotone::createInstance() + { + return new MonotoneInstance(this); + } + +}; \ No newline at end of file diff --git a/src/soloud/src/audiosource/openmpt/soloud_openmpt.cpp b/src/soloud/src/audiosource/openmpt/soloud_openmpt.cpp new file mode 100644 index 0000000..6c64e51 --- /dev/null +++ b/src/soloud/src/audiosource/openmpt/soloud_openmpt.cpp @@ -0,0 +1,151 @@ +/* +Openmpt module for SoLoud audio engine +Copyright (c) 2016 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include +#include "soloud_openmpt.h" +#include "soloud_file.h" + +extern "C" +{ + void * openmpt_module_create_from_memory(const void * filedata, size_t filesize, void *logfunc, void * user,void * ctls); + void openmpt_module_destroy(void * mod); + int openmpt_module_read_float_stereo(void * mod, int samplerate, size_t count, float * left, float * right); +} + +namespace SoLoud +{ + OpenmptInstance::OpenmptInstance(Openmpt *aParent) + { + mParent = aParent; + mModfile = openmpt_module_create_from_memory((const void*)mParent->mData, mParent->mDataLen, NULL, NULL, NULL); + mPlaying = mModfile != NULL; + } + + unsigned int OpenmptInstance::getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize) + { + if (mModfile == NULL) + return 0; + int s = aSamplesToRead; + unsigned int outofs = 0; + + while (s && mPlaying) + { + int samples = 512; + if (s < samples) samples = s; + int res = openmpt_module_read_float_stereo(mModfile, (int)floor(mSamplerate), samples, aBuffer + outofs, aBuffer + outofs + aBufferSize); + if (res == 0) + { + mPlaying = 0; + return outofs; + } + outofs += samples; + s -= samples; + } + + return outofs; + } + + bool OpenmptInstance::hasEnded() + { + return !mPlaying; + } + + OpenmptInstance::~OpenmptInstance() + { + if (mModfile) + { + openmpt_module_destroy(mModfile); + } + mModfile = 0; + } + + result Openmpt::loadMem(unsigned char *aMem, unsigned int aLength, bool aCopy, bool aTakeOwnership) + { + MemoryFile mf; + int res = mf.openMem(aMem, aLength, aCopy, aTakeOwnership); + if (res != SO_NO_ERROR) + return res; + return loadFile(&mf); + } + + result Openmpt::load(const char *aFilename) + { + DiskFile df; + int res = df.open(aFilename); + if (res != SO_NO_ERROR) + return res; + return loadFile(&df); + } + + result Openmpt::loadFile(File *aFile) + { + if (mData) + { + delete[] mData; + } + + mDataLen = aFile->length(); + mData = new char[mDataLen]; + if (!mData) + { + mData = 0; + mDataLen = 0; + return OUT_OF_MEMORY; + } + aFile->read((unsigned char*)mData, mDataLen); + + void *mpf = openmpt_module_create_from_memory((const void*)mData, mDataLen, NULL, NULL, NULL); + if (!mpf) + { + delete[] mData; + mDataLen = 0; + return FILE_LOAD_FAILED; + } + openmpt_module_destroy(mpf); + return 0; + } + + Openmpt::Openmpt() + { + mBaseSamplerate = 44100; + mChannels = 2; + mData = 0; + mDataLen = 0; + } + + Openmpt::~Openmpt() + { + stop(); + delete[] mData; + mData = 0; + mDataLen = 0; + } + + AudioSourceInstance * Openmpt::createInstance() + { + return new OpenmptInstance(this); + } + +}; diff --git a/src/soloud/src/audiosource/openmpt/soloud_openmpt_dll.c b/src/soloud/src/audiosource/openmpt/soloud_openmpt_dll.c new file mode 100644 index 0000000..45ec003 --- /dev/null +++ b/src/soloud/src/audiosource/openmpt/soloud_openmpt_dll.c @@ -0,0 +1,132 @@ +/* +Openmpt module for SoLoud audio engine +Copyright (c) 2016 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include +#if defined(_WIN32)||defined(_WIN64) +#define WINDOWS_VERSION +#endif // __WINDOWS__ +#include + +typedef void * (*dll_openmpt_module_create_from_memory)(const void * filedata, size_t filesize, void *logfunc, void * user, void * ctls); +typedef void (*dll_openmpt_module_destroy)(void * mod); +typedef int (*dll_openmpt_module_read_float_stereo)(void * mod, int samplerate, size_t count, float * left, float * right); + +static dll_openmpt_module_create_from_memory d_openmpt_module_create_from_memory = NULL; +static dll_openmpt_module_destroy d_openmpt_module_destroy = NULL; +static dll_openmpt_module_read_float_stereo d_openmpt_module_read_float_stereo = NULL; + +#ifdef WINDOWS_VERSION +#include + +static HMODULE openDll() +{ + HMODULE res = LoadLibraryA("libopenmpt.dll"); + return res; +} + +static void* getDllProc(HMODULE aDllHandle, const char *aProcName) +{ + return GetProcAddress(aDllHandle, aProcName); +} + +#elif defined(__vita__) + +static void * openDll() +{ + return NULL; +} + +static void* getDllProc(void * aLibrary, const char *aProcName) +{ + return NULL; +} + +#else +#include // dll functions + +static void * openDll() +{ + void * res; + res = dlopen("libopenmpt.so", RTLD_LAZY); + return res; +} + +static void* getDllProc(void * aLibrary, const char *aProcName) +{ + return dlsym(aLibrary, aProcName); +} + +#endif + +static int load_dll() +{ +#ifdef WINDOWS_VERSION + HMODULE dll = NULL; +#else + void * dll = NULL; +#endif + + if (d_openmpt_module_create_from_memory != NULL) + { + return 1; + } + + dll = openDll(); + + if (dll) + { + d_openmpt_module_create_from_memory = (dll_openmpt_module_create_from_memory)getDllProc(dll, "openmpt_module_create_from_memory"); + d_openmpt_module_destroy = (dll_openmpt_module_destroy)getDllProc(dll, "openmpt_module_destroy"); + d_openmpt_module_read_float_stereo = (dll_openmpt_module_read_float_stereo)getDllProc(dll, "openmpt_module_read_float_stereo"); + + + if (d_openmpt_module_create_from_memory && + d_openmpt_module_destroy && + d_openmpt_module_read_float_stereo) + { + return 1; + } + } + d_openmpt_module_create_from_memory = NULL; + return 0; +} + +void *openmpt_module_create_from_memory(const void * filedata, size_t filesize, void *logfunc, void * user, void * ctls) +{ + if (load_dll()) + return d_openmpt_module_create_from_memory(filedata, filesize, logfunc, user, ctls); + return 0; +} + +void openmpt_module_destroy(void * mod) +{ + if (load_dll()) + d_openmpt_module_destroy(mod); +} + +int openmpt_module_read_float_stereo(void * mod, int samplerate, size_t count, float * left, float * right) +{ + if (load_dll()) + return d_openmpt_module_read_float_stereo(mod, samplerate, count, left, right); + return 0; +} diff --git a/src/soloud/src/audiosource/sfxr/soloud_sfxr.cpp b/src/soloud/src/audiosource/sfxr/soloud_sfxr.cpp new file mode 100644 index 0000000..461f1a3 --- /dev/null +++ b/src/soloud/src/audiosource/sfxr/soloud_sfxr.cpp @@ -0,0 +1,633 @@ +/* +SFXR module for SoLoud audio engine +Copyright (c) 2014 Jari Komppa +Based on code (c) by Tomas Pettersson, re-licensed under zlib by permission + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include +#include +#include "soloud_sfxr.h" +#include "soloud_file.h" + +namespace SoLoud +{ + + Prg::Prg() + { + } + + void Prg::srand(int aSeed) + { + index = 0; + int i; + for (i = 0; i < 16; i++) + state[i] = aSeed + i * aSeed + i; + } + + // WELL512 implementation, public domain by Chris Lomont + unsigned int Prg::rand() + { + unsigned int a, b, c, d; + a = state[index]; + c = state[(index+13)&15]; + b = a^c^(a<<16)^(c<<15); + c = state[(index+9)&15]; + c ^= (c>>11); + a = state[index] = b^c; + d = a^((a<<5)&0xDA442D24UL); + index = (index + 15)&15; + a = state[index]; + state[index] = a^b^d^(a<<2)^(b<<18)^(c<<28); + return state[index]; + } + + SfxrInstance::SfxrInstance(Sfxr *aParent) + { + mParent = aParent; + mParams = aParent->mParams; + mRand.srand(0x792352); + resetSample(false); + playing_sample = 1; + } + +#define frnd(x) ((float)(mRand.rand()%10001)/10000*(x)) + + unsigned int SfxrInstance::getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize) + { + float *buffer = aBuffer; + unsigned int i; + for (i = 0; i < aSamplesToRead; i++) + { + rep_time++; + if (rep_limit != 0 && rep_time >= rep_limit) + { + rep_time = 0; + resetSample(true); + } + + // frequency envelopes/arpeggios + arp_time++; + if (arp_limit != 0 && arp_time >= arp_limit) + { + arp_limit = 0; + fperiod *= arp_mod; + } + fslide += fdslide; + fperiod *= fslide; + if (fperiod > fmaxperiod) + { + fperiod = fmaxperiod; + if (mParams.p_freq_limit > 0.0f) + { + if (mFlags & LOOPING) + { + resetSample(false); + } + else + { + playing_sample = false; + return i; + } + } + } + float rfperiod = (float)fperiod; + if (vib_amp > 0.0f) + { + vib_phase += vib_speed; + rfperiod = (float)(fperiod * (1.0 + sin(vib_phase) * vib_amp)); + } + period = (int)rfperiod; + if (period < 8) period = 8; + square_duty += square_slide; + if (square_duty < 0.0f) square_duty = 0.0f; + if (square_duty > 0.5f) square_duty = 0.5f; + // volume envelope + env_time++; + if (env_time > env_length[env_stage]) + { + env_time = 0; + env_stage++; + if (env_stage == 3) + { + if (mFlags & LOOPING) + { + resetSample(false); + } + else + { + playing_sample = false; + return i; + } + } + } + if (env_stage == 0) + { + if (env_length[0]) + { + env_vol = (float)env_time / env_length[0]; + } + else + { + env_vol = 0; + } + } + if (env_stage == 1) + { + if (env_length[1]) + { + env_vol = 1.0f + (float)pow(1.0f - (float)env_time / env_length[1], 1.0f) * 2.0f * mParams.p_env_punch; + } + else + { + env_vol = 0; + } + } + if (env_stage == 2) + { + if (env_length[2]) + { + env_vol = 1.0f - (float)env_time / env_length[2]; + } + else + { + env_vol = 0; + } + } + + // phaser step + fphase += fdphase; + iphase = abs((int)fphase); + if (iphase > 1023) iphase = 1023; + + if (flthp_d != 0.0f) + { + flthp *= flthp_d; + if (flthp < 0.00001f) flthp = 0.00001f; + if (flthp > 0.1f) flthp = 0.1f; + } + + float ssample = 0.0f; + for (int si = 0; si < 8; si++) // 8x supersampling + { + float sample = 0.0f; + phase++; + if (phase >= period) + { + phase %= period; + if (mParams.wave_type == 3) + { + for (int k = 0; k < 32; k++) + { + noise_buffer[k] = frnd(2.0f) - 1.0f; + } + } + } + // base waveform + float fp = (float)phase / period; + switch (mParams.wave_type) + { + case 0: // square + if (fp < square_duty) + { + sample = 0.5f; + } + else + { + sample = -0.5f; + } + break; + case 1: // sawtooth + sample = 1.0f - fp * 2; + break; + case 2: // sine + sample = (float)sin(fp * 2 * M_PI); + break; + case 3: // noise + sample = noise_buffer[phase * 32 / period]; + break; + } + // lp filter + float pp = fltp; + fltw *= fltw_d; + if (fltw < 0.0f) fltw = 0.0f; + if (fltw > 0.1f) fltw = 0.1f; + if (mParams.p_lpf_freq != 1.0f) + { + fltdp += (sample - fltp) * fltw; + fltdp -= fltdp * fltdmp; + } + else + { + fltp = sample; + fltdp = 0.0f; + } + fltp += fltdp; + // hp filter + fltphp += fltp - pp; + fltphp -= fltphp * flthp; + sample = fltphp; + // phaser + phaser_buffer[ipp & 1023] = sample; + sample += phaser_buffer[(ipp - iphase + 1024) & 1023]; + ipp = (ipp + 1) & 1023; + // final accumulation and envelope application + ssample += sample*env_vol; + } + ssample = ssample / 8 * mParams.master_vol; + + ssample *= 2.0f * mParams.sound_vol; + + if (buffer != NULL) + { + if (ssample > 1.0f) ssample = 1.0f; + if (ssample < -1.0f) ssample = -1.0f; + *buffer = ssample; + buffer++; + } + } + return aSamplesToRead; + } + + bool SfxrInstance::hasEnded() + { + return !playing_sample; + } + + void SfxrInstance::resetSample(bool aRestart) + { + if(!aRestart) + phase=0; + fperiod=100.0/(mParams.p_base_freq*mParams.p_base_freq+0.001); + period=(int)fperiod; + fmaxperiod=100.0/(mParams.p_freq_limit*mParams.p_freq_limit+0.001); + fslide=1.0-pow((double)mParams.p_freq_ramp, 3.0)*0.01; + fdslide=-pow((double)mParams.p_freq_dramp, 3.0)*0.000001; + square_duty=0.5f-mParams.p_duty*0.5f; + square_slide=-mParams.p_duty_ramp*0.00005f; + if(mParams.p_arp_mod>=0.0f) + arp_mod=1.0-pow((double)mParams.p_arp_mod, 2.0)*0.9; + else + arp_mod=1.0+pow((double)mParams.p_arp_mod, 2.0)*10.0; + arp_time=0; + arp_limit=(int)(pow(1.0f-mParams.p_arp_speed, 2.0f)*20000+32); + if(mParams.p_arp_speed==1.0f) + arp_limit=0; + if(!aRestart) + { + // reset filter + fltp=0.0f; + fltdp=0.0f; + fltw=(float)pow(mParams.p_lpf_freq, 3.0f)*0.1f; + fltw_d=1.0f+mParams.p_lpf_ramp*0.0001f; + fltdmp=5.0f/(1.0f+(float)pow(mParams.p_lpf_resonance, 2.0f)*20.0f)*(0.01f+fltw); + if(fltdmp>0.8f) fltdmp=0.8f; + fltphp=0.0f; + flthp=(float)pow(mParams.p_hpf_freq, 2.0f)*0.1f; + flthp_d=(float)(1.0+mParams.p_hpf_ramp*0.0003f); + // reset vibrato + vib_phase=0.0f; + vib_speed=(float)pow(mParams.p_vib_speed, 2.0f)*0.01f; + vib_amp=mParams.p_vib_strength*0.5f; + // reset envelope + env_vol=0.0f; + env_stage=0; + env_time=0; + env_length[0]=(int)(mParams.p_env_attack*mParams.p_env_attack*100000.0f); + env_length[1]=(int)(mParams.p_env_sustain*mParams.p_env_sustain*100000.0f); + env_length[2]=(int)(mParams.p_env_decay*mParams.p_env_decay*100000.0f); + + fphase=(float)pow(mParams.p_pha_offset, 2.0f)*1020.0f; + if(mParams.p_pha_offset<0.0f) fphase=-fphase; + fdphase=(float)pow(mParams.p_pha_ramp, 2.0f)*1.0f; + if(mParams.p_pha_ramp<0.0f) fdphase=-fdphase; + iphase=abs((int)fphase); + ipp=0; + for(int i=0;i<1024;i++) + phaser_buffer[i]=0.0f; + + for(int i=0;i<32;i++) + noise_buffer[i]=frnd(2.0f)-1.0f; + + rep_time=0; + rep_limit=(int)(pow(1.0f-mParams.p_repeat_speed, 2.0f)*20000+32); + if(mParams.p_repeat_speed==0.0f) + rep_limit=0; + } + } + + +#define rnd(n) (mRand.rand()%((n)+1)) +#undef frnd +#define frnd(x) ((float)(mRand.rand()%10001)/10000*(x)) + + + result Sfxr::loadPreset(int aPresetNo, int aRandSeed) + { + if (aPresetNo < 0 || aPresetNo > 6) + return INVALID_PARAMETER; + + resetParams(); + mRand.srand(aRandSeed); + switch(aPresetNo) + { + case 0: // pickup/coin + mParams.p_base_freq=0.4f+frnd(0.5f); + mParams.p_env_attack=0.0f; + mParams.p_env_sustain=frnd(0.1f); + mParams.p_env_decay=0.1f+frnd(0.4f); + mParams.p_env_punch=0.3f+frnd(0.3f); + if(rnd(1)) + { + mParams.p_arp_speed=0.5f+frnd(0.2f); + mParams.p_arp_mod=0.2f+frnd(0.4f); + } + break; + case 1: // laser/shoot + mParams.wave_type=rnd(2); + if(mParams.wave_type==2 && rnd(1)) + mParams.wave_type=rnd(1); + mParams.p_base_freq=0.5f+frnd(0.5f); + mParams.p_freq_limit=mParams.p_base_freq-0.2f-frnd(0.6f); + if(mParams.p_freq_limit<0.2f) mParams.p_freq_limit=0.2f; + mParams.p_freq_ramp=-0.15f-frnd(0.2f); + if(rnd(2)==0) + { + mParams.p_base_freq=0.3f+frnd(0.6f); + mParams.p_freq_limit=frnd(0.1f); + mParams.p_freq_ramp=-0.35f-frnd(0.3f); + } + if(rnd(1)) + { + mParams.p_duty=frnd(0.5f); + mParams.p_duty_ramp=frnd(0.2f); + } + else + { + mParams.p_duty=0.4f+frnd(0.5f); + mParams.p_duty_ramp=-frnd(0.7f); + } + mParams.p_env_attack=0.0f; + mParams.p_env_sustain=0.1f+frnd(0.2f); + mParams.p_env_decay=frnd(0.4f); + if(rnd(1)) + mParams.p_env_punch=frnd(0.3f); + if(rnd(2)==0) + { + mParams.p_pha_offset=frnd(0.2f); + mParams.p_pha_ramp=-frnd(0.2f); + } + if(rnd(1)) + mParams.p_hpf_freq=frnd(0.3f); + break; + case 2: // explosion + mParams.wave_type=3; + if(rnd(1)) + { + mParams.p_base_freq=0.1f+frnd(0.4f); + mParams.p_freq_ramp=-0.1f+frnd(0.4f); + } + else + { + mParams.p_base_freq=0.2f+frnd(0.7f); + mParams.p_freq_ramp=-0.2f-frnd(0.2f); + } + mParams.p_base_freq*=mParams.p_base_freq; + if(rnd(4)==0) + mParams.p_freq_ramp=0.0f; + if(rnd(2)==0) + mParams.p_repeat_speed=0.3f+frnd(0.5f); + mParams.p_env_attack=0.0f; + mParams.p_env_sustain=0.1f+frnd(0.3f); + mParams.p_env_decay=frnd(0.5f); + if(rnd(1)==0) + { + mParams.p_pha_offset=-0.3f+frnd(0.9f); + mParams.p_pha_ramp=-frnd(0.3f); + } + mParams.p_env_punch=0.2f+frnd(0.6f); + if(rnd(1)) + { + mParams.p_vib_strength=frnd(0.7f); + mParams.p_vib_speed=frnd(0.6f); + } + if(rnd(2)==0) + { + mParams.p_arp_speed=0.6f+frnd(0.3f); + mParams.p_arp_mod=0.8f-frnd(1.6f); + } + break; + case 3: // powerup + if(rnd(1)) + mParams.wave_type=1; + else + mParams.p_duty=frnd(0.6f); + if(rnd(1)) + { + mParams.p_base_freq=0.2f+frnd(0.3f); + mParams.p_freq_ramp=0.1f+frnd(0.4f); + mParams.p_repeat_speed=0.4f+frnd(0.4f); + } + else + { + mParams.p_base_freq=0.2f+frnd(0.3f); + mParams.p_freq_ramp=0.05f+frnd(0.2f); + if(rnd(1)) + { + mParams.p_vib_strength=frnd(0.7f); + mParams.p_vib_speed=frnd(0.6f); + } + } + mParams.p_env_attack=0.0f; + mParams.p_env_sustain=frnd(0.4f); + mParams.p_env_decay=0.1f+frnd(0.4f); + break; + case 4: // hit/hurt + mParams.wave_type=rnd(2); + if(mParams.wave_type==2) + mParams.wave_type=3; + if(mParams.wave_type==0) + mParams.p_duty=frnd(0.6f); + mParams.p_base_freq=0.2f+frnd(0.6f); + mParams.p_freq_ramp=-0.3f-frnd(0.4f); + mParams.p_env_attack=0.0f; + mParams.p_env_sustain=frnd(0.1f); + mParams.p_env_decay=0.1f+frnd(0.2f); + if(rnd(1)) + mParams.p_hpf_freq=frnd(0.3f); + break; + case 5: // jump + mParams.wave_type=0; + mParams.p_duty=frnd(0.6f); + mParams.p_base_freq=0.3f+frnd(0.3f); + mParams.p_freq_ramp=0.1f+frnd(0.2f); + mParams.p_env_attack=0.0f; + mParams.p_env_sustain=0.1f+frnd(0.3f); + mParams.p_env_decay=0.1f+frnd(0.2f); + if(rnd(1)) + mParams.p_hpf_freq=frnd(0.3f); + if(rnd(1)) + mParams.p_lpf_freq=1.0f-frnd(0.6f); + break; + case 6: // blip/select + mParams.wave_type=rnd(1); + if(mParams.wave_type==0) + mParams.p_duty=frnd(0.6f); + mParams.p_base_freq=0.2f+frnd(0.4f); + mParams.p_env_attack=0.0f; + mParams.p_env_sustain=0.1f+frnd(0.1f); + mParams.p_env_decay=frnd(0.2f); + mParams.p_hpf_freq=0.1f; + break; + } + return 0; + } + + void Sfxr::resetParams() + { + mParams.wave_type=0; + + mParams.p_base_freq=0.3f; + mParams.p_freq_limit=0.0f; + mParams.p_freq_ramp=0.0f; + mParams.p_freq_dramp=0.0f; + mParams.p_duty=0.0f; + mParams.p_duty_ramp=0.0f; + + mParams.p_vib_strength=0.0f; + mParams.p_vib_speed=0.0f; + mParams.p_vib_delay=0.0f; + + mParams.p_env_attack=0.0f; + mParams.p_env_sustain=0.3f; + mParams.p_env_decay=0.4f; + mParams.p_env_punch=0.0f; + + mParams.filter_on=false; + mParams.p_lpf_resonance=0.0f; + mParams.p_lpf_freq=1.0f; + mParams.p_lpf_ramp=0.0f; + mParams.p_hpf_freq=0.0f; + mParams.p_hpf_ramp=0.0f; + + mParams.p_pha_offset=0.0f; + mParams.p_pha_ramp=0.0f; + + mParams.p_repeat_speed=0.0f; + + mParams.p_arp_speed=0.0f; + mParams.p_arp_mod=0.0f; + + mParams.master_vol=0.05f; + mParams.sound_vol=0.5f; + } + + result Sfxr::loadParamsMem(unsigned char *aMem, unsigned int aLength, bool aCopy, bool aTakeOwnership) + { + MemoryFile mf; + int res = mf.openMem(aMem, aLength, aCopy, aTakeOwnership); + if (res != SO_NO_ERROR) + return res; + return loadParamsFile(&mf); + } + + result Sfxr::loadParams(const char *aFilename) + { + DiskFile df; + int res = df.open(aFilename); + if (res != SO_NO_ERROR) + return res; + return loadParamsFile(&df); + } + + result Sfxr::loadParamsFile(File *aFile) + { + int version=0; + aFile->read((unsigned char*)&version, sizeof(int)); + if(version!=100 && version!=101 && version!=102) + { + return FILE_LOAD_FAILED; + } + + aFile->read((unsigned char*)&mParams.wave_type, sizeof(int)); + + + mParams.sound_vol=0.5f; + if(version==102) + aFile->read((unsigned char*)&mParams.sound_vol, sizeof(float)); + + aFile->read((unsigned char*)&mParams.p_base_freq, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_freq_limit, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_freq_ramp, sizeof(float)); + if(version>=101) + aFile->read((unsigned char*)&mParams.p_freq_dramp, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_duty, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_duty_ramp, sizeof(float)); + + aFile->read((unsigned char*)&mParams.p_vib_strength, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_vib_speed, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_vib_delay, sizeof(float)); + + aFile->read((unsigned char*)&mParams.p_env_attack, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_env_sustain, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_env_decay, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_env_punch, sizeof(float)); + + aFile->read((unsigned char*)&mParams.filter_on, sizeof(bool)); + aFile->read((unsigned char*)&mParams.p_lpf_resonance, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_lpf_freq, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_lpf_ramp, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_hpf_freq, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_hpf_ramp, sizeof(float)); + + aFile->read((unsigned char*)&mParams.p_pha_offset, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_pha_ramp, sizeof(float)); + + aFile->read((unsigned char*)&mParams.p_repeat_speed, sizeof(float)); + + if(version>=101) + { + aFile->read((unsigned char*)&mParams.p_arp_speed, sizeof(float)); + aFile->read((unsigned char*)&mParams.p_arp_mod, sizeof(float)); + } + + return 0; + } + + Sfxr::~Sfxr() + { + stop(); + } + + Sfxr::Sfxr() + { + resetParams(); + mBaseSamplerate = 44100; + } + + + AudioSourceInstance * Sfxr::createInstance() + { + return new SfxrInstance(this); + } + +}; \ No newline at end of file diff --git a/src/soloud/src/audiosource/speech/Elements.def b/src/soloud/src/audiosource/speech/Elements.def new file mode 100644 index 0000000..cdeabbd --- /dev/null +++ b/src/soloud/src/audiosource/speech/Elements.def @@ -0,0 +1,1659 @@ +/*mName, mRK, mDU, mUD, mFont, mDict, mIpa, mFeat, interpolators*/ +/* (mSteady, mFixed, mProportion, mExtDelay, mIntDelay) */ +{"END", 31, 5, 5,0x00,NULL,NULL,0, + { + { 270, 135, 50, 3, 3}, /* ELM_FN 0 */ + { 490, 0, 100, 0, 0}, /* ELM_F1 0 */ + { 1480, 0, 100, 0, 0}, /* ELM_F2 0 */ + { 2500, 0, 100, 0, 0}, /* ELM_F3 0 */ + { 60, 0, 100, 0, 0}, /* ELM_B1 0 */ + { 90, 0, 100, 0, 0}, /* ELM_B2 0 */ + { 150, 0, 100, 0, 0}, /* ELM_B3 0 */ + { -30, -10.5, 100, 3, 0}, /* ELM_AN -10.5 */ + { -30, -10.5, 100, 3, 0}, /* ELM_A1 -10.5 */ + { -30, -10.5, 100, 3, 0}, /* ELM_A2 -10.5 */ + { -30, -10.5, 100, 3, 0}, /* ELM_A3 -10.5 */ + { -30, -10.5, 100, 3, 0}, /* ELM_A4 -10.5 */ + { -30, 0, 100, 3, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 3, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 3, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"Q", 29, 6, 6,0x00,NULL,NULL,0, + { + { 270, 135, 50, 3, 3}, /* ELM_FN 0 */ + { 490, 0, 100, 3, 3}, /* ELM_F1 0 */ + { 1480, 0, 100, 3, 3}, /* ELM_F2 0 */ + { 2500, 0, 100, 3, 3}, /* ELM_F3 0 */ + { 60, 0, 100, 3, 3}, /* ELM_B1 0 */ + { 90, 0, 100, 3, 3}, /* ELM_B2 0 */ + { 150, 0, 100, 3, 3}, /* ELM_B3 0 */ + { -30, -10.5, 100, 3, 0}, /* ELM_AN -10.5 */ + { -30, -10.5, 100, 3, 0}, /* ELM_A1 -10.5 */ + { -30, -10.5, 100, 3, 0}, /* ELM_A2 -10.5 */ + { -30, -10.5, 100, 3, 0}, /* ELM_A3 -10.5 */ + { -30, -10.5, 100, 3, 0}, /* ELM_A4 -10.5 */ + { -30, 0, 100, 3, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 3, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 3, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"P", 23, 8, 8,0x70,"p","p",ELM_FEATURE_BLB|ELM_FEATURE_STP|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 2, 2}, /* ELM_F1 15 */ + { 760, 350, 50, 2, 2}, /* ELM_F2 -30 */ + { 2500, 0, 100, 0, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 2, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 2, 2}, /* ELM_B2 0 */ + { 150, 0, 100, 0, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A2 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"PY", 29, 1, 1,0x70,"p","p",ELM_FEATURE_BLB|ELM_FEATURE_STP|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 0, 100, 0, 0}, /* ELM_F1 0 */ + { 760, 0, 100, 0, 0}, /* ELM_F2 0 */ + { 2500, 0, 100, 0, 0}, /* ELM_F3 0 */ + { 60, 0, 100, 0, 0}, /* ELM_B1 0 */ + { 90, 0, 100, 0, 0}, /* ELM_B2 0 */ + { 150, 0, 100, 0, 0}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 24.5, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 49, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 43.75, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 38.5, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"PZ", 23, 2, 2,0x70,"p","p",ELM_FEATURE_BLB|ELM_FEATURE_STP|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 2, 2}, /* ELM_F1 15 */ + { 760, 350, 50, 2, 2}, /* ELM_F2 -30 */ + { 2500, 0, 100, 2, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 2, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 2, 2}, /* ELM_B2 0 */ + { 150, 0, 100, 2, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 24.5, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 38.5, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 33.25, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 28, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"T", 23, 6, 6,0x74,"t","t",ELM_FEATURE_ALV|ELM_FEATURE_STP|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 2, 2}, /* ELM_F1 15 */ + { 1780, 950, 50, 2, 2}, /* ELM_F2 60 */ + { 2680, 2680, 0, 0, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 2, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 2, 2}, /* ELM_B2 0 */ + { 150, 150, 0, 0, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A2 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"TY", 29, 1, 1,0x74,"t","t",ELM_FEATURE_ALV|ELM_FEATURE_STP|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 0, 100, 0, 0}, /* ELM_F1 0 */ + { 1780, 0, 100, 0, 0}, /* ELM_F2 0 */ + { 2680, 0, 100, 0, 0}, /* ELM_F3 0 */ + { 60, 0, 100, 0, 0}, /* ELM_B1 0 */ + { 90, 0, 100, 0, 0}, /* ELM_B2 0 */ + { 150, 0, 100, 0, 0}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 38.5, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 50.75, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"TZ", 23, 2, 2,0x74,"t","t",ELM_FEATURE_ALV|ELM_FEATURE_STP|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 2, 1}, /* ELM_F1 15 */ + { 1780, 950, 50, 2, 1}, /* ELM_F2 60 */ + { 2680, 2680, 0, 2, 0}, /* ELM_F3 0 */ + { 60, 30, 50, 2, 1}, /* ELM_B1 0 */ + { 90, 45, 50, 2, 1}, /* ELM_B2 0 */ + { 150, 150, 0, 2, 0}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 28, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 40.25, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"K", 23, 8, 8,0x6B,"k","k",ELM_FEATURE_STP|ELM_FEATURE_VEL|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 3, 3}, /* ELM_F1 15 */ + { 1480, 1550, 50, 3, 3}, /* ELM_F2 810 */ + { 2620, 1580, 50, 3, 3}, /* ELM_F3 270 */ + { 60, 30, 50, 3, 3}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 3}, /* ELM_B2 0 */ + { 150, 75, 50, 3, 3}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A2 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"KY", 29, 1, 1,0x6B,"k","k",ELM_FEATURE_STP|ELM_FEATURE_VEL|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 0, 100, 0, 0}, /* ELM_F1 0 */ + { 1480, 0, 100, 0, 0}, /* ELM_F2 0 */ + { 2620, 0, 100, 0, 0}, /* ELM_F3 0 */ + { 60, 0, 100, 0, 0}, /* ELM_B1 0 */ + { 90, 0, 100, 0, 0}, /* ELM_B2 0 */ + { 150, 0, 100, 0, 0}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 50.75, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 50.75, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 29.75, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"KZ", 23, 4, 4,0x6B,"k","k",ELM_FEATURE_STP|ELM_FEATURE_VEL|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 3, 3}, /* ELM_F1 15 */ + { 1480, 1550, 50, 3, 3}, /* ELM_F2 810 */ + { 2620, 1580, 50, 3, 3}, /* ELM_F3 270 */ + { 60, 30, 50, 3, 3}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 3}, /* ELM_B2 0 */ + { 150, 75, 50, 3, 3}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 40.25, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 40.25, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 19.25, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"B", 26,12,12,0x62,"b","b",ELM_FEATURE_BLB|ELM_FEATURE_STP|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 2, 2}, /* ELM_F1 15 */ + { 760, 350, 50, 2, 2}, /* ELM_F2 -30 */ + { 2500, 0, 100, 0, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 2, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 2, 2}, /* ELM_B2 0 */ + { 150, 0, 100, 0, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 24.5, 0, 100, 0, 0}, /* ELM_A1 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A2 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"BY", 29, 1, 1,0x62,"b","b",ELM_FEATURE_BLB|ELM_FEATURE_STP|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 0, 100, 0, 0}, /* ELM_F1 0 */ + { 760, 0, 100, 0, 0}, /* ELM_F2 0 */ + { 2500, 0, 100, 0, 0}, /* ELM_F3 0 */ + { 60, 0, 100, 0, 0}, /* ELM_B1 0 */ + { 90, 0, 100, 0, 0}, /* ELM_B2 0 */ + { 150, 0, 100, 0, 0}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 24.5, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 49, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 43.25, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 38.5, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"BZ", 26, 0, 0,0x62,"b","b",ELM_FEATURE_BLB|ELM_FEATURE_STP|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 2, 0}, /* ELM_F1 15 */ + { 760, 350, 50, 2, 0}, /* ELM_F2 -30 */ + { 2500, 0, 100, 0, 0}, /* ELM_F3 0 */ + { 60, 30, 50, 2, 0}, /* ELM_B1 0 */ + { 90, 45, 50, 2, 0}, /* ELM_B2 0 */ + { 150, 0, 100, 0, 0}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A2 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"D", 26, 8, 8,0x64,"d","d",ELM_FEATURE_ALV|ELM_FEATURE_STP|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 2, 2}, /* ELM_F1 15 */ + { 1780, 950, 50, 2, 2}, /* ELM_F2 60 */ + { 2680, 2680, 0, 2, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 2, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 2, 2}, /* ELM_B2 0 */ + { 150, 150, 0, 2, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 31.5, 0, 100, 0, 0}, /* ELM_A1 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A2 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"DY", 29, 1, 1,0x64,"d","d",ELM_FEATURE_ALV|ELM_FEATURE_STP|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 0, 100, 0, 0}, /* ELM_F1 0 */ + { 1780, 0, 100, 0, 0}, /* ELM_F2 0 */ + { 2680, 0, 100, 0, 0}, /* ELM_F3 0 */ + { 60, 0, 100, 0, 0}, /* ELM_B1 0 */ + { 90, 0, 100, 0, 0}, /* ELM_B2 0 */ + { 150, 0, 100, 0, 0}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 38.5, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 38.5, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 35, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 45.5, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"DZ", 26, 1, 1,0x64,"d","d",ELM_FEATURE_ALV|ELM_FEATURE_STP|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 2, 0}, /* ELM_F1 15 */ + { 1780, 950, 50, 2, 0}, /* ELM_F2 60 */ + { 2680, 2680, 0, 2, 0}, /* ELM_F3 0 */ + { 60, 30, 50, 2, 0}, /* ELM_B1 0 */ + { 90, 45, 50, 2, 0}, /* ELM_B2 0 */ + { 150, 150, 0, 2, 0}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 38.5, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 28, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 24.5, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 35, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + + +{"G", 26,12,12,0x67,"g","g",ELM_FEATURE_STP|ELM_FEATURE_VCD|ELM_FEATURE_VEL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 3, 3}, /* ELM_F1 15 */ + { 1480, 1550, 50, 3, 3}, /* ELM_F2 810 */ + { 2620, 1580, 50, 3, 3}, /* ELM_F3 270 */ + { 60, 30, 50, 3, 3}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 3}, /* ELM_B2 0 */ + { 150, 75, 50, 3, 3}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 35, 0, 100, 0, 0}, /* ELM_A1 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A2 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"GY", 29, 1, 1,0x67,"g","g",ELM_FEATURE_STP|ELM_FEATURE_VCD|ELM_FEATURE_VEL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 0, 100, 0, 0}, /* ELM_F1 0 */ + { 1480, 0, 100, 0, 0}, /* ELM_F2 0 */ + { 2620, 0, 100, 0, 0}, /* ELM_F3 0 */ + { 60, 0, 100, 0, 0}, /* ELM_B1 0 */ + { 90, 0, 100, 0, 0}, /* ELM_B2 0 */ + { 150, 0, 100, 0, 0}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 35, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 45.5, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 40.25, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 24.5, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"GZ", 26, 2, 2,0x67,"g","g",ELM_FEATURE_STP|ELM_FEATURE_VCD|ELM_FEATURE_VEL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 3, 2}, /* ELM_F1 15 */ + { 1480, 1550, 50, 3, 2}, /* ELM_F2 810 */ + { 2620, 1580, 50, 3, 2}, /* ELM_F3 270 */ + { 60, 30, 50, 3, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 2}, /* ELM_B2 0 */ + { 150, 75, 50, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 35, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 35, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 29.75, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 14, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"M", 15, 8, 8,0x6D,"m","m",ELM_FEATURE_BLB|ELM_FEATURE_NAS, + { + { 360, 360, 0, 3, 0}, /* ELM_FN 0 */ + { 480, 480, 0, 3, 0}, /* ELM_F1 0 */ + { 1000, 350, 50, 3, 0}, /* ELM_F2 -150 */ + { 2200, 0, 100, 5, 0}, /* ELM_F3 0 */ + { 40, 20, 50, 3, 0}, /* ELM_B1 0 */ + { 175, 87, 50, 3, 0}, /* ELM_B2 -0.5 */ + { 120, 0, 100, 5, 0}, /* ELM_B3 0 */ + { 42, 21, 50, 3, 0}, /* ELM_AN 0 */ + { 26, -10, 100, 3, 0}, /* ELM_A1 -10 */ + { 30, -10, 100, 3, 0}, /* ELM_A2 -10 */ + { 33, -10, 100, 3, 0}, /* ELM_A3 -10 */ + { -30, -10, 100, 3, 0}, /* ELM_A4 -10 */ + { -30, 0, 100, 3, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 3, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 3, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 2, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 2, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 2, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 2, 0} /* ELM_AF 0 */ + } +}, + +{"N", 15, 8, 8,0x6E,"n","n",ELM_FEATURE_ALV|ELM_FEATURE_NAS, + { + { 450, 450, 0, 3, 0}, /* ELM_FN 0 */ + { 480, 480, 0, 3, 0}, /* ELM_F1 0 */ + { 1780, 950, 50, 3, 3}, /* ELM_F2 60 */ + { 2620, 2680, 0, 3, 0}, /* ELM_F3 60 */ + { 40, 20, 50, 3, 0}, /* ELM_B1 0 */ + { 300, 150, 50, 3, 3}, /* ELM_B2 0 */ + { 260, 130, 50, 3, 0}, /* ELM_B3 0 */ + { 42, 21, 50, 3, 0}, /* ELM_AN 0 */ + { 35, -10, 100, 3, 0}, /* ELM_A1 -10 */ + { 35, -10, 100, 3, 0}, /* ELM_A2 -10 */ + { 35, -10, 100, 3, 0}, /* ELM_A3 -10 */ + { 20, -10, 100, 3, 0}, /* ELM_A4 -10 */ + { -30, 0, 100, 3, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 3, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 3, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 2, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 2, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 2, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 2, 0} /* ELM_AF 0 */ + } +}, + +{"NG", 15, 8, 8,0x4E,"N","N",ELM_FEATURE_NAS|ELM_FEATURE_VEL, + { + { 360, 360, 0, 3, 0}, /* ELM_FN 0 */ + { 480, 480, 0, 3, 0}, /* ELM_F1 0 */ + { 820, 1550, 50, 5, 3}, /* ELM_F2 1140 */ + { 2800, 1580, 50, 3, 3}, /* ELM_F3 180 */ + { 160, 80, 0, 5, 0}, /* ELM_B1 -80 */ + { 150, 75, 50, 5, 3}, /* ELM_B2 0 */ + { 100, 50, 50, 3, 0}, /* ELM_B3 0 */ + { 42, 21, 50, 3, 3}, /* ELM_AN 0 */ + { 20, 0, 100, 3, 0}, /* ELM_A1 0 */ + { 30, 0, 100, 3, 0}, /* ELM_A2 0 */ + { 35, 0, 100, 3, 0}, /* ELM_A3 0 */ + { 0, 0, 100, 3, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 3, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 3, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 3, 0}, /* ELM_AB 0 */ + { 52, 26, 50, 2, 0}, /* ELM_AV 0 */ + { 56, 28, 50, 2, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 2, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 2, 0} /* ELM_AF 0 */ + } +}, + +{"F", 18,12,12,0x66,"f","f",ELM_FEATURE_FRC|ELM_FEATURE_LBD|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 400, 170, 50, 3, 2}, /* ELM_F1 -30 */ + { 1420, 350, 50, 3, 2}, /* ELM_F2 -360 */ + { 2560, 980, 50, 3, 2}, /* ELM_F3 -300 */ + { 60, 30, 50, 3, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 2}, /* ELM_B2 0 */ + { 150, 75, 50, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 0, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 0, 0, 100, 0, 0}, /* ELM_A3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { 54, 27, 50, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 32, 16, 50, 0, 0}, /* ELM_ASP 0 */ + { 54, 30, 50, 0, 0} /* ELM_AF 3 */ + } +}, + +{"TH", 18,15,15,0x54,"T","T",ELM_FEATURE_DNT|ELM_FEATURE_FRC|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 400, 170, 50, 3, 2}, /* ELM_F1 -30 */ + { 1780, 1190, 50, 3, 2}, /* ELM_F2 300 */ + { 2680, 2680, 0, 3, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 3, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 2}, /* ELM_B2 0 */ + { 150, 150, 0, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 26.25, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 28, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 22.75, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"S", 18,12,12,0x73,"s","s",ELM_FEATURE_ALV|ELM_FEATURE_FRC|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 400, 170, 50, 3, 2}, /* ELM_F1 -30 */ + { 1720, 950, 50, 3, 2}, /* ELM_F2 90 */ + { 2620, 0, 100, 3, 2}, /* ELM_F3 0 */ + { 200, 100, 50, 3, 2}, /* ELM_B1 0 */ + { 96, 48, 50, 3, 2}, /* ELM_B2 0 */ + { 220, 0, 100, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 28, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 28, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 40.25, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 32, 16, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"SH", 18,12,12,0x53,"S","S",ELM_FEATURE_FRC|ELM_FEATURE_PLA|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 400, 170, 50, 3, 2}, /* ELM_F1 -30 */ + { 2200, 1190, 50, 3, 2}, /* ELM_F2 90 */ + { 2560, 0, 100, 3, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 3, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 2}, /* ELM_B2 0 */ + { 150, 0, 100, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 31.5, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 42, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 31.5, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"X", 18,12,12,0x78,"x","x",ELM_FEATURE_FRC|ELM_FEATURE_VEL|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 3, 3}, /* ELM_F1 15 */ + { 1480, 1550, 50, 3, 3}, /* ELM_F2 810 */ + { 2620, 1580, 50, 3, 3}, /* ELM_F3 270 */ + { 60, 30, 50, 3, 3}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 3}, /* ELM_B2 0 */ + { 150, 75, 50, 3, 3}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 40.25, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 40.25, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 19.25, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"H", 9,10,10,0x68,"h","h",ELM_FEATURE_APR|ELM_FEATURE_GLT, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 490, 0, 100, 0, 7}, /* ELM_F1 0 */ + { 1480, 0, 100, 0, 7}, /* ELM_F2 0 */ + { 2500, 0, 100, 0, 7}, /* ELM_F3 0 */ + { 60, 0, 100, 0, 7}, /* ELM_B1 0 */ + { 90, 0, 100, 0, 7}, /* ELM_B2 0 */ + { 150, 0, 100, 0, 7}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 7}, /* ELM_AN 0 */ + { 35, -14, 100, 0, 7}, /* ELM_A1 -14 */ + { 36.75, -14, 100, 0, 7}, /* ELM_A2 -14 */ + { 26.25, -7, 100, 0, 7}, /* ELM_A3 -7 */ + { 22.75, -3.5, 100, 0, 7}, /* ELM_A4 -3.5 */ + { -30, 0, 100, 0, 7}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 7}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 7}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"V", 20, 4, 4,0x76,"v","v",ELM_FEATURE_FRC|ELM_FEATURE_LBD|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 280, 170, 50, 3, 2}, /* ELM_F1 30 */ + { 1420, 350, 50, 3, 2}, /* ELM_F2 -360 */ + { 2560, 980, 50, 3, 2}, /* ELM_F3 -300 */ + { 60, 30, 50, 3, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 2}, /* ELM_B2 0 */ + { 150, 75, 50, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 29.75, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 40.25, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 36.75, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 33.25, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"QQ", 30, 0, 0,0x5A,"Z","Z",ELM_FEATURE_FRC|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 280, 0, 100, 0, 0}, /* ELM_F1 0 */ + { 1420, 0, 100, 0, 0}, /* ELM_F2 0 */ + { 2560, 0, 100, 0, 0}, /* ELM_F3 0 */ + { 60, 0, 100, 0, 0}, /* ELM_B1 0 */ + { 90, 0, 100, 0, 0}, /* ELM_B2 0 */ + { 150, 0, 100, 0, 0}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 29.75, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 40.25, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 36.75, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 33.25, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"DH", 20, 4, 4,0x54,"D","D",ELM_FEATURE_DNT|ELM_FEATURE_FRC|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 280, 170, 50, 3, 2}, /* ELM_F1 30 */ + { 1600, 1190, 50, 3, 2}, /* ELM_F2 390 */ + { 2560, 0, 100, 3, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 3, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 2}, /* ELM_B2 0 */ + { 150, 0, 100, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 29.75, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 31.5, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 26.25, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 28, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { 54, 27, 50, 0, 0}, /* ELM_AB 0 */ + { 36, 18, 50, 0, 0}, /* ELM_AV 0 */ + { 54, 27, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"DI", 20, 4, 4,0x54,"D","D",ELM_FEATURE_DNT|ELM_FEATURE_FRC|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 280, 170, 50, 3, 2}, /* ELM_F1 30 */ + { 1600, 1190, 50, 3, 2}, /* ELM_F2 390 */ + { 2560, 0, 100, 3, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 3, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 2}, /* ELM_B2 0 */ + { 150, 0, 100, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 29.75, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 31.5, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 26.25, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 28, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"Z", 20, 4, 4,0x7A,"z","z",ELM_FEATURE_ALV|ELM_FEATURE_FRC|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 280, 170, 50, 3, 2}, /* ELM_F1 30 */ + { 1720, 950, 50, 3, 2}, /* ELM_F2 90 */ + { 2560, 0, 100, 3, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 3, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 2}, /* ELM_B2 0 */ + { 150, 0, 100, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 29.75, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 24.5, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 24.5, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 36.75, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 40, 20, 50, 0, 0}, /* ELM_AV 0 */ + { 54, 27, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"ZZ", 20, 4, 4,0x7A,"z","z",ELM_FEATURE_ALV|ELM_FEATURE_FRC|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 280, 170, 50, 3, 2}, /* ELM_F1 30 */ + { 1720, 950, 50, 3, 2}, /* ELM_F2 90 */ + { 2560, 0, 100, 3, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 3, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 2}, /* ELM_B2 0 */ + { 150, 0, 100, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 29.75, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 24.5, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 24.5, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 36.75, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"ZH", 20, 4, 4,0x5A,"Z","Z",ELM_FEATURE_FRC|ELM_FEATURE_PLA|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 280, 170, 50, 3, 2}, /* ELM_F1 30 */ + { 2020, 1190, 50, 3, 2}, /* ELM_F2 180 */ + { 2560, 0, 100, 3, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 3, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 2}, /* ELM_B2 0 */ + { 150, 0, 100, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 29.75, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 26.25, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 36.75, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 26.25, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"CH", 23, 4, 4,0x74,"t","t",ELM_FEATURE_ALV|ELM_FEATURE_STP|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 2, 2}, /* ELM_F1 15 */ + { 1780, 950, 50, 2, 2}, /* ELM_F2 60 */ + { 2680, 2680, 0, 2, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 2, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 2, 2}, /* ELM_B2 0 */ + { 150, 150, 0, 2, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A2 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"CI", 18, 8, 8,0x53,"S","S",ELM_FEATURE_FRC|ELM_FEATURE_PLA|ELM_FEATURE_VLS, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 400, 170, 50, 3, 2}, /* ELM_F1 -30 */ + { 2020, 1190, 50, 3, 2}, /* ELM_F2 180 */ + { 2560, 0, 100, 3, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 3, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 2}, /* ELM_B2 0 */ + { 150, 0, 100, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 31.5, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 42, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 31.5, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AV 0 */ + { 0, 0, 50, 0, 0}, /* ELM_AVC 0 */ + { 60, 30, 50, 0, 0}, /* ELM_ASP 0 */ + { 60, 30, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"J", 26, 4, 4,0x64,"d","d",ELM_FEATURE_ALV|ELM_FEATURE_STP|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 110, 50, 2, 2}, /* ELM_F1 15 */ + { 1780, 950, 50, 2, 2}, /* ELM_F2 60 */ + { 2680, 2680, 0, 2, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 2, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 2, 2}, /* ELM_B2 0 */ + { 150, 150, 0, 2, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 31.5, 0, 100, 0, 0}, /* ELM_A1 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A2 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"JY", 20, 3, 3,0x5A,"Z","Z",ELM_FEATURE_FRC|ELM_FEATURE_PLA|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 280, 170, 50, 3, 2}, /* ELM_F1 30 */ + { 2020, 1190, 50, 3, 2}, /* ELM_F2 180 */ + { 2560, 0, 100, 3, 2}, /* ELM_F3 0 */ + { 60, 30, 50, 3, 2}, /* ELM_B1 0 */ + { 90, 45, 50, 3, 2}, /* ELM_B2 0 */ + { 150, 0, 100, 3, 2}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 29.75, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 26.25, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 36.75, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 26.25, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"L", 11, 8, 8,0x6C,"l","l",ELM_FEATURE_ALV|ELM_FEATURE_LAT|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 460, 230, 50, 6, 0}, /* ELM_F1 0 */ + { 1480, 710, 50, 6, 0}, /* ELM_F2 -30 */ + { 2500, 1220, 50, 6, 0}, /* ELM_F3 -30 */ + { 60, 30, 50, 6, 0}, /* ELM_B1 0 */ + { 90, 45, 50, 6, 0}, /* ELM_B2 0 */ + { 150, 75, 50, 6, 0}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 36.75, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 26.25, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 26.25, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 21, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"LL", 11, 8, 8,0x6C,"l","l",ELM_FEATURE_ALV|ELM_FEATURE_LAT|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 460, 230, 50, 6, 0}, /* ELM_F1 0 */ + { 940, 470, 50, 6, 0}, /* ELM_F2 0 */ + { 2500, 1220, 50, 6, 0}, /* ELM_F3 -30 */ + { 60, 30, 50, 6, 0}, /* ELM_B1 0 */ + { 90, 45, 50, 6, 0}, /* ELM_B2 0 */ + { 150, 75, 50, 6, 0}, /* ELM_B3 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AN 0 */ + { 36.75, 0, 100, 0, 0}, /* ELM_A1 0 */ + { 26.25, 0, 100, 0, 0}, /* ELM_A2 0 */ + { 26.25, 0, 100, 0, 0}, /* ELM_A3 0 */ + { 21, 0, 100, 0, 0}, /* ELM_A4 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A5 0 */ + { -30, 0, 100, 0, 0}, /* ELM_A6 0 */ + { -30, 0, 100, 0, 0}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"RX", 10,10,10,0xD5,"R","",ELM_FEATURE_RZD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 490, 0, 100, 0, 5}, /* ELM_F1 0 */ + { 1180, 0, 100, 0, 5}, /* ELM_F2 0 */ + { 1600, 1600, 0, 5, 5}, /* ELM_F3 0 */ + { 60, 30, 50, 0, 5}, /* ELM_B1 0 */ + { 90, 45, 50, 5, 5}, /* ELM_B2 0 */ + { 70, 35, 50, 5, 5}, /* ELM_B3 0 */ + { -30, 0, 100, 5, 5}, /* ELM_AN 0 */ + { 42, 21, 50, 5, 5}, /* ELM_A1 0 */ + { 35, 17.5, 50, 5, 5}, /* ELM_A2 0 */ + { 35, 17.5, 50, 5, 5}, /* ELM_A3 0 */ + { -30, 0, 50, 5, 5}, /* ELM_A4 15 */ + { -30, -15, 50, 5, 5}, /* ELM_A5 0 */ + { -30, -15, 50, 5, 5}, /* ELM_A6 0 */ + { -30, -15, 50, 5, 5}, /* ELM_AB 0 */ + { 50, 25, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"R", 10,11,11,0xA8,"r","r",ELM_FEATURE_ALV|ELM_FEATURE_APR, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 490, 0, 100, 0, 5}, /* ELM_F1 0 */ + { 1180, 590, 50, 5, 5}, /* ELM_F2 0 */ + { 1600, 740, 50, 5, 5}, /* ELM_F3 -60 */ + { 60, 0, 100, 0, 5}, /* ELM_B1 0 */ + { 90, 45, 50, 5, 5}, /* ELM_B2 0 */ + { 150, 75, 50, 5, 5}, /* ELM_B3 0 */ + { -30, 0, 100, 5, 5}, /* ELM_AN 0 */ + { 42, 21, 50, 5, 5}, /* ELM_A1 0 */ + { 35, 17.5, 50, 5, 5}, /* ELM_A2 0 */ + { 35, 17.5, 50, 5, 5}, /* ELM_A3 0 */ + { -30, 0, 50, 5, 5}, /* ELM_A4 15 */ + { -30, -15, 50, 5, 5}, /* ELM_A5 0 */ + { -30, -15, 50, 5, 5}, /* ELM_A6 0 */ + { -30, -15, 50, 5, 5}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"W", 10, 8, 8,0x77,"w","w",ELM_FEATURE_APR|ELM_FEATURE_LBV|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 190, 50, 50, 4, 4}, /* ELM_F1 -45 */ + { 760, 350, 50, 4, 4}, /* ELM_F2 -30 */ + { 2020, 980, 50, 4, 4}, /* ELM_F3 -30 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 43.75, 21, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 28, 14, 50, 4, 4}, /* ELM_A2 0 */ + { 21, 10.5, 50, 4, 4}, /* ELM_A3 0 */ + { -30, 0, 50, 4, 4}, /* ELM_A4 15 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"Y", 10, 7, 7,0x6A,"j","j",ELM_FEATURE_APR|ELM_FEATURE_PAL|ELM_FEATURE_VCD, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 250, 110, 50, 4, 4}, /* ELM_F1 -15 */ + { 2500, 1190, 50, 4, 4}, /* ELM_F2 -60 */ + { 2980, 1460, 50, 4, 4}, /* ELM_F3 -30 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 33.25, 17.5, 50, 4, 4}, /* ELM_A2 0.875 */ + { 38.5, 17.5, 50, 4, 4}, /* ELM_A3 -1.75 */ + { 31.5, 14, 50, 4, 4}, /* ELM_A4 -1.75 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"I", 2, 8, 6,0x49,"I","I",ELM_FEATURE_FNT|ELM_FEATURE_SMH|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 400, 170, 50, 4, 4}, /* ELM_F1 -30 */ + { 2080, 1070, 50, 4, 4}, /* ELM_F2 30 */ + { 2560, 1340, 50, 4, 4}, /* ELM_F3 60 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 36.75, 17.5, 50, 4, 4}, /* ELM_A2 -0.875 */ + { 35, 17.5, 50, 4, 4}, /* ELM_A3 0 */ + { 29.75, 14, 50, 4, 4}, /* ELM_A4 -0.875 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"E", 2, 8, 4,0x45,"e","E",ELM_FEATURE_FNT|ELM_FEATURE_LMD|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 640, 350, 50, 4, 4}, /* ELM_F1 30 */ + { 2020, 1070, 50, 4, 4}, /* ELM_F2 60 */ + { 2500, 1220, 50, 4, 4}, /* ELM_F3 -30 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 42, 21, 50, 4, 4}, /* ELM_A2 0 */ + { 38.5, 17.5, 50, 4, 4}, /* ELM_A3 -1.75 */ + { 31.5, 14, 50, 4, 4}, /* ELM_A4 -1.75 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"AA", 2,10, 5,0x51,"&","&",ELM_FEATURE_FNT|ELM_FEATURE_LOW|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 790, 410, 50, 4, 4}, /* ELM_F1 15 */ + { 1780, 950, 50, 4, 4}, /* ELM_F2 60 */ + { 2500, 1220, 50, 4, 4}, /* ELM_F3 -30 */ + { 130, 65, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 47.25, 24.5, 50, 4, 4}, /* ELM_A2 0.875 */ + { 38.5, 17.5, 50, 4, 4}, /* ELM_A3 -1.75 */ + { 31.5, 14, 50, 4, 4}, /* ELM_A4 -1.75 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"U", 2, 9, 6,0xC3,"V","V",ELM_FEATURE_BCK|ELM_FEATURE_LMD|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 700, 350, 50, 4, 4}, /* ELM_F1 0 */ + { 1360, 710, 50, 4, 4}, /* ELM_F2 30 */ + { 2500, 1220, 50, 4, 4}, /* ELM_F3 -30 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 43.75, 21, 50, 4, 4}, /* ELM_A2 -0.875 */ + { 31.5, 14, 50, 4, 4}, /* ELM_A3 -1.75 */ + { 24.5, 10.5, 50, 4, 4}, /* ELM_A4 -1.75 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"O", 2, 9, 6,0x81,"0","A.",ELM_FEATURE_BCK|ELM_FEATURE_LOW|ELM_FEATURE_RND|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 610, 290, 50, 4, 4}, /* ELM_F1 -15 */ + { 880, 470, 50, 4, 4}, /* ELM_F2 30 */ + { 2500, 1220, 50, 4, 4}, /* ELM_F3 -30 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 47.25, 24.5, 50, 4, 4}, /* ELM_A2 0.875 */ + { 22.75, 10.5, 50, 4, 4}, /* ELM_A3 -0.875 */ + { 15.75, 7, 50, 4, 4}, /* ELM_A4 -0.875 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"OO", 2, 6, 4,0x55,"U","U",ELM_FEATURE_BCK|ELM_FEATURE_RND|ELM_FEATURE_SMH|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 370, 170, 50, 4, 4}, /* ELM_F1 -15 */ + { 1000, 470, 50, 4, 4}, /* ELM_F2 -30 */ + { 2500, 1220, 50, 4, 4}, /* ELM_F3 -30 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 42, 21, 50, 4, 4}, /* ELM_A2 0 */ + { 28, 14, 50, 4, 4}, /* ELM_A3 0 */ + { 22.75, 10.5, 50, 4, 4}, /* ELM_A4 -0.875 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"A", 2, 4, 4,0xAB,"@","@",ELM_FEATURE_CNT|ELM_FEATURE_MDL|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 490, 230, 50, 4, 4}, /* ELM_F1 -15 */ + { 1480, 710, 50, 4, 4}, /* ELM_F2 -30 */ + { 2500, 1220, 50, 4, 4}, /* ELM_F3 -30 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A2 -0.875 */ + { 33.25, 17.5, 50, 4, 4}, /* ELM_A3 0.875 */ + { 26.25, 14, 50, 4, 4}, /* ELM_A4 0.875 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"EE", 2,11, 7,0x69,"i","i",ELM_FEATURE_FNT|ELM_FEATURE_HGH|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 250, 110, 50, 4, 4}, /* ELM_F1 -15 */ + { 2320, 1190, 50, 4, 4}, /* ELM_F2 30 */ + { 3200, 1580, 50, 4, 4}, /* ELM_F3 -20 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 33.25, 17.5, 50, 4, 4}, /* ELM_A2 0.875 */ + { 36.75, 17.5, 50, 4, 4}, /* ELM_A3 -0.875 */ + { 31.5, 14, 50, 4, 4}, /* ELM_A4 -1.75 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"ER", 2,16,16,0xCE,"3","V\"",ELM_FEATURE_CNT|ELM_FEATURE_LMD|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 580, 290, 50, 4, 4}, /* ELM_F1 0 */ + { 1420, 710, 50, 4, 4}, /* ELM_F2 0 */ + { 2500, 1220, 50, 4, 4}, /* ELM_F3 -30 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 45.5, 21, 50, 4, 4}, /* ELM_A2 -1.75 */ + { 33.25, 17.5, 50, 4, 4}, /* ELM_A3 0.875 */ + { 26.25, 14, 50, 4, 4}, /* ELM_A4 0.875 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"AR", 2,15,15,0x41,"A","A",ELM_FEATURE_BCK|ELM_FEATURE_LOW|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 790, 410, 50, 4, 4}, /* ELM_F1 15 */ + { 880, 470, 50, 4, 4}, /* ELM_F2 30 */ + { 2500, 1220, 50, 4, 4}, /* ELM_F3 -30 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 49, 24.5, 50, 4, 4}, /* ELM_A2 0 */ + { 29.75, 14, 50, 4, 4}, /* ELM_A3 -0.875 */ + { 22.75, 10.5, 50, 4, 4}, /* ELM_A4 -0.875 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"AW", 2,16,10,0x8D,"O","O",ELM_FEATURE_BCK|ELM_FEATURE_LMD|ELM_FEATURE_RND|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 490, 230, 50, 4, 4}, /* ELM_F1 -15 */ + { 820, 470, 50, 4, 4}, /* ELM_F2 60 */ + { 2500, 1220, 50, 4, 4}, /* ELM_F3 -30 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 45.5, 21, 50, 4, 4}, /* ELM_A2 -1.75 */ + { 22.75, 10.5, 50, 4, 4}, /* ELM_A3 -0.875 */ + { 17.5, 7, 50, 4, 4}, /* ELM_A4 -1.75 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"UU", 2,14, 9,0x75,"u","u",ELM_FEATURE_BCK|ELM_FEATURE_HGH|ELM_FEATURE_RND|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 250, 110, 50, 4, 4}, /* ELM_F1 -15 */ + { 880, 470, 50, 4, 4}, /* ELM_F2 30 */ + { 2200, 1100, 50, 4, 4}, /* ELM_F3 0 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 38.5, 17.5, 50, 4, 4}, /* ELM_A2 -1.75 */ + { 17.5, 7, 50, 4, 4}, /* ELM_A3 -1.75 */ + { 10.5, 3.5, 50, 4, 4}, /* ELM_A4 -1.75 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"AI", 2, 9, 6,0x45,"e","E",ELM_FEATURE_FNT|ELM_FEATURE_LMD|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 640, 290, 50, 5, 5}, /* ELM_F1 -30 */ + { 1600, 830, 50, 5, 5}, /* ELM_F2 30 */ + { 2500, 1220, 50, 5, 5}, /* ELM_F3 -30 */ + { 60, 30, 50, 5, 5}, /* ELM_B1 0 */ + { 90, 45, 50, 5, 5}, /* ELM_B2 0 */ + { 150, 75, 50, 5, 5}, /* ELM_B3 0 */ + { -30, 0, 100, 5, 5}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 5, 5}, /* ELM_A1 -0.875 */ + { 45.5, 21, 50, 5, 5}, /* ELM_A2 -1.75 */ + { 35, 17.5, 50, 5, 5}, /* ELM_A3 0 */ + { 29.75, 14, 50, 5, 5}, /* ELM_A4 -0.875 */ + { -30, -15, 50, 5, 5}, /* ELM_A5 0 */ + { -30, -15, 50, 5, 5}, /* ELM_A6 0 */ + { -30, -15, 50, 5, 5}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"IE", 2, 9, 6,0x61,"a","a",ELM_FEATURE_CNT|ELM_FEATURE_LOW|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 790, 410, 50, 5, 5}, /* ELM_F1 15 */ + { 880, 470, 50, 5, 5}, /* ELM_F2 30 */ + { 2500, 1220, 50, 5, 5}, /* ELM_F3 -30 */ + { 60, 30, 50, 5, 5}, /* ELM_B1 0 */ + { 90, 45, 50, 5, 5}, /* ELM_B2 0 */ + { 150, 75, 50, 5, 5}, /* ELM_B3 0 */ + { -30, 0, 100, 5, 5}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 5, 5}, /* ELM_A1 -0.875 */ + { 49, 24.5, 50, 5, 5}, /* ELM_A2 0 */ + { 29.75, 14, 50, 5, 5}, /* ELM_A3 -0.875 */ + { 22.75, 10.5, 50, 5, 5}, /* ELM_A4 -0.875 */ + { -30, -15, 50, 5, 5}, /* ELM_A5 0 */ + { -30, -15, 50, 5, 5}, /* ELM_A6 0 */ + { -30, -15, 50, 5, 5}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"OI", 2, 9, 6,0x6F,"o","o",ELM_FEATURE_BCK|ELM_FEATURE_RND|ELM_FEATURE_UMD|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 490, 230, 50, 5, 5}, /* ELM_F1 -15 */ + { 820, 350, 50, 5, 5}, /* ELM_F2 -60 */ + { 2500, 1220, 50, 5, 5}, /* ELM_F3 -30 */ + { 60, 30, 50, 5, 5}, /* ELM_B1 0 */ + { 90, 45, 50, 5, 5}, /* ELM_B2 0 */ + { 150, 75, 50, 5, 5}, /* ELM_B3 0 */ + { -30, 0, 100, 5, 5}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 5, 5}, /* ELM_A1 -0.875 */ + { 45.5, 21, 50, 5, 5}, /* ELM_A2 -1.75 */ + { 22.75, 10.5, 50, 5, 5}, /* ELM_A3 -0.875 */ + { 17.5, 7, 50, 5, 5}, /* ELM_A4 -1.75 */ + { -30, -15, 50, 5, 5}, /* ELM_A5 0 */ + { -30, -15, 50, 5, 5}, /* ELM_A6 0 */ + { -30, -15, 50, 5, 5}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"OU", 2, 9, 6,0x61,"a","a",ELM_FEATURE_CNT|ELM_FEATURE_LOW|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 790, 410, 50, 5, 5}, /* ELM_F1 15 */ + { 1300, 590, 50, 5, 5}, /* ELM_F2 -60 */ + { 2500, 1220, 50, 5, 5}, /* ELM_F3 -30 */ + { 60, 30, 50, 5, 5}, /* ELM_B1 0 */ + { 90, 45, 50, 5, 5}, /* ELM_B2 0 */ + { 150, 75, 50, 5, 5}, /* ELM_B3 0 */ + { -30, 0, 100, 5, 5}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 5, 5}, /* ELM_A1 -0.875 */ + { 47.25, 24.5, 50, 5, 5}, /* ELM_A2 0.875 */ + { 35, 17.5, 50, 5, 5}, /* ELM_A3 0 */ + { 28, 14, 50, 5, 5}, /* ELM_A4 0 */ + { -30, -15, 50, 5, 5}, /* ELM_A5 0 */ + { -30, -15, 50, 5, 5}, /* ELM_A6 0 */ + { -30, -15, 50, 5, 5}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"OV", 2, 8, 6,0x55,"U","U",ELM_FEATURE_BCK|ELM_FEATURE_RND|ELM_FEATURE_SMH|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 370, 170, 50, 4, 4}, /* ELM_F1 -15 */ + { 1000, 470, 50, 4, 4}, /* ELM_F2 -30 */ + { 2500, 1220, 50, 4, 4}, /* ELM_F3 -30 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 42, 21, 50, 4, 4}, /* ELM_A2 0 */ + { 28, 14, 50, 4, 4}, /* ELM_A3 0 */ + { 22.75, 10.5, 50, 4, 4}, /* ELM_A4 -0.875 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"OA", 2, 9, 6,0xAB,"@","@",ELM_FEATURE_CNT|ELM_FEATURE_MDL|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 490, 230, 50, 5, 5}, /* ELM_F1 -15 */ + { 1480, 710, 50, 5, 5}, /* ELM_F2 -30 */ + { 2500, 1220, 50, 5, 5}, /* ELM_F3 -30 */ + { 60, 30, 50, 5, 5}, /* ELM_B1 0 */ + { 90, 45, 50, 5, 5}, /* ELM_B2 0 */ + { 150, 75, 50, 5, 5}, /* ELM_B3 0 */ + { -30, 0, 100, 5, 5}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 5, 5}, /* ELM_A1 -0.875 */ + { 50.75, 24.5, 50, 5, 5}, /* ELM_A2 -0.875 */ + { 33.25, 17.5, 50, 5, 5}, /* ELM_A3 0.875 */ + { 26.25, 14, 50, 5, 5}, /* ELM_A4 0.875 */ + { -30, -15, 50, 5, 5}, /* ELM_A5 0 */ + { -30, -15, 50, 5, 5}, /* ELM_A6 0 */ + { -30, -15, 50, 5, 5}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"IA", 2, 9, 6,0x49,"I","I",ELM_FEATURE_FNT|ELM_FEATURE_SMH|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 310, 170, 50, 5, 5}, /* ELM_F1 15 */ + { 2200, 1070, 50, 5, 5}, /* ELM_F2 -30 */ + { 2920, 1460, 50, 5, 5}, /* ELM_F3 0 */ + { 60, 30, 50, 5, 5}, /* ELM_B1 0 */ + { 90, 45, 50, 5, 5}, /* ELM_B2 0 */ + { 150, 75, 50, 5, 5}, /* ELM_B3 0 */ + { -30, 0, 100, 5, 5}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 5, 5}, /* ELM_A1 -0.875 */ + { 35, 17.5, 50, 5, 5}, /* ELM_A2 0 */ + { 36.75, 17.5, 50, 5, 5}, /* ELM_A3 -0.875 */ + { 31.5, 14, 50, 5, 5}, /* ELM_A4 -1.75 */ + { -30, -15, 50, 5, 5}, /* ELM_A5 0 */ + { -30, -15, 50, 5, 5}, /* ELM_A6 0 */ + { -30, -15, 50, 5, 5}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"IB", 2, 8, 6,0x51,"@","@",ELM_FEATURE_FNT|ELM_FEATURE_LOW|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 490, 230, 50, 4, 4}, /* ELM_F1 -15 */ + { 1480, 710, 50, 4, 4}, /* ELM_F2 -30 */ + { 2500, 1220, 50, 4, 4}, /* ELM_F3 -30 */ + { 60, 30, 50, 4, 4}, /* ELM_B1 0 */ + { 90, 45, 50, 4, 4}, /* ELM_B2 0 */ + { 150, 75, 50, 4, 4}, /* ELM_B3 0 */ + { -30, 0, 100, 4, 4}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A1 -0.875 */ + { 50.75, 24.5, 50, 4, 4}, /* ELM_A2 -0.875 */ + { 33.25, 17.5, 50, 4, 4}, /* ELM_A3 0.875 */ + { 26.25, 14, 50, 4, 4}, /* ELM_A4 0.875 */ + { -30, -15, 50, 4, 4}, /* ELM_A5 0 */ + { -30, -15, 50, 4, 4}, /* ELM_A6 0 */ + { -30, -15, 50, 4, 4}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"AIR", 2, 9, 6,0x45,"e","E",ELM_FEATURE_FNT|ELM_FEATURE_LMD|ELM_FEATURE_UNR|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 640, 350, 50, 5, 5}, /* ELM_F1 30 */ + { 2020, 1070, 50, 5, 5}, /* ELM_F2 60 */ + { 2500, 1220, 50, 5, 5}, /* ELM_F3 -30 */ + { 60, 30, 50, 5, 5}, /* ELM_B1 0 */ + { 90, 45, 50, 5, 5}, /* ELM_B2 0 */ + { 150, 75, 50, 5, 5}, /* ELM_B3 0 */ + { -30, 0, 100, 5, 5}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 5, 5}, /* ELM_A1 -0.875 */ + { 42, 21, 50, 5, 5}, /* ELM_A2 0 */ + { 38.5, 17.5, 50, 5, 5}, /* ELM_A3 -1.75 */ + { 31.5, 14, 50, 5, 5}, /* ELM_A4 -1.75 */ + { -30, -15, 50, 5, 5}, /* ELM_A5 0 */ + { -30, -15, 50, 5, 5}, /* ELM_A6 0 */ + { -30, -15, 50, 5, 5}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"OOR", 2, 9, 6,0x55,"U","U",ELM_FEATURE_BCK|ELM_FEATURE_RND|ELM_FEATURE_SMH|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 370, 170, 50, 5, 5}, /* ELM_F1 -15 */ + { 1000, 470, 50, 5, 5}, /* ELM_F2 -30 */ + { 2500, 1220, 50, 5, 5}, /* ELM_F3 -30 */ + { 60, 30, 50, 5, 5}, /* ELM_B1 0 */ + { 90, 45, 50, 5, 5}, /* ELM_B2 0 */ + { 150, 75, 50, 5, 5}, /* ELM_B3 0 */ + { -30, 0, 100, 5, 5}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 5, 5}, /* ELM_A1 -0.875 */ + { 42, 21, 50, 5, 5}, /* ELM_A2 0 */ + { 28, 14, 50, 5, 5}, /* ELM_A3 0 */ + { 22.75, 7, 50, 5, 5}, /* ELM_A4 -4.375 */ + { -30, -15, 50, 5, 5}, /* ELM_A5 0 */ + { -30, -15, 50, 5, 5}, /* ELM_A6 0 */ + { -30, -15, 50, 5, 5}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +}, + +{"OR", 2, 9, 6,0x8D,"O","O",ELM_FEATURE_BCK|ELM_FEATURE_LMD|ELM_FEATURE_RND|ELM_FEATURE_VWL, + { + { 270, 135, 50, 0, 0}, /* ELM_FN 0 */ + { 490, 230, 50, 5, 5}, /* ELM_F1 -15 */ + { 820, 470, 50, 5, 5}, /* ELM_F2 60 */ + { 2500, 1220, 50, 5, 5}, /* ELM_F3 -30 */ + { 60, 30, 50, 5, 5}, /* ELM_B1 0 */ + { 90, 45, 50, 5, 5}, /* ELM_B2 0 */ + { 150, 75, 50, 5, 5}, /* ELM_B3 0 */ + { -30, 0, 100, 5, 5}, /* ELM_AN 0 */ + { 50.75, 24.5, 50, 5, 5}, /* ELM_A1 -0.875 */ + { 45.5, 21, 50, 5, 5}, /* ELM_A2 -1.75 */ + { 22.75, 10.5, 50, 5, 5}, /* ELM_A3 -0.875 */ + { 17.5, 7, 50, 5, 5}, /* ELM_A4 -1.75 */ + { -30, -15, 50, 5, 5}, /* ELM_A5 0 */ + { -30, -15, 50, 5, 5}, /* ELM_A6 0 */ + { -30, -15, 50, 5, 5}, /* ELM_AB 0 */ + { 62, 31, 50, 0, 0}, /* ELM_AV 0 */ + { 16, 8, 50, 0, 0}, /* ELM_AVC 0 */ + { 0, 0, 50, 0, 0}, /* ELM_ASP 0 */ + { 0, 0, 50, 0, 0} /* ELM_AF 0 */ + } +} + diff --git a/src/soloud/src/audiosource/speech/darray.cpp b/src/soloud/src/audiosource/speech/darray.cpp new file mode 100644 index 0000000..4a6eae4 --- /dev/null +++ b/src/soloud/src/audiosource/speech/darray.cpp @@ -0,0 +1,70 @@ +#include +#include +#include "darray.h" + +darray::darray() +{ + mAllocChunk = 128; + mAllocated = mUsed = 0; + mData = NULL; +} + +void darray::clear() +{ + free(mData); + mAllocChunk = 128; + mAllocated = mUsed = 0; + mData = NULL; +} + +darray::~darray() +{ + clear(); +} + +char * darray::getDataInPos(int aPosition) +{ + if (aPosition < mAllocated && aPosition < mUsed) + return mData + aPosition; + + if (aPosition >= mAllocated) + { + int newsize = mAllocated; + + while (newsize <= aPosition) + { + newsize += mAllocChunk; + mAllocChunk *= 2; + } + + char *newdata = (char*)realloc(mData, newsize); + if (!newdata) + { + free(mData); + mData = NULL; + mAllocated = mUsed = 0; + return NULL; + } + else + { + memset(newdata + mAllocated, 0, newsize - mAllocated); + } + + mData = newdata; + mAllocated = newsize; + } + + if (aPosition >= mUsed) + { + mUsed = aPosition + 1; + } + + return mData + aPosition; +} + +void darray::put(int aData) +{ + char *s = getDataInPos(mUsed); + + *s = aData; +} diff --git a/src/soloud/src/audiosource/speech/darray.h b/src/soloud/src/audiosource/speech/darray.h new file mode 100644 index 0000000..d3251fb --- /dev/null +++ b/src/soloud/src/audiosource/speech/darray.h @@ -0,0 +1,22 @@ +#if !defined(DARRAY_H) +#define DARRAY_H + +class darray +{ +protected: + char *mData; + int mUsed; + int mAllocated; + int mAllocChunk; +public: + darray(); + ~darray(); + void clear(); + char *getDataInPos(int aPosition); + void put(int aData); + int getSize() const { return mUsed; } + char *getData() { return mData; } +}; + +#endif + diff --git a/src/soloud/src/audiosource/speech/klatt.cpp b/src/soloud/src/audiosource/speech/klatt.cpp new file mode 100644 index 0000000..cd5e116 --- /dev/null +++ b/src/soloud/src/audiosource/speech/klatt.cpp @@ -0,0 +1,1073 @@ +#include +#include +#include "klatt.h" +#include "darray.h" +#include "resonator.h" + +#ifndef PI +#define PI 3.1415926535897932384626433832795f +#endif + +#ifndef NULL +#define NULL 0 +#endif + +class Interp +{ +public: + float mSteady; + float mFixed; + char mProportion; + char mExtDelay; + char mIntDelay; +}; + + +enum Eparm_e +{ + ELM_FN, ELM_F1, ELM_F2, ELM_F3, + ELM_B1, ELM_B2, ELM_B3, ELM_AN, + ELM_A1, ELM_A2, ELM_A3, ELM_A4, + ELM_A5, ELM_A6, ELM_AB, ELM_AV, + ELM_AVC, ELM_ASP, ELM_AF, + ELM_COUNT +}; + +class Element +{ +public: + const char *mName; // unused + const char mRK; + const char mDU; + const char mUD; + unsigned char mFont; // unused + const char *mDict; // unused + const char *mIpa; // unused + int mFeat; // only ELM_FEATURE_VWL + Interp mInterpolator[ELM_COUNT]; + }; + +enum ELEMENT_FEATURES +{ + ELM_FEATURE_ALV = 0x00000001, + ELM_FEATURE_APR = 0x00000002, + ELM_FEATURE_BCK = 0x00000004, + ELM_FEATURE_BLB = 0x00000008, + ELM_FEATURE_CNT = 0x00000010, + ELM_FEATURE_DNT = 0x00000020, + ELM_FEATURE_FNT = 0x00000040, + ELM_FEATURE_FRC = 0x00000080, + ELM_FEATURE_GLT = 0x00000100, + ELM_FEATURE_HGH = 0x00000200, + ELM_FEATURE_LAT = 0x00000400, + ELM_FEATURE_LBD = 0x00000800, + ELM_FEATURE_LBV = 0x00001000, + ELM_FEATURE_LMD = 0x00002000, + ELM_FEATURE_LOW = 0x00004000, + ELM_FEATURE_MDL = 0x00008000, + ELM_FEATURE_NAS = 0x00010000, + ELM_FEATURE_PAL = 0x00020000, + ELM_FEATURE_PLA = 0x00040000, + ELM_FEATURE_RND = 0x00080000, + ELM_FEATURE_RZD = 0x00100000, + ELM_FEATURE_SMH = 0x00200000, + ELM_FEATURE_STP = 0x00400000, + ELM_FEATURE_UMD = 0x00800000, + ELM_FEATURE_UNR = 0x01000000, + ELM_FEATURE_VCD = 0x02000000, + ELM_FEATURE_VEL = 0x04000000, + ELM_FEATURE_VLS = 0x08000000, + ELM_FEATURE_VWL = 0x10000000 +}; + +enum ELEMENTS +{ + ELM_END = 0, + ELM_Q, ELM_P, ELM_PY, ELM_PZ, ELM_T, ELM_TY, + ELM_TZ, ELM_K, ELM_KY, ELM_KZ, ELM_B, ELM_BY, ELM_BZ, + ELM_D, ELM_DY, ELM_DZ, ELM_G, ELM_GY, ELM_GZ, ELM_M, + ELM_N, ELM_NG, ELM_F, ELM_TH, ELM_S, ELM_SH, ELM_X, + ELM_H, ELM_V, ELM_QQ, ELM_DH, ELM_DI, ELM_Z, ELM_ZZ, + ELM_ZH, ELM_CH, ELM_CI, ELM_J, ELM_JY, ELM_L, ELM_LL, + ELM_RX, ELM_R, ELM_W, ELM_Y, ELM_I, ELM_E, ELM_AA, + ELM_U, ELM_O, ELM_OO, ELM_A, ELM_EE, ELM_ER, ELM_AR, + ELM_AW, ELM_UU, ELM_AI, ELM_IE, ELM_OI, ELM_OU, ELM_OV, + ELM_OA, ELM_IA, ELM_IB, ELM_AIR,ELM_OOR,ELM_OR +}; + +#define PHONEME_COUNT 53 +#define AMP_ADJ 14 +#define StressDur(e,s) (s,((e->mDU + e->mUD)/2)) + + + + +class PhonemeToElements +{ +public: + int mKey; + char mData[8]; +}; + +/* Order is important - 2 byte phonemes first, otherwise + the search function will fail*/ +static PhonemeToElements phoneme_to_elements[PHONEME_COUNT] = +{ + /* mKey, count, 0-7 elements */ +/* tS */ 0x5374, 2, ELM_CH, ELM_CI, 0, 0, 0, 0, 0, +/* dZ */ 0x5a64, 4, ELM_J, ELM_JY, ELM_QQ, ELM_JY, 0, 0, 0, +/* rr */ 0x7272, 3, ELM_R, ELM_QQ, ELM_R, 0, 0, 0, 0, +/* eI */ 0x4965, 2, ELM_AI, ELM_I, 0, 0, 0, 0, 0, +/* aI */ 0x4961, 2, ELM_IE, ELM_I, 0, 0, 0, 0, 0, +/* oI */ 0x496f, 2, ELM_OI, ELM_I, 0, 0, 0, 0, 0, +/* aU */ 0x5561, 2, ELM_OU, ELM_OV, 0, 0, 0, 0, 0, +/* @U */ 0x5540, 2, ELM_OA, ELM_OV, 0, 0, 0, 0, 0, +/* I@ */ 0x4049, 2, ELM_IA, ELM_IB, 0, 0, 0, 0, 0, +/* e@ */ 0x4065, 2, ELM_AIR, ELM_IB, 0, 0, 0, 0, 0, +/* U@ */ 0x4055, 2, ELM_OOR, ELM_IB, 0, 0, 0, 0, 0, +/* O@ */ 0x404f, 2, ELM_OR, ELM_IB, 0, 0, 0, 0, 0, +/* oU */ 0x556f, 2, ELM_OI, ELM_OV, 0, 0, 0, 0, 0, +/* */ 0x0020, 1, ELM_Q, 0, 0, 0, 0, 0, 0, +/* p */ 0x0070, 3, ELM_P, ELM_PY, ELM_PZ, 0, 0, 0, 0, +/* t */ 0x0074, 3, ELM_T, ELM_TY, ELM_TZ, 0, 0, 0, 0, +/* k */ 0x006b, 3, ELM_K, ELM_KY, ELM_KZ, 0, 0, 0, 0, +/* b */ 0x0062, 3, ELM_B, ELM_BY, ELM_BZ, 0, 0, 0, 0, +/* d */ 0x0064, 3, ELM_D, ELM_DY, ELM_DZ, 0, 0, 0, 0, +/* g */ 0x0067, 3, ELM_G, ELM_GY, ELM_GZ, 0, 0, 0, 0, +/* m */ 0x006d, 1, ELM_M, 0, 0, 0, 0, 0, 0, +/* n */ 0x006e, 1, ELM_N, 0, 0, 0, 0, 0, 0, +/* N */ 0x004e, 1, ELM_NG, 0, 0, 0, 0, 0, 0, +/* f */ 0x0066, 1, ELM_F, 0, 0, 0, 0, 0, 0, +/* T */ 0x0054, 1, ELM_TH, 0, 0, 0, 0, 0, 0, +/* s */ 0x0073, 1, ELM_S, 0, 0, 0, 0, 0, 0, +/* S */ 0x0053, 1, ELM_SH, 0, 0, 0, 0, 0, 0, +/* h */ 0x0068, 1, ELM_H, 0, 0, 0, 0, 0, 0, +/* v */ 0x0076, 3, ELM_V, ELM_QQ, ELM_V, 0, 0, 0, 0, +/* D */ 0x0044, 3, ELM_DH, ELM_QQ, ELM_DI, 0, 0, 0, 0, +/* z */ 0x007a, 3, ELM_Z, ELM_QQ, ELM_ZZ, 0, 0, 0, 0, +/* Z */ 0x005a, 3, ELM_ZH, ELM_QQ, ELM_ZH, 0, 0, 0, 0, +/* l */ 0x006c, 1, ELM_L, 0, 0, 0, 0, 0, 0, +/* r */ 0x0072, 1, ELM_R, 0, 0, 0, 0, 0, 0, +/* R */ 0x0052, 1, ELM_RX, 0, 0, 0, 0, 0, 0, +/* w */ 0x0077, 1, ELM_W, 0, 0, 0, 0, 0, 0, +/* x */ 0x0078, 1, ELM_X, 0, 0, 0, 0, 0, 0, +/* % */ 0x0025, 1, ELM_QQ, 0, 0, 0, 0, 0, 0, +/* j */ 0x006a, 1, ELM_Y, 0, 0, 0, 0, 0, 0, +/* I */ 0x0049, 1, ELM_I, 0, 0, 0, 0, 0, 0, +/* e */ 0x0065, 1, ELM_E, 0, 0, 0, 0, 0, 0, +/* & */ 0x0026, 1, ELM_AA, 0, 0, 0, 0, 0, 0, +/* V */ 0x0056, 1, ELM_U, 0, 0, 0, 0, 0, 0, +/* 0 */ 0x0030, 1, ELM_O, 0, 0, 0, 0, 0, 0, +/* U */ 0x0055, 1, ELM_OO, 0, 0, 0, 0, 0, 0, +/* @ */ 0x0040, 1, ELM_A, 0, 0, 0, 0, 0, 0, +/* i */ 0x0069, 1, ELM_EE, 0, 0, 0, 0, 0, 0, +/* 3 */ 0x0033, 1, ELM_ER, 0, 0, 0, 0, 0, 0, +/* A */ 0x0041, 1, ELM_AR, 0, 0, 0, 0, 0, 0, +/* O */ 0x004f, 1, ELM_AW, 0, 0, 0, 0, 0, 0, +/* u */ 0x0075, 1, ELM_UU, 0, 0, 0, 0, 0, 0, +/* o */ 0x006f, 1, ELM_OI, 0, 0, 0, 0, 0, 0, +/* . */ 0x002e, 1, ELM_END,0, 0, 0, 0, 0, 0, +}; + +static Element gElement[] = +{ +#include "Elements.def" +}; + +static short clip(float input) +{ + int temp = (int)input; + /* clip on boundaries of 16-bit word */ + + if (temp < -32767) + { + //assert? + temp = -32767; + } + else + if (temp > 32767) + { + //assert? + temp = 32767; + } + + return (short)(temp); +} + +/* Convert from decibels to a linear scale factor */ +static float DBtoLIN(int dB) +{ + /* + * Convertion table, db to linear, 87 dB --> 32767 + * 86 dB --> 29491 (1 dB down = 0.5**1/6) + * ... + * 81 dB --> 16384 (6 dB down = 0.5) + * ... + * 0 dB --> 0 + * + * The just noticeable difference for a change in intensity of a vowel + * is approximately 1 dB. Thus all amplitudes are quantized to 1 dB + * steps. + */ + + static const float amptable[88] = + { + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 6.0, 7.0, + 8.0, 9.0, 10.0, 11.0, 13.0, + 14.0, 16.0, 18.0, 20.0, 22.0, + 25.0, 28.0, 32.0, 35.0, 40.0, + 45.0, 51.0, 57.0, 64.0, 71.0, + 80.0, 90.0, 101.0, 114.0, 128.0, + 142.0, 159.0, 179.0, 202.0, 227.0, + 256.0, 284.0, 318.0, 359.0, 405.0, + 455.0, 512.0, 568.0, 638.0, 719.0, + 811.0, 911.0, 1024.0, 1137.0, 1276.0, + 1438.0, 1622.0, 1823.0, 2048.0, 2273.0, + 2552.0, 2875.0, 3244.0, 3645.0, 4096.0, + 4547.0, 5104.0, 5751.0, 6488.0, 7291.0, + 8192.0, 9093.0, 10207.0, 11502.0, 12976.0, + 14582.0, 16384.0, 18350.0, 20644.0, 23429.0, + 26214.0, 29491.0, 32767.0 + }; + + // Check limits or argument (can be removed in final product) + if (dB < 0) + { + dB = 0; + } + else + if (dB >= 88) + { + dB = 87; + } + + return amptable[dB] * 0.001f; +} + + + +klatt_frame::klatt_frame() : + mF0FundamentalFreq(1330), mVoicingAmpdb(60), mFormant1Freq(500), + mFormant1Bandwidth(60), mFormant2Freq(1500), mFormant2Bandwidth(90), + mFormant3Freq(2800), mFormant3Bandwidth(150), mFormant4Freq(3250), + mFormant4Bandwidth(200), mFormant5Freq(3700), mFormant5Bandwidth(200), + mFormant6Freq(4990), mFormant6Bandwidth(500), mNasalZeroFreq(270), + mNasalZeroBandwidth(100), mNasalPoleFreq(270), mNasalPoleBandwidth(100), + mAspirationAmpdb(0), mNoSamplesInOpenPeriod(30), mVoicingBreathiness(0), + mVoicingSpectralTiltdb(10), mFricationAmpdb(0), mSkewnessOfAlternatePeriods(0), + mFormant1Ampdb(0), mFormant1ParallelBandwidth(80), mFormant2Ampdb(0), + mFormant2ParallelBandwidth(200), mFormant3Ampdb(0), mFormant3ParallelBandwidth(350), + mFormant4Ampdb(0), mFormant4ParallelBandwidth(500), mFormant5Ampdb(0), + mFormant5ParallelBandwidth(600), mFormant6Ampdb(0), mFormant6ParallelBandwidth(800), + mParallelNasalPoleAmpdb(0), mBypassFricationAmpdb(0), mPalallelVoicingAmpdb(0), + mOverallGaindb(62) +{ +}; + + +klatt::klatt() : + mBaseF0(1330), + mBaseSpeed(10.0f), + mBaseDeclination(0.5f), + mBaseWaveform(KW_SAW), + mF0Flutter(0), + mSampleRate(0), + mNspFr(0), + mF0FundamentalFreq(0), + mVoicingAmpdb(0), + mSkewnessOfAlternatePeriods(0), + mTimeCount(0), + mNPer(0), + mT0(0), + mNOpen(0), + mNMod(0), + mAmpVoice(0), + mAmpBypas(0), + mAmpAspir(0), + mAmpFrica(0), + mAmpBreth(0), + mSkew(0), + mVLast(0), + mNLast(0), + mGlotLast(0), + mDecay(0), + mOneMd(0), + mElementCount(0), + mElement(0), + mElementIndex(0), + mLastElement(0), + mTStress(0), + mNTStress(0), + mTop(0) +{ +} + +/* +function FLUTTER + +This function adds F0 flutter, as specified in: + +"Analysis, synthesis and perception of voice quality variations among +female and male talkers" D.H. Klatt and L.C. Klatt JASA 87(2) February 1990. +Flutter is added by applying a quasi-random element constructed from three +slowly varying sine waves. +*/ +void klatt::flutter() +{ + int original_f0 = mFrame.mF0FundamentalFreq / 10; + float fla = (float) mF0Flutter / 50; + float flb = (float) original_f0 / 100; + float flc = (float)sin(2 * PI * 12.7 * mTimeCount); + float fld = (float)sin(2 * PI * 7.1 * mTimeCount); + float fle = (float)sin(2 * PI * 4.7 * mTimeCount); + float delta_f0 = fla * flb * (flc + fld + fle) * 10; + mF0FundamentalFreq += (int) delta_f0; +} + +/* Vwave is the differentiated glottal flow waveform, there is a weak +spectral zero around 800 Hz, magic constants a,b reset pitch-synch +*/ + +float klatt::natural_source(int aNper) +{ + // See if glottis open + if (aNper < mNOpen) + { + switch (mBaseWaveform) + { + case KW_TRIANGLE: + return ((aNper % 200) - 100) * 81.92f; // triangle + case KW_SIN: + return (float)(sin(aNper * 0.0314) * 8192); // sin + case KW_SQUARE: + return ((aNper % 200) - 100) > 0 ? 8192.0f : -8192.0f; // square + case KW_PULSE: + return ((aNper % 200) - 100) > 50 ? 8192.0f : -8192.0f; // pulse + case KW_NOISE: + return (int)mNLast & 1 ? -8192.0f : 8192.0f; + case KW_WARBLE: + return (int)mNLast & 7 ? -8192.0f : 8192.0f; + case KW_SAW: // fallthrough + default: + return (abs((aNper % 200) - 100) - 50) * 163.84f; // saw + } + } + else + { + // Glottis closed + return (0.0); + } + +} + +/* Reset selected parameters pitch-synchronously */ + +void klatt::pitch_synch_par_reset(int ns) +{ + if (mF0FundamentalFreq > 0) + { + mT0 = (40 * mSampleRate) / mF0FundamentalFreq; + + /* Period in samp*4 */ + mAmpVoice = DBtoLIN(mVoicingAmpdb); + + /* Duration of period before amplitude modulation */ + mNMod = mT0; + + if (mVoicingAmpdb > 0) + { + mNMod >>= 1; + } + + /* Breathiness of voicing waveform */ + + mAmpBreth = DBtoLIN(mFrame.mVoicingBreathiness) * 0.1f; + + /* Set open phase of glottal period */ + /* where 40 <= open phase <= 263 */ + + mNOpen = 4 * mFrame.mNoSamplesInOpenPeriod; + + if (mNOpen >= (mT0 - 1)) + { + mNOpen = mT0 - 2; + } + + if (mNOpen < 40) + { + mNOpen = 40; /* F0 max = 1000 Hz */ + } + + int temp; + float temp1; + + temp = mSampleRate / mNOpen; + mCritDampedGlotLowPassFilter.initResonator(0L, temp, mSampleRate); + + /* Make gain at F1 about constant */ + + temp1 = mNOpen * .00833f; + mCritDampedGlotLowPassFilter.setGain(temp1 * temp1); + + /* Truncate skewness so as not to exceed duration of closed phase + of glottal period */ + + temp = mT0 - mNOpen; + + if (mSkewnessOfAlternatePeriods > temp) + { + mSkewnessOfAlternatePeriods = temp; + } + + if (mSkew >= 0) + { + mSkew = mSkewnessOfAlternatePeriods; /* Reset mSkew to requested mSkewnessOfAlternatePeriods */ + } + else + { + mSkew = -mSkewnessOfAlternatePeriods; + } + + /* Add skewness to closed portion of voicing period */ + + mT0 = mT0 + mSkew; + mSkew = -mSkew; + } + else + { + mT0 = 4; /* Default for f0 undefined */ + mAmpVoice = 0.0; + mNMod = mT0; + mAmpBreth = 0.0; + } + + /* Reset these pars pitch synchronously or at update rate if f0=0 */ + + if ((mT0 != 4) || (ns == 0)) + { + /* Set one-pole ELM_FEATURE_LOW-pass filter that tilts glottal source */ + mDecay = (0.033f * mFrame.mVoicingSpectralTiltdb); /* Function of samp_rate ? */ + + if (mDecay > 0.0f) + { + mOneMd = 1.0f - mDecay; + } + else + { + mOneMd = 1.0f; + } + } +} + + +/* Get variable parameters from host computer, +initially also get definition of fixed pars +*/ + +void klatt::frame_init() +{ + int mOverallGaindb; /* Overall gain, 60 dB is unity 0 to 60 */ + float amp_parF1; /* mFormant1Ampdb converted to linear gain */ + float amp_parFN; /* mParallelNasalPoleAmpdb converted to linear gain */ + float amp_parF2; /* mFormant2Ampdb converted to linear gain */ + float amp_parF3; /* mFormant3Ampdb converted to linear gain */ + float amp_parF4; /* mFormant4Ampdb converted to linear gain */ + float amp_parF5; /* mFormant5Ampdb converted to linear gain */ + float amp_parF6; /* mFormant6Ampdb converted to linear gain */ + + /* Read speech frame definition into temp store + and move some parameters into active use immediately + (voice-excited ones are updated pitch synchronously + to avoid waveform glitches). + */ + + mF0FundamentalFreq = mFrame.mF0FundamentalFreq; + mVoicingAmpdb = mFrame.mVoicingAmpdb - 7; + + if (mVoicingAmpdb < 0) mVoicingAmpdb = 0; + + mAmpAspir = DBtoLIN(mFrame.mAspirationAmpdb) * .05f; + mAmpFrica = DBtoLIN(mFrame.mFricationAmpdb) * 0.25f; + mSkewnessOfAlternatePeriods = mFrame.mSkewnessOfAlternatePeriods; + + /* Fudge factors (which comprehend affects of formants on each other?) + with these in place ALL_PARALLEL should sound as close as + possible to CASCADE_PARALLEL. + Possible problem feeding in Holmes's amplitudes given this. + */ + amp_parF1 = DBtoLIN(mFrame.mFormant1Ampdb) * 0.4f; /* -7.96 dB */ + amp_parF2 = DBtoLIN(mFrame.mFormant2Ampdb) * 0.15f; /* -16.5 dB */ + amp_parF3 = DBtoLIN(mFrame.mFormant3Ampdb) * 0.06f; /* -24.4 dB */ + amp_parF4 = DBtoLIN(mFrame.mFormant4Ampdb) * 0.04f; /* -28.0 dB */ + amp_parF5 = DBtoLIN(mFrame.mFormant5Ampdb) * 0.022f; /* -33.2 dB */ + amp_parF6 = DBtoLIN(mFrame.mFormant6Ampdb) * 0.03f; /* -30.5 dB */ + amp_parFN = DBtoLIN(mFrame.mParallelNasalPoleAmpdb) * 0.6f; /* -4.44 dB */ + mAmpBypas = DBtoLIN(mFrame.mBypassFricationAmpdb) * 0.05f; /* -26.0 db */ + + // Set coeficients of nasal resonator and zero antiresonator + mNasalPole.initResonator(mFrame.mNasalPoleFreq, mFrame.mNasalPoleBandwidth, mSampleRate); + + mNasalZero.initAntiresonator(mFrame.mNasalZeroFreq, mFrame.mNasalZeroBandwidth, mSampleRate); + + // Set coefficients of parallel resonators, and amplitude of outputs + mParallelFormant1.initResonator(mFrame.mFormant1Freq, mFrame.mFormant1ParallelBandwidth, mSampleRate); + mParallelFormant1.setGain(amp_parF1); + + mParallelResoNasalPole.initResonator(mFrame.mNasalPoleFreq, mFrame.mNasalPoleBandwidth, mSampleRate); + mParallelResoNasalPole.setGain(amp_parFN); + + mParallelFormant2.initResonator(mFrame.mFormant2Freq, mFrame.mFormant2ParallelBandwidth, mSampleRate); + mParallelFormant2.setGain(amp_parF2); + + mParallelFormant3.initResonator(mFrame.mFormant3Freq, mFrame.mFormant3ParallelBandwidth, mSampleRate); + mParallelFormant3.setGain(amp_parF3); + + mParallelFormant4.initResonator(mFrame.mFormant4Freq, mFrame.mFormant4ParallelBandwidth, mSampleRate); + mParallelFormant4.setGain(amp_parF4); + + mParallelFormant5.initResonator(mFrame.mFormant5Freq, mFrame.mFormant5ParallelBandwidth, mSampleRate); + mParallelFormant5.setGain(amp_parF5); + + mParallelFormant6.initResonator(mFrame.mFormant6Freq, mFrame.mFormant6ParallelBandwidth, mSampleRate); + mParallelFormant6.setGain(amp_parF6); + + + /* fold overall gain into output resonator */ + mOverallGaindb = mFrame.mOverallGaindb - 3; + + if (mOverallGaindb <= 0) + mOverallGaindb = 57; + + /* output ELM_FEATURE_LOW-pass filter - resonator with freq 0 and BW = globals->mSampleRate + Thus 3db point is globals->mSampleRate/2 i.e. Nyquist limit. + Only 3db down seems rather mild... + */ + mOutputLowPassFilter.initResonator(0L, (int)mSampleRate, mSampleRate); + mOutputLowPassFilter.setGain(DBtoLIN(mOverallGaindb)); +} + +/* +function PARWAV + +CONVERT FRAME OF PARAMETER DATA TO A WAVEFORM CHUNK +Synthesize globals->mNspFr samples of waveform and store in jwave[]. +*/ + +void klatt::parwave(short int *jwave) +{ + /* Output of cascade branch, also final output */ + + /* Initialize synthesizer and get specification for current speech + frame from host microcomputer */ + + frame_init(); + + if (mF0Flutter != 0) + { + mTimeCount++; /* used for f0 flutter */ + flutter(); /* add f0 flutter */ + } + + /* MAIN LOOP, for each output sample of current frame: */ + + int ns; + for (ns = 0; ns < mNspFr; ns++) + { + float noise; + int n4; + float sourc; /* Sound source if all-parallel config used */ + float glotout; /* Output of glottal sound source */ + float par_glotout; /* Output of parallelglottal sound sourc */ + float voice = 0; /* Current sample of voicing waveform */ + float frics; /* Frication sound source */ + float aspiration; /* Aspiration sound source */ + int nrand; /* Varible used by random number generator */ + + /* Our own code like rand(), but portable + whole upper 31 bits of seed random + assumes 32-bit unsigned arithmetic + with untested code to handle larger. + */ + mSeed = mSeed * 1664525 + 1; + + mSeed &= 0xFFFFFFFF; + + /* Shift top bits of seed up to top of int then back down to LS 14 bits */ + /* Assumes 8 bits per sizeof unit i.e. a "byte" */ + nrand = (((int) mSeed) << (8 * sizeof(int) - 32)) >> (8 * sizeof(int) - 14); + + /* Tilt down noise spectrum by soft ELM_FEATURE_LOW-pass filter having + * a pole near the origin in the z-plane, i.e. + * output = input + (0.75 * lastoutput) */ + + noise = nrand + (0.75f * mNLast); /* Function of samp_rate ? */ + + mNLast = noise; + + /* Amplitude modulate noise (reduce noise amplitude during + second half of glottal period) if voicing simultaneously present + */ + + if (mNPer > mNMod) + { + noise *= 0.5f; + } + + /* Compute frication noise */ + sourc = frics = mAmpFrica * noise; + + /* Compute voicing waveform : (run glottal source simulation at + 4 times normal sample rate to minimize quantization noise in + period of female voice) + */ + + for (n4 = 0; n4 < 4; n4++) + { + /* use a more-natural-shaped source waveform with excitation + occurring both upon opening and upon closure, stronest at closure */ + voice = natural_source(mNPer); + + /* Reset period when counter 'mNPer' reaches mT0 */ + + if (mNPer >= mT0) + { + mNPer = 0; + pitch_synch_par_reset(ns); + } + + /* Low-pass filter voicing waveform before downsampling from 4*globals->mSampleRate */ + /* to globals->mSampleRate samples/sec. Resonator f=.09*globals->mSampleRate, bw=.06*globals->mSampleRate */ + + voice = mDownSampLowPassFilter.resonate(voice); /* in=voice, out=voice */ + + /* Increment counter that keeps track of 4*globals->mSampleRate samples/sec */ + mNPer++; + } + + /* Tilt spectrum of voicing source down by soft ELM_FEATURE_LOW-pass filtering, amount + of tilt determined by mVoicingSpectralTiltdb + */ + voice = (voice * mOneMd) + (mVLast * mDecay); + + mVLast = voice; + + /* Add breathiness during glottal open phase */ + if (mNPer < mNOpen) + { + /* Amount of breathiness determined by parameter mVoicingBreathiness */ + /* Use nrand rather than noise because noise is ELM_FEATURE_LOW-passed */ + voice += mAmpBreth * nrand; + } + + /* Set amplitude of voicing */ + glotout = mAmpVoice * voice; + + /* Compute aspiration amplitude and add to voicing source */ + aspiration = mAmpAspir * noise; + + glotout += aspiration; + + par_glotout = glotout; + + /* NIS - rsynth "hack" + As Holmes' scheme is weak at nasals and (physically) nasal cavity + is "back near glottis" feed glottal source through nasal resonators + Don't think this is quite right, but improves things a bit + */ + par_glotout = mNasalZero.antiresonate(par_glotout); + par_glotout = mNasalPole.resonate(par_glotout); + /* And just use mParallelFormant1 NOT mParallelResoNasalPole */ + float out = mParallelFormant1.resonate(par_glotout); + /* Sound sourc for other parallel resonators is frication + plus first difference of voicing waveform. + */ + sourc += (par_glotout - mGlotLast); + mGlotLast = par_glotout; + + /* Standard parallel vocal tract + Formants F6,F5,F4,F3,F2, outputs added with alternating sign + */ + out = mParallelFormant6.resonate(sourc) - out; + out = mParallelFormant5.resonate(sourc) - out; + out = mParallelFormant4.resonate(sourc) - out; + out = mParallelFormant3.resonate(sourc) - out; + out = mParallelFormant2.resonate(sourc) - out; + + out = mAmpBypas * sourc - out; + out = mOutputLowPassFilter.resonate(out); + + *jwave++ = clip(out); /* Convert back to integer */ + } +} + + + +static char * phoneme_to_element_lookup(char *s, void ** data) +{ + int key8 = *s; + int key16 = key8 + (s[1] << 8); + if (s[1] == 0) key16 = -1; // avoid key8==key16 + int i; + for (i = 0; i < PHONEME_COUNT; i++) + { + if (phoneme_to_elements[i].mKey == key16) + { + *data = &phoneme_to_elements[i].mData; + return s+2; + } + if (phoneme_to_elements[i].mKey == key8) + { + *data = &phoneme_to_elements[i].mData; + return s+1; + } + } + // should never happen + *data = NULL; + return s+1; +} + + + +int klatt::phone_to_elm(char *aPhoneme, int aCount, darray *aElement) +{ + int stress = 0; + char *s = aPhoneme; + int t = 0; + char *limit = s + aCount; + + while (s < limit && *s) + { + char *e = NULL; + s = phoneme_to_element_lookup(s, (void**)&e); + + if (e) + { + int n = *e++; + + while (n-- > 0) + { + int x = *e++; + Element * p = &gElement[x]; + /* This works because only vowels have mUD != mDU, + and we set stress just before a vowel + */ + aElement->put(x); + + if (!(p->mFeat & ELM_FEATURE_VWL)) + stress = 0; + + int stressdur = StressDur(p,stress); + + t += stressdur; + + aElement->put(stressdur); + aElement->put(stress); + } + } + + else + { + char ch = *s++; + + switch (ch) + { + + case '\'': /* Primary stress */ + stress = 3; + break; + + case ',': /* Secondary stress */ + stress = 2; + break; + + case '+': /* Tertiary stress */ + stress = 1; + break; + + case '-': /* hyphen in input */ + break; + + default: +// fprintf(stderr, "Ignoring %c in '%.*s'\n", ch, aCount, aPhoneme); + break; + } + } + } + + return t; +} + + + +/* 'a' is dominant element, 'b' is dominated + ext is flag to say to use external times from 'a' rather + than internal i.e. ext != 0 if 'a' is NOT current element. + */ + +static void set_trans(Slope *t, Element * a, Element * b,int ext, int e) +{ + int i; + + for (i = 0; i < ELM_COUNT; i++) + { + t[i].mTime = ((ext) ? a->mInterpolator[i].mExtDelay : a->mInterpolator[i].mIntDelay); + + if (t[i].mTime) + { + t[i].mValue = a->mInterpolator[i].mFixed + (a->mInterpolator[i].mProportion * b->mInterpolator[i].mSteady) * 0.01f; // mProportion is in scale 0..100, so *0.01. + } + else + { + t[i].mValue = b->mInterpolator[i].mSteady; + } + } +} + +static float lerp(float a, float b, int t, int d) +{ + if (t <= 0) + { + return a; + } + + if (t >= d) + { + return b; + } + + float f = (float)t / (float)d; + return a + (b - a) * f; +} + +static float interpolate(Slope *aStartSlope, Slope *aEndSlope, float aMidValue, int aTime, int aDuration) +{ + int steadyTime = aDuration - (aStartSlope->mTime + aEndSlope->mTime); + + if (steadyTime >= 0) + { + // Interpolate to a midpoint, stay there for a while, then interpolate to end + + if (aTime < aStartSlope->mTime) + { + // interpolate to the first value + return lerp(aStartSlope->mValue, aMidValue, aTime, aStartSlope->mTime); + } + // reached midpoint + + aTime -= aStartSlope->mTime; + + if (aTime <= steadyTime) + { + // still at steady state + return aMidValue; + } + + // interpolate to the end + return lerp(aMidValue, aEndSlope->mValue, aTime - steadyTime, aEndSlope->mTime); + } + else + { + // No steady state + float f = 1.0f - ((float) aTime / (float) aDuration); + float sp = lerp(aStartSlope->mValue, aMidValue, aTime, aStartSlope->mTime); + float ep = lerp(aEndSlope->mValue, aMidValue, aDuration - aTime, aEndSlope->mTime); + return f * sp + ((float) 1.0 - f) * ep; + } +} + + + +void klatt::initsynth(int aElementCount,unsigned char *aElement) +{ + mElement = aElement; + mElementCount = aElementCount; + mElementIndex = 0; + mLastElement = &gElement[0]; + mSeed = 5; + mTStress = 0; + mNTStress = 0; + mFrame.mF0FundamentalFreq = mBaseF0; + mTop = 1.1f * mFrame.mF0FundamentalFreq; + mFrame.mNasalPoleFreq = (int)mLastElement->mInterpolator[ELM_FN].mSteady; + mFrame.mFormant1ParallelBandwidth = mFrame.mFormant1Bandwidth = 60; + mFrame.mFormant2ParallelBandwidth = mFrame.mFormant2Bandwidth = 90; + mFrame.mFormant3ParallelBandwidth = mFrame.mFormant3Bandwidth = 150; +// mFrame.mFormant4ParallelBandwidth = (default) + + // Set stress attack/decay slope + mStressS.mTime = 40; + mStressE.mTime = 40; + mStressE.mValue = 0.0; +} + +int klatt::synth(int aSampleCount, short *aSamplePointer) +{ + short *samp = aSamplePointer; + + if (mElementIndex >= mElementCount) + return -1; + + Element * currentElement = &gElement[mElement[mElementIndex++]]; + int dur = mElement[mElementIndex++]; + mElementIndex++; // skip stress + + if (currentElement->mRK == 31) // "END" + { + // Reset the fundamental frequency top + mFrame.mF0FundamentalFreq = mBaseF0; + mTop = 1.1f * mFrame.mF0FundamentalFreq; + } + + // Skip zero length elements which are only there to affect + // boundary values of adjacent elements + + if (dur > 0) + { + Element * ne = (mElementIndex < mElementCount) ? &gElement[mElement[mElementIndex]] : &gElement[0]; + Slope start[ELM_COUNT]; + Slope end[ELM_COUNT]; + int t; + + if (currentElement->mRK > mLastElement->mRK) + { + set_trans(start, currentElement, mLastElement, 0, 's'); + // we dominate last + } + else + { + set_trans(start, mLastElement, currentElement, 1, 's'); + // last dominates us + } + + if (ne->mRK > currentElement->mRK) + { + set_trans(end, ne, currentElement, 1, 'e'); + // next dominates us + } + else + { + set_trans(end, currentElement, ne, 0, 'e'); + // we dominate next + } + + for (t = 0; t < dur; t++, mTStress++) + { + float base = mTop * 0.8f; // 3 * top / 5 + float tp[ELM_COUNT]; + int j; + + if (mTStress == mNTStress) + { + int j = mElementIndex; + mStressS = mStressE; + mTStress = 0; + mNTStress = dur; + + while (j <= mElementCount) + { + Element * e = (j < mElementCount) ? &gElement[mElement[j++]] : &gElement[0]; + int du = (j < mElementCount) ? mElement[j++] : 0; + int s = (j < mElementCount) ? mElement[j++] : 3; + + if (s || e->mFeat & ELM_FEATURE_VWL) + { + int d = 0; + + if (s) + mStressE.mValue = (float) s / 3; + else + mStressE.mValue = (float) 0.1; + + do + { + d += du; + e = (j < mElementCount) ? &gElement[mElement[j++]] : &gElement[0]; + du = mElement[j++]; + } + + while ((e->mFeat & ELM_FEATURE_VWL) && mElement[j++] == s); + + mNTStress += d / 2; + + break; + } + + mNTStress += du; + } + } + + for (j = 0; j < ELM_COUNT; j++) + { + tp[j] = interpolate(&start[j], &end[j], (float) currentElement->mInterpolator[j].mSteady, t, dur); + } + + // Now call the synth for each frame + + mFrame.mF0FundamentalFreq = (int)(base + (mTop - base) * interpolate(&mStressS, &mStressE, (float)0, mTStress, mNTStress)); + mFrame.mVoicingAmpdb = mFrame.mPalallelVoicingAmpdb = (int)tp[ELM_AV]; + mFrame.mFricationAmpdb = (int)tp[ELM_AF]; + mFrame.mNasalZeroFreq = (int)tp[ELM_FN]; + mFrame.mAspirationAmpdb = (int)tp[ELM_ASP]; + mFrame.mVoicingBreathiness = (int)tp[ELM_AVC]; + mFrame.mFormant1ParallelBandwidth = mFrame.mFormant1Bandwidth = (int)tp[ELM_B1]; + mFrame.mFormant2ParallelBandwidth = mFrame.mFormant2Bandwidth = (int)tp[ELM_B2]; + mFrame.mFormant3ParallelBandwidth = mFrame.mFormant3Bandwidth = (int)tp[ELM_B3]; + mFrame.mFormant1Freq = (int)tp[ELM_F1]; + mFrame.mFormant2Freq = (int)tp[ELM_F2]; + mFrame.mFormant3Freq = (int)tp[ELM_F3]; + + // AMP_ADJ + is a kludge to get amplitudes up to klatt-compatible levels + + + //pars.mParallelNasalPoleAmpdb = AMP_ADJ + tp[ELM_AN]; + + mFrame.mBypassFricationAmpdb = AMP_ADJ + (int)tp[ELM_AB]; + mFrame.mFormant5Ampdb = AMP_ADJ + (int)tp[ELM_A5]; + mFrame.mFormant6Ampdb = AMP_ADJ + (int)tp[ELM_A6]; + mFrame.mFormant1Ampdb = AMP_ADJ + (int)tp[ELM_A1]; + mFrame.mFormant2Ampdb = AMP_ADJ + (int)tp[ELM_A2]; + mFrame.mFormant3Ampdb = AMP_ADJ + (int)tp[ELM_A3]; + mFrame.mFormant4Ampdb = AMP_ADJ + (int)tp[ELM_A4]; + + parwave(samp); + + samp += mNspFr; + + // Declination of f0 envelope 0.25Hz / cS + mTop -= mBaseDeclination;// 0.5; + } + } + + mLastElement = currentElement; + + return (int)(samp - aSamplePointer); +} + + +void klatt::init(int aBaseFrequency, float aBaseSpeed, float aBaseDeclination, int aBaseWaveform) +{ + mBaseF0 = aBaseFrequency; + mBaseSpeed = aBaseSpeed; + mBaseDeclination = aBaseDeclination; + mBaseWaveform = aBaseWaveform; + + mSampleRate = 11025; + mF0Flutter = 0; + mF0FundamentalFreq = mBaseF0; + mFrame.mF0FundamentalFreq = mBaseF0; + + int FLPhz = (950 * mSampleRate) / 10000; + int BLPhz = (630 * mSampleRate) / 10000; + mNspFr = (int)(mSampleRate * mBaseSpeed) / 1000; + + mDownSampLowPassFilter.initResonator(FLPhz, BLPhz, mSampleRate); + + mNPer = 0; /* LG */ + mT0 = 0; /* LG */ + + mVLast = 0; /* Previous output of voice */ + mNLast = 0; /* Previous output of random number generator */ + mGlotLast = 0; /* Previous value of glotout */ +} diff --git a/src/soloud/src/audiosource/speech/klatt.h b/src/soloud/src/audiosource/speech/klatt.h new file mode 100644 index 0000000..6621090 --- /dev/null +++ b/src/soloud/src/audiosource/speech/klatt.h @@ -0,0 +1,153 @@ +#ifndef KLATT_H +#define KLATT_H + +#include "resonator.h" + +#define CASCADE_PARALLEL 1 +#define ALL_PARALLEL 2 +#define NPAR 40 + +class klatt_frame +{ +public: + int mF0FundamentalFreq; // Voicing fund freq in Hz + int mVoicingAmpdb; // Amp of voicing in dB, 0 to 70 + int mFormant1Freq; // First formant freq in Hz, 200 to 1300 + int mFormant1Bandwidth; // First formant bw in Hz, 40 to 1000 + int mFormant2Freq; // Second formant freq in Hz, 550 to 3000 + int mFormant2Bandwidth; // Second formant bw in Hz, 40 to 1000 + int mFormant3Freq; // Third formant freq in Hz, 1200 to 4999 + int mFormant3Bandwidth; // Third formant bw in Hz, 40 to 1000 + int mFormant4Freq; // Fourth formant freq in Hz, 1200 to 4999 + int mFormant4Bandwidth; // Fourth formant bw in Hz, 40 to 1000 + int mFormant5Freq; // Fifth formant freq in Hz, 1200 to 4999 + int mFormant5Bandwidth; // Fifth formant bw in Hz, 40 to 1000 + int mFormant6Freq; // Sixth formant freq in Hz, 1200 to 4999 + int mFormant6Bandwidth; // Sixth formant bw in Hz, 40 to 2000 + int mNasalZeroFreq; // Nasal zero freq in Hz, 248 to 528 + int mNasalZeroBandwidth; // Nasal zero bw in Hz, 40 to 1000 + int mNasalPoleFreq; // Nasal pole freq in Hz, 248 to 528 + int mNasalPoleBandwidth; // Nasal pole bw in Hz, 40 to 1000 + int mAspirationAmpdb; // Amp of aspiration in dB, 0 to 70 + int mNoSamplesInOpenPeriod; // # of samples in open period, 10 to 65 + int mVoicingBreathiness; // Breathiness in voicing, 0 to 80 + int mVoicingSpectralTiltdb; // Voicing spectral tilt in dB, 0 to 24 + int mFricationAmpdb; // Amp of frication in dB, 0 to 80 + int mSkewnessOfAlternatePeriods; // Skewness of alternate periods, 0 to 40 in sample#/2 + int mFormant1Ampdb; // Amp of par 1st formant in dB, 0 to 80 + int mFormant1ParallelBandwidth; // Par. 1st formant bw in Hz, 40 to 1000 + int mFormant2Ampdb; // Amp of F2 frication in dB, 0 to 80 + int mFormant2ParallelBandwidth; // Par. 2nd formant bw in Hz, 40 to 1000 + int mFormant3Ampdb; // Amp of F3 frication in dB, 0 to 80 + int mFormant3ParallelBandwidth; // Par. 3rd formant bw in Hz, 40 to 1000 + int mFormant4Ampdb; // Amp of F4 frication in dB, 0 to 80 + int mFormant4ParallelBandwidth; // Par. 4th formant bw in Hz, 40 to 1000 + int mFormant5Ampdb; // Amp of F5 frication in dB, 0 to 80 + int mFormant5ParallelBandwidth; // Par. 5th formant bw in Hz, 40 to 1000 + int mFormant6Ampdb; // Amp of F6 (same as r6pa), 0 to 80 + int mFormant6ParallelBandwidth; // Par. 6th formant bw in Hz, 40 to 2000 + int mParallelNasalPoleAmpdb; // Amp of par nasal pole in dB, 0 to 80 + int mBypassFricationAmpdb; // Amp of bypass fric. in dB, 0 to 80 + int mPalallelVoicingAmpdb; // Amp of voicing, par in dB, 0 to 70 + int mOverallGaindb; // Overall gain, 60 dB is unity, 0 to 60 + klatt_frame(); +}; + +class darray; +class Element; + +class Slope +{ +public: + float mValue; /* boundary value */ + int mTime; /* transition time */ + Slope() + { + mValue = 0; + mTime = 0; + } +}; + + +enum KLATT_WAVEFORM +{ + KW_SAW, + KW_TRIANGLE, + KW_SIN, + KW_SQUARE, + KW_PULSE, + KW_NOISE, + KW_WARBLE +}; + +class klatt +{ + // resonators + resonator mParallelFormant1, mParallelFormant2, mParallelFormant3, + mParallelFormant4, mParallelFormant5, mParallelFormant6, + mParallelResoNasalPole, mNasalPole, mNasalZero, + mCritDampedGlotLowPassFilter, mDownSampLowPassFilter, mOutputLowPassFilter; +public: + int mBaseF0; + float mBaseSpeed; + float mBaseDeclination; + int mBaseWaveform; + + int mF0Flutter; + int mSampleRate; + int mNspFr; + int mF0FundamentalFreq; // Voicing fund freq in Hz + int mVoicingAmpdb; // Amp of voicing in dB, 0 to 70 + int mSkewnessOfAlternatePeriods; // Skewness of alternate periods,0 to 40 + int mTimeCount; // used for f0 flutter + int mNPer; // Current loc in voicing period 40000 samp/s + int mT0; // Fundamental period in output samples times 4 + int mNOpen; // Number of samples in open phase of period + int mNMod; // Position in period to begin noise amp. modul + + // Various amplitude variables used in main loop + + float mAmpVoice; // mVoicingAmpdb converted to linear gain + float mAmpBypas; // mBypassFricationAmpdb converted to linear gain + float mAmpAspir; // AP converted to linear gain + float mAmpFrica; // mFricationAmpdb converted to linear gain + float mAmpBreth; // ATURB converted to linear gain + + // State variables of sound sources + + int mSkew; // Alternating jitter, in half-period units + float mVLast; // Previous output of voice + float mNLast; // Previous output of random number generator + float mGlotLast; // Previous value of glotout + float mDecay; // mVoicingSpectralTiltdb converted to exponential time const + float mOneMd; // in voicing one-pole ELM_FEATURE_LOW-pass filter + + unsigned int mSeed; // random seed + + + + float natural_source(int aNper); + + void frame_init(); + void flutter(); + void pitch_synch_par_reset(int ns); + void parwave(short int *jwave); + void init(int aBaseFrequency = 1330, float aBaseSpeed = 10.0f, float aBaseDeclination = 0.5f, int aBaseWaveform = KW_SAW); + static int phone_to_elm(char *aPhoneme, int aCount, darray *aElement); + + int mElementCount; + unsigned char *mElement; + int mElementIndex; + klatt_frame mFrame; + Element * mLastElement; + int mTStress; + int mNTStress; + Slope mStressS; + Slope mStressE; + float mTop; + void initsynth(int aElementCount,unsigned char *aElement); + int synth(int aSampleCount, short *aSamplePointer); + klatt(); +}; + +#endif \ No newline at end of file diff --git a/src/soloud/src/audiosource/speech/legal_readme.txt b/src/soloud/src/audiosource/speech/legal_readme.txt new file mode 100644 index 0000000..878ec6a --- /dev/null +++ b/src/soloud/src/audiosource/speech/legal_readme.txt @@ -0,0 +1,38 @@ +The speech synth is based on rsynth by the late +Nick Ing-Simmons (et al). + +He described the legal status as: + + This is a text to speech system produced by + integrating various pieces of code and tables + of data, which are all (I believe) in the + public domain. + +Since then, the rsynth source code has passed legal +checks by several open source organizations, so it +"should" be pretty safe. + +The primary copyright claims seem to have to do +with text-to-speech dictionary use, which I've +removed completely. + +I've done some serious refactoring, clean-up and +feature removal on the source, as all I need is +"a" free, simple speech synth, not a "good" +speech synth. Since I've removed a bunch of stuff, +this is probably safer public domain release +than the original. + +(I'm rather surprised there's no good public domain +speech synths out there; after all, it's 2013..) + +I'm placing my changes in public domain as well, +or if that's not acceptable for you, then CC0: +http://creativecommons.org/publicdomain/zero/1.0/ + +The SoLoud interface files (soloud_speech.*) are +under ZLib/LibPNG license. + +-- Jari Komppa + 2013 + \ No newline at end of file diff --git a/src/soloud/src/audiosource/speech/resonator.cpp b/src/soloud/src/audiosource/speech/resonator.cpp new file mode 100644 index 0000000..6214444 --- /dev/null +++ b/src/soloud/src/audiosource/speech/resonator.cpp @@ -0,0 +1,74 @@ +#include +#include "resonator.h" + +#ifndef PI +#define PI 3.1415926535897932384626433832795f +#endif + +/* Convert formant freqencies and bandwidth into resonator difference equation coefficents + */ +void resonator::initResonator( + int aFrequency, /* Frequency of resonator in Hz */ + int aBandwidth, /* Bandwidth of resonator in Hz */ + int aSamplerate) +{ + float arg = (-PI / aSamplerate) * aBandwidth; + float r = (float)exp(arg); + mC = -(r * r); + arg = (-2.0f * PI / aSamplerate) * aFrequency; + mB = r * (float)cos(arg) * 2.0f; + mA = 1.0f - mB - mC; +} + +/* Convert formant freqencies and bandwidth into anti-resonator difference equation constants + */ +void resonator::initAntiresonator( + int aFrequency, /* Frequency of resonator in Hz */ + int aBandwidth, /* Bandwidth of resonator in Hz */ + int aSamplerate) +{ + initResonator(aFrequency, aBandwidth, aSamplerate); /* First compute ordinary resonator coefficients */ + /* Now convert to antiresonator coefficients */ + mA = 1.0f / mA; /* a'= 1/a */ + mB *= -mA; /* b'= -b/a */ + mC *= -mA; /* c'= -c/a */ +} + +/* Generic resonator function */ +float resonator::resonate(float input) +{ + float x = mA * input + mB * mP1 + mC * mP2; + mP2 = mP1; + mP1 = x; + return x; +} + +/* Generic anti-resonator function + Same as resonator except that a,b,c need to be set with initAntiresonator() + and we save inputs in p1/p2 rather than outputs. + There is currently only one of these - "mNasalZero" +*/ +/* Output = (mNasalZero.a * input) + (mNasalZero.b * oldin1) + (mNasalZero.c * oldin2) */ + +float resonator::antiresonate(float input) +{ + float x = mA * input + mB * mP1 + mC * mP2; + mP2 = mP1; + mP1 = input; + return x; +} + +resonator::resonator() +{ + mA = mB = mC = mP1 = mP2 = 0; +} + +resonator::~resonator() +{ +} + +void resonator::setGain(float aG) +{ + mA *= aG; +} + diff --git a/src/soloud/src/audiosource/speech/resonator.h b/src/soloud/src/audiosource/speech/resonator.h new file mode 100644 index 0000000..2e75e31 --- /dev/null +++ b/src/soloud/src/audiosource/speech/resonator.h @@ -0,0 +1,44 @@ +#ifndef RESONATOR_H +#define RESONATOR_H + +class resonator +{ + float mA, mB, mC, mP1, mP2; +public: + + /* Convert formant freqencies and bandwidth into resonator difference equation coefficents + */ + void initResonator( + int aFrequency, /* Frequency of resonator in Hz */ + int aBandwidth, /* Bandwidth of resonator in Hz */ + int aSamplerate); + + /* Convert formant freqencies and bandwidth into anti-resonator difference equation constants + */ + void initAntiresonator( + int aFrequency, /* Frequency of resonator in Hz */ + int aBandwidth, /* Bandwidth of resonator in Hz */ + int aSamplerate); + + /* Set gain */ + void setGain(float aG); + + /* Generic resonator function */ + float resonate(float input); + + /* Generic anti-resonator function + Same as resonator except that a,b,c need to be set with initAntiresonator() + and we save inputs in p1/p2 rather than outputs. + There is currently only one of these - "mNasalZero" + + Output = (mNasalZero.a * input) + (mNasalZero.b * oldin1) + (mNasalZero.c * oldin2) + */ + + float antiresonate(float input); + + resonator(); + + ~resonator(); +}; + +#endif \ No newline at end of file diff --git a/src/soloud/src/audiosource/speech/soloud_speech.cpp b/src/soloud/src/audiosource/speech/soloud_speech.cpp new file mode 100644 index 0000000..df3889e --- /dev/null +++ b/src/soloud/src/audiosource/speech/soloud_speech.cpp @@ -0,0 +1,148 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include +#include "soloud.h" +#include "soloud_speech.h" + + +namespace SoLoud +{ + SpeechInstance::SpeechInstance(Speech *aParent) + { + mParent = aParent; + mSynth.init(mParent->mBaseFrequency, mParent->mBaseSpeed, mParent->mBaseDeclination, mParent->mBaseWaveform); + mSample = new short[mSynth.mNspFr * 100]; + mSynth.initsynth(mParent->mElement.getSize(), (unsigned char *)mParent->mElement.getData()); + mOffset = 10; + mSampleCount = 10; + } + + SpeechInstance::~SpeechInstance() + { + delete[] mSample; + } + + static void writesamples(short * aSrc, float * aDst, int aCount) + { + int i; + for (i = 0; i < aCount; i++) + { + aDst[i] = aSrc[i] * (1 / (float)0x8000); + } + } + + unsigned int SpeechInstance::getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize) + { + mSynth.init(mParent->mBaseFrequency, mParent->mBaseSpeed, mParent->mBaseDeclination, mParent->mBaseWaveform); + unsigned int samples_out = 0; + if (mSampleCount > mOffset) + { + unsigned int copycount = mSampleCount - mOffset; + if (copycount > aSamplesToRead) + { + copycount = aSamplesToRead; + } + writesamples(mSample + mOffset, aBuffer, copycount); + mOffset += copycount; + samples_out += copycount; + } + + while (mSampleCount >= 0 && samples_out < aSamplesToRead) + { + mOffset = 0; + mSampleCount = mSynth.synth(mSynth.mNspFr, mSample); + if (mSampleCount > 0) + { + unsigned int copycount = mSampleCount; + if (copycount > aSamplesToRead - samples_out) + { + copycount = aSamplesToRead - samples_out; + } + writesamples(mSample, aBuffer + samples_out, copycount); + mOffset += copycount; + samples_out += copycount; + } + } + return samples_out; + } + + result SpeechInstance::rewind() + { + mSynth.init(mParent->mBaseFrequency, mParent->mBaseSpeed, mParent->mBaseDeclination, mParent->mBaseWaveform); + mSynth.initsynth(mParent->mElement.getSize(), (unsigned char *)mParent->mElement.getData()); + mOffset = 10; + mSampleCount = 10; + mStreamPosition = 0.0f; + return 0; + } + + bool SpeechInstance::hasEnded() + { + if (mSampleCount < 0) + return 1; + return 0; + } + + result Speech::setParams(unsigned int aBaseFrequency, float aBaseSpeed, float aBaseDeclination, int aBaseWaveform) + { + mBaseFrequency = aBaseFrequency; + mBaseSpeed = aBaseSpeed; + mBaseDeclination = aBaseDeclination; + mBaseWaveform = aBaseWaveform; + return 0; + } + + result Speech::setText(const char *aText) + { + if (aText == NULL) + return INVALID_PARAMETER; + + stop(); + mElement.clear(); + darray phone; + xlate_string(aText, &phone); + mFrames = klatt::phone_to_elm(phone.getData(), phone.getSize(), &mElement); + return 0; + } + + Speech::Speech() + { + mBaseSamplerate = 11025; + mFrames = 0; + mBaseFrequency = 1330; + mBaseSpeed = 10; + mBaseDeclination = 0.5f; + mBaseWaveform = KW_SQUARE; + } + + Speech::~Speech() + { + stop(); + } + + AudioSourceInstance *Speech::createInstance() + { + return new SpeechInstance(this); + } +}; diff --git a/src/soloud/src/audiosource/speech/tts.cpp b/src/soloud/src/audiosource/speech/tts.cpp new file mode 100644 index 0000000..e65082a --- /dev/null +++ b/src/soloud/src/audiosource/speech/tts.cpp @@ -0,0 +1,1421 @@ +#include +#include +#include +#include +#include "darray.h" +#include "tts.h" + +static const char *ASCII[] = +{ + "null", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "space", "exclamation mark", "double quote", "hash", + "dollar", "percent", "ampersand", "quote", + "open parenthesis", "close parenthesis", "asterisk", "plus", + "comma", "minus", "full stop", "slash", + "zero", "one", "two", "three", + "four", "five", "six", "seven", + "eight", "nine", "colon", "semi colon", + "less than", "equals", "greater than", "question mark", +#ifndef ALPHA_IN_DICT + "at", "ay", "bee", "see", + "dee", "e", "eff", "gee", + "aych", "i", "jay", "kay", + "ell", "em", "en", "ohe", + "pee", "kju", "are", "es", + "tee", "you", "vee", "double you", + "eks", "why", "zed", "open bracket", +#else /* ALPHA_IN_DICT */ + "at", "A", "B", "C", + "D", "E", "F", "G", + "H", "I", "J", "K", + "L", "M", "N", "O", + "P", "Q", "R", "S", + "T", "U", "V", "W", + "X", "Y", "Z", "open bracket", +#endif /* ALPHA_IN_DICT */ + "back slash", "close bracket", "circumflex", "underscore", +#ifndef ALPHA_IN_DICT + "back quote", "ay", "bee", "see", + "dee", "e", "eff", "gee", + "aych", "i", "jay", "kay", + "ell", "em", "en", "ohe", + "pee", "kju", "are", "es", + "tee", "you", "vee", "double you", + "eks", "why", "zed", "open brace", +#else /* ALPHA_IN_DICT */ + "back quote", "A", "B", "C", + "D", "E", "F", "G", + "H", "I", "J", "K", + "L", "M", "N", "O", + "P", "Q", "R", "S", + "T", "U", "V", "W", + "X", "Y", "Z", "open brace", +#endif /* ALPHA_IN_DICT */ + "vertical bar", "close brace", "tilde", "delete", + NULL +}; + +/* Context definitions */ +static const char Anything[] = ""; +/* No context requirement */ + +static const char Nothing[] = " "; +/* Context is beginning or end of word */ + +static const char Silent[] = ""; +/* No phonemes */ + + +#define LEFT_PART 0 +#define MATCH_PART 1 +#define RIGHT_PART 2 +#define OUT_PART 3 + +typedef const char *Rule[4]; +/* Rule is an array of 4 character pointers */ + + +/*0 = Punctuation */ +/* +** LEFT_PART MATCH_PART RIGHT_PART OUT_PART +*/ + + +static Rule punct_rules[] = +{ + {Anything, " ", Anything, " "}, + {Anything, "-", Anything, ""}, + {".", "'S", Anything, "z"}, + {"#:.E", "'S", Anything, "z"}, + {"#", "'S", Anything, "z"}, + {Anything, "'", Anything, ""}, + {Anything, ",", Anything, " "}, + {Anything, ".", Anything, " "}, + {Anything, "?", Anything, " "}, + {Anything, "!", Anything, " "}, + {Anything, 0, Anything, Silent}, +}; + +static Rule A_rules[] = +{ + {Anything, "A", Nothing, "@"}, + {Nothing, "ARE", Nothing, "0r"}, + {Nothing, "AR", "O", "@r"}, + {Anything, "AR", "#", "er"}, + {"^", "AS", "#", "eIs"}, + {Anything, "A", "WA", "@"}, + {Anything, "AW", Anything, "O"}, + {" :", "ANY", Anything, "eni"}, + {Anything, "A", "^+#", "eI"}, + {"#:", "ALLY", Anything, "@li"}, + {Nothing, "AL", "#", "@l"}, + {Anything, "AGAIN", Anything, "@gen"}, + {"#:", "AG", "E", "IdZ"}, + {Anything, "A", "^+:#", "&"}, + {" :", "A", "^+ ", "eI"}, + {Anything, "A", "^%", "eI"}, + {Nothing, "ARR", Anything, "@r"}, + {Anything, "ARR", Anything, "&r"}, + {" :", "AR", Nothing, "0r"}, + {Anything, "AR", Nothing, "3"}, + {Anything, "AR", Anything, "0r"}, + {Anything, "AIR", Anything, "er"}, + {Anything, "AI", Anything, "eI"}, + {Anything, "AY", Anything, "eI"}, + {Anything, "AU", Anything, "O"}, + {"#:", "AL", Nothing, "@l"}, + {"#:", "ALS", Nothing, "@lz"}, + {Anything, "ALK", Anything, "Ok"}, + {Anything, "AL", "^", "Ol"}, + {" :", "ABLE", Anything, "eIb@l"}, + {Anything, "ABLE", Anything, "@b@l"}, + {Anything, "ANG", "+", "eIndZ"}, + {"^", "A", "^#", "eI"}, + {Anything, "A", Anything, "&"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule B_rules[] = +{ + {Nothing, "BE", "^#", "bI"}, + {Anything, "BEING", Anything, "biIN"}, + {Nothing, "BOTH", Nothing, "b@UT"}, + {Nothing, "BUS", "#", "bIz"}, + {Anything, "BUIL", Anything, "bIl"}, + {Anything, "B", Anything, "b"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule C_rules[] = +{ + {Nothing, "CH", "^", "k"}, + {"^E", "CH", Anything, "k"}, + {Anything, "CH", Anything, "tS"}, + {" S", "CI", "#", "saI"}, + {Anything, "CI", "A", "S"}, + {Anything, "CI", "O", "S"}, + {Anything, "CI", "EN", "S"}, + {Anything, "C", "+", "s"}, + {Anything, "CK", Anything, "k"}, + {Anything, "COM", "%", "kVm"}, + {Anything, "C", Anything, "k"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule D_rules[] = +{ + {"#:", "DED", Nothing, "dId"}, + {".E", "D", Nothing, "d"}, + {"#:^E", "D", Nothing, "t"}, + {Nothing, "DE", "^#", "dI"}, + {Nothing, "DO", Nothing, "mDU"}, + {Nothing, "DOES", Anything, "dVz"}, + {Nothing, "DOING", Anything, "duIN"}, + {Nothing, "DOW", Anything, "daU"}, + {Anything, "DU", "A", "dZu"}, + {Anything, "D", Anything, "d"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule E_rules[] = +{ + {"#:", "E", Nothing, ""}, + {"':^", "E", Nothing, ""}, + {" :", "E", Nothing, "i"}, + {"#", "ED", Nothing, "d"}, + {"#:", "E", "D ", ""}, + {Anything, "EV", "ER", "ev"}, + {Anything, "E", "^%", "i"}, + {Anything, "ERI", "#", "iri"}, + {Anything, "ERI", Anything, "erI"}, + {"#:", "ER", "#", "3"}, + {Anything, "ER", "#", "er"}, + {Anything, "ER", Anything, "3"}, + {Nothing, "EVEN", Anything, "iven"}, + {"#:", "E", "W", ""}, + {"T", "EW", Anything, "u"}, + {"S", "EW", Anything, "u"}, + {"R", "EW", Anything, "u"}, + {"D", "EW", Anything, "u"}, + {"L", "EW", Anything, "u"}, + {"Z", "EW", Anything, "u"}, + {"N", "EW", Anything, "u"}, + {"J", "EW", Anything, "u"}, + {"TH", "EW", Anything, "u"}, + {"CH", "EW", Anything, "u"}, + {"SH", "EW", Anything, "u"}, + {Anything, "EW", Anything, "ju"}, + {Anything, "E", "O", "i"}, + {"#:S", "ES", Nothing, "Iz"}, + {"#:C", "ES", Nothing, "Iz"}, + {"#:G", "ES", Nothing, "Iz"}, + {"#:Z", "ES", Nothing, "Iz"}, + {"#:X", "ES", Nothing, "Iz"}, + {"#:J", "ES", Nothing, "Iz"}, + {"#:CH", "ES", Nothing, "Iz"}, + {"#:SH", "ES", Nothing, "Iz"}, + {"#:", "E", "S ", ""}, + {"#:", "ELY", Nothing, "li"}, + {"#:", "EMENT", Anything, "ment"}, + {Anything, "EFUL", Anything, "fUl"}, + {Anything, "EE", Anything, "i"}, + {Anything, "EARN", Anything, "3n"}, + {Nothing, "EAR", "^", "3"}, + {Anything, "EAD", Anything, "ed"}, + {"#:", "EA", Nothing, "i@"}, + {Anything, "EA", "SU", "e"}, + {Anything, "EA", Anything, "i"}, + {Anything, "EIGH", Anything, "eI"}, + {Anything, "EI", Anything, "i"}, + {Nothing, "EYE", Anything, "aI"}, + {Anything, "EY", Anything, "i"}, + {Anything, "EU", Anything, "ju"}, + {Anything, "E", Anything, "e"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule F_rules[] = +{ + {Anything, "FUL", Anything, "fUl"}, + {Anything, "F", Anything, "f"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule G_rules[] = +{ + {Anything, "GIV", Anything, "gIv"}, + {Nothing, "G", "I^", "g"}, + {Anything, "GE", "T", "ge"}, + {"SU", "GGES", Anything, "gdZes"}, + {Anything, "GG", Anything, "g"}, + {" B#", "G", Anything, "g"}, + {Anything, "G", "+", "dZ"}, + {Anything, "GREAT", Anything, "greIt"}, + {"#", "GH", Anything, ""}, + {Anything, "G", Anything, "g"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule H_rules[] = +{ + {Nothing, "HAV", Anything, "h&v"}, + {Nothing, "HERE", Anything, "hir"}, + {Nothing, "HOUR", Anything, "aU3"}, + {Anything, "HOW", Anything, "haU"}, + {Anything, "H", "#", "h"}, + {Anything, "H", Anything, ""}, + {Anything, 0, Anything, Silent}, +}; + +static Rule I_rules[] = +{ + {Nothing, "IAIN", Nothing, "I@n"}, + {Nothing, "ING", Nothing, "IN"}, + {Nothing, "IN", Anything, "In"}, + {Nothing, "I", Nothing, "aI"}, + {Anything, "IN", "D", "aIn"}, + {Anything, "IER", Anything, "i3"}, + {"#:R", "IED", Anything, "id"}, + {Anything, "IED", Nothing, "aId"}, + {Anything, "IEN", Anything, "ien"}, + {Anything, "IE", "T", "aIe"}, + {" :", "I", "%", "aI"}, + {Anything, "I", "%", "i"}, + {Anything, "IE", Anything, "i"}, + {Anything, "I", "^+:#", "I"}, + {Anything, "IR", "#", "aIr"}, + {Anything, "IZ", "%", "aIz"}, + {Anything, "IS", "%", "aIz"}, + {Anything, "I", "D%", "aI"}, + {"+^", "I", "^+", "I"}, + {Anything, "I", "T%", "aI"}, + {"#:^", "I", "^+", "I"}, + {Anything, "I", "^+", "aI"}, + {Anything, "IR", Anything, "3"}, + {Anything, "IGH", Anything, "aI"}, + {Anything, "ILD", Anything, "aIld"}, + {Anything, "IGN", Nothing, "aIn"}, + {Anything, "IGN", "^", "aIn"}, + {Anything, "IGN", "%", "aIn"}, + {Anything, "IQUE", Anything, "ik"}, + {"^", "I", "^#", "aI"}, + {Anything, "I", Anything, "I"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule J_rules[] = +{ + {Anything, "J", Anything, "dZ"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule K_rules[] = +{ + {Nothing, "K", "N", ""}, + {Anything, "K", Anything, "k"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule L_rules[] = +{ + {Anything, "LO", "C#", "l@U"}, + {"L", "L", Anything, ""}, + {"#:^", "L", "%", "@l"}, + {Anything, "LEAD", Anything, "lid"}, + {Anything, "L", Anything, "l"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule M_rules[] = +{ + {Anything, "MOV", Anything, "muv"}, + {"#", "MM", "#", "m"}, + {Anything, "M", Anything, "m"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule N_rules[] = +{ + {"E", "NG", "+", "ndZ"}, + {Anything, "NG", "R", "Ng"}, + {Anything, "NG", "#", "Ng"}, + {Anything, "NGL", "%", "Ng@l"}, + {Anything, "NG", Anything, "N"}, + {Anything, "NK", Anything, "Nk"}, + {Nothing, "NOW", Nothing, "naU"}, + {"#", "NG", Nothing, "Ng"}, + {Anything, "N", Anything, "n"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule O_rules[] = +{ + {Anything, "OF", Nothing, "@v"}, + {Anything, "OROUGH", Anything, "3@U"}, + {"#:", "OR", Nothing, "3"}, + {"#:", "ORS", Nothing, "3z"}, + {Anything, "OR", Anything, "Or"}, + {Nothing, "ONE", Anything, "wVn"}, + {Anything, "OW", Anything, "@U"}, + {Nothing, "OVER", Anything, "@Uv3"}, + {Anything, "OV", Anything, "Vv"}, + {Anything, "O", "^%", "@U"}, + {Anything, "O", "^EN", "@U"}, + {Anything, "O", "^I#", "@U"}, + {Anything, "OL", "D", "@Ul"}, + {Anything, "OUGHT", Anything, "Ot"}, + {Anything, "OUGH", Anything, "Vf"}, + {Nothing, "OU", Anything, "aU"}, + {"H", "OU", "S#", "aU"}, + {Anything, "OUS", Anything, "@s"}, + {Anything, "OUR", Anything, "Or"}, + {Anything, "OULD", Anything, "Ud"}, + {"^", "OU", "^L", "V"}, + {Anything, "OUP", Anything, "up"}, + {Anything, "OU", Anything, "aU"}, + {Anything, "OY", Anything, "oI"}, + {Anything, "OING", Anything, "@UIN"}, + {Anything, "OI", Anything, "oI"}, + {Anything, "OOR", Anything, "Or"}, + {Anything, "OOK", Anything, "Uk"}, + {Anything, "OOD", Anything, "Ud"}, + {Anything, "OO", Anything, "u"}, + {Anything, "O", "E", "@U"}, + {Anything, "O", Nothing, "@U"}, + {Anything, "OA", Anything, "@U"}, + {Nothing, "ONLY", Anything, "@Unli"}, + {Nothing, "ONCE", Anything, "wVns"}, + {Anything, "ON'T", Anything, "@Unt"}, + {"C", "O", "N", "0"}, + {Anything, "O", "NG", "O"}, + {" :^", "O", "N", "V"}, + {"I", "ON", Anything, "@n"}, + {"#:", "ON", Nothing, "@n"}, + {"#^", "ON", Anything, "@n"}, + {Anything, "O", "ST ", "@U"}, + {Anything, "OF", "^", "Of"}, + {Anything, "OTHER", Anything, "VD3"}, + {Anything, "OSS", Nothing, "Os"}, + {"#:^", "OM", Anything, "Vm"}, + {Anything, "O", Anything, "0"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule P_rules[] = +{ + {Anything, "PH", Anything, "f"}, + {Anything, "PEOP", Anything, "pip"}, + {Anything, "POW", Anything, "paU"}, + {Anything, "PUT", Nothing, "pUt"}, + {Anything, "P", Anything, "p"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule Q_rules[] = +{ + {Anything, "QUAR", Anything, "kwOr"}, + {Anything, "QU", Anything, "kw"}, + {Anything, "Q", Anything, "k"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule R_rules[] = +{ + {Nothing, "RE", "^#", "ri"}, + {Anything, "R", Anything, "r"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule S_rules[] = +{ + {Anything, "SH", Anything, "S"}, + {"#", "SION", Anything, "Z@n"}, + {Anything, "SOME", Anything, "sVm"}, + {"#", "SUR", "#", "Z3"}, + {Anything, "SUR", "#", "S3"}, + {"#", "SU", "#", "Zu"}, + {"#", "SSU", "#", "Su"}, + {"#", "SED", Nothing, "zd"}, + {"#", "S", "#", "z"}, + {Anything, "SAID", Anything, "sed"}, + {"^", "SION", Anything, "S@n"}, + {Anything, "S", "S", ""}, + {".", "S", Nothing, "z"}, + {"#:.E", "S", Nothing, "z"}, + {"#:^##", "S", Nothing, "z"}, + {"#:^#", "S", Nothing, "s"}, + {"U", "S", Nothing, "s"}, + {" :#", "S", Nothing, "z"}, + {Nothing, "SCH", Anything, "sk"}, + {Anything, "S", "C+", ""}, + {"#", "SM", Anything, "zm"}, + {"#", "SN", "'", "z@n"}, + {Anything, "S", Anything, "s"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule T_rules[] = +{ + {Nothing, "THE", Nothing, "D@"}, + {Anything, "TO", Nothing, "tu"}, + {Anything, "THAT", Nothing, "D&t"}, + {Nothing, "THIS", Nothing, "DIs"}, + {Nothing, "THEY", Anything, "DeI"}, + {Nothing, "THERE", Anything, "Der"}, + {Anything, "THER", Anything, "D3"}, + {Anything, "THEIR", Anything, "Der"}, + {Nothing, "THAN", Nothing, "D&n"}, + {Nothing, "THEM", Nothing, "Dem"}, + {Anything, "THESE", Nothing, "Diz"}, + {Nothing, "THEN", Anything, "Den"}, + {Anything, "THROUGH", Anything, "Tru"}, + {Anything, "THOSE", Anything, "D@Uz"}, + {Anything, "THOUGH", Nothing, "D@U"}, + {Nothing, "THUS", Anything, "DVs"}, + {Anything, "TH", Anything, "T"}, + {"#:", "TED", Nothing, "tId"}, + {"S", "TI", "#N", "tS"}, + {Anything, "TI", "O", "S"}, + {Anything, "TI", "A", "S"}, + {Anything, "TIEN", Anything, "S@n"}, + {Anything, "TUR", "#", "tS3"}, + {Anything, "TU", "A", "tSu"}, + {Nothing, "TWO", Anything, "tu"}, + {Anything, "T", Anything, "t"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule U_rules[] = +{ + {Nothing, "UN", "I", "jun"}, + {Nothing, "UN", Anything, "Vn"}, + {Nothing, "UPON", Anything, "@pOn"}, + {"T", "UR", "#", "Ur"}, + {"S", "UR", "#", "Ur"}, + {"R", "UR", "#", "Ur"}, + {"D", "UR", "#", "Ur"}, + {"L", "UR", "#", "Ur"}, + {"Z", "UR", "#", "Ur"}, + {"N", "UR", "#", "Ur"}, + {"J", "UR", "#", "Ur"}, + {"TH", "UR", "#", "Ur"}, + {"CH", "UR", "#", "Ur"}, + {"SH", "UR", "#", "Ur"}, + {Anything, "UR", "#", "jUr"}, + {Anything, "UR", Anything, "3"}, + {Anything, "U", "^ ", "V"}, + {Anything, "U", "^^", "V"}, + {Anything, "UY", Anything, "aI"}, + {" G", "U", "#", ""}, + {"G", "U", "%", ""}, + {"G", "U", "#", "w"}, + {"#N", "U", Anything, "ju"}, + {"T", "U", Anything, "u"}, + {"S", "U", Anything, "u"}, + {"R", "U", Anything, "u"}, + {"D", "U", Anything, "u"}, + {"L", "U", Anything, "u"}, + {"Z", "U", Anything, "u"}, + {"N", "U", Anything, "u"}, + {"J", "U", Anything, "u"}, + {"TH", "U", Anything, "u"}, + {"CH", "U", Anything, "u"}, + {"SH", "U", Anything, "u"}, + {Anything, "U", Anything, "ju"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule V_rules[] = +{ + {Anything, "VIEW", Anything, "vju"}, + {Anything, "V", Anything, "v"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule W_rules[] = +{ + {Nothing, "WERE", Anything, "w3"}, + {Anything, "WA", "S", "w0"}, + {Anything, "WA", "T", "w0"}, + {Anything, "WHERE", Anything, "hwer"}, + {Anything, "WHAT", Anything, "hw0t"}, + {Anything, "WHOL", Anything, "h@Ul"}, + {Anything, "WHO", Anything, "hu"}, + {Anything, "WH", Anything, "hw"}, + {Anything, "WAR", Anything, "wOr"}, + {Anything, "WOR", "^", "w3"}, + {Anything, "WR", Anything, "r"}, + {Anything, "W", Anything, "w"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule X_rules[] = +{ + {Anything, "X", Anything, "ks"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule Y_rules[] = +{ + {Anything, "YOUNG", Anything, "jVN"}, + {Nothing, "YOU", Anything, "ju"}, + {Nothing, "YES", Anything, "jes"}, + {Nothing, "Y", Anything, "j"}, + {"#:^", "Y", Nothing, "i"}, + {"#:^", "Y", "I", "i"}, + {" :", "Y", Nothing, "aI"}, + {" :", "Y", "#", "aI"}, + {" :", "Y", "^+:#", "I"}, + {" :", "Y", "^#", "aI"}, + {Anything, "Y", Anything, "I"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule Z_rules[] = +{ + {Anything, "Z", Anything, "z"}, + {Anything, 0, Anything, Silent}, +}; + +static Rule *Rules[] = +{ + punct_rules, + A_rules, B_rules, C_rules, D_rules, E_rules, F_rules, G_rules, + H_rules, I_rules, J_rules, K_rules, L_rules, M_rules, N_rules, + O_rules, P_rules, Q_rules, R_rules, S_rules, T_rules, U_rules, + V_rules, W_rules, X_rules, Y_rules, Z_rules +}; + + +static const char *Cardinals[] = +{ + "zero", "one", "two", "three", "four", + "five", "six", "seven", "eight", "nine", + "ten", "eleven", "twelve", "thirteen", "fourteen", + "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" +}; + + +static const char *Twenties[] = +{ + "twenty", "thirty", "forty", "fifty", + "sixty", "seventy", "eighty", "ninety" +}; + + +static const char *Ordinals[] = +{ + "zeroth", "first", "second", "third", "fourth", + "fifth", "sixth", "seventh","eighth", "ninth", + "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", + "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth" +}; + + +static const char *Ord_twenties[] = +{ + "twentieth", "thirtieth", "fortieth", "fiftieth", + "sixtieth", "seventieth", "eightieth", "ninetieth" +}; + + +/* +** Translate a number to phonemes. This version is for CARDINAL numbers. +** Note: this is recursive. +*/ +static int xlate_cardinal(int value, darray *phone) +{ + int nph = 0; + + if (value < 0) + { + nph += xlate_string("minus", phone); + value = (-value); + + if (value < 0) /* Overflow! -32768 */ + { + nph += xlate_string("a lot", phone); + return nph; + } + } + + if (value >= 1000000000L) + /* Billions */ + { + nph += xlate_cardinal(value / 1000000000L, phone); + nph += xlate_string("billion", phone); + value = value % 1000000000; + + if (value == 0) + return nph; /* Even billion */ + + if (value < 100) + nph += xlate_string("and", phone); + + /* as in THREE BILLION AND FIVE */ + } + + if (value >= 1000000L) + /* Millions */ + { + nph += xlate_cardinal(value / 1000000L, phone); + nph += xlate_string("million", phone); + value = value % 1000000L; + + if (value == 0) + return nph; /* Even million */ + + if (value < 100) + nph += xlate_string("and", phone); + + /* as in THREE MILLION AND FIVE */ + } + + /* Thousands 1000..1099 2000..99999 */ + /* 1100 to 1999 is eleven-hunderd to ninteen-hunderd */ + + if ((value >= 1000L && value <= 1099L) || value >= 2000L) + { + nph += xlate_cardinal(value / 1000L, phone); + nph += xlate_string("thousand", phone); + value = value % 1000L; + + if (value == 0) + return nph; /* Even thousand */ + + if (value < 100) + nph += xlate_string("and", phone); + + /* as in THREE THOUSAND AND FIVE */ + } + + if (value >= 100L) + { + nph += xlate_string(Cardinals[value / 100], phone); + nph += xlate_string("hundred", phone); + value = value % 100; + + if (value == 0) + return nph; /* Even hundred */ + } + + if (value >= 20) + { + nph += xlate_string(Twenties[(value - 20) / 10], phone); + value = value % 10; + + if (value == 0) + return nph; /* Even ten */ + } + + nph += xlate_string(Cardinals[value], phone); + + return nph; +} + +/* +** Translate a number to phonemes. This version is for ORDINAL numbers. +** Note: this is recursive. +*/ +static int xlate_ordinal(int value, darray *phone) +{ + int nph = 0; + + if (value < 0) + { + nph += xlate_string("minus", phone); + value = (-value); + + if (value < 0) /* Overflow! -32768 */ + { + nph += xlate_string("a lot", phone); + return nph; + } + } + + if (value >= 1000000000L) + /* Billions */ + { + nph += xlate_cardinal(value / 1000000000L, phone); + value = value % 1000000000; + + if (value == 0) + { + nph += xlate_string("billionth", phone); + return nph; /* Even billion */ + } + + nph += xlate_string("billion", phone); + + if (value < 100) + nph += xlate_string("and", phone); + + /* as in THREE BILLION AND FIVE */ + } + + if (value >= 1000000L) + /* Millions */ + { + nph += xlate_cardinal(value / 1000000L, phone); + value = value % 1000000L; + + if (value == 0) + { + nph += xlate_string("millionth", phone); + return nph; /* Even million */ + } + + nph += xlate_string("million", phone); + + if (value < 100) + nph += xlate_string("and", phone); + + /* as in THREE MILLION AND FIVE */ + } + + /* Thousands 1000..1099 2000..99999 */ + /* 1100 to 1999 is eleven-hunderd to ninteen-hunderd */ + + if ((value >= 1000L && value <= 1099L) || value >= 2000L) + { + nph += xlate_cardinal(value / 1000L, phone); + value = value % 1000L; + + if (value == 0) + { + nph += xlate_string("thousandth", phone); + return nph; /* Even thousand */ + } + + nph += xlate_string("thousand", phone); + + if (value < 100) + nph += xlate_string("and", phone); + + /* as in THREE THOUSAND AND FIVE */ + } + + if (value >= 100L) + { + nph += xlate_string(Cardinals[value / 100], phone); + value = value % 100; + + if (value == 0) + { + nph += xlate_string("hundredth", phone); + return nph; /* Even hundred */ + } + + nph += xlate_string("hundred", phone); + } + + if (value >= 20) + { + if ((value % 10) == 0) + { + nph += xlate_string(Ord_twenties[(value - 20) / 10], phone); + return nph; /* Even ten */ + } + + nph += xlate_string(Twenties[(value - 20) / 10], phone); + + value = value % 10; + } + + nph += xlate_string(Ordinals[value], phone); + + return nph; +} + + +static int isvowel(int chr) +{ + return (chr == 'A' || chr == 'E' || chr == 'I' || + chr == 'O' || chr == 'U'); +} + +static int isconsonant(int chr) +{ + return (isupper(chr) && !isvowel(chr)); +} + +static int leftmatch( + const char *pattern, /* first char of pattern to match in text */ + const char *context) /* last char of text to be matched */ + +{ + const char *pat; + const char *text; + int count; + + if (*pattern == '\0') + /* null string matches any context */ + { + return 1; + } + + /* point to last character in pattern string */ + count = (int)strlen(pattern); + + pat = pattern + (count - 1); + + text = context; + + for (; count > 0; pat--, count--) + { + /* First check for simple text or space */ + if (isalpha(*pat) || *pat == '\'' || *pat == ' ') + { + if (*pat != *text) + { + return 0; + } + else + { + text--; + continue; + } + } + + switch (*pat) + { + + case '#': /* One or more vowels */ + + if (!isvowel(*text)) + return 0; + + text--; + + while (isvowel(*text)) + text--; + + break; + + case ':': /* Zero or more consonants */ + while (isconsonant(*text)) + text--; + + break; + + case '^': /* One consonant */ + if (!isconsonant(*text)) + return 0; + + text--; + + break; + + case '.': /* B, D, V, G, J, L, M, N, R, W, Z */ + if (*text != 'B' && *text != 'D' && *text != 'V' + && *text != 'G' && *text != 'J' && *text != 'L' + && *text != 'M' && *text != 'N' && *text != 'R' + && *text != 'W' && *text != 'Z') + return 0; + + text--; + + break; + + case '+': /* E, I or Y (front vowel) */ + if (*text != 'E' && *text != 'I' && *text != 'Y') + return 0; + + text--; + + break; + + case '%': + + default: + fprintf(stderr, "Bad char in left rule: '%c'\n", *pat); + + return 0; + } + } + + return 1; +} + +static int rightmatch( + const char *pattern, /* first char of pattern to match in text */ + const char *context) /* last char of text to be matched */ +{ + const char *pat; + const char *text; + + if (*pattern == '\0') + /* null string matches any context */ + return 1; + + pat = pattern; + + text = context; + + for (pat = pattern; *pat != '\0'; pat++) + { + /* First check for simple text or space */ + if (isalpha(*pat) || *pat == '\'' || *pat == ' ') + { + if (*pat != *text) + { + return 0; + } + else + { + text++; + continue; + } + } + + switch (*pat) + { + + case '#': /* One or more vowels */ + + if (!isvowel(*text)) + return 0; + + text++; + + while (isvowel(*text)) + text++; + + break; + + case ':': /* Zero or more consonants */ + while (isconsonant(*text)) + text++; + + break; + + case '^': /* One consonant */ + if (!isconsonant(*text)) + return 0; + + text++; + + break; + + case '.': /* B, D, V, G, J, L, M, N, R, W, Z */ + if (*text != 'B' && *text != 'D' && *text != 'V' + && *text != 'G' && *text != 'J' && *text != 'L' + && *text != 'M' && *text != 'N' && *text != 'R' + && *text != 'W' && *text != 'Z') + return 0; + + text++; + + break; + + case '+': /* E, I or Y (front vowel) */ + if (*text != 'E' && *text != 'I' && *text != 'Y') + return 0; + + text++; + + break; + + case '%': /* ER, E, ES, ED, ING, ELY (a suffix) */ + if (*text == 'E') + { + text++; + + if (*text == 'L') + { + text++; + + if (*text == 'Y') + { + text++; + break; + } + + else + { + text--; /* Don't gobble L */ + break; + } + } + + else + if (*text == 'R' || *text == 'S' || *text == 'D') + text++; + + break; + } + + else + if (*text == 'I') + { + text++; + + if (*text == 'N') + { + text++; + + if (*text == 'G') + { + text++; + break; + } + } + + return 0; + } + + else + return 0; + + default: + fprintf(stderr, "Bad char in right rule:'%c'\n", *pat); + + return 0; + } + } + + return 1; +} + +static void phone_cat(darray *arg, const char *s) +{ + char ch; + + while ((ch = *s++)) + arg->put(ch); +} + + +static int find_rule(darray *arg, char *word, int index, Rule *rules) +{ + for (;;) /* Search for the rule */ + { + Rule *rule; + const char *left, + *match, + *right, + *output; + int remainder; + rule = rules++; + match = (*rule)[1]; + + if (match == 0) + /* bad symbol! */ + { + fprintf(stderr, "Error: Can't find rule for: '%c' in \"%s\"\n", + word[index], word); + return index + 1; /* Skip it! */ + } + + for (remainder = index; *match != '\0'; match++, remainder++) + { + if (*match != word[remainder]) + break; + } + + if (*match != '\0') + continue; /* found missmatch */ + + left = (*rule)[0]; + + right = (*rule)[2]; + + if (!leftmatch(left, &word[index - 1])) + continue; + + if (!rightmatch(right, &word[remainder])) + continue; + + output = (*rule)[3]; + + phone_cat(arg, output); + + return remainder; + } +} + +static void guess_word(darray *arg, char *word) +{ + int index; /* Current position in word */ + int type; /* First letter of match part */ + index = 1; /* Skip the initial blank */ + + do + { + if (isupper(word[index])) + type = word[index] - 'A' + 1; + else + type = 0; + + index = find_rule(arg, word, index, Rules[type]); + } + + while (word[index] != '\0'); +} + + +static int NRL(const char *s, int n, darray *phone) +{ + int old = phone->getSize(); + char *word = (char *) malloc(n + 3); + char *d = word; + *d++ = ' '; + + while (n-- > 0) + { + char ch = *s++; + + if (islower(ch)) + ch = toupper(ch); + + *d++ = ch; + } + + *d++ = ' '; // kinda unnecessary + + *d = '\0'; + guess_word(phone, word); + free(word); + return phone->getSize() - old; +} + + +static int spell_out(const char *word, int n, darray *phone) +{ + int nph = 0; + + while (n-- > 0) + { + nph += xlate_string(ASCII[*word++ & 0x7F], phone); + } + + return nph; +} + +static int suspect_word(const char *s, int n) +{ + int i = 0; + int seen_lower = 0; + int seen_upper = 0; + int seen_vowel = 0; + int last = 0; + + for (i = 0; i < n; i++) + { + char ch = *s++; + + if (i && last != '-' && isupper(ch)) + seen_upper = 1; + + if (islower(ch)) + { + seen_lower = 1; + ch = toupper(ch); + } + + if (ch == 'A' || ch == 'E' || ch == 'I' || ch == 'O' || ch == 'U' || ch == 'Y') + seen_vowel = 1; + + last = ch; + } + + return !seen_vowel || (seen_upper && seen_lower) || !seen_lower; +} + +static int xlate_word(const char *word, int n, darray *phone) +{ + int nph = 0; + + if (*word != '[') + { + if (suspect_word(word, n)) + return spell_out(word, n, phone); + else + { + nph += NRL(word, n, phone); + } + } + + else + { + if ((++word)[(--n) - 1] == ']') + n--; + + while (n-- > 0) + { + phone->put(*word++); + nph++; + } + } + + phone->put(' '); + + return nph + 1; +} + + +int xlate_string(const char *string, darray *phone) +{ + int nph = 0; + const char *s = string; + char ch; + + while (isspace(ch = *s)) + s++; + + while ((ch = *s)) + { + const char *word = s; + + if (isalpha(ch)) + { + while (isalpha(ch = *s) || ((ch == '\'' || ch == '-' || ch == '.') && isalpha(s[1]))) + { + s++; + } + + if (!ch || isspace(ch) || ispunct(ch) || (isdigit(ch) && !suspect_word(word, (int)(s - word)))) + { + nph += xlate_word(word, (int)(s - word), phone); + } + else + { + while ((ch = *s) && !isspace(ch) && !ispunct(ch)) + { + s++; + } + + nph += spell_out(word, (int)(s - word), phone); + } + } + else + { + if (isdigit(ch) || (ch == '-' && isdigit(s[1]))) + { + int sign = (ch == '-') ? -1 : 1; + int value = 0; + + if (sign < 0) + { + ch = *++s; + } + + while (isdigit(ch = *s)) + { + value = value * 10 + ch - '0'; + s++; + } + + if (ch == '.' && isdigit(s[1])) + { + word = ++s; + nph += xlate_cardinal(value * sign, phone); + nph += xlate_string("point", phone); + + while (isdigit(ch = *s)) + { + s++; + } + + nph += spell_out(word, (int)(s - word), phone); + } + else + { + /* check for ordinals, date, time etc. can go in here */ + nph += xlate_cardinal(value * sign, phone); + } + } + else + { + if (ch == '[' && strchr(s, ']')) + { + const char *word = s; + + while (*s && *s++ != ']') + /* nothing */ + ; + + nph += xlate_word(word, (int)(s - word), phone); + } + else + { + if (ispunct(ch)) + { + switch (ch) + { + + case '!': + + case '?': + + case '.': + s++; + phone->put('.');// (' '); + break; + + case '"': /* change pitch ? */ + + case ':': + + case '-': + + case ';': + + case ',': + + case '(': + + case ')': + s++; + phone->put(' '); + break; + + case '[': + { + const char *e = strchr(s, ']'); + + if (e) + { + s++; + + while (s < e) + phone->put(*s++); + + s = e + 1; + + break; + } + } + // fallthrough + default: + nph += spell_out(word, 1, phone); + s++; + break; + } + } + else + { + while ((ch = *s) && !isspace(ch)) + { + s++; + } + + nph += spell_out(word, (int)(s - word), phone); + } + } + } + + while (isspace(ch = *s)) + s++; + } + } + + return nph; +} diff --git a/src/soloud/src/audiosource/speech/tts.h b/src/soloud/src/audiosource/speech/tts.h new file mode 100644 index 0000000..8966e76 --- /dev/null +++ b/src/soloud/src/audiosource/speech/tts.h @@ -0,0 +1,5 @@ + +extern int xlate_string (const char *string,darray *phone); + + + diff --git a/src/soloud/src/audiosource/tedsid/sid.cpp b/src/soloud/src/audiosource/tedsid/sid.cpp new file mode 100644 index 0000000..6864d06 --- /dev/null +++ b/src/soloud/src/audiosource/tedsid/sid.cpp @@ -0,0 +1,623 @@ +// Issues: +// - Filter cutoff frequencies not 100% accurate +// - Combined waveforms of the 6581 incorrect (SID card used 8580 anyway) +// - filter distortion not emulated +// - no joystick or paddle support +// - probably many more + +#include +#ifndef __vita__ +#include +#endif +#include "sid.h" +//#include "Tedmem.h" + +#define DIGIBLASTER_MULT 14 + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +// Hack to store master volume +//unsigned int SIDsound::masterVolume = 0; + +// +// Random number generator for noise waveform +// + +// Test a bit. Returns 1 if bit is set. +inline static long bit(long val, unsigned int bitnr) +{ + return (val >> bitnr) & 1; +} + +inline void SIDsound::updateShiftReg(SIDVoice &v) +{ + unsigned int shiftReg = v.shiftReg; + unsigned int bit22 = bit(shiftReg,22); + unsigned int bit17 = bit(shiftReg,17); + + // Shift 1 bit left + shiftReg = ((shiftReg) << 1);// & 0x7fffff; + + // Feed bit 0 + v.shiftReg = shiftReg | (bit22 ^ bit17); +} + +inline int SIDsound::waveNoise(SIDVoice &v) +{ + unsigned int shiftReg = v.shiftReg; + // Pick out bits to make output value, left shift by 4 + return + (bit(shiftReg,22) << 11) | + (bit(shiftReg,20) << 10) | + (bit(shiftReg,16) << 9) | + (bit(shiftReg,13) << 8) | + (bit(shiftReg,11) << 7) | + (bit(shiftReg, 7) << 6) | + (bit(shiftReg, 4) << 5) | + (bit(shiftReg, 2) << 4); +}; + +void SIDsound::setModel(unsigned int model) +{ + int i; + + switch (model) { + case SID8580DB: + case SID8580: + for ( i=0; i<2048; i++) { + double x = i / 8.0; + //double cf = 12500.0 * i / 2048.0; // specs and YAPE + // approximate with a 3-degree polynomial + //double cf = 0.0003*x*x*x + 0.0882*x*x + 44.49*x - 38.409; + // approximate with a 2-degree polynomial + //double cf = -0.0177*x*x + 55.261*x - 55.518; // CSG 8580R4 + double cf = -0.0156*x*x + 48.473*x - 45.074; // 8580R5 + cutOffFreq[i] = cf <= 0 ? 0 : cf; + } + dcWave = 0x800; + dcMixer = 0; + dcVoice = 0; + break; + + case SID6581: // R4 actually + for (i=0; i<1024; i++) { + cutOffFreq[i] = (tanh(((double)i/1.5 - 1024.0)/1024.0*M_PI) + tanh(M_PI)) + * (6000.0 - 220.0) + 220.0; + } + for (; i<1056; i++) { + double x = ((double)i - 1024.0) / (1056.0 - 1003.); + cutOffFreq[i] = x*(1315.0 - 1003.0) + 1003.0; + } + for (; i<2048; i++) { + double x = ((double)i - 1056.0) / (2048.0 - 1056.0); + cutOffFreq[i] = //(tanh (((double)i - 2048.0)/1024.0*M_PI) + tanh(M_PI)) + //* (20163.0 - 1315.0) + 1315.0; + (20163.0 - 1315.0) * x + 1315.0; + } + dcWave = 0x380; + dcMixer = -0xFFF*0xFF/18 >> 7; + dcVoice = 0x800*0xFF; + break; + + case SID6581R1: // 6581 R1 + for (i=0; i<1024; i++) { + cutOffFreq[i] = (tanh(((double)i-1024.0)/1024.0*M_PI) + tanh(M_PI)) + * (6000.0 - 220.0) + 220.0; + } + for (; i<2048; i++) { + cutOffFreq[i] = (tanh (((double)i-2048.0)/1024.0*M_PI) + tanh(M_PI)) + * (18000.0 - 4600.0) + 4600.0; + } + dcWave = 0x380; + dcMixer = -0xFFF*0xFF/18 >> 7; + dcVoice = 0x800*0xFF; + break; + } + setFilterCutoff(); + model_ = model; +} + +// Static data members +const unsigned int SIDsound::RateCountPeriod[16] = { + 0x7F00,0x0006,0x003C,0x0330,0x20C0,0x6755,0x3800,0x500E, + 0x1212,0x0222,0x1848,0x59B8,0x3840,0x77E2,0x7625,0x0A93 +}; + +const unsigned char SIDsound::envGenDRdivisors[256] = { + 30,30,30,30,30,30,16,16,16,16,16,16,16,16,8,8, + 8,8,8,8,8,8,8,8,8,8,4,4,4,4,4,4, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 4,4,4,4,4,4,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 +}; + +void SIDsound::calcEnvelopeTable() +{ + // number of SIDsound envelope clocks per sample (0x1FFFFF) + const double deltaSampleCyclesFloat = ((double) sidBaseFreq * 256.0) / (double)sampleRate; + sidCyclesPerSampleInt = (unsigned int) (deltaSampleCyclesFloat + 0.5); +} + +void SIDsound::setFrequency(unsigned int sid_frequency) +{ + switch (sid_frequency) { + case 0: + sidBaseFreq = TED_SOUND_CLOCK * 4; // 312 * 114 * 50 / 2; + break; + default: + sidBaseFreq = SOUND_FREQ_PAL_C64; + break; + } + calcEnvelopeTable(); +} + +void SIDsound::setSampleRate(unsigned int sampleRate_) +{ + sampleRate = sampleRate_; + calcEnvelopeTable(); +} + +SIDsound::SIDsound(unsigned int model, unsigned int chnlDisableMask) : enableDigiBlaster(false) +{ + unsigned int i; + masterVolume = 0; + + // Link voices together + for (i=0; i<3; i++) { + voice[i].modulatedBy = &voice[(i+2)%3]; // previous voice + voice[i].modulatesThis = &voice[(i+1)%3]; // next voice + voice[i].disabled = !!((chnlDisableMask >> i) & 1); + } + + filterCutoff = 0; + setModel(model); + setFrequency(0); + reset(); +} + +void SIDsound::reset(void) +{ + volume = masterVolume; + + lastByteWritten = 0; + + for (int v=0; v<3; v++) { + voice[v].wave = WAVE_NONE; + voice[v].egState = EG_FROZEN; + voice[v].accu = voice[v].add = 0; + voice[v].freq = voice[v].pw = 0; + voice[v].envCurrLevel = voice[v].envSustainLevel = 0; + voice[v].gate = voice[v].ring = voice[v].test = 0; + voice[v].filter = voice[v].sync = false; + voice[v].muted = 0; + // Initial value of internal shift register + voice[v].shiftReg = 0x7FFFFC; + voice[v].envExpCounter = 0; + voice[v].envAttackAdd = voice[v].envDecaySub = voice[v].envReleaseSub = 0; + voice[v].envCounterCompare = RateCountPeriod[0]; + voice[v].envCounter = 0x7fff; + } + + filterType = FILTER_NONE; + filterCutoff = filterResonance = 0; + + Vhp = Vbp = Vlp = 0; + setFilterCutoff(); + setResonance(); + + dcDigiBlaster = 0; + clockDeltaRemainder = 0; +} + +inline int SIDsound::getWaveSample(SIDVoice &v) +{ + switch (v.wave) { + case WAVE_TRI: + return waveTriangle(v); + case WAVE_SAW: + return waveSaw(v); + case WAVE_PULSE: + return wavePulse(v); + case WAVE_TRISAW: + return waveTriSaw(v); + case WAVE_TRIPULSE: + return waveTriPulse(v); + case WAVE_SAWPULSE: + return waveSawPulse(v); + case WAVE_TRISAWPULSE: + return waveTriSawPulse(v); + case WAVE_NOISE: + return waveNoise(v); + default: + return 0x800; + } +} + +unsigned char SIDsound::read(unsigned int adr) +{ + switch(adr) { + case 0x19: + case 0x1A: + // POTX/POTY paddle AD converters (unemulated) + lastByteWritten = 0; + return 0xFF; + + // Voice 3 (only) oscillator readout + case 0x1B: + lastByteWritten = 0; + return (unsigned char)(getWaveSample(voice[2])>>0); // 4? + + // Voice 3 EG readout + case 0x1C: + return (unsigned char)(voice[2].envCurrLevel); + + case 0x1E: // Digiblaster DAC readout + if (enableDigiBlaster && model_ == SID8580) + { + return (unsigned char) (dcDigiBlaster >> DIGIBLASTER_MULT); + } + return lastByteWritten; + + default: + // Write-only registers return the last value written + return lastByteWritten; + } +} + +void SIDsound::write(unsigned int adr, unsigned char value) +{ + lastByteWritten = value; + + SIDVoice &v = voice[adr/7]; + switch (adr) { + case 0: + case 7: + case 14: + v.freq = (unsigned short)((v.freq & 0xff00) | value); + v.add = (unsigned int)(((double)v.freq + * sidBaseFreq) * 16.0 / sampleRate + 0.5); + break; + + case 1: + case 8: + case 15: + v.freq = (unsigned short)((v.freq & 0xff) | (value << 8)); + v.add = (unsigned int)(((double)v.freq + * sidBaseFreq) * 16.0 / sampleRate + 0.5); + break; + + case 2: + case 9: + case 16: + v.pw = (unsigned short)((v.pw & 0x0f00) | value); + break; + + case 3: + case 10: + case 17: + v.pw = (unsigned short)((v.pw & 0xff) | ((value & 0xf) << 8)); + break; + + case 4: + case 11: + case 18: + if ((value & 1) != (unsigned char) v.gate) { + if (value & 1) { + // gate on + v.egState = EG_ATTACK; + v.envCounterCompare = v.envAttackAdd; + } else { + // gate off +#if 00 + if (v.egState != EG_FROZEN) +#endif + v.egState = EG_RELEASE; + v.envCounterCompare = v.envReleaseSub; + } + v.gate = value & 1; + } + v.modulatedBy->sync = value & 2; + v.ring = value & 4; + if ((value & 8) && !v.test) { + v.accu = 0; //(model_ >= SID8580) ? 0 : 0; + unsigned int bit19 = (v.shiftReg >> 19) & 1; + v.shiftReg = (v.shiftReg & 0x7ffffd) | ((bit19^1) << 1); + v.test = 0xFFF; + } else if (v.test && !(value & 8)) { + unsigned int bit0 = ((v.shiftReg >> 22) ^ (v.shiftReg >> 17)) & 0x1; + v.shiftReg <<= 1; + v.shiftReg &= 0x7fffff; + v.shiftReg |= bit0; + v.test = 0x000; + } + v.wave = (value >> 4) & 0x0F; + if (v.wave > 8) { + v.shiftReg &= 0x7fffff^(1<<22)^(1<<20)^(1<<16)^(1<<13)^(1<<11)^(1<<7)^(1<<4)^(1<<2); + } + break; + + case 5: + case 12: + case 19: + v.envAttackAdd = value >> 4; + v.envDecaySub = value & 0x0F; + if (v.egState == EG_ATTACK) + v.envCounterCompare = v.envAttackAdd; + else if (v.egState == EG_DECAY) + v.envCounterCompare = v.envDecaySub; + break; + + case 6: + case 13: + case 20: + v.envSustainLevel = (value >> 4) * 0x11; + v.envReleaseSub = value & 0x0F; + if (v.egState == EG_RELEASE) + v.envCounterCompare = v.envReleaseSub; + break; + + case 21: + if ((unsigned int)(value&7) != (filterCutoff&7)) { + filterCutoff = (value&7)|(filterCutoff&0x7F8); + setFilterCutoff(); + } + break; + + case 22: + filterCutoff = (value<<3)|(filterCutoff&7); + setFilterCutoff(); + break; + + case 23: + voice[0].filter = value & 1; + voice[1].filter = value & 2; + voice[2].filter = value & 4; + filterResonance = (unsigned char)(value >> 4); + setResonance(); + break; + + case 24: + volume = value & 0x0F; + voice[2].muted = value & 0x80; + filterType = (unsigned char)((value >> 4) & 7); + break; + + case 30: // Digiblaster DAC + if (enableDigiBlaster && model_ == SID8580) + { + dcDigiBlaster = (value ^ 0x00) << DIGIBLASTER_MULT; + } + break; + + case 31: // Digiblaster ADC + break; + } +} + +inline void SIDsound::setFilterCutoff() +{ + const double freqDomainDivCoeff = 2 * M_PI * 1.048576; + w0 = int(cutOffFreq[filterCutoff] * freqDomainDivCoeff); + // Limit cutoff to Nyquist frq to keep the sample based filter stable + const double NyquistFrq = double(sampleRate) / 2; + const double maxCutOff = NyquistFrq > 16000.0 ? 16000.0 : NyquistFrq; + const int w0MaxDt = int(maxCutOff * freqDomainDivCoeff); // 16000 + if (w0 > w0MaxDt) w0 = w0MaxDt; +} + +inline void SIDsound::setResonance() +{ + resonanceCoeffDiv1024 = (int) (1024.0/(0.707 + 1.9 * (double) filterResonance / 15.0) + 0.5); // 2.3 +} + +inline unsigned int SIDsound::clock() +{ + unsigned int count = sidCyclesPerSampleInt >> 8; + unsigned int tmp = sidCyclesPerSampleInt & 0xFF; + unsigned int newCount = clockDeltaRemainder + tmp; + + if (newCount >= 0x100) { + clockDeltaRemainder = newCount & 0xFF; + count++; + } else { + clockDeltaRemainder = newCount; + } + return count; +} + +// simplified version of http://bel.fi/~alankila/c64-sw/index-cpp.html +inline int SIDsound::filterOutput(unsigned int cycles, int Vi) +{ + int w0deltaTime = w0 >> 6; + Vi >>= 7; + unsigned int count = cycles; + + do { + int dVlp = (w0deltaTime * Vbp >> 14); + Vlp -= dVlp; + int dVbp = (w0deltaTime * Vhp >> 14); + Vbp -= dVbp; + Vhp = (Vbp * resonanceCoeffDiv1024 >> 10) - Vlp - Vi; + } while (--count); + + int Vf; + + switch (filterType) { + default: + case FILTER_NONE: + Vf = 0; + break; + case FILTER_LP: + Vf = Vlp; + break; + case FILTER_BP: + Vf = Vbp; + break; + case FILTER_LPBP: + Vf = Vlp + Vbp; + break; + case FILTER_HP: + Vf = Vhp; + break; + case FILTER_NOTCH: + Vf = Vlp + Vhp; + break; + case FILTER_HPBP: + Vf = Vbp + Vhp; + break; + case FILTER_ALL: + Vf = Vlp + Vbp + Vhp; + break; + } + return Vf << 7; +} + +// Envelope based on: +// http://blog.kevtris.org/?p=13 +inline int SIDsound::doEnvelopeGenerator(unsigned int cycles, SIDVoice &v) +{ + unsigned int count = cycles; + + do { + unsigned int LFSR = v.envCounter; + if (LFSR != RateCountPeriod[v.envCounterCompare]) { + const unsigned int feedback = ((LFSR >> 14) ^ (LFSR >> 13)) & 1; + LFSR = ((LFSR << 1) | feedback) & 0x7FFF; + v.envCounter = LFSR; + } else { + // LFSR = 0x7fff reset LFSR + v.envCounter = 0x7fff; + + if (v.egState == EG_ATTACK || ++v.envExpCounter == envGenDRdivisors[v.envCurrLevel]) { + + v.envExpCounter = 0; + + switch (v.egState) { + + case EG_ATTACK: + // According to Bob Yannes, Attack is linear... + if ( ((++v.envCurrLevel) & 0xFF) == 0xFF) { + v.egState = EG_DECAY; + v.envCounterCompare = v.envDecaySub; + } + break; + + case EG_DECAY: + if (v.envCurrLevel != v.envSustainLevel) { + v.envCurrLevel--; + v.envCurrLevel &= 0xFF; + if (!v.envCurrLevel) + v.egState = EG_FROZEN; + } + break; + + case EG_RELEASE: + v.envCurrLevel = (v.envCurrLevel - 1) & 0xFF; + if (!v.envCurrLevel) + v.egState = EG_FROZEN; + break; + + case EG_FROZEN: + v.envCurrLevel = 0; + break; + } + } + } + } while (--count); + + return v.envCurrLevel & 0xFF; // envelope is 8 bits +} + +void SIDsound::calcSamples(short *buf, long accu) +{ + for (;accu--;) { + // Outputs for normal and filtered sounds + int sumFilteredOutput = 0; + int sumOutput = 0; + int sample; + + const unsigned int cyclesToDo = clock(); + // Loop for the three voices + int j = 2; + do { + SIDVoice &v = voice[j]; + int envelope = doEnvelopeGenerator(cyclesToDo, v); + // Waveform generator + if (!v.test) { +#if 1 + unsigned int accPrev = v.accu; + // Update accumulator + v.accu += v.add; + // FIXME Apply ring modulation. + if (v.sync && !(accPrev & 0x8000000) && (v.accu & 0x8000000) + ) +#else + v.accPrev = v.accu; + // Update accumulator if test bit not set + v.accu += v.add; + unsigned int accPrev = v.accPrev; + if (v.sync && !(v.accPrev & 0x8000000) && (v.accu & 0x8000000) + && !( v.modulatedBy->sync && !(v.modulatedBy->accPrev & 0x800000) && + (v.modulatedBy->accu & 0x800000)) + ) +#endif + v.modulatesThis->accu = 0; + if (v.freq) { + unsigned int accNext = accPrev; + unsigned int freq = v.freq << 4; + do { + accNext += freq; + // noise shift register is updating even when waveform is not selected + if (!(accPrev & 0x0800000) && (accNext & 0x0800000)) + updateShiftReg(v); + accPrev = accNext; + } while ( accNext < v.accu ); + } + // real accu is 24 bit but we use FP integer arithmetic + v.accu &= 0xFFFFFFF; + } + int output = v.disabled ? 0x0800 : getWaveSample(v); + + if (v.filter) + sumFilteredOutput += (output - dcWave) * envelope + dcVoice; + else { + if (v.muted) + sumOutput += (0x0800 - dcWave) * envelope + dcVoice; + else + sumOutput += (output - dcWave) * envelope + dcVoice; + } + } while (j--); + + int accu = (sumOutput + filterOutput(cyclesToDo, sumFilteredOutput) + + dcMixer + dcDigiBlaster) * volume; + +#if 1 + sample = accu >> 12; +#else + unsigned int interPolationFac = (clockDeltaRemainder - sidCyclesPerSampleInt) & 0xFF; + accu >>= 7; + sample = (prevAccu * (0xFF ^ interPolationFac) + accu * (interPolationFac)) >> 12; + prevAccu = accu; +#endif + + *buf++ = (short) sample; + } +} + +SIDsound::~SIDsound() +{ + masterVolume = volume; +} diff --git a/src/soloud/src/audiosource/tedsid/sid.h b/src/soloud/src/audiosource/tedsid/sid.h new file mode 100644 index 0000000..23d0cb7 --- /dev/null +++ b/src/soloud/src/audiosource/tedsid/sid.h @@ -0,0 +1,180 @@ +#ifndef _SID_H +#define _SID_H + +#define SOUND_FREQ_PAL_C64 985248 +#define TED_SOUND_CLOCK (221680) + +enum { + SID6581 = 0, + SID8580, + SID8580DB, + SID6581R1 +}; + +// SID class +class SIDsound +{ +public: + SIDsound(unsigned int model, unsigned int chnlDisableMask); + virtual ~SIDsound(); + virtual void reset(); + virtual void setReplayFreq() { + calcEnvelopeTable(); + }; + void setModel(unsigned int model); + void setFrequency(unsigned int sid_frequency); + void setSampleRate(unsigned int sampleRate_); + void calcEnvelopeTable(); + unsigned char read(unsigned int adr); + void write(unsigned int adr, unsigned char byte); + void calcSamples(short *buf, long count); + void enableDisableChannel(unsigned int ch, bool enabled) { + voice[ch].disabled = !enabled; + } + +private: + + // SIDsound waveforms + enum { + WAVE_NONE, WAVE_TRI, WAVE_SAW, WAVE_TRISAW, WAVE_PULSE, + WAVE_TRIPULSE, WAVE_SAWPULSE, WAVE_TRISAWPULSE, WAVE_NOISE + }; + // Envelope Genarator states + enum { + EG_FROZEN, EG_ATTACK, EG_DECAY, EG_RELEASE + }; + // Filter types + enum { + FILTER_NONE, FILTER_LP, FILTER_BP, FILTER_LPBP, FILTER_HP, FILTER_NOTCH, FILTER_HPBP, FILTER_ALL + }; + // Class for SID voices + class SIDVoice { + public: + int wave; // the selected waveform + int egState; // state of the EG + SIDVoice *modulatedBy; // the voice that modulates this one + SIDVoice *modulatesThis;// the voice that is modulated by this one + + unsigned int accu; // accumulator of the waveform generator, 8.16 fixed + unsigned int accPrev; // previous accu value (for ring modulation) + unsigned int add; // added to the accumulator for each sample + unsigned int shiftReg; // shift register for noise waveform + + unsigned short freq; // voice frequency + unsigned short pw; // pulse-width value + + unsigned int envAttackAdd; + unsigned int envDecaySub; + unsigned int envSustainLevel; + unsigned int envReleaseSub; + unsigned int envCurrLevel; + unsigned int envCounter; + unsigned int envExpCounter; + unsigned int envCounterCompare; + + unsigned int gate; // EG gate flag + unsigned int ring; // ring modulation flag + unsigned int test; // test flag + unsigned int filter; // voice filtered flag + unsigned int muted; // voice muted flag (only for 3rd voice) + bool disabled; // voice disabled + + // This bit is set for the modulating voice, + // not for the modulated one (compared to the real one) + unsigned int sync; // sync modulation flag + }; + int volume; // SID Master volume + unsigned int sidBaseFreq; // SID base frequency + unsigned int sidCyclesPerSampleInt; + unsigned int clockDeltaRemainder; + int dcMixer; // different for 6581 and 8580 (constant level output for digi) + int dcVoice; + int dcWave; + int dcDigiBlaster; + //int extIn; + // + unsigned int clock(); + // Wave generator functions + inline static int waveTriangle(SIDVoice &v); + inline static int waveSaw(SIDVoice &v); + inline static int wavePulse(SIDVoice &v); + inline static int waveTriSaw(SIDVoice &v); + inline static int waveTriPulse(SIDVoice &v); + inline static int waveSawPulse(SIDVoice &v); + inline static int waveTriSawPulse(SIDVoice &v); + inline static int waveNoise(SIDVoice &v); + inline static int getWaveSample(SIDVoice &v); + inline void updateShiftReg(SIDVoice &v); + // Envelope + inline int doEnvelopeGenerator(unsigned int cycles, SIDVoice &v); + static const unsigned int RateCountPeriod[16]; // Factors for A/D/S/R Timing + static const unsigned char envGenDRdivisors[256]; // For exponential approximation of D/R + /*static*/ unsigned int masterVolume; + // voice array for the 3 channels + SIDVoice voice[3]; + // filter stuff + unsigned char filterType; // filter type + unsigned int filterCutoff; // SID filter frequency + unsigned char filterResonance; // filter resonance (0..15) + double cutOffFreq[2048]; // filter cutoff frequency register + int resonanceCoeffDiv1024; // filter resonance * 1024 + int w0; // filter cutoff freq + void setResonance(); + void setFilterCutoff(); + int filterOutput(unsigned int cycles, int Vi); + int Vhp; // highpass + int Vbp; // bandpass + int Vlp; // lowpass + // + unsigned char lastByteWritten;// Last value written to the SID + int model_; + bool enableDigiBlaster; + unsigned int sampleRate; +}; + +/* + Wave outputs +*/ +inline int SIDsound::waveTriangle(SIDVoice &v) +{ + unsigned int msb = (v.ring ? v.accu ^ v.modulatedBy->accu : v.accu) + & 0x8000000; + // triangle wave 15 bit only + return ((msb ? ~v.accu : v.accu) >> 15) & 0xFFE; +} + +inline int SIDsound::waveSaw(SIDVoice &v) +{ + return (v.accu >> 16) & 0xFFF; +} + +inline int SIDsound::wavePulse(SIDVoice &v) +{ + // square wave starts high + return (v.test | ((v.accu >> 16) >= v.pw ? 0xFFF : 0x000)); +} + +inline int SIDsound::waveTriSaw(SIDVoice &v) +{ + unsigned int sm = (waveTriangle(v)) & (waveSaw(v)); + return (sm>>1) & (sm<<1); +} + +inline int SIDsound::waveTriPulse(SIDVoice &v) +{ + unsigned int sm = (waveTriangle(v)) & (wavePulse(v)); + return (sm>>1) & (sm<<1); +} + +inline int SIDsound::waveSawPulse(SIDVoice &v) +{ + return (waveSaw(v)) & (wavePulse(v)); +} + +inline int SIDsound::waveTriSawPulse(SIDVoice &v) +{ + unsigned int sm = (waveTriangle(v)) & (waveSaw(v)) & (wavePulse(v)); + return (sm>>1) & (sm<<1); +} + +#endif diff --git a/src/soloud/src/audiosource/tedsid/soloud_tedsid.cpp b/src/soloud/src/audiosource/tedsid/soloud_tedsid.cpp new file mode 100644 index 0000000..4e5f844 --- /dev/null +++ b/src/soloud/src/audiosource/tedsid/soloud_tedsid.cpp @@ -0,0 +1,256 @@ +/* +TED/SID module for SoLoud audio engine +Copyright (c) 2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include +#include +#include +#include "sid.h" +#include "ted.h" +#include "soloud_tedsid.h" +#include "soloud_file.h" + +namespace SoLoud +{ + + TedSidInstance::TedSidInstance(TedSid *aParent) + { + mParent = aParent; + mSampleCount = 0; + mSID = new SIDsound(mParent->mModel, 0); + mSID->setFrequency(0); + mSID->setSampleRate(TED_SOUND_CLOCK); + mSID->setFrequency(1); + + mTED = new TED(); + mTED->oscillatorInit(); + + mNextReg = 100; // NOP + mNextVal = 0; + int i; + for (i = 0; i < 128; i++) + mRegValues[i] = 0; + } + + unsigned int TedSidInstance::getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize) + { + unsigned int i; + for (i = 0; i < aSamplesToRead; i++) + { + tick(); + short sample; + mSID->calcSamples(&sample, 1); + short tedsample = 0; + mTED->renderSound(1, &tedsample); + aBuffer[i] = (sample + tedsample) / 8192.0f; + mSampleCount--; + } + return aSamplesToRead; + } + + void TedSidInstance::tick() + { + if (mParent->mFile == 0) + return; + + while (mSampleCount == 0) + { + mRegValues[mNextReg] = mNextVal; + if (mNextReg < 64) + { + mSID->write(mNextReg, mNextVal); + } + else + if (mNextReg < 64 + 5) + { + mTED->writeSoundReg(mNextReg - 64, mNextVal); + } +// mSampleCount = mParent->mFile->read16(); + mNextVal = mParent->mFile->read8(); + mNextReg = mParent->mFile->read8(); + if (mNextReg & 0x80) + { + // timestamp! + mSampleCount = ((int)(mNextReg & 0x7f) << 8) | mNextVal; + mNextVal = mParent->mFile->read8(); + mNextReg = mParent->mFile->read8(); + } + if (mParent->mFile->eof()) + mParent->mFile->seek(8); + } + } + + float TedSidInstance::getInfo(unsigned int aInfoKey) + { + return (float)mRegValues[aInfoKey & 127]; + } + + bool TedSidInstance::hasEnded() + { + return 0; + } + + TedSidInstance::~TedSidInstance() + { + delete mSID; + delete mTED; + } + + TedSid::TedSid() + { + mBaseSamplerate = TED_SOUND_CLOCK; + mChannels = 1; + mFile = 0; + mFileOwned = false; + } + + TedSid::~TedSid() + { + stop(); + if (mFileOwned) + delete mFile; + } + + result TedSid::loadMem(unsigned char *aMem, unsigned int aLength, bool aCopy, bool aTakeOwnership) + { + if (!aMem || aLength == 0) + return INVALID_PARAMETER; + MemoryFile *mf = new MemoryFile; + if (!mf) + return OUT_OF_MEMORY; + int res = mf->openMem(aMem, aLength, aCopy, aTakeOwnership); + if (res != SO_NO_ERROR) + { + delete mf; + return res; + } + res = loadFile(mf); + if (res != SO_NO_ERROR) + { + delete mf; + return res; + } + mFileOwned = aCopy || aTakeOwnership; + + return SO_NO_ERROR; + } + + result TedSid::load(const char *aFilename) + { + if (!aFilename) + return INVALID_PARAMETER; + DiskFile *df = new DiskFile; + if (!df) return OUT_OF_MEMORY; + int res = df->open(aFilename); + if (res != SO_NO_ERROR) + { + delete df; + return res; + } + res = loadFile(df); + if (res != SO_NO_ERROR) + { + delete df; + return res; + } + mFileOwned = true; + return SO_NO_ERROR; + } + + result TedSid::loadToMem(const char *aFilename) + { + if (!aFilename) + return INVALID_PARAMETER; + MemoryFile *mf = new MemoryFile; + if (!mf) return OUT_OF_MEMORY; + int res = mf->openToMem(aFilename); + if (res != SO_NO_ERROR) + { + delete mf; + return res; + } + res = loadFile(mf); + if (res != SO_NO_ERROR) + { + delete mf; + return res; + } + mFileOwned = true; + return SO_NO_ERROR; + } + + result TedSid::loadFileToMem(File *aFile) + { + if (!aFile) + return INVALID_PARAMETER; + MemoryFile *mf = new MemoryFile; + if (!mf) return OUT_OF_MEMORY; + int res = mf->openFileToMem(aFile); + if (res != SO_NO_ERROR) + { + delete mf; + return res; + } + res = loadFile(mf); + if (res != SO_NO_ERROR) + { + delete mf; + return res; + } + mFileOwned = true; + return SO_NO_ERROR; + } + + result TedSid::loadFile(File *aFile) + { + if (aFile == NULL) + return INVALID_PARAMETER; + if (mFileOwned) + delete mFile; + // Expect a file wih header and at least one reg write + if (aFile->length() < 4+4+2+2) return FILE_LOAD_FAILED; + + aFile->seek(0); + if (aFile->read8() != 'D') return FILE_LOAD_FAILED; + if (aFile->read8() != 'u') return FILE_LOAD_FAILED; + if (aFile->read8() != 'm') return FILE_LOAD_FAILED; + if (aFile->read8() != 'p') return FILE_LOAD_FAILED; + if (aFile->read8() != 0) return FILE_LOAD_FAILED; + mModel = aFile->read8(); + aFile->seek(8); + + mFile = aFile; + mFileOwned = false; + + + return SO_NO_ERROR; + } + + + AudioSourceInstance * TedSid::createInstance() + { + return new TedSidInstance(this); + } + +}; \ No newline at end of file diff --git a/src/soloud/src/audiosource/tedsid/ted.cpp b/src/soloud/src/audiosource/tedsid/ted.cpp new file mode 100644 index 0000000..21d5978 --- /dev/null +++ b/src/soloud/src/audiosource/tedsid/ted.cpp @@ -0,0 +1,236 @@ +#include +#include "ted.h" + + +#define PRECISION 0 +#define OSCRELOADVAL (0x3FF << PRECISION) +#define TED_SOUND_CLOCK (221680) + +TED::TED() +{ + sampleRate = TED_SOUND_CLOCK; + masterVolume = 8; + Volume = 8; + Snd1Status = 0; + Snd2Status = 0; + SndNoiseStatus = 0; + DAStatus = 0; + Freq1 = 0; + Freq2 = 0; + NoiseCounter = 0; + FlipFlop[0] = FlipFlop[1] = 0; + dcOutput[0] = dcOutput[1] = 0; + oscCount[0] = oscCount[1] = 0; + OscReload[0] = OscReload[1] = 0; + waveForm[0] = waveForm[1] = 0; + oscStep = 0; + memset(noise, 0, sizeof(noise)); + channelMask[0] = channelMask[1] = 0; + vol = 0; +} + +void TED::enableChannel(unsigned int channel, bool enable) +{ + channelMask[channel % 3] = enable ? -1 : 0; +} + +inline void TED::setFreq(unsigned int channel, int freq) +{ + dcOutput[channel] = (freq == 0x3FE) ? 1 : 0; + OscReload[channel] = ((freq + 1)&0x3FF) << PRECISION; +} + +void TED::oscillatorReset() +{ + FlipFlop[0] = dcOutput[0] = 0; + FlipFlop[1] = dcOutput[1] = 0; + oscCount[0] = 0; + oscCount[1] = 0; + NoiseCounter = 0; + Freq1 = Freq2 = 0; + DAStatus = Snd1Status = Snd2Status = 0; +} + +// call only once! +void TED::oscillatorInit() +{ + oscillatorReset(); + /* initialise im with 0xa8 */ + int im = 0xa8; + for (unsigned int i = 0; i<256; i++) { + noise[i] = im & 1; + im = (im<<1)+(1^((im>>7)&1)^((im>>5)&1)^((im>>4)&1)^((im>>1)&1)); + } + oscStep = (1 << PRECISION) << 0; + + // set player specific parameters + waveForm[0] = waveForm[1] = 1; + masterVolume = 8; + enableChannel(0, true); + enableChannel(1, true); + enableChannel(2, true); +} + +void TED::writeSoundReg(unsigned int reg, unsigned char value) +{ + + switch (reg) { + case 0: + Freq1 = (Freq1 & 0x300) | value; + setFreq(0, Freq1); + break; + case 1: + Freq2 = (Freq2 & 0x300) | value; + setFreq(1, Freq2); + break; + case 2: + Freq2 = (Freq2 & 0xFF) | (value << 8); + setFreq(1, Freq2); + break; + case 3: + DAStatus = value & 0x80; + if (DAStatus) { + FlipFlop[0] = 1; + FlipFlop[1] = 1; + oscCount[0] = OscReload[0]; + oscCount[1] = OscReload[1]; + NoiseCounter = 0xFF; + } + Volume = value & 0x0F; + if (Volume > 8) Volume = 8; + Volume = (Volume << 8) * masterVolume / 10; + Snd1Status = value & 0x10; + Snd2Status = value & 0x20; + SndNoiseStatus = value & 0x40; + break; + case 4: + Freq1 = (Freq1 & 0xFF) | (value << 8); + setFreq(0, Freq1); + break; + } +} + +inline unsigned int TED::waveSquare(unsigned int channel) +{ + return Volume; +} + +inline unsigned int TED::waveSawTooth(unsigned int channel) +{ + unsigned int mod; + +#if 0 + int msb = OSCRELOADVAL + 1 - OscReload[channel]; + int diff = 2 * msb - int(FlipFlop[channel]) * msb - int(oscCount[channel]) + int(OscReload[channel]); + //if (diff < 0) diff = 0; + //if (oscCount[channel] >= 0x3fa) diff = 0; + mod = (Volume * diff) / (2 * msb); +#else + int diff = int(oscCount[channel]) - int(OscReload[channel]); + if (diff < 0) diff = 0; + mod = (Volume * diff) / (OSCRELOADVAL + 1 - OscReload[channel]); +#endif + return mod; +} + +inline unsigned int TED::waveTriangle(unsigned int channel) +{ + unsigned int mod; + int msb; + +#if 0 + msb = OSCRELOADVAL + 1 - OscReload[channel]; + int diff = FlipFlop[channel] ? int(oscCount[channel]) - int(OscReload[channel]) + : int(OSCRELOADVAL) - int(oscCount[channel]); + //if (diff < 0) diff = 0; + //if (oscCount[channel] >= 0x3fa) diff = 0; + mod = (3 * Volume * diff / msb / 2); +#else + /* + msb = (OscReload[channel] + OSCRELOADVAL) / 2; + int diff = oscCount[channel] < msb ? oscCount[channel] - OscReload[channel] : OSCRELOADVAL - oscCount[channel]; + mod = (2 * diff * Volume / (OSCRELOADVAL - OscReload[channel] + 1)); + if (mod > Volume) mod = Volume; + */ + msb = (OscReload[channel] + OSCRELOADVAL) / 2; + mod = oscCount[channel] < msb ? oscCount[channel] : (oscCount[channel] - msb); + mod = (mod * Volume / msb); +#endif + return mod; +} + +inline unsigned int TED::getWaveSample(unsigned int channel, unsigned int wave) +{ + unsigned int sm; + + switch (wave) { + default: + case 1: // square + return waveSquare(channel); + break; + case 2: // sawtooth + return waveSawTooth(channel); + break; + case 4: // triangle + return waveTriangle(channel); + break; + + // combined waveforms á la SID + case 3: // square + sawtooth + sm = waveSawTooth(channel) + waveSquare(channel); + return sm /= 2; + break; + case 5: // square + triangle + sm = waveTriangle(channel) + waveSquare(channel); + return sm /= 2; + break; + case 6: // sawtooth + triangle + sm = waveTriangle(channel) + waveSawTooth(channel); + return sm /= 2; + break; + case 7: // square + sawtooth + triangle + sm = waveTriangle(channel) + waveSawTooth(channel) + waveSquare(channel); + return sm /= 3; + break; + } +} + +void TED::renderSound(unsigned int nrsamples, short *buffer) +{ + // Calculate the buffer... + if (DAStatus) {// digi? + short sample = 0;//audiohwspec->silence; + if (Snd1Status) sample = Volume; + if (Snd2Status) sample += Volume; + for (;nrsamples--;) { + *buffer++ = sample & channelMask[2]; + } + } else { + unsigned int result; + for (;nrsamples--;) { + // Channel 1 + if (dcOutput[0]) { + FlipFlop[0] = 1; + } else if ((oscCount[0] += oscStep) >= OSCRELOADVAL) { + FlipFlop[0] ^= 1; + oscCount[0] = OscReload[0] + (oscCount[0] - OSCRELOADVAL); + } + // Channel 2 + if (dcOutput[1]) { + FlipFlop[1] = 1; + } else if ((oscCount[1] += oscStep) >= OSCRELOADVAL) { + NoiseCounter = (NoiseCounter + 1) & 0xFF; + FlipFlop[1] ^= 1; + oscCount[1] = OscReload[1] + (oscCount[1] - OSCRELOADVAL); + } + result = (Snd1Status && FlipFlop[0]) ? (getWaveSample(0, waveForm[0]) & channelMask[0]) : 0; + if (Snd2Status && FlipFlop[1]) { + result += getWaveSample(1, waveForm[1]) & channelMask[1]; + } else if (SndNoiseStatus && noise[NoiseCounter] & channelMask[2]) { + result += Volume; + } + *buffer++ = result; + } // for + } +} + diff --git a/src/soloud/src/audiosource/tedsid/ted.h b/src/soloud/src/audiosource/tedsid/ted.h new file mode 100644 index 0000000..8f2b196 --- /dev/null +++ b/src/soloud/src/audiosource/tedsid/ted.h @@ -0,0 +1,44 @@ + +class TED +{ +public: + unsigned int masterVolume; + int Volume; + int Snd1Status; + int Snd2Status; + int SndNoiseStatus; + int DAStatus; + unsigned short Freq1; + unsigned short Freq2; + int NoiseCounter; + int FlipFlop[2]; + int dcOutput[2]; + int oscCount[2]; + int OscReload[2]; + int waveForm[2]; + int oscStep; + int sampleRate; + unsigned char noise[256]; // 0-8 + unsigned int channelMask[3]; + int vol; + + TED(); + void enableChannel(unsigned int channel, bool enable); + void setFreq(unsigned int channel, int freq); + void oscillatorReset(); + void oscillatorInit(); + void writeSoundReg(unsigned int reg, unsigned char value); + void storeToBuffer(short *buffer, unsigned int count); + unsigned int waveSquare(unsigned int channel); + unsigned int waveSawTooth(unsigned int channel); + unsigned int waveTriangle(unsigned int channel); + unsigned int getWaveSample(unsigned int channel, unsigned int wave); + void renderSound(unsigned int nrsamples, short *buffer); + void setMasterVolume(unsigned int shift); + void selectWaveForm(unsigned int channel, unsigned int wave); + void setplaybackSpeed(unsigned int speed); + unsigned int getTimeSinceLastReset(); + void setSampleRate(unsigned int value); + void setFilterOrder(unsigned int value); + void initFilter(unsigned int sampleRate_, unsigned int filterOrder_); +}; \ No newline at end of file diff --git a/src/soloud/src/audiosource/vic/soloud_vic.cpp b/src/soloud/src/audiosource/vic/soloud_vic.cpp new file mode 100644 index 0000000..0d7ac60 --- /dev/null +++ b/src/soloud/src/audiosource/vic/soloud_vic.cpp @@ -0,0 +1,168 @@ +/* +SoLoud audio engine +Copyright (c) 2015 Jari Komppa + +VIC 6560/6561 sound chip emulator +Copyright (c) 2015 Petri Hakkinen + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include +#include +#include +#include "soloud_vic.h" + +namespace SoLoud +{ + + VicInstance::VicInstance(Vic *aParent) + { + m_parent = aParent; + + for(int i = 0; i < 4; i++) + m_phase[i] = 0; + + m_noisePos = 0; + } + + VicInstance::~VicInstance() + { + } + + unsigned int VicInstance::getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize) + { + unsigned int phaseAdder[4] = { 0, 0, 0, 0 }; + for(int i = 0; i < 4; i++) + { + unsigned char reg = m_parent->getRegister(i); + if(reg >= 128) + { + float freq = m_parent->m_clocks[i] / (float)(reg < 255 ? 255 - reg : 1); + phaseAdder[i] = (unsigned int)(freq * 65536.0f / 44100.0f + 0.5f); + } + } + + for(int i = 0; i < (signed)aSamplesToRead; i++) + { + float s = 0.0f; + + // square waves + for(int v = 0; v < 3; v++) + { + if(phaseAdder[v] != 0) + { + s += (m_phase[v] < 32768 ? 0.5f : -0.5f); + m_phase[v] = (m_phase[v] + phaseAdder[v]) & 65535; + } + } + + // noise + if(phaseAdder[3] != 0) + { + s += (float)m_parent->m_noise[m_noisePos] / 255.0f - 0.5f; + + m_phase[3] += phaseAdder[3]; + + if(m_phase[3] >= 32768) + { + m_noisePos = (m_noisePos + 1) & 8191; + m_phase[3] &= 32767; + } + } + + aBuffer[i] = s / 4.0f; + } + return aSamplesToRead; + } + + bool VicInstance::hasEnded() + { + return false; + } + + Vic::Vic() + { + mBaseSamplerate = 44100; + setModel(PAL); + + for(int i = 0; i < MAX_REGS; i++) + m_regs[i] = 0; + + // Galois LFSR (source: https://en.wikipedia.org/wiki/Linear_feedback_shift_register) + unsigned short lfsr = 0xACE1u; + for(int i = 0; i < 8192; i++) + { + unsigned lsb = lfsr & 1; + lfsr >>= 1; + lfsr ^= (unsigned)(-(signed)lsb) & 0xB400u; + m_noise[i] = (lfsr & 0xff) ^ (lfsr >> 8); + } + } + + Vic::~Vic() + { + stop(); + } + + void Vic::setModel(int model) + { + m_model = model; + + switch(model) + { + case PAL: + m_clocks[0] = 4329.0f; + m_clocks[1] = 8659.0f; + m_clocks[2] = 17320.0f; + m_clocks[3] = 34640.0f; + break; + + case NTSC: + m_clocks[0] = 3995.0f; + m_clocks[1] = 7990.0f; + m_clocks[2] = 15980.0f; + m_clocks[3] = 31960.0f; + break; + } + } + + int Vic::getModel() const + { + return m_model; + } + + void Vic::setRegister(int reg, unsigned char value) + { + m_regs[reg] = value; + } + + unsigned char Vic::getRegister(int reg) + { + return m_regs[reg]; + } + + AudioSourceInstance * Vic::createInstance() + { + return new VicInstance(this); + } + +}; \ No newline at end of file diff --git a/src/soloud/src/audiosource/vizsn/soloud_vizsn.cpp b/src/soloud/src/audiosource/vizsn/soloud_vizsn.cpp new file mode 100644 index 0000000..5d654d2 --- /dev/null +++ b/src/soloud/src/audiosource/vizsn/soloud_vizsn.cpp @@ -0,0 +1,533 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +vizsn speech synthesizer (c) by Ville-Matias Heikkilä, +released under WTFPL, http://www.wtfpl.net/txt/copying/ +(in short, "do whatever you want to") + +Integration and changes to work with SoLoud by Jari Komppa, +released under same license. +*/ + +#include +#include +#include +#include +#include "soloud_vizsn.h" + +/* + + lähtöfunktiot: voice, noise + muut: pitch + volyymit: voice, asp, fric, bypass (4kpl) + resonaattorit: lp, nz (ar), npc, 1p, 2p, 3p, 4p, 5p, 6p, out (10kpl) + + + [voice] [noise] + | + | | + | | + (re.LP) | + | | + (*voice) (*asp) + | | + `--[+]--' + | + (ar.NZ) + | + (re.NPC) + | + .-----------. [noise] + | | | +(re.1P) (diff) (*fric) + | | | + | [+]------' + | | + [-]-(re.4P)--[ + | | + [-]-(re.3P)--[ <-- selvitäänkö kahdella tuossa vaiheessa? + | | + [-]-(re.2P)--[ + | | + | (*bypass) + | | + `----[-]----' + |' + (re.OUT) + | + out + + resonaattori: + + x = a*input + b*y + c*z + + ja tulos kiertää x=>y=>z + + antiresonaattori: + + x = a*input + b*y + c*z + + inputti kiertää i=>y=>z +*/ + +#define RLP 0 +#define RNZ 1 +#define RNPC 2 +#define ROUT 3 +#define R1P 4 +#define R2P 5 +#define R3P 6 +#define R4P 7 +#define R5P 8 +#define R6P 9 +#define P_A 0 +#define P_AE 1 +#define P_E 2 +#define P_OE 3 +#define P_O 4 +#define P_I 5 +#define P_Y 6 +#define P_U 7 +#define P_H 8 +#define P_V 9 +#define P_J 10 +#define P_S 11 +#define P_L 12 +#define P_R 13 +#define P_K 14 +#define P_T 15 +#define P_P 16 +#define P_N 17 +#define P_M 18 +#define P_NG 19 +#define P_NEW 12 +#define P_END -1 +#define P_CLR -2 + +#define filter(i,v) i##last=i+(i##last*v); +#define plosive(a) ((((a) >= P_K) && ((a) <= P_P)) || ((a) == P_CLR)) + +static const float vowtab[8][4][2] = +{ + /* a */ 0.10f, 1.6f, 0, 0, 0.2f, 1.5f, 0, 0, + /* ä */ 0.10f, 1.6f, 0, 0, 0.2f, 0, 0, 0, + + /* e */ 0.08f, 1.8f, 0, 0, 0.2f, -0.8f, 0, 0, + /* ö */ 0.08f, 1.8f, 0, 0, 0.3f, 0.9f, 0, 0, + /* o */ 0.08f, 1.8f, 0, 0, 0.2f, 1.6f, 0, 0, + + /* i */ 0.05f, 1.9f, 0, 0, 0.2f, -1.5f, 0, 0, + /* y */ 0.05f, 1.9f, 0, 0, 0.2f, 0.8f, 0, 0, + /* u */ 0.05f, 1.9f, 0, 0, 0.1f, 1.7f, 0, 0 +}; + +/* integerisoi (8bit): kerro kolmella */ +static const float voo[13][5 + 3 * 10] = +{ + /* frikatiivit & puolivokaalit */ + + /* h */ + 0.0f, 0.0f, 0.36f, 0.0f, 0.0f, 0.32f, 1.36f, -0.67f, 22.0f, -42.0f, 21.0f, 0.04f, 1.9f, -0.92f, 1.17f, 0.8f, -0.02f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + + /* v */ + 0.7f, 0.0f, 0.0f, 0.0f, 0.0f, 0.32f, 1.36f, -0.67f, 22.0f, -42.0f, 21.0f, 0.04f, 1.9f, -0.92f, 1.17f, 0.8f, -0.02f, + 0.005f, 1.9f, -0.95f, 0.0f, 0.0f, 0.0f, 0.04f, 1.5f, -0.93f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + + /* j */ + 0.7f, 0.0f, 0.0f, 0.0f, 0.0f, 0.32f, 1.36f, -0.67f, 22.0f, -42.0f, 21.0f, 0.04f, 1.9f, -0.92f, 1.17f, 0.8f, -0.02f, + 0.005f, 1.9f, -0.95f, 0.0f, 0.0f, 0.0f, 0.04f, -1.5f, -0.93f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + + /* s */ + 0.0f, 0.0f, 0.36f, 0.0f, 0.0f, 0.32f, 1.36f, -0.67f, 22.0f, -42.0f, 21.0f, 0.04f, 1.9f, -0.92f, 1.7f, 0.1f, -0.02f, + 0.0f, 0.1f, -0.02f, 0.0f, 1.76f, -0.85f, 0.01f, 0.42f, -0.93f, 0.02f, -1.37f, -0.68f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + + /* l */ + 0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 0.32f, 1.36f, -0.67f, 22.0f, -42.0f, 21.0f, 0.04f, 1.88f, -0.92f, 1.17f, 0.8f, -0.02f, + 0.1f, -0.5f, -0.93f, 0.6f, 1.0f, -0.93f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + + /* r */ + 0.8f, 0.4f, 0.0f, 0.0f, 0.0f, 0.32f, 1.36f, -0.67f, 22.0f, -42.0f, 21.0f, 0.04f, 1.9f, -0.92f, 1.17f, 0.8f, -0.02f, + -0.2f, 0.0f, -0.93f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + + /* klusiilit */ + + /* k */ + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.32f, 1.36f, -0.67f, 22.0f, -42.0f, 21.0f, 0.04f, 1.9f, -0.92f, 1.17f, 0.8f, -0.02f, + 0.0f, 1.95f, -0.94f, 0.4f, 1.0f, -0.93f, 0.6f, 1.0f, -0.89f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + + /* t */ + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.32f, 1.36f, -0.67f, 22.0f, -42.0f, 21.0f, 0.04f, 1.9f, -0.92f, 1.17f, 0.8f, -0.02f, + 0.0f, 1.6f, -0.94f, 0.0f, 0.3f, -0.93f, 1.5f, -0.6f, -0.89f, 1.8f, -1.5f, -0.68f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + + /* p */ + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.32f, 1.36f, -0.67f, 22.0f, -42.0f, 21.0f, 0.04f, 1.9f, -0.92f, 1.17f, 0.8f, -0.02f, + 0.01f, 1.9f, -0.94f, 1.5f, 1.7f, -0.93f, 1.0f, 1.0f, -0.89f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + + /* nasaalit */ + + /* n */ + 0.81f, 0.0f, 0.0f, 0.0f, 0.0f, 0.32f, 1.36f, -0.67f, 15.0f, -20.0f, 10.0f, 0.04f, 1.88f, -0.92f, 1.17f, 0.1f, -0.02f, + 0.02f, 1.83f, -0.97f, 0.10f, 0.26f, -0.83f, 0.06f, -0.85f, -0.82f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + + /* m */ + 0.81f, 0.0f, 0.0f, 0.0f, 0.0f, 0.32f, 1.36f, -0.67f, 15.0f, -20.0f, 10.0f, 0.04f, 1.88f, -0.92f, 1.17f, 0.1f, -0.02f, + 0.08f, 1.5f, -0.94f, 0.06f, 1.0f, -0.9f, 0.05f, 1.5f, -0.89f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + + /* ng */ + 0.81f, 0.0f, 0.0f, 0.0f, 0.0f, 0.32f, 1.36f, -0.67f, 15.0f, -20.0f, 10.0f, 0.04f, 1.88f, -0.92f, 1.17f, 0.1f, -0.02f, + 0.1f, 1.6f, -0.94f, 0.7f, -1.0f, -0.91f, 0.1f, 1.16f, -0.91f, 0.03f, -1.3f, -0.68f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f +}; + +static const char keyz[] = +{ + P_A, P_P, P_S, P_T, P_E, P_V, P_NG, + P_H, P_I, P_J, P_K, P_L, P_M, P_N, + P_O, P_P, P_K, P_R, P_S, P_T, P_U, + P_V, P_U, P_S, P_Y, P_S, P_AE, P_OE +}; + +namespace SoLoud +{ + float VizsnResonator::resonate(float i) + { + float x = (a * i) + (b * p1) + (c * p2); + p2 = p1; + p1 = x; + return x; + } + + float VizsnResonator::antiresonate(float i) + { + float x = a * i + b * p1 + c * p2; + p2 = p1; + p1 = i; + return x; + } + VizsnInstance::VizsnInstance(Vizsn *aParent) + { + mParent = aParent; + mPtr = 0; + mCurrentVoiceType = 6; + memset(mEchobuf, 0, 1024 * sizeof(int)); + mPitch = 800; + mS = mParent->mText; + mBufwrite = 0; + mBufread = 0; + mA = 0; + mB = 100; + mOrgv = -1; + mGlotlast = 0; + } + + VizsnInstance::~VizsnInstance() + { + } + + unsigned int VizsnInstance::getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize) + { + unsigned int idx = 0; + int i, j; + if (mBufwrite > mBufread) + { + for (; mBufwrite > mBufread && idx < aSamplesToRead; mBufread++) + { + aBuffer[idx] = mBuf[mBufread]; + idx++; + } + } + if (idx == aSamplesToRead) return aSamplesToRead; + mBufwrite = mBufread = 0; + while (idx + mBufwrite < aSamplesToRead) + { + setphone(&mBank0, *mS, mPitch); + + if (*mS == P_END) + { + mBuf[mBufwrite] = 0; + mBufwrite++; + SOLOUD_ASSERT(mBufwrite < 2048); + break; + } + + setphone(&mBank1, mS[1], mPitch);//pitch+=50); -- RAISE SOUND + + slidePrepare(50); + + mNper = 0; + + for (i = plosive(*mS) ? 100 : 300; i; i--) + { + mBuf[mBufwrite] = genwave(); + mBufwrite++; + SOLOUD_ASSERT(mBufwrite < 2048); + } + + if (!plosive(mS[1])) + { + for (i = 50; i; i--) + { + for (j = 10; j; j--) + { + mBuf[mBufwrite] = genwave(); + mBufwrite++; + SOLOUD_ASSERT(mBufwrite < 2048); + } + slideTick(); + } + } + else + { + for (i = 50; i; i--) + { + for (j = 3; j; j--) + { + mBuf[mBufwrite] = genwave(); + mBufwrite++; + SOLOUD_ASSERT(mBufwrite < 2048); + } + } + } + + mS++; + + memcpy(&mBank0, &mBank1, sizeof(VizsnBank)); + } + for (; idx < aSamplesToRead; idx++) + { + aBuffer[idx] = mBuf[mBufread]; + mBufread++; + } + return aSamplesToRead; + } + + float VizsnInstance::vcsrc(int aPitch, int aVoicetype) + { + mA += aPitch; + + if (mOrgv != aVoicetype) + { + mOrgv = aVoicetype; + mA = 0; + mB = 100; + } + + switch (aVoicetype) + { + case 0: return (mA & (256 + 128 + 32)) * 5 * 0.0002f; + case 1: return (float)(sin(mA * 0.0002) * cos(mA * 0.0003) * 0.2f + ((rand() % 200 - 100) / 300.0f)); // ilmava + case 2: return (float)tan(mA*0.00002)*0.01f; // burpy + case 3: return ((mA & 65535) > 32768 ? 65535 : 0) * 0.00001f; // square wave + case 4: return mA * mA * 0.0000000002f; // kuisku + case 5: mA += 3; mB++; return ((mA & 255) > ((mB >> 2) & 255)) ? 0.3f : 0.0f; + case 7: return ((mA >> 8) & (256 + 128)) * 0.001f; // robottipulssi + case 8: return (float)(rand() % (1 + ((mA & 65535) >> 8))) / 256; // -- hiukka ihmisempi tsaatana + case 9: return ((float)(rand() & 32767)) / 32767; // -- noise: tsaatana + case 6: // fallthrough + default: return (mA & 65535) * (0.001f / 256); /*-- sawtooth: near natural */ + } + } + + float VizsnInstance::noisrc() + { + return ((float)(rand() & 32767)) / 32768; + } + + float VizsnInstance::genwave() + { + float s, o, noise, voice, glot, parglot; + int ob; + + noise = noisrc(); + + if (mNper > mNmod) + { + noise *= 0.5f; + } + + s = mBank0.frica * noise; + + voice = vcsrc((int)floor(mBank0.pitch), mCurrentVoiceType); + voice = mBank0.r[RLP].resonate(voice); + + if (mNper < mNopen) + { + voice += mBank0.breth * noise; + } + + parglot = glot = (mBank0.voice * voice) + (mBank0.aspir * noise); + + parglot = mBank0.r[RNZ].antiresonate(parglot); + parglot = mBank0.r[RNPC].resonate(parglot); + s += (parglot - mGlotlast); + mGlotlast = parglot; + o = mBank0.r[R1P].resonate(parglot); + + int i; + for (i = R4P; i > R2P; i--) + { + o = mBank0.r[i].resonate(s) - o; + } + + o = mBank0.r[ROUT].resonate(mBank0.bypas * s - o); + + /*********/ + + ob = (int)floor(o * 400 * 256 + (mEchobuf[mPtr] / 4)); + mEchobuf[mPtr] = ob; + mPtr = (mPtr + 1) & 1023; + + ob = (ob >> 8) + 128; + + if (ob < 0) ob = 0; + if (ob > 255) ob = 255; + + mNper++; + + return ob * (1.0f / 255.0f); + } + + void VizsnInstance::setphone(VizsnBank *aB, char aP, float aPitch) + { + int i; + aB->frica = aB->aspir = aB->bypas = aB->breth = aB->voice = 0; + aB->pitch = mPitch; + + if (aP < 0) + { + for (i = 0; i < 10; i++) + { + aB->r[i].p1 = aB->r[i].p2 = aB->r[i].a = aB->r[i].b = aB->r[i].c = 0; + } + } + else + { + if (aP < 8) + { + /* vokaali */ + VizsnResonator *r = aB->r; + const float *s = vowtab[aP][0]; + + r[R1P].c = -0.95f; r[R2P].c = -0.93f; r[R3P].c = -0.88f; r[R4P].c = -0.67f; + r[RLP].a = 0.31f; r[RLP].b = 1.35f; r[RLP].c = -0.67f; + r[RNZ].a = 22.0f; r[RNZ].b = -42.0f; r[RNZ].c = 21.0f; + r[RNPC].a = 0.04f; r[RNPC].b = 1.87f; r[RNPC].c = -0.92f; + r[ROUT].a = 1.16f; r[ROUT].b = 0.08f; r[ROUT].c = -0.02f; + + r += R1P; + + for (i = 4; i; i--) + { + r->a = *s++; + r->b = *s++; + r++; + } + + aB->voice = 0.8f; + } + else + { + /* v */ + int i; + const float *v = voo[aP - 8]; + + aB->voice = *v++; + aB->aspir = *v++; + aB->frica = *v++; + aB->bypas = *v++; + aB->breth = *v++; + + for (i = 0; i < 10; i++) + { + aB->r[i].a = *v++; + aB->r[i].b = *v++; + aB->r[i].c = *v++; + } + + aB->voice = 0.8f; + + aB->breth = 0.18f; + } + } + } + + void VizsnInstance::slidePrepare(int aNumtix) + { + int i; + + for (i = 0; i < 10; i++) + { + mBank0to1.r[i].a = (mBank1.r[i].a - mBank0.r[i].a) / aNumtix; + mBank0to1.r[i].b = (mBank1.r[i].b - mBank0.r[i].b) / aNumtix; + mBank0to1.r[i].c = (mBank1.r[i].c - mBank0.r[i].c) / aNumtix; + } + + mBank0to1.pitch = (mBank1.pitch - mBank0.pitch) / aNumtix; + } + + void VizsnInstance::slideTick() + { + int i; + + for (i = 0; i < 10; i++) + { + mBank0.r[i].a += mBank0to1.r[i].a; + mBank0.r[i].b += mBank0to1.r[i].b; + mBank0.r[i].c += mBank0to1.r[i].c; + } + + mBank0.pitch += mBank0to1.pitch; + } + + bool VizsnInstance::hasEnded() + { + return *mS == P_END; + } + + Vizsn::Vizsn() + { + mBaseSamplerate = 8000; + mText = 0; + } + + Vizsn::~Vizsn() + { + stop(); + } + + AudioSourceInstance * Vizsn::createInstance() + { + return new VizsnInstance(this); + } + + void Vizsn::setText(char *aText) + { + if (!aText) + return; + stop(); + delete[] mText; + int len = (int)strlen(aText); + mText = new char[len + 3]; + memcpy(mText+1, aText, len); + mText[0] = P_CLR; + int i; + for (i = 0; i < len; i++) + { + int c = mText[i + 1]; + if (c == '\x84' || c == -124) c = '{'; // ä + if (c == '\x94' || c == -108) c = '|'; // ö + if (c >= 'a' && c <= '|') + { + mText[i + 1] = keyz[c - 'a']; + } + else + { + mText[i + 1] = P_CLR; + } + } + mText[len + 1] = P_END; + mText[len + 2] = 0; + } + +}; diff --git a/src/soloud/src/audiosource/wav/dr_flac.h b/src/soloud/src/audiosource/wav/dr_flac.h new file mode 100644 index 0000000..fab4212 --- /dev/null +++ b/src/soloud/src/audiosource/wav/dr_flac.h @@ -0,0 +1,8154 @@ +// FLAC audio decoder. Public domain. See "unlicense" statement at the end of this file. +// dr_flac - v0.10.0 - 2018-09-11 +// +// David Reid - mackron@gmail.com + +// USAGE +// +// dr_flac is a single-file library. To use it, do something like the following in one .c file. +// #define DR_FLAC_IMPLEMENTATION +// #include "dr_flac.h" +// +// You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, +// do something like the following: +// +// drflac* pFlac = drflac_open_file("MySong.flac"); +// if (pFlac == NULL) { +// // Failed to open FLAC file +// } +// +// drflac_int32* pSamples = malloc(pFlac->totalSampleCount * sizeof(drflac_int32)); +// drflac_uint64 numberOfInterleavedSamplesActuallyRead = drflac_read_pcm_frames_s32(pFlac, pFlac->totalSampleCount, pSamples); +// +// The drflac object represents the decoder. It is a transparent type so all the information you need, such as the number of +// channels and the bits per sample, should be directly accessible - just make sure you don't change their values. Samples are +// always output as interleaved signed 32-bit PCM. In the example above a native FLAC stream was opened, however dr_flac has +// seamless support for Ogg encapsulated FLAC streams as well. +// +// You do not need to decode the entire stream in one go - you just specify how many samples you'd like at any given time and +// the decoder will give you as many samples as it can, up to the amount requested. Later on when you need the next batch of +// samples, just call it again. Example: +// +// while (drflac_read_pcm_frames_s32(pFlac, chunkSize, pChunkSamples) > 0) { +// do_something(); +// } +// +// You can seek to a specific sample with drflac_seek_to_sample(). The given sample is based on interleaving. So for example, +// if you were to seek to the sample at index 0 in a stereo stream, you'll be seeking to the first sample of the left channel. +// The sample at index 1 will be the first sample of the right channel. The sample at index 2 will be the second sample of the +// left channel, etc. +// +// +// If you just want to quickly decode an entire FLAC file in one go you can do something like this: +// +// unsigned int channels; +// unsigned int sampleRate; +// drflac_uint64 totalSampleCount; +// drflac_int32* pSampleData = drflac_open_and_decode_file_s32("MySong.flac", &channels, &sampleRate, &totalSampleCount); +// if (pSampleData == NULL) { +// // Failed to open and decode FLAC file. +// } +// +// ... +// +// drflac_free(pSampleData); +// +// +// You can read samples as signed 16-bit integer and 32-bit floating-point PCM with the *_s16() and *_f32() family of APIs +// respectively, but note that these should be considered lossy. +// +// +// If you need access to metadata (album art, etc.), use drflac_open_with_metadata(), drflac_open_file_with_metdata() or +// drflac_open_memory_with_metadata(). The rationale for keeping these APIs separate is that they're slightly slower than the +// normal versions and also just a little bit harder to use. +// +// dr_flac reports metadata to the application through the use of a callback, and every metadata block is reported before +// drflac_open_with_metdata() returns. +// +// +// The main opening APIs (drflac_open(), etc.) will fail if the header is not present. The presents a problem in certain +// scenarios such as broadcast style streams like internet radio where the header may not be present because the user has +// started playback mid-stream. To handle this, use the relaxed APIs: drflac_open_relaxed() and drflac_open_with_metadata_relaxed(). +// +// It is not recommended to use these APIs for file based streams because a missing header would usually indicate a +// corrupted or perverse file. In addition, these APIs can take a long time to initialize because they may need to spend +// a lot of time finding the first frame. +// +// +// +// OPTIONS +// #define these options before including this file. +// +// #define DR_FLAC_NO_STDIO +// Disable drflac_open_file() and family. +// +// #define DR_FLAC_NO_OGG +// Disables support for Ogg/FLAC streams. +// +// #define DR_FLAC_BUFFER_SIZE +// Defines the size of the internal buffer to store data from onRead(). This buffer is used to reduce the number of calls +// back to the client for more data. Larger values means more memory, but better performance. My tests show diminishing +// returns after about 4KB (which is the default). Consider reducing this if you have a very efficient implementation of +// onRead(), or increase it if it's very inefficient. Must be a multiple of 8. +// +// #define DR_FLAC_NO_CRC +// Disables CRC checks. This will offer a performance boost when CRC is unnecessary. +// +// #define DR_FLAC_NO_SIMD +// Disables SIMD optimizations (SSE on x86/x64 architectures). Use this if you are having compatibility issues with your +// compiler. +// +// +// +// QUICK NOTES +// - dr_flac does not currently support changing the sample rate nor channel count mid stream. +// - Audio data is output as signed 32-bit PCM, regardless of the bits per sample the FLAC stream is encoded as. +// - This has not been tested on big-endian architectures. +// - dr_flac is not thread-safe, but its APIs can be called from any thread so long as you do your own synchronization. +// - When using Ogg encapsulation, a corrupted metadata block will result in drflac_open_with_metadata() and drflac_open() +// returning inconsistent samples. + +#ifndef dr_flac_h +#define dr_flac_h + +#include + +#if defined(_MSC_VER) && _MSC_VER < 1600 +typedef signed char drflac_int8; +typedef unsigned char drflac_uint8; +typedef signed short drflac_int16; +typedef unsigned short drflac_uint16; +typedef signed int drflac_int32; +typedef unsigned int drflac_uint32; +typedef signed __int64 drflac_int64; +typedef unsigned __int64 drflac_uint64; +#else +#include +typedef int8_t drflac_int8; +typedef uint8_t drflac_uint8; +typedef int16_t drflac_int16; +typedef uint16_t drflac_uint16; +typedef int32_t drflac_int32; +typedef uint32_t drflac_uint32; +typedef int64_t drflac_int64; +typedef uint64_t drflac_uint64; +#endif +typedef drflac_uint8 drflac_bool8; +typedef drflac_uint32 drflac_bool32; +#define DRFLAC_TRUE 1 +#define DRFLAC_FALSE 0 + +#if defined(_MSC_VER) && _MSC_VER >= 1700 // Visual Studio 2012 +#define DRFLAC_DEPRECATED __declspec(deprecated) +#elif (defined(__GNUC__) && __GNUC__ >= 4) +#define DRFLAC_DEPRECATED __attribute__((deprecated)) +#elif (defined(__clang__) && __has_feature(attribute_deprecated)) +#define DRFLAC_DEPRECATED __attribute__((deprecated)) +#else +#define DRFLAC_DEPRECATED +#endif + +// As data is read from the client it is placed into an internal buffer for fast access. This controls the +// size of that buffer. Larger values means more speed, but also more memory. In my testing there is diminishing +// returns after about 4KB, but you can fiddle with this to suit your own needs. Must be a multiple of 8. +#ifndef DR_FLAC_BUFFER_SIZE +#define DR_FLAC_BUFFER_SIZE 4096 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Check if we can enable 64-bit optimizations. +#if defined(_WIN64) || defined(_LP64) || defined(__LP64__) +#define DRFLAC_64BIT +#endif + +#ifdef DRFLAC_64BIT +typedef drflac_uint64 drflac_cache_t; +#else +typedef drflac_uint32 drflac_cache_t; +#endif + +// The various metadata block types. +#define DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO 0 +#define DRFLAC_METADATA_BLOCK_TYPE_PADDING 1 +#define DRFLAC_METADATA_BLOCK_TYPE_APPLICATION 2 +#define DRFLAC_METADATA_BLOCK_TYPE_SEEKTABLE 3 +#define DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT 4 +#define DRFLAC_METADATA_BLOCK_TYPE_CUESHEET 5 +#define DRFLAC_METADATA_BLOCK_TYPE_PICTURE 6 +#define DRFLAC_METADATA_BLOCK_TYPE_INVALID 127 + +// The various picture types specified in the PICTURE block. +#define DRFLAC_PICTURE_TYPE_OTHER 0 +#define DRFLAC_PICTURE_TYPE_FILE_ICON 1 +#define DRFLAC_PICTURE_TYPE_OTHER_FILE_ICON 2 +#define DRFLAC_PICTURE_TYPE_COVER_FRONT 3 +#define DRFLAC_PICTURE_TYPE_COVER_BACK 4 +#define DRFLAC_PICTURE_TYPE_LEAFLET_PAGE 5 +#define DRFLAC_PICTURE_TYPE_MEDIA 6 +#define DRFLAC_PICTURE_TYPE_LEAD_ARTIST 7 +#define DRFLAC_PICTURE_TYPE_ARTIST 8 +#define DRFLAC_PICTURE_TYPE_CONDUCTOR 9 +#define DRFLAC_PICTURE_TYPE_BAND 10 +#define DRFLAC_PICTURE_TYPE_COMPOSER 11 +#define DRFLAC_PICTURE_TYPE_LYRICIST 12 +#define DRFLAC_PICTURE_TYPE_RECORDING_LOCATION 13 +#define DRFLAC_PICTURE_TYPE_DURING_RECORDING 14 +#define DRFLAC_PICTURE_TYPE_DURING_PERFORMANCE 15 +#define DRFLAC_PICTURE_TYPE_SCREEN_CAPTURE 16 +#define DRFLAC_PICTURE_TYPE_BRIGHT_COLORED_FISH 17 +#define DRFLAC_PICTURE_TYPE_ILLUSTRATION 18 +#define DRFLAC_PICTURE_TYPE_BAND_LOGOTYPE 19 +#define DRFLAC_PICTURE_TYPE_PUBLISHER_LOGOTYPE 20 + +typedef enum +{ + drflac_container_native, + drflac_container_ogg, + drflac_container_unknown +} drflac_container; + +typedef enum +{ + drflac_seek_origin_start, + drflac_seek_origin_current +} drflac_seek_origin; + +// Packing is important on this structure because we map this directly to the raw data within the SEEKTABLE metadata block. +#pragma pack(2) +typedef struct +{ + drflac_uint64 firstSample; + drflac_uint64 frameOffset; // The offset from the first byte of the header of the first frame. + drflac_uint16 sampleCount; +} drflac_seekpoint; +#pragma pack() + +typedef struct +{ + drflac_uint16 minBlockSize; + drflac_uint16 maxBlockSize; + drflac_uint32 minFrameSize; + drflac_uint32 maxFrameSize; + drflac_uint32 sampleRate; + drflac_uint8 channels; + drflac_uint8 bitsPerSample; + drflac_uint64 totalSampleCount; + drflac_uint8 md5[16]; +} drflac_streaminfo; + +typedef struct +{ + // The metadata type. Use this to know how to interpret the data below. + drflac_uint32 type; + + // A pointer to the raw data. This points to a temporary buffer so don't hold on to it. It's best to + // not modify the contents of this buffer. Use the structures below for more meaningful and structured + // information about the metadata. It's possible for this to be null. + const void* pRawData; + + // The size in bytes of the block and the buffer pointed to by pRawData if it's non-NULL. + drflac_uint32 rawDataSize; + + union + { + drflac_streaminfo streaminfo; + + struct + { + int unused; + } padding; + + struct + { + drflac_uint32 id; + const void* pData; + drflac_uint32 dataSize; + } application; + + struct + { + drflac_uint32 seekpointCount; + const drflac_seekpoint* pSeekpoints; + } seektable; + + struct + { + drflac_uint32 vendorLength; + const char* vendor; + drflac_uint32 commentCount; + const void* pComments; + } vorbis_comment; + + struct + { + char catalog[128]; + drflac_uint64 leadInSampleCount; + drflac_bool32 isCD; + drflac_uint8 trackCount; + const void* pTrackData; + } cuesheet; + + struct + { + drflac_uint32 type; + drflac_uint32 mimeLength; + const char* mime; + drflac_uint32 descriptionLength; + const char* description; + drflac_uint32 width; + drflac_uint32 height; + drflac_uint32 colorDepth; + drflac_uint32 indexColorCount; + drflac_uint32 pictureDataSize; + const drflac_uint8* pPictureData; + } picture; + } data; +} drflac_metadata; + + +// Callback for when data needs to be read from the client. +// +// pUserData [in] The user data that was passed to drflac_open() and family. +// pBufferOut [out] The output buffer. +// bytesToRead [in] The number of bytes to read. +// +// Returns the number of bytes actually read. +// +// A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until +// either the entire bytesToRead is filled or you have reached the end of the stream. +typedef size_t (* drflac_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); + +// Callback for when data needs to be seeked. +// +// pUserData [in] The user data that was passed to drflac_open() and family. +// offset [in] The number of bytes to move, relative to the origin. Will never be negative. +// origin [in] The origin of the seek - the current position or the start of the stream. +// +// Returns whether or not the seek was successful. +// +// The offset will never be negative. Whether or not it is relative to the beginning or current position is determined +// by the "origin" parameter which will be either drflac_seek_origin_start or drflac_seek_origin_current. +typedef drflac_bool32 (* drflac_seek_proc)(void* pUserData, int offset, drflac_seek_origin origin); + +// Callback for when a metadata block is read. +// +// pUserData [in] The user data that was passed to drflac_open() and family. +// pMetadata [in] A pointer to a structure containing the data of the metadata block. +// +// Use pMetadata->type to determine which metadata block is being handled and how to read the data. +typedef void (* drflac_meta_proc)(void* pUserData, drflac_metadata* pMetadata); + + +// Structure for internal use. Only used for decoders opened with drflac_open_memory. +typedef struct +{ + const drflac_uint8* data; + size_t dataSize; + size_t currentReadPos; +} drflac__memory_stream; + +// Structure for internal use. Used for bit streaming. +typedef struct +{ + // The function to call when more data needs to be read. + drflac_read_proc onRead; + + // The function to call when the current read position needs to be moved. + drflac_seek_proc onSeek; + + // The user data to pass around to onRead and onSeek. + void* pUserData; + + + // The number of unaligned bytes in the L2 cache. This will always be 0 until the end of the stream is hit. At the end of the + // stream there will be a number of bytes that don't cleanly fit in an L1 cache line, so we use this variable to know whether + // or not the bistreamer needs to run on a slower path to read those last bytes. This will never be more than sizeof(drflac_cache_t). + size_t unalignedByteCount; + + // The content of the unaligned bytes. + drflac_cache_t unalignedCache; + + // The index of the next valid cache line in the "L2" cache. + drflac_uint32 nextL2Line; + + // The number of bits that have been consumed by the cache. This is used to determine how many valid bits are remaining. + drflac_uint32 consumedBits; + + // The cached data which was most recently read from the client. There are two levels of cache. Data flows as such: + // Client -> L2 -> L1. The L2 -> L1 movement is aligned and runs on a fast path in just a few instructions. + drflac_cache_t cacheL2[DR_FLAC_BUFFER_SIZE/sizeof(drflac_cache_t)]; + drflac_cache_t cache; + + // CRC-16. This is updated whenever bits are read from the bit stream. Manually set this to 0 to reset the CRC. For FLAC, this + // is reset to 0 at the beginning of each frame. + drflac_uint16 crc16; + drflac_cache_t crc16Cache; // A cache for optimizing CRC calculations. This is filled when when the L1 cache is reloaded. + drflac_uint32 crc16CacheIgnoredBytes; // The number of bytes to ignore when updating the CRC-16 from the CRC-16 cache. +} drflac_bs; + +typedef struct +{ + // The type of the subframe: SUBFRAME_CONSTANT, SUBFRAME_VERBATIM, SUBFRAME_FIXED or SUBFRAME_LPC. + drflac_uint8 subframeType; + + // The number of wasted bits per sample as specified by the sub-frame header. + drflac_uint8 wastedBitsPerSample; + + // The order to use for the prediction stage for SUBFRAME_FIXED and SUBFRAME_LPC. + drflac_uint8 lpcOrder; + + // The number of bits per sample for this subframe. This is not always equal to the current frame's bit per sample because + // an extra bit is required for side channels when interchannel decorrelation is being used. + drflac_uint32 bitsPerSample; + + // A pointer to the buffer containing the decoded samples in the subframe. This pointer is an offset from drflac::pExtraData. Note that + // it's a signed 32-bit integer for each value. + drflac_int32* pDecodedSamples; +} drflac_subframe; + +typedef struct +{ + // If the stream uses variable block sizes, this will be set to the index of the first sample. If fixed block sizes are used, this will + // always be set to 0. + drflac_uint64 sampleNumber; + + // If the stream uses fixed block sizes, this will be set to the frame number. If variable block sizes are used, this will always be 0. + drflac_uint32 frameNumber; + + // The sample rate of this frame. + drflac_uint32 sampleRate; + + // The number of samples in each sub-frame within this frame. + drflac_uint16 blockSize; + + // The channel assignment of this frame. This is not always set to the channel count. If interchannel decorrelation is being used this + // will be set to DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE, DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE or DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE. + drflac_uint8 channelAssignment; + + // The number of bits per sample within this frame. + drflac_uint8 bitsPerSample; + + // The frame's CRC. + drflac_uint8 crc8; +} drflac_frame_header; + +typedef struct +{ + // The header. + drflac_frame_header header; + + // The number of samples left to be read in this frame. This is initially set to the block size multiplied by the channel count. As samples + // are read, this will be decremented. When it reaches 0, the decoder will see this frame as fully consumed and load the next frame. + drflac_uint32 samplesRemaining; + + // The list of sub-frames within the frame. There is one sub-frame for each channel, and there's a maximum of 8 channels. + drflac_subframe subframes[8]; +} drflac_frame; + +typedef struct +{ + // The function to call when a metadata block is read. + drflac_meta_proc onMeta; + + // The user data posted to the metadata callback function. + void* pUserDataMD; + + + // The sample rate. Will be set to something like 44100. + drflac_uint32 sampleRate; + + // The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. Maximum 8. This is set based on the + // value specified in the STREAMINFO block. + drflac_uint8 channels; + + // The bits per sample. Will be set to something like 16, 24, etc. + drflac_uint8 bitsPerSample; + + // The maximum block size, in samples. This number represents the number of samples in each channel (not combined). + drflac_uint16 maxBlockSize; + + // The total number of samples making up the stream. This includes every channel. For example, if the stream has 2 channels, + // with each channel having a total of 4096, this value will be set to 2*4096 = 8192. Can be 0 in which case it's still a + // valid stream, but just means the total sample count is unknown. Likely the case with streams like internet radio. + drflac_uint64 totalSampleCount; + drflac_uint64 totalPCMFrameCount; // <-- Equal to totalSampleCount / channels. + + + // The container type. This is set based on whether or not the decoder was opened from a native or Ogg stream. + drflac_container container; + + // The number of seekpoints in the seektable. + drflac_uint32 seekpointCount; + + + // Information about the frame the decoder is currently sitting on. + drflac_frame currentFrame; + + // The index of the sample the decoder is currently sitting on. This is only used for seeking. + drflac_uint64 currentSample; + + // The position of the first frame in the stream. This is only ever used for seeking. + drflac_uint64 firstFramePos; + + + // A hack to avoid a malloc() when opening a decoder with drflac_open_memory(). + drflac__memory_stream memoryStream; + + + // A pointer to the decoded sample data. This is an offset of pExtraData. + drflac_int32* pDecodedSamples; + + // A pointer to the seek table. This is an offset of pExtraData, or NULL if there is no seek table. + drflac_seekpoint* pSeekpoints; + + // Internal use only. Only used with Ogg containers. Points to a drflac_oggbs object. This is an offset of pExtraData. + void* _oggbs; + + // The bit streamer. The raw FLAC data is fed through this object. + drflac_bs bs; + + // Variable length extra data. We attach this to the end of the object so we can avoid unnecessary mallocs. + drflac_uint8 pExtraData[1]; +} drflac; + + +// Opens a FLAC decoder. +// +// onRead [in] The function to call when data needs to be read from the client. +// onSeek [in] The function to call when the read position of the client data needs to move. +// pUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. +// +// Returns a pointer to an object representing the decoder. +// +// Close the decoder with drflac_close(). +// +// This function will automatically detect whether or not you are attempting to open a native or Ogg encapsulated +// FLAC, both of which should work seamlessly without any manual intervention. Ogg encapsulation also works with +// multiplexed streams which basically means it can play FLAC encoded audio tracks in videos. +// +// This is the lowest level function for opening a FLAC stream. You can also use drflac_open_file() and drflac_open_memory() +// to open the stream from a file or from a block of memory respectively. +// +// The STREAMINFO block must be present for this to succeed. Use drflac_open_relaxed() to open a FLAC stream where +// the header may not be present. +// +// See also: drflac_open_file(), drflac_open_memory(), drflac_open_with_metadata(), drflac_close() +drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData); + +// The same as drflac_open(), except attempts to open the stream even when a header block is not present. +// +// Because the header is not necessarily available, the caller must explicitly define the container (Native or Ogg). Do +// not set this to drflac_container_unknown - that is for internal use only. +// +// Opening in relaxed mode will continue reading data from onRead until it finds a valid frame. If a frame is never +// found it will continue forever. To abort, force your onRead callback to return 0, which dr_flac will use as an +// indicator that the end of the stream was found. +drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData); + +// Opens a FLAC decoder and notifies the caller of the metadata chunks (album art, etc.). +// +// onRead [in] The function to call when data needs to be read from the client. +// onSeek [in] The function to call when the read position of the client data needs to move. +// onMeta [in] The function to call for every metadata block. +// pUserData [in, optional] A pointer to application defined data that will be passed to onRead, onSeek and onMeta. +// +// Returns a pointer to an object representing the decoder. +// +// Close the decoder with drflac_close(). +// +// This is slower than drflac_open(), so avoid this one if you don't need metadata. Internally, this will do a DRFLAC_MALLOC() +// and DRFLAC_FREE() for every metadata block except for STREAMINFO and PADDING blocks. +// +// The caller is notified of the metadata via the onMeta callback. All metadata blocks will be handled before the function +// returns. +// +// The STREAMINFO block must be present for this to succeed. Use drflac_open_with_metadata_relaxed() to open a FLAC +// stream where the header may not be present. +// +// Note that this will behave inconsistently with drflac_open() if the stream is an Ogg encapsulated stream and a metadata +// block is corrupted. This is due to the way the Ogg stream recovers from corrupted pages. When drflac_open_with_metadata() +// is being used, the open routine will try to read the contents of the metadata block, whereas drflac_open() will simply +// seek past it (for the sake of efficiency). This inconsistency can result in different samples being returned depending on +// whether or not the stream is being opened with metadata. +// +// See also: drflac_open_file_with_metadata(), drflac_open_memory_with_metadata(), drflac_open(), drflac_close() +drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData); + +// The same as drflac_open_with_metadata(), except attempts to open the stream even when a header block is not present. +// +// See also: drflac_open_with_metadata(), drflac_open_relaxed() +drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData); + +// Closes the given FLAC decoder. +// +// pFlac [in] The decoder to close. +// +// This will destroy the decoder object. +void drflac_close(drflac* pFlac); + + +// Reads sample data from the given FLAC decoder, output as interleaved signed 32-bit PCM. +// +// pFlac [in] The decoder. +// framesToRead [in] The number of PCM frames to read. +// pBufferOut [out, optional] A pointer to the buffer that will receive the decoded samples. +// +// Returns the number of PCM frames actually read. +// +// pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames +// seeked. +drflac_uint64 drflac_read_pcm_frames_s32(drflac* pFlac, drflac_uint64 framesToRead, drflac_int32* pBufferOut); + +// Same as drflac_read_pcm_frames_s32(), except outputs samples as 16-bit integer PCM rather than 32-bit. +// +// Note that this is lossy for streams where the bits per sample is larger than 16. +drflac_uint64 drflac_read_pcm_frames_s16(drflac* pFlac, drflac_uint64 framesToRead, drflac_int16* pBufferOut); + +// Same as drflac_read_pcm_frames_s32(), except outputs samples as 32-bit floating-point PCM. +// +// Note that this should be considered lossy due to the nature of floating point numbers not being able to exactly +// represent every possible number. +drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut); + +// Seeks to the PCM frame at the given index. +// +// pFlac [in] The decoder. +// pcmFrameIndex [in] The index of the PCM frame to seek to. See notes below. +// +// Returns DRFLAC_TRUE if successful; DRFLAC_FALSE otherwise. +drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex); + + + +#ifndef DR_FLAC_NO_STDIO +// Opens a FLAC decoder from the file at the given path. +// +// filename [in] The path of the file to open, either absolute or relative to the current directory. +// +// Returns a pointer to an object representing the decoder. +// +// Close the decoder with drflac_close(). +// +// This will hold a handle to the file until the decoder is closed with drflac_close(). Some platforms will restrict the +// number of files a process can have open at any given time, so keep this mind if you have many decoders open at the +// same time. +// +// See also: drflac_open(), drflac_open_file_with_metadata(), drflac_close() +drflac* drflac_open_file(const char* filename); + +// Opens a FLAC decoder from the file at the given path and notifies the caller of the metadata chunks (album art, etc.) +// +// Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. +drflac* drflac_open_file_with_metadata(const char* filename, drflac_meta_proc onMeta, void* pUserData); +#endif + +// Opens a FLAC decoder from a pre-allocated block of memory +// +// This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for +// the lifetime of the decoder. +drflac* drflac_open_memory(const void* data, size_t dataSize); + +// Opens a FLAC decoder from a pre-allocated block of memory and notifies the caller of the metadata chunks (album art, etc.) +// +// Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. +drflac* drflac_open_memory_with_metadata(const void* data, size_t dataSize, drflac_meta_proc onMeta, void* pUserData); + + + +//// High Level APIs //// + +// Opens a FLAC stream from the given callbacks and fully decodes it in a single operation. The return value is a +// pointer to the sample data as interleaved signed 32-bit PCM. The returned data must be freed with DRFLAC_FREE(). +// +// Sometimes a FLAC file won't keep track of the total sample count. In this situation the function will continuously +// read samples into a dynamically sized buffer on the heap until no samples are left. +// +// Do not call this function on a broadcast type of stream (like internet radio streams and whatnot). +drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); + +// Same as drflac_open_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. +drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); + +// Same as drflac_open_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. +float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); + +#ifndef DR_FLAC_NO_STDIO +// Same as drflac_open_and_read_pcm_frames_s32() except opens the decoder from a file. +drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); + +// Same as drflac_open_file_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. +drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); + +// Same as drflac_open_file_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. +float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); +#endif + +// Same as drflac_open_and_read_pcm_frames_s32() except opens the decoder from a block of memory. +drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); + +// Same as drflac_open_memory_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. +drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); + +// Same as drflac_open_memory_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. +float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); + +// Frees memory that was allocated internally by dr_flac. +void drflac_free(void* p); + + +// Structure representing an iterator for vorbis comments in a VORBIS_COMMENT metadata block. +typedef struct +{ + drflac_uint32 countRemaining; + const char* pRunningData; +} drflac_vorbis_comment_iterator; + +// Initializes a vorbis comment iterator. This can be used for iterating over the vorbis comments in a VORBIS_COMMENT +// metadata block. +void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments); + +// Goes to the next vorbis comment in the given iterator. If null is returned it means there are no more comments. The +// returned string is NOT null terminated. +const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut); + + +// Structure representing an iterator for cuesheet tracks in a CUESHEET metadata block. +typedef struct +{ + drflac_uint32 countRemaining; + const char* pRunningData; +} drflac_cuesheet_track_iterator; + +// Packing is important on this structure because we map this directly to the raw data within the CUESHEET metadata block. +#pragma pack(4) +typedef struct +{ + drflac_uint64 offset; + drflac_uint8 index; + drflac_uint8 reserved[3]; +} drflac_cuesheet_track_index; +#pragma pack() + +typedef struct +{ + drflac_uint64 offset; + drflac_uint8 trackNumber; + char ISRC[12]; + drflac_bool8 isAudio; + drflac_bool8 preEmphasis; + drflac_uint8 indexCount; + const drflac_cuesheet_track_index* pIndexPoints; +} drflac_cuesheet_track; + +// Initializes a cuesheet track iterator. This can be used for iterating over the cuesheet tracks in a CUESHEET metadata +// block. +void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData); + +// Goes to the next cuesheet track in the given iterator. If DRFLAC_FALSE is returned it means there are no more comments. +drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack); + + +//// Deprecated APIs //// +DRFLAC_DEPRECATED drflac_uint64 drflac_read_s32(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int32* pBufferOut); // Use drflac_read_pcm_frames_s32() instead. +DRFLAC_DEPRECATED drflac_uint64 drflac_read_s16(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int16* pBufferOut); // Use drflac_read_pcm_frames_s16() instead. +DRFLAC_DEPRECATED drflac_uint64 drflac_read_f32(drflac* pFlac, drflac_uint64 samplesToRead, float* pBufferOut); // Use drflac_read_pcm_frames_f32() instead. +DRFLAC_DEPRECATED drflac_bool32 drflac_seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex); // Use drflac_seek_to_pcm_frame() instead. +DRFLAC_DEPRECATED drflac_int32* drflac_open_and_decode_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); // Use drflac_open_and_read_pcm_frames_s32(). +DRFLAC_DEPRECATED drflac_int16* drflac_open_and_decode_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); // Use drflac_open_and_read_pcm_frames_s16(). +DRFLAC_DEPRECATED float* drflac_open_and_decode_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); // Use drflac_open_and_read_pcm_frames_f32(). +DRFLAC_DEPRECATED drflac_int32* drflac_open_and_decode_file_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); // Use drflac_open_file_and_read_pcm_frames_s32(). +DRFLAC_DEPRECATED drflac_int16* drflac_open_and_decode_file_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); // Use drflac_open_file_and_read_pcm_frames_s16(). +DRFLAC_DEPRECATED float* drflac_open_and_decode_file_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); // Use drflac_open_file_and_read_pcm_frames_f32(). +DRFLAC_DEPRECATED drflac_int32* drflac_open_and_decode_memory_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); // Use drflac_open_memory_and_read_pcm_frames_s32(). +DRFLAC_DEPRECATED drflac_int16* drflac_open_and_decode_memory_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); // Use drflac_open_memory_and_read_pcm_frames_s16(). +DRFLAC_DEPRECATED float* drflac_open_and_decode_memory_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); // Use drflac_open_memory_and_read_pcm_frames_f32(). + +#ifdef __cplusplus +} +#endif +#endif //dr_flac_h + + +/////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef DR_FLAC_IMPLEMENTATION +#ifdef __linux__ + #ifndef _BSD_SOURCE + #define _BSD_SOURCE + #endif + #ifndef __USE_BSD + #define __USE_BSD + #endif + #include +#endif + +#include +#include + +#ifdef _MSC_VER +#define DRFLAC_INLINE __forceinline +#else +#ifdef __GNUC__ +#define DRFLAC_INLINE inline __attribute__((always_inline)) +#else +#define DRFLAC_INLINE inline +#endif +#endif + +// CPU architecture. +#if defined(__x86_64__) || defined(_M_X64) + #define DRFLAC_X64 +#elif defined(__i386) || defined(_M_IX86) + #define DRFLAC_X86 +#elif defined(__arm__) || defined(_M_ARM) + #define DRFLAC_ARM +#endif + +// Intrinsics Support +#if !defined(DR_FLAC_NO_SIMD) + #if defined(DRFLAC_X64) || defined(DRFLAC_X86) + #if defined(_MSC_VER) && !defined(__clang__) + // MSVC. + #if !defined(DRFLAC_NO_SSE2) // Assume all MSVC compilers support SSE2 intrinsics. + #define DRFLAC_SUPPORT_SSE2 + #endif + #if _MSC_VER >= 1600 && !defined(DRFLAC_NO_SSE41) // 2010 + #define DRFLAC_SUPPORT_SSE41 + #endif + #else + // Assume GNUC-style. + #if defined(__SSE2__) && !defined(DRFLAC_NO_SSE2) + #define DRFLAC_SUPPORT_SSE2 + #endif + #if defined(__SSE4_1__) && !defined(DRFLAC_NO_SSE41) + #define DRFLAC_SUPPORT_SSE41 + #endif + #endif + + // If at this point we still haven't determined compiler support for the intrinsics just fall back to __has_include. + #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) + #if !defined(DRFLAC_SUPPORT_SSE2) && !defined(DRFLAC_NO_SSE2) && __has_include() + #define DRFLAC_SUPPORT_SSE2 + #endif + #if !defined(DRFLAC_SUPPORT_SSE41) && !defined(DRFLAC_NO_SSE41) && __has_include() + #define DRFLAC_SUPPORT_SSE41 + #endif + #endif + + #if defined(DRFLAC_SUPPORT_SSE41) + #include + #elif defined(DRFLAC_SUPPORT_SSE2) + #include + #endif + #endif + + #if defined(DRFLAC_ARM) + #if !defined(DRFLAC_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + #define DRFLAC_SUPPORT_NEON + #endif + + // Fall back to looking for the #include file. + #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) + #if !defined(DRFLAC_SUPPORT_NEON) && !defined(DRFLAC_NO_NEON) && __has_include() + #define DRFLAC_SUPPORT_NEON + #endif + #endif + + #if defined(DRFLAC_SUPPORT_NEON) + #include + #endif + #endif +#endif + +// Compile-time CPU feature support. +#if !defined(DR_FLAC_NO_SIMD) && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) + #if defined(_MSC_VER) && !defined(__clang__) + #if _MSC_VER >= 1400 + #include + static void drflac__cpuid(int info[4], int fid) + { + __cpuid(info, fid); + } + #else + #define DRFLAC_NO_CPUID + #endif + #else + #if defined(__GNUC__) || defined(__clang__) + static void drflac__cpuid(int info[4], int fid) + { + // It looks like the -fPIC option uses the ebx register which GCC complains about. We can work around this by just using a different register, the + // specific register of which I'm letting the compiler decide on. The "k" prefix is used to specify a 32-bit register. The {...} syntax is for + // supporting different assembly dialects. + // + // What's basically happening is that we're saving and restoring the ebx register manually. + #if defined(DRFLAC_X86) && defined(__PIC__) + __asm__ __volatile__ ( + "xchg{l} {%%}ebx, %k1;" + "cpuid;" + "xchg{l} {%%}ebx, %k1;" + : "=a"(info[0]), "=&r"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #else + __asm__ __volatile__ ( + "cpuid" : "=a"(info[0]), "=b"(info[1]), "=c"(info[2]), "=d"(info[3]) : "a"(fid), "c"(0) + ); + #endif + } + #else + #define DRFLAC_NO_CPUID + #endif + #endif +#else + #define DRFLAC_NO_CPUID +#endif + +static DRFLAC_INLINE drflac_bool32 drflac_has_sse2() +{ +#if defined(DRFLAC_SUPPORT_SSE2) + #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE2) + #if defined(DRFLAC_X64) + return DRFLAC_TRUE; // 64-bit targets always support SSE2. + #elif (defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__) + return DRFLAC_TRUE; // If the compiler is allowed to freely generate SSE2 code we can assume support. + #else + #if defined(DRFLAC_NO_CPUID) + return DRFLAC_FALSE; + #else + int info[4]; + drflac_cpuid(info, 1); + return (info[3] & (1 << 26)) != 0; + #endif + #endif + #else + return DRFLAC_FALSE; // SSE2 is only supported on x86 and x64 architectures. + #endif +#else + return DRFLAC_FALSE; // No compiler support. +#endif +} + +static DRFLAC_INLINE drflac_bool32 drflac_has_sse41() +{ +#if defined(DRFLAC_SUPPORT_SSE41) + #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE41) + #if defined(DRFLAC_X64) + return DRFLAC_TRUE; // 64-bit targets always support SSE4.1. + #elif (defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE4_1__) + return DRFLAC_TRUE; // If the compiler is allowed to freely generate SSE41 code we can assume support. + #else + #if defined(DRFLAC_NO_CPUID) + return DRFLAC_FALSE; + #else + int info[4]; + drflac_cpuid(info, 1); + return (info[2] & (1 << 19)) != 0; + #endif + #endif + #else + return DRFLAC_FALSE; // SSE41 is only supported on x86 and x64 architectures. + #endif +#else + return DRFLAC_FALSE; // No compiler support. +#endif +} + + +#if defined(_MSC_VER) && _MSC_VER >= 1500 && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) + #define DRFLAC_HAS_LZCNT_INTRINSIC +#elif (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) + #define DRFLAC_HAS_LZCNT_INTRINSIC +#elif defined(__clang__) + #if __has_builtin(__builtin_clzll) || __has_builtin(__builtin_clzl) + #define DRFLAC_HAS_LZCNT_INTRINSIC + #endif +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1300 + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC +#elif defined(__clang__) + #if __has_builtin(__builtin_bswap16) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap32) + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap64) + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + #endif +#elif defined(__GNUC__) + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + #endif + #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #endif +#endif + + +// Standard library stuff. +#ifndef DRFLAC_ASSERT +#include +#define DRFLAC_ASSERT(expression) assert(expression) +#endif +#ifndef DRFLAC_MALLOC +#define DRFLAC_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRFLAC_REALLOC +#define DRFLAC_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRFLAC_FREE +#define DRFLAC_FREE(p) free((p)) +#endif +#ifndef DRFLAC_COPY_MEMORY +#define DRFLAC_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRFLAC_ZERO_MEMORY +#define DRFLAC_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif + +#define DRFLAC_MAX_SIMD_VECTOR_SIZE 64 // 64 for AVX-512 in the future. + +typedef drflac_int32 drflac_result; +#define DRFLAC_SUCCESS 0 +#define DRFLAC_ERROR -1 // A generic error. +#define DRFLAC_INVALID_ARGS -2 +#define DRFLAC_END_OF_STREAM -128 +#define DRFLAC_CRC_MISMATCH -129 + +#define DRFLAC_SUBFRAME_CONSTANT 0 +#define DRFLAC_SUBFRAME_VERBATIM 1 +#define DRFLAC_SUBFRAME_FIXED 8 +#define DRFLAC_SUBFRAME_LPC 32 +#define DRFLAC_SUBFRAME_RESERVED 255 + +#define DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE 0 +#define DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2 1 + +#define DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT 0 +#define DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE 8 +#define DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE 9 +#define DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE 10 + +// Keeps track of the number of leading samples for each sub-frame. This is required because the SSE pipeline will occasionally +// reference excess prior samples. +#define DRFLAC_LEADING_SAMPLES 32 + + +#define drflac_align(x, a) ((((x) + (a) - 1) / (a)) * (a)) +#define drflac_assert DRFLAC_ASSERT +#define drflac_copy_memory DRFLAC_COPY_MEMORY +#define drflac_zero_memory DRFLAC_ZERO_MEMORY + + +// CPU caps. +static drflac_bool32 drflac__gIsLZCNTSupported = DRFLAC_FALSE; +#ifndef DRFLAC_NO_CPUID +static drflac_bool32 drflac__gIsSSE2Supported = DRFLAC_FALSE; +static drflac_bool32 drflac__gIsSSE41Supported = DRFLAC_FALSE; +static void drflac__init_cpu_caps() +{ + int info[4] = {0}; + + // LZCNT + drflac__cpuid(info, 0x80000001); + drflac__gIsLZCNTSupported = (info[2] & (1 << 5)) != 0; + + // SSE2 + drflac__gIsSSE2Supported = drflac_has_sse2(); + + // SSE4.1 + drflac__gIsSSE41Supported = drflac_has_sse41(); +} +#endif + + +//// Endian Management //// +static DRFLAC_INLINE drflac_bool32 drflac__is_little_endian() +{ +#if defined(DRFLAC_X86) || defined(DRFLAC_X64) + return DRFLAC_TRUE; +#else + int n = 1; + return (*(char*)&n) == 1; +#endif +} + +static DRFLAC_INLINE drflac_uint16 drflac__swap_endian_uint16(drflac_uint16 n) +{ +#ifdef DRFLAC_HAS_BYTESWAP16_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_ushort(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap16(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF00) >> 8) | + ((n & 0x00FF) << 8); +#endif +} + +static DRFLAC_INLINE drflac_uint32 drflac__swap_endian_uint32(drflac_uint32 n) +{ +#ifdef DRFLAC_HAS_BYTESWAP32_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_ulong(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap32(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & 0xFF000000) >> 24) | + ((n & 0x00FF0000) >> 8) | + ((n & 0x0000FF00) << 8) | + ((n & 0x000000FF) << 24); +#endif +} + +static DRFLAC_INLINE drflac_uint64 drflac__swap_endian_uint64(drflac_uint64 n) +{ +#ifdef DRFLAC_HAS_BYTESWAP64_INTRINSIC + #if defined(_MSC_VER) + return _byteswap_uint64(n); + #elif defined(__GNUC__) || defined(__clang__) + return __builtin_bswap64(n); + #else + #error "This compiler does not support the byte swap intrinsic." + #endif +#else + return ((n & (drflac_uint64)0xFF00000000000000) >> 56) | + ((n & (drflac_uint64)0x00FF000000000000) >> 40) | + ((n & (drflac_uint64)0x0000FF0000000000) >> 24) | + ((n & (drflac_uint64)0x000000FF00000000) >> 8) | + ((n & (drflac_uint64)0x00000000FF000000) << 8) | + ((n & (drflac_uint64)0x0000000000FF0000) << 24) | + ((n & (drflac_uint64)0x000000000000FF00) << 40) | + ((n & (drflac_uint64)0x00000000000000FF) << 56); +#endif +} + + +static DRFLAC_INLINE drflac_uint16 drflac__be2host_16(drflac_uint16 n) +{ +#ifdef __linux__ + return be16toh(n); +#else + if (drflac__is_little_endian()) { + return drflac__swap_endian_uint16(n); + } + + return n; +#endif +} + +static DRFLAC_INLINE drflac_uint32 drflac__be2host_32(drflac_uint32 n) +{ +#ifdef __linux__ + return be32toh(n); +#else + if (drflac__is_little_endian()) { + return drflac__swap_endian_uint32(n); + } + + return n; +#endif +} + +static DRFLAC_INLINE drflac_uint64 drflac__be2host_64(drflac_uint64 n) +{ +#ifdef __linux__ + return be64toh(n); +#else + if (drflac__is_little_endian()) { + return drflac__swap_endian_uint64(n); + } + + return n; +#endif +} + + +static DRFLAC_INLINE drflac_uint32 drflac__le2host_32(drflac_uint32 n) +{ +#ifdef __linux__ + return le32toh(n); +#else + if (!drflac__is_little_endian()) { + return drflac__swap_endian_uint32(n); + } + + return n; +#endif +} + + +static DRFLAC_INLINE drflac_uint32 drflac__unsynchsafe_32(drflac_uint32 n) +{ + drflac_uint32 result = 0; + result |= (n & 0x7F000000) >> 3; + result |= (n & 0x007F0000) >> 2; + result |= (n & 0x00007F00) >> 1; + result |= (n & 0x0000007F) >> 0; + + return result; +} + + + +// The CRC code below is based on this document: http://zlib.net/crc_v3.txt +static drflac_uint8 drflac__crc8_table[] = { + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 +}; + +static drflac_uint16 drflac__crc16_table[] = { + 0x0000, 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011, + 0x8033, 0x0036, 0x003C, 0x8039, 0x0028, 0x802D, 0x8027, 0x0022, + 0x8063, 0x0066, 0x006C, 0x8069, 0x0078, 0x807D, 0x8077, 0x0072, + 0x0050, 0x8055, 0x805F, 0x005A, 0x804B, 0x004E, 0x0044, 0x8041, + 0x80C3, 0x00C6, 0x00CC, 0x80C9, 0x00D8, 0x80DD, 0x80D7, 0x00D2, + 0x00F0, 0x80F5, 0x80FF, 0x00FA, 0x80EB, 0x00EE, 0x00E4, 0x80E1, + 0x00A0, 0x80A5, 0x80AF, 0x00AA, 0x80BB, 0x00BE, 0x00B4, 0x80B1, + 0x8093, 0x0096, 0x009C, 0x8099, 0x0088, 0x808D, 0x8087, 0x0082, + 0x8183, 0x0186, 0x018C, 0x8189, 0x0198, 0x819D, 0x8197, 0x0192, + 0x01B0, 0x81B5, 0x81BF, 0x01BA, 0x81AB, 0x01AE, 0x01A4, 0x81A1, + 0x01E0, 0x81E5, 0x81EF, 0x01EA, 0x81FB, 0x01FE, 0x01F4, 0x81F1, + 0x81D3, 0x01D6, 0x01DC, 0x81D9, 0x01C8, 0x81CD, 0x81C7, 0x01C2, + 0x0140, 0x8145, 0x814F, 0x014A, 0x815B, 0x015E, 0x0154, 0x8151, + 0x8173, 0x0176, 0x017C, 0x8179, 0x0168, 0x816D, 0x8167, 0x0162, + 0x8123, 0x0126, 0x012C, 0x8129, 0x0138, 0x813D, 0x8137, 0x0132, + 0x0110, 0x8115, 0x811F, 0x011A, 0x810B, 0x010E, 0x0104, 0x8101, + 0x8303, 0x0306, 0x030C, 0x8309, 0x0318, 0x831D, 0x8317, 0x0312, + 0x0330, 0x8335, 0x833F, 0x033A, 0x832B, 0x032E, 0x0324, 0x8321, + 0x0360, 0x8365, 0x836F, 0x036A, 0x837B, 0x037E, 0x0374, 0x8371, + 0x8353, 0x0356, 0x035C, 0x8359, 0x0348, 0x834D, 0x8347, 0x0342, + 0x03C0, 0x83C5, 0x83CF, 0x03CA, 0x83DB, 0x03DE, 0x03D4, 0x83D1, + 0x83F3, 0x03F6, 0x03FC, 0x83F9, 0x03E8, 0x83ED, 0x83E7, 0x03E2, + 0x83A3, 0x03A6, 0x03AC, 0x83A9, 0x03B8, 0x83BD, 0x83B7, 0x03B2, + 0x0390, 0x8395, 0x839F, 0x039A, 0x838B, 0x038E, 0x0384, 0x8381, + 0x0280, 0x8285, 0x828F, 0x028A, 0x829B, 0x029E, 0x0294, 0x8291, + 0x82B3, 0x02B6, 0x02BC, 0x82B9, 0x02A8, 0x82AD, 0x82A7, 0x02A2, + 0x82E3, 0x02E6, 0x02EC, 0x82E9, 0x02F8, 0x82FD, 0x82F7, 0x02F2, + 0x02D0, 0x82D5, 0x82DF, 0x02DA, 0x82CB, 0x02CE, 0x02C4, 0x82C1, + 0x8243, 0x0246, 0x024C, 0x8249, 0x0258, 0x825D, 0x8257, 0x0252, + 0x0270, 0x8275, 0x827F, 0x027A, 0x826B, 0x026E, 0x0264, 0x8261, + 0x0220, 0x8225, 0x822F, 0x022A, 0x823B, 0x023E, 0x0234, 0x8231, + 0x8213, 0x0216, 0x021C, 0x8219, 0x0208, 0x820D, 0x8207, 0x0202 +}; + +static DRFLAC_INLINE drflac_uint8 drflac_crc8_byte(drflac_uint8 crc, drflac_uint8 data) +{ + return drflac__crc8_table[crc ^ data]; +} + +static DRFLAC_INLINE drflac_uint8 drflac_crc8(drflac_uint8 crc, drflac_uint32 data, drflac_uint32 count) +{ + drflac_assert(count <= 32); + +#ifdef DR_FLAC_NO_CRC + (void)crc; + (void)data; + (void)count; + return 0; +#else +#if 0 + // REFERENCE (use of this implementation requires an explicit flush by doing "drflac_crc8(crc, 0, 8);") + drflac_uint8 p = 0x07; + for (int i = count-1; i >= 0; --i) { + drflac_uint8 bit = (data & (1 << i)) >> i; + if (crc & 0x80) { + crc = ((crc << 1) | bit) ^ p; + } else { + crc = ((crc << 1) | bit); + } + } + return crc; +#else + drflac_uint32 wholeBytes = count >> 3; + drflac_uint32 leftoverBits = count - (wholeBytes*8); + + static drflac_uint64 leftoverDataMaskTable[8] = { + 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F + }; + drflac_uint64 leftoverDataMask = leftoverDataMaskTable[leftoverBits]; + + switch (wholeBytes) { + case 4: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0xFF000000UL << leftoverBits)) >> (24 + leftoverBits))); + case 3: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x00FF0000UL << leftoverBits)) >> (16 + leftoverBits))); + case 2: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x0000FF00UL << leftoverBits)) >> ( 8 + leftoverBits))); + case 1: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x000000FFUL << leftoverBits)) >> ( 0 + leftoverBits))); + case 0: if (leftoverBits > 0) crc = (crc << leftoverBits) ^ drflac__crc8_table[(crc >> (8 - leftoverBits)) ^ (data & leftoverDataMask)]; + } + return crc; +#endif +#endif +} + +static DRFLAC_INLINE drflac_uint16 drflac_crc16_byte(drflac_uint16 crc, drflac_uint8 data) +{ + return (crc << 8) ^ drflac__crc16_table[(drflac_uint8)(crc >> 8) ^ data]; +} + +static DRFLAC_INLINE drflac_uint16 drflac_crc16_bytes(drflac_uint16 crc, drflac_cache_t data, drflac_uint32 byteCount) +{ + switch (byteCount) + { +#ifdef DRFLAC_64BIT + case 8: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 56) & 0xFF)); + case 7: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 48) & 0xFF)); + case 6: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 40) & 0xFF)); + case 5: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 32) & 0xFF)); +#endif + case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 24) & 0xFF)); + case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 16) & 0xFF)); + case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 8) & 0xFF)); + case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 0) & 0xFF)); + } + + return crc; +} + +static DRFLAC_INLINE drflac_uint16 drflac_crc16__32bit(drflac_uint16 crc, drflac_uint32 data, drflac_uint32 count) +{ + drflac_assert(count <= 64); + +#ifdef DR_FLAC_NO_CRC + (void)crc; + (void)data; + (void)count; + return 0; +#else +#if 0 + // REFERENCE (use of this implementation requires an explicit flush by doing "drflac_crc16(crc, 0, 16);") + drflac_uint16 p = 0x8005; + for (int i = count-1; i >= 0; --i) { + drflac_uint16 bit = (data & (1ULL << i)) >> i; + if (r & 0x8000) { + r = ((r << 1) | bit) ^ p; + } else { + r = ((r << 1) | bit); + } + } + + return crc; +#else + drflac_uint32 wholeBytes = count >> 3; + drflac_uint32 leftoverBits = count - (wholeBytes*8); + + static drflac_uint64 leftoverDataMaskTable[8] = { + 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F + }; + drflac_uint64 leftoverDataMask = leftoverDataMaskTable[leftoverBits]; + + switch (wholeBytes) { + default: + case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0xFF000000UL << leftoverBits)) >> (24 + leftoverBits))); + case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0x00FF0000UL << leftoverBits)) >> (16 + leftoverBits))); + case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0x0000FF00UL << leftoverBits)) >> ( 8 + leftoverBits))); + case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & (0x000000FFUL << leftoverBits)) >> ( 0 + leftoverBits))); + case 0: if (leftoverBits > 0) crc = (crc << leftoverBits) ^ drflac__crc16_table[(crc >> (16 - leftoverBits)) ^ (data & leftoverDataMask)]; + } + return crc; +#endif +#endif +} + +static DRFLAC_INLINE drflac_uint16 drflac_crc16__64bit(drflac_uint16 crc, drflac_uint64 data, drflac_uint32 count) +{ + drflac_assert(count <= 64); + +#ifdef DR_FLAC_NO_CRC + (void)crc; + (void)data; + (void)count; + return 0; +#else + drflac_uint32 wholeBytes = count >> 3; + drflac_uint32 leftoverBits = count - (wholeBytes*8); + + static drflac_uint64 leftoverDataMaskTable[8] = { + 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F + }; + drflac_uint64 leftoverDataMask = leftoverDataMaskTable[leftoverBits]; + + switch (wholeBytes) { + default: + case 8: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0xFF00000000000000 << leftoverBits)) >> (56 + leftoverBits))); + case 7: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x00FF000000000000 << leftoverBits)) >> (48 + leftoverBits))); + case 6: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x0000FF0000000000 << leftoverBits)) >> (40 + leftoverBits))); + case 5: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x000000FF00000000 << leftoverBits)) >> (32 + leftoverBits))); + case 4: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x00000000FF000000 << leftoverBits)) >> (24 + leftoverBits))); + case 3: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x0000000000FF0000 << leftoverBits)) >> (16 + leftoverBits))); + case 2: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x000000000000FF00 << leftoverBits)) >> ( 8 + leftoverBits))); + case 1: crc = drflac_crc16_byte(crc, (drflac_uint8)((data & ((drflac_uint64)0x00000000000000FF << leftoverBits)) >> ( 0 + leftoverBits))); + case 0: if (leftoverBits > 0) crc = (crc << leftoverBits) ^ drflac__crc16_table[(crc >> (16 - leftoverBits)) ^ (data & leftoverDataMask)]; + } + return crc; +#endif +} + + +static DRFLAC_INLINE drflac_uint16 drflac_crc16(drflac_uint16 crc, drflac_cache_t data, drflac_uint32 count) +{ +#ifdef DRFLAC_64BIT + return drflac_crc16__64bit(crc, data, count); +#else + return drflac_crc16__32bit(crc, data, count); +#endif +} + + +#ifdef DRFLAC_64BIT +#define drflac__be2host__cache_line drflac__be2host_64 +#else +#define drflac__be2host__cache_line drflac__be2host_32 +#endif + +// BIT READING ATTEMPT #2 +// +// This uses a 32- or 64-bit bit-shifted cache - as bits are read, the cache is shifted such that the first valid bit is sitting +// on the most significant bit. It uses the notion of an L1 and L2 cache (borrowed from CPU architecture), where the L1 cache +// is a 32- or 64-bit unsigned integer (depending on whether or not a 32- or 64-bit build is being compiled) and the L2 is an +// array of "cache lines", with each cache line being the same size as the L1. The L2 is a buffer of about 4KB and is where data +// from onRead() is read into. +#define DRFLAC_CACHE_L1_SIZE_BYTES(bs) (sizeof((bs)->cache)) +#define DRFLAC_CACHE_L1_SIZE_BITS(bs) (sizeof((bs)->cache)*8) +#define DRFLAC_CACHE_L1_BITS_REMAINING(bs) (DRFLAC_CACHE_L1_SIZE_BITS(bs) - (bs)->consumedBits) +#define DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount) (~((~(drflac_cache_t)0) >> (_bitCount))) +#define DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, _bitCount) (DRFLAC_CACHE_L1_SIZE_BITS(bs) - (_bitCount)) +#define DRFLAC_CACHE_L1_SELECT(bs, _bitCount) (((bs)->cache) & DRFLAC_CACHE_L1_SELECTION_MASK(_bitCount)) +#define DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, _bitCount) (DRFLAC_CACHE_L1_SELECT((bs), (_bitCount)) >> DRFLAC_CACHE_L1_SELECTION_SHIFT((bs), (_bitCount))) +#define DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, _bitCount)(DRFLAC_CACHE_L1_SELECT((bs), (_bitCount)) >> (DRFLAC_CACHE_L1_SELECTION_SHIFT((bs), (_bitCount)) & (DRFLAC_CACHE_L1_SIZE_BITS(bs)-1))) +#define DRFLAC_CACHE_L2_SIZE_BYTES(bs) (sizeof((bs)->cacheL2)) +#define DRFLAC_CACHE_L2_LINE_COUNT(bs) (DRFLAC_CACHE_L2_SIZE_BYTES(bs) / sizeof((bs)->cacheL2[0])) +#define DRFLAC_CACHE_L2_LINES_REMAINING(bs) (DRFLAC_CACHE_L2_LINE_COUNT(bs) - (bs)->nextL2Line) + + +#ifndef DR_FLAC_NO_CRC +static DRFLAC_INLINE void drflac__reset_crc16(drflac_bs* bs) +{ + bs->crc16 = 0; + bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; +} + +static DRFLAC_INLINE void drflac__update_crc16(drflac_bs* bs) +{ + bs->crc16 = drflac_crc16_bytes(bs->crc16, bs->crc16Cache, DRFLAC_CACHE_L1_SIZE_BYTES(bs) - bs->crc16CacheIgnoredBytes); + bs->crc16CacheIgnoredBytes = 0; +} + +static DRFLAC_INLINE drflac_uint16 drflac__flush_crc16(drflac_bs* bs) +{ + // We should never be flushing in a situation where we are not aligned on a byte boundary. + drflac_assert((DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7) == 0); + + // The bits that were read from the L1 cache need to be accumulated. The number of bytes needing to be accumulated is determined + // by the number of bits that have been consumed. + if (DRFLAC_CACHE_L1_BITS_REMAINING(bs) == 0) { + drflac__update_crc16(bs); + } else { + // We only accumulate the consumed bits. + bs->crc16 = drflac_crc16_bytes(bs->crc16, bs->crc16Cache >> DRFLAC_CACHE_L1_BITS_REMAINING(bs), (bs->consumedBits >> 3) - bs->crc16CacheIgnoredBytes); + + // The bits that we just accumulated should never be accumulated again. We need to keep track of how many bytes were accumulated + // so we can handle that later. + bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; + } + + return bs->crc16; +} +#endif + +static DRFLAC_INLINE drflac_bool32 drflac__reload_l1_cache_from_l2(drflac_bs* bs) +{ + // Fast path. Try loading straight from L2. + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + bs->cache = bs->cacheL2[bs->nextL2Line++]; + return DRFLAC_TRUE; + } + + // If we get here it means we've run out of data in the L2 cache. We'll need to fetch more from the client, if there's + // any left. + if (bs->unalignedByteCount > 0) { + return DRFLAC_FALSE; // If we have any unaligned bytes it means there's no more aligned bytes left in the client. + } + + size_t bytesRead = bs->onRead(bs->pUserData, bs->cacheL2, DRFLAC_CACHE_L2_SIZE_BYTES(bs)); + + bs->nextL2Line = 0; + if (bytesRead == DRFLAC_CACHE_L2_SIZE_BYTES(bs)) { + bs->cache = bs->cacheL2[bs->nextL2Line++]; + return DRFLAC_TRUE; + } + + + // If we get here it means we were unable to retrieve enough data to fill the entire L2 cache. It probably + // means we've just reached the end of the file. We need to move the valid data down to the end of the buffer + // and adjust the index of the next line accordingly. Also keep in mind that the L2 cache must be aligned to + // the size of the L1 so we'll need to seek backwards by any misaligned bytes. + size_t alignedL1LineCount = bytesRead / DRFLAC_CACHE_L1_SIZE_BYTES(bs); + + // We need to keep track of any unaligned bytes for later use. + bs->unalignedByteCount = bytesRead - (alignedL1LineCount * DRFLAC_CACHE_L1_SIZE_BYTES(bs)); + if (bs->unalignedByteCount > 0) { + bs->unalignedCache = bs->cacheL2[alignedL1LineCount]; + } + + if (alignedL1LineCount > 0) { + size_t offset = DRFLAC_CACHE_L2_LINE_COUNT(bs) - alignedL1LineCount; + for (size_t i = alignedL1LineCount; i > 0; --i) { + bs->cacheL2[i-1 + offset] = bs->cacheL2[i-1]; + } + + bs->nextL2Line = (drflac_uint32)offset; + bs->cache = bs->cacheL2[bs->nextL2Line++]; + return DRFLAC_TRUE; + } else { + // If we get into this branch it means we weren't able to load any L1-aligned data. + bs->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT(bs); + return DRFLAC_FALSE; + } +} + +static drflac_bool32 drflac__reload_cache(drflac_bs* bs) +{ +#ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); +#endif + + // Fast path. Try just moving the next value in the L2 cache to the L1 cache. + if (drflac__reload_l1_cache_from_l2(bs)) { + bs->cache = drflac__be2host__cache_line(bs->cache); + bs->consumedBits = 0; +#ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs->cache; +#endif + return DRFLAC_TRUE; + } + + // Slow path. + + // If we get here it means we have failed to load the L1 cache from the L2. Likely we've just reached the end of the stream and the last + // few bytes did not meet the alignment requirements for the L2 cache. In this case we need to fall back to a slower path and read the + // data from the unaligned cache. + size_t bytesRead = bs->unalignedByteCount; + if (bytesRead == 0) { + bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); // <-- The stream has been exhausted, so marked the bits as consumed. + return DRFLAC_FALSE; + } + + drflac_assert(bytesRead < DRFLAC_CACHE_L1_SIZE_BYTES(bs)); + bs->consumedBits = (drflac_uint32)(DRFLAC_CACHE_L1_SIZE_BYTES(bs) - bytesRead) * 8; + + bs->cache = drflac__be2host__cache_line(bs->unalignedCache); + bs->cache &= DRFLAC_CACHE_L1_SELECTION_MASK(DRFLAC_CACHE_L1_BITS_REMAINING(bs)); // <-- Make sure the consumed bits are always set to zero. Other parts of the library depend on this property. + bs->unalignedByteCount = 0; // <-- At this point the unaligned bytes have been moved into the cache and we thus have no more unaligned bytes. + +#ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs->cache >> bs->consumedBits; + bs->crc16CacheIgnoredBytes = bs->consumedBits >> 3; +#endif + return DRFLAC_TRUE; +} + +static void drflac__reset_cache(drflac_bs* bs) +{ + bs->nextL2Line = DRFLAC_CACHE_L2_LINE_COUNT(bs); // <-- This clears the L2 cache. + bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); // <-- This clears the L1 cache. + bs->cache = 0; + bs->unalignedByteCount = 0; // <-- This clears the trailing unaligned bytes. + bs->unalignedCache = 0; + +#ifndef DR_FLAC_NO_CRC + bs->crc16Cache = 0; + bs->crc16CacheIgnoredBytes = 0; +#endif +} + + +static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned int bitCount, drflac_uint32* pResultOut) +{ + drflac_assert(bs != NULL); + drflac_assert(pResultOut != NULL); + drflac_assert(bitCount > 0); + drflac_assert(bitCount <= 32); + + if (bs->consumedBits == DRFLAC_CACHE_L1_SIZE_BITS(bs)) { + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + } + + if (bitCount <= DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + // If we want to load all 32-bits from a 32-bit cache we need to do it slightly differently because we can't do + // a 32-bit shift on a 32-bit integer. This will never be the case on 64-bit caches, so we can have a slightly + // more optimal solution for this. +#ifdef DRFLAC_64BIT + *pResultOut = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCount); + bs->consumedBits += bitCount; + bs->cache <<= bitCount; +#else + if (bitCount < DRFLAC_CACHE_L1_SIZE_BITS(bs)) { + *pResultOut = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCount); + bs->consumedBits += bitCount; + bs->cache <<= bitCount; + } else { + // Cannot shift by 32-bits, so need to do it differently. + *pResultOut = (drflac_uint32)bs->cache; + bs->consumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs); + bs->cache = 0; + } +#endif + + return DRFLAC_TRUE; + } else { + // It straddles the cached data. It will never cover more than the next chunk. We just read the number in two parts and combine them. + drflac_uint32 bitCountHi = DRFLAC_CACHE_L1_BITS_REMAINING(bs); + drflac_uint32 bitCountLo = bitCount - bitCountHi; + drflac_uint32 resultHi = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountHi); + + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + *pResultOut = (resultHi << bitCountLo) | (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountLo); + bs->consumedBits += bitCountLo; + bs->cache <<= bitCountLo; + return DRFLAC_TRUE; + } +} + +static drflac_bool32 drflac__read_int32(drflac_bs* bs, unsigned int bitCount, drflac_int32* pResult) +{ + drflac_assert(bs != NULL); + drflac_assert(pResult != NULL); + drflac_assert(bitCount > 0); + drflac_assert(bitCount <= 32); + + drflac_uint32 result; + if (!drflac__read_uint32(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + drflac_uint32 signbit = ((result >> (bitCount-1)) & 0x01); + result |= (~signbit + 1) << bitCount; + + *pResult = (drflac_int32)result; + return DRFLAC_TRUE; +} + +#ifdef DRFLAC_64BIT +static drflac_bool32 drflac__read_uint64(drflac_bs* bs, unsigned int bitCount, drflac_uint64* pResultOut) +{ + drflac_assert(bitCount <= 64); + drflac_assert(bitCount > 32); + + drflac_uint32 resultHi; + if (!drflac__read_uint32(bs, bitCount - 32, &resultHi)) { + return DRFLAC_FALSE; + } + + drflac_uint32 resultLo; + if (!drflac__read_uint32(bs, 32, &resultLo)) { + return DRFLAC_FALSE; + } + + *pResultOut = (((drflac_uint64)resultHi) << 32) | ((drflac_uint64)resultLo); + return DRFLAC_TRUE; +} +#endif + +// Function below is unused, but leaving it here in case I need to quickly add it again. +#if 0 +static drflac_bool32 drflac__read_int64(drflac_bs* bs, unsigned int bitCount, drflac_int64* pResultOut) +{ + drflac_assert(bitCount <= 64); + + drflac_uint64 result; + if (!drflac__read_uint64(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + drflac_uint64 signbit = ((result >> (bitCount-1)) & 0x01); + result |= (~signbit + 1) << bitCount; + + *pResultOut = (drflac_int64)result; + return DRFLAC_TRUE; +} +#endif + +static drflac_bool32 drflac__read_uint16(drflac_bs* bs, unsigned int bitCount, drflac_uint16* pResult) +{ + drflac_assert(bs != NULL); + drflac_assert(pResult != NULL); + drflac_assert(bitCount > 0); + drflac_assert(bitCount <= 16); + + drflac_uint32 result; + if (!drflac__read_uint32(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + *pResult = (drflac_uint16)result; + return DRFLAC_TRUE; +} + +#if 0 +static drflac_bool32 drflac__read_int16(drflac_bs* bs, unsigned int bitCount, drflac_int16* pResult) +{ + drflac_assert(bs != NULL); + drflac_assert(pResult != NULL); + drflac_assert(bitCount > 0); + drflac_assert(bitCount <= 16); + + drflac_int32 result; + if (!drflac__read_int32(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + *pResult = (drflac_int16)result; + return DRFLAC_TRUE; +} +#endif + +static drflac_bool32 drflac__read_uint8(drflac_bs* bs, unsigned int bitCount, drflac_uint8* pResult) +{ + drflac_assert(bs != NULL); + drflac_assert(pResult != NULL); + drflac_assert(bitCount > 0); + drflac_assert(bitCount <= 8); + + drflac_uint32 result; + if (!drflac__read_uint32(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + *pResult = (drflac_uint8)result; + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__read_int8(drflac_bs* bs, unsigned int bitCount, drflac_int8* pResult) +{ + drflac_assert(bs != NULL); + drflac_assert(pResult != NULL); + drflac_assert(bitCount > 0); + drflac_assert(bitCount <= 8); + + drflac_int32 result; + if (!drflac__read_int32(bs, bitCount, &result)) { + return DRFLAC_FALSE; + } + + *pResult = (drflac_int8)result; + return DRFLAC_TRUE; +} + + +static drflac_bool32 drflac__seek_bits(drflac_bs* bs, size_t bitsToSeek) +{ + if (bitsToSeek <= DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + bs->consumedBits += (drflac_uint32)bitsToSeek; + bs->cache <<= bitsToSeek; + return DRFLAC_TRUE; + } else { + // It straddles the cached data. This function isn't called too frequently so I'm favouring simplicity here. + bitsToSeek -= DRFLAC_CACHE_L1_BITS_REMAINING(bs); + bs->consumedBits += DRFLAC_CACHE_L1_BITS_REMAINING(bs); + bs->cache = 0; + + // Simple case. Seek in groups of the same number as bits that fit within a cache line. +#ifdef DRFLAC_64BIT + while (bitsToSeek >= DRFLAC_CACHE_L1_SIZE_BITS(bs)) { + drflac_uint64 bin; + if (!drflac__read_uint64(bs, DRFLAC_CACHE_L1_SIZE_BITS(bs), &bin)) { + return DRFLAC_FALSE; + } + bitsToSeek -= DRFLAC_CACHE_L1_SIZE_BITS(bs); + } +#else + while (bitsToSeek >= DRFLAC_CACHE_L1_SIZE_BITS(bs)) { + drflac_uint32 bin; + if (!drflac__read_uint32(bs, DRFLAC_CACHE_L1_SIZE_BITS(bs), &bin)) { + return DRFLAC_FALSE; + } + bitsToSeek -= DRFLAC_CACHE_L1_SIZE_BITS(bs); + } +#endif + + // Whole leftover bytes. + while (bitsToSeek >= 8) { + drflac_uint8 bin; + if (!drflac__read_uint8(bs, 8, &bin)) { + return DRFLAC_FALSE; + } + bitsToSeek -= 8; + } + + // Leftover bits. + if (bitsToSeek > 0) { + drflac_uint8 bin; + if (!drflac__read_uint8(bs, (drflac_uint32)bitsToSeek, &bin)) { + return DRFLAC_FALSE; + } + bitsToSeek = 0; // <-- Necessary for the assert below. + } + + drflac_assert(bitsToSeek == 0); + return DRFLAC_TRUE; + } +} + + +// This function moves the bit streamer to the first bit after the sync code (bit 15 of the of the frame header). It will also update the CRC-16. +static drflac_bool32 drflac__find_and_seek_to_next_sync_code(drflac_bs* bs) +{ + drflac_assert(bs != NULL); + + // The sync code is always aligned to 8 bits. This is convenient for us because it means we can do byte-aligned movements. The first + // thing to do is align to the next byte. + if (!drflac__seek_bits(bs, DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7)) { + return DRFLAC_FALSE; + } + + for (;;) { +#ifndef DR_FLAC_NO_CRC + drflac__reset_crc16(bs); +#endif + + drflac_uint8 hi; + if (!drflac__read_uint8(bs, 8, &hi)) { + return DRFLAC_FALSE; + } + + if (hi == 0xFF) { + drflac_uint8 lo; + if (!drflac__read_uint8(bs, 6, &lo)) { + return DRFLAC_FALSE; + } + + if (lo == 0x3E) { + return DRFLAC_TRUE; + } else { + if (!drflac__seek_bits(bs, DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7)) { + return DRFLAC_FALSE; + } + } + } + } + + // Should never get here. + //return DRFLAC_FALSE; +} + + +#if !defined(DR_FLAC_NO_SIMD) && defined(DRFLAC_HAS_LZCNT_INTRINSIC) +#define DRFLAC_IMPLEMENT_CLZ_LZCNT +#endif +#if defined(_MSC_VER) && _MSC_VER >= 1400 && (defined(DRFLAC_X64) || defined(DRFLAC_X86)) +#define DRFLAC_IMPLEMENT_CLZ_MSVC +#endif + +static DRFLAC_INLINE drflac_uint32 drflac__clz_software(drflac_cache_t x) +{ + if (x == 0) { + return sizeof(x)*8; + } + + static drflac_uint32 clz_table_4[] = { + 0, + 4, + 3, 3, + 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1 + }; + + drflac_uint32 n = clz_table_4[x >> (sizeof(x)*8 - 4)]; + if (n == 0) { +#ifdef DRFLAC_64BIT + if ((x & 0xFFFFFFFF00000000ULL) == 0) { n = 32; x <<= 32; } + if ((x & 0xFFFF000000000000ULL) == 0) { n += 16; x <<= 16; } + if ((x & 0xFF00000000000000ULL) == 0) { n += 8; x <<= 8; } + if ((x & 0xF000000000000000ULL) == 0) { n += 4; x <<= 4; } +#else + if ((x & 0xFFFF0000) == 0) { n = 16; x <<= 16; } + if ((x & 0xFF000000) == 0) { n += 8; x <<= 8; } + if ((x & 0xF0000000) == 0) { n += 4; x <<= 4; } +#endif + n += clz_table_4[x >> (sizeof(x)*8 - 4)]; + } + + return n - 1; +} + +#ifdef DRFLAC_IMPLEMENT_CLZ_LZCNT +static DRFLAC_INLINE drflac_bool32 drflac__is_lzcnt_supported() +{ + // If the compiler itself does not support the intrinsic then we'll need to return false. +#ifdef DRFLAC_HAS_LZCNT_INTRINSIC + return drflac__gIsLZCNTSupported; +#else + return DRFLAC_FALSE; +#endif +} + +static DRFLAC_INLINE drflac_uint32 drflac__clz_lzcnt(drflac_cache_t x) +{ +#if defined(_MSC_VER) && !defined(__clang__) + #ifdef DRFLAC_64BIT + return (drflac_uint32)__lzcnt64(x); + #else + return (drflac_uint32)__lzcnt(x); + #endif +#else + #if defined(__GNUC__) || defined(__clang__) + if (x == 0) { + return sizeof(x)*8; + } + #ifdef DRFLAC_64BIT + return (drflac_uint32)__builtin_clzll((unsigned long long)x); + #else + return (drflac_uint32)__builtin_clzl((unsigned long)x); + #endif + #else + // Unsupported compiler. + #error "This compiler does not support the lzcnt intrinsic." + #endif +#endif +} +#endif + +#ifdef DRFLAC_IMPLEMENT_CLZ_MSVC +#include // For BitScanReverse(). + +static DRFLAC_INLINE drflac_uint32 drflac__clz_msvc(drflac_cache_t x) +{ + if (x == 0) { + return sizeof(x)*8; + } + + drflac_uint32 n; +#ifdef DRFLAC_64BIT + _BitScanReverse64((unsigned long*)&n, x); +#else + _BitScanReverse((unsigned long*)&n, x); +#endif + return sizeof(x)*8 - n - 1; +} +#endif + +static DRFLAC_INLINE drflac_uint32 drflac__clz(drflac_cache_t x) +{ +#ifdef DRFLAC_IMPLEMENT_CLZ_LZCNT + if (drflac__is_lzcnt_supported()) { + return drflac__clz_lzcnt(x); + } else +#endif + { +#ifdef DRFLAC_IMPLEMENT_CLZ_MSVC + return drflac__clz_msvc(x); +#else + return drflac__clz_software(x); +#endif + } +} + + +static inline drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs, unsigned int* pOffsetOut) +{ + drflac_uint32 zeroCounter = 0; + while (bs->cache == 0) { + zeroCounter += (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs); + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + } + + drflac_uint32 setBitOffsetPlus1 = drflac__clz(bs->cache); + setBitOffsetPlus1 += 1; + + bs->consumedBits += setBitOffsetPlus1; + bs->cache <<= setBitOffsetPlus1; + + *pOffsetOut = zeroCounter + setBitOffsetPlus1 - 1; + return DRFLAC_TRUE; +} + + + +static drflac_bool32 drflac__seek_to_byte(drflac_bs* bs, drflac_uint64 offsetFromStart) +{ + drflac_assert(bs != NULL); + drflac_assert(offsetFromStart > 0); + + // Seeking from the start is not quite as trivial as it sounds because the onSeek callback takes a signed 32-bit integer (which + // is intentional because it simplifies the implementation of the onSeek callbacks), however offsetFromStart is unsigned 64-bit. + // To resolve we just need to do an initial seek from the start, and then a series of offset seeks to make up the remainder. + if (offsetFromStart > 0x7FFFFFFF) { + drflac_uint64 bytesRemaining = offsetFromStart; + if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, drflac_seek_origin_start)) { + return DRFLAC_FALSE; + } + bytesRemaining -= 0x7FFFFFFF; + + while (bytesRemaining > 0x7FFFFFFF) { + if (!bs->onSeek(bs->pUserData, 0x7FFFFFFF, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + bytesRemaining -= 0x7FFFFFFF; + } + + if (bytesRemaining > 0) { + if (!bs->onSeek(bs->pUserData, (int)bytesRemaining, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + } + } else { + if (!bs->onSeek(bs->pUserData, (int)offsetFromStart, drflac_seek_origin_start)) { + return DRFLAC_FALSE; + } + } + + // The cache should be reset to force a reload of fresh data from the client. + drflac__reset_cache(bs); + return DRFLAC_TRUE; +} + + +static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64* pNumberOut, drflac_uint8* pCRCOut) +{ + drflac_assert(bs != NULL); + drflac_assert(pNumberOut != NULL); + + drflac_uint8 crc = *pCRCOut; + + unsigned char utf8[7] = {0}; + if (!drflac__read_uint8(bs, 8, utf8)) { + *pNumberOut = 0; + return DRFLAC_END_OF_STREAM; + } + crc = drflac_crc8(crc, utf8[0], 8); + + if ((utf8[0] & 0x80) == 0) { + *pNumberOut = utf8[0]; + *pCRCOut = crc; + return DRFLAC_SUCCESS; + } + + int byteCount = 1; + if ((utf8[0] & 0xE0) == 0xC0) { + byteCount = 2; + } else if ((utf8[0] & 0xF0) == 0xE0) { + byteCount = 3; + } else if ((utf8[0] & 0xF8) == 0xF0) { + byteCount = 4; + } else if ((utf8[0] & 0xFC) == 0xF8) { + byteCount = 5; + } else if ((utf8[0] & 0xFE) == 0xFC) { + byteCount = 6; + } else if ((utf8[0] & 0xFF) == 0xFE) { + byteCount = 7; + } else { + *pNumberOut = 0; + return DRFLAC_CRC_MISMATCH; // Bad UTF-8 encoding. + } + + // Read extra bytes. + drflac_assert(byteCount > 1); + + drflac_uint64 result = (drflac_uint64)(utf8[0] & (0xFF >> (byteCount + 1))); + for (int i = 1; i < byteCount; ++i) { + if (!drflac__read_uint8(bs, 8, utf8 + i)) { + *pNumberOut = 0; + return DRFLAC_END_OF_STREAM; + } + crc = drflac_crc8(crc, utf8[i], 8); + + result = (result << 6) | (utf8[i] & 0x3F); + } + + *pNumberOut = result; + *pCRCOut = crc; + return DRFLAC_SUCCESS; +} + + + + +// The next two functions are responsible for calculating the prediction. +// +// When the bits per sample is >16 we need to use 64-bit integer arithmetic because otherwise we'll run out of precision. It's +// safe to assume this will be slower on 32-bit platforms so we use a more optimal solution when the bits per sample is <=16. +static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) +{ + drflac_assert(order <= 32); + + // 32-bit version. + + // VC++ optimizes this to a single jmp. I've not yet verified this for other compilers. + drflac_int32 prediction = 0; + + switch (order) + { + case 32: prediction += coefficients[31] * pDecodedSamples[-32]; + case 31: prediction += coefficients[30] * pDecodedSamples[-31]; + case 30: prediction += coefficients[29] * pDecodedSamples[-30]; + case 29: prediction += coefficients[28] * pDecodedSamples[-29]; + case 28: prediction += coefficients[27] * pDecodedSamples[-28]; + case 27: prediction += coefficients[26] * pDecodedSamples[-27]; + case 26: prediction += coefficients[25] * pDecodedSamples[-26]; + case 25: prediction += coefficients[24] * pDecodedSamples[-25]; + case 24: prediction += coefficients[23] * pDecodedSamples[-24]; + case 23: prediction += coefficients[22] * pDecodedSamples[-23]; + case 22: prediction += coefficients[21] * pDecodedSamples[-22]; + case 21: prediction += coefficients[20] * pDecodedSamples[-21]; + case 20: prediction += coefficients[19] * pDecodedSamples[-20]; + case 19: prediction += coefficients[18] * pDecodedSamples[-19]; + case 18: prediction += coefficients[17] * pDecodedSamples[-18]; + case 17: prediction += coefficients[16] * pDecodedSamples[-17]; + case 16: prediction += coefficients[15] * pDecodedSamples[-16]; + case 15: prediction += coefficients[14] * pDecodedSamples[-15]; + case 14: prediction += coefficients[13] * pDecodedSamples[-14]; + case 13: prediction += coefficients[12] * pDecodedSamples[-13]; + case 12: prediction += coefficients[11] * pDecodedSamples[-12]; + case 11: prediction += coefficients[10] * pDecodedSamples[-11]; + case 10: prediction += coefficients[ 9] * pDecodedSamples[-10]; + case 9: prediction += coefficients[ 8] * pDecodedSamples[- 9]; + case 8: prediction += coefficients[ 7] * pDecodedSamples[- 8]; + case 7: prediction += coefficients[ 6] * pDecodedSamples[- 7]; + case 6: prediction += coefficients[ 5] * pDecodedSamples[- 6]; + case 5: prediction += coefficients[ 4] * pDecodedSamples[- 5]; + case 4: prediction += coefficients[ 3] * pDecodedSamples[- 4]; + case 3: prediction += coefficients[ 2] * pDecodedSamples[- 3]; + case 2: prediction += coefficients[ 1] * pDecodedSamples[- 2]; + case 1: prediction += coefficients[ 0] * pDecodedSamples[- 1]; + } + + return (drflac_int32)(prediction >> shift); +} + +static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) +{ + drflac_assert(order <= 32); + + // 64-bit version. + + // This method is faster on the 32-bit build when compiling with VC++. See note below. +#ifndef DRFLAC_64BIT + drflac_int64 prediction; + if (order == 8) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + } + else if (order == 7) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + } + else if (order == 3) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + } + else if (order == 6) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + } + else if (order == 5) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + } + else if (order == 4) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + } + else if (order == 12) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; + prediction += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; + prediction += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; + prediction += coefficients[11] * (drflac_int64)pDecodedSamples[-12]; + } + else if (order == 2) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + } + else if (order == 1) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + } + else if (order == 10) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; + prediction += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; + } + else if (order == 9) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; + } + else if (order == 11) + { + prediction = coefficients[0] * (drflac_int64)pDecodedSamples[-1]; + prediction += coefficients[1] * (drflac_int64)pDecodedSamples[-2]; + prediction += coefficients[2] * (drflac_int64)pDecodedSamples[-3]; + prediction += coefficients[3] * (drflac_int64)pDecodedSamples[-4]; + prediction += coefficients[4] * (drflac_int64)pDecodedSamples[-5]; + prediction += coefficients[5] * (drflac_int64)pDecodedSamples[-6]; + prediction += coefficients[6] * (drflac_int64)pDecodedSamples[-7]; + prediction += coefficients[7] * (drflac_int64)pDecodedSamples[-8]; + prediction += coefficients[8] * (drflac_int64)pDecodedSamples[-9]; + prediction += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; + prediction += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; + } + else + { + prediction = 0; + for (int j = 0; j < (int)order; ++j) { + prediction += coefficients[j] * (drflac_int64)pDecodedSamples[-j-1]; + } + } +#endif + + // VC++ optimizes this to a single jmp instruction, but only the 64-bit build. The 32-bit build generates less efficient code for some + // reason. The ugly version above is faster so we'll just switch between the two depending on the target platform. +#ifdef DRFLAC_64BIT + drflac_int64 prediction = 0; + + switch (order) + { + case 32: prediction += coefficients[31] * (drflac_int64)pDecodedSamples[-32]; + case 31: prediction += coefficients[30] * (drflac_int64)pDecodedSamples[-31]; + case 30: prediction += coefficients[29] * (drflac_int64)pDecodedSamples[-30]; + case 29: prediction += coefficients[28] * (drflac_int64)pDecodedSamples[-29]; + case 28: prediction += coefficients[27] * (drflac_int64)pDecodedSamples[-28]; + case 27: prediction += coefficients[26] * (drflac_int64)pDecodedSamples[-27]; + case 26: prediction += coefficients[25] * (drflac_int64)pDecodedSamples[-26]; + case 25: prediction += coefficients[24] * (drflac_int64)pDecodedSamples[-25]; + case 24: prediction += coefficients[23] * (drflac_int64)pDecodedSamples[-24]; + case 23: prediction += coefficients[22] * (drflac_int64)pDecodedSamples[-23]; + case 22: prediction += coefficients[21] * (drflac_int64)pDecodedSamples[-22]; + case 21: prediction += coefficients[20] * (drflac_int64)pDecodedSamples[-21]; + case 20: prediction += coefficients[19] * (drflac_int64)pDecodedSamples[-20]; + case 19: prediction += coefficients[18] * (drflac_int64)pDecodedSamples[-19]; + case 18: prediction += coefficients[17] * (drflac_int64)pDecodedSamples[-18]; + case 17: prediction += coefficients[16] * (drflac_int64)pDecodedSamples[-17]; + case 16: prediction += coefficients[15] * (drflac_int64)pDecodedSamples[-16]; + case 15: prediction += coefficients[14] * (drflac_int64)pDecodedSamples[-15]; + case 14: prediction += coefficients[13] * (drflac_int64)pDecodedSamples[-14]; + case 13: prediction += coefficients[12] * (drflac_int64)pDecodedSamples[-13]; + case 12: prediction += coefficients[11] * (drflac_int64)pDecodedSamples[-12]; + case 11: prediction += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; + case 10: prediction += coefficients[ 9] * (drflac_int64)pDecodedSamples[-10]; + case 9: prediction += coefficients[ 8] * (drflac_int64)pDecodedSamples[- 9]; + case 8: prediction += coefficients[ 7] * (drflac_int64)pDecodedSamples[- 8]; + case 7: prediction += coefficients[ 6] * (drflac_int64)pDecodedSamples[- 7]; + case 6: prediction += coefficients[ 5] * (drflac_int64)pDecodedSamples[- 6]; + case 5: prediction += coefficients[ 4] * (drflac_int64)pDecodedSamples[- 5]; + case 4: prediction += coefficients[ 3] * (drflac_int64)pDecodedSamples[- 4]; + case 3: prediction += coefficients[ 2] * (drflac_int64)pDecodedSamples[- 3]; + case 2: prediction += coefficients[ 1] * (drflac_int64)pDecodedSamples[- 2]; + case 1: prediction += coefficients[ 0] * (drflac_int64)pDecodedSamples[- 1]; + } +#endif + + return (drflac_int32)(prediction >> shift); +} + +static DRFLAC_INLINE void drflac__calculate_prediction_64_x4(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, const drflac_uint32 riceParamParts[4], drflac_int32* pDecodedSamples) +{ + drflac_assert(order <= 32); + + drflac_int64 prediction0 = 0; + drflac_int64 prediction1 = 0; + drflac_int64 prediction2 = 0; + drflac_int64 prediction3 = 0; + + switch (order) + { + case 32: + prediction0 += coefficients[31] * (drflac_int64)pDecodedSamples[-32]; + prediction1 += coefficients[31] * (drflac_int64)pDecodedSamples[-31]; + prediction2 += coefficients[31] * (drflac_int64)pDecodedSamples[-30]; + prediction3 += coefficients[31] * (drflac_int64)pDecodedSamples[-29]; + case 31: + prediction0 += coefficients[30] * (drflac_int64)pDecodedSamples[-31]; + prediction1 += coefficients[30] * (drflac_int64)pDecodedSamples[-30]; + prediction2 += coefficients[30] * (drflac_int64)pDecodedSamples[-29]; + prediction3 += coefficients[30] * (drflac_int64)pDecodedSamples[-28]; + case 30: + prediction0 += coefficients[29] * (drflac_int64)pDecodedSamples[-30]; + prediction1 += coefficients[29] * (drflac_int64)pDecodedSamples[-29]; + prediction2 += coefficients[29] * (drflac_int64)pDecodedSamples[-28]; + prediction3 += coefficients[29] * (drflac_int64)pDecodedSamples[-27]; + case 29: + prediction0 += coefficients[28] * (drflac_int64)pDecodedSamples[-29]; + prediction1 += coefficients[28] * (drflac_int64)pDecodedSamples[-28]; + prediction2 += coefficients[28] * (drflac_int64)pDecodedSamples[-27]; + prediction3 += coefficients[28] * (drflac_int64)pDecodedSamples[-26]; + case 28: + prediction0 += coefficients[27] * (drflac_int64)pDecodedSamples[-28]; + prediction1 += coefficients[27] * (drflac_int64)pDecodedSamples[-27]; + prediction2 += coefficients[27] * (drflac_int64)pDecodedSamples[-26]; + prediction3 += coefficients[27] * (drflac_int64)pDecodedSamples[-25]; + case 27: + prediction0 += coefficients[26] * (drflac_int64)pDecodedSamples[-27]; + prediction1 += coefficients[26] * (drflac_int64)pDecodedSamples[-26]; + prediction2 += coefficients[26] * (drflac_int64)pDecodedSamples[-25]; + prediction3 += coefficients[26] * (drflac_int64)pDecodedSamples[-24]; + case 26: + prediction0 += coefficients[25] * (drflac_int64)pDecodedSamples[-26]; + prediction1 += coefficients[25] * (drflac_int64)pDecodedSamples[-25]; + prediction2 += coefficients[25] * (drflac_int64)pDecodedSamples[-24]; + prediction3 += coefficients[25] * (drflac_int64)pDecodedSamples[-23]; + case 25: + prediction0 += coefficients[24] * (drflac_int64)pDecodedSamples[-25]; + prediction1 += coefficients[24] * (drflac_int64)pDecodedSamples[-24]; + prediction2 += coefficients[24] * (drflac_int64)pDecodedSamples[-23]; + prediction3 += coefficients[24] * (drflac_int64)pDecodedSamples[-22]; + case 24: + prediction0 += coefficients[23] * (drflac_int64)pDecodedSamples[-24]; + prediction1 += coefficients[23] * (drflac_int64)pDecodedSamples[-23]; + prediction2 += coefficients[23] * (drflac_int64)pDecodedSamples[-22]; + prediction3 += coefficients[23] * (drflac_int64)pDecodedSamples[-21]; + case 23: + prediction0 += coefficients[22] * (drflac_int64)pDecodedSamples[-23]; + prediction1 += coefficients[22] * (drflac_int64)pDecodedSamples[-22]; + prediction2 += coefficients[22] * (drflac_int64)pDecodedSamples[-21]; + prediction3 += coefficients[22] * (drflac_int64)pDecodedSamples[-20]; + case 22: + prediction0 += coefficients[21] * (drflac_int64)pDecodedSamples[-22]; + prediction1 += coefficients[21] * (drflac_int64)pDecodedSamples[-21]; + prediction2 += coefficients[21] * (drflac_int64)pDecodedSamples[-20]; + prediction3 += coefficients[21] * (drflac_int64)pDecodedSamples[-19]; + case 21: + prediction0 += coefficients[20] * (drflac_int64)pDecodedSamples[-21]; + prediction1 += coefficients[20] * (drflac_int64)pDecodedSamples[-20]; + prediction2 += coefficients[20] * (drflac_int64)pDecodedSamples[-19]; + prediction3 += coefficients[20] * (drflac_int64)pDecodedSamples[-18]; + case 20: + prediction0 += coefficients[19] * (drflac_int64)pDecodedSamples[-20]; + prediction1 += coefficients[19] * (drflac_int64)pDecodedSamples[-19]; + prediction2 += coefficients[19] * (drflac_int64)pDecodedSamples[-18]; + prediction3 += coefficients[19] * (drflac_int64)pDecodedSamples[-17]; + case 19: + prediction0 += coefficients[18] * (drflac_int64)pDecodedSamples[-19]; + prediction1 += coefficients[18] * (drflac_int64)pDecodedSamples[-18]; + prediction2 += coefficients[18] * (drflac_int64)pDecodedSamples[-17]; + prediction3 += coefficients[18] * (drflac_int64)pDecodedSamples[-16]; + case 18: + prediction0 += coefficients[17] * (drflac_int64)pDecodedSamples[-18]; + prediction1 += coefficients[17] * (drflac_int64)pDecodedSamples[-17]; + prediction2 += coefficients[17] * (drflac_int64)pDecodedSamples[-16]; + prediction3 += coefficients[17] * (drflac_int64)pDecodedSamples[-15]; + case 17: + prediction0 += coefficients[16] * (drflac_int64)pDecodedSamples[-17]; + prediction1 += coefficients[16] * (drflac_int64)pDecodedSamples[-16]; + prediction2 += coefficients[16] * (drflac_int64)pDecodedSamples[-15]; + prediction3 += coefficients[16] * (drflac_int64)pDecodedSamples[-14]; + + case 16: + prediction0 += coefficients[15] * (drflac_int64)pDecodedSamples[-16]; + prediction1 += coefficients[15] * (drflac_int64)pDecodedSamples[-15]; + prediction2 += coefficients[15] * (drflac_int64)pDecodedSamples[-14]; + prediction3 += coefficients[15] * (drflac_int64)pDecodedSamples[-13]; + case 15: + prediction0 += coefficients[14] * (drflac_int64)pDecodedSamples[-15]; + prediction1 += coefficients[14] * (drflac_int64)pDecodedSamples[-14]; + prediction2 += coefficients[14] * (drflac_int64)pDecodedSamples[-13]; + prediction3 += coefficients[14] * (drflac_int64)pDecodedSamples[-12]; + case 14: + prediction0 += coefficients[13] * (drflac_int64)pDecodedSamples[-14]; + prediction1 += coefficients[13] * (drflac_int64)pDecodedSamples[-13]; + prediction2 += coefficients[13] * (drflac_int64)pDecodedSamples[-12]; + prediction3 += coefficients[13] * (drflac_int64)pDecodedSamples[-11]; + case 13: + prediction0 += coefficients[12] * (drflac_int64)pDecodedSamples[-13]; + prediction1 += coefficients[12] * (drflac_int64)pDecodedSamples[-12]; + prediction2 += coefficients[12] * (drflac_int64)pDecodedSamples[-11]; + prediction3 += coefficients[12] * (drflac_int64)pDecodedSamples[-10]; + case 12: + prediction0 += coefficients[11] * (drflac_int64)pDecodedSamples[-12]; + prediction1 += coefficients[11] * (drflac_int64)pDecodedSamples[-11]; + prediction2 += coefficients[11] * (drflac_int64)pDecodedSamples[-10]; + prediction3 += coefficients[11] * (drflac_int64)pDecodedSamples[- 9]; + case 11: + prediction0 += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; + prediction1 += coefficients[10] * (drflac_int64)pDecodedSamples[-10]; + prediction2 += coefficients[10] * (drflac_int64)pDecodedSamples[- 9]; + prediction3 += coefficients[10] * (drflac_int64)pDecodedSamples[- 8]; + case 10: + prediction0 += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; + prediction1 += coefficients[9] * (drflac_int64)pDecodedSamples[- 9]; + prediction2 += coefficients[9] * (drflac_int64)pDecodedSamples[- 8]; + prediction3 += coefficients[9] * (drflac_int64)pDecodedSamples[- 7]; + case 9: + prediction0 += coefficients[8] * (drflac_int64)pDecodedSamples[- 9]; + prediction1 += coefficients[8] * (drflac_int64)pDecodedSamples[- 8]; + prediction2 += coefficients[8] * (drflac_int64)pDecodedSamples[- 7]; + prediction3 += coefficients[8] * (drflac_int64)pDecodedSamples[- 6]; + case 8: + prediction0 += coefficients[7] * (drflac_int64)pDecodedSamples[- 8]; + prediction1 += coefficients[7] * (drflac_int64)pDecodedSamples[- 7]; + prediction2 += coefficients[7] * (drflac_int64)pDecodedSamples[- 6]; + prediction3 += coefficients[7] * (drflac_int64)pDecodedSamples[- 5]; + case 7: + prediction0 += coefficients[6] * (drflac_int64)pDecodedSamples[- 7]; + prediction1 += coefficients[6] * (drflac_int64)pDecodedSamples[- 6]; + prediction2 += coefficients[6] * (drflac_int64)pDecodedSamples[- 5]; + prediction3 += coefficients[6] * (drflac_int64)pDecodedSamples[- 4]; + case 6: + prediction0 += coefficients[5] * (drflac_int64)pDecodedSamples[- 6]; + prediction1 += coefficients[5] * (drflac_int64)pDecodedSamples[- 5]; + prediction2 += coefficients[5] * (drflac_int64)pDecodedSamples[- 4]; + prediction3 += coefficients[5] * (drflac_int64)pDecodedSamples[- 3]; + case 5: + prediction0 += coefficients[4] * (drflac_int64)pDecodedSamples[- 5]; + prediction1 += coefficients[4] * (drflac_int64)pDecodedSamples[- 4]; + prediction2 += coefficients[4] * (drflac_int64)pDecodedSamples[- 3]; + prediction3 += coefficients[4] * (drflac_int64)pDecodedSamples[- 2]; + case 4: + prediction0 += coefficients[3] * (drflac_int64)pDecodedSamples[- 4]; + prediction1 += coefficients[3] * (drflac_int64)pDecodedSamples[- 3]; + prediction2 += coefficients[3] * (drflac_int64)pDecodedSamples[- 2]; + prediction3 += coefficients[3] * (drflac_int64)pDecodedSamples[- 1]; + order = 3; + } + + switch (order) + { + case 3: prediction0 += coefficients[ 2] * (drflac_int64)pDecodedSamples[- 3]; + case 2: prediction0 += coefficients[ 1] * (drflac_int64)pDecodedSamples[- 2]; + case 1: prediction0 += coefficients[ 0] * (drflac_int64)pDecodedSamples[- 1]; + } + pDecodedSamples[0] = riceParamParts[0] + (drflac_int32)(prediction0 >> shift); + + switch (order) + { + case 3: prediction1 += coefficients[ 2] * (drflac_int64)pDecodedSamples[- 2]; + case 2: prediction1 += coefficients[ 1] * (drflac_int64)pDecodedSamples[- 1]; + case 1: prediction1 += coefficients[ 0] * (drflac_int64)pDecodedSamples[ 0]; + } + pDecodedSamples[1] = riceParamParts[1] + (drflac_int32)(prediction1 >> shift); + + switch (order) + { + case 3: prediction2 += coefficients[ 2] * (drflac_int64)pDecodedSamples[- 1]; + case 2: prediction2 += coefficients[ 1] * (drflac_int64)pDecodedSamples[ 0]; + case 1: prediction2 += coefficients[ 0] * (drflac_int64)pDecodedSamples[ 1]; + } + pDecodedSamples[2] = riceParamParts[2] + (drflac_int32)(prediction2 >> shift); + + switch (order) + { + case 3: prediction3 += coefficients[ 2] * (drflac_int64)pDecodedSamples[ 0]; + case 2: prediction3 += coefficients[ 1] * (drflac_int64)pDecodedSamples[ 1]; + case 1: prediction3 += coefficients[ 0] * (drflac_int64)pDecodedSamples[ 2]; + } + pDecodedSamples[3] = riceParamParts[3] + (drflac_int32)(prediction3 >> shift); +} + +#if defined(DRFLAC_SUPPORT_SSE41) +static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64__sse41(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) +{ + drflac_assert(order <= 32); + + __m128i prediction = _mm_setzero_si128(); + + switch (order) + { + case 32: + case 31: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[31], 0, coefficients[30]), _mm_set_epi32(0, pDecodedSamples[-32], 0, pDecodedSamples[-31]))); + case 30: + case 29: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[29], 0, coefficients[28]), _mm_set_epi32(0, pDecodedSamples[-30], 0, pDecodedSamples[-29]))); + case 28: + case 27: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[27], 0, coefficients[26]), _mm_set_epi32(0, pDecodedSamples[-28], 0, pDecodedSamples[-27]))); + case 26: + case 25: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[25], 0, coefficients[24]), _mm_set_epi32(0, pDecodedSamples[-26], 0, pDecodedSamples[-25]))); + case 24: + case 23: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[23], 0, coefficients[22]), _mm_set_epi32(0, pDecodedSamples[-24], 0, pDecodedSamples[-23]))); + case 22: + case 21: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[21], 0, coefficients[20]), _mm_set_epi32(0, pDecodedSamples[-22], 0, pDecodedSamples[-21]))); + case 20: + case 19: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[19], 0, coefficients[18]), _mm_set_epi32(0, pDecodedSamples[-20], 0, pDecodedSamples[-19]))); + case 18: + case 17: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[17], 0, coefficients[16]), _mm_set_epi32(0, pDecodedSamples[-18], 0, pDecodedSamples[-17]))); + case 16: + case 15: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[15], 0, coefficients[14]), _mm_set_epi32(0, pDecodedSamples[-16], 0, pDecodedSamples[-15]))); + case 14: + case 13: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[13], 0, coefficients[12]), _mm_set_epi32(0, pDecodedSamples[-14], 0, pDecodedSamples[-13]))); + case 12: + case 11: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[11], 0, coefficients[10]), _mm_set_epi32(0, pDecodedSamples[-12], 0, pDecodedSamples[-11]))); + case 10: + case 9: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 9], 0, coefficients[ 8]), _mm_set_epi32(0, pDecodedSamples[-10], 0, pDecodedSamples[- 9]))); + case 8: + case 7: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 7], 0, coefficients[ 6]), _mm_set_epi32(0, pDecodedSamples[- 8], 0, pDecodedSamples[- 7]))); + case 6: + case 5: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 5], 0, coefficients[ 4]), _mm_set_epi32(0, pDecodedSamples[- 6], 0, pDecodedSamples[- 5]))); + case 4: + case 3: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 3], 0, coefficients[ 2]), _mm_set_epi32(0, pDecodedSamples[- 4], 0, pDecodedSamples[- 3]))); + case 2: + case 1: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 1], 0, coefficients[ 0]), _mm_set_epi32(0, pDecodedSamples[- 2], 0, pDecodedSamples[- 1]))); + } + + return (drflac_int32)(( + ((drflac_uint64*)&prediction)[0] + + ((drflac_uint64*)&prediction)[1]) >> shift); +} + +static DRFLAC_INLINE void drflac__calculate_prediction_64_x2__sse41(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, const drflac_uint32 riceParamParts[4], drflac_int32* pDecodedSamples) +{ + drflac_assert(order <= 32); + + __m128i prediction = _mm_setzero_si128(); + drflac_int64 predictions[2] = {0, 0}; + + switch (order) + { + case 32: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[31], 0, coefficients[31]), _mm_set_epi32(0, pDecodedSamples[-31], 0, pDecodedSamples[-32]))); + case 31: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[30], 0, coefficients[30]), _mm_set_epi32(0, pDecodedSamples[-30], 0, pDecodedSamples[-31]))); + case 30: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[29], 0, coefficients[29]), _mm_set_epi32(0, pDecodedSamples[-29], 0, pDecodedSamples[-30]))); + case 29: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[28], 0, coefficients[28]), _mm_set_epi32(0, pDecodedSamples[-28], 0, pDecodedSamples[-29]))); + case 28: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[27], 0, coefficients[27]), _mm_set_epi32(0, pDecodedSamples[-27], 0, pDecodedSamples[-28]))); + case 27: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[26], 0, coefficients[26]), _mm_set_epi32(0, pDecodedSamples[-26], 0, pDecodedSamples[-27]))); + case 26: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[25], 0, coefficients[25]), _mm_set_epi32(0, pDecodedSamples[-25], 0, pDecodedSamples[-26]))); + case 25: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[24], 0, coefficients[24]), _mm_set_epi32(0, pDecodedSamples[-24], 0, pDecodedSamples[-25]))); + case 24: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[23], 0, coefficients[23]), _mm_set_epi32(0, pDecodedSamples[-23], 0, pDecodedSamples[-24]))); + case 23: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[22], 0, coefficients[22]), _mm_set_epi32(0, pDecodedSamples[-22], 0, pDecodedSamples[-23]))); + case 22: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[21], 0, coefficients[21]), _mm_set_epi32(0, pDecodedSamples[-21], 0, pDecodedSamples[-22]))); + case 21: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[20], 0, coefficients[20]), _mm_set_epi32(0, pDecodedSamples[-20], 0, pDecodedSamples[-21]))); + case 20: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[19], 0, coefficients[19]), _mm_set_epi32(0, pDecodedSamples[-19], 0, pDecodedSamples[-20]))); + case 19: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[18], 0, coefficients[18]), _mm_set_epi32(0, pDecodedSamples[-18], 0, pDecodedSamples[-19]))); + case 18: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[17], 0, coefficients[17]), _mm_set_epi32(0, pDecodedSamples[-17], 0, pDecodedSamples[-18]))); + case 17: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[16], 0, coefficients[16]), _mm_set_epi32(0, pDecodedSamples[-16], 0, pDecodedSamples[-17]))); + case 16: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[15], 0, coefficients[15]), _mm_set_epi32(0, pDecodedSamples[-15], 0, pDecodedSamples[-16]))); + case 15: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[14], 0, coefficients[14]), _mm_set_epi32(0, pDecodedSamples[-14], 0, pDecodedSamples[-15]))); + case 14: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[13], 0, coefficients[13]), _mm_set_epi32(0, pDecodedSamples[-13], 0, pDecodedSamples[-14]))); + case 13: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[12], 0, coefficients[12]), _mm_set_epi32(0, pDecodedSamples[-12], 0, pDecodedSamples[-13]))); + case 12: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[11], 0, coefficients[11]), _mm_set_epi32(0, pDecodedSamples[-11], 0, pDecodedSamples[-12]))); + case 11: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[10], 0, coefficients[10]), _mm_set_epi32(0, pDecodedSamples[-10], 0, pDecodedSamples[-11]))); + case 10: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 9], 0, coefficients[ 9]), _mm_set_epi32(0, pDecodedSamples[- 9], 0, pDecodedSamples[-10]))); + case 9: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 8], 0, coefficients[ 8]), _mm_set_epi32(0, pDecodedSamples[- 8], 0, pDecodedSamples[- 9]))); + case 8: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 7], 0, coefficients[ 7]), _mm_set_epi32(0, pDecodedSamples[- 7], 0, pDecodedSamples[- 8]))); + case 7: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 6], 0, coefficients[ 6]), _mm_set_epi32(0, pDecodedSamples[- 6], 0, pDecodedSamples[- 7]))); + case 6: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 5], 0, coefficients[ 5]), _mm_set_epi32(0, pDecodedSamples[- 5], 0, pDecodedSamples[- 6]))); + case 5: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 4], 0, coefficients[ 4]), _mm_set_epi32(0, pDecodedSamples[- 4], 0, pDecodedSamples[- 5]))); + case 4: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 3], 0, coefficients[ 3]), _mm_set_epi32(0, pDecodedSamples[- 3], 0, pDecodedSamples[- 4]))); + case 3: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 2], 0, coefficients[ 2]), _mm_set_epi32(0, pDecodedSamples[- 2], 0, pDecodedSamples[- 3]))); + case 2: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 1], 0, coefficients[ 1]), _mm_set_epi32(0, pDecodedSamples[- 1], 0, pDecodedSamples[- 2]))); + order = 1; + } + + _mm_storeu_si128((__m128i*)predictions, prediction); + + switch (order) + { + case 1: predictions[0] += coefficients[ 0] * (drflac_int64)pDecodedSamples[- 1]; + } + pDecodedSamples[0] = riceParamParts[0] + (drflac_int32)(predictions[0] >> shift); + + switch (order) + { + case 1: predictions[1] += coefficients[ 0] * (drflac_int64)pDecodedSamples[ 0]; + } + pDecodedSamples[1] = riceParamParts[1] + (drflac_int32)(predictions[1] >> shift); +} + + +static DRFLAC_INLINE __m128i drflac__mm_not_si128(__m128i a) +{ + return _mm_xor_si128(a, _mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128())); +} + +static DRFLAC_INLINE __m128i drflac__mm_slide1_epi32(__m128i a, __m128i b) +{ + // a3a2a1a0/b3b2b1b0 -> a2a1a0b3 + + // Result = a2a1a0b3 + __m128i b3a3b2a2 = _mm_unpackhi_epi32(a, b); + __m128i a2b3a2b3 = _mm_shuffle_epi32(b3a3b2a2, _MM_SHUFFLE(0, 3, 0, 3)); + __m128i a1a2a0b3 = _mm_unpacklo_epi32(a2b3a2b3, a); + __m128i a2a1a0b3 = _mm_shuffle_epi32(a1a2a0b3, _MM_SHUFFLE(2, 3, 1, 0)); + return a2a1a0b3; +} + +static DRFLAC_INLINE __m128i drflac__mm_slide2_epi32(__m128i a, __m128i b) +{ + // Result = a1a0b3b2 + __m128i b1b0b3b2 = _mm_shuffle_epi32(b, _MM_SHUFFLE(1, 0, 3, 2)); + __m128i a1b3a0b2 = _mm_unpacklo_epi32(b1b0b3b2, a); + __m128i a1a0b3b2 = _mm_shuffle_epi32(a1b3a0b2, _MM_SHUFFLE(3, 1, 2, 0)); + return a1a0b3b2; +} + +static DRFLAC_INLINE __m128i drflac__mm_slide3_epi32(__m128i a, __m128i b) +{ + // Result = a0b3b2b1 + __m128i b1a1b0a0 = _mm_unpacklo_epi32(a, b); + __m128i a0b1a0b1 = _mm_shuffle_epi32(b1a1b0a0, _MM_SHUFFLE(0, 3, 0, 3)); + __m128i b3a0b2b1 = _mm_unpackhi_epi32(a0b1a0b1, b); + __m128i a0b3b2b1 = _mm_shuffle_epi32(b3a0b2b1, _MM_SHUFFLE(2, 3, 1, 0)); + return a0b3b2b1; +} + +static DRFLAC_INLINE void drflac__calculate_prediction_32_x4__sse41(drflac_uint32 order, drflac_int32 shift, const __m128i* coefficients128, const __m128i riceParamParts128, drflac_int32* pDecodedSamples) +{ + drflac_assert(order <= 32); + + // I don't think this is as efficient as it could be. More work needs to be done on this. + if (order > 0) { + __m128i s_09_10_11_12 = _mm_loadu_si128((const __m128i*)(pDecodedSamples - 12)); + __m128i s_05_06_07_08 = _mm_loadu_si128((const __m128i*)(pDecodedSamples - 8)); + __m128i s_01_02_03_04 = _mm_loadu_si128((const __m128i*)(pDecodedSamples - 4)); + + __m128i prediction = _mm_setzero_si128(); + + // The idea with this switch is to do do a single jump based on the value of "order". In my test library, "order" is never larger than 12, so + // I have decided to do a less optimal solution in the order > 12 case. + switch (order) + { + case 32: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[31], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 32)))); + case 31: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[30], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 31)))); + case 30: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[29], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 30)))); + case 29: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[28], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 29)))); + case 28: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[27], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 28)))); + case 27: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[26], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 27)))); + case 26: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[25], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 26)))); + case 25: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[24], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 25)))); + case 24: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[23], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 24)))); + case 23: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[22], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 23)))); + case 22: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[21], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 22)))); + case 21: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[20], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 21)))); + case 20: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[19], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 20)))); + case 19: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[18], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 19)))); + case 18: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[17], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 18)))); + case 17: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[16], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 17)))); + case 16: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[15], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 16)))); + case 15: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[14], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 15)))); + case 14: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[13], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 14)))); + case 13: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[12], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 13)))); + + case 12: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[11], s_09_10_11_12)); + case 11: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[10], drflac__mm_slide3_epi32(s_05_06_07_08, s_09_10_11_12))); + case 10: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 9], drflac__mm_slide2_epi32(s_05_06_07_08, s_09_10_11_12))); + case 9: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 8], drflac__mm_slide1_epi32(s_05_06_07_08, s_09_10_11_12))); + case 8: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 7], s_05_06_07_08)); + case 7: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 6], drflac__mm_slide3_epi32(s_01_02_03_04, s_05_06_07_08))); + case 6: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 5], drflac__mm_slide2_epi32(s_01_02_03_04, s_05_06_07_08))); + case 5: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 4], drflac__mm_slide1_epi32(s_01_02_03_04, s_05_06_07_08))); + case 4: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 3], s_01_02_03_04)); order = 3; // <-- Don't forget to set order to 3 here! + case 3: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 2], drflac__mm_slide3_epi32(_mm_setzero_si128(), s_01_02_03_04))); + case 2: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 1], drflac__mm_slide2_epi32(_mm_setzero_si128(), s_01_02_03_04))); + case 1: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 0], drflac__mm_slide1_epi32(_mm_setzero_si128(), s_01_02_03_04))); + } + + drflac_int32 predictions[4]; + _mm_storeu_si128((__m128i*)predictions, prediction); + + drflac_uint32 riceParamParts[4]; + _mm_storeu_si128((__m128i*)riceParamParts, riceParamParts128); + + predictions[0] = riceParamParts[0] + (predictions[0] >> shift); + + switch (order) + { + case 3: predictions[3] += ((const drflac_int32*)&coefficients128[ 2])[0] * predictions[ 0]; + case 2: predictions[2] += ((const drflac_int32*)&coefficients128[ 1])[0] * predictions[ 0]; + case 1: predictions[1] += ((const drflac_int32*)&coefficients128[ 0])[0] * predictions[ 0]; + } + predictions[1] = riceParamParts[1] + (predictions[1] >> shift); + + switch (order) + { + case 3: + case 2: predictions[3] += ((const drflac_int32*)&coefficients128[ 1])[0] * predictions[ 1]; + case 1: predictions[2] += ((const drflac_int32*)&coefficients128[ 0])[0] * predictions[ 1]; + } + predictions[2] = riceParamParts[2] + (predictions[2] >> shift); + + switch (order) + { + case 3: + case 2: + case 1: predictions[3] += ((const drflac_int32*)&coefficients128[ 0])[0] * predictions[ 2]; + } + predictions[3] = riceParamParts[3] + (predictions[3] >> shift); + + pDecodedSamples[0] = predictions[0]; + pDecodedSamples[1] = predictions[1]; + pDecodedSamples[2] = predictions[2]; + pDecodedSamples[3] = predictions[3]; + } else { + _mm_storeu_si128((__m128i*)pDecodedSamples, riceParamParts128); + } +} +#endif + +#if 0 +// Reference implementation for reading and decoding samples with residual. This is intentionally left unoptimized for the +// sake of readability and should only be used as a reference. +static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + drflac_assert(bs != NULL); + drflac_assert(count > 0); + drflac_assert(pSamplesOut != NULL); + + for (drflac_uint32 i = 0; i < count; ++i) { + drflac_uint32 zeroCounter = 0; + for (;;) { + drflac_uint8 bit; + if (!drflac__read_uint8(bs, 1, &bit)) { + return DRFLAC_FALSE; + } + + if (bit == 0) { + zeroCounter += 1; + } else { + break; + } + } + + drflac_uint32 decodedRice; + if (riceParam > 0) { + if (!drflac__read_uint32(bs, riceParam, &decodedRice)) { + return DRFLAC_FALSE; + } + } else { + decodedRice = 0; + } + + decodedRice |= (zeroCounter << riceParam); + if ((decodedRice & 0x01)) { + decodedRice = ~(decodedRice >> 1); + } else { + decodedRice = (decodedRice >> 1); + } + + + if (bitsPerSample > 16) { + pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i); + } else { + pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i); + } + } + + return DRFLAC_TRUE; +} +#endif + +#if 0 +static drflac_bool32 drflac__read_rice_parts__reference(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) +{ + drflac_uint32 zeroCounter = 0; + for (;;) { + drflac_uint8 bit; + if (!drflac__read_uint8(bs, 1, &bit)) { + return DRFLAC_FALSE; + } + + if (bit == 0) { + zeroCounter += 1; + } else { + break; + } + } + + drflac_uint32 decodedRice; + if (riceParam > 0) { + if (!drflac__read_uint32(bs, riceParam, &decodedRice)) { + return DRFLAC_FALSE; + } + } else { + decodedRice = 0; + } + + *pZeroCounterOut = zeroCounter; + *pRiceParamPartOut = decodedRice; + return DRFLAC_TRUE; +} +#endif + +#if 0 +static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) +{ + drflac_assert(riceParam > 0); // <-- riceParam should never be 0. drflac__read_rice_parts__param_equals_zero() should be used instead for this case. + + drflac_cache_t riceParamMask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParam); + + drflac_uint32 zeroCounter = 0; + while (bs->cache == 0) { + zeroCounter += (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs); + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + } + + drflac_uint32 setBitOffsetPlus1 = drflac__clz(bs->cache); + zeroCounter += setBitOffsetPlus1; + setBitOffsetPlus1 += 1; + + + drflac_uint32 riceParamPart; + drflac_uint32 riceLength = setBitOffsetPlus1 + riceParam; + if (riceLength < DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + riceParamPart = (drflac_uint32)((bs->cache & (riceParamMask >> setBitOffsetPlus1)) >> DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceLength)); + + bs->consumedBits += riceLength; + bs->cache <<= riceLength; + } else { + bs->consumedBits += riceLength; + bs->cache <<= setBitOffsetPlus1 & (DRFLAC_CACHE_L1_SIZE_BITS(bs)-1); // <-- Equivalent to "if (setBitOffsetPlus1 < DRFLAC_CACHE_L1_SIZE_BITS(bs)) { bs->cache <<= setBitOffsetPlus1; }" + + // It straddles the cached data. It will never cover more than the next chunk. We just read the number in two parts and combine them. + drflac_uint32 bitCountLo = bs->consumedBits - DRFLAC_CACHE_L1_SIZE_BITS(bs); + drflac_cache_t resultHi = DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, riceParam); // <-- Use DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE() if ever this function allows riceParam=0. + + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { +#ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); +#endif + bs->cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs->consumedBits = 0; +#ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs->cache; +#endif + } else { + // Slow path. We need to fetch more data from the client. + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + } + + riceParamPart = (drflac_uint32)(resultHi | DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, bitCountLo)); + + bs->consumedBits += bitCountLo; + bs->cache <<= bitCountLo; + } + + pZeroCounterOut[0] = zeroCounter; + pRiceParamPartOut[0] = riceParamPart; + + return DRFLAC_TRUE; +} +#endif + +static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x1(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) +{ + drflac_uint32 riceParamPlus1 = riceParam + 1; + //drflac_cache_t riceParamPlus1Mask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParamPlus1); + drflac_uint32 riceParamPlus1Shift = DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceParamPlus1); + drflac_uint32 riceParamPlus1MaxConsumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs) - riceParamPlus1; + + // The idea here is to use local variables for the cache in an attempt to encourage the compiler to store them in registers. I have + // no idea how this will work in practice... + drflac_cache_t bs_cache = bs->cache; + drflac_uint32 bs_consumedBits = bs->consumedBits; + + // The first thing to do is find the first unset bit. Most likely a bit will be set in the current cache line. + drflac_uint32 lzcount = drflac__clz(bs_cache); + if (lzcount < sizeof(bs_cache)*8) { + pZeroCounterOut[0] = lzcount; + + // It is most likely that the riceParam part (which comes after the zero counter) is also on this cache line. When extracting + // this, we include the set bit from the unary coded part because it simplifies cache management. This bit will be handled + // outside of this function at a higher level. + extract_rice_param_part: + bs_cache <<= lzcount; + bs_consumedBits += lzcount; + + if (bs_consumedBits <= riceParamPlus1MaxConsumedBits) { + // Getting here means the rice parameter part is wholly contained within the current cache line. + pRiceParamPartOut[0] = (drflac_uint32)(bs_cache >> riceParamPlus1Shift); + bs_cache <<= riceParamPlus1; + bs_consumedBits += riceParamPlus1; + } else { + // Getting here means the rice parameter part straddles the cache line. We need to read from the tail of the current cache + // line, reload the cache, and then combine it with the head of the next cache line. + + // Grab the high part of the rice parameter part. + drflac_uint32 riceParamPartHi = (drflac_uint32)(bs_cache >> riceParamPlus1Shift); + + // Before reloading the cache we need to grab the size in bits of the low part. + drflac_uint32 riceParamPartLoBitCount = bs_consumedBits - riceParamPlus1MaxConsumedBits; + drflac_assert(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); + + // Now reload the cache. + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = riceParamPartLoBitCount; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + // Slow path. We need to fetch more data from the client. + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; + } + + // We should now have enough information to construct the rice parameter part. + drflac_uint32 riceParamPartLo = (drflac_uint32)(bs_cache >> (DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceParamPartLoBitCount))); + pRiceParamPartOut[0] = riceParamPartHi | riceParamPartLo; + + bs_cache <<= riceParamPartLoBitCount; + } + } else { + // Getting here means there are no bits set on the cache line. This is a less optimal case because we just wasted a call + // to drflac__clz() and we need to reload the cache. + drflac_uint32 zeroCounter = (drflac_uint32)(DRFLAC_CACHE_L1_SIZE_BITS(bs) - bs_consumedBits); + for (;;) { + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = 0; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + // Slow path. We need to fetch more data from the client. + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits; + } + + lzcount = drflac__clz(bs_cache); + zeroCounter += lzcount; + + if (lzcount < sizeof(bs_cache)*8) { + break; + } + } + + pZeroCounterOut[0] = zeroCounter; + goto extract_rice_param_part; + } + + // Make sure the cache is restored at the end of it all. + bs->cache = bs_cache; + bs->consumedBits = bs_consumedBits; + + return DRFLAC_TRUE; +} + +static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x4(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) +{ + drflac_uint32 riceParamPlus1 = riceParam + 1; + //drflac_cache_t riceParamPlus1Mask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParamPlus1); + drflac_uint32 riceParamPlus1Shift = DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceParamPlus1); + drflac_uint32 riceParamPlus1MaxConsumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs) - riceParamPlus1; + + // The idea here is to use local variables for the cache in an attempt to encourage the compiler to store them in registers. I have + // no idea how this will work in practice... + drflac_cache_t bs_cache = bs->cache; + drflac_uint32 bs_consumedBits = bs->consumedBits; + + // What this is doing is trying to efficiently extract 4 rice parts at a time, the idea being that we can exploit certain properties + // to our advantage to make things more efficient. + for (int i = 0; i < 4; ++i) { + // The first thing to do is find the first unset bit. Most likely a bit will be set in the current cache line. + drflac_uint32 lzcount = drflac__clz(bs_cache); + if (lzcount < sizeof(bs_cache)*8) { + pZeroCounterOut[i] = lzcount; + + // It is most likely that the riceParam part (which comes after the zero counter) is also on this cache line. When extracting + // this, we include the set bit from the unary coded part because it simplifies cache management. This bit will be handled + // outside of this function at a higher level. + extract_rice_param_part: + bs_cache <<= lzcount; + bs_consumedBits += lzcount; + + if (bs_consumedBits <= riceParamPlus1MaxConsumedBits) { + // Getting here means the rice parameter part is wholly contained within the current cache line. + pRiceParamPartOut[i] = (drflac_uint32)(bs_cache >> riceParamPlus1Shift); + bs_cache <<= riceParamPlus1; + bs_consumedBits += riceParamPlus1; + } else { + // Getting here means the rice parameter part straddles the cache line. We need to read from the tail of the current cache + // line, reload the cache, and then combine it with the head of the next cache line. + + // Grab the high part of the rice parameter part. + drflac_uint32 riceParamPartHi = (drflac_uint32)(bs_cache >> riceParamPlus1Shift); + + // Before reloading the cache we need to grab the size in bits of the low part. + drflac_uint32 riceParamPartLoBitCount = bs_consumedBits - riceParamPlus1MaxConsumedBits; + + // Now reload the cache. + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = riceParamPartLoBitCount; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + // Slow path. We need to fetch more data from the client. + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; + } + + // We should now have enough information to construct the rice parameter part. + drflac_uint32 riceParamPartLo = (drflac_uint32)(bs_cache >> (DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceParamPartLoBitCount))); + pRiceParamPartOut[i] = riceParamPartHi | riceParamPartLo; + + bs_cache <<= riceParamPartLoBitCount; + } + } else { + // Getting here means there are no bits set on the cache line. This is a less optimal case because we just wasted a call + // to drflac__clz() and we need to reload the cache. + drflac_uint32 zeroCounter = (drflac_uint32)(DRFLAC_CACHE_L1_SIZE_BITS(bs) - bs_consumedBits); + for (;;) { + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = 0; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + // Slow path. We need to fetch more data from the client. + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits; + } + + lzcount = drflac__clz(bs_cache); + zeroCounter += lzcount; + + if (lzcount < sizeof(bs_cache)*8) { + break; + } + } + + pZeroCounterOut[i] = zeroCounter; + goto extract_rice_param_part; + } + } + + // Make sure the cache is restored at the end of it all. + bs->cache = bs_cache; + bs->consumedBits = bs_consumedBits; + + return DRFLAC_TRUE; +} + +static DRFLAC_INLINE drflac_bool32 drflac__seek_rice_parts(drflac_bs* bs, drflac_uint8 riceParam) +{ + drflac_uint32 riceParamPlus1 = riceParam + 1; + drflac_uint32 riceParamPlus1MaxConsumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs) - riceParamPlus1; + + // The idea here is to use local variables for the cache in an attempt to encourage the compiler to store them in registers. I have + // no idea how this will work in practice... + drflac_cache_t bs_cache = bs->cache; + drflac_uint32 bs_consumedBits = bs->consumedBits; + + // The first thing to do is find the first unset bit. Most likely a bit will be set in the current cache line. + drflac_uint32 lzcount = drflac__clz(bs_cache); + if (lzcount < sizeof(bs_cache)*8) { + // It is most likely that the riceParam part (which comes after the zero counter) is also on this cache line. When extracting + // this, we include the set bit from the unary coded part because it simplifies cache management. This bit will be handled + // outside of this function at a higher level. + extract_rice_param_part: + bs_cache <<= lzcount; + bs_consumedBits += lzcount; + + if (bs_consumedBits <= riceParamPlus1MaxConsumedBits) { + // Getting here means the rice parameter part is wholly contained within the current cache line. + bs_cache <<= riceParamPlus1; + bs_consumedBits += riceParamPlus1; + } else { + // Getting here means the rice parameter part straddles the cache line. We need to read from the tail of the current cache + // line, reload the cache, and then combine it with the head of the next cache line. + + // Before reloading the cache we need to grab the size in bits of the low part. + drflac_uint32 riceParamPartLoBitCount = bs_consumedBits - riceParamPlus1MaxConsumedBits; + drflac_assert(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); + + // Now reload the cache. + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = riceParamPartLoBitCount; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + // Slow path. We need to fetch more data from the client. + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; + } + + bs_cache <<= riceParamPartLoBitCount; + } + } else { + // Getting here means there are no bits set on the cache line. This is a less optimal case because we just wasted a call + // to drflac__clz() and we need to reload the cache. + for (;;) { + if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { + #ifndef DR_FLAC_NO_CRC + drflac__update_crc16(bs); + #endif + bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); + bs_consumedBits = 0; + #ifndef DR_FLAC_NO_CRC + bs->crc16Cache = bs_cache; + #endif + } else { + // Slow path. We need to fetch more data from the client. + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + + bs_cache = bs->cache; + bs_consumedBits = bs->consumedBits; + } + + lzcount = drflac__clz(bs_cache); + if (lzcount < sizeof(bs_cache)*8) { + break; + } + } + + goto extract_rice_param_part; + } + + // Make sure the cache is restored at the end of it all. + bs->cache = bs_cache; + bs->consumedBits = bs_consumedBits; + + return DRFLAC_TRUE; +} + + +static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + drflac_assert(bs != NULL); + drflac_assert(count > 0); + drflac_assert(pSamplesOut != NULL); + + drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + drflac_uint32 zeroCountPart0; + drflac_uint32 zeroCountPart1; + drflac_uint32 zeroCountPart2; + drflac_uint32 zeroCountPart3; + drflac_uint32 riceParamPart0; + drflac_uint32 riceParamPart1; + drflac_uint32 riceParamPart2; + drflac_uint32 riceParamPart3; + drflac_uint32 riceParamMask = ~((~0UL) << riceParam); + const drflac_int32* pSamplesOutEnd = pSamplesOut + ((count >> 2) << 2); + + if (bitsPerSample >= 24) { + while (pSamplesOut < pSamplesOutEnd) { + // Rice extraction. It's faster to do this one at a time against local variables than it is to use the x4 version + // against an array. Not sure why, but perhaps it's making more efficient use of registers? + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart1, &riceParamPart1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart2, &riceParamPart2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart3, &riceParamPart3)) { + return DRFLAC_FALSE; + } + + riceParamPart0 &= riceParamMask; + riceParamPart1 &= riceParamMask; + riceParamPart2 &= riceParamMask; + riceParamPart3 &= riceParamMask; + + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart1 |= (zeroCountPart1 << riceParam); + riceParamPart2 |= (zeroCountPart2 << riceParam); + riceParamPart3 |= (zeroCountPart3 << riceParam); + + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; + riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; + riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; + + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 3); + + pSamplesOut += 4; + } + } else { + while (pSamplesOut < pSamplesOutEnd) { + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart1, &riceParamPart1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart2, &riceParamPart2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart3, &riceParamPart3)) { + return DRFLAC_FALSE; + } + + riceParamPart0 &= riceParamMask; + riceParamPart1 &= riceParamMask; + riceParamPart2 &= riceParamMask; + riceParamPart3 &= riceParamMask; + + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart1 |= (zeroCountPart1 << riceParam); + riceParamPart2 |= (zeroCountPart2 << riceParam); + riceParamPart3 |= (zeroCountPart3 << riceParam); + + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; + riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; + riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; + + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 3); + + pSamplesOut += 4; + } + } + + + + drflac_uint32 i = ((count >> 2) << 2); + while (i < count) { + // Rice extraction. + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0)) { + return DRFLAC_FALSE; + } + + // Rice reconstruction. + riceParamPart0 &= riceParamMask; + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + //riceParamPart0 = (riceParamPart0 >> 1) ^ (~(riceParamPart0 & 0x01) + 1); + + // Sample reconstruction. + if (bitsPerSample >= 24) { + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0); + } else { + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); + } + + i += 1; + pSamplesOut += 1; + } + + return DRFLAC_TRUE; +} + +#if defined(DRFLAC_SUPPORT_SSE41) +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + drflac_assert(bs != NULL); + drflac_assert(count > 0); + drflac_assert(pSamplesOut != NULL); + + static drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + //drflac_uint32 zeroCountParts[4]; + //drflac_uint32 riceParamParts[4]; + + drflac_uint32 zeroCountParts0; + drflac_uint32 zeroCountParts1; + drflac_uint32 zeroCountParts2; + drflac_uint32 zeroCountParts3; + drflac_uint32 riceParamParts0; + drflac_uint32 riceParamParts1; + drflac_uint32 riceParamParts2; + drflac_uint32 riceParamParts3; + + drflac_uint32 riceParamMask = ~((~0UL) << riceParam); + __m128i riceParamMask128 = _mm_set1_epi32(riceParamMask); + __m128i one = _mm_set1_epi32(0x01); + + const drflac_int32* pSamplesOutEnd = pSamplesOut + ((count >> 2) << 2); + + if (bitsPerSample >= 24) { + while (pSamplesOut < pSamplesOutEnd) { + // Rice extraction. + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts1, &riceParamParts1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts2, &riceParamParts2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts3, &riceParamParts3)) { + return DRFLAC_FALSE; + } + + __m128i zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); + __m128i riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); + + riceParamPart128 = _mm_and_si128(riceParamPart128, riceParamMask128); + riceParamPart128 = _mm_or_si128(riceParamPart128, _mm_slli_epi32(zeroCountPart128, riceParam)); + riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_mullo_epi32(_mm_and_si128(riceParamPart128, one), _mm_set1_epi32(0xFFFFFFFF))); // <-- Only supported from SSE4.1 + //riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_add_epi32(drflac__mm_not_si128(_mm_and_si128(riceParamPart128, one)), one)); // <-- SSE2 compatible + + drflac_uint32 riceParamParts[4]; + _mm_storeu_si128((__m128i*)riceParamParts, riceParamPart128); + + #if defined(DRFLAC_64BIT) + // The scalar implementation seems to be faster on 64-bit in my testing. + drflac__calculate_prediction_64_x4(order, shift, coefficients, riceParamParts, pSamplesOut); + #else + pSamplesOut[0] = riceParamParts[0] + drflac__calculate_prediction_64__sse41(order, shift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamParts[1] + drflac__calculate_prediction_64__sse41(order, shift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamParts[2] + drflac__calculate_prediction_64__sse41(order, shift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamParts[3] + drflac__calculate_prediction_64__sse41(order, shift, coefficients, pSamplesOut + 3); + #endif + + pSamplesOut += 4; + } + } else { + drflac_int32 coefficientsUnaligned[32*4 + 4] = {0}; + drflac_int32* coefficients128 = (drflac_int32*)(((size_t)coefficientsUnaligned + 15) & ~15); + for (drflac_uint32 i = 0; i < order; ++i) { + coefficients128[i*4+0] = coefficients[i]; + coefficients128[i*4+1] = coefficients[i]; + coefficients128[i*4+2] = coefficients[i]; + coefficients128[i*4+3] = coefficients[i]; + } + + while (pSamplesOut < pSamplesOutEnd) { + // Rice extraction. +#if 1 + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts1, &riceParamParts1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts2, &riceParamParts2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts3, &riceParamParts3)) { + return DRFLAC_FALSE; + } + + __m128i zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); + __m128i riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); +#else + if (!drflac__read_rice_parts_x4(bs, riceParam, zeroCountParts, riceParamParts)) { + return DRFLAC_FALSE; + } + + __m128i zeroCountPart128 = _mm_set_epi32(zeroCountParts[3], zeroCountParts[2], zeroCountParts[1], zeroCountParts[0]); + __m128i riceParamPart128 = _mm_set_epi32(riceParamParts[3], riceParamParts[2], riceParamParts[1], riceParamParts[0]); +#endif + + riceParamPart128 = _mm_and_si128(riceParamPart128, riceParamMask128); + riceParamPart128 = _mm_or_si128(riceParamPart128, _mm_slli_epi32(zeroCountPart128, riceParam)); + riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_mullo_epi32(_mm_and_si128(riceParamPart128, one), _mm_set1_epi32(0xFFFFFFFF))); + +#if 1 + drflac__calculate_prediction_32_x4__sse41(order, shift, (const __m128i*)coefficients128, riceParamPart128, pSamplesOut); +#else + drflac_int32 riceParamParts[4]; + _mm_storeu_si128((__m128i*)riceParamParts, riceParamPart128); + + pSamplesOut[0] = riceParamParts[0] + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamParts[1] + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamParts[2] + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamParts[3] + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 3); +#endif + + pSamplesOut += 4; + } + } + + + drflac_uint32 i = ((count >> 2) << 2); + while (i < count) { + // Rice extraction. + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0)) { + return DRFLAC_FALSE; + } + + // Rice reconstruction. + riceParamParts0 &= riceParamMask; + riceParamParts0 |= (zeroCountParts0 << riceParam); + riceParamParts0 = (riceParamParts0 >> 1) ^ t[riceParamParts0 & 0x01]; + + // Sample reconstruction. + if (bitsPerSample >= 24) { + pSamplesOut[0] = riceParamParts0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0); + } else { + pSamplesOut[0] = riceParamParts0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); + } + + i += 1; + pSamplesOut += 1; + } + + return DRFLAC_TRUE; +} +#endif + +static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ +#if defined(DRFLAC_SUPPORT_SSE41) + if (drflac__gIsSSE41Supported) { + return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + } else +#endif + { + // Scalar fallback. + #if 0 + return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + #else + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + #endif + } +} + +// Reads and seeks past a string of residual values as Rice codes. The decoder should be sitting on the first bit of the Rice codes. +static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam) +{ + drflac_assert(bs != NULL); + drflac_assert(count > 0); + + for (drflac_uint32 i = 0; i < count; ++i) { + if (!drflac__seek_rice_parts(bs, riceParam)) { + return DRFLAC_FALSE; + } + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + drflac_assert(bs != NULL); + drflac_assert(count > 0); + drflac_assert(unencodedBitsPerSample <= 31); // <-- unencodedBitsPerSample is a 5 bit number, so cannot exceed 31. + drflac_assert(pSamplesOut != NULL); + + for (unsigned int i = 0; i < count; ++i) { + if (unencodedBitsPerSample > 0) { + if (!drflac__read_int32(bs, unencodedBitsPerSample, pSamplesOut + i)) { + return DRFLAC_FALSE; + } + } else { + pSamplesOut[i] = 0; + } + + if (bitsPerSample > 16) { + pSamplesOut[i] += drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i); + } else { + pSamplesOut[i] += drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i); + } + } + + return DRFLAC_TRUE; +} + + +// Reads and decodes the residual for the sub-frame the decoder is currently sitting on. This function should be called +// when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be ignored. The +// and parameters are used to determine how many residual values need to be decoded. +static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) +{ + drflac_assert(bs != NULL); + drflac_assert(blockSize != 0); + drflac_assert(pDecodedSamples != NULL); // <-- Should we allow NULL, in which case we just seek past the residual rather than do a full decode? + + drflac_uint8 residualMethod; + if (!drflac__read_uint8(bs, 2, &residualMethod)) { + return DRFLAC_FALSE; + } + + if (residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE && residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { + return DRFLAC_FALSE; // Unknown or unsupported residual coding method. + } + + // Ignore the first values. + pDecodedSamples += order; + + + drflac_uint8 partitionOrder; + if (!drflac__read_uint8(bs, 4, &partitionOrder)) { + return DRFLAC_FALSE; + } + + // From the FLAC spec: + // The Rice partition order in a Rice-coded residual section must be less than or equal to 8. + if (partitionOrder > 8) { + return DRFLAC_FALSE; + } + + // Validation check. + if ((blockSize / (1 << partitionOrder)) <= order) { + return DRFLAC_FALSE; + } + + drflac_uint32 samplesInPartition = (blockSize / (1 << partitionOrder)) - order; + drflac_uint32 partitionsRemaining = (1 << partitionOrder); + for (;;) { + drflac_uint8 riceParam = 0; + if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE) { + if (!drflac__read_uint8(bs, 4, &riceParam)) { + return DRFLAC_FALSE; + } + if (riceParam == 15) { + riceParam = 0xFF; + } + } else if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { + if (!drflac__read_uint8(bs, 5, &riceParam)) { + return DRFLAC_FALSE; + } + if (riceParam == 31) { + riceParam = 0xFF; + } + } + + if (riceParam != 0xFF) { + if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, order, shift, coefficients, pDecodedSamples)) { + return DRFLAC_FALSE; + } + } else { + unsigned char unencodedBitsPerSample = 0; + if (!drflac__read_uint8(bs, 5, &unencodedBitsPerSample)) { + return DRFLAC_FALSE; + } + + if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, order, shift, coefficients, pDecodedSamples)) { + return DRFLAC_FALSE; + } + } + + pDecodedSamples += samplesInPartition; + + + if (partitionsRemaining == 1) { + break; + } + + partitionsRemaining -= 1; + + if (partitionOrder != 0) { + samplesInPartition = blockSize / (1 << partitionOrder); + } + } + + return DRFLAC_TRUE; +} + +// Reads and seeks past the residual for the sub-frame the decoder is currently sitting on. This function should be called +// when the decoder is sitting at the very start of the RESIDUAL block. The first residuals will be set to 0. The +// and parameters are used to determine how many residual values need to be decoded. +static drflac_bool32 drflac__read_and_seek_residual(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 order) +{ + drflac_assert(bs != NULL); + drflac_assert(blockSize != 0); + + drflac_uint8 residualMethod; + if (!drflac__read_uint8(bs, 2, &residualMethod)) { + return DRFLAC_FALSE; + } + + if (residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE && residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { + return DRFLAC_FALSE; // Unknown or unsupported residual coding method. + } + + drflac_uint8 partitionOrder; + if (!drflac__read_uint8(bs, 4, &partitionOrder)) { + return DRFLAC_FALSE; + } + + // From the FLAC spec: + // The Rice partition order in a Rice-coded residual section must be less than or equal to 8. + if (partitionOrder > 8) { + return DRFLAC_FALSE; + } + + // Validation check. + if ((blockSize / (1 << partitionOrder)) <= order) { + return DRFLAC_FALSE; + } + + drflac_uint32 samplesInPartition = (blockSize / (1 << partitionOrder)) - order; + drflac_uint32 partitionsRemaining = (1 << partitionOrder); + for (;;) + { + drflac_uint8 riceParam = 0; + if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE) { + if (!drflac__read_uint8(bs, 4, &riceParam)) { + return DRFLAC_FALSE; + } + if (riceParam == 15) { + riceParam = 0xFF; + } + } else if (residualMethod == DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { + if (!drflac__read_uint8(bs, 5, &riceParam)) { + return DRFLAC_FALSE; + } + if (riceParam == 31) { + riceParam = 0xFF; + } + } + + if (riceParam != 0xFF) { + if (!drflac__read_and_seek_residual__rice(bs, samplesInPartition, riceParam)) { + return DRFLAC_FALSE; + } + } else { + unsigned char unencodedBitsPerSample = 0; + if (!drflac__read_uint8(bs, 5, &unencodedBitsPerSample)) { + return DRFLAC_FALSE; + } + + if (!drflac__seek_bits(bs, unencodedBitsPerSample * samplesInPartition)) { + return DRFLAC_FALSE; + } + } + + + if (partitionsRemaining == 1) { + break; + } + + partitionsRemaining -= 1; + samplesInPartition = blockSize / (1 << partitionOrder); + } + + return DRFLAC_TRUE; +} + + +static drflac_bool32 drflac__decode_samples__constant(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_int32* pDecodedSamples) +{ + // Only a single sample needs to be decoded here. + drflac_int32 sample; + if (!drflac__read_int32(bs, bitsPerSample, &sample)) { + return DRFLAC_FALSE; + } + + // We don't really need to expand this, but it does simplify the process of reading samples. If this becomes a performance issue (unlikely) + // we'll want to look at a more efficient way. + for (drflac_uint32 i = 0; i < blockSize; ++i) { + pDecodedSamples[i] = sample; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples__verbatim(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_int32* pDecodedSamples) +{ + for (drflac_uint32 i = 0; i < blockSize; ++i) { + drflac_int32 sample; + if (!drflac__read_int32(bs, bitsPerSample, &sample)) { + return DRFLAC_FALSE; + } + + pDecodedSamples[i] = sample; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_uint8 lpcOrder, drflac_int32* pDecodedSamples) +{ + static drflac_int32 lpcCoefficientsTable[5][4] = { + {0, 0, 0, 0}, + {1, 0, 0, 0}, + {2, -1, 0, 0}, + {3, -3, 1, 0}, + {4, -6, 4, -1} + }; + + // Warm up samples and coefficients. + for (drflac_uint32 i = 0; i < lpcOrder; ++i) { + drflac_int32 sample; + if (!drflac__read_int32(bs, bitsPerSample, &sample)) { + return DRFLAC_FALSE; + } + + pDecodedSamples[i] = sample; + } + + + if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, 0, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { + return DRFLAC_FALSE; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_uint8 lpcOrder, drflac_int32* pDecodedSamples) +{ + drflac_uint8 i; + + // Warm up samples. + for (i = 0; i < lpcOrder; ++i) { + drflac_int32 sample; + if (!drflac__read_int32(bs, bitsPerSample, &sample)) { + return DRFLAC_FALSE; + } + + pDecodedSamples[i] = sample; + } + + drflac_uint8 lpcPrecision; + if (!drflac__read_uint8(bs, 4, &lpcPrecision)) { + return DRFLAC_FALSE; + } + if (lpcPrecision == 15) { + return DRFLAC_FALSE; // Invalid. + } + lpcPrecision += 1; + + + drflac_int8 lpcShift; + if (!drflac__read_int8(bs, 5, &lpcShift)) { + return DRFLAC_FALSE; + } + + + drflac_int32 coefficients[32]; + drflac_zero_memory(coefficients, sizeof(coefficients)); + for (i = 0; i < lpcOrder; ++i) { + if (!drflac__read_int32(bs, lpcPrecision, coefficients + i)) { + return DRFLAC_FALSE; + } + } + + if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, coefficients, pDecodedSamples)) { + return DRFLAC_FALSE; + } + + return DRFLAC_TRUE; +} + + +static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_uint8 streaminfoBitsPerSample, drflac_frame_header* header) +{ + drflac_assert(bs != NULL); + drflac_assert(header != NULL); + + const drflac_uint32 sampleRateTable[12] = {0, 88200, 176400, 192000, 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000}; + const drflac_uint8 bitsPerSampleTable[8] = {0, 8, 12, (drflac_uint8)-1, 16, 20, 24, (drflac_uint8)-1}; // -1 = reserved. + + // Keep looping until we find a valid sync code. + for (;;) { + if (!drflac__find_and_seek_to_next_sync_code(bs)) { + return DRFLAC_FALSE; + } + + drflac_uint8 crc8 = 0xCE; // 0xCE = drflac_crc8(0, 0x3FFE, 14); + + drflac_uint8 reserved = 0; + if (!drflac__read_uint8(bs, 1, &reserved)) { + return DRFLAC_FALSE; + } + if (reserved == 1) { + continue; + } + crc8 = drflac_crc8(crc8, reserved, 1); + + + drflac_uint8 blockingStrategy = 0; + if (!drflac__read_uint8(bs, 1, &blockingStrategy)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, blockingStrategy, 1); + + + drflac_uint8 blockSize = 0; + if (!drflac__read_uint8(bs, 4, &blockSize)) { + return DRFLAC_FALSE; + } + if (blockSize == 0) { + continue; + } + crc8 = drflac_crc8(crc8, blockSize, 4); + + + drflac_uint8 sampleRate = 0; + if (!drflac__read_uint8(bs, 4, &sampleRate)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, sampleRate, 4); + + + drflac_uint8 channelAssignment = 0; + if (!drflac__read_uint8(bs, 4, &channelAssignment)) { + return DRFLAC_FALSE; + } + if (channelAssignment > 10) { + continue; + } + crc8 = drflac_crc8(crc8, channelAssignment, 4); + + + drflac_uint8 bitsPerSample = 0; + if (!drflac__read_uint8(bs, 3, &bitsPerSample)) { + return DRFLAC_FALSE; + } + if (bitsPerSample == 3 || bitsPerSample == 7) { + continue; + } + crc8 = drflac_crc8(crc8, bitsPerSample, 3); + + + if (!drflac__read_uint8(bs, 1, &reserved)) { + return DRFLAC_FALSE; + } + if (reserved == 1) { + continue; + } + crc8 = drflac_crc8(crc8, reserved, 1); + + + drflac_bool32 isVariableBlockSize = blockingStrategy == 1; + if (isVariableBlockSize) { + drflac_uint64 sampleNumber; + drflac_result result = drflac__read_utf8_coded_number(bs, &sampleNumber, &crc8); + if (result != DRFLAC_SUCCESS) { + if (result == DRFLAC_END_OF_STREAM) { + return DRFLAC_FALSE; + } else { + continue; + } + } + header->frameNumber = 0; + header->sampleNumber = sampleNumber; + } else { + drflac_uint64 frameNumber = 0; + drflac_result result = drflac__read_utf8_coded_number(bs, &frameNumber, &crc8); + if (result != DRFLAC_SUCCESS) { + if (result == DRFLAC_END_OF_STREAM) { + return DRFLAC_FALSE; + } else { + continue; + } + } + header->frameNumber = (drflac_uint32)frameNumber; // <-- Safe cast. + header->sampleNumber = 0; + } + + + if (blockSize == 1) { + header->blockSize = 192; + } else if (blockSize >= 2 && blockSize <= 5) { + header->blockSize = 576 * (1 << (blockSize - 2)); + } else if (blockSize == 6) { + if (!drflac__read_uint16(bs, 8, &header->blockSize)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->blockSize, 8); + header->blockSize += 1; + } else if (blockSize == 7) { + if (!drflac__read_uint16(bs, 16, &header->blockSize)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->blockSize, 16); + header->blockSize += 1; + } else { + header->blockSize = 256 * (1 << (blockSize - 8)); + } + + + if (sampleRate <= 11) { + header->sampleRate = sampleRateTable[sampleRate]; + } else if (sampleRate == 12) { + if (!drflac__read_uint32(bs, 8, &header->sampleRate)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->sampleRate, 8); + header->sampleRate *= 1000; + } else if (sampleRate == 13) { + if (!drflac__read_uint32(bs, 16, &header->sampleRate)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->sampleRate, 16); + } else if (sampleRate == 14) { + if (!drflac__read_uint32(bs, 16, &header->sampleRate)) { + return DRFLAC_FALSE; + } + crc8 = drflac_crc8(crc8, header->sampleRate, 16); + header->sampleRate *= 10; + } else { + continue; // Invalid. Assume an invalid block. + } + + + header->channelAssignment = channelAssignment; + + header->bitsPerSample = bitsPerSampleTable[bitsPerSample]; + if (header->bitsPerSample == 0) { + header->bitsPerSample = streaminfoBitsPerSample; + } + + if (!drflac__read_uint8(bs, 8, &header->crc8)) { + return DRFLAC_FALSE; + } + +#ifndef DR_FLAC_NO_CRC + if (header->crc8 != crc8) { + continue; // CRC mismatch. Loop back to the top and find the next sync code. + } +#endif + return DRFLAC_TRUE; + } +} + +static drflac_bool32 drflac__read_subframe_header(drflac_bs* bs, drflac_subframe* pSubframe) +{ + drflac_uint8 header; + if (!drflac__read_uint8(bs, 8, &header)) { + return DRFLAC_FALSE; + } + + // First bit should always be 0. + if ((header & 0x80) != 0) { + return DRFLAC_FALSE; + } + + int type = (header & 0x7E) >> 1; + if (type == 0) { + pSubframe->subframeType = DRFLAC_SUBFRAME_CONSTANT; + } else if (type == 1) { + pSubframe->subframeType = DRFLAC_SUBFRAME_VERBATIM; + } else { + if ((type & 0x20) != 0) { + pSubframe->subframeType = DRFLAC_SUBFRAME_LPC; + pSubframe->lpcOrder = (type & 0x1F) + 1; + } else if ((type & 0x08) != 0) { + pSubframe->subframeType = DRFLAC_SUBFRAME_FIXED; + pSubframe->lpcOrder = (type & 0x07); + if (pSubframe->lpcOrder > 4) { + pSubframe->subframeType = DRFLAC_SUBFRAME_RESERVED; + pSubframe->lpcOrder = 0; + } + } else { + pSubframe->subframeType = DRFLAC_SUBFRAME_RESERVED; + } + } + + if (pSubframe->subframeType == DRFLAC_SUBFRAME_RESERVED) { + return DRFLAC_FALSE; + } + + // Wasted bits per sample. + pSubframe->wastedBitsPerSample = 0; + if ((header & 0x01) == 1) { + unsigned int wastedBitsPerSample; + if (!drflac__seek_past_next_set_bit(bs, &wastedBitsPerSample)) { + return DRFLAC_FALSE; + } + pSubframe->wastedBitsPerSample = (unsigned char)wastedBitsPerSample + 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame, int subframeIndex, drflac_int32* pDecodedSamplesOut) +{ + drflac_assert(bs != NULL); + drflac_assert(frame != NULL); + + drflac_subframe* pSubframe = frame->subframes + subframeIndex; + if (!drflac__read_subframe_header(bs, pSubframe)) { + return DRFLAC_FALSE; + } + + // Side channels require an extra bit per sample. Took a while to figure that one out... + pSubframe->bitsPerSample = frame->header.bitsPerSample; + if ((frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE || frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE) && subframeIndex == 1) { + pSubframe->bitsPerSample += 1; + } else if (frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { + pSubframe->bitsPerSample += 1; + } + + // Need to handle wasted bits per sample. + if (pSubframe->wastedBitsPerSample >= pSubframe->bitsPerSample) { + return DRFLAC_FALSE; + } + pSubframe->bitsPerSample -= pSubframe->wastedBitsPerSample; + pSubframe->pDecodedSamples = pDecodedSamplesOut; + + switch (pSubframe->subframeType) + { + case DRFLAC_SUBFRAME_CONSTANT: + { + drflac__decode_samples__constant(bs, frame->header.blockSize, pSubframe->bitsPerSample, pSubframe->pDecodedSamples); + } break; + + case DRFLAC_SUBFRAME_VERBATIM: + { + drflac__decode_samples__verbatim(bs, frame->header.blockSize, pSubframe->bitsPerSample, pSubframe->pDecodedSamples); + } break; + + case DRFLAC_SUBFRAME_FIXED: + { + drflac__decode_samples__fixed(bs, frame->header.blockSize, pSubframe->bitsPerSample, pSubframe->lpcOrder, pSubframe->pDecodedSamples); + } break; + + case DRFLAC_SUBFRAME_LPC: + { + drflac__decode_samples__lpc(bs, frame->header.blockSize, pSubframe->bitsPerSample, pSubframe->lpcOrder, pSubframe->pDecodedSamples); + } break; + + default: return DRFLAC_FALSE; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__seek_subframe(drflac_bs* bs, drflac_frame* frame, int subframeIndex) +{ + drflac_assert(bs != NULL); + drflac_assert(frame != NULL); + + drflac_subframe* pSubframe = frame->subframes + subframeIndex; + if (!drflac__read_subframe_header(bs, pSubframe)) { + return DRFLAC_FALSE; + } + + // Side channels require an extra bit per sample. Took a while to figure that one out... + pSubframe->bitsPerSample = frame->header.bitsPerSample; + if ((frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE || frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE) && subframeIndex == 1) { + pSubframe->bitsPerSample += 1; + } else if (frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { + pSubframe->bitsPerSample += 1; + } + + // Need to handle wasted bits per sample. + if (pSubframe->wastedBitsPerSample >= pSubframe->bitsPerSample) { + return DRFLAC_FALSE; + } + pSubframe->bitsPerSample -= pSubframe->wastedBitsPerSample; + pSubframe->pDecodedSamples = NULL; + + switch (pSubframe->subframeType) + { + case DRFLAC_SUBFRAME_CONSTANT: + { + if (!drflac__seek_bits(bs, pSubframe->bitsPerSample)) { + return DRFLAC_FALSE; + } + } break; + + case DRFLAC_SUBFRAME_VERBATIM: + { + unsigned int bitsToSeek = frame->header.blockSize * pSubframe->bitsPerSample; + if (!drflac__seek_bits(bs, bitsToSeek)) { + return DRFLAC_FALSE; + } + } break; + + case DRFLAC_SUBFRAME_FIXED: + { + unsigned int bitsToSeek = pSubframe->lpcOrder * pSubframe->bitsPerSample; + if (!drflac__seek_bits(bs, bitsToSeek)) { + return DRFLAC_FALSE; + } + + if (!drflac__read_and_seek_residual(bs, frame->header.blockSize, pSubframe->lpcOrder)) { + return DRFLAC_FALSE; + } + } break; + + case DRFLAC_SUBFRAME_LPC: + { + unsigned int bitsToSeek = pSubframe->lpcOrder * pSubframe->bitsPerSample; + if (!drflac__seek_bits(bs, bitsToSeek)) { + return DRFLAC_FALSE; + } + + unsigned char lpcPrecision; + if (!drflac__read_uint8(bs, 4, &lpcPrecision)) { + return DRFLAC_FALSE; + } + if (lpcPrecision == 15) { + return DRFLAC_FALSE; // Invalid. + } + lpcPrecision += 1; + + + bitsToSeek = (pSubframe->lpcOrder * lpcPrecision) + 5; // +5 for shift. + if (!drflac__seek_bits(bs, bitsToSeek)) { + return DRFLAC_FALSE; + } + + if (!drflac__read_and_seek_residual(bs, frame->header.blockSize, pSubframe->lpcOrder)) { + return DRFLAC_FALSE; + } + } break; + + default: return DRFLAC_FALSE; + } + + return DRFLAC_TRUE; +} + + +static DRFLAC_INLINE drflac_uint8 drflac__get_channel_count_from_channel_assignment(drflac_int8 channelAssignment) +{ + drflac_assert(channelAssignment <= 10); + + drflac_uint8 lookup[] = {1, 2, 3, 4, 5, 6, 7, 8, 2, 2, 2}; + return lookup[channelAssignment]; +} + +static drflac_result drflac__decode_flac_frame(drflac* pFlac) +{ + // This function should be called while the stream is sitting on the first byte after the frame header. + drflac_zero_memory(pFlac->currentFrame.subframes, sizeof(pFlac->currentFrame.subframes)); + + // The frame block size must never be larger than the maximum block size defined by the FLAC stream. + if (pFlac->currentFrame.header.blockSize > pFlac->maxBlockSize) { + return DRFLAC_ERROR; + } + + // The number of channels in the frame must match the channel count from the STREAMINFO block. + int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + if (channelCount != (int)pFlac->channels) { + return DRFLAC_ERROR; + } + + for (int i = 0; i < channelCount; ++i) { + if (!drflac__decode_subframe(&pFlac->bs, &pFlac->currentFrame, i, pFlac->pDecodedSamples + ((pFlac->currentFrame.header.blockSize+DRFLAC_LEADING_SAMPLES) * i) + DRFLAC_LEADING_SAMPLES)) { + return DRFLAC_ERROR; + } + } + + drflac_uint8 paddingSizeInBits = DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7; + if (paddingSizeInBits > 0) { + drflac_uint8 padding = 0; + if (!drflac__read_uint8(&pFlac->bs, paddingSizeInBits, &padding)) { + return DRFLAC_END_OF_STREAM; + } + } + +#ifndef DR_FLAC_NO_CRC + drflac_uint16 actualCRC16 = drflac__flush_crc16(&pFlac->bs); +#endif + drflac_uint16 desiredCRC16; + if (!drflac__read_uint16(&pFlac->bs, 16, &desiredCRC16)) { + return DRFLAC_END_OF_STREAM; + } + +#ifndef DR_FLAC_NO_CRC + if (actualCRC16 != desiredCRC16) { + return DRFLAC_CRC_MISMATCH; // CRC mismatch. + } +#endif + + pFlac->currentFrame.samplesRemaining = pFlac->currentFrame.header.blockSize * channelCount; + + return DRFLAC_SUCCESS; +} + +static drflac_result drflac__seek_flac_frame(drflac* pFlac) +{ + int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + for (int i = 0; i < channelCount; ++i) { + if (!drflac__seek_subframe(&pFlac->bs, &pFlac->currentFrame, i)) { + return DRFLAC_ERROR; + } + } + + // Padding. + if (!drflac__seek_bits(&pFlac->bs, DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7)) { + return DRFLAC_ERROR; + } + + // CRC. +#ifndef DR_FLAC_NO_CRC + drflac_uint16 actualCRC16 = drflac__flush_crc16(&pFlac->bs); +#endif + drflac_uint16 desiredCRC16; + if (!drflac__read_uint16(&pFlac->bs, 16, &desiredCRC16)) { + return DRFLAC_END_OF_STREAM; + } + +#ifndef DR_FLAC_NO_CRC + if (actualCRC16 != desiredCRC16) { + return DRFLAC_CRC_MISMATCH; // CRC mismatch. + } +#endif + + return DRFLAC_SUCCESS; +} + +static drflac_bool32 drflac__read_and_decode_next_flac_frame(drflac* pFlac) +{ + drflac_assert(pFlac != NULL); + + for (;;) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + return DRFLAC_FALSE; + } + + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result != DRFLAC_SUCCESS) { + if (result == DRFLAC_CRC_MISMATCH) { + continue; // CRC mismatch. Skip to the next frame. + } else { + return DRFLAC_FALSE; + } + } + + return DRFLAC_TRUE; + } +} + + +static void drflac__get_current_frame_sample_range(drflac* pFlac, drflac_uint64* pFirstSampleInFrameOut, drflac_uint64* pLastSampleInFrameOut) +{ + drflac_assert(pFlac != NULL); + + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + + drflac_uint64 firstSampleInFrame = pFlac->currentFrame.header.sampleNumber; + if (firstSampleInFrame == 0) { + firstSampleInFrame = pFlac->currentFrame.header.frameNumber * pFlac->maxBlockSize*channelCount; + } + + drflac_uint64 lastSampleInFrame = firstSampleInFrame + (pFlac->currentFrame.header.blockSize*channelCount); + if (lastSampleInFrame > 0) { + lastSampleInFrame -= 1; // Needs to be zero based. + } + + if (pFirstSampleInFrameOut) *pFirstSampleInFrameOut = firstSampleInFrame; + if (pLastSampleInFrameOut) *pLastSampleInFrameOut = lastSampleInFrame; +} + +static drflac_bool32 drflac__seek_to_first_frame(drflac* pFlac) +{ + drflac_assert(pFlac != NULL); + + drflac_bool32 result = drflac__seek_to_byte(&pFlac->bs, pFlac->firstFramePos); + + drflac_zero_memory(&pFlac->currentFrame, sizeof(pFlac->currentFrame)); + pFlac->currentSample = 0; + + return result; +} + +static DRFLAC_INLINE drflac_result drflac__seek_to_next_flac_frame(drflac* pFlac) +{ + // This function should only ever be called while the decoder is sitting on the first byte past the FRAME_HEADER section. + drflac_assert(pFlac != NULL); + return drflac__seek_flac_frame(pFlac); +} + +drflac_uint64 drflac__seek_forward_by_samples(drflac* pFlac, drflac_uint64 samplesToRead) +{ + drflac_uint64 samplesRead = 0; + while (samplesToRead > 0) { + if (pFlac->currentFrame.samplesRemaining == 0) { + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + break; // Couldn't read the next frame, so just break from the loop and return. + } + } else { + if (pFlac->currentFrame.samplesRemaining > samplesToRead) { + samplesRead += samplesToRead; + pFlac->currentFrame.samplesRemaining -= (drflac_uint32)samplesToRead; // <-- Safe cast. Will always be < currentFrame.samplesRemaining < 65536. + samplesToRead = 0; + } else { + samplesRead += pFlac->currentFrame.samplesRemaining; + samplesToRead -= pFlac->currentFrame.samplesRemaining; + pFlac->currentFrame.samplesRemaining = 0; + } + } + } + + pFlac->currentSample += samplesRead; + return samplesRead; +} + +drflac_uint64 drflac__seek_forward_by_pcm_frames(drflac* pFlac, drflac_uint64 pcmFramesToSeek) +{ + return drflac__seek_forward_by_samples(pFlac, pcmFramesToSeek*pFlac->channels); +} + +static drflac_bool32 drflac__seek_to_sample__brute_force(drflac* pFlac, drflac_uint64 sampleIndex) +{ + drflac_assert(pFlac != NULL); + + drflac_bool32 isMidFrame = DRFLAC_FALSE; + + // If we are seeking forward we start from the current position. Otherwise we need to start all the way from the start of the file. + drflac_uint64 runningSampleCount; + if (sampleIndex >= pFlac->currentSample) { + // Seeking forward. Need to seek from the current position. + runningSampleCount = pFlac->currentSample; + + // The frame header for the first frame may not yet have been read. We need to do that if necessary. + if (pFlac->currentSample == 0 && pFlac->currentFrame.samplesRemaining == 0) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + return DRFLAC_FALSE; + } + } else { + isMidFrame = DRFLAC_TRUE; + } + } else { + // Seeking backwards. Need to seek from the start of the file. + runningSampleCount = 0; + + // Move back to the start. + if (!drflac__seek_to_first_frame(pFlac)) { + return DRFLAC_FALSE; + } + + // Decode the first frame in preparation for sample-exact seeking below. + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + return DRFLAC_FALSE; + } + } + + // We need to as quickly as possible find the frame that contains the target sample. To do this, we iterate over each frame and inspect its + // header. If based on the header we can determine that the frame contains the sample, we do a full decode of that frame. + for (;;) { + drflac_uint64 firstSampleInFrame = 0; + drflac_uint64 lastSampleInFrame = 0; + drflac__get_current_frame_sample_range(pFlac, &firstSampleInFrame, &lastSampleInFrame); + + drflac_uint64 sampleCountInThisFrame = (lastSampleInFrame - firstSampleInFrame) + 1; + if (sampleIndex < (runningSampleCount + sampleCountInThisFrame)) { + // The sample should be in this frame. We need to fully decode it, however if it's an invalid frame (a CRC mismatch), we need to pretend + // it never existed and keep iterating. + drflac_uint64 samplesToDecode = sampleIndex - runningSampleCount; + + if (!isMidFrame) { + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + // The frame is valid. We just need to skip over some samples to ensure it's sample-exact. + return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; // <-- If this fails, something bad has happened (it should never fail). + } else { + if (result == DRFLAC_CRC_MISMATCH) { + goto next_iteration; // CRC mismatch. Pretend this frame never existed. + } else { + return DRFLAC_FALSE; + } + } + } else { + // We started seeking mid-frame which means we need to skip the frame decoding part. + return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; + } + } else { + // It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this + // frame never existed and leave the running sample count untouched. + if (!isMidFrame) { + drflac_result result = drflac__seek_to_next_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + runningSampleCount += sampleCountInThisFrame; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + goto next_iteration; // CRC mismatch. Pretend this frame never existed. + } else { + return DRFLAC_FALSE; + } + } + } else { + // We started seeking mid-frame which means we need to seek by reading to the end of the frame instead of with + // drflac__seek_to_next_flac_frame() which only works if the decoder is sitting on the byte just after the frame header. + runningSampleCount += pFlac->currentFrame.samplesRemaining; + pFlac->currentFrame.samplesRemaining = 0; + isMidFrame = DRFLAC_FALSE; + } + } + + next_iteration: + // Grab the next frame in preparation for the next iteration. + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + return DRFLAC_FALSE; + } + } +} + + +static drflac_bool32 drflac__seek_to_sample__seek_table(drflac* pFlac, drflac_uint64 sampleIndex) +{ + drflac_assert(pFlac != NULL); + + if (pFlac->pSeekpoints == NULL || pFlac->seekpointCount == 0) { + return DRFLAC_FALSE; + } + + + drflac_uint32 iClosestSeekpoint = 0; + for (drflac_uint32 iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { + if (pFlac->pSeekpoints[iSeekpoint].firstSample*pFlac->channels >= sampleIndex) { + break; + } + + iClosestSeekpoint = iSeekpoint; + } + + + drflac_bool32 isMidFrame = DRFLAC_FALSE; + + // At this point we should have found the seekpoint closest to our sample. If we are seeking forward and the closest seekpoint is _before_ the current sample, we + // just seek forward from where we are. Otherwise we start seeking from the seekpoint's first sample. + drflac_uint64 runningSampleCount; + if ((sampleIndex >= pFlac->currentSample) && (pFlac->pSeekpoints[iClosestSeekpoint].firstSample*pFlac->channels <= pFlac->currentSample)) { + // Optimized case. Just seek forward from where we are. + runningSampleCount = pFlac->currentSample; + + // The frame header for the first frame may not yet have been read. We need to do that if necessary. + if (pFlac->currentSample == 0 && pFlac->currentFrame.samplesRemaining == 0) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + return DRFLAC_FALSE; + } + } else { + isMidFrame = DRFLAC_TRUE; + } + } else { + // Slower case. Seek to the start of the seekpoint and then seek forward from there. + runningSampleCount = pFlac->pSeekpoints[iClosestSeekpoint].firstSample*pFlac->channels; + + if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFramePos + pFlac->pSeekpoints[iClosestSeekpoint].frameOffset)) { + return DRFLAC_FALSE; + } + + // Grab the frame the seekpoint is sitting on in preparation for the sample-exact seeking below. + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + return DRFLAC_FALSE; + } + } + + for (;;) { + drflac_uint64 firstSampleInFrame = 0; + drflac_uint64 lastSampleInFrame = 0; + drflac__get_current_frame_sample_range(pFlac, &firstSampleInFrame, &lastSampleInFrame); + + drflac_uint64 sampleCountInThisFrame = (lastSampleInFrame - firstSampleInFrame) + 1; + if (sampleIndex < (runningSampleCount + sampleCountInThisFrame)) { + // The sample should be in this frame. We need to fully decode it, but if it's an invalid frame (a CRC mismatch) we need to pretend + // it never existed and keep iterating. + drflac_uint64 samplesToDecode = sampleIndex - runningSampleCount; + + if (!isMidFrame) { + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + // The frame is valid. We just need to skip over some samples to ensure it's sample-exact. + return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; // <-- If this fails, something bad has happened (it should never fail). + } else { + if (result == DRFLAC_CRC_MISMATCH) { + goto next_iteration; // CRC mismatch. Pretend this frame never existed. + } else { + return DRFLAC_FALSE; + } + } + } else { + // We started seeking mid-frame which means we need to skip the frame decoding part. + return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; + } + } else { + // It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this + // frame never existed and leave the running sample count untouched. + if (!isMidFrame) { + drflac_result result = drflac__seek_to_next_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + runningSampleCount += sampleCountInThisFrame; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + goto next_iteration; // CRC mismatch. Pretend this frame never existed. + } else { + return DRFLAC_FALSE; + } + } + } else { + // We started seeking mid-frame which means we need to seek by reading to the end of the frame instead of with + // drflac__seek_to_next_flac_frame() which only works if the decoder is sitting on the byte just after the frame header. + runningSampleCount += pFlac->currentFrame.samplesRemaining; + pFlac->currentFrame.samplesRemaining = 0; + isMidFrame = DRFLAC_FALSE; + } + } + + next_iteration: + // Grab the next frame in preparation for the next iteration. + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + return DRFLAC_FALSE; + } + } +} + + +#ifndef DR_FLAC_NO_OGG +typedef struct +{ + drflac_uint8 capturePattern[4]; // Should be "OggS" + drflac_uint8 structureVersion; // Always 0. + drflac_uint8 headerType; + drflac_uint64 granulePosition; + drflac_uint32 serialNumber; + drflac_uint32 sequenceNumber; + drflac_uint32 checksum; + drflac_uint8 segmentCount; + drflac_uint8 segmentTable[255]; +} drflac_ogg_page_header; +#endif + +typedef struct +{ + drflac_read_proc onRead; + drflac_seek_proc onSeek; + drflac_meta_proc onMeta; + drflac_container container; + void* pUserData; + void* pUserDataMD; + drflac_uint32 sampleRate; + drflac_uint8 channels; + drflac_uint8 bitsPerSample; + drflac_uint64 totalSampleCount; + drflac_uint16 maxBlockSize; + drflac_uint64 runningFilePos; + drflac_bool32 hasStreamInfoBlock; + drflac_bool32 hasMetadataBlocks; + drflac_bs bs; // <-- A bit streamer is required for loading data during initialization. + drflac_frame_header firstFrameHeader; // <-- The header of the first frame that was read during relaxed initalization. Only set if there is no STREAMINFO block. + +#ifndef DR_FLAC_NO_OGG + drflac_uint32 oggSerial; + drflac_uint64 oggFirstBytePos; + drflac_ogg_page_header oggBosHeader; +#endif +} drflac_init_info; + +static DRFLAC_INLINE void drflac__decode_block_header(drflac_uint32 blockHeader, drflac_uint8* isLastBlock, drflac_uint8* blockType, drflac_uint32* blockSize) +{ + blockHeader = drflac__be2host_32(blockHeader); + *isLastBlock = (blockHeader & (0x01 << 31)) >> 31; + *blockType = (blockHeader & (0x7F << 24)) >> 24; + *blockSize = (blockHeader & 0xFFFFFF); +} + +static DRFLAC_INLINE drflac_bool32 drflac__read_and_decode_block_header(drflac_read_proc onRead, void* pUserData, drflac_uint8* isLastBlock, drflac_uint8* blockType, drflac_uint32* blockSize) +{ + drflac_uint32 blockHeader; + if (onRead(pUserData, &blockHeader, 4) != 4) { + return DRFLAC_FALSE; + } + + drflac__decode_block_header(blockHeader, isLastBlock, blockType, blockSize); + return DRFLAC_TRUE; +} + +drflac_bool32 drflac__read_streaminfo(drflac_read_proc onRead, void* pUserData, drflac_streaminfo* pStreamInfo) +{ + // min/max block size. + drflac_uint32 blockSizes; + if (onRead(pUserData, &blockSizes, 4) != 4) { + return DRFLAC_FALSE; + } + + // min/max frame size. + drflac_uint64 frameSizes = 0; + if (onRead(pUserData, &frameSizes, 6) != 6) { + return DRFLAC_FALSE; + } + + // Sample rate, channels, bits per sample and total sample count. + drflac_uint64 importantProps; + if (onRead(pUserData, &importantProps, 8) != 8) { + return DRFLAC_FALSE; + } + + // MD5 + drflac_uint8 md5[16]; + if (onRead(pUserData, md5, sizeof(md5)) != sizeof(md5)) { + return DRFLAC_FALSE; + } + + blockSizes = drflac__be2host_32(blockSizes); + frameSizes = drflac__be2host_64(frameSizes); + importantProps = drflac__be2host_64(importantProps); + + pStreamInfo->minBlockSize = (blockSizes & 0xFFFF0000) >> 16; + pStreamInfo->maxBlockSize = blockSizes & 0x0000FFFF; + pStreamInfo->minFrameSize = (drflac_uint32)((frameSizes & (drflac_uint64)0xFFFFFF0000000000) >> 40); + pStreamInfo->maxFrameSize = (drflac_uint32)((frameSizes & (drflac_uint64)0x000000FFFFFF0000) >> 16); + pStreamInfo->sampleRate = (drflac_uint32)((importantProps & (drflac_uint64)0xFFFFF00000000000) >> 44); + pStreamInfo->channels = (drflac_uint8 )((importantProps & (drflac_uint64)0x00000E0000000000) >> 41) + 1; + pStreamInfo->bitsPerSample = (drflac_uint8 )((importantProps & (drflac_uint64)0x000001F000000000) >> 36) + 1; + pStreamInfo->totalSampleCount = (importantProps & (drflac_uint64)0x0000000FFFFFFFFF) * pStreamInfo->channels; + drflac_copy_memory(pStreamInfo->md5, md5, sizeof(md5)); + + return DRFLAC_TRUE; +} + +drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_uint64* pFirstFramePos, drflac_uint64* pSeektablePos, drflac_uint32* pSeektableSize) +{ + // We want to keep track of the byte position in the stream of the seektable. At the time of calling this function we know that + // we'll be sitting on byte 42. + drflac_uint64 runningFilePos = 42; + drflac_uint64 seektablePos = 0; + drflac_uint32 seektableSize = 0; + + for (;;) { + drflac_uint8 isLastBlock = 0; + drflac_uint8 blockType; + drflac_uint32 blockSize; + if (!drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize)) { + return DRFLAC_FALSE; + } + runningFilePos += 4; + + + drflac_metadata metadata; + metadata.type = blockType; + metadata.pRawData = NULL; + metadata.rawDataSize = 0; + + switch (blockType) + { + case DRFLAC_METADATA_BLOCK_TYPE_APPLICATION: + { + if (blockSize < 4) { + return DRFLAC_FALSE; + } + + if (onMeta) { + void* pRawData = DRFLAC_MALLOC(blockSize); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + metadata.data.application.id = drflac__be2host_32(*(drflac_uint32*)pRawData); + metadata.data.application.pData = (const void*)((drflac_uint8*)pRawData + sizeof(drflac_uint32)); + metadata.data.application.dataSize = blockSize - sizeof(drflac_uint32); + onMeta(pUserDataMD, &metadata); + + DRFLAC_FREE(pRawData); + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_SEEKTABLE: + { + seektablePos = runningFilePos; + seektableSize = blockSize; + + if (onMeta) { + void* pRawData = DRFLAC_MALLOC(blockSize); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + metadata.data.seektable.seekpointCount = blockSize/sizeof(drflac_seekpoint); + metadata.data.seektable.pSeekpoints = (const drflac_seekpoint*)pRawData; + + // Endian swap. + for (drflac_uint32 iSeekpoint = 0; iSeekpoint < metadata.data.seektable.seekpointCount; ++iSeekpoint) { + drflac_seekpoint* pSeekpoint = (drflac_seekpoint*)pRawData + iSeekpoint; + pSeekpoint->firstSample = drflac__be2host_64(pSeekpoint->firstSample); + pSeekpoint->frameOffset = drflac__be2host_64(pSeekpoint->frameOffset); + pSeekpoint->sampleCount = drflac__be2host_16(pSeekpoint->sampleCount); + } + + onMeta(pUserDataMD, &metadata); + + DRFLAC_FREE(pRawData); + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT: + { + if (blockSize < 8) { + return DRFLAC_FALSE; + } + + if (onMeta) { + void* pRawData = DRFLAC_MALLOC(blockSize); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + + const char* pRunningData = (const char*)pRawData; + const char* const pRunningDataEnd = (const char*)pRawData + blockSize; + + metadata.data.vorbis_comment.vendorLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + + // Need space for the rest of the block + if ((pRunningDataEnd - pRunningData) - 4 < (drflac_int64)metadata.data.vorbis_comment.vendorLength) { // <-- Note the order of operations to avoid overflow to a valid value + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength; + metadata.data.vorbis_comment.commentCount = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + + // Need space for 'commentCount' comments after the block, which at minimum is a drflac_uint32 per comment + if ((pRunningDataEnd - pRunningData) / sizeof(drflac_uint32) < metadata.data.vorbis_comment.commentCount) { // <-- Note the order of operations to avoid overflow to a valid value + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + metadata.data.vorbis_comment.pComments = pRunningData; + + // Check that the comments section is valid before passing it to the callback + for (drflac_uint32 i = 0; i < metadata.data.vorbis_comment.commentCount; ++i) { + if (pRunningDataEnd - pRunningData < 4) { + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + const drflac_uint32 commentLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + if (pRunningDataEnd - pRunningData < (drflac_int64)commentLength) { // <-- Note the order of operations to avoid overflow to a valid value + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + pRunningData += commentLength; + } + + onMeta(pUserDataMD, &metadata); + + DRFLAC_FREE(pRawData); + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_CUESHEET: + { + if (blockSize < 396) { + return DRFLAC_FALSE; + } + + if (onMeta) { + void* pRawData = DRFLAC_MALLOC(blockSize); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + + char* pRunningData = (char*)pRawData; + const char* const pRunningDataEnd = (const char*)pRawData + blockSize; + + drflac_copy_memory(metadata.data.cuesheet.catalog, pRunningData, 128); pRunningData += 128; + metadata.data.cuesheet.leadInSampleCount = drflac__be2host_64(*(const drflac_uint64*)pRunningData); pRunningData += 8; + metadata.data.cuesheet.isCD = (pRunningData[0] & 0x80) != 0; pRunningData += 259; + metadata.data.cuesheet.trackCount = pRunningData[0]; pRunningData += 1; + metadata.data.cuesheet.pTrackData = pRunningData; + + // Check that the cuesheet tracks are valid before passing it to the callback + for (drflac_uint8 i = 0; i < metadata.data.cuesheet.trackCount; ++i) { + if (pRunningDataEnd - pRunningData < 36) { + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + + // Skip to the index point count + pRunningData += 35; + const drflac_uint8 indexCount = pRunningData[0]; pRunningData += 1; + const drflac_uint32 indexPointSize = indexCount * sizeof(drflac_cuesheet_track_index); + if (pRunningDataEnd - pRunningData < (drflac_int64)indexPointSize) { + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + + // Endian swap. + for (drflac_uint8 index = 0; index < indexCount; ++index) { + drflac_cuesheet_track_index* pTrack = (drflac_cuesheet_track_index*)pRunningData; + pRunningData += sizeof(drflac_cuesheet_track_index); + pTrack->offset = drflac__be2host_64(pTrack->offset); + } + } + + onMeta(pUserDataMD, &metadata); + + DRFLAC_FREE(pRawData); + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_PICTURE: + { + if (blockSize < 32) { + return DRFLAC_FALSE; + } + + if (onMeta) { + void* pRawData = DRFLAC_MALLOC(blockSize); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + + const char* pRunningData = (const char*)pRawData; + const char* const pRunningDataEnd = (const char*)pRawData + blockSize; + + metadata.data.picture.type = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.mimeLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + + // Need space for the rest of the block + if ((pRunningDataEnd - pRunningData) - 24 < (drflac_int64)metadata.data.picture.mimeLength) { // <-- Note the order of operations to avoid overflow to a valid value + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength; + metadata.data.picture.descriptionLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + + // Need space for the rest of the block + if ((pRunningDataEnd - pRunningData) - 20 < (drflac_int64)metadata.data.picture.descriptionLength) { // <-- Note the order of operations to avoid overflow to a valid value + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength; + metadata.data.picture.width = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.height = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.colorDepth = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.indexColorCount = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.pictureDataSize = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.pPictureData = (const drflac_uint8*)pRunningData; + + // Need space for the picture after the block + if (pRunningDataEnd - pRunningData < (drflac_int64)metadata.data.picture.pictureDataSize) { // <-- Note the order of operations to avoid overflow to a valid value + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + + onMeta(pUserDataMD, &metadata); + + DRFLAC_FREE(pRawData); + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_PADDING: + { + if (onMeta) { + metadata.data.padding.unused = 0; + + // Padding doesn't have anything meaningful in it, so just skip over it, but make sure the caller is aware of it by firing the callback. + if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { + isLastBlock = DRFLAC_TRUE; // An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. + } else { + onMeta(pUserDataMD, &metadata); + } + } + } break; + + case DRFLAC_METADATA_BLOCK_TYPE_INVALID: + { + // Invalid chunk. Just skip over this one. + if (onMeta) { + if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { + isLastBlock = DRFLAC_TRUE; // An error occurred while seeking. Attempt to recover by treating this as the last block which will in turn terminate the loop. + } + } + } break; + + default: + { + // It's an unknown chunk, but not necessarily invalid. There's a chance more metadata blocks might be defined later on, so we + // can at the very least report the chunk to the application and let it look at the raw data. + if (onMeta) { + void* pRawData = DRFLAC_MALLOC(blockSize); + if (pRawData == NULL) { + return DRFLAC_FALSE; + } + + if (onRead(pUserData, pRawData, blockSize) != blockSize) { + DRFLAC_FREE(pRawData); + return DRFLAC_FALSE; + } + + metadata.pRawData = pRawData; + metadata.rawDataSize = blockSize; + onMeta(pUserDataMD, &metadata); + + DRFLAC_FREE(pRawData); + } + } break; + } + + // If we're not handling metadata, just skip over the block. If we are, it will have been handled earlier in the switch statement above. + if (onMeta == NULL && blockSize > 0) { + if (!onSeek(pUserData, blockSize, drflac_seek_origin_current)) { + isLastBlock = DRFLAC_TRUE; + } + } + + runningFilePos += blockSize; + if (isLastBlock) { + break; + } + } + + *pSeektablePos = seektablePos; + *pSeektableSize = seektableSize; + *pFirstFramePos = runningFilePos; + + return DRFLAC_TRUE; +} + +drflac_bool32 drflac__init_private__native(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_bool32 relaxed) +{ + (void)onSeek; + + // Pre: The bit stream should be sitting just past the 4-byte id header. + + pInit->container = drflac_container_native; + + // The first metadata block should be the STREAMINFO block. + drflac_uint8 isLastBlock; + drflac_uint8 blockType; + drflac_uint32 blockSize; + if (!drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize)) { + return DRFLAC_FALSE; + } + + if (blockType != DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO || blockSize != 34) { + if (!relaxed) { + // We're opening in strict mode and the first block is not the STREAMINFO block. Error. + return DRFLAC_FALSE; + } else { + // Relaxed mode. To open from here we need to just find the first frame and set the sample rate, etc. to whatever is defined + // for that frame. + pInit->hasStreamInfoBlock = DRFLAC_FALSE; + pInit->hasMetadataBlocks = DRFLAC_FALSE; + + if (!drflac__read_next_flac_frame_header(&pInit->bs, 0, &pInit->firstFrameHeader)) { + return DRFLAC_FALSE; // Couldn't find a frame. + } + + if (pInit->firstFrameHeader.bitsPerSample == 0) { + return DRFLAC_FALSE; // Failed to initialize because the first frame depends on the STREAMINFO block, which does not exist. + } + + pInit->sampleRate = pInit->firstFrameHeader.sampleRate; + pInit->channels = drflac__get_channel_count_from_channel_assignment(pInit->firstFrameHeader.channelAssignment); + pInit->bitsPerSample = pInit->firstFrameHeader.bitsPerSample; + pInit->maxBlockSize = 65535; // <-- See notes here: https://xiph.org/flac/format.html#metadata_block_streaminfo + return DRFLAC_TRUE; + } + } else { + drflac_streaminfo streaminfo; + if (!drflac__read_streaminfo(onRead, pUserData, &streaminfo)) { + return DRFLAC_FALSE; + } + + pInit->hasStreamInfoBlock = DRFLAC_TRUE; + pInit->sampleRate = streaminfo.sampleRate; + pInit->channels = streaminfo.channels; + pInit->bitsPerSample = streaminfo.bitsPerSample; + pInit->totalSampleCount = streaminfo.totalSampleCount; + pInit->maxBlockSize = streaminfo.maxBlockSize; // Don't care about the min block size - only the max (used for determining the size of the memory allocation). + pInit->hasMetadataBlocks = !isLastBlock; + + if (onMeta) { + drflac_metadata metadata; + metadata.type = DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO; + metadata.pRawData = NULL; + metadata.rawDataSize = 0; + metadata.data.streaminfo = streaminfo; + onMeta(pUserDataMD, &metadata); + } + + return DRFLAC_TRUE; + } +} + +#ifndef DR_FLAC_NO_OGG +#define DRFLAC_OGG_MAX_PAGE_SIZE 65307 +#define DRFLAC_OGG_CAPTURE_PATTERN_CRC32 1605413199 // CRC-32 of "OggS". + +typedef enum +{ + drflac_ogg_recover_on_crc_mismatch, + drflac_ogg_fail_on_crc_mismatch +} drflac_ogg_crc_mismatch_recovery; + + +static drflac_uint32 drflac__crc32_table[] = { + 0x00000000L, 0x04C11DB7L, 0x09823B6EL, 0x0D4326D9L, + 0x130476DCL, 0x17C56B6BL, 0x1A864DB2L, 0x1E475005L, + 0x2608EDB8L, 0x22C9F00FL, 0x2F8AD6D6L, 0x2B4BCB61L, + 0x350C9B64L, 0x31CD86D3L, 0x3C8EA00AL, 0x384FBDBDL, + 0x4C11DB70L, 0x48D0C6C7L, 0x4593E01EL, 0x4152FDA9L, + 0x5F15ADACL, 0x5BD4B01BL, 0x569796C2L, 0x52568B75L, + 0x6A1936C8L, 0x6ED82B7FL, 0x639B0DA6L, 0x675A1011L, + 0x791D4014L, 0x7DDC5DA3L, 0x709F7B7AL, 0x745E66CDL, + 0x9823B6E0L, 0x9CE2AB57L, 0x91A18D8EL, 0x95609039L, + 0x8B27C03CL, 0x8FE6DD8BL, 0x82A5FB52L, 0x8664E6E5L, + 0xBE2B5B58L, 0xBAEA46EFL, 0xB7A96036L, 0xB3687D81L, + 0xAD2F2D84L, 0xA9EE3033L, 0xA4AD16EAL, 0xA06C0B5DL, + 0xD4326D90L, 0xD0F37027L, 0xDDB056FEL, 0xD9714B49L, + 0xC7361B4CL, 0xC3F706FBL, 0xCEB42022L, 0xCA753D95L, + 0xF23A8028L, 0xF6FB9D9FL, 0xFBB8BB46L, 0xFF79A6F1L, + 0xE13EF6F4L, 0xE5FFEB43L, 0xE8BCCD9AL, 0xEC7DD02DL, + 0x34867077L, 0x30476DC0L, 0x3D044B19L, 0x39C556AEL, + 0x278206ABL, 0x23431B1CL, 0x2E003DC5L, 0x2AC12072L, + 0x128E9DCFL, 0x164F8078L, 0x1B0CA6A1L, 0x1FCDBB16L, + 0x018AEB13L, 0x054BF6A4L, 0x0808D07DL, 0x0CC9CDCAL, + 0x7897AB07L, 0x7C56B6B0L, 0x71159069L, 0x75D48DDEL, + 0x6B93DDDBL, 0x6F52C06CL, 0x6211E6B5L, 0x66D0FB02L, + 0x5E9F46BFL, 0x5A5E5B08L, 0x571D7DD1L, 0x53DC6066L, + 0x4D9B3063L, 0x495A2DD4L, 0x44190B0DL, 0x40D816BAL, + 0xACA5C697L, 0xA864DB20L, 0xA527FDF9L, 0xA1E6E04EL, + 0xBFA1B04BL, 0xBB60ADFCL, 0xB6238B25L, 0xB2E29692L, + 0x8AAD2B2FL, 0x8E6C3698L, 0x832F1041L, 0x87EE0DF6L, + 0x99A95DF3L, 0x9D684044L, 0x902B669DL, 0x94EA7B2AL, + 0xE0B41DE7L, 0xE4750050L, 0xE9362689L, 0xEDF73B3EL, + 0xF3B06B3BL, 0xF771768CL, 0xFA325055L, 0xFEF34DE2L, + 0xC6BCF05FL, 0xC27DEDE8L, 0xCF3ECB31L, 0xCBFFD686L, + 0xD5B88683L, 0xD1799B34L, 0xDC3ABDEDL, 0xD8FBA05AL, + 0x690CE0EEL, 0x6DCDFD59L, 0x608EDB80L, 0x644FC637L, + 0x7A089632L, 0x7EC98B85L, 0x738AAD5CL, 0x774BB0EBL, + 0x4F040D56L, 0x4BC510E1L, 0x46863638L, 0x42472B8FL, + 0x5C007B8AL, 0x58C1663DL, 0x558240E4L, 0x51435D53L, + 0x251D3B9EL, 0x21DC2629L, 0x2C9F00F0L, 0x285E1D47L, + 0x36194D42L, 0x32D850F5L, 0x3F9B762CL, 0x3B5A6B9BL, + 0x0315D626L, 0x07D4CB91L, 0x0A97ED48L, 0x0E56F0FFL, + 0x1011A0FAL, 0x14D0BD4DL, 0x19939B94L, 0x1D528623L, + 0xF12F560EL, 0xF5EE4BB9L, 0xF8AD6D60L, 0xFC6C70D7L, + 0xE22B20D2L, 0xE6EA3D65L, 0xEBA91BBCL, 0xEF68060BL, + 0xD727BBB6L, 0xD3E6A601L, 0xDEA580D8L, 0xDA649D6FL, + 0xC423CD6AL, 0xC0E2D0DDL, 0xCDA1F604L, 0xC960EBB3L, + 0xBD3E8D7EL, 0xB9FF90C9L, 0xB4BCB610L, 0xB07DABA7L, + 0xAE3AFBA2L, 0xAAFBE615L, 0xA7B8C0CCL, 0xA379DD7BL, + 0x9B3660C6L, 0x9FF77D71L, 0x92B45BA8L, 0x9675461FL, + 0x8832161AL, 0x8CF30BADL, 0x81B02D74L, 0x857130C3L, + 0x5D8A9099L, 0x594B8D2EL, 0x5408ABF7L, 0x50C9B640L, + 0x4E8EE645L, 0x4A4FFBF2L, 0x470CDD2BL, 0x43CDC09CL, + 0x7B827D21L, 0x7F436096L, 0x7200464FL, 0x76C15BF8L, + 0x68860BFDL, 0x6C47164AL, 0x61043093L, 0x65C52D24L, + 0x119B4BE9L, 0x155A565EL, 0x18197087L, 0x1CD86D30L, + 0x029F3D35L, 0x065E2082L, 0x0B1D065BL, 0x0FDC1BECL, + 0x3793A651L, 0x3352BBE6L, 0x3E119D3FL, 0x3AD08088L, + 0x2497D08DL, 0x2056CD3AL, 0x2D15EBE3L, 0x29D4F654L, + 0xC5A92679L, 0xC1683BCEL, 0xCC2B1D17L, 0xC8EA00A0L, + 0xD6AD50A5L, 0xD26C4D12L, 0xDF2F6BCBL, 0xDBEE767CL, + 0xE3A1CBC1L, 0xE760D676L, 0xEA23F0AFL, 0xEEE2ED18L, + 0xF0A5BD1DL, 0xF464A0AAL, 0xF9278673L, 0xFDE69BC4L, + 0x89B8FD09L, 0x8D79E0BEL, 0x803AC667L, 0x84FBDBD0L, + 0x9ABC8BD5L, 0x9E7D9662L, 0x933EB0BBL, 0x97FFAD0CL, + 0xAFB010B1L, 0xAB710D06L, 0xA6322BDFL, 0xA2F33668L, + 0xBCB4666DL, 0xB8757BDAL, 0xB5365D03L, 0xB1F740B4L +}; + +static DRFLAC_INLINE drflac_uint32 drflac_crc32_byte(drflac_uint32 crc32, drflac_uint8 data) +{ +#ifndef DR_FLAC_NO_CRC + return (crc32 << 8) ^ drflac__crc32_table[(drflac_uint8)((crc32 >> 24) & 0xFF) ^ data]; +#else + (void)data; + return crc32; +#endif +} + +#if 0 +static DRFLAC_INLINE drflac_uint32 drflac_crc32_uint32(drflac_uint32 crc32, drflac_uint32 data) +{ + crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 24) & 0xFF)); + crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 16) & 0xFF)); + crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 8) & 0xFF)); + crc32 = drflac_crc32_byte(crc32, (drflac_uint8)((data >> 0) & 0xFF)); + return crc32; +} + +static DRFLAC_INLINE drflac_uint32 drflac_crc32_uint64(drflac_uint32 crc32, drflac_uint64 data) +{ + crc32 = drflac_crc32_uint32(crc32, (drflac_uint32)((data >> 32) & 0xFFFFFFFF)); + crc32 = drflac_crc32_uint32(crc32, (drflac_uint32)((data >> 0) & 0xFFFFFFFF)); + return crc32; +} +#endif + +static DRFLAC_INLINE drflac_uint32 drflac_crc32_buffer(drflac_uint32 crc32, drflac_uint8* pData, drflac_uint32 dataSize) +{ + // This can be optimized. + for (drflac_uint32 i = 0; i < dataSize; ++i) { + crc32 = drflac_crc32_byte(crc32, pData[i]); + } + return crc32; +} + + +static DRFLAC_INLINE drflac_bool32 drflac_ogg__is_capture_pattern(drflac_uint8 pattern[4]) +{ + return pattern[0] == 'O' && pattern[1] == 'g' && pattern[2] == 'g' && pattern[3] == 'S'; +} + +static DRFLAC_INLINE drflac_uint32 drflac_ogg__get_page_header_size(drflac_ogg_page_header* pHeader) +{ + return 27 + pHeader->segmentCount; +} + +static DRFLAC_INLINE drflac_uint32 drflac_ogg__get_page_body_size(drflac_ogg_page_header* pHeader) +{ + drflac_uint32 pageBodySize = 0; + for (int i = 0; i < pHeader->segmentCount; ++i) { + pageBodySize += pHeader->segmentTable[i]; + } + + return pageBodySize; +} + +drflac_result drflac_ogg__read_page_header_after_capture_pattern(drflac_read_proc onRead, void* pUserData, drflac_ogg_page_header* pHeader, drflac_uint32* pBytesRead, drflac_uint32* pCRC32) +{ + drflac_assert(*pCRC32 == DRFLAC_OGG_CAPTURE_PATTERN_CRC32); + + drflac_uint8 data[23]; + if (onRead(pUserData, data, 23) != 23) { + return DRFLAC_END_OF_STREAM; + } + *pBytesRead += 23; + + pHeader->structureVersion = data[0]; + pHeader->headerType = data[1]; + drflac_copy_memory(&pHeader->granulePosition, &data[ 2], 8); + drflac_copy_memory(&pHeader->serialNumber, &data[10], 4); + drflac_copy_memory(&pHeader->sequenceNumber, &data[14], 4); + drflac_copy_memory(&pHeader->checksum, &data[18], 4); + pHeader->segmentCount = data[22]; + + // Calculate the CRC. Note that for the calculation the checksum part of the page needs to be set to 0. + data[18] = 0; + data[19] = 0; + data[20] = 0; + data[21] = 0; + + drflac_uint32 i; + for (i = 0; i < 23; ++i) { + *pCRC32 = drflac_crc32_byte(*pCRC32, data[i]); + } + + + if (onRead(pUserData, pHeader->segmentTable, pHeader->segmentCount) != pHeader->segmentCount) { + return DRFLAC_END_OF_STREAM; + } + *pBytesRead += pHeader->segmentCount; + + for (i = 0; i < pHeader->segmentCount; ++i) { + *pCRC32 = drflac_crc32_byte(*pCRC32, pHeader->segmentTable[i]); + } + + return DRFLAC_SUCCESS; +} + +drflac_result drflac_ogg__read_page_header(drflac_read_proc onRead, void* pUserData, drflac_ogg_page_header* pHeader, drflac_uint32* pBytesRead, drflac_uint32* pCRC32) +{ + *pBytesRead = 0; + + drflac_uint8 id[4]; + if (onRead(pUserData, id, 4) != 4) { + return DRFLAC_END_OF_STREAM; + } + *pBytesRead += 4; + + // We need to read byte-by-byte until we find the OggS capture pattern. + for (;;) { + if (drflac_ogg__is_capture_pattern(id)) { + *pCRC32 = DRFLAC_OGG_CAPTURE_PATTERN_CRC32; + + drflac_result result = drflac_ogg__read_page_header_after_capture_pattern(onRead, pUserData, pHeader, pBytesRead, pCRC32); + if (result == DRFLAC_SUCCESS) { + return DRFLAC_SUCCESS; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + continue; + } else { + return result; + } + } + } else { + // The first 4 bytes did not equal the capture pattern. Read the next byte and try again. + id[0] = id[1]; + id[1] = id[2]; + id[2] = id[3]; + if (onRead(pUserData, &id[3], 1) != 1) { + return DRFLAC_END_OF_STREAM; + } + *pBytesRead += 1; + } + } +} + + +// The main part of the Ogg encapsulation is the conversion from the physical Ogg bitstream to the native FLAC bitstream. It works +// in three general stages: Ogg Physical Bitstream -> Ogg/FLAC Logical Bitstream -> FLAC Native Bitstream. dr_flac is designed +// in such a way that the core sections assume everything is delivered in native format. Therefore, for each encapsulation type +// dr_flac is supporting there needs to be a layer sitting on top of the onRead and onSeek callbacks that ensures the bits read from +// the physical Ogg bitstream are converted and delivered in native FLAC format. +typedef struct +{ + drflac_read_proc onRead; // The original onRead callback from drflac_open() and family. + drflac_seek_proc onSeek; // The original onSeek callback from drflac_open() and family. + void* pUserData; // The user data passed on onRead and onSeek. This is the user data that was passed on drflac_open() and family. + drflac_uint64 currentBytePos; // The position of the byte we are sitting on in the physical byte stream. Used for efficient seeking. + drflac_uint64 firstBytePos; // The position of the first byte in the physical bitstream. Points to the start of the "OggS" identifier of the FLAC bos page. + drflac_uint32 serialNumber; // The serial number of the FLAC audio pages. This is determined by the initial header page that was read during initialization. + drflac_ogg_page_header bosPageHeader; // Used for seeking. + drflac_ogg_page_header currentPageHeader; + drflac_uint32 bytesRemainingInPage; + drflac_uint32 pageDataSize; + drflac_uint8 pageData[DRFLAC_OGG_MAX_PAGE_SIZE]; +} drflac_oggbs; // oggbs = Ogg Bitstream + +static size_t drflac_oggbs__read_physical(drflac_oggbs* oggbs, void* bufferOut, size_t bytesToRead) +{ + size_t bytesActuallyRead = oggbs->onRead(oggbs->pUserData, bufferOut, bytesToRead); + oggbs->currentBytePos += bytesActuallyRead; + + return bytesActuallyRead; +} + +static drflac_bool32 drflac_oggbs__seek_physical(drflac_oggbs* oggbs, drflac_uint64 offset, drflac_seek_origin origin) +{ + if (origin == drflac_seek_origin_start) { + if (offset <= 0x7FFFFFFF) { + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, drflac_seek_origin_start)) { + return DRFLAC_FALSE; + } + oggbs->currentBytePos = offset; + + return DRFLAC_TRUE; + } else { + if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, drflac_seek_origin_start)) { + return DRFLAC_FALSE; + } + oggbs->currentBytePos = offset; + + return drflac_oggbs__seek_physical(oggbs, offset - 0x7FFFFFFF, drflac_seek_origin_current); + } + } else { + while (offset > 0x7FFFFFFF) { + if (!oggbs->onSeek(oggbs->pUserData, 0x7FFFFFFF, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + oggbs->currentBytePos += 0x7FFFFFFF; + offset -= 0x7FFFFFFF; + } + + if (!oggbs->onSeek(oggbs->pUserData, (int)offset, drflac_seek_origin_current)) { // <-- Safe cast thanks to the loop above. + return DRFLAC_FALSE; + } + oggbs->currentBytePos += offset; + + return DRFLAC_TRUE; + } +} + +static drflac_bool32 drflac_oggbs__goto_next_page(drflac_oggbs* oggbs, drflac_ogg_crc_mismatch_recovery recoveryMethod) +{ + drflac_ogg_page_header header; + for (;;) { + drflac_uint32 crc32 = 0; + drflac_uint32 bytesRead; + if (drflac_ogg__read_page_header(oggbs->onRead, oggbs->pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { + return DRFLAC_FALSE; + } + oggbs->currentBytePos += bytesRead; + + drflac_uint32 pageBodySize = drflac_ogg__get_page_body_size(&header); + if (pageBodySize > DRFLAC_OGG_MAX_PAGE_SIZE) { + continue; // Invalid page size. Assume it's corrupted and just move to the next page. + } + + if (header.serialNumber != oggbs->serialNumber) { + // It's not a FLAC page. Skip it. + if (pageBodySize > 0 && !drflac_oggbs__seek_physical(oggbs, pageBodySize, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + continue; + } + + + // We need to read the entire page and then do a CRC check on it. If there's a CRC mismatch we need to skip this page. + if (drflac_oggbs__read_physical(oggbs, oggbs->pageData, pageBodySize) != pageBodySize) { + return DRFLAC_FALSE; + } + oggbs->pageDataSize = pageBodySize; + +#ifndef DR_FLAC_NO_CRC + drflac_uint32 actualCRC32 = drflac_crc32_buffer(crc32, oggbs->pageData, oggbs->pageDataSize); + if (actualCRC32 != header.checksum) { + if (recoveryMethod == drflac_ogg_recover_on_crc_mismatch) { + continue; // CRC mismatch. Skip this page. + } else { + // Even though we are failing on a CRC mismatch, we still want our stream to be in a good state. Therefore we + // go to the next valid page to ensure we're in a good state, but return false to let the caller know that the + // seek did not fully complete. + drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch); + return DRFLAC_FALSE; + } + } +#else + (void)recoveryMethod; // <-- Silence a warning. +#endif + + oggbs->currentPageHeader = header; + oggbs->bytesRemainingInPage = pageBodySize; + return DRFLAC_TRUE; + } +} + +// Function below is unused at the moment, but I might be re-adding it later. +#if 0 +static drflac_uint8 drflac_oggbs__get_current_segment_index(drflac_oggbs* oggbs, drflac_uint8* pBytesRemainingInSeg) +{ + drflac_uint32 bytesConsumedInPage = drflac_ogg__get_page_body_size(&oggbs->currentPageHeader) - oggbs->bytesRemainingInPage; + drflac_uint8 iSeg = 0; + drflac_uint32 iByte = 0; + while (iByte < bytesConsumedInPage) { + drflac_uint8 segmentSize = oggbs->currentPageHeader.segmentTable[iSeg]; + if (iByte + segmentSize > bytesConsumedInPage) { + break; + } else { + iSeg += 1; + iByte += segmentSize; + } + } + + *pBytesRemainingInSeg = oggbs->currentPageHeader.segmentTable[iSeg] - (drflac_uint8)(bytesConsumedInPage - iByte); + return iSeg; +} + +static drflac_bool32 drflac_oggbs__seek_to_next_packet(drflac_oggbs* oggbs) +{ + // The current packet ends when we get to the segment with a lacing value of < 255 which is not at the end of a page. + for (;;) { + drflac_bool32 atEndOfPage = DRFLAC_FALSE; + + drflac_uint8 bytesRemainingInSeg; + drflac_uint8 iFirstSeg = drflac_oggbs__get_current_segment_index(oggbs, &bytesRemainingInSeg); + + drflac_uint32 bytesToEndOfPacketOrPage = bytesRemainingInSeg; + for (drflac_uint8 iSeg = iFirstSeg; iSeg < oggbs->currentPageHeader.segmentCount; ++iSeg) { + drflac_uint8 segmentSize = oggbs->currentPageHeader.segmentTable[iSeg]; + if (segmentSize < 255) { + if (iSeg == oggbs->currentPageHeader.segmentCount-1) { + atEndOfPage = DRFLAC_TRUE; + } + + break; + } + + bytesToEndOfPacketOrPage += segmentSize; + } + + // At this point we will have found either the packet or the end of the page. If were at the end of the page we'll + // want to load the next page and keep searching for the end of the packet. + drflac_oggbs__seek_physical(oggbs, bytesToEndOfPacketOrPage, drflac_seek_origin_current); + oggbs->bytesRemainingInPage -= bytesToEndOfPacketOrPage; + + if (atEndOfPage) { + // We're potentially at the next packet, but we need to check the next page first to be sure because the packet may + // straddle pages. + if (!drflac_oggbs__goto_next_page(oggbs)) { + return DRFLAC_FALSE; + } + + // If it's a fresh packet it most likely means we're at the next packet. + if ((oggbs->currentPageHeader.headerType & 0x01) == 0) { + return DRFLAC_TRUE; + } + } else { + // We're at the next packet. + return DRFLAC_TRUE; + } + } +} + +static drflac_bool32 drflac_oggbs__seek_to_next_frame(drflac_oggbs* oggbs) +{ + // The bitstream should be sitting on the first byte just after the header of the frame. + + // What we're actually doing here is seeking to the start of the next packet. + return drflac_oggbs__seek_to_next_packet(oggbs); +} +#endif + +static size_t drflac__on_read_ogg(void* pUserData, void* bufferOut, size_t bytesToRead) +{ + drflac_oggbs* oggbs = (drflac_oggbs*)pUserData; + drflac_assert(oggbs != NULL); + + drflac_uint8* pRunningBufferOut = (drflac_uint8*)bufferOut; + + // Reading is done page-by-page. If we've run out of bytes in the page we need to move to the next one. + size_t bytesRead = 0; + while (bytesRead < bytesToRead) { + size_t bytesRemainingToRead = bytesToRead - bytesRead; + + if (oggbs->bytesRemainingInPage >= bytesRemainingToRead) { + drflac_copy_memory(pRunningBufferOut, oggbs->pageData + (oggbs->pageDataSize - oggbs->bytesRemainingInPage), bytesRemainingToRead); + bytesRead += bytesRemainingToRead; + oggbs->bytesRemainingInPage -= (drflac_uint32)bytesRemainingToRead; + break; + } + + // If we get here it means some of the requested data is contained in the next pages. + if (oggbs->bytesRemainingInPage > 0) { + drflac_copy_memory(pRunningBufferOut, oggbs->pageData + (oggbs->pageDataSize - oggbs->bytesRemainingInPage), oggbs->bytesRemainingInPage); + bytesRead += oggbs->bytesRemainingInPage; + pRunningBufferOut += oggbs->bytesRemainingInPage; + oggbs->bytesRemainingInPage = 0; + } + + drflac_assert(bytesRemainingToRead > 0); + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { + break; // Failed to go to the next page. Might have simply hit the end of the stream. + } + } + + return bytesRead; +} + +static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_seek_origin origin) +{ + drflac_oggbs* oggbs = (drflac_oggbs*)pUserData; + drflac_assert(oggbs != NULL); + drflac_assert(offset >= 0); // <-- Never seek backwards. + + // Seeking is always forward which makes things a lot simpler. + if (origin == drflac_seek_origin_start) { + if (!drflac_oggbs__seek_physical(oggbs, (int)oggbs->firstBytePos, drflac_seek_origin_start)) { + return DRFLAC_FALSE; + } + + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { + return DRFLAC_FALSE; + } + + return drflac__on_seek_ogg(pUserData, offset, drflac_seek_origin_current); + } + + + drflac_assert(origin == drflac_seek_origin_current); + + int bytesSeeked = 0; + while (bytesSeeked < offset) { + int bytesRemainingToSeek = offset - bytesSeeked; + drflac_assert(bytesRemainingToSeek >= 0); + + if (oggbs->bytesRemainingInPage >= (size_t)bytesRemainingToSeek) { + bytesSeeked += bytesRemainingToSeek; + oggbs->bytesRemainingInPage -= bytesRemainingToSeek; + break; + } + + // If we get here it means some of the requested data is contained in the next pages. + if (oggbs->bytesRemainingInPage > 0) { + bytesSeeked += (int)oggbs->bytesRemainingInPage; + oggbs->bytesRemainingInPage = 0; + } + + drflac_assert(bytesRemainingToSeek > 0); + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { + // Failed to go to the next page. We either hit the end of the stream or had a CRC mismatch. + return DRFLAC_FALSE; + } + } + + return DRFLAC_TRUE; +} + +drflac_bool32 drflac_ogg__seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex) +{ + drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + + drflac_uint64 originalBytePos = oggbs->currentBytePos; // For recovery. + + // First seek to the first frame. + if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFramePos)) { + return DRFLAC_FALSE; + } + oggbs->bytesRemainingInPage = 0; + + drflac_uint64 runningGranulePosition = 0; + drflac_uint64 runningFrameBytePos = oggbs->currentBytePos; // <-- Points to the OggS identifier. + for (;;) { + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { + drflac_oggbs__seek_physical(oggbs, originalBytePos, drflac_seek_origin_start); + return DRFLAC_FALSE; // Never did find that sample... + } + + runningFrameBytePos = oggbs->currentBytePos - drflac_ogg__get_page_header_size(&oggbs->currentPageHeader) - oggbs->pageDataSize; + if (oggbs->currentPageHeader.granulePosition*pFlac->channels >= sampleIndex) { + break; // The sample is somewhere in the previous page. + } + + + // At this point we know the sample is not in the previous page. It could possibly be in this page. For simplicity we + // disregard any pages that do not begin a fresh packet. + if ((oggbs->currentPageHeader.headerType & 0x01) == 0) { // <-- Is it a fresh page? + if (oggbs->currentPageHeader.segmentTable[0] >= 2) { + drflac_uint8 firstBytesInPage[2]; + firstBytesInPage[0] = oggbs->pageData[0]; + firstBytesInPage[1] = oggbs->pageData[1]; + + if ((firstBytesInPage[0] == 0xFF) && (firstBytesInPage[1] & 0xFC) == 0xF8) { // <-- Does the page begin with a frame's sync code? + runningGranulePosition = oggbs->currentPageHeader.granulePosition*pFlac->channels; + } + + continue; + } + } + } + + + // We found the page that that is closest to the sample, so now we need to find it. The first thing to do is seek to the + // start of that page. In the loop above we checked that it was a fresh page which means this page is also the start of + // a new frame. This property means that after we've seeked to the page we can immediately start looping over frames until + // we find the one containing the target sample. + if (!drflac_oggbs__seek_physical(oggbs, runningFrameBytePos, drflac_seek_origin_start)) { + return DRFLAC_FALSE; + } + if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { + return DRFLAC_FALSE; + } + + + // At this point we'll be sitting on the first byte of the frame header of the first frame in the page. We just keep + // looping over these frames until we find the one containing the sample we're after. + drflac_uint64 runningSampleCount = runningGranulePosition; + for (;;) { + // There are two ways to find the sample and seek past irrelevant frames: + // 1) Use the native FLAC decoder. + // 2) Use Ogg's framing system. + // + // Both of these options have their own pros and cons. Using the native FLAC decoder is slower because it needs to + // do a full decode of the frame. Using Ogg's framing system is faster, but more complicated and involves some code + // duplication for the decoding of frame headers. + // + // Another thing to consider is that using the Ogg framing system will perform direct seeking of the physical Ogg + // bitstream. This is important to consider because it means we cannot read data from the drflac_bs object using the + // standard drflac__*() APIs because that will read in extra data for its own internal caching which in turn breaks + // the positioning of the read pointer of the physical Ogg bitstream. Therefore, anything that would normally be read + // using the native FLAC decoding APIs, such as drflac__read_next_flac_frame_header(), need to be re-implemented so as to + // avoid the use of the drflac_bs object. + // + // Considering these issues, I have decided to use the slower native FLAC decoding method for the following reasons: + // 1) Seeking is already partially accelerated using Ogg's paging system in the code block above. + // 2) Seeking in an Ogg encapsulated FLAC stream is probably quite uncommon. + // 3) Simplicity. + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + return DRFLAC_FALSE; + } + + drflac_uint64 firstSampleInFrame = 0; + drflac_uint64 lastSampleInFrame = 0; + drflac__get_current_frame_sample_range(pFlac, &firstSampleInFrame, &lastSampleInFrame); + + drflac_uint64 sampleCountInThisFrame = (lastSampleInFrame - firstSampleInFrame) + 1; + if (sampleIndex < (runningSampleCount + sampleCountInThisFrame)) { + // The sample should be in this frame. We need to fully decode it, however if it's an invalid frame (a CRC mismatch), we need to pretend + // it never existed and keep iterating. + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + // The frame is valid. We just need to skip over some samples to ensure it's sample-exact. + drflac_uint64 samplesToDecode = (size_t)(sampleIndex - runningSampleCount); // <-- Safe cast because the maximum number of samples in a frame is 65535. + if (samplesToDecode == 0) { + return DRFLAC_TRUE; + } + return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; // <-- If this fails, something bad has happened (it should never fail). + } else { + if (result == DRFLAC_CRC_MISMATCH) { + continue; // CRC mismatch. Pretend this frame never existed. + } else { + return DRFLAC_FALSE; + } + } + } else { + // It's not in this frame. We need to seek past the frame, but check if there was a CRC mismatch. If so, we pretend this + // frame never existed and leave the running sample count untouched. + drflac_result result = drflac__seek_to_next_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + runningSampleCount += sampleCountInThisFrame; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + continue; // CRC mismatch. Pretend this frame never existed. + } else { + return DRFLAC_FALSE; + } + } + } + } +} + + +drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_bool32 relaxed) +{ + // Pre: The bit stream should be sitting just past the 4-byte OggS capture pattern. + (void)relaxed; + + pInit->container = drflac_container_ogg; + pInit->oggFirstBytePos = 0; + + // We'll get here if the first 4 bytes of the stream were the OggS capture pattern, however it doesn't necessarily mean the + // stream includes FLAC encoded audio. To check for this we need to scan the beginning-of-stream page markers and check if + // any match the FLAC specification. Important to keep in mind that the stream may be multiplexed. + drflac_ogg_page_header header; + + drflac_uint32 crc32 = DRFLAC_OGG_CAPTURE_PATTERN_CRC32; + drflac_uint32 bytesRead = 0; + if (drflac_ogg__read_page_header_after_capture_pattern(onRead, pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { + return DRFLAC_FALSE; + } + pInit->runningFilePos += bytesRead; + + for (;;) { + // Break if we're past the beginning of stream page. + if ((header.headerType & 0x02) == 0) { + return DRFLAC_FALSE; + } + + + // Check if it's a FLAC header. + int pageBodySize = drflac_ogg__get_page_body_size(&header); + if (pageBodySize == 51) { // 51 = the lacing value of the FLAC header packet. + // It could be a FLAC page... + drflac_uint32 bytesRemainingInPage = pageBodySize; + + drflac_uint8 packetType; + if (onRead(pUserData, &packetType, 1) != 1) { + return DRFLAC_FALSE; + } + + bytesRemainingInPage -= 1; + if (packetType == 0x7F) { + // Increasingly more likely to be a FLAC page... + drflac_uint8 sig[4]; + if (onRead(pUserData, sig, 4) != 4) { + return DRFLAC_FALSE; + } + + bytesRemainingInPage -= 4; + if (sig[0] == 'F' && sig[1] == 'L' && sig[2] == 'A' && sig[3] == 'C') { + // Almost certainly a FLAC page... + drflac_uint8 mappingVersion[2]; + if (onRead(pUserData, mappingVersion, 2) != 2) { + return DRFLAC_FALSE; + } + + if (mappingVersion[0] != 1) { + return DRFLAC_FALSE; // Only supporting version 1.x of the Ogg mapping. + } + + // The next 2 bytes are the non-audio packets, not including this one. We don't care about this because we're going to + // be handling it in a generic way based on the serial number and packet types. + if (!onSeek(pUserData, 2, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + + // Expecting the native FLAC signature "fLaC". + if (onRead(pUserData, sig, 4) != 4) { + return DRFLAC_FALSE; + } + + if (sig[0] == 'f' && sig[1] == 'L' && sig[2] == 'a' && sig[3] == 'C') { + // The remaining data in the page should be the STREAMINFO block. + drflac_uint8 isLastBlock; + drflac_uint8 blockType; + drflac_uint32 blockSize; + if (!drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize)) { + return DRFLAC_FALSE; + } + + if (blockType != DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO || blockSize != 34) { + return DRFLAC_FALSE; // Invalid block type. First block must be the STREAMINFO block. + } + + drflac_streaminfo streaminfo; + if (drflac__read_streaminfo(onRead, pUserData, &streaminfo)) { + // Success! + pInit->hasStreamInfoBlock = DRFLAC_TRUE; + pInit->sampleRate = streaminfo.sampleRate; + pInit->channels = streaminfo.channels; + pInit->bitsPerSample = streaminfo.bitsPerSample; + pInit->totalSampleCount = streaminfo.totalSampleCount; + pInit->maxBlockSize = streaminfo.maxBlockSize; + pInit->hasMetadataBlocks = !isLastBlock; + + if (onMeta) { + drflac_metadata metadata; + metadata.type = DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO; + metadata.pRawData = NULL; + metadata.rawDataSize = 0; + metadata.data.streaminfo = streaminfo; + onMeta(pUserDataMD, &metadata); + } + + pInit->runningFilePos += pageBodySize; + pInit->oggFirstBytePos = pInit->runningFilePos - 79; // Subtracting 79 will place us right on top of the "OggS" identifier of the FLAC bos page. + pInit->oggSerial = header.serialNumber; + pInit->oggBosHeader = header; + break; + } else { + // Failed to read STREAMINFO block. Aww, so close... + return DRFLAC_FALSE; + } + } else { + // Invalid file. + return DRFLAC_FALSE; + } + } else { + // Not a FLAC header. Skip it. + if (!onSeek(pUserData, bytesRemainingInPage, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + } + } else { + // Not a FLAC header. Seek past the entire page and move on to the next. + if (!onSeek(pUserData, bytesRemainingInPage, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + } + } else { + if (!onSeek(pUserData, pageBodySize, drflac_seek_origin_current)) { + return DRFLAC_FALSE; + } + } + + pInit->runningFilePos += pageBodySize; + + + // Read the header of the next page. + if (drflac_ogg__read_page_header(onRead, pUserData, &header, &bytesRead, &crc32) != DRFLAC_SUCCESS) { + return DRFLAC_FALSE; + } + pInit->runningFilePos += bytesRead; + } + + + // If we get here it means we found a FLAC audio stream. We should be sitting on the first byte of the header of the next page. The next + // packets in the FLAC logical stream contain the metadata. The only thing left to do in the initialization phase for Ogg is to create the + // Ogg bistream object. + pInit->hasMetadataBlocks = DRFLAC_TRUE; // <-- Always have at least VORBIS_COMMENT metadata block. + return DRFLAC_TRUE; +} +#endif + +drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) +{ + if (pInit == NULL || onRead == NULL || onSeek == NULL) { + return DRFLAC_FALSE; + } + + drflac_zero_memory(pInit, sizeof(*pInit)); + pInit->onRead = onRead; + pInit->onSeek = onSeek; + pInit->onMeta = onMeta; + pInit->container = container; + pInit->pUserData = pUserData; + pInit->pUserDataMD = pUserDataMD; + + pInit->bs.onRead = onRead; + pInit->bs.onSeek = onSeek; + pInit->bs.pUserData = pUserData; + drflac__reset_cache(&pInit->bs); + + + // If the container is explicitly defined then we can try opening in relaxed mode. + drflac_bool32 relaxed = container != drflac_container_unknown; + + drflac_uint8 id[4]; + + // Skip over any ID3 tags. + for (;;) { + if (onRead(pUserData, id, 4) != 4) { + return DRFLAC_FALSE; // Ran out of data. + } + pInit->runningFilePos += 4; + + if (id[0] == 'I' && id[1] == 'D' && id[2] == '3') { + drflac_uint8 header[6]; + if (onRead(pUserData, header, 6) != 6) { + return DRFLAC_FALSE; // Ran out of data. + } + pInit->runningFilePos += 6; + + drflac_uint8 flags = header[1]; + drflac_uint32 headerSize; + drflac_copy_memory(&headerSize, header+2, 4); + headerSize = drflac__unsynchsafe_32(drflac__be2host_32(headerSize)); + if (flags & 0x10) { + headerSize += 10; + } + + if (!onSeek(pUserData, headerSize, drflac_seek_origin_current)) { + return DRFLAC_FALSE; // Failed to seek past the tag. + } + pInit->runningFilePos += headerSize; + } else { + break; + } + } + + if (id[0] == 'f' && id[1] == 'L' && id[2] == 'a' && id[3] == 'C') { + return drflac__init_private__native(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); + } +#ifndef DR_FLAC_NO_OGG + if (id[0] == 'O' && id[1] == 'g' && id[2] == 'g' && id[3] == 'S') { + return drflac__init_private__ogg(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); + } +#endif + + // If we get here it means we likely don't have a header. Try opening in relaxed mode, if applicable. + if (relaxed) { + if (container == drflac_container_native) { + return drflac__init_private__native(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); + } +#ifndef DR_FLAC_NO_OGG + if (container == drflac_container_ogg) { + return drflac__init_private__ogg(pInit, onRead, onSeek, onMeta, pUserData, pUserDataMD, relaxed); + } +#endif + } + + // Unsupported container. + return DRFLAC_FALSE; +} + +void drflac__init_from_info(drflac* pFlac, drflac_init_info* pInit) +{ + drflac_assert(pFlac != NULL); + drflac_assert(pInit != NULL); + + drflac_zero_memory(pFlac, sizeof(*pFlac)); + pFlac->bs = pInit->bs; + pFlac->onMeta = pInit->onMeta; + pFlac->pUserDataMD = pInit->pUserDataMD; + pFlac->maxBlockSize = pInit->maxBlockSize; + pFlac->sampleRate = pInit->sampleRate; + pFlac->channels = (drflac_uint8)pInit->channels; + pFlac->bitsPerSample = (drflac_uint8)pInit->bitsPerSample; + pFlac->totalSampleCount = pInit->totalSampleCount; + pFlac->totalPCMFrameCount = pInit->totalSampleCount / pFlac->channels; + pFlac->container = pInit->container; +} + +drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) +{ +#ifndef DRFLAC_NO_CPUID + // CPU support first. + drflac__init_cpu_caps(); +#endif + + drflac_init_info init; + if (!drflac__init_private(&init, onRead, onSeek, onMeta, container, pUserData, pUserDataMD)) { + return NULL; + } + + // The size of the allocation for the drflac object needs to be large enough to fit the following: + // 1) The main members of the drflac structure + // 2) A block of memory large enough to store the decoded samples of the largest frame in the stream + // 3) If the container is Ogg, a drflac_oggbs object + // + // The complicated part of the allocation is making sure there's enough room the decoded samples, taking into consideration + // the different SIMD instruction sets. + drflac_uint32 allocationSize = sizeof(drflac); + + // The allocation size for decoded frames depends on the number of 32-bit integers that fit inside the largest SIMD vector + // we are supporting. + drflac_uint32 wholeSIMDVectorCountPerChannel; + if (((init.maxBlockSize+DRFLAC_LEADING_SAMPLES) % (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) == 0) { + wholeSIMDVectorCountPerChannel = ((init.maxBlockSize+DRFLAC_LEADING_SAMPLES) / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))); + } else { + wholeSIMDVectorCountPerChannel = ((init.maxBlockSize+DRFLAC_LEADING_SAMPLES) / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) + 1; + } + + drflac_uint32 decodedSamplesAllocationSize = wholeSIMDVectorCountPerChannel * DRFLAC_MAX_SIMD_VECTOR_SIZE * init.channels; + + allocationSize += decodedSamplesAllocationSize; + allocationSize += DRFLAC_MAX_SIMD_VECTOR_SIZE; // Allocate extra bytes to ensure we have enough for alignment. + +#ifndef DR_FLAC_NO_OGG + // There's additional data required for Ogg streams. + drflac_uint32 oggbsAllocationSize = 0; + if (init.container == drflac_container_ogg) { + oggbsAllocationSize = sizeof(drflac_oggbs); + allocationSize += oggbsAllocationSize; + } + + drflac_oggbs oggbs; + drflac_zero_memory(&oggbs, sizeof(oggbs)); + if (init.container == drflac_container_ogg) { + oggbs.onRead = onRead; + oggbs.onSeek = onSeek; + oggbs.pUserData = pUserData; + oggbs.currentBytePos = init.oggFirstBytePos; + oggbs.firstBytePos = init.oggFirstBytePos; + oggbs.serialNumber = init.oggSerial; + oggbs.bosPageHeader = init.oggBosHeader; + oggbs.bytesRemainingInPage = 0; + } +#endif + + // This part is a bit awkward. We need to load the seektable so that it can be referenced in-memory, but I want the drflac object to + // consist of only a single heap allocation. To this, the size of the seek table needs to be known, which we determine when reading + // and decoding the metadata. + drflac_uint64 firstFramePos = 42; // <-- We know we are at byte 42 at this point. + drflac_uint64 seektablePos = 0; + drflac_uint32 seektableSize = 0; + if (init.hasMetadataBlocks) { + drflac_read_proc onReadOverride = onRead; + drflac_seek_proc onSeekOverride = onSeek; + void* pUserDataOverride = pUserData; + +#ifndef DR_FLAC_NO_OGG + if (init.container == drflac_container_ogg) { + onReadOverride = drflac__on_read_ogg; + onSeekOverride = drflac__on_seek_ogg; + pUserDataOverride = (void*)&oggbs; + } +#endif + + if (!drflac__read_and_decode_metadata(onReadOverride, onSeekOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seektableSize)) { + return NULL; + } + + allocationSize += seektableSize; + } + + + drflac* pFlac = (drflac*)DRFLAC_MALLOC(allocationSize); + drflac__init_from_info(pFlac, &init); + pFlac->pDecodedSamples = (drflac_int32*)drflac_align((size_t)pFlac->pExtraData, DRFLAC_MAX_SIMD_VECTOR_SIZE); + +#ifndef DR_FLAC_NO_OGG + if (init.container == drflac_container_ogg) { + drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + seektableSize); + *pInternalOggbs = oggbs; + + // The Ogg bistream needs to be layered on top of the original bitstream. + pFlac->bs.onRead = drflac__on_read_ogg; + pFlac->bs.onSeek = drflac__on_seek_ogg; + pFlac->bs.pUserData = (void*)pInternalOggbs; + pFlac->_oggbs = (void*)pInternalOggbs; + } +#endif + + pFlac->firstFramePos = firstFramePos; + + // NOTE: Seektables are not currently compatible with Ogg encapsulation (Ogg has its own accelerated seeking system). I may change this later, so I'm leaving this here for now. +#ifndef DR_FLAC_NO_OGG + if (init.container == drflac_container_ogg) + { + pFlac->pSeekpoints = NULL; + pFlac->seekpointCount = 0; + } + else +#endif + { + // If we have a seektable we need to load it now, making sure we move back to where we were previously. + if (seektablePos != 0) { + pFlac->seekpointCount = seektableSize / sizeof(*pFlac->pSeekpoints); + pFlac->pSeekpoints = (drflac_seekpoint*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize); + + // Seek to the seektable, then just read directly into our seektable buffer. + if (pFlac->bs.onSeek(pFlac->bs.pUserData, (int)seektablePos, drflac_seek_origin_start)) { + if (pFlac->bs.onRead(pFlac->bs.pUserData, pFlac->pSeekpoints, seektableSize) == seektableSize) { + // Endian swap. + for (drflac_uint32 iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { + pFlac->pSeekpoints[iSeekpoint].firstSample = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].firstSample); + pFlac->pSeekpoints[iSeekpoint].frameOffset = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].frameOffset); + pFlac->pSeekpoints[iSeekpoint].sampleCount = drflac__be2host_16(pFlac->pSeekpoints[iSeekpoint].sampleCount); + } + } else { + // Failed to read the seektable. Pretend we don't have one. + pFlac->pSeekpoints = NULL; + pFlac->seekpointCount = 0; + } + + // We need to seek back to where we were. If this fails it's a critical error. + if (!pFlac->bs.onSeek(pFlac->bs.pUserData, (int)pFlac->firstFramePos, drflac_seek_origin_start)) { + DRFLAC_FREE(pFlac); + return NULL; + } + } else { + // Failed to seek to the seektable. Ominous sign, but for now we can just pretend we don't have one. + pFlac->pSeekpoints = NULL; + pFlac->seekpointCount = 0; + } + } + } + + + + // If we get here, but don't have a STREAMINFO block, it means we've opened the stream in relaxed mode and need to decode + // the first frame. + if (!init.hasStreamInfoBlock) { + pFlac->currentFrame.header = init.firstFrameHeader; + do + { + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + break; + } else { + if (result == DRFLAC_CRC_MISMATCH) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + DRFLAC_FREE(pFlac); + return NULL; + } + continue; + } else { + DRFLAC_FREE(pFlac); + return NULL; + } + } + } while (1); + } + + return pFlac; +} + + + +#ifndef DR_FLAC_NO_STDIO +#include + +static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead) +{ + return fread(bufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +static drflac_bool32 drflac__on_seek_stdio(void* pUserData, int offset, drflac_seek_origin origin) +{ + drflac_assert(offset >= 0); // <-- Never seek backwards. + + return fseek((FILE*)pUserData, offset, (origin == drflac_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +static FILE* drflac__fopen(const char* filename) +{ + FILE* pFile; +#ifdef _MSC_VER + if (fopen_s(&pFile, filename, "rb") != 0) { + return NULL; + } +#else + pFile = fopen(filename, "rb"); + if (pFile == NULL) { + return NULL; + } +#endif + + return pFile; +} + + +drflac* drflac_open_file(const char* filename) +{ + FILE* file = drflac__fopen(filename); + if (file == NULL) { + return NULL; + } + + drflac* pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)file); + if (pFlac == NULL) { + fclose(file); + return NULL; + } + + return pFlac; +} + +drflac* drflac_open_file_with_metadata(const char* filename, drflac_meta_proc onMeta, void* pUserData) +{ + FILE* file = drflac__fopen(filename); + if (file == NULL) { + return NULL; + } + + drflac* pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)file, pUserData); + if (pFlac == NULL) { + fclose(file); + return pFlac; + } + + return pFlac; +} +#endif //DR_FLAC_NO_STDIO + +static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t bytesToRead) +{ + drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + drflac_assert(memoryStream != NULL); + drflac_assert(memoryStream->dataSize >= memoryStream->currentReadPos); + + size_t bytesRemaining = memoryStream->dataSize - memoryStream->currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + drflac_copy_memory(bufferOut, memoryStream->data + memoryStream->currentReadPos, bytesToRead); + memoryStream->currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +static drflac_bool32 drflac__on_seek_memory(void* pUserData, int offset, drflac_seek_origin origin) +{ + drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; + drflac_assert(memoryStream != NULL); + drflac_assert(offset >= 0); // <-- Never seek backwards. + + if (offset > (drflac_int64)memoryStream->dataSize) { + return DRFLAC_FALSE; + } + + if (origin == drflac_seek_origin_current) { + if (memoryStream->currentReadPos + offset <= memoryStream->dataSize) { + memoryStream->currentReadPos += offset; + } else { + return DRFLAC_FALSE; // Trying to seek too far forward. + } + } else { + if ((drflac_uint32)offset <= memoryStream->dataSize) { + memoryStream->currentReadPos = offset; + } else { + return DRFLAC_FALSE; // Trying to seek too far forward. + } + } + + return DRFLAC_TRUE; +} + +drflac* drflac_open_memory(const void* data, size_t dataSize) +{ + drflac__memory_stream memoryStream; + memoryStream.data = (const unsigned char*)data; + memoryStream.dataSize = dataSize; + memoryStream.currentReadPos = 0; + drflac* pFlac = drflac_open(drflac__on_read_memory, drflac__on_seek_memory, &memoryStream); + if (pFlac == NULL) { + return NULL; + } + + pFlac->memoryStream = memoryStream; + + // This is an awful hack... +#ifndef DR_FLAC_NO_OGG + if (pFlac->container == drflac_container_ogg) + { + drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + oggbs->pUserData = &pFlac->memoryStream; + } + else +#endif + { + pFlac->bs.pUserData = &pFlac->memoryStream; + } + + return pFlac; +} + +drflac* drflac_open_memory_with_metadata(const void* data, size_t dataSize, drflac_meta_proc onMeta, void* pUserData) +{ + drflac__memory_stream memoryStream; + memoryStream.data = (const unsigned char*)data; + memoryStream.dataSize = dataSize; + memoryStream.currentReadPos = 0; + drflac* pFlac = drflac_open_with_metadata_private(drflac__on_read_memory, drflac__on_seek_memory, onMeta, drflac_container_unknown, &memoryStream, pUserData); + if (pFlac == NULL) { + return NULL; + } + + pFlac->memoryStream = memoryStream; + + // This is an awful hack... +#ifndef DR_FLAC_NO_OGG + if (pFlac->container == drflac_container_ogg) + { + drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + oggbs->pUserData = &pFlac->memoryStream; + } + else +#endif + { + pFlac->bs.pUserData = &pFlac->memoryStream; + } + + return pFlac; +} + + + +drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData) +{ + return drflac_open_with_metadata_private(onRead, onSeek, NULL, drflac_container_unknown, pUserData, pUserData); +} +drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData) +{ + return drflac_open_with_metadata_private(onRead, onSeek, NULL, container, pUserData, pUserData); +} + +drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData) +{ + return drflac_open_with_metadata_private(onRead, onSeek, onMeta, drflac_container_unknown, pUserData, pUserData); +} +drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData) +{ + return drflac_open_with_metadata_private(onRead, onSeek, onMeta, container, pUserData, pUserData); +} + +void drflac_close(drflac* pFlac) +{ + if (pFlac == NULL) { + return; + } + +#ifndef DR_FLAC_NO_STDIO + // If we opened the file with drflac_open_file() we will want to close the file handle. We can know whether or not drflac_open_file() + // was used by looking at the callbacks. + if (pFlac->bs.onRead == drflac__on_read_stdio) { + fclose((FILE*)pFlac->bs.pUserData); + } + +#ifndef DR_FLAC_NO_OGG + // Need to clean up Ogg streams a bit differently due to the way the bit streaming is chained. + if (pFlac->container == drflac_container_ogg) { + drflac_assert(pFlac->bs.onRead == drflac__on_read_ogg); + drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; + if (oggbs->onRead == drflac__on_read_stdio) { + fclose((FILE*)oggbs->pUserData); + } + } +#endif +#endif + + DRFLAC_FREE(pFlac); +} + +drflac_uint64 drflac__read_s32__misaligned(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int32* bufferOut) +{ + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + + // We should never be calling this when the number of samples to read is >= the sample count. + drflac_assert(samplesToRead < channelCount); + drflac_assert(pFlac->currentFrame.samplesRemaining > 0 && samplesToRead <= pFlac->currentFrame.samplesRemaining); + + + drflac_uint64 samplesRead = 0; + while (samplesToRead > 0) { + drflac_uint64 totalSamplesInFrame = pFlac->currentFrame.header.blockSize * channelCount; + drflac_uint64 samplesReadFromFrameSoFar = totalSamplesInFrame - pFlac->currentFrame.samplesRemaining; + drflac_uint64 channelIndex = samplesReadFromFrameSoFar % channelCount; + + drflac_uint64 nextSampleInFrame = samplesReadFromFrameSoFar / channelCount; + + int decodedSample = 0; + switch (pFlac->currentFrame.header.channelAssignment) + { + case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: + { + if (channelIndex == 0) { + decodedSample = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample; + } else { + int side = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample; + int left = pFlac->currentFrame.subframes[channelIndex - 1].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex - 1].wastedBitsPerSample; + decodedSample = left - side; + } + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: + { + if (channelIndex == 0) { + int side = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample; + int right = pFlac->currentFrame.subframes[channelIndex + 1].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 1].wastedBitsPerSample; + decodedSample = side + right; + } else { + decodedSample = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample; + } + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: + { + int mid; + int side; + if (channelIndex == 0) { + mid = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample; + side = pFlac->currentFrame.subframes[channelIndex + 1].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 1].wastedBitsPerSample; + + mid = (((unsigned int)mid) << 1) | (side & 0x01); + decodedSample = (mid + side) >> 1; + } else { + mid = pFlac->currentFrame.subframes[channelIndex - 1].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex - 1].wastedBitsPerSample; + side = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample; + + mid = (((unsigned int)mid) << 1) | (side & 0x01); + decodedSample = (mid - side) >> 1; + } + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: + default: + { + decodedSample = pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample; + } break; + } + + + decodedSample <<= (32 - pFlac->bitsPerSample); + + if (bufferOut) { + *bufferOut++ = decodedSample; + } + + samplesRead += 1; + pFlac->currentFrame.samplesRemaining -= 1; + samplesToRead -= 1; + } + + return samplesRead; +} + +drflac_uint64 drflac_read_s32(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int32* bufferOut) +{ + // Note that is allowed to be null, in which case this will act like a seek. + if (pFlac == NULL || samplesToRead == 0) { + return 0; + } + + if (bufferOut == NULL) { + return drflac__seek_forward_by_samples(pFlac, samplesToRead); + } + + + drflac_uint64 samplesRead = 0; + while (samplesToRead > 0) { + // If we've run out of samples in this frame, go to the next. + if (pFlac->currentFrame.samplesRemaining == 0) { + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + break; // Couldn't read the next frame, so just break from the loop and return. + } + } else { + // Here is where we grab the samples and interleave them. + + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + drflac_uint64 totalSamplesInFrame = pFlac->currentFrame.header.blockSize * channelCount; + drflac_uint64 samplesReadFromFrameSoFar = totalSamplesInFrame - pFlac->currentFrame.samplesRemaining; + + drflac_uint64 misalignedSampleCount = samplesReadFromFrameSoFar % channelCount; + if (misalignedSampleCount > 0) { + drflac_uint64 misalignedSamplesRead = drflac__read_s32__misaligned(pFlac, misalignedSampleCount, bufferOut); + samplesRead += misalignedSamplesRead; + samplesReadFromFrameSoFar += misalignedSamplesRead; + bufferOut += misalignedSamplesRead; + samplesToRead -= misalignedSamplesRead; + pFlac->currentSample += misalignedSamplesRead; + } + + + drflac_uint64 alignedSampleCountPerChannel = samplesToRead / channelCount; + if (alignedSampleCountPerChannel > pFlac->currentFrame.samplesRemaining / channelCount) { + alignedSampleCountPerChannel = pFlac->currentFrame.samplesRemaining / channelCount; + } + + drflac_uint64 firstAlignedSampleInFrame = samplesReadFromFrameSoFar / channelCount; + unsigned int unusedBitsPerSample = 32 - pFlac->bitsPerSample; + + switch (pFlac->currentFrame.header.channelAssignment) + { + case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: + { + const drflac_int32* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; + + for (drflac_uint64 i = 0; i < alignedSampleCountPerChannel; ++i) { + int left = pDecodedSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); + int side = pDecodedSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); + int right = left - side; + + bufferOut[i*2+0] = left; + bufferOut[i*2+1] = right; + } + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: + { + const drflac_int32* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; + + for (drflac_uint64 i = 0; i < alignedSampleCountPerChannel; ++i) { + int side = pDecodedSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); + int right = pDecodedSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); + int left = right + side; + + bufferOut[i*2+0] = left; + bufferOut[i*2+1] = right; + } + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: + { + const drflac_int32* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; + + for (drflac_uint64 i = 0; i < alignedSampleCountPerChannel; ++i) { + int mid = pDecodedSamples0[i] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + int side = pDecodedSamples1[i] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + + mid = (((drflac_uint32)mid) << 1) | (side & 0x01); + + bufferOut[i*2+0] = ((mid + side) >> 1) << (unusedBitsPerSample); + bufferOut[i*2+1] = ((mid - side) >> 1) << (unusedBitsPerSample); + } + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: + default: + { + if (pFlac->currentFrame.header.channelAssignment == 1) // 1 = Stereo + { + // Stereo optimized inner loop unroll. + const drflac_int32* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; + + for (drflac_uint64 i = 0; i < alignedSampleCountPerChannel; ++i) { + bufferOut[i*2+0] = pDecodedSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); + bufferOut[i*2+1] = pDecodedSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); + } + } + else + { + // Generic interleaving. + for (drflac_uint64 i = 0; i < alignedSampleCountPerChannel; ++i) { + for (unsigned int j = 0; j < channelCount; ++j) { + bufferOut[(i*channelCount)+j] = (pFlac->currentFrame.subframes[j].pDecodedSamples[firstAlignedSampleInFrame + i]) << (unusedBitsPerSample + pFlac->currentFrame.subframes[j].wastedBitsPerSample); + } + } + } + } break; + } + + drflac_uint64 alignedSamplesRead = alignedSampleCountPerChannel * channelCount; + samplesRead += alignedSamplesRead; + samplesReadFromFrameSoFar += alignedSamplesRead; + bufferOut += alignedSamplesRead; + samplesToRead -= alignedSamplesRead; + pFlac->currentSample += alignedSamplesRead; + pFlac->currentFrame.samplesRemaining -= (unsigned int)alignedSamplesRead; + + + // At this point we may still have some excess samples left to read. + if (samplesToRead > 0 && pFlac->currentFrame.samplesRemaining > 0) { + drflac_uint64 excessSamplesRead = 0; + if (samplesToRead < pFlac->currentFrame.samplesRemaining) { + excessSamplesRead = drflac__read_s32__misaligned(pFlac, samplesToRead, bufferOut); + } else { + excessSamplesRead = drflac__read_s32__misaligned(pFlac, pFlac->currentFrame.samplesRemaining, bufferOut); + } + + samplesRead += excessSamplesRead; + samplesReadFromFrameSoFar += excessSamplesRead; + bufferOut += excessSamplesRead; + samplesToRead -= excessSamplesRead; + pFlac->currentSample += excessSamplesRead; + } + } + } + + return samplesRead; +} + +drflac_uint64 drflac_read_pcm_frames_s32(drflac* pFlac, drflac_uint64 framesToRead, drflac_int32* pBufferOut) +{ +#if defined(_MSC_VER) && !defined(__clang__) + #pragma warning(push) + #pragma warning(disable:4996) // was declared deprecated +#elif defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + return drflac_read_s32(pFlac, framesToRead*pFlac->channels, pBufferOut) / pFlac->channels; +#if defined(_MSC_VER) && !defined(__clang__) + #pragma warning(pop) +#elif defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic pop +#endif +} + + +drflac_uint64 drflac_read_s16(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int16* pBufferOut) +{ + // This reads samples in 2 passes and can probably be optimized. + drflac_uint64 totalSamplesRead = 0; + +#if defined(_MSC_VER) && !defined(__clang__) + #pragma warning(push) + #pragma warning(disable:4996) // was declared deprecated +#elif defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + while (samplesToRead > 0) { + drflac_int32 samples32[4096]; + drflac_uint64 samplesJustRead = drflac_read_s32(pFlac, (samplesToRead > 4096) ? 4096 : samplesToRead, samples32); + if (samplesJustRead == 0) { + break; // Reached the end. + } + + // s32 -> s16 + for (drflac_uint64 i = 0; i < samplesJustRead; ++i) { + pBufferOut[i] = (drflac_int16)(samples32[i] >> 16); + } + + totalSamplesRead += samplesJustRead; + samplesToRead -= samplesJustRead; + pBufferOut += samplesJustRead; + } + +#if defined(_MSC_VER) && !defined(__clang__) + #pragma warning(pop) +#elif defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic pop +#endif + + return totalSamplesRead; +} + +drflac_uint64 drflac_read_pcm_frames_s16(drflac* pFlac, drflac_uint64 framesToRead, drflac_int16* pBufferOut) +{ + // This reads samples in 2 passes and can probably be optimized. + drflac_uint64 totalPCMFramesRead = 0; + + while (framesToRead > 0) { + drflac_int32 samples32[4096]; + drflac_uint64 framesJustRead = drflac_read_pcm_frames_s32(pFlac, (framesToRead > 4096/pFlac->channels) ? 4096/pFlac->channels : framesToRead, samples32); + if (framesJustRead == 0) { + break; // Reached the end. + } + + // s32 -> s16 + for (drflac_uint64 iFrame = 0; iFrame < framesJustRead; ++iFrame) { + for (drflac_uint32 iChannel = 0; iChannel < pFlac->channels; ++iChannel) { + drflac_uint64 iSample = iFrame*pFlac->channels + iChannel; + pBufferOut[iSample] = (drflac_int16)(samples32[iSample] >> 16); + } + } + + totalPCMFramesRead += framesJustRead; + framesToRead -= framesJustRead; + pBufferOut += framesJustRead * pFlac->channels; + } + + return totalPCMFramesRead; +} + + +drflac_uint64 drflac_read_f32(drflac* pFlac, drflac_uint64 samplesToRead, float* pBufferOut) +{ + // This reads samples in 2 passes and can probably be optimized. + drflac_uint64 totalSamplesRead = 0; + +#if defined(_MSC_VER) && !defined(__clang__) + #pragma warning(push) + #pragma warning(disable:4996) // was declared deprecated +#elif defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + + while (samplesToRead > 0) { + drflac_int32 samples32[4096]; + drflac_uint64 samplesJustRead = drflac_read_s32(pFlac, (samplesToRead > 4096) ? 4096 : samplesToRead, samples32); + if (samplesJustRead == 0) { + break; // Reached the end. + } + + // s32 -> f32 + for (drflac_uint64 i = 0; i < samplesJustRead; ++i) { + pBufferOut[i] = (float)(samples32[i] / 2147483648.0); + } + + totalSamplesRead += samplesJustRead; + samplesToRead -= samplesJustRead; + pBufferOut += samplesJustRead; + } + +#if defined(_MSC_VER) && !defined(__clang__) + #pragma warning(pop) +#elif defined(__GNUC__) || defined(__clang__) + #pragma GCC diagnostic pop +#endif + + return totalSamplesRead; +} + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + int left = pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); + int side = pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); + int right = left - side; + + pOutputSamples[i*2+0] = (float)(left / 2147483648.0); + pOutputSamples[i*2+1] = (float)(right / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 frameCount4 = frameCount >> 2; + + float factor = 1 / 2147483648.0; + + drflac_int32 shift0 = unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample; + drflac_int32 shift1 = unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample; + for (drflac_uint64 i = 0; i < frameCount4; ++i) { + drflac_int32 left0 = pInputSamples0[i*4+0] << shift0; + drflac_int32 left1 = pInputSamples0[i*4+1] << shift0; + drflac_int32 left2 = pInputSamples0[i*4+2] << shift0; + drflac_int32 left3 = pInputSamples0[i*4+3] << shift0; + + drflac_int32 side0 = pInputSamples1[i*4+0] << shift1; + drflac_int32 side1 = pInputSamples1[i*4+1] << shift1; + drflac_int32 side2 = pInputSamples1[i*4+2] << shift1; + drflac_int32 side3 = pInputSamples1[i*4+3] << shift1; + + drflac_int32 right0 = left0 - side0; + drflac_int32 right1 = left1 - side1; + drflac_int32 right2 = left2 - side2; + drflac_int32 right3 = left3 - side3; + + pOutputSamples[i*8+0] = left0 * factor; + pOutputSamples[i*8+1] = right0 * factor; + pOutputSamples[i*8+2] = left1 * factor; + pOutputSamples[i*8+3] = right1 * factor; + pOutputSamples[i*8+4] = left2 * factor; + pOutputSamples[i*8+5] = right2 * factor; + pOutputSamples[i*8+6] = left3 * factor; + pOutputSamples[i*8+7] = right3 * factor; + } + + for (drflac_uint64 i = (frameCount4 << 2); i < frameCount; ++i) { + int left = pInputSamples0[i] << shift0; + int side = pInputSamples1[i] << shift1; + int right = left - side; + + pOutputSamples[i*2+0] = (float)(left * factor); + pOutputSamples[i*2+1] = (float)(right * factor); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_assert(pFlac->bitsPerSample <= 24); + + drflac_uint64 frameCount4 = frameCount >> 2; + + __m128 factor = _mm_set1_ps(1.0f / 8388608.0f); + int shift0 = (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample) - 8; + int shift1 = (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample) - 8; + + for (drflac_uint64 i = 0; i < frameCount4; ++i) { + __m128i inputSample0 = _mm_loadu_si128((const __m128i*)pInputSamples0 + i); + __m128i inputSample1 = _mm_loadu_si128((const __m128i*)pInputSamples1 + i); + + __m128i left = _mm_slli_epi32(inputSample0, shift0); + __m128i side = _mm_slli_epi32(inputSample1, shift1); + __m128i right = _mm_sub_epi32(left, side); + __m128 leftf = _mm_mul_ps(_mm_cvtepi32_ps(left), factor); + __m128 rightf = _mm_mul_ps(_mm_cvtepi32_ps(right), factor); + + pOutputSamples[i*8+0] = ((float*)&leftf)[0]; + pOutputSamples[i*8+1] = ((float*)&rightf)[0]; + pOutputSamples[i*8+2] = ((float*)&leftf)[1]; + pOutputSamples[i*8+3] = ((float*)&rightf)[1]; + pOutputSamples[i*8+4] = ((float*)&leftf)[2]; + pOutputSamples[i*8+5] = ((float*)&rightf)[2]; + pOutputSamples[i*8+6] = ((float*)&leftf)[3]; + pOutputSamples[i*8+7] = ((float*)&rightf)[3]; + } + + for (drflac_uint64 i = (frameCount4 << 2); i < frameCount; ++i) { + int left = pInputSamples0[i] << shift0; + int side = pInputSamples1[i] << shift1; + int right = left - side; + + pOutputSamples[i*2+0] = (float)(left / 8388608.0f); + pOutputSamples[i*2+1] = (float)(right / 8388608.0f); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + // Scalar fallback. +#if 0 + drflac_read_pcm_frames_f32__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + int side = pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); + int right = pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); + int left = right + side; + + pOutputSamples[i*2+0] = (float)(left / 2147483648.0); + pOutputSamples[i*2+1] = (float)(right / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 frameCount4 = frameCount >> 2; + + float factor = 1 / 2147483648.0; + + drflac_int32 shift0 = unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample; + drflac_int32 shift1 = unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample; + for (drflac_uint64 i = 0; i < frameCount4; ++i) { + drflac_int32 side0 = pInputSamples0[i*4+0] << shift1; + drflac_int32 side1 = pInputSamples0[i*4+1] << shift1; + drflac_int32 side2 = pInputSamples0[i*4+2] << shift1; + drflac_int32 side3 = pInputSamples0[i*4+3] << shift1; + + drflac_int32 right0 = pInputSamples1[i*4+0] << shift0; + drflac_int32 right1 = pInputSamples1[i*4+1] << shift0; + drflac_int32 right2 = pInputSamples1[i*4+2] << shift0; + drflac_int32 right3 = pInputSamples1[i*4+3] << shift0; + + drflac_int32 left0 = right0 + side0; + drflac_int32 left1 = right1 + side1; + drflac_int32 left2 = right2 + side2; + drflac_int32 left3 = right3 + side3; + + pOutputSamples[i*8+0] = left0 * factor; + pOutputSamples[i*8+1] = right0 * factor; + pOutputSamples[i*8+2] = left1 * factor; + pOutputSamples[i*8+3] = right1 * factor; + pOutputSamples[i*8+4] = left2 * factor; + pOutputSamples[i*8+5] = right2 * factor; + pOutputSamples[i*8+6] = left3 * factor; + pOutputSamples[i*8+7] = right3 * factor; + } + + for (drflac_uint64 i = (frameCount4 << 2); i < frameCount; ++i) { + int side = pInputSamples0[i] << shift0; + int right = pInputSamples1[i] << shift1; + int left = right + side; + + pOutputSamples[i*2+0] = (float)(left * factor); + pOutputSamples[i*2+1] = (float)(right * factor); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_assert(pFlac->bitsPerSample <= 24); + + drflac_uint64 frameCount4 = frameCount >> 2; + + __m128 factor = _mm_set1_ps(1.0f / 8388608.0f); + int shift0 = (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample) - 8; + int shift1 = (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample) - 8; + + for (drflac_uint64 i = 0; i < frameCount4; ++i) { + __m128i inputSample0 = _mm_loadu_si128((const __m128i*)pInputSamples0 + i); + __m128i inputSample1 = _mm_loadu_si128((const __m128i*)pInputSamples1 + i); + + __m128i side = _mm_slli_epi32(inputSample0, shift0); + __m128i right = _mm_slli_epi32(inputSample1, shift1); + __m128i left = _mm_add_epi32(right, side); + __m128 leftf = _mm_mul_ps(_mm_cvtepi32_ps(left), factor); + __m128 rightf = _mm_mul_ps(_mm_cvtepi32_ps(right), factor); + + pOutputSamples[i*8+0] = ((float*)&leftf)[0]; + pOutputSamples[i*8+1] = ((float*)&rightf)[0]; + pOutputSamples[i*8+2] = ((float*)&leftf)[1]; + pOutputSamples[i*8+3] = ((float*)&rightf)[1]; + pOutputSamples[i*8+4] = ((float*)&leftf)[2]; + pOutputSamples[i*8+5] = ((float*)&rightf)[2]; + pOutputSamples[i*8+6] = ((float*)&leftf)[3]; + pOutputSamples[i*8+7] = ((float*)&rightf)[3]; + } + + for (drflac_uint64 i = (frameCount4 << 2); i < frameCount; ++i) { + int side = pInputSamples0[i] << shift0; + int right = pInputSamples1[i] << shift1; + int left = right + side; + + pOutputSamples[i*2+0] = (float)(left / 8388608.0f); + pOutputSamples[i*2+1] = (float)(right / 8388608.0f); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + // Scalar fallback. +#if 0 + drflac_read_pcm_frames_f32__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + int mid = pInputSamples0[i] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + int side = pInputSamples1[i] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + + mid = (((drflac_uint32)mid) << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (float)((((mid + side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); + pOutputSamples[i*2+1] = (float)((((mid - side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 frameCount4 = frameCount >> 2; + + float factor = 1 / 2147483648.0; + + int shift = unusedBitsPerSample; + if (shift > 0) { + shift -= 1; + for (drflac_uint64 i = 0; i < frameCount4; ++i) { + int mid0 = pInputSamples0[i*4+0] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + int mid1 = pInputSamples0[i*4+1] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + int mid2 = pInputSamples0[i*4+2] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + int mid3 = pInputSamples0[i*4+3] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + + int side0 = pInputSamples1[i*4+0] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + int side1 = pInputSamples1[i*4+1] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + int side2 = pInputSamples1[i*4+2] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + int side3 = pInputSamples1[i*4+3] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + + mid0 = (((drflac_uint32)mid0) << 1) | (side0 & 0x01); + mid1 = (((drflac_uint32)mid1) << 1) | (side1 & 0x01); + mid2 = (((drflac_uint32)mid2) << 1) | (side2 & 0x01); + mid3 = (((drflac_uint32)mid3) << 1) | (side3 & 0x01); + + int temp0L = ((mid0 + side0) << shift); + int temp1L = ((mid1 + side1) << shift); + int temp2L = ((mid2 + side2) << shift); + int temp3L = ((mid3 + side3) << shift); + + int temp0R = ((mid0 - side0) << shift); + int temp1R = ((mid1 - side1) << shift); + int temp2R = ((mid2 - side2) << shift); + int temp3R = ((mid3 - side3) << shift); + + pOutputSamples[i*8+0] = (float)(temp0L * factor); + pOutputSamples[i*8+1] = (float)(temp0R * factor); + pOutputSamples[i*8+2] = (float)(temp1L * factor); + pOutputSamples[i*8+3] = (float)(temp1R * factor); + pOutputSamples[i*8+4] = (float)(temp2L * factor); + pOutputSamples[i*8+5] = (float)(temp2R * factor); + pOutputSamples[i*8+6] = (float)(temp3L * factor); + pOutputSamples[i*8+7] = (float)(temp3R * factor); + } + } else { + for (drflac_uint64 i = 0; i < frameCount4; ++i) { + int mid0 = pInputSamples0[i*4+0] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + int mid1 = pInputSamples0[i*4+1] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + int mid2 = pInputSamples0[i*4+2] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + int mid3 = pInputSamples0[i*4+3] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + + int side0 = pInputSamples1[i*4+0] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + int side1 = pInputSamples1[i*4+1] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + int side2 = pInputSamples1[i*4+2] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + int side3 = pInputSamples1[i*4+3] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + + mid0 = (((drflac_uint32)mid0) << 1) | (side0 & 0x01); + mid1 = (((drflac_uint32)mid1) << 1) | (side1 & 0x01); + mid2 = (((drflac_uint32)mid2) << 1) | (side2 & 0x01); + mid3 = (((drflac_uint32)mid3) << 1) | (side3 & 0x01); + + int temp0L = ((mid0 + side0) >> 1); + int temp1L = ((mid1 + side1) >> 1); + int temp2L = ((mid2 + side2) >> 1); + int temp3L = ((mid3 + side3) >> 1); + + int temp0R = ((mid0 - side0) >> 1); + int temp1R = ((mid1 - side1) >> 1); + int temp2R = ((mid2 - side2) >> 1); + int temp3R = ((mid3 - side3) >> 1); + + pOutputSamples[i*8+0] = (float)(temp0L * factor); + pOutputSamples[i*8+1] = (float)(temp0R * factor); + pOutputSamples[i*8+2] = (float)(temp1L * factor); + pOutputSamples[i*8+3] = (float)(temp1R * factor); + pOutputSamples[i*8+4] = (float)(temp2L * factor); + pOutputSamples[i*8+5] = (float)(temp2R * factor); + pOutputSamples[i*8+6] = (float)(temp3L * factor); + pOutputSamples[i*8+7] = (float)(temp3R * factor); + } + } + + for (drflac_uint64 i = (frameCount4 << 2); i < frameCount; ++i) { + int mid = pInputSamples0[i] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + int side = pInputSamples1[i] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + + mid = (((drflac_uint32)mid) << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (float)((((mid + side) >> 1) << unusedBitsPerSample) * factor); + pOutputSamples[i*2+1] = (float)((((mid - side) >> 1) << unusedBitsPerSample) * factor); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_assert(pFlac->bitsPerSample <= 24); + + drflac_uint64 frameCount4 = frameCount >> 2; + + float factor = 1.0f / 8388608.0f; + __m128 factor128 = _mm_set1_ps(1.0f / 8388608.0f); + + int shift = unusedBitsPerSample - 8; + if (shift == 0) { + for (drflac_uint64 i = 0; i < frameCount4; ++i) { + __m128i inputSample0 = _mm_loadu_si128((const __m128i*)pInputSamples0 + i); + __m128i inputSample1 = _mm_loadu_si128((const __m128i*)pInputSamples1 + i); + + __m128i mid = _mm_slli_epi32(inputSample0, pFlac->currentFrame.subframes[0].wastedBitsPerSample); + __m128i side = _mm_slli_epi32(inputSample1, pFlac->currentFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + __m128i tempL = _mm_add_epi32(mid, side); + __m128i tempR = _mm_sub_epi32(mid, side); + + // Signed bit shift. + tempL = _mm_or_si128(_mm_srli_epi32(tempL, 1), _mm_and_si128(tempL, _mm_set1_epi32(0x80000000))); + tempR = _mm_or_si128(_mm_srli_epi32(tempR, 1), _mm_and_si128(tempR, _mm_set1_epi32(0x80000000))); + + __m128 leftf = _mm_mul_ps(_mm_cvtepi32_ps(tempL), factor128); + __m128 rightf = _mm_mul_ps(_mm_cvtepi32_ps(tempR), factor128); + + pOutputSamples[i*8+0] = ((float*)&leftf)[0]; + pOutputSamples[i*8+1] = ((float*)&rightf)[0]; + pOutputSamples[i*8+2] = ((float*)&leftf)[1]; + pOutputSamples[i*8+3] = ((float*)&rightf)[1]; + pOutputSamples[i*8+4] = ((float*)&leftf)[2]; + pOutputSamples[i*8+5] = ((float*)&rightf)[2]; + pOutputSamples[i*8+6] = ((float*)&leftf)[3]; + pOutputSamples[i*8+7] = ((float*)&rightf)[3]; + } + + for (drflac_uint64 i = (frameCount4 << 2); i < frameCount; ++i) { + int mid = pInputSamples0[i] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + int side = pInputSamples1[i] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + + mid = (((drflac_uint32)mid) << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (float)(((mid + side) >> 1) * factor); + pOutputSamples[i*2+1] = (float)(((mid - side) >> 1) * factor); + } + } else { + for (drflac_uint64 i = 0; i < frameCount4; ++i) { + __m128i inputSample0 = _mm_loadu_si128((const __m128i*)pInputSamples0 + i); + __m128i inputSample1 = _mm_loadu_si128((const __m128i*)pInputSamples1 + i); + + __m128i mid = _mm_slli_epi32(inputSample0, pFlac->currentFrame.subframes[0].wastedBitsPerSample); + __m128i side = _mm_slli_epi32(inputSample1, pFlac->currentFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + __m128i tempL = _mm_slli_epi32(_mm_srli_epi32(_mm_add_epi32(mid, side), 1), shift); + __m128i tempR = _mm_slli_epi32(_mm_srli_epi32(_mm_sub_epi32(mid, side), 1), shift); + + __m128 leftf = _mm_mul_ps(_mm_cvtepi32_ps(tempL), factor128); + __m128 rightf = _mm_mul_ps(_mm_cvtepi32_ps(tempR), factor128); + + pOutputSamples[i*8+0] = ((float*)&leftf)[0]; + pOutputSamples[i*8+1] = ((float*)&rightf)[0]; + pOutputSamples[i*8+2] = ((float*)&leftf)[1]; + pOutputSamples[i*8+3] = ((float*)&rightf)[1]; + pOutputSamples[i*8+4] = ((float*)&leftf)[2]; + pOutputSamples[i*8+5] = ((float*)&rightf)[2]; + pOutputSamples[i*8+6] = ((float*)&leftf)[3]; + pOutputSamples[i*8+7] = ((float*)&rightf)[3]; + } + + for (drflac_uint64 i = (frameCount4 << 2); i < frameCount; ++i) { + int mid = pInputSamples0[i] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + int side = pInputSamples1[i] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + + mid = (((drflac_uint32)mid) << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (float)((((mid + side) >> 1) << shift) * factor); + pOutputSamples[i*2+1] = (float)((((mid - side) >> 1) << shift) * factor); + } + } +} +#endif + + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + // Scalar fallback. +#if 0 + drflac_read_pcm_frames_f32__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + pOutputSamples[i*2+0] = (float)((pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample)) / 2147483648.0); + pOutputSamples[i*2+1] = (float)((pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample)) / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 frameCount4 = frameCount >> 2; + + float factor = 1 / 2147483648.0; + + int shift0 = (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); + int shift1 = (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); + + for (drflac_uint64 i = 0; i < frameCount4; ++i) { + int tempL0 = pInputSamples0[i*4+0] << shift0; + int tempL1 = pInputSamples0[i*4+1] << shift0; + int tempL2 = pInputSamples0[i*4+2] << shift0; + int tempL3 = pInputSamples0[i*4+3] << shift0; + + int tempR0 = pInputSamples1[i*4+0] << shift1; + int tempR1 = pInputSamples1[i*4+1] << shift1; + int tempR2 = pInputSamples1[i*4+2] << shift1; + int tempR3 = pInputSamples1[i*4+3] << shift1; + + pOutputSamples[i*8+0] = (float)(tempL0 * factor); + pOutputSamples[i*8+1] = (float)(tempR0 * factor); + pOutputSamples[i*8+2] = (float)(tempL1 * factor); + pOutputSamples[i*8+3] = (float)(tempR1 * factor); + pOutputSamples[i*8+4] = (float)(tempL2 * factor); + pOutputSamples[i*8+5] = (float)(tempR2 * factor); + pOutputSamples[i*8+6] = (float)(tempL3 * factor); + pOutputSamples[i*8+7] = (float)(tempR3 * factor); + } + + for (drflac_uint64 i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (float)((pInputSamples0[i] << shift0) * factor); + pOutputSamples[i*2+1] = (float)((pInputSamples1[i] << shift1) * factor); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 frameCount4 = frameCount >> 2; + + float factor = 1.0f / 8388608.0f; + __m128 factor128 = _mm_set1_ps(1.0f / 8388608.0f); + + int shift0 = (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample) - 8; + int shift1 = (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample) - 8; + + for (drflac_uint64 i = 0; i < frameCount4; ++i) { + __m128i inputSample0 = _mm_loadu_si128((const __m128i*)pInputSamples0 + i); + __m128i inputSample1 = _mm_loadu_si128((const __m128i*)pInputSamples1 + i); + + __m128i i32L = _mm_slli_epi32(inputSample0, shift0); + __m128i i32R = _mm_slli_epi32(inputSample1, shift1); + + __m128 f32L = _mm_mul_ps(_mm_cvtepi32_ps(i32L), factor128); + __m128 f32R = _mm_mul_ps(_mm_cvtepi32_ps(i32R), factor128); + + pOutputSamples[i*8+0] = ((float*)&f32L)[0]; + pOutputSamples[i*8+1] = ((float*)&f32R)[0]; + pOutputSamples[i*8+2] = ((float*)&f32L)[1]; + pOutputSamples[i*8+3] = ((float*)&f32R)[1]; + pOutputSamples[i*8+4] = ((float*)&f32L)[2]; + pOutputSamples[i*8+5] = ((float*)&f32R)[2]; + pOutputSamples[i*8+6] = ((float*)&f32L)[3]; + pOutputSamples[i*8+7] = ((float*)&f32R)[3]; + } + + for (drflac_uint64 i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (float)((pInputSamples0[i] << shift0) * factor); + pOutputSamples[i*2+1] = (float)((pInputSamples1[i] << shift1) * factor); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + // Scalar fallback. +#if 0 + drflac_read_pcm_frames_f32__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_f32__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + +drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut) +{ + if (pFlac == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); + } + + drflac_uint64 framesRead = 0; + while (framesToRead > 0) { + // If we've run out of samples in this frame, go to the next. + if (pFlac->currentFrame.samplesRemaining == 0) { + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + break; // Couldn't read the next frame, so just break from the loop and return. + } + } else { + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + drflac_uint64 totalFramesInPacket = pFlac->currentFrame.header.blockSize; + drflac_uint64 framesReadFromPacketSoFar = totalFramesInPacket - (pFlac->currentFrame.samplesRemaining/channelCount); + drflac_uint64 iFirstPCMFrame = framesReadFromPacketSoFar; + drflac_int32 unusedBitsPerSample = 32 - pFlac->bitsPerSample; + + drflac_uint64 frameCountThisIteration = framesToRead; + if (frameCountThisIteration > pFlac->currentFrame.samplesRemaining / channelCount) { + frameCountThisIteration = pFlac->currentFrame.samplesRemaining / channelCount; + } + + if (channelCount == 2) { + const drflac_int32* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + iFirstPCMFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + iFirstPCMFrame; + + switch (pFlac->currentFrame.header.channelAssignment) + { + case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: + { + drflac_read_pcm_frames_f32__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: + { + drflac_read_pcm_frames_f32__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: + { + drflac_read_pcm_frames_f32__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: + default: + { + drflac_read_pcm_frames_f32__decode_independent_stereo(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + } + } else { + // Generic interleaving. + for (drflac_uint64 i = 0; i < frameCountThisIteration; ++i) { + for (unsigned int j = 0; j < channelCount; ++j) { + pBufferOut[(i*channelCount)+j] = (float)(((pFlac->currentFrame.subframes[j].pDecodedSamples[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFrame.subframes[j].wastedBitsPerSample)) / 2147483648.0); + } + } + } + + drflac_uint64 samplesReadThisIteration = frameCountThisIteration * channelCount; + framesRead += frameCountThisIteration; + framesReadFromPacketSoFar += frameCountThisIteration; + pBufferOut += samplesReadThisIteration; + framesToRead -= frameCountThisIteration; + pFlac->currentSample += samplesReadThisIteration; + pFlac->currentFrame.samplesRemaining -= (unsigned int)samplesReadThisIteration; + } + } + + return framesRead; +} + +drflac_bool32 drflac_seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex) +{ + if (pFlac == NULL) { + return DRFLAC_FALSE; + } + + // If we don't know where the first frame begins then we can't seek. This will happen when the STREAMINFO block was not present + // when the decoder was opened. + if (pFlac->firstFramePos == 0) { + return DRFLAC_FALSE; + } + + if (sampleIndex == 0) { + pFlac->currentSample = 0; + return drflac__seek_to_first_frame(pFlac); + } else { + drflac_bool32 wasSuccessful = DRFLAC_FALSE; + + // Clamp the sample to the end. + if (sampleIndex >= pFlac->totalSampleCount) { + sampleIndex = pFlac->totalSampleCount - 1; + } + + // If the target sample and the current sample are in the same frame we just move the position forward. + if (sampleIndex > pFlac->currentSample) { + // Forward. + drflac_uint32 offset = (drflac_uint32)(sampleIndex - pFlac->currentSample); + if (pFlac->currentFrame.samplesRemaining > offset) { + pFlac->currentFrame.samplesRemaining -= offset; + pFlac->currentSample = sampleIndex; + return DRFLAC_TRUE; + } + } else { + // Backward. + drflac_uint32 offsetAbs = (drflac_uint32)(pFlac->currentSample - sampleIndex); + drflac_uint32 currentFrameSampleCount = pFlac->currentFrame.header.blockSize * drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + drflac_uint32 currentFrameSamplesConsumed = (drflac_uint32)(currentFrameSampleCount - pFlac->currentFrame.samplesRemaining); + if (currentFrameSamplesConsumed > offsetAbs) { + pFlac->currentFrame.samplesRemaining += offsetAbs; + pFlac->currentSample = sampleIndex; + return DRFLAC_TRUE; + } + } + + // Different techniques depending on encapsulation. Using the native FLAC seektable with Ogg encapsulation is a bit awkward so + // we'll instead use Ogg's natural seeking facility. +#ifndef DR_FLAC_NO_OGG + if (pFlac->container == drflac_container_ogg) + { + wasSuccessful = drflac_ogg__seek_to_sample(pFlac, sampleIndex); + } + else +#endif + { + // First try seeking via the seek table. If this fails, fall back to a brute force seek which is much slower. + wasSuccessful = drflac__seek_to_sample__seek_table(pFlac, sampleIndex); + if (!wasSuccessful) { + wasSuccessful = drflac__seek_to_sample__brute_force(pFlac, sampleIndex); + } + } + + pFlac->currentSample = sampleIndex; + return wasSuccessful; + } +} + +drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex) +{ + if (pFlac == NULL) { + return DRFLAC_FALSE; + } + + // If we don't know where the first frame begins then we can't seek. This will happen when the STREAMINFO block was not present + // when the decoder was opened. + if (pFlac->firstFramePos == 0) { + return DRFLAC_FALSE; + } + + if (pcmFrameIndex == 0) { + pFlac->currentSample = 0; + return drflac__seek_to_first_frame(pFlac); + } else { + drflac_bool32 wasSuccessful = DRFLAC_FALSE; + + // Clamp the sample to the end. + if (pcmFrameIndex >= pFlac->totalPCMFrameCount) { + pcmFrameIndex = pFlac->totalPCMFrameCount - 1; + } + + // If the target sample and the current sample are in the same frame we just move the position forward. + if (pcmFrameIndex*pFlac->channels > pFlac->currentSample) { + // Forward. + drflac_uint32 offset = (drflac_uint32)(pcmFrameIndex*pFlac->channels - pFlac->currentSample); + if (pFlac->currentFrame.samplesRemaining > offset) { + pFlac->currentFrame.samplesRemaining -= offset; + pFlac->currentSample = pcmFrameIndex*pFlac->channels; + return DRFLAC_TRUE; + } + } else { + // Backward. + drflac_uint32 offsetAbs = (drflac_uint32)(pFlac->currentSample - pcmFrameIndex*pFlac->channels); + drflac_uint32 currentFrameSampleCount = pFlac->currentFrame.header.blockSize * drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + drflac_uint32 currentFrameSamplesConsumed = (drflac_uint32)(currentFrameSampleCount - pFlac->currentFrame.samplesRemaining); + if (currentFrameSamplesConsumed > offsetAbs) { + pFlac->currentFrame.samplesRemaining += offsetAbs; + pFlac->currentSample = pcmFrameIndex*pFlac->channels; + return DRFLAC_TRUE; + } + } + + // Different techniques depending on encapsulation. Using the native FLAC seektable with Ogg encapsulation is a bit awkward so + // we'll instead use Ogg's natural seeking facility. +#ifndef DR_FLAC_NO_OGG + if (pFlac->container == drflac_container_ogg) + { + wasSuccessful = drflac_ogg__seek_to_sample(pFlac, pcmFrameIndex*pFlac->channels); + } + else +#endif + { + // First try seeking via the seek table. If this fails, fall back to a brute force seek which is much slower. + wasSuccessful = drflac__seek_to_sample__seek_table(pFlac, pcmFrameIndex*pFlac->channels); + if (!wasSuccessful) { + wasSuccessful = drflac__seek_to_sample__brute_force(pFlac, pcmFrameIndex*pFlac->channels); + } + } + + pFlac->currentSample = pcmFrameIndex*pFlac->channels; + return wasSuccessful; + } +} + + + +//// High Level APIs //// + +#if defined(SIZE_MAX) + #define DRFLAC_SIZE_MAX SIZE_MAX +#else + #if defined(DRFLAC_64BIT) + #define DRFLAC_SIZE_MAX ((drflac_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRFLAC_SIZE_MAX 0xFFFFFFFF + #endif +#endif + + +// Using a macro as the definition of the drflac__full_decode_and_close_*() API family. Sue me. +#define DRFLAC_DEFINE_FULL_READ_AND_CLOSE(extension, type) \ +static type* drflac__full_read_and_close_ ## extension (drflac* pFlac, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut)\ +{ \ + drflac_assert(pFlac != NULL); \ + \ + type* pSampleData = NULL; \ + drflac_uint64 totalPCMFrameCount = pFlac->totalPCMFrameCount; \ + \ + if (totalPCMFrameCount == 0) { \ + type buffer[4096]; \ + \ + size_t sampleDataBufferSize = sizeof(buffer); \ + pSampleData = (type*)DRFLAC_MALLOC(sampleDataBufferSize); \ + if (pSampleData == NULL) { \ + goto on_error; \ + } \ + \ + drflac_uint64 pcmFramesRead; \ + while ((pcmFramesRead = (drflac_uint64)drflac_read_pcm_frames_##extension(pFlac, sizeof(buffer)/sizeof(buffer[0])/pFlac->channels, buffer)) > 0) { \ + if (((totalPCMFrameCount + pcmFramesRead) * pFlac->channels * sizeof(type)) > sampleDataBufferSize) { \ + sampleDataBufferSize *= 2; \ + type* pNewSampleData = (type*)DRFLAC_REALLOC(pSampleData, sampleDataBufferSize); \ + if (pNewSampleData == NULL) { \ + DRFLAC_FREE(pSampleData); \ + goto on_error; \ + } \ + \ + pSampleData = pNewSampleData; \ + } \ + \ + drflac_copy_memory(pSampleData + (totalPCMFrameCount*pFlac->channels), buffer, (size_t)(pcmFramesRead*pFlac->channels*sizeof(type))); \ + totalPCMFrameCount += pcmFramesRead; \ + } \ + \ + /* At this point everything should be decoded, but we just want to fill the unused part buffer with silence - need to \ + protect those ears from random noise! */ \ + drflac_zero_memory(pSampleData + (totalPCMFrameCount*pFlac->channels), (size_t)(sampleDataBufferSize - totalPCMFrameCount*pFlac->channels*sizeof(type))); \ + } else { \ + drflac_uint64 dataSize = totalPCMFrameCount*pFlac->channels*sizeof(type); \ + if (dataSize > DRFLAC_SIZE_MAX) { \ + goto on_error; /* The decoded data is too big. */ \ + } \ + \ + pSampleData = (type*)DRFLAC_MALLOC((size_t)dataSize); /* <-- Safe cast as per the check above. */ \ + if (pSampleData == NULL) { \ + goto on_error; \ + } \ + \ + totalPCMFrameCount = drflac_read_pcm_frames_##extension(pFlac, pFlac->totalPCMFrameCount, pSampleData); \ + } \ + \ + if (sampleRateOut) *sampleRateOut = pFlac->sampleRate; \ + if (channelsOut) *channelsOut = pFlac->channels; \ + if (totalPCMFrameCountOut) *totalPCMFrameCountOut = totalPCMFrameCount; \ + \ + drflac_close(pFlac); \ + return pSampleData; \ + \ +on_error: \ + drflac_close(pFlac); \ + return NULL; \ +} + +DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s32, drflac_int32) +DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s16, drflac_int16) +DRFLAC_DEFINE_FULL_READ_AND_CLOSE(f32, float) + +drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut) +{ + // Safety. + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalPCMFrameCountOut) *totalPCMFrameCountOut = 0; + + drflac* pFlac = drflac_open(onRead, onSeek, pUserData); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); +} + +drflac_int32* drflac_open_and_decode_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) +{ + // Safety. + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalSampleCountOut) *totalSampleCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + + drflac_int32* pResult = drflac_open_and_read_pcm_frames_s32(onRead, onSeek, pUserData, &channels, &sampleRate, &totalPCMFrameCount); + if (pResult == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalSampleCountOut) *totalSampleCountOut = totalPCMFrameCount * channels; + + return pResult; +} + + + +drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut) +{ + // Safety. + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalPCMFrameCountOut) *totalPCMFrameCountOut = 0; + + drflac* pFlac = drflac_open(onRead, onSeek, pUserData); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s16(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); +} + +drflac_int16* drflac_open_and_decode_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) +{ + // Safety. + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalSampleCountOut) *totalSampleCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + + drflac_int16* pResult = drflac_open_and_read_pcm_frames_s16(onRead, onSeek, pUserData, &channels, &sampleRate, &totalPCMFrameCount); + if (pResult == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalSampleCountOut) *totalSampleCountOut = totalPCMFrameCount * channels; + + return pResult; +} + + +float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut) +{ + // Safety. + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalPCMFrameCountOut) *totalPCMFrameCountOut = 0; + + drflac* pFlac = drflac_open(onRead, onSeek, pUserData); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_f32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); +} + +float* drflac_open_and_decode_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) +{ + // Safety. + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalSampleCountOut) *totalSampleCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + + float* pResult = drflac_open_and_read_pcm_frames_f32(onRead, onSeek, pUserData, &channels, &sampleRate, &totalPCMFrameCount); + if (pResult == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalSampleCountOut) *totalSampleCountOut = totalPCMFrameCount * channels; + + return pResult; +} + +#ifndef DR_FLAC_NO_STDIO +drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalPCMFrameCount) *totalPCMFrameCount = 0; + + drflac* pFlac = drflac_open_file(filename); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s32(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +drflac_int32* drflac_open_and_decode_file_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) +{ + // Safety. + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalSampleCountOut) *totalSampleCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + + drflac_int32* pResult = drflac_open_file_and_read_pcm_frames_s32(filename, &channels, &sampleRate, &totalPCMFrameCount); + if (pResult == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalSampleCountOut) *totalSampleCountOut = totalPCMFrameCount * channels; + + return pResult; +} + + +drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalPCMFrameCount) *totalPCMFrameCount = 0; + + drflac* pFlac = drflac_open_file(filename); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s16(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +drflac_int16* drflac_open_and_decode_file_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) +{ + // Safety. + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalSampleCountOut) *totalSampleCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + + drflac_int16* pResult = drflac_open_file_and_read_pcm_frames_s16(filename, &channels, &sampleRate, &totalPCMFrameCount); + if (pResult == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalSampleCountOut) *totalSampleCountOut = totalPCMFrameCount * channels; + + return pResult; +} + + +float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalPCMFrameCount) *totalPCMFrameCount = 0; + + drflac* pFlac = drflac_open_file(filename); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_f32(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +float* drflac_open_and_decode_file_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) +{ + // Safety. + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalSampleCountOut) *totalSampleCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + + float* pResult = drflac_open_file_and_read_pcm_frames_f32(filename, &channels, &sampleRate, &totalPCMFrameCount); + if (pResult == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalSampleCountOut) *totalSampleCountOut = totalPCMFrameCount * channels; + + return pResult; +} +#endif + +drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalPCMFrameCount) *totalPCMFrameCount = 0; + + drflac* pFlac = drflac_open_memory(data, dataSize); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s32(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +drflac_int32* drflac_open_and_decode_memory_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) +{ + // Safety. + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalSampleCountOut) *totalSampleCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + + drflac_int32* pResult = drflac_open_memory_and_read_pcm_frames_s32(data, dataSize, &channels, &sampleRate, &totalPCMFrameCount); + if (pResult == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalSampleCountOut) *totalSampleCountOut = totalPCMFrameCount * channels; + + return pResult; +} + + +drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalPCMFrameCount) *totalPCMFrameCount = 0; + + drflac* pFlac = drflac_open_memory(data, dataSize); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_s16(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +drflac_int16* drflac_open_and_decode_memory_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) +{ + // Safety. + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalSampleCountOut) *totalSampleCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + + drflac_int16* pResult = drflac_open_memory_and_read_pcm_frames_s16(data, dataSize, &channels, &sampleRate, &totalPCMFrameCount); + if (pResult == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalSampleCountOut) *totalSampleCountOut = totalPCMFrameCount * channels; + + return pResult; +} + + +float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalPCMFrameCount) *totalPCMFrameCount = 0; + + drflac* pFlac = drflac_open_memory(data, dataSize); + if (pFlac == NULL) { + return NULL; + } + + return drflac__full_read_and_close_f32(pFlac, channels, sampleRate, totalPCMFrameCount); +} + +float* drflac_open_and_decode_memory_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) +{ + // Safety. + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalSampleCountOut) *totalSampleCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drflac_uint64 totalPCMFrameCount; + + float* pResult = drflac_open_memory_and_read_pcm_frames_f32(data, dataSize, &channels, &sampleRate, &totalPCMFrameCount); + if (pResult == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalSampleCountOut) *totalSampleCountOut = totalPCMFrameCount * channels; + + return pResult; +} + + +void drflac_free(void* pSampleDataReturnedByOpenAndDecode) +{ + DRFLAC_FREE(pSampleDataReturnedByOpenAndDecode); +} + + + + +void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments) +{ + if (pIter == NULL) { + return; + } + + pIter->countRemaining = commentCount; + pIter->pRunningData = (const char*)pComments; +} + +const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut) +{ + // Safety. + if (pCommentLengthOut) *pCommentLengthOut = 0; + + if (pIter == NULL || pIter->countRemaining == 0 || pIter->pRunningData == NULL) { + return NULL; + } + + drflac_uint32 length = drflac__le2host_32(*(const drflac_uint32*)pIter->pRunningData); + pIter->pRunningData += 4; + + const char* pComment = pIter->pRunningData; + pIter->pRunningData += length; + pIter->countRemaining -= 1; + + if (pCommentLengthOut) *pCommentLengthOut = length; + return pComment; +} + + + + +void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData) +{ + if (pIter == NULL) { + return; + } + + pIter->countRemaining = trackCount; + pIter->pRunningData = (const char*)pTrackData; +} + +drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack) +{ + if (pIter == NULL || pIter->countRemaining == 0 || pIter->pRunningData == NULL) { + return DRFLAC_FALSE; + } + + drflac_cuesheet_track cuesheetTrack; + + const char* pRunningData = pIter->pRunningData; + + drflac_uint64 offsetHi = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + drflac_uint64 offsetLo = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + cuesheetTrack.offset = offsetLo | (offsetHi << 32); + cuesheetTrack.trackNumber = pRunningData[0]; pRunningData += 1; + drflac_copy_memory(cuesheetTrack.ISRC, pRunningData, sizeof(cuesheetTrack.ISRC)); pRunningData += 12; + cuesheetTrack.isAudio = (pRunningData[0] & 0x80) != 0; + cuesheetTrack.preEmphasis = (pRunningData[0] & 0x40) != 0; pRunningData += 14; + cuesheetTrack.indexCount = pRunningData[0]; pRunningData += 1; + cuesheetTrack.pIndexPoints = (const drflac_cuesheet_track_index*)pRunningData; pRunningData += cuesheetTrack.indexCount * sizeof(drflac_cuesheet_track_index); + + pIter->pRunningData = pRunningData; + pIter->countRemaining -= 1; + + if (pCuesheetTrack) *pCuesheetTrack = cuesheetTrack; + return DRFLAC_TRUE; +} +#endif //DR_FLAC_IMPLEMENTATION + + +// REVISION HISTORY +// +// v0.10.0 - 2018-09-11 +// - Remove the DR_FLAC_NO_WIN32_IO option and the Win32 file IO functionality. If you need to use Win32 file IO you +// need to do it yourself via the callback API. +// - Fix the clang build. +// - Fix undefined behavior. +// - Fix errors with CUESHEET metdata blocks. +// - Add an API for iterating over each cuesheet track in the CUESHEET metadata block. This works the same way as the +// Vorbis comment API. +// - Other miscellaneous bug fixes, mostly relating to invalid FLAC streams. +// - Minor optimizations. +// +// v0.9.11 - 2018-08-29 +// - Fix a bug with sample reconstruction. +// +// v0.9.10 - 2018-08-07 +// - Improve 64-bit detection. +// +// v0.9.9 - 2018-08-05 +// - Fix C++ build on older versions of GCC. +// +// v0.9.8 - 2018-07-24 +// - Fix compilation errors. +// +// v0.9.7 - 2018-07-05 +// - Fix a warning. +// +// v0.9.6 - 2018-06-29 +// - Fix some typos. +// +// v0.9.5 - 2018-06-23 +// - Fix some warnings. +// +// v0.9.4 - 2018-06-14 +// - Optimizations to seeking. +// - Clean up. +// +// v0.9.3 - 2018-05-22 +// - Bug fix. +// +// v0.9.2 - 2018-05-12 +// - Fix a compilation error due to a missing break statement. +// +// v0.9.1 - 2018-04-29 +// - Fix compilation error with Clang. +// +// v0.9 - 2018-04-24 +// - Fix Clang build. +// - Start using major.minor.revision versioning. +// +// v0.8g - 2018-04-19 +// - Fix build on non-x86/x64 architectures. +// +// v0.8f - 2018-02-02 +// - Stop pretending to support changing rate/channels mid stream. +// +// v0.8e - 2018-02-01 +// - Fix a crash when the block size of a frame is larger than the maximum block size defined by the FLAC stream. +// - Fix a crash the the Rice partition order is invalid. +// +// v0.8d - 2017-09-22 +// - Add support for decoding streams with ID3 tags. ID3 tags are just skipped. +// +// v0.8c - 2017-09-07 +// - Fix warning on non-x86/x64 architectures. +// +// v0.8b - 2017-08-19 +// - Fix build on non-x86/x64 architectures. +// +// v0.8a - 2017-08-13 +// - A small optimization for the Clang build. +// +// v0.8 - 2017-08-12 +// - API CHANGE: Rename dr_* types to drflac_*. +// - Optimizations. This brings dr_flac back to about the same class of efficiency as the reference implementation. +// - Add support for custom implementations of malloc(), realloc(), etc. +// - Add CRC checking to Ogg encapsulated streams. +// - Fix VC++ 6 build. This is only for the C++ compiler. The C compiler is not currently supported. +// - Bug fixes. +// +// v0.7 - 2017-07-23 +// - Add support for opening a stream without a header block. To do this, use drflac_open_relaxed() / drflac_open_with_metadata_relaxed(). +// +// v0.6 - 2017-07-22 +// - Add support for recovering from invalid frames. With this change, dr_flac will simply skip over invalid frames as if they +// never existed. Frames are checked against their sync code, the CRC-8 of the frame header and the CRC-16 of the whole frame. +// +// v0.5 - 2017-07-16 +// - Fix typos. +// - Change drflac_bool* types to unsigned. +// - Add CRC checking. This makes dr_flac slower, but can be disabled with #define DR_FLAC_NO_CRC. +// +// v0.4f - 2017-03-10 +// - Fix a couple of bugs with the bitstreaming code. +// +// v0.4e - 2017-02-17 +// - Fix some warnings. +// +// v0.4d - 2016-12-26 +// - Add support for 32-bit floating-point PCM decoding. +// - Use drflac_int*/drflac_uint* sized types to improve compiler support. +// - Minor improvements to documentation. +// +// v0.4c - 2016-12-26 +// - Add support for signed 16-bit integer PCM decoding. +// +// v0.4b - 2016-10-23 +// - A minor change to drflac_bool8 and drflac_bool32 types. +// +// v0.4a - 2016-10-11 +// - Rename drBool32 to drflac_bool32 for styling consistency. +// +// v0.4 - 2016-09-29 +// - API/ABI CHANGE: Use fixed size 32-bit booleans instead of the built-in bool type. +// - API CHANGE: Rename drflac_open_and_decode*() to drflac_open_and_decode*_s32(). +// - API CHANGE: Swap the order of "channels" and "sampleRate" parameters in drflac_open_and_decode*(). Rationale for this is to +// keep it consistent with drflac_audio. +// +// v0.3f - 2016-09-21 +// - Fix a warning with GCC. +// +// v0.3e - 2016-09-18 +// - Fixed a bug where GCC 4.3+ was not getting properly identified. +// - Fixed a few typos. +// - Changed date formats to ISO 8601 (YYYY-MM-DD). +// +// v0.3d - 2016-06-11 +// - Minor clean up. +// +// v0.3c - 2016-05-28 +// - Fixed compilation error. +// +// v0.3b - 2016-05-16 +// - Fixed Linux/GCC build. +// - Updated documentation. +// +// v0.3a - 2016-05-15 +// - Minor fixes to documentation. +// +// v0.3 - 2016-05-11 +// - Optimizations. Now at about parity with the reference implementation on 32-bit builds. +// - Lots of clean up. +// +// v0.2b - 2016-05-10 +// - Bug fixes. +// +// v0.2a - 2016-05-10 +// - Made drflac_open_and_decode() more robust. +// - Removed an unused debugging variable +// +// v0.2 - 2016-05-09 +// - Added support for Ogg encapsulation. +// - API CHANGE. Have the onSeek callback take a third argument which specifies whether or not the seek +// should be relative to the start or the current position. Also changes the seeking rules such that +// seeking offsets will never be negative. +// - Have drflac_open_and_decode() fail gracefully if the stream has an unknown total sample count. +// +// v0.1b - 2016-05-07 +// - Properly close the file handle in drflac_open_file() and family when the decoder fails to initialize. +// - Removed a stale comment. +// +// v0.1a - 2016-05-05 +// - Minor formatting changes. +// - Fixed a warning on the GCC build. +// +// v0.1 - 2016-05-03 +// - Initial versioned release. + + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/src/soloud/src/audiosource/wav/dr_impl.cpp b/src/soloud/src/audiosource/wav/dr_impl.cpp new file mode 100644 index 0000000..fa152e1 --- /dev/null +++ b/src/soloud/src/audiosource/wav/dr_impl.cpp @@ -0,0 +1,13 @@ +#define DR_MP3_IMPLEMENTATION +#define DR_MP3_NO_STDIO +#define DR_MP3_FLOAT_OUTPUT +#include "dr_mp3.h" + +#define DR_WAV_IMPLEMENTATION +#define DR_WAV_NO_STDIO +#include "dr_wav.h" + +#define DR_FLAC_IMPLEMENTATION +#define DR_FLAC_NO_STDIO +#define DR_FLAC_NO_CRC +#include "dr_flac.h" diff --git a/src/soloud/src/audiosource/wav/dr_mp3.h b/src/soloud/src/audiosource/wav/dr_mp3.h new file mode 100644 index 0000000..070f0c1 --- /dev/null +++ b/src/soloud/src/audiosource/wav/dr_mp3.h @@ -0,0 +1,3200 @@ +// MP3 audio decoder. Public domain. See "unlicense" statement at the end of this file. +// dr_mp3 - v0.4.0 - 2018-xx-xx +// +// David Reid - mackron@gmail.com +// +// Based off minimp3 (https://github.com/lieff/minimp3) which is where the real work was done. See the bottom of this file for +// differences between minimp3 and dr_mp3. + +// USAGE +// ===== +// dr_mp3 is a single-file library. To use it, do something like the following in one .c file. +// #define DR_MP3_IMPLEMENTATION +// #include "dr_mp3.h" +// +// You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, +// do something like the following: +// +// drmp3 mp3; +// if (!drmp3_init_file(&mp3, "MySong.mp3", NULL)) { +// // Failed to open file +// } +// +// ... +// +// drmp3_uint64 framesRead = drmp3_read_f32(pMP3, framesToRead, pFrames); +// +// The drmp3 object is transparent so you can get access to the channel count and sample rate like so: +// +// drmp3_uint32 channels = mp3.channels; +// drmp3_uint32 sampleRate = mp3.sampleRate; +// +// The third parameter of drmp3_init_file() in the example above allows you to control the output channel count and sample rate. It +// is a pointer to a drmp3_config object. Setting any of the variables of this object to 0 will cause dr_mp3 to use defaults. +// +// The example above initializes a decoder from a file, but you can also initialize it from a block of memory and read and seek +// callbacks with drmp3_init_memory() and drmp3_init() respectively. +// +// You do need to do any annoying memory management when reading PCM frames - this is all managed internally. You can request +// any number of PCM frames in each call to drmp3_read_f32() and it will return as many PCM frames as it can, up to the requested +// amount. +// +// You can also decode an entire file in one go with drmp3_open_and_decode_f32(), drmp3_open_and_decode_memory_f32() and +// drmp3_open_and_decode_file_f32(). +// +// +// OPTIONS +// ======= +// #define these options before including this file. +// +// #define DR_MP3_NO_STDIO +// Disable drmp3_init_file(), etc. +// +// #define DR_MP3_NO_SIMD +// Disable SIMD optimizations. + +#ifndef dr_mp3_h +#define dr_mp3_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#if defined(_MSC_VER) && _MSC_VER < 1600 +typedef signed char drmp3_int8; +typedef unsigned char drmp3_uint8; +typedef signed short drmp3_int16; +typedef unsigned short drmp3_uint16; +typedef signed int drmp3_int32; +typedef unsigned int drmp3_uint32; +typedef signed __int64 drmp3_int64; +typedef unsigned __int64 drmp3_uint64; +#else +#include +typedef int8_t drmp3_int8; +typedef uint8_t drmp3_uint8; +typedef int16_t drmp3_int16; +typedef uint16_t drmp3_uint16; +typedef int32_t drmp3_int32; +typedef uint32_t drmp3_uint32; +typedef int64_t drmp3_int64; +typedef uint64_t drmp3_uint64; +#endif +typedef drmp3_uint8 drmp3_bool8; +typedef drmp3_uint32 drmp3_bool32; +#define DRMP3_TRUE 1 +#define DRMP3_FALSE 0 + +#define DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 +#define DRMP3_MAX_SAMPLES_PER_FRAME (DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME*2) + + +// Low Level Push API +// ================== +typedef struct +{ + int frame_bytes, channels, hz, layer, bitrate_kbps; +} drmp3dec_frame_info; + +typedef struct +{ + float mdct_overlap[2][9*32], qmf_state[15*2*32]; + int reserv, free_format_bytes; + unsigned char header[4], reserv_buf[511]; +} drmp3dec; + +// Initializes a low level decoder. +void drmp3dec_init(drmp3dec *dec); + +// Reads a frame from a low level decoder. +int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info); + +// Helper for converting between f32 and s16. +void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples); + + + + +// Main API (Pull API) +// =================== + +typedef struct drmp3_src drmp3_src; +typedef drmp3_uint64 (* drmp3_src_read_proc)(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData); // Returns the number of frames that were read. + +typedef enum +{ + drmp3_src_algorithm_none, + drmp3_src_algorithm_linear +} drmp3_src_algorithm; + +#define DRMP3_SRC_CACHE_SIZE_IN_FRAMES 512 +typedef struct +{ + drmp3_src* pSRC; + float pCachedFrames[2 * DRMP3_SRC_CACHE_SIZE_IN_FRAMES]; + drmp3_uint32 cachedFrameCount; + drmp3_uint32 iNextFrame; +} drmp3_src_cache; + +typedef struct +{ + drmp3_uint32 sampleRateIn; + drmp3_uint32 sampleRateOut; + drmp3_uint32 channels; + drmp3_src_algorithm algorithm; + drmp3_uint32 cacheSizeInFrames; // The number of frames to read from the client at a time. +} drmp3_src_config; + +struct drmp3_src +{ + drmp3_src_config config; + drmp3_src_read_proc onRead; + void* pUserData; + float bin[256]; + drmp3_src_cache cache; // <-- For simplifying and optimizing client -> memory reading. + union + { + struct + { + float alpha; + drmp3_bool32 isPrevFramesLoaded : 1; + drmp3_bool32 isNextFramesLoaded : 1; + } linear; + } algo; +}; + +typedef enum +{ + drmp3_seek_origin_start, + drmp3_seek_origin_current +} drmp3_seek_origin; + +// Callback for when data is read. Return value is the number of bytes actually read. +// +// pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family. +// pBufferOut [out] The output buffer. +// bytesToRead [in] The number of bytes to read. +// +// Returns the number of bytes actually read. +// +// A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until +// either the entire bytesToRead is filled or you have reached the end of the stream. +typedef size_t (* drmp3_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); + +// Callback for when data needs to be seeked. +// +// pUserData [in] The user data that was passed to drmp3_init(), drmp3_open() and family. +// offset [in] The number of bytes to move, relative to the origin. Will never be negative. +// origin [in] The origin of the seek - the current position or the start of the stream. +// +// Returns whether or not the seek was successful. +// +// Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which +// will be either drmp3_seek_origin_start or drmp3_seek_origin_current. +typedef drmp3_bool32 (* drmp3_seek_proc)(void* pUserData, int offset, drmp3_seek_origin origin); + +typedef struct +{ + drmp3_uint32 outputChannels; + drmp3_uint32 outputSampleRate; +} drmp3_config; + +typedef struct +{ + drmp3dec decoder; + drmp3dec_frame_info frameInfo; + drmp3_uint32 channels; + drmp3_uint32 sampleRate; + drmp3_read_proc onRead; + drmp3_seek_proc onSeek; + void* pUserData; + drmp3_uint32 mp3FrameChannels; // The number of channels in the currently loaded MP3 frame. Internal use only. + drmp3_uint32 mp3FrameSampleRate; // The sample rate of the currently loaded MP3 frame. Internal use only. + drmp3_uint32 pcmFramesConsumedInMP3Frame; + drmp3_uint32 pcmFramesRemainingInMP3Frame; + drmp3_uint8 pcmFrames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME]; // <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT. + drmp3_uint64 currentPCMFrame; // The current PCM frame, globally, based on the output sample rate. Mainly used for seeking. + drmp3_src src; + size_t dataSize; + size_t dataCapacity; + drmp3_uint8* pData; + drmp3_bool32 atEnd : 1; + struct + { + const drmp3_uint8* pData; + size_t dataSize; + size_t currentReadPos; + } memory; // Only used for decoders that were opened against a block of memory. +} drmp3; + +// Initializes an MP3 decoder. +// +// onRead [in] The function to call when data needs to be read from the client. +// onSeek [in] The function to call when the read position of the client data needs to move. +// pUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. +// +// Returns true if successful; false otherwise. +// +// Close the loader with drmp3_uninit(). +// +// See also: drmp3_init_file(), drmp3_init_memory(), drmp3_uninit() +drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig); + +// Initializes an MP3 decoder from a block of memory. +// +// This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for +// the lifetime of the drmp3 object. +// +// The buffer should contain the contents of the entire MP3 file. +drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_config* pConfig); + +#ifndef DR_MP3_NO_STDIO +// Initializes an MP3 decoder from a file. +// +// This holds the internal FILE object until drmp3_uninit() is called. Keep this in mind if you're caching drmp3 +// objects because the operating system may restrict the number of file handles an application can have open at +// any given time. +drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* filePath, const drmp3_config* pConfig); +#endif + +// Uninitializes an MP3 decoder. +void drmp3_uninit(drmp3* pMP3); + +// Reads PCM frames as interleaved 32-bit IEEE floating point PCM. +// +// Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames. +drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut); + +// Seeks to a specific frame. +// +// Note that this is _not_ an MP3 frame, but rather a PCM frame. +drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex); + +// Calculates the total number of PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet +// radio. Runs in linear time. Returns 0 on error. +drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3); + +// Calculates the total number of MP3 frames in the MP3 stream. Cannot be used for infinite streams such as internet +// radio. Runs in linear time. Returns 0 on error. +drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3); + + + +// Opens an decodes an entire MP3 stream as a single operation. +// +// pConfig is both an input and output. On input it contains what you want. On output it contains what you got. +// +// Free the returned pointer with drmp3_free(). +float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); +float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); +#ifndef DR_MP3_NO_STDIO +float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); +#endif + +// Frees any memory that was allocated by a public drmp3 API. +void drmp3_free(void* p); + +#ifdef __cplusplus +} +#endif +#endif // dr_mp3_h + + +///////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +///////////////////////////////////////////////////// +#ifdef DR_MP3_IMPLEMENTATION +#include +#include +#include +#include // For INT_MAX + +// Disable SIMD when compiling with TCC for now. +#if defined(__TINYC__) +#define DR_MP3_NO_SIMD +#endif + +#define DRMP3_OFFSET_PTR(p, offset) ((void*)((drmp3_uint8*)(p) + (offset))) + +#define DRMP3_MAX_FREE_FORMAT_FRAME_SIZE 2304 /* more than ISO spec's */ +#ifndef DRMP3_MAX_FRAME_SYNC_MATCHES +#define DRMP3_MAX_FRAME_SYNC_MATCHES 10 +#endif + +#define DRMP3_MAX_L3_FRAME_PAYLOAD_BYTES DRMP3_MAX_FREE_FORMAT_FRAME_SIZE /* MUST be >= 320000/8/32000*1152 = 1440 */ + +#define DRMP3_MAX_BITRESERVOIR_BYTES 511 +#define DRMP3_SHORT_BLOCK_TYPE 2 +#define DRMP3_STOP_BLOCK_TYPE 3 +#define DRMP3_MODE_MONO 3 +#define DRMP3_MODE_JOINT_STEREO 1 +#define DRMP3_HDR_SIZE 4 +#define DRMP3_HDR_IS_MONO(h) (((h[3]) & 0xC0) == 0xC0) +#define DRMP3_HDR_IS_MS_STEREO(h) (((h[3]) & 0xE0) == 0x60) +#define DRMP3_HDR_IS_FREE_FORMAT(h) (((h[2]) & 0xF0) == 0) +#define DRMP3_HDR_IS_CRC(h) (!((h[1]) & 1)) +#define DRMP3_HDR_TEST_PADDING(h) ((h[2]) & 0x2) +#define DRMP3_HDR_TEST_MPEG1(h) ((h[1]) & 0x8) +#define DRMP3_HDR_TEST_NOT_MPEG25(h) ((h[1]) & 0x10) +#define DRMP3_HDR_TEST_I_STEREO(h) ((h[3]) & 0x10) +#define DRMP3_HDR_TEST_MS_STEREO(h) ((h[3]) & 0x20) +#define DRMP3_HDR_GET_STEREO_MODE(h) (((h[3]) >> 6) & 3) +#define DRMP3_HDR_GET_STEREO_MODE_EXT(h) (((h[3]) >> 4) & 3) +#define DRMP3_HDR_GET_LAYER(h) (((h[1]) >> 1) & 3) +#define DRMP3_HDR_GET_BITRATE(h) ((h[2]) >> 4) +#define DRMP3_HDR_GET_SAMPLE_RATE(h) (((h[2]) >> 2) & 3) +#define DRMP3_HDR_GET_MY_SAMPLE_RATE(h) (DRMP3_HDR_GET_SAMPLE_RATE(h) + (((h[1] >> 3) & 1) + ((h[1] >> 4) & 1))*3) +#define DRMP3_HDR_IS_FRAME_576(h) ((h[1] & 14) == 2) +#define DRMP3_HDR_IS_LAYER_1(h) ((h[1] & 6) == 6) + +#define DRMP3_BITS_DEQUANTIZER_OUT -1 +#define DRMP3_MAX_SCF (255 + DRMP3_BITS_DEQUANTIZER_OUT*4 - 210) +#define DRMP3_MAX_SCFI ((DRMP3_MAX_SCF + 3) & ~3) + +#define DRMP3_MIN(a, b) ((a) > (b) ? (b) : (a)) +#define DRMP3_MAX(a, b) ((a) < (b) ? (b) : (a)) + +#if !defined(DR_MP3_NO_SIMD) + +#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(_M_ARM64) || defined(__x86_64__) || defined(__aarch64__)) +/* x64 always have SSE2, arm64 always have neon, no need for generic code */ +#define DR_MP3_ONLY_SIMD +#endif + +#if (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))) || ((defined(__i386__) || defined(__x86_64__)) && defined(__SSE2__)) +#if defined(_MSC_VER) +#include +#endif +#include +#define DRMP3_HAVE_SSE 1 +#define DRMP3_HAVE_SIMD 1 +#define DRMP3_VSTORE _mm_storeu_ps +#define DRMP3_VLD _mm_loadu_ps +#define DRMP3_VSET _mm_set1_ps +#define DRMP3_VADD _mm_add_ps +#define DRMP3_VSUB _mm_sub_ps +#define DRMP3_VMUL _mm_mul_ps +#define DRMP3_VMAC(a, x, y) _mm_add_ps(a, _mm_mul_ps(x, y)) +#define DRMP3_VMSB(a, x, y) _mm_sub_ps(a, _mm_mul_ps(x, y)) +#define DRMP3_VMUL_S(x, s) _mm_mul_ps(x, _mm_set1_ps(s)) +#define DRMP3_VREV(x) _mm_shuffle_ps(x, x, _MM_SHUFFLE(0, 1, 2, 3)) +typedef __m128 drmp3_f4; +#if defined(_MSC_VER) || defined(DR_MP3_ONLY_SIMD) +#define drmp3_cpuid __cpuid +#else +static __inline__ __attribute__((always_inline)) void drmp3_cpuid(int CPUInfo[], const int InfoType) +{ +#if defined(__PIC__) + __asm__ __volatile__( +#if defined(__x86_64__) + "push %%rbx\n" + "cpuid\n" + "xchgl %%ebx, %1\n" + "pop %%rbx\n" +#else + "xchgl %%ebx, %1\n" + "cpuid\n" + "xchgl %%ebx, %1\n" +#endif + : "=a" (CPUInfo[0]), "=r" (CPUInfo[1]), "=c" (CPUInfo[2]), "=d" (CPUInfo[3]) + : "a" (InfoType)); +#else + __asm__ __volatile__( + "cpuid" + : "=a" (CPUInfo[0]), "=b" (CPUInfo[1]), "=c" (CPUInfo[2]), "=d" (CPUInfo[3]) + : "a" (InfoType)); +#endif +} +#endif +static int drmp3_have_simd() +{ +#ifdef DR_MP3_ONLY_SIMD + return 1; +#else + static int g_have_simd; + int CPUInfo[4]; +#ifdef MINIMP3_TEST + static int g_counter; + if (g_counter++ > 100) + return 0; +#endif + if (g_have_simd) + goto end; + drmp3_cpuid(CPUInfo, 0); + if (CPUInfo[0] > 0) + { + drmp3_cpuid(CPUInfo, 1); + g_have_simd = (CPUInfo[3] & (1 << 26)) + 1; /* SSE2 */ + return g_have_simd - 1; + } + +end: + return g_have_simd - 1; +#endif +} +#elif defined(__ARM_NEON) || defined(__aarch64__) +#include +#define DRMP3_HAVE_SIMD 1 +#define DRMP3_VSTORE vst1q_f32 +#define DRMP3_VLD vld1q_f32 +#define DRMP3_VSET vmovq_n_f32 +#define DRMP3_VADD vaddq_f32 +#define DRMP3_VSUB vsubq_f32 +#define DRMP3_VMUL vmulq_f32 +#define DRMP3_VMAC(a, x, y) vmlaq_f32(a, x, y) +#define DRMP3_VMSB(a, x, y) vmlsq_f32(a, x, y) +#define DRMP3_VMUL_S(x, s) vmulq_f32(x, vmovq_n_f32(s)) +#define DRMP3_VREV(x) vcombine_f32(vget_high_f32(vrev64q_f32(x)), vget_low_f32(vrev64q_f32(x))) +typedef float32x4_t drmp3_f4; +static int drmp3_have_simd() +{ /* TODO: detect neon for !DR_MP3_ONLY_SIMD */ + return 1; +} +#else +#define DRMP3_HAVE_SIMD 0 +#ifdef DR_MP3_ONLY_SIMD +#error DR_MP3_ONLY_SIMD used, but SSE/NEON not enabled +#endif +#endif + +#else + +#define DRMP3_HAVE_SIMD 0 + +#endif + +typedef struct +{ + const drmp3_uint8 *buf; + int pos, limit; +} drmp3_bs; + +typedef struct +{ + float scf[3*64]; + drmp3_uint8 total_bands, stereo_bands, bitalloc[64], scfcod[64]; +} drmp3_L12_scale_info; + +typedef struct +{ + drmp3_uint8 tab_offset, code_tab_width, band_count; +} drmp3_L12_subband_alloc; + +typedef struct +{ + const drmp3_uint8 *sfbtab; + drmp3_uint16 part_23_length, big_values, scalefac_compress; + drmp3_uint8 global_gain, block_type, mixed_block_flag, n_long_sfb, n_short_sfb; + drmp3_uint8 table_select[3], region_count[3], subblock_gain[3]; + drmp3_uint8 preflag, scalefac_scale, count1_table, scfsi; +} drmp3_L3_gr_info; + +typedef struct +{ + drmp3_bs bs; + drmp3_uint8 maindata[DRMP3_MAX_BITRESERVOIR_BYTES + DRMP3_MAX_L3_FRAME_PAYLOAD_BYTES]; + drmp3_L3_gr_info gr_info[4]; + float grbuf[2][576], scf[40], syn[18 + 15][2*32]; + drmp3_uint8 ist_pos[2][39]; +} drmp3dec_scratch; + +static void drmp3_bs_init(drmp3_bs *bs, const drmp3_uint8 *data, int bytes) +{ + bs->buf = data; + bs->pos = 0; + bs->limit = bytes*8; +} + +static drmp3_uint32 drmp3_bs_get_bits(drmp3_bs *bs, int n) +{ + drmp3_uint32 next, cache = 0, s = bs->pos & 7; + int shl = n + s; + const drmp3_uint8 *p = bs->buf + (bs->pos >> 3); + if ((bs->pos += n) > bs->limit) + return 0; + next = *p++ & (255 >> s); + while ((shl -= 8) > 0) + { + cache |= next << shl; + next = *p++; + } + return cache | (next >> -shl); +} + +static int drmp3_hdr_valid(const drmp3_uint8 *h) +{ + return h[0] == 0xff && + ((h[1] & 0xF0) == 0xf0 || (h[1] & 0xFE) == 0xe2) && + (DRMP3_HDR_GET_LAYER(h) != 0) && + (DRMP3_HDR_GET_BITRATE(h) != 15) && + (DRMP3_HDR_GET_SAMPLE_RATE(h) != 3); +} + +static int drmp3_hdr_compare(const drmp3_uint8 *h1, const drmp3_uint8 *h2) +{ + return drmp3_hdr_valid(h2) && + ((h1[1] ^ h2[1]) & 0xFE) == 0 && + ((h1[2] ^ h2[2]) & 0x0C) == 0 && + !(DRMP3_HDR_IS_FREE_FORMAT(h1) ^ DRMP3_HDR_IS_FREE_FORMAT(h2)); +} + +static unsigned drmp3_hdr_bitrate_kbps(const drmp3_uint8 *h) +{ + static const drmp3_uint8 halfrate[2][3][15] = { + { { 0,4,8,12,16,20,24,28,32,40,48,56,64,72,80 }, { 0,4,8,12,16,20,24,28,32,40,48,56,64,72,80 }, { 0,16,24,28,32,40,48,56,64,72,80,88,96,112,128 } }, + { { 0,16,20,24,28,32,40,48,56,64,80,96,112,128,160 }, { 0,16,24,28,32,40,48,56,64,80,96,112,128,160,192 }, { 0,16,32,48,64,80,96,112,128,144,160,176,192,208,224 } }, + }; + return 2*halfrate[!!DRMP3_HDR_TEST_MPEG1(h)][DRMP3_HDR_GET_LAYER(h) - 1][DRMP3_HDR_GET_BITRATE(h)]; +} + +static unsigned drmp3_hdr_sample_rate_hz(const drmp3_uint8 *h) +{ + static const unsigned g_hz[3] = { 44100, 48000, 32000 }; + return g_hz[DRMP3_HDR_GET_SAMPLE_RATE(h)] >> (int)!DRMP3_HDR_TEST_MPEG1(h) >> (int)!DRMP3_HDR_TEST_NOT_MPEG25(h); +} + +static unsigned drmp3_hdr_frame_samples(const drmp3_uint8 *h) +{ + return DRMP3_HDR_IS_LAYER_1(h) ? 384 : (1152 >> (int)DRMP3_HDR_IS_FRAME_576(h)); +} + +static int drmp3_hdr_frame_bytes(const drmp3_uint8 *h, int free_format_size) +{ + int frame_bytes = drmp3_hdr_frame_samples(h)*drmp3_hdr_bitrate_kbps(h)*125/drmp3_hdr_sample_rate_hz(h); + if (DRMP3_HDR_IS_LAYER_1(h)) + { + frame_bytes &= ~3; /* slot align */ + } + return frame_bytes ? frame_bytes : free_format_size; +} + +static int drmp3_hdr_padding(const drmp3_uint8 *h) +{ + return DRMP3_HDR_TEST_PADDING(h) ? (DRMP3_HDR_IS_LAYER_1(h) ? 4 : 1) : 0; +} + +#ifndef DR_MP3_ONLY_MP3 +static const drmp3_L12_subband_alloc *drmp3_L12_subband_alloc_table(const drmp3_uint8 *hdr, drmp3_L12_scale_info *sci) +{ + const drmp3_L12_subband_alloc *alloc; + int mode = DRMP3_HDR_GET_STEREO_MODE(hdr); + int nbands, stereo_bands = (mode == DRMP3_MODE_MONO) ? 0 : (mode == DRMP3_MODE_JOINT_STEREO) ? (DRMP3_HDR_GET_STEREO_MODE_EXT(hdr) << 2) + 4 : 32; + + if (DRMP3_HDR_IS_LAYER_1(hdr)) + { + static const drmp3_L12_subband_alloc g_alloc_L1[] = { { 76, 4, 32 } }; + alloc = g_alloc_L1; + nbands = 32; + } else if (!DRMP3_HDR_TEST_MPEG1(hdr)) + { + static const drmp3_L12_subband_alloc g_alloc_L2M2[] = { { 60, 4, 4 }, { 44, 3, 7 }, { 44, 2, 19 } }; + alloc = g_alloc_L2M2; + nbands = 30; + } else + { + static const drmp3_L12_subband_alloc g_alloc_L2M1[] = { { 0, 4, 3 }, { 16, 4, 8 }, { 32, 3, 12 }, { 40, 2, 7 } }; + int sample_rate_idx = DRMP3_HDR_GET_SAMPLE_RATE(hdr); + unsigned kbps = drmp3_hdr_bitrate_kbps(hdr) >> (int)(mode != DRMP3_MODE_MONO); + if (!kbps) /* free-format */ + { + kbps = 192; + } + + alloc = g_alloc_L2M1; + nbands = 27; + if (kbps < 56) + { + static const drmp3_L12_subband_alloc g_alloc_L2M1_lowrate[] = { { 44, 4, 2 }, { 44, 3, 10 } }; + alloc = g_alloc_L2M1_lowrate; + nbands = sample_rate_idx == 2 ? 12 : 8; + } else if (kbps >= 96 && sample_rate_idx != 1) + { + nbands = 30; + } + } + + sci->total_bands = (drmp3_uint8)nbands; + sci->stereo_bands = (drmp3_uint8)DRMP3_MIN(stereo_bands, nbands); + + return alloc; +} + +static void drmp3_L12_read_scalefactors(drmp3_bs *bs, drmp3_uint8 *pba, drmp3_uint8 *scfcod, int bands, float *scf) +{ + static const float g_deq_L12[18*3] = { +#define DRMP3_DQ(x) 9.53674316e-07f/x, 7.56931807e-07f/x, 6.00777173e-07f/x + DRMP3_DQ(3),DRMP3_DQ(7),DRMP3_DQ(15),DRMP3_DQ(31),DRMP3_DQ(63),DRMP3_DQ(127),DRMP3_DQ(255),DRMP3_DQ(511),DRMP3_DQ(1023),DRMP3_DQ(2047),DRMP3_DQ(4095),DRMP3_DQ(8191),DRMP3_DQ(16383),DRMP3_DQ(32767),DRMP3_DQ(65535),DRMP3_DQ(3),DRMP3_DQ(5),DRMP3_DQ(9) + }; + int i, m; + for (i = 0; i < bands; i++) + { + float s = 0; + int ba = *pba++; + int mask = ba ? 4 + ((19 >> scfcod[i]) & 3) : 0; + for (m = 4; m; m >>= 1) + { + if (mask & m) + { + int b = drmp3_bs_get_bits(bs, 6); + s = g_deq_L12[ba*3 - 6 + b % 3]*(1 << 21 >> b/3); + } + *scf++ = s; + } + } +} + +static void drmp3_L12_read_scale_info(const drmp3_uint8 *hdr, drmp3_bs *bs, drmp3_L12_scale_info *sci) +{ + static const drmp3_uint8 g_bitalloc_code_tab[] = { + 0,17, 3, 4, 5,6,7, 8,9,10,11,12,13,14,15,16, + 0,17,18, 3,19,4,5, 6,7, 8, 9,10,11,12,13,16, + 0,17,18, 3,19,4,5,16, + 0,17,18,16, + 0,17,18,19, 4,5,6, 7,8, 9,10,11,12,13,14,15, + 0,17,18, 3,19,4,5, 6,7, 8, 9,10,11,12,13,14, + 0, 2, 3, 4, 5,6,7, 8,9,10,11,12,13,14,15,16 + }; + const drmp3_L12_subband_alloc *subband_alloc = drmp3_L12_subband_alloc_table(hdr, sci); + + int i, k = 0, ba_bits = 0; + const drmp3_uint8 *ba_code_tab = g_bitalloc_code_tab; + + for (i = 0; i < sci->total_bands; i++) + { + drmp3_uint8 ba; + if (i == k) + { + k += subband_alloc->band_count; + ba_bits = subband_alloc->code_tab_width; + ba_code_tab = g_bitalloc_code_tab + subband_alloc->tab_offset; + subband_alloc++; + } + ba = ba_code_tab[drmp3_bs_get_bits(bs, ba_bits)]; + sci->bitalloc[2*i] = ba; + if (i < sci->stereo_bands) + { + ba = ba_code_tab[drmp3_bs_get_bits(bs, ba_bits)]; + } + sci->bitalloc[2*i + 1] = sci->stereo_bands ? ba : 0; + } + + for (i = 0; i < 2*sci->total_bands; i++) + { + sci->scfcod[i] = (drmp3_uint8)(sci->bitalloc[i] ? DRMP3_HDR_IS_LAYER_1(hdr) ? 2 : drmp3_bs_get_bits(bs, 2) : 6); + } + + drmp3_L12_read_scalefactors(bs, sci->bitalloc, sci->scfcod, sci->total_bands*2, sci->scf); + + for (i = sci->stereo_bands; i < sci->total_bands; i++) + { + sci->bitalloc[2*i + 1] = 0; + } +} + +static int drmp3_L12_dequantize_granule(float *grbuf, drmp3_bs *bs, drmp3_L12_scale_info *sci, int group_size) +{ + int i, j, k, choff = 576; + for (j = 0; j < 4; j++) + { + float *dst = grbuf + group_size*j; + for (i = 0; i < 2*sci->total_bands; i++) + { + int ba = sci->bitalloc[i]; + if (ba != 0) + { + if (ba < 17) + { + int half = (1 << (ba - 1)) - 1; + for (k = 0; k < group_size; k++) + { + dst[k] = (float)((int)drmp3_bs_get_bits(bs, ba) - half); + } + } else + { + unsigned mod = (2 << (ba - 17)) + 1; /* 3, 5, 9 */ + unsigned code = drmp3_bs_get_bits(bs, mod + 2 - (mod >> 3)); /* 5, 7, 10 */ + for (k = 0; k < group_size; k++, code /= mod) + { + dst[k] = (float)((int)(code % mod - mod/2)); + } + } + } + dst += choff; + choff = 18 - choff; + } + } + return group_size*4; +} + +static void drmp3_L12_apply_scf_384(drmp3_L12_scale_info *sci, const float *scf, float *dst) +{ + int i, k; + memcpy(dst + 576 + sci->stereo_bands*18, dst + sci->stereo_bands*18, (sci->total_bands - sci->stereo_bands)*18*sizeof(float)); + for (i = 0; i < sci->total_bands; i++, dst += 18, scf += 6) + { + for (k = 0; k < 12; k++) + { + dst[k + 0] *= scf[0]; + dst[k + 576] *= scf[3]; + } + } +} +#endif + +static int drmp3_L3_read_side_info(drmp3_bs *bs, drmp3_L3_gr_info *gr, const drmp3_uint8 *hdr) +{ + static const drmp3_uint8 g_scf_long[8][23] = { + { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, + { 12,12,12,12,12,12,16,20,24,28,32,40,48,56,64,76,90,2,2,2,2,2,0 }, + { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, + { 6,6,6,6,6,6,8,10,12,14,16,18,22,26,32,38,46,54,62,70,76,36,0 }, + { 6,6,6,6,6,6,8,10,12,14,16,20,24,28,32,38,46,52,60,68,58,54,0 }, + { 4,4,4,4,4,4,6,6,8,8,10,12,16,20,24,28,34,42,50,54,76,158,0 }, + { 4,4,4,4,4,4,6,6,6,8,10,12,16,18,22,28,34,40,46,54,54,192,0 }, + { 4,4,4,4,4,4,6,6,8,10,12,16,20,24,30,38,46,56,68,84,102,26,0 } + }; + static const drmp3_uint8 g_scf_short[8][40] = { + { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 8,8,8,8,8,8,8,8,8,12,12,12,16,16,16,20,20,20,24,24,24,28,28,28,36,36,36,2,2,2,2,2,2,2,2,2,26,26,26,0 }, + { 4,4,4,4,4,4,4,4,4,6,6,6,6,6,6,8,8,8,10,10,10,14,14,14,18,18,18,26,26,26,32,32,32,42,42,42,18,18,18,0 }, + { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,32,32,32,44,44,44,12,12,12,0 }, + { 4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,22,22,22,30,30,30,56,56,56,0 }, + { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,6,6,6,10,10,10,12,12,12,14,14,14,16,16,16,20,20,20,26,26,26,66,66,66,0 }, + { 4,4,4,4,4,4,4,4,4,4,4,4,6,6,6,8,8,8,12,12,12,16,16,16,20,20,20,26,26,26,34,34,34,42,42,42,12,12,12,0 } + }; + static const drmp3_uint8 g_scf_mixed[8][40] = { + { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 12,12,12,4,4,4,8,8,8,12,12,12,16,16,16,20,20,20,24,24,24,28,28,28,36,36,36,2,2,2,2,2,2,2,2,2,26,26,26,0 }, + { 6,6,6,6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,14,14,14,18,18,18,26,26,26,32,32,32,42,42,42,18,18,18,0 }, + { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,32,32,32,44,44,44,12,12,12,0 }, + { 6,6,6,6,6,6,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,24,24,24,30,30,30,40,40,40,18,18,18,0 }, + { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,8,8,8,10,10,10,12,12,12,14,14,14,18,18,18,22,22,22,30,30,30,56,56,56,0 }, + { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,6,6,6,10,10,10,12,12,12,14,14,14,16,16,16,20,20,20,26,26,26,66,66,66,0 }, + { 4,4,4,4,4,4,6,6,4,4,4,6,6,6,8,8,8,12,12,12,16,16,16,20,20,20,26,26,26,34,34,34,42,42,42,12,12,12,0 } + }; + + unsigned tables, scfsi = 0; + int main_data_begin, part_23_sum = 0; + int sr_idx = DRMP3_HDR_GET_MY_SAMPLE_RATE(hdr); sr_idx -= (sr_idx != 0); + int gr_count = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2; + + if (DRMP3_HDR_TEST_MPEG1(hdr)) + { + gr_count *= 2; + main_data_begin = drmp3_bs_get_bits(bs, 9); + scfsi = drmp3_bs_get_bits(bs, 7 + gr_count); + } else + { + main_data_begin = drmp3_bs_get_bits(bs, 8 + gr_count) >> gr_count; + } + + do + { + if (DRMP3_HDR_IS_MONO(hdr)) + { + scfsi <<= 4; + } + gr->part_23_length = (drmp3_uint16)drmp3_bs_get_bits(bs, 12); + part_23_sum += gr->part_23_length; + gr->big_values = (drmp3_uint16)drmp3_bs_get_bits(bs, 9); + if (gr->big_values > 288) + { + return -1; + } + gr->global_gain = (drmp3_uint8)drmp3_bs_get_bits(bs, 8); + gr->scalefac_compress = (drmp3_uint16)drmp3_bs_get_bits(bs, DRMP3_HDR_TEST_MPEG1(hdr) ? 4 : 9); + gr->sfbtab = g_scf_long[sr_idx]; + gr->n_long_sfb = 22; + gr->n_short_sfb = 0; + if (drmp3_bs_get_bits(bs, 1)) + { + gr->block_type = (drmp3_uint8)drmp3_bs_get_bits(bs, 2); + if (!gr->block_type) + { + return -1; + } + gr->mixed_block_flag = (drmp3_uint8)drmp3_bs_get_bits(bs, 1); + gr->region_count[0] = 7; + gr->region_count[1] = 255; + if (gr->block_type == DRMP3_SHORT_BLOCK_TYPE) + { + scfsi &= 0x0F0F; + if (!gr->mixed_block_flag) + { + gr->region_count[0] = 8; + gr->sfbtab = g_scf_short[sr_idx]; + gr->n_long_sfb = 0; + gr->n_short_sfb = 39; + } else + { + gr->sfbtab = g_scf_mixed[sr_idx]; + gr->n_long_sfb = DRMP3_HDR_TEST_MPEG1(hdr) ? 8 : 6; + gr->n_short_sfb = 30; + } + } + tables = drmp3_bs_get_bits(bs, 10); + tables <<= 5; + gr->subblock_gain[0] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); + gr->subblock_gain[1] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); + gr->subblock_gain[2] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); + } else + { + gr->block_type = 0; + gr->mixed_block_flag = 0; + tables = drmp3_bs_get_bits(bs, 15); + gr->region_count[0] = (drmp3_uint8)drmp3_bs_get_bits(bs, 4); + gr->region_count[1] = (drmp3_uint8)drmp3_bs_get_bits(bs, 3); + gr->region_count[2] = 255; + } + gr->table_select[0] = (drmp3_uint8)(tables >> 10); + gr->table_select[1] = (drmp3_uint8)((tables >> 5) & 31); + gr->table_select[2] = (drmp3_uint8)((tables) & 31); + gr->preflag = (drmp3_uint8)(DRMP3_HDR_TEST_MPEG1(hdr) ? drmp3_bs_get_bits(bs, 1) : (gr->scalefac_compress >= 500)); + gr->scalefac_scale = (drmp3_uint8)drmp3_bs_get_bits(bs, 1); + gr->count1_table = (drmp3_uint8)drmp3_bs_get_bits(bs, 1); + gr->scfsi = (drmp3_uint8)((scfsi >> 12) & 15); + scfsi <<= 4; + gr++; + } while(--gr_count); + + if (part_23_sum + bs->pos > bs->limit + main_data_begin*8) + { + return -1; + } + + return main_data_begin; +} + +static void drmp3_L3_read_scalefactors(drmp3_uint8 *scf, drmp3_uint8 *ist_pos, const drmp3_uint8 *scf_size, const drmp3_uint8 *scf_count, drmp3_bs *bitbuf, int scfsi) +{ + int i, k; + for (i = 0; i < 4 && scf_count[i]; i++, scfsi *= 2) + { + int cnt = scf_count[i]; + if (scfsi & 8) + { + memcpy(scf, ist_pos, cnt); + } else + { + int bits = scf_size[i]; + if (!bits) + { + memset(scf, 0, cnt); + memset(ist_pos, 0, cnt); + } else + { + int max_scf = (scfsi < 0) ? (1 << bits) - 1 : -1; + for (k = 0; k < cnt; k++) + { + int s = drmp3_bs_get_bits(bitbuf, bits); + ist_pos[k] = (drmp3_uint8)(s == max_scf ? -1 : s); + scf[k] = (drmp3_uint8)s; + } + } + } + ist_pos += cnt; + scf += cnt; + } + scf[0] = scf[1] = scf[2] = 0; +} + +static float drmp3_L3_ldexp_q2(float y, int exp_q2) +{ + static const float g_expfrac[4] = { 9.31322575e-10f,7.83145814e-10f,6.58544508e-10f,5.53767716e-10f }; + int e; + do + { + e = DRMP3_MIN(30*4, exp_q2); + y *= g_expfrac[e & 3]*(1 << 30 >> (e >> 2)); + } while ((exp_q2 -= e) > 0); + return y; +} + +static void drmp3_L3_decode_scalefactors(const drmp3_uint8 *hdr, drmp3_uint8 *ist_pos, drmp3_bs *bs, const drmp3_L3_gr_info *gr, float *scf, int ch) +{ + static const drmp3_uint8 g_scf_partitions[3][28] = { + { 6,5,5, 5,6,5,5,5,6,5, 7,3,11,10,0,0, 7, 7, 7,0, 6, 6,6,3, 8, 8,5,0 }, + { 8,9,6,12,6,9,9,9,6,9,12,6,15,18,0,0, 6,15,12,0, 6,12,9,6, 6,18,9,0 }, + { 9,9,6,12,9,9,9,9,9,9,12,6,18,18,0,0,12,12,12,0,12, 9,9,6,15,12,9,0 } + }; + const drmp3_uint8 *scf_partition = g_scf_partitions[!!gr->n_short_sfb + !gr->n_long_sfb]; + drmp3_uint8 scf_size[4], iscf[40]; + int i, scf_shift = gr->scalefac_scale + 1, gain_exp, scfsi = gr->scfsi; + float gain; + + if (DRMP3_HDR_TEST_MPEG1(hdr)) + { + static const drmp3_uint8 g_scfc_decode[16] = { 0,1,2,3, 12,5,6,7, 9,10,11,13, 14,15,18,19 }; + int part = g_scfc_decode[gr->scalefac_compress]; + scf_size[1] = scf_size[0] = (drmp3_uint8)(part >> 2); + scf_size[3] = scf_size[2] = (drmp3_uint8)(part & 3); + } else + { + static const drmp3_uint8 g_mod[6*4] = { 5,5,4,4,5,5,4,1,4,3,1,1,5,6,6,1,4,4,4,1,4,3,1,1 }; + int k, modprod, sfc, ist = DRMP3_HDR_TEST_I_STEREO(hdr) && ch; + sfc = gr->scalefac_compress >> ist; + for (k = ist*3*4; sfc >= 0; sfc -= modprod, k += 4) + { + for (modprod = 1, i = 3; i >= 0; i--) + { + scf_size[i] = (drmp3_uint8)(sfc / modprod % g_mod[k + i]); + modprod *= g_mod[k + i]; + } + } + scf_partition += k; + scfsi = -16; + } + drmp3_L3_read_scalefactors(iscf, ist_pos, scf_size, scf_partition, bs, scfsi); + + if (gr->n_short_sfb) + { + int sh = 3 - scf_shift; + for (i = 0; i < gr->n_short_sfb; i += 3) + { + iscf[gr->n_long_sfb + i + 0] += gr->subblock_gain[0] << sh; + iscf[gr->n_long_sfb + i + 1] += gr->subblock_gain[1] << sh; + iscf[gr->n_long_sfb + i + 2] += gr->subblock_gain[2] << sh; + } + } else if (gr->preflag) + { + static const drmp3_uint8 g_preamp[10] = { 1,1,1,1,2,2,3,3,3,2 }; + for (i = 0; i < 10; i++) + { + iscf[11 + i] += g_preamp[i]; + } + } + + gain_exp = gr->global_gain + DRMP3_BITS_DEQUANTIZER_OUT*4 - 210 - (DRMP3_HDR_IS_MS_STEREO(hdr) ? 2 : 0); + gain = drmp3_L3_ldexp_q2(1 << (DRMP3_MAX_SCFI/4), DRMP3_MAX_SCFI - gain_exp); + for (i = 0; i < (int)(gr->n_long_sfb + gr->n_short_sfb); i++) + { + scf[i] = drmp3_L3_ldexp_q2(gain, iscf[i] << scf_shift); + } +} + +static const float g_drmp3_pow43[129 + 16] = { + 0,-1,-2.519842f,-4.326749f,-6.349604f,-8.549880f,-10.902724f,-13.390518f,-16.000000f,-18.720754f,-21.544347f,-24.463781f,-27.473142f,-30.567351f,-33.741992f,-36.993181f, + 0,1,2.519842f,4.326749f,6.349604f,8.549880f,10.902724f,13.390518f,16.000000f,18.720754f,21.544347f,24.463781f,27.473142f,30.567351f,33.741992f,36.993181f,40.317474f,43.711787f,47.173345f,50.699631f,54.288352f,57.937408f,61.644865f,65.408941f,69.227979f,73.100443f,77.024898f,81.000000f,85.024491f,89.097188f,93.216975f,97.382800f,101.593667f,105.848633f,110.146801f,114.487321f,118.869381f,123.292209f,127.755065f,132.257246f,136.798076f,141.376907f,145.993119f,150.646117f,155.335327f,160.060199f,164.820202f,169.614826f,174.443577f,179.305980f,184.201575f,189.129918f,194.090580f,199.083145f,204.107210f,209.162385f,214.248292f,219.364564f,224.510845f,229.686789f,234.892058f,240.126328f,245.389280f,250.680604f,256.000000f,261.347174f,266.721841f,272.123723f,277.552547f,283.008049f,288.489971f,293.998060f,299.532071f,305.091761f,310.676898f,316.287249f,321.922592f,327.582707f,333.267377f,338.976394f,344.709550f,350.466646f,356.247482f,362.051866f,367.879608f,373.730522f,379.604427f,385.501143f,391.420496f,397.362314f,403.326427f,409.312672f,415.320884f,421.350905f,427.402579f,433.475750f,439.570269f,445.685987f,451.822757f,457.980436f,464.158883f,470.357960f,476.577530f,482.817459f,489.077615f,495.357868f,501.658090f,507.978156f,514.317941f,520.677324f,527.056184f,533.454404f,539.871867f,546.308458f,552.764065f,559.238575f,565.731879f,572.243870f,578.774440f,585.323483f,591.890898f,598.476581f,605.080431f,611.702349f,618.342238f,625.000000f,631.675540f,638.368763f,645.079578f +}; + +static float drmp3_L3_pow_43(int x) +{ + float frac; + int sign, mult = 256; + + if (x < 129) + { + return g_drmp3_pow43[16 + x]; + } + + if (x < 1024) + { + mult = 16; + x <<= 3; + } + + sign = 2*x & 64; + frac = (float)((x & 63) - sign) / ((x & ~63) + sign); + return g_drmp3_pow43[16 + ((x + sign) >> 6)]*(1.f + frac*((4.f/3) + frac*(2.f/9)))*mult; +} + +static void drmp3_L3_huffman(float *dst, drmp3_bs *bs, const drmp3_L3_gr_info *gr_info, const float *scf, int layer3gr_limit) +{ + static const drmp3_int16 tabs[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 785,785,785,785,784,784,784,784,513,513,513,513,513,513,513,513,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256, + -255,1313,1298,1282,785,785,785,785,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,290,288, + -255,1313,1298,1282,769,769,769,769,529,529,529,529,529,529,529,529,528,528,528,528,528,528,528,528,512,512,512,512,512,512,512,512,290,288, + -253,-318,-351,-367,785,785,785,785,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,819,818,547,547,275,275,275,275,561,560,515,546,289,274,288,258, + -254,-287,1329,1299,1314,1312,1057,1057,1042,1042,1026,1026,784,784,784,784,529,529,529,529,529,529,529,529,769,769,769,769,768,768,768,768,563,560,306,306,291,259, + -252,-413,-477,-542,1298,-575,1041,1041,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-383,-399,1107,1092,1106,1061,849,849,789,789,1104,1091,773,773,1076,1075,341,340,325,309,834,804,577,577,532,532,516,516,832,818,803,816,561,561,531,531,515,546,289,289,288,258, + -252,-429,-493,-559,1057,1057,1042,1042,529,529,529,529,529,529,529,529,784,784,784,784,769,769,769,769,512,512,512,512,512,512,512,512,-382,1077,-415,1106,1061,1104,849,849,789,789,1091,1076,1029,1075,834,834,597,581,340,340,339,324,804,833,532,532,832,772,818,803,817,787,816,771,290,290,290,290,288,258, + -253,-349,-414,-447,-463,1329,1299,-479,1314,1312,1057,1057,1042,1042,1026,1026,785,785,785,785,784,784,784,784,769,769,769,769,768,768,768,768,-319,851,821,-335,836,850,805,849,341,340,325,336,533,533,579,579,564,564,773,832,578,548,563,516,321,276,306,291,304,259, + -251,-572,-733,-830,-863,-879,1041,1041,784,784,784,784,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-511,-527,-543,1396,1351,1381,1366,1395,1335,1380,-559,1334,1138,1138,1063,1063,1350,1392,1031,1031,1062,1062,1364,1363,1120,1120,1333,1348,881,881,881,881,375,374,359,373,343,358,341,325,791,791,1123,1122,-703,1105,1045,-719,865,865,790,790,774,774,1104,1029,338,293,323,308,-799,-815,833,788,772,818,803,816,322,292,307,320,561,531,515,546,289,274,288,258, + -251,-525,-605,-685,-765,-831,-846,1298,1057,1057,1312,1282,785,785,785,785,784,784,784,784,769,769,769,769,512,512,512,512,512,512,512,512,1399,1398,1383,1367,1382,1396,1351,-511,1381,1366,1139,1139,1079,1079,1124,1124,1364,1349,1363,1333,882,882,882,882,807,807,807,807,1094,1094,1136,1136,373,341,535,535,881,775,867,822,774,-591,324,338,-671,849,550,550,866,864,609,609,293,336,534,534,789,835,773,-751,834,804,308,307,833,788,832,772,562,562,547,547,305,275,560,515,290,290, + -252,-397,-477,-557,-622,-653,-719,-735,-750,1329,1299,1314,1057,1057,1042,1042,1312,1282,1024,1024,785,785,785,785,784,784,784,784,769,769,769,769,-383,1127,1141,1111,1126,1140,1095,1110,869,869,883,883,1079,1109,882,882,375,374,807,868,838,881,791,-463,867,822,368,263,852,837,836,-543,610,610,550,550,352,336,534,534,865,774,851,821,850,805,593,533,579,564,773,832,578,578,548,548,577,577,307,276,306,291,516,560,259,259, + -250,-2107,-2507,-2764,-2909,-2974,-3007,-3023,1041,1041,1040,1040,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-767,-1052,-1213,-1277,-1358,-1405,-1469,-1535,-1550,-1582,-1614,-1647,-1662,-1694,-1726,-1759,-1774,-1807,-1822,-1854,-1886,1565,-1919,-1935,-1951,-1967,1731,1730,1580,1717,-1983,1729,1564,-1999,1548,-2015,-2031,1715,1595,-2047,1714,-2063,1610,-2079,1609,-2095,1323,1323,1457,1457,1307,1307,1712,1547,1641,1700,1699,1594,1685,1625,1442,1442,1322,1322,-780,-973,-910,1279,1278,1277,1262,1276,1261,1275,1215,1260,1229,-959,974,974,989,989,-943,735,478,478,495,463,506,414,-1039,1003,958,1017,927,942,987,957,431,476,1272,1167,1228,-1183,1256,-1199,895,895,941,941,1242,1227,1212,1135,1014,1014,490,489,503,487,910,1013,985,925,863,894,970,955,1012,847,-1343,831,755,755,984,909,428,366,754,559,-1391,752,486,457,924,997,698,698,983,893,740,740,908,877,739,739,667,667,953,938,497,287,271,271,683,606,590,712,726,574,302,302,738,736,481,286,526,725,605,711,636,724,696,651,589,681,666,710,364,467,573,695,466,466,301,465,379,379,709,604,665,679,316,316,634,633,436,436,464,269,424,394,452,332,438,363,347,408,393,448,331,422,362,407,392,421,346,406,391,376,375,359,1441,1306,-2367,1290,-2383,1337,-2399,-2415,1426,1321,-2431,1411,1336,-2447,-2463,-2479,1169,1169,1049,1049,1424,1289,1412,1352,1319,-2495,1154,1154,1064,1064,1153,1153,416,390,360,404,403,389,344,374,373,343,358,372,327,357,342,311,356,326,1395,1394,1137,1137,1047,1047,1365,1392,1287,1379,1334,1364,1349,1378,1318,1363,792,792,792,792,1152,1152,1032,1032,1121,1121,1046,1046,1120,1120,1030,1030,-2895,1106,1061,1104,849,849,789,789,1091,1076,1029,1090,1060,1075,833,833,309,324,532,532,832,772,818,803,561,561,531,560,515,546,289,274,288,258, + -250,-1179,-1579,-1836,-1996,-2124,-2253,-2333,-2413,-2477,-2542,-2574,-2607,-2622,-2655,1314,1313,1298,1312,1282,785,785,785,785,1040,1040,1025,1025,768,768,768,768,-766,-798,-830,-862,-895,-911,-927,-943,-959,-975,-991,-1007,-1023,-1039,-1055,-1070,1724,1647,-1103,-1119,1631,1767,1662,1738,1708,1723,-1135,1780,1615,1779,1599,1677,1646,1778,1583,-1151,1777,1567,1737,1692,1765,1722,1707,1630,1751,1661,1764,1614,1736,1676,1763,1750,1645,1598,1721,1691,1762,1706,1582,1761,1566,-1167,1749,1629,767,766,751,765,494,494,735,764,719,749,734,763,447,447,748,718,477,506,431,491,446,476,461,505,415,430,475,445,504,399,460,489,414,503,383,474,429,459,502,502,746,752,488,398,501,473,413,472,486,271,480,270,-1439,-1455,1357,-1471,-1487,-1503,1341,1325,-1519,1489,1463,1403,1309,-1535,1372,1448,1418,1476,1356,1462,1387,-1551,1475,1340,1447,1402,1386,-1567,1068,1068,1474,1461,455,380,468,440,395,425,410,454,364,467,466,464,453,269,409,448,268,432,1371,1473,1432,1417,1308,1460,1355,1446,1459,1431,1083,1083,1401,1416,1458,1445,1067,1067,1370,1457,1051,1051,1291,1430,1385,1444,1354,1415,1400,1443,1082,1082,1173,1113,1186,1066,1185,1050,-1967,1158,1128,1172,1097,1171,1081,-1983,1157,1112,416,266,375,400,1170,1142,1127,1065,793,793,1169,1033,1156,1096,1141,1111,1155,1080,1126,1140,898,898,808,808,897,897,792,792,1095,1152,1032,1125,1110,1139,1079,1124,882,807,838,881,853,791,-2319,867,368,263,822,852,837,866,806,865,-2399,851,352,262,534,534,821,836,594,594,549,549,593,593,533,533,848,773,579,579,564,578,548,563,276,276,577,576,306,291,516,560,305,305,275,259, + -251,-892,-2058,-2620,-2828,-2957,-3023,-3039,1041,1041,1040,1040,769,769,769,769,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,256,-511,-527,-543,-559,1530,-575,-591,1528,1527,1407,1526,1391,1023,1023,1023,1023,1525,1375,1268,1268,1103,1103,1087,1087,1039,1039,1523,-604,815,815,815,815,510,495,509,479,508,463,507,447,431,505,415,399,-734,-782,1262,-815,1259,1244,-831,1258,1228,-847,-863,1196,-879,1253,987,987,748,-767,493,493,462,477,414,414,686,669,478,446,461,445,474,429,487,458,412,471,1266,1264,1009,1009,799,799,-1019,-1276,-1452,-1581,-1677,-1757,-1821,-1886,-1933,-1997,1257,1257,1483,1468,1512,1422,1497,1406,1467,1496,1421,1510,1134,1134,1225,1225,1466,1451,1374,1405,1252,1252,1358,1480,1164,1164,1251,1251,1238,1238,1389,1465,-1407,1054,1101,-1423,1207,-1439,830,830,1248,1038,1237,1117,1223,1148,1236,1208,411,426,395,410,379,269,1193,1222,1132,1235,1221,1116,976,976,1192,1162,1177,1220,1131,1191,963,963,-1647,961,780,-1663,558,558,994,993,437,408,393,407,829,978,813,797,947,-1743,721,721,377,392,844,950,828,890,706,706,812,859,796,960,948,843,934,874,571,571,-1919,690,555,689,421,346,539,539,944,779,918,873,932,842,903,888,570,570,931,917,674,674,-2575,1562,-2591,1609,-2607,1654,1322,1322,1441,1441,1696,1546,1683,1593,1669,1624,1426,1426,1321,1321,1639,1680,1425,1425,1305,1305,1545,1668,1608,1623,1667,1592,1638,1666,1320,1320,1652,1607,1409,1409,1304,1304,1288,1288,1664,1637,1395,1395,1335,1335,1622,1636,1394,1394,1319,1319,1606,1621,1392,1392,1137,1137,1137,1137,345,390,360,375,404,373,1047,-2751,-2767,-2783,1062,1121,1046,-2799,1077,-2815,1106,1061,789,789,1105,1104,263,355,310,340,325,354,352,262,339,324,1091,1076,1029,1090,1060,1075,833,833,788,788,1088,1028,818,818,803,803,561,561,531,531,816,771,546,546,289,274,288,258, + -253,-317,-381,-446,-478,-509,1279,1279,-811,-1179,-1451,-1756,-1900,-2028,-2189,-2253,-2333,-2414,-2445,-2511,-2526,1313,1298,-2559,1041,1041,1040,1040,1025,1025,1024,1024,1022,1007,1021,991,1020,975,1019,959,687,687,1018,1017,671,671,655,655,1016,1015,639,639,758,758,623,623,757,607,756,591,755,575,754,559,543,543,1009,783,-575,-621,-685,-749,496,-590,750,749,734,748,974,989,1003,958,988,973,1002,942,987,957,972,1001,926,986,941,971,956,1000,910,985,925,999,894,970,-1071,-1087,-1102,1390,-1135,1436,1509,1451,1374,-1151,1405,1358,1480,1420,-1167,1507,1494,1389,1342,1465,1435,1450,1326,1505,1310,1493,1373,1479,1404,1492,1464,1419,428,443,472,397,736,526,464,464,486,457,442,471,484,482,1357,1449,1434,1478,1388,1491,1341,1490,1325,1489,1463,1403,1309,1477,1372,1448,1418,1433,1476,1356,1462,1387,-1439,1475,1340,1447,1402,1474,1324,1461,1371,1473,269,448,1432,1417,1308,1460,-1711,1459,-1727,1441,1099,1099,1446,1386,1431,1401,-1743,1289,1083,1083,1160,1160,1458,1445,1067,1067,1370,1457,1307,1430,1129,1129,1098,1098,268,432,267,416,266,400,-1887,1144,1187,1082,1173,1113,1186,1066,1050,1158,1128,1143,1172,1097,1171,1081,420,391,1157,1112,1170,1142,1127,1065,1169,1049,1156,1096,1141,1111,1155,1080,1126,1154,1064,1153,1140,1095,1048,-2159,1125,1110,1137,-2175,823,823,1139,1138,807,807,384,264,368,263,868,838,853,791,867,822,852,837,866,806,865,790,-2319,851,821,836,352,262,850,805,849,-2399,533,533,835,820,336,261,578,548,563,577,532,532,832,772,562,562,547,547,305,275,560,515,290,290,288,258 }; + static const drmp3_uint8 tab32[] = { 130,162,193,209,44,28,76,140,9,9,9,9,9,9,9,9,190,254,222,238,126,94,157,157,109,61,173,205}; + static const drmp3_uint8 tab33[] = { 252,236,220,204,188,172,156,140,124,108,92,76,60,44,28,12 }; + static const drmp3_int16 tabindex[2*16] = { 0,32,64,98,0,132,180,218,292,364,426,538,648,746,0,1126,1460,1460,1460,1460,1460,1460,1460,1460,1842,1842,1842,1842,1842,1842,1842,1842 }; + static const drmp3_uint8 g_linbits[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,6,8,10,13,4,5,6,7,8,9,11,13 }; + +#define DRMP3_PEEK_BITS(n) (bs_cache >> (32 - n)) +#define DRMP3_FLUSH_BITS(n) { bs_cache <<= (n); bs_sh += (n); } +#define DRMP3_CHECK_BITS while (bs_sh >= 0) { bs_cache |= (drmp3_uint32)*bs_next_ptr++ << bs_sh; bs_sh -= 8; } +#define DRMP3_BSPOS ((bs_next_ptr - bs->buf)*8 - 24 + bs_sh) + + float one = 0.0f; + int ireg = 0, big_val_cnt = gr_info->big_values; + const drmp3_uint8 *sfb = gr_info->sfbtab; + const drmp3_uint8 *bs_next_ptr = bs->buf + bs->pos/8; + drmp3_uint32 bs_cache = (((bs_next_ptr[0]*256u + bs_next_ptr[1])*256u + bs_next_ptr[2])*256u + bs_next_ptr[3]) << (bs->pos & 7); + int pairs_to_decode, np, bs_sh = (bs->pos & 7) - 8; + bs_next_ptr += 4; + + while (big_val_cnt > 0) + { + int tab_num = gr_info->table_select[ireg]; + int sfb_cnt = gr_info->region_count[ireg++]; + const drmp3_int16 *codebook = tabs + tabindex[tab_num]; + int linbits = g_linbits[tab_num]; + do + { + np = *sfb++ / 2; + pairs_to_decode = DRMP3_MIN(big_val_cnt, np); + one = *scf++; + do + { + int j, w = 5; + int leaf = codebook[DRMP3_PEEK_BITS(w)]; + while (leaf < 0) + { + DRMP3_FLUSH_BITS(w); + w = leaf & 7; + leaf = codebook[DRMP3_PEEK_BITS(w) - (leaf >> 3)]; + } + DRMP3_FLUSH_BITS(leaf >> 8); + + for (j = 0; j < 2; j++, dst++, leaf >>= 4) + { + int lsb = leaf & 0x0F; + if (lsb == 15 && linbits) + { + lsb += DRMP3_PEEK_BITS(linbits); + DRMP3_FLUSH_BITS(linbits); + DRMP3_CHECK_BITS; + *dst = one*drmp3_L3_pow_43(lsb)*((int32_t)bs_cache < 0 ? -1: 1); + } else + { + *dst = g_drmp3_pow43[16 + lsb - 16*(bs_cache >> 31)]*one; + } + DRMP3_FLUSH_BITS(lsb ? 1 : 0); + } + DRMP3_CHECK_BITS; + } while (--pairs_to_decode); + } while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0); + } + + for (np = 1 - big_val_cnt;; dst += 4) + { + const drmp3_uint8 *codebook_count1 = (gr_info->count1_table) ? tab33 : tab32; + int leaf = codebook_count1[DRMP3_PEEK_BITS(4)]; + if (!(leaf & 8)) + { + leaf = codebook_count1[(leaf >> 3) + (bs_cache << 4 >> (32 - (leaf & 3)))]; + } + DRMP3_FLUSH_BITS(leaf & 7); + if (DRMP3_BSPOS > layer3gr_limit) + { + break; + } +#define DRMP3_RELOAD_SCALEFACTOR if (!--np) { np = *sfb++/2; if (!np) break; one = *scf++; } +#define DRMP3_DEQ_COUNT1(s) if (leaf & (128 >> s)) { dst[s] = ((drmp3_int32)bs_cache < 0) ? -one : one; DRMP3_FLUSH_BITS(1) } + DRMP3_RELOAD_SCALEFACTOR; + DRMP3_DEQ_COUNT1(0); + DRMP3_DEQ_COUNT1(1); + DRMP3_RELOAD_SCALEFACTOR; + DRMP3_DEQ_COUNT1(2); + DRMP3_DEQ_COUNT1(3); + DRMP3_CHECK_BITS; + } + + bs->pos = layer3gr_limit; +} + +static void drmp3_L3_midside_stereo(float *left, int n) +{ + int i = 0; + float *right = left + 576; +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (; i < n - 3; i += 4) + { + drmp3_f4 vl = DRMP3_VLD(left + i); + drmp3_f4 vr = DRMP3_VLD(right + i); + DRMP3_VSTORE(left + i, DRMP3_VADD(vl, vr)); + DRMP3_VSTORE(right + i, DRMP3_VSUB(vl, vr)); + } +#endif + for (; i < n; i++) + { + float a = left[i]; + float b = right[i]; + left[i] = a + b; + right[i] = a - b; + } +} + +static void drmp3_L3_intensity_stereo_band(float *left, int n, float kl, float kr) +{ + int i; + for (i = 0; i < n; i++) + { + left[i + 576] = left[i]*kr; + left[i] = left[i]*kl; + } +} + +static void drmp3_L3_stereo_top_band(const float *right, const drmp3_uint8 *sfb, int nbands, int max_band[3]) +{ + int i, k; + + max_band[0] = max_band[1] = max_band[2] = -1; + + for (i = 0; i < nbands; i++) + { + for (k = 0; k < sfb[i]; k += 2) + { + if (right[k] != 0 || right[k + 1] != 0) + { + max_band[i % 3] = i; + break; + } + } + right += sfb[i]; + } +} + +static void drmp3_L3_stereo_process(float *left, const drmp3_uint8 *ist_pos, const drmp3_uint8 *sfb, const drmp3_uint8 *hdr, int max_band[3], int mpeg2_sh) +{ + static const float g_pan[7*2] = { 0,1,0.21132487f,0.78867513f,0.36602540f,0.63397460f,0.5f,0.5f,0.63397460f,0.36602540f,0.78867513f,0.21132487f,1,0 }; + unsigned i, max_pos = DRMP3_HDR_TEST_MPEG1(hdr) ? 7 : 64; + + for (i = 0; sfb[i]; i++) + { + unsigned ipos = ist_pos[i]; + if ((int)i > max_band[i % 3] && ipos < max_pos) + { + float kl, kr, s = DRMP3_HDR_TEST_MS_STEREO(hdr) ? 1.41421356f : 1; + if (DRMP3_HDR_TEST_MPEG1(hdr)) + { + kl = g_pan[2*ipos]; + kr = g_pan[2*ipos + 1]; + } else + { + kl = 1; + kr = drmp3_L3_ldexp_q2(1, (ipos + 1) >> 1 << mpeg2_sh); + if (ipos & 1) + { + kl = kr; + kr = 1; + } + } + drmp3_L3_intensity_stereo_band(left, sfb[i], kl*s, kr*s); + } else if (DRMP3_HDR_TEST_MS_STEREO(hdr)) + { + drmp3_L3_midside_stereo(left, sfb[i]); + } + left += sfb[i]; + } +} + +static void drmp3_L3_intensity_stereo(float *left, drmp3_uint8 *ist_pos, const drmp3_L3_gr_info *gr, const drmp3_uint8 *hdr) +{ + int max_band[3], n_sfb = gr->n_long_sfb + gr->n_short_sfb; + int i, max_blocks = gr->n_short_sfb ? 3 : 1; + + drmp3_L3_stereo_top_band(left + 576, gr->sfbtab, n_sfb, max_band); + if (gr->n_long_sfb) + { + max_band[0] = max_band[1] = max_band[2] = DRMP3_MAX(DRMP3_MAX(max_band[0], max_band[1]), max_band[2]); + } + for (i = 0; i < max_blocks; i++) + { + int default_pos = DRMP3_HDR_TEST_MPEG1(hdr) ? 3 : 0; + int itop = n_sfb - max_blocks + i; + int prev = itop - max_blocks; + ist_pos[itop] = (drmp3_uint8)(max_band[i] >= prev ? default_pos : ist_pos[prev]); + } + drmp3_L3_stereo_process(left, ist_pos, gr->sfbtab, hdr, max_band, gr[1].scalefac_compress & 1); +} + +static void drmp3_L3_reorder(float *grbuf, float *scratch, const drmp3_uint8 *sfb) +{ + int i, len; + float *src = grbuf, *dst = scratch; + + for (;0 != (len = *sfb); sfb += 3, src += 2*len) + { + for (i = 0; i < len; i++, src++) + { + *dst++ = src[0*len]; + *dst++ = src[1*len]; + *dst++ = src[2*len]; + } + } + memcpy(grbuf, scratch, (dst - scratch)*sizeof(float)); +} + +static void drmp3_L3_antialias(float *grbuf, int nbands) +{ + static const float g_aa[2][8] = { + {0.85749293f,0.88174200f,0.94962865f,0.98331459f,0.99551782f,0.99916056f,0.99989920f,0.99999316f}, + {0.51449576f,0.47173197f,0.31337745f,0.18191320f,0.09457419f,0.04096558f,0.01419856f,0.00369997f} + }; + + for (; nbands > 0; nbands--, grbuf += 18) + { + int i = 0; +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (; i < 8; i += 4) + { + drmp3_f4 vu = DRMP3_VLD(grbuf + 18 + i); + drmp3_f4 vd = DRMP3_VLD(grbuf + 14 - i); + drmp3_f4 vc0 = DRMP3_VLD(g_aa[0] + i); + drmp3_f4 vc1 = DRMP3_VLD(g_aa[1] + i); + vd = DRMP3_VREV(vd); + DRMP3_VSTORE(grbuf + 18 + i, DRMP3_VSUB(DRMP3_VMUL(vu, vc0), DRMP3_VMUL(vd, vc1))); + vd = DRMP3_VADD(DRMP3_VMUL(vu, vc1), DRMP3_VMUL(vd, vc0)); + DRMP3_VSTORE(grbuf + 14 - i, DRMP3_VREV(vd)); + } +#endif +#ifndef DR_MP3_ONLY_SIMD + for(; i < 8; i++) + { + float u = grbuf[18 + i]; + float d = grbuf[17 - i]; + grbuf[18 + i] = u*g_aa[0][i] - d*g_aa[1][i]; + grbuf[17 - i] = u*g_aa[1][i] + d*g_aa[0][i]; + } +#endif + } +} + +static void drmp3_L3_dct3_9(float *y) +{ + float s0, s1, s2, s3, s4, s5, s6, s7, s8, t0, t2, t4; + + s0 = y[0]; s2 = y[2]; s4 = y[4]; s6 = y[6]; s8 = y[8]; + t0 = s0 + s6*0.5f; + s0 -= s6; + t4 = (s4 + s2)*0.93969262f; + t2 = (s8 + s2)*0.76604444f; + s6 = (s4 - s8)*0.17364818f; + s4 += s8 - s2; + + s2 = s0 - s4*0.5f; + y[4] = s4 + s0; + s8 = t0 - t2 + s6; + s0 = t0 - t4 + t2; + s4 = t0 + t4 - s6; + + s1 = y[1]; s3 = y[3]; s5 = y[5]; s7 = y[7]; + + s3 *= 0.86602540f; + t0 = (s5 + s1)*0.98480775f; + t4 = (s5 - s7)*0.34202014f; + t2 = (s1 + s7)*0.64278761f; + s1 = (s1 - s5 - s7)*0.86602540f; + + s5 = t0 - s3 - t2; + s7 = t4 - s3 - t0; + s3 = t4 + s3 - t2; + + y[0] = s4 - s7; + y[1] = s2 + s1; + y[2] = s0 - s3; + y[3] = s8 + s5; + y[5] = s8 - s5; + y[6] = s0 + s3; + y[7] = s2 - s1; + y[8] = s4 + s7; +} + +static void drmp3_L3_imdct36(float *grbuf, float *overlap, const float *window, int nbands) +{ + int i, j; + static const float g_twid9[18] = { + 0.73727734f,0.79335334f,0.84339145f,0.88701083f,0.92387953f,0.95371695f,0.97629601f,0.99144486f,0.99904822f,0.67559021f,0.60876143f,0.53729961f,0.46174861f,0.38268343f,0.30070580f,0.21643961f,0.13052619f,0.04361938f + }; + + for (j = 0; j < nbands; j++, grbuf += 18, overlap += 9) + { + float co[9], si[9]; + co[0] = -grbuf[0]; + si[0] = grbuf[17]; + for (i = 0; i < 4; i++) + { + si[8 - 2*i] = grbuf[4*i + 1] - grbuf[4*i + 2]; + co[1 + 2*i] = grbuf[4*i + 1] + grbuf[4*i + 2]; + si[7 - 2*i] = grbuf[4*i + 4] - grbuf[4*i + 3]; + co[2 + 2*i] = -(grbuf[4*i + 3] + grbuf[4*i + 4]); + } + drmp3_L3_dct3_9(co); + drmp3_L3_dct3_9(si); + + si[1] = -si[1]; + si[3] = -si[3]; + si[5] = -si[5]; + si[7] = -si[7]; + + i = 0; + +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (; i < 8; i += 4) + { + drmp3_f4 vovl = DRMP3_VLD(overlap + i); + drmp3_f4 vc = DRMP3_VLD(co + i); + drmp3_f4 vs = DRMP3_VLD(si + i); + drmp3_f4 vr0 = DRMP3_VLD(g_twid9 + i); + drmp3_f4 vr1 = DRMP3_VLD(g_twid9 + 9 + i); + drmp3_f4 vw0 = DRMP3_VLD(window + i); + drmp3_f4 vw1 = DRMP3_VLD(window + 9 + i); + drmp3_f4 vsum = DRMP3_VADD(DRMP3_VMUL(vc, vr1), DRMP3_VMUL(vs, vr0)); + DRMP3_VSTORE(overlap + i, DRMP3_VSUB(DRMP3_VMUL(vc, vr0), DRMP3_VMUL(vs, vr1))); + DRMP3_VSTORE(grbuf + i, DRMP3_VSUB(DRMP3_VMUL(vovl, vw0), DRMP3_VMUL(vsum, vw1))); + vsum = DRMP3_VADD(DRMP3_VMUL(vovl, vw1), DRMP3_VMUL(vsum, vw0)); + DRMP3_VSTORE(grbuf + 14 - i, DRMP3_VREV(vsum)); + } +#endif + for (; i < 9; i++) + { + float ovl = overlap[i]; + float sum = co[i]*g_twid9[9 + i] + si[i]*g_twid9[0 + i]; + overlap[i] = co[i]*g_twid9[0 + i] - si[i]*g_twid9[9 + i]; + grbuf[i] = ovl*window[0 + i] - sum*window[9 + i]; + grbuf[17 - i] = ovl*window[9 + i] + sum*window[0 + i]; + } + } +} + +static void drmp3_L3_idct3(float x0, float x1, float x2, float *dst) +{ + float m1 = x1*0.86602540f; + float a1 = x0 - x2*0.5f; + dst[1] = x0 + x2; + dst[0] = a1 + m1; + dst[2] = a1 - m1; +} + +static void drmp3_L3_imdct12(float *x, float *dst, float *overlap) +{ + static const float g_twid3[6] = { 0.79335334f,0.92387953f,0.99144486f, 0.60876143f,0.38268343f,0.13052619f }; + float co[3], si[3]; + int i; + + drmp3_L3_idct3(-x[0], x[6] + x[3], x[12] + x[9], co); + drmp3_L3_idct3(x[15], x[12] - x[9], x[6] - x[3], si); + si[1] = -si[1]; + + for (i = 0; i < 3; i++) + { + float ovl = overlap[i]; + float sum = co[i]*g_twid3[3 + i] + si[i]*g_twid3[0 + i]; + overlap[i] = co[i]*g_twid3[0 + i] - si[i]*g_twid3[3 + i]; + dst[i] = ovl*g_twid3[2 - i] - sum*g_twid3[5 - i]; + dst[5 - i] = ovl*g_twid3[5 - i] + sum*g_twid3[2 - i]; + } +} + +static void drmp3_L3_imdct_short(float *grbuf, float *overlap, int nbands) +{ + for (;nbands > 0; nbands--, overlap += 9, grbuf += 18) + { + float tmp[18]; + memcpy(tmp, grbuf, sizeof(tmp)); + memcpy(grbuf, overlap, 6*sizeof(float)); + drmp3_L3_imdct12(tmp, grbuf + 6, overlap + 6); + drmp3_L3_imdct12(tmp + 1, grbuf + 12, overlap + 6); + drmp3_L3_imdct12(tmp + 2, overlap, overlap + 6); + } +} + +static void drmp3_L3_change_sign(float *grbuf) +{ + int b, i; + for (b = 0, grbuf += 18; b < 32; b += 2, grbuf += 36) + for (i = 1; i < 18; i += 2) + grbuf[i] = -grbuf[i]; +} + +static void drmp3_L3_imdct_gr(float *grbuf, float *overlap, unsigned block_type, unsigned n_long_bands) +{ + static const float g_mdct_window[2][18] = { + { 0.99904822f,0.99144486f,0.97629601f,0.95371695f,0.92387953f,0.88701083f,0.84339145f,0.79335334f,0.73727734f,0.04361938f,0.13052619f,0.21643961f,0.30070580f,0.38268343f,0.46174861f,0.53729961f,0.60876143f,0.67559021f }, + { 1,1,1,1,1,1,0.99144486f,0.92387953f,0.79335334f,0,0,0,0,0,0,0.13052619f,0.38268343f,0.60876143f } + }; + if (n_long_bands) + { + drmp3_L3_imdct36(grbuf, overlap, g_mdct_window[0], n_long_bands); + grbuf += 18*n_long_bands; + overlap += 9*n_long_bands; + } + if (block_type == DRMP3_SHORT_BLOCK_TYPE) + drmp3_L3_imdct_short(grbuf, overlap, 32 - n_long_bands); + else + drmp3_L3_imdct36(grbuf, overlap, g_mdct_window[block_type == DRMP3_STOP_BLOCK_TYPE], 32 - n_long_bands); +} + +static void drmp3_L3_save_reservoir(drmp3dec *h, drmp3dec_scratch *s) +{ + int pos = (s->bs.pos + 7)/8u; + int remains = s->bs.limit/8u - pos; + if (remains > DRMP3_MAX_BITRESERVOIR_BYTES) + { + pos += remains - DRMP3_MAX_BITRESERVOIR_BYTES; + remains = DRMP3_MAX_BITRESERVOIR_BYTES; + } + if (remains > 0) + { + memmove(h->reserv_buf, s->maindata + pos, remains); + } + h->reserv = remains; +} + +static int drmp3_L3_restore_reservoir(drmp3dec *h, drmp3_bs *bs, drmp3dec_scratch *s, int main_data_begin) +{ + int frame_bytes = (bs->limit - bs->pos)/8; + int bytes_have = DRMP3_MIN(h->reserv, main_data_begin); + memcpy(s->maindata, h->reserv_buf + DRMP3_MAX(0, h->reserv - main_data_begin), DRMP3_MIN(h->reserv, main_data_begin)); + memcpy(s->maindata + bytes_have, bs->buf + bs->pos/8, frame_bytes); + drmp3_bs_init(&s->bs, s->maindata, bytes_have + frame_bytes); + return h->reserv >= main_data_begin; +} + +static void drmp3_L3_decode(drmp3dec *h, drmp3dec_scratch *s, drmp3_L3_gr_info *gr_info, int nch) +{ + int ch; + + for (ch = 0; ch < nch; ch++) + { + int layer3gr_limit = s->bs.pos + gr_info[ch].part_23_length; + drmp3_L3_decode_scalefactors(h->header, s->ist_pos[ch], &s->bs, gr_info + ch, s->scf, ch); + drmp3_L3_huffman(s->grbuf[ch], &s->bs, gr_info + ch, s->scf, layer3gr_limit); + } + + if (DRMP3_HDR_TEST_I_STEREO(h->header)) + { + drmp3_L3_intensity_stereo(s->grbuf[0], s->ist_pos[1], gr_info, h->header); + } else if (DRMP3_HDR_IS_MS_STEREO(h->header)) + { + drmp3_L3_midside_stereo(s->grbuf[0], 576); + } + + for (ch = 0; ch < nch; ch++, gr_info++) + { + int aa_bands = 31; + int n_long_bands = (gr_info->mixed_block_flag ? 2 : 0) << (int)(DRMP3_HDR_GET_MY_SAMPLE_RATE(h->header) == 2); + + if (gr_info->n_short_sfb) + { + aa_bands = n_long_bands - 1; + drmp3_L3_reorder(s->grbuf[ch] + n_long_bands*18, s->syn[0], gr_info->sfbtab + gr_info->n_long_sfb); + } + + drmp3_L3_antialias(s->grbuf[ch], aa_bands); + drmp3_L3_imdct_gr(s->grbuf[ch], h->mdct_overlap[ch], gr_info->block_type, n_long_bands); + drmp3_L3_change_sign(s->grbuf[ch]); + } +} + +static void drmp3d_DCT_II(float *grbuf, int n) +{ + static const float g_sec[24] = { + 10.19000816f,0.50060302f,0.50241929f,3.40760851f,0.50547093f,0.52249861f,2.05778098f,0.51544732f,0.56694406f,1.48416460f,0.53104258f,0.64682180f,1.16943991f,0.55310392f,0.78815460f,0.97256821f,0.58293498f,1.06067765f,0.83934963f,0.62250412f,1.72244716f,0.74453628f,0.67480832f,5.10114861f + }; + int i, k = 0; +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (; k < n; k += 4) + { + drmp3_f4 t[4][8], *x; + float *y = grbuf + k; + + for (x = t[0], i = 0; i < 8; i++, x++) + { + drmp3_f4 x0 = DRMP3_VLD(&y[i*18]); + drmp3_f4 x1 = DRMP3_VLD(&y[(15 - i)*18]); + drmp3_f4 x2 = DRMP3_VLD(&y[(16 + i)*18]); + drmp3_f4 x3 = DRMP3_VLD(&y[(31 - i)*18]); + drmp3_f4 t0 = DRMP3_VADD(x0, x3); + drmp3_f4 t1 = DRMP3_VADD(x1, x2); + drmp3_f4 t2 = DRMP3_VMUL_S(DRMP3_VSUB(x1, x2), g_sec[3*i + 0]); + drmp3_f4 t3 = DRMP3_VMUL_S(DRMP3_VSUB(x0, x3), g_sec[3*i + 1]); + x[0] = DRMP3_VADD(t0, t1); + x[8] = DRMP3_VMUL_S(DRMP3_VSUB(t0, t1), g_sec[3*i + 2]); + x[16] = DRMP3_VADD(t3, t2); + x[24] = DRMP3_VMUL_S(DRMP3_VSUB(t3, t2), g_sec[3*i + 2]); + } + for (x = t[0], i = 0; i < 4; i++, x += 8) + { + drmp3_f4 x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7], xt; + xt = DRMP3_VSUB(x0, x7); x0 = DRMP3_VADD(x0, x7); + x7 = DRMP3_VSUB(x1, x6); x1 = DRMP3_VADD(x1, x6); + x6 = DRMP3_VSUB(x2, x5); x2 = DRMP3_VADD(x2, x5); + x5 = DRMP3_VSUB(x3, x4); x3 = DRMP3_VADD(x3, x4); + x4 = DRMP3_VSUB(x0, x3); x0 = DRMP3_VADD(x0, x3); + x3 = DRMP3_VSUB(x1, x2); x1 = DRMP3_VADD(x1, x2); + x[0] = DRMP3_VADD(x0, x1); + x[4] = DRMP3_VMUL_S(DRMP3_VSUB(x0, x1), 0.70710677f); + x5 = DRMP3_VADD(x5, x6); + x6 = DRMP3_VMUL_S(DRMP3_VADD(x6, x7), 0.70710677f); + x7 = DRMP3_VADD(x7, xt); + x3 = DRMP3_VMUL_S(DRMP3_VADD(x3, x4), 0.70710677f); + x5 = DRMP3_VSUB(x5, DRMP3_VMUL_S(x7, 0.198912367f)); /* rotate by PI/8 */ + x7 = DRMP3_VADD(x7, DRMP3_VMUL_S(x5, 0.382683432f)); + x5 = DRMP3_VSUB(x5, DRMP3_VMUL_S(x7, 0.198912367f)); + x0 = DRMP3_VSUB(xt, x6); xt = DRMP3_VADD(xt, x6); + x[1] = DRMP3_VMUL_S(DRMP3_VADD(xt, x7), 0.50979561f); + x[2] = DRMP3_VMUL_S(DRMP3_VADD(x4, x3), 0.54119611f); + x[3] = DRMP3_VMUL_S(DRMP3_VSUB(x0, x5), 0.60134488f); + x[5] = DRMP3_VMUL_S(DRMP3_VADD(x0, x5), 0.89997619f); + x[6] = DRMP3_VMUL_S(DRMP3_VSUB(x4, x3), 1.30656302f); + x[7] = DRMP3_VMUL_S(DRMP3_VSUB(xt, x7), 2.56291556f); + } + + if (k > n - 3) + { +#if DRMP3_HAVE_SSE +#define DRMP3_VSAVE2(i, v) _mm_storel_pi((__m64 *)(void*)&y[i*18], v) +#else +#define DRMP3_VSAVE2(i, v) vst1_f32((float32_t *)&y[i*18], vget_low_f32(v)) +#endif + for (i = 0; i < 7; i++, y += 4*18) + { + drmp3_f4 s = DRMP3_VADD(t[3][i], t[3][i + 1]); + DRMP3_VSAVE2(0, t[0][i]); + DRMP3_VSAVE2(1, DRMP3_VADD(t[2][i], s)); + DRMP3_VSAVE2(2, DRMP3_VADD(t[1][i], t[1][i + 1])); + DRMP3_VSAVE2(3, DRMP3_VADD(t[2][1 + i], s)); + } + DRMP3_VSAVE2(0, t[0][7]); + DRMP3_VSAVE2(1, DRMP3_VADD(t[2][7], t[3][7])); + DRMP3_VSAVE2(2, t[1][7]); + DRMP3_VSAVE2(3, t[3][7]); + } else + { +#define DRMP3_VSAVE4(i, v) DRMP3_VSTORE(&y[i*18], v) + for (i = 0; i < 7; i++, y += 4*18) + { + drmp3_f4 s = DRMP3_VADD(t[3][i], t[3][i + 1]); + DRMP3_VSAVE4(0, t[0][i]); + DRMP3_VSAVE4(1, DRMP3_VADD(t[2][i], s)); + DRMP3_VSAVE4(2, DRMP3_VADD(t[1][i], t[1][i + 1])); + DRMP3_VSAVE4(3, DRMP3_VADD(t[2][1 + i], s)); + } + DRMP3_VSAVE4(0, t[0][7]); + DRMP3_VSAVE4(1, DRMP3_VADD(t[2][7], t[3][7])); + DRMP3_VSAVE4(2, t[1][7]); + DRMP3_VSAVE4(3, t[3][7]); + } + } else +#endif +#ifdef DR_MP3_ONLY_SIMD + {} +#else + for (; k < n; k++) + { + float t[4][8], *x, *y = grbuf + k; + + for (x = t[0], i = 0; i < 8; i++, x++) + { + float x0 = y[i*18]; + float x1 = y[(15 - i)*18]; + float x2 = y[(16 + i)*18]; + float x3 = y[(31 - i)*18]; + float t0 = x0 + x3; + float t1 = x1 + x2; + float t2 = (x1 - x2)*g_sec[3*i + 0]; + float t3 = (x0 - x3)*g_sec[3*i + 1]; + x[0] = t0 + t1; + x[8] = (t0 - t1)*g_sec[3*i + 2]; + x[16] = t3 + t2; + x[24] = (t3 - t2)*g_sec[3*i + 2]; + } + for (x = t[0], i = 0; i < 4; i++, x += 8) + { + float x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4], x5 = x[5], x6 = x[6], x7 = x[7], xt; + xt = x0 - x7; x0 += x7; + x7 = x1 - x6; x1 += x6; + x6 = x2 - x5; x2 += x5; + x5 = x3 - x4; x3 += x4; + x4 = x0 - x3; x0 += x3; + x3 = x1 - x2; x1 += x2; + x[0] = x0 + x1; + x[4] = (x0 - x1)*0.70710677f; + x5 = x5 + x6; + x6 = (x6 + x7)*0.70710677f; + x7 = x7 + xt; + x3 = (x3 + x4)*0.70710677f; + x5 -= x7*0.198912367f; /* rotate by PI/8 */ + x7 += x5*0.382683432f; + x5 -= x7*0.198912367f; + x0 = xt - x6; xt += x6; + x[1] = (xt + x7)*0.50979561f; + x[2] = (x4 + x3)*0.54119611f; + x[3] = (x0 - x5)*0.60134488f; + x[5] = (x0 + x5)*0.89997619f; + x[6] = (x4 - x3)*1.30656302f; + x[7] = (xt - x7)*2.56291556f; + + } + for (i = 0; i < 7; i++, y += 4*18) + { + y[0*18] = t[0][i]; + y[1*18] = t[2][i] + t[3][i] + t[3][i + 1]; + y[2*18] = t[1][i] + t[1][i + 1]; + y[3*18] = t[2][i + 1] + t[3][i] + t[3][i + 1]; + } + y[0*18] = t[0][7]; + y[1*18] = t[2][7] + t[3][7]; + y[2*18] = t[1][7]; + y[3*18] = t[3][7]; + } +#endif +} + +#ifndef DR_MP3_FLOAT_OUTPUT +typedef drmp3_int16 drmp3d_sample_t; + +static drmp3_int16 drmp3d_scale_pcm(float sample) +{ + if (sample >= 32766.5) return (drmp3_int16) 32767; + if (sample <= -32767.5) return (drmp3_int16)-32768; + drmp3_int16 s = (drmp3_int16)(sample + .5f); + s -= (s < 0); /* away from zero, to be compliant */ + return (drmp3_int16)s; +} +#else +typedef float drmp3d_sample_t; + +static float drmp3d_scale_pcm(float sample) +{ + return sample*(1.f/32768.f); +} +#endif + +static void drmp3d_synth_pair(drmp3d_sample_t *pcm, int nch, const float *z) +{ + float a; + a = (z[14*64] - z[ 0]) * 29; + a += (z[ 1*64] + z[13*64]) * 213; + a += (z[12*64] - z[ 2*64]) * 459; + a += (z[ 3*64] + z[11*64]) * 2037; + a += (z[10*64] - z[ 4*64]) * 5153; + a += (z[ 5*64] + z[ 9*64]) * 6574; + a += (z[ 8*64] - z[ 6*64]) * 37489; + a += z[ 7*64] * 75038; + pcm[0] = drmp3d_scale_pcm(a); + + z += 2; + a = z[14*64] * 104; + a += z[12*64] * 1567; + a += z[10*64] * 9727; + a += z[ 8*64] * 64019; + a += z[ 6*64] * -9975; + a += z[ 4*64] * -45; + a += z[ 2*64] * 146; + a += z[ 0*64] * -5; + pcm[16*nch] = drmp3d_scale_pcm(a); +} + +static void drmp3d_synth(float *xl, drmp3d_sample_t *dstl, int nch, float *lins) +{ + int i; + float *xr = xl + 576*(nch - 1); + drmp3d_sample_t *dstr = dstl + (nch - 1); + + static const float g_win[] = { + -1,26,-31,208,218,401,-519,2063,2000,4788,-5517,7134,5959,35640,-39336,74992, + -1,24,-35,202,222,347,-581,2080,1952,4425,-5879,7640,5288,33791,-41176,74856, + -1,21,-38,196,225,294,-645,2087,1893,4063,-6237,8092,4561,31947,-43006,74630, + -1,19,-41,190,227,244,-711,2085,1822,3705,-6589,8492,3776,30112,-44821,74313, + -1,17,-45,183,228,197,-779,2075,1739,3351,-6935,8840,2935,28289,-46617,73908, + -1,16,-49,176,228,153,-848,2057,1644,3004,-7271,9139,2037,26482,-48390,73415, + -2,14,-53,169,227,111,-919,2032,1535,2663,-7597,9389,1082,24694,-50137,72835, + -2,13,-58,161,224,72,-991,2001,1414,2330,-7910,9592,70,22929,-51853,72169, + -2,11,-63,154,221,36,-1064,1962,1280,2006,-8209,9750,-998,21189,-53534,71420, + -2,10,-68,147,215,2,-1137,1919,1131,1692,-8491,9863,-2122,19478,-55178,70590, + -3,9,-73,139,208,-29,-1210,1870,970,1388,-8755,9935,-3300,17799,-56778,69679, + -3,8,-79,132,200,-57,-1283,1817,794,1095,-8998,9966,-4533,16155,-58333,68692, + -4,7,-85,125,189,-83,-1356,1759,605,814,-9219,9959,-5818,14548,-59838,67629, + -4,7,-91,117,177,-106,-1428,1698,402,545,-9416,9916,-7154,12980,-61289,66494, + -5,6,-97,111,163,-127,-1498,1634,185,288,-9585,9838,-8540,11455,-62684,65290 + }; + float *zlin = lins + 15*64; + const float *w = g_win; + + zlin[4*15] = xl[18*16]; + zlin[4*15 + 1] = xr[18*16]; + zlin[4*15 + 2] = xl[0]; + zlin[4*15 + 3] = xr[0]; + + zlin[4*31] = xl[1 + 18*16]; + zlin[4*31 + 1] = xr[1 + 18*16]; + zlin[4*31 + 2] = xl[1]; + zlin[4*31 + 3] = xr[1]; + + drmp3d_synth_pair(dstr, nch, lins + 4*15 + 1); + drmp3d_synth_pair(dstr + 32*nch, nch, lins + 4*15 + 64 + 1); + drmp3d_synth_pair(dstl, nch, lins + 4*15); + drmp3d_synth_pair(dstl + 32*nch, nch, lins + 4*15 + 64); + +#if DRMP3_HAVE_SIMD + if (drmp3_have_simd()) for (i = 14; i >= 0; i--) + { +#define DRMP3_VLOAD(k) drmp3_f4 w0 = DRMP3_VSET(*w++); drmp3_f4 w1 = DRMP3_VSET(*w++); drmp3_f4 vz = DRMP3_VLD(&zlin[4*i - 64*k]); drmp3_f4 vy = DRMP3_VLD(&zlin[4*i - 64*(15 - k)]); +#define DRMP3_V0(k) { DRMP3_VLOAD(k) b = DRMP3_VADD(DRMP3_VMUL(vz, w1), DRMP3_VMUL(vy, w0)) ; a = DRMP3_VSUB(DRMP3_VMUL(vz, w0), DRMP3_VMUL(vy, w1)); } +#define DRMP3_V1(k) { DRMP3_VLOAD(k) b = DRMP3_VADD(b, DRMP3_VADD(DRMP3_VMUL(vz, w1), DRMP3_VMUL(vy, w0))); a = DRMP3_VADD(a, DRMP3_VSUB(DRMP3_VMUL(vz, w0), DRMP3_VMUL(vy, w1))); } +#define DRMP3_V2(k) { DRMP3_VLOAD(k) b = DRMP3_VADD(b, DRMP3_VADD(DRMP3_VMUL(vz, w1), DRMP3_VMUL(vy, w0))); a = DRMP3_VADD(a, DRMP3_VSUB(DRMP3_VMUL(vy, w1), DRMP3_VMUL(vz, w0))); } + drmp3_f4 a, b; + zlin[4*i] = xl[18*(31 - i)]; + zlin[4*i + 1] = xr[18*(31 - i)]; + zlin[4*i + 2] = xl[1 + 18*(31 - i)]; + zlin[4*i + 3] = xr[1 + 18*(31 - i)]; + zlin[4*i + 64] = xl[1 + 18*(1 + i)]; + zlin[4*i + 64 + 1] = xr[1 + 18*(1 + i)]; + zlin[4*i - 64 + 2] = xl[18*(1 + i)]; + zlin[4*i - 64 + 3] = xr[18*(1 + i)]; + + DRMP3_V0(0) DRMP3_V2(1) DRMP3_V1(2) DRMP3_V2(3) DRMP3_V1(4) DRMP3_V2(5) DRMP3_V1(6) DRMP3_V2(7) + + { +#ifndef DR_MP3_FLOAT_OUTPUT +#if DRMP3_HAVE_SSE + static const drmp3_f4 g_max = { 32767.0f, 32767.0f, 32767.0f, 32767.0f }; + static const drmp3_f4 g_min = { -32768.0f, -32768.0f, -32768.0f, -32768.0f }; + __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)), + _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min))); + dstr[(15 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 1); + dstr[(17 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 5); + dstl[(15 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 0); + dstl[(17 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 4); + dstr[(47 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 3); + dstr[(49 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 7); + dstl[(47 - i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 2); + dstl[(49 + i)*nch] = (drmp3_int16)_mm_extract_epi16(pcm8, 6); +#else + int16x4_t pcma, pcmb; + a = DRMP3_VADD(a, DRMP3_VSET(0.5f)); + b = DRMP3_VADD(b, DRMP3_VSET(0.5f)); + pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, DRMP3_VSET(0))))); + pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, DRMP3_VSET(0))))); + vst1_lane_s16(dstr + (15 - i)*nch, pcma, 1); + vst1_lane_s16(dstr + (17 + i)*nch, pcmb, 1); + vst1_lane_s16(dstl + (15 - i)*nch, pcma, 0); + vst1_lane_s16(dstl + (17 + i)*nch, pcmb, 0); + vst1_lane_s16(dstr + (47 - i)*nch, pcma, 3); + vst1_lane_s16(dstr + (49 + i)*nch, pcmb, 3); + vst1_lane_s16(dstl + (47 - i)*nch, pcma, 2); + vst1_lane_s16(dstl + (49 + i)*nch, pcmb, 2); +#endif +#else + static const drmp3_f4 g_scale = { 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f }; + a = DRMP3_VMUL(a, g_scale); + b = DRMP3_VMUL(b, g_scale); +#if DRMP3_HAVE_SSE + _mm_store_ss(dstr + (15 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(1, 1, 1, 1))); + _mm_store_ss(dstr + (17 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(1, 1, 1, 1))); + _mm_store_ss(dstl + (15 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(0, 0, 0, 0))); + _mm_store_ss(dstl + (17 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(0, 0, 0, 0))); + _mm_store_ss(dstr + (47 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 3, 3, 3))); + _mm_store_ss(dstr + (49 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(3, 3, 3, 3))); + _mm_store_ss(dstl + (47 - i)*nch, _mm_shuffle_ps(a, a, _MM_SHUFFLE(2, 2, 2, 2))); + _mm_store_ss(dstl + (49 + i)*nch, _mm_shuffle_ps(b, b, _MM_SHUFFLE(2, 2, 2, 2))); +#else + vst1q_lane_f32(dstr + (15 - i)*nch, a, 1); + vst1q_lane_f32(dstr + (17 + i)*nch, b, 1); + vst1q_lane_f32(dstl + (15 - i)*nch, a, 0); + vst1q_lane_f32(dstl + (17 + i)*nch, b, 0); + vst1q_lane_f32(dstr + (47 - i)*nch, a, 3); + vst1q_lane_f32(dstr + (49 + i)*nch, b, 3); + vst1q_lane_f32(dstl + (47 - i)*nch, a, 2); + vst1q_lane_f32(dstl + (49 + i)*nch, b, 2); +#endif +#endif /* DR_MP3_FLOAT_OUTPUT */ + } + } else +#endif +#ifdef DR_MP3_ONLY_SIMD + {} +#else + for (i = 14; i >= 0; i--) + { +#define DRMP3_LOAD(k) float w0 = *w++; float w1 = *w++; float *vz = &zlin[4*i - k*64]; float *vy = &zlin[4*i - (15 - k)*64]; +#define DRMP3_S0(k) { int j; DRMP3_LOAD(k); for (j = 0; j < 4; j++) b[j] = vz[j]*w1 + vy[j]*w0, a[j] = vz[j]*w0 - vy[j]*w1; } +#define DRMP3_S1(k) { int j; DRMP3_LOAD(k); for (j = 0; j < 4; j++) b[j] += vz[j]*w1 + vy[j]*w0, a[j] += vz[j]*w0 - vy[j]*w1; } +#define DRMP3_S2(k) { int j; DRMP3_LOAD(k); for (j = 0; j < 4; j++) b[j] += vz[j]*w1 + vy[j]*w0, a[j] += vy[j]*w1 - vz[j]*w0; } + float a[4], b[4]; + + zlin[4*i] = xl[18*(31 - i)]; + zlin[4*i + 1] = xr[18*(31 - i)]; + zlin[4*i + 2] = xl[1 + 18*(31 - i)]; + zlin[4*i + 3] = xr[1 + 18*(31 - i)]; + zlin[4*(i + 16)] = xl[1 + 18*(1 + i)]; + zlin[4*(i + 16) + 1] = xr[1 + 18*(1 + i)]; + zlin[4*(i - 16) + 2] = xl[18*(1 + i)]; + zlin[4*(i - 16) + 3] = xr[18*(1 + i)]; + + DRMP3_S0(0) DRMP3_S2(1) DRMP3_S1(2) DRMP3_S2(3) DRMP3_S1(4) DRMP3_S2(5) DRMP3_S1(6) DRMP3_S2(7) + + dstr[(15 - i)*nch] = drmp3d_scale_pcm(a[1]); + dstr[(17 + i)*nch] = drmp3d_scale_pcm(b[1]); + dstl[(15 - i)*nch] = drmp3d_scale_pcm(a[0]); + dstl[(17 + i)*nch] = drmp3d_scale_pcm(b[0]); + dstr[(47 - i)*nch] = drmp3d_scale_pcm(a[3]); + dstr[(49 + i)*nch] = drmp3d_scale_pcm(b[3]); + dstl[(47 - i)*nch] = drmp3d_scale_pcm(a[2]); + dstl[(49 + i)*nch] = drmp3d_scale_pcm(b[2]); + } +#endif +} + +static void drmp3d_synth_granule(float *qmf_state, float *grbuf, int nbands, int nch, drmp3d_sample_t *pcm, float *lins) +{ + int i; + for (i = 0; i < nch; i++) + { + drmp3d_DCT_II(grbuf + 576*i, nbands); + } + + memcpy(lins, qmf_state, sizeof(float)*15*64); + + for (i = 0; i < nbands; i += 2) + { + drmp3d_synth(grbuf + i, pcm + 32*nch*i, nch, lins + i*64); + } +#ifndef DR_MP3_NONSTANDARD_BUT_LOGICAL + if (nch == 1) + { + for (i = 0; i < 15*64; i += 2) + { + qmf_state[i] = lins[nbands*64 + i]; + } + } else +#endif + { + memcpy(qmf_state, lins + nbands*64, sizeof(float)*15*64); + } +} + +static int drmp3d_match_frame(const drmp3_uint8 *hdr, int mp3_bytes, int frame_bytes) +{ + int i, nmatch; + for (i = 0, nmatch = 0; nmatch < DRMP3_MAX_FRAME_SYNC_MATCHES; nmatch++) + { + i += drmp3_hdr_frame_bytes(hdr + i, frame_bytes) + drmp3_hdr_padding(hdr + i); + if (i + DRMP3_HDR_SIZE > mp3_bytes) + return nmatch > 0; + if (!drmp3_hdr_compare(hdr, hdr + i)) + return 0; + } + return 1; +} + +static int drmp3d_find_frame(const drmp3_uint8 *mp3, int mp3_bytes, int *free_format_bytes, int *ptr_frame_bytes) +{ + int i, k; + for (i = 0; i < mp3_bytes - DRMP3_HDR_SIZE; i++, mp3++) + { + if (drmp3_hdr_valid(mp3)) + { + int frame_bytes = drmp3_hdr_frame_bytes(mp3, *free_format_bytes); + int frame_and_padding = frame_bytes + drmp3_hdr_padding(mp3); + + for (k = DRMP3_HDR_SIZE; !frame_bytes && k < DRMP3_MAX_FREE_FORMAT_FRAME_SIZE && i + 2*k < mp3_bytes - DRMP3_HDR_SIZE; k++) + { + if (drmp3_hdr_compare(mp3, mp3 + k)) + { + int fb = k - drmp3_hdr_padding(mp3); + int nextfb = fb + drmp3_hdr_padding(mp3 + k); + if (i + k + nextfb + DRMP3_HDR_SIZE > mp3_bytes || !drmp3_hdr_compare(mp3, mp3 + k + nextfb)) + continue; + frame_and_padding = k; + frame_bytes = fb; + *free_format_bytes = fb; + } + } + + if ((frame_bytes && i + frame_and_padding <= mp3_bytes && + drmp3d_match_frame(mp3, mp3_bytes - i, frame_bytes)) || + (!i && frame_and_padding == mp3_bytes)) + { + *ptr_frame_bytes = frame_and_padding; + return i; + } + *free_format_bytes = 0; + } + } + *ptr_frame_bytes = 0; + return i; +} + +void drmp3dec_init(drmp3dec *dec) +{ + dec->header[0] = 0; +} + +int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info) +{ + int i = 0, igr, frame_size = 0, success = 1; + const drmp3_uint8 *hdr; + drmp3_bs bs_frame[1]; + drmp3dec_scratch scratch; + + if (mp3_bytes > 4 && dec->header[0] == 0xff && drmp3_hdr_compare(dec->header, mp3)) + { + frame_size = drmp3_hdr_frame_bytes(mp3, dec->free_format_bytes) + drmp3_hdr_padding(mp3); + if (frame_size != mp3_bytes && (frame_size + DRMP3_HDR_SIZE > mp3_bytes || !drmp3_hdr_compare(mp3, mp3 + frame_size))) + { + frame_size = 0; + } + } + if (!frame_size) + { + memset(dec, 0, sizeof(drmp3dec)); + i = drmp3d_find_frame(mp3, mp3_bytes, &dec->free_format_bytes, &frame_size); + if (!frame_size || i + frame_size > mp3_bytes) + { + info->frame_bytes = i; + return 0; + } + } + + hdr = mp3 + i; + memcpy(dec->header, hdr, DRMP3_HDR_SIZE); + info->frame_bytes = i + frame_size; + info->channels = DRMP3_HDR_IS_MONO(hdr) ? 1 : 2; + info->hz = drmp3_hdr_sample_rate_hz(hdr); + info->layer = 4 - DRMP3_HDR_GET_LAYER(hdr); + info->bitrate_kbps = drmp3_hdr_bitrate_kbps(hdr); + + if (!pcm) + { + return drmp3_hdr_frame_samples(hdr); + } + + drmp3_bs_init(bs_frame, hdr + DRMP3_HDR_SIZE, frame_size - DRMP3_HDR_SIZE); + if (DRMP3_HDR_IS_CRC(hdr)) + { + drmp3_bs_get_bits(bs_frame, 16); + } + + if (info->layer == 3) + { + int main_data_begin = drmp3_L3_read_side_info(bs_frame, scratch.gr_info, hdr); + if (main_data_begin < 0 || bs_frame->pos > bs_frame->limit) + { + drmp3dec_init(dec); + return 0; + } + success = drmp3_L3_restore_reservoir(dec, bs_frame, &scratch, main_data_begin); + if (success) + { + for (igr = 0; igr < (DRMP3_HDR_TEST_MPEG1(hdr) ? 2 : 1); igr++, pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*576*info->channels)) + { + memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); + drmp3_L3_decode(dec, &scratch, scratch.gr_info + igr*info->channels, info->channels); + drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 18, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); + } + } + drmp3_L3_save_reservoir(dec, &scratch); + } else + { +#ifdef DR_MP3_ONLY_MP3 + return 0; +#else + drmp3_L12_scale_info sci[1]; + drmp3_L12_read_scale_info(hdr, bs_frame, sci); + + memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); + for (i = 0, igr = 0; igr < 3; igr++) + { + if (12 == (i += drmp3_L12_dequantize_granule(scratch.grbuf[0] + i, bs_frame, sci, info->layer | 1))) + { + i = 0; + drmp3_L12_apply_scf_384(sci, sci->scf + igr, scratch.grbuf[0]); + drmp3d_synth_granule(dec->qmf_state, scratch.grbuf[0], 12, info->channels, (drmp3d_sample_t*)pcm, scratch.syn[0]); + memset(scratch.grbuf[0], 0, 576*2*sizeof(float)); + pcm = DRMP3_OFFSET_PTR(pcm, sizeof(drmp3d_sample_t)*384*info->channels); + } + if (bs_frame->pos > bs_frame->limit) + { + drmp3dec_init(dec); + return 0; + } + } +#endif + } + return success*drmp3_hdr_frame_samples(dec->header); +} + +void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) +{ + if(num_samples > 0) + { + int i = 0; +#if DRMP3_HAVE_SIMD + int aligned_count = num_samples & ~7; + for(; i < aligned_count; i+=8) + { + static const drmp3_f4 g_scale = { 32768.0f, 32768.0f, 32768.0f, 32768.0f }; + drmp3_f4 a = DRMP3_VMUL(DRMP3_VLD(&in[i ]), g_scale); + drmp3_f4 b = DRMP3_VMUL(DRMP3_VLD(&in[i+4]), g_scale); +#if DRMP3_HAVE_SSE + static const drmp3_f4 g_max = { 32767.0f, 32767.0f, 32767.0f, 32767.0f }; + static const drmp3_f4 g_min = { -32768.0f, -32768.0f, -32768.0f, -32768.0f }; + __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)), + _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min))); + out[i ] = (drmp3_int16)_mm_extract_epi16(pcm8, 0); + out[i+1] = (drmp3_int16)_mm_extract_epi16(pcm8, 1); + out[i+2] = (drmp3_int16)_mm_extract_epi16(pcm8, 2); + out[i+3] = (drmp3_int16)_mm_extract_epi16(pcm8, 3); + out[i+4] = (drmp3_int16)_mm_extract_epi16(pcm8, 4); + out[i+5] = (drmp3_int16)_mm_extract_epi16(pcm8, 5); + out[i+6] = (drmp3_int16)_mm_extract_epi16(pcm8, 6); + out[i+7] = (drmp3_int16)_mm_extract_epi16(pcm8, 7); +#else + int16x4_t pcma, pcmb; + a = DRMP3_VADD(a, DRMP3_VSET(0.5f)); + b = DRMP3_VADD(b, DRMP3_VSET(0.5f)); + pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, DRMP3_VSET(0))))); + pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, DRMP3_VSET(0))))); + vst1_lane_s16(out+i , pcma, 0); + vst1_lane_s16(out+i+1, pcma, 1); + vst1_lane_s16(out+i+2, pcma, 2); + vst1_lane_s16(out+i+3, pcma, 3); + vst1_lane_s16(out+i+4, pcmb, 0); + vst1_lane_s16(out+i+5, pcmb, 1); + vst1_lane_s16(out+i+6, pcmb, 2); + vst1_lane_s16(out+i+7, pcmb, 3); +#endif + } +#endif + for(; i < num_samples; i++) + { + float sample = in[i] * 32768.0f; + if (sample >= 32766.5) + out[i] = (drmp3_int16) 32767; + else if (sample <= -32767.5) + out[i] = (drmp3_int16)-32768; + else + { + short s = (drmp3_int16)(sample + .5f); + s -= (s < 0); /* away from zero, to be compliant */ + out[i] = s; + } + } + } +} + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Main Public API +// +/////////////////////////////////////////////////////////////////////////////// + +#if defined(SIZE_MAX) + #define DRMP3_SIZE_MAX SIZE_MAX +#else + #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) + #define DRMP3_SIZE_MAX ((drmp3_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRMP3_SIZE_MAX 0xFFFFFFFF + #endif +#endif + +// Options. +#ifndef DR_MP3_DEFAULT_CHANNELS +#define DR_MP3_DEFAULT_CHANNELS 2 +#endif +#ifndef DR_MP3_DEFAULT_SAMPLE_RATE +#define DR_MP3_DEFAULT_SAMPLE_RATE 44100 +#endif + + +// Standard library stuff. +#ifndef DRMP3_ASSERT +#include +#define DRMP3_ASSERT(expression) assert(expression) +#endif +#ifndef DRMP3_COPY_MEMORY +#define DRMP3_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRMP3_ZERO_MEMORY +#define DRMP3_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif +#define DRMP3_ZERO_OBJECT(p) DRMP3_ZERO_MEMORY((p), sizeof(*(p))) +#ifndef DRMP3_MALLOC +#define DRMP3_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRMP3_REALLOC +#define DRMP3_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRMP3_FREE +#define DRMP3_FREE(p) free((p)) +#endif + +#define drmp3_assert DRMP3_ASSERT +#define drmp3_copy_memory DRMP3_COPY_MEMORY +#define drmp3_zero_memory DRMP3_ZERO_MEMORY +#define drmp3_zero_object DRMP3_ZERO_OBJECT +#define drmp3_malloc DRMP3_MALLOC +#define drmp3_realloc DRMP3_REALLOC + +#define drmp3_countof(x) (sizeof(x) / sizeof(x[0])) +#define drmp3_max(x, y) (((x) > (y)) ? (x) : (y)) +#define drmp3_min(x, y) (((x) < (y)) ? (x) : (y)) + +#define DRMP3_DATA_CHUNK_SIZE 16384 // The size in bytes of each chunk of data to read from the MP3 stream. minimp3 recommends 16K. + +static inline float drmp3_mix_f32(float x, float y, float a) +{ + return x*(1-a) + y*a; +} + +static void drmp3_blend_f32(float* pOut, float* pInA, float* pInB, float factor, drmp3_uint32 channels) +{ + for (drmp3_uint32 i = 0; i < channels; ++i) { + pOut[i] = drmp3_mix_f32(pInA[i], pInB[i], factor); + } +} + +void drmp3_src_cache_init(drmp3_src* pSRC, drmp3_src_cache* pCache) +{ + drmp3_assert(pSRC != NULL); + drmp3_assert(pCache != NULL); + + pCache->pSRC = pSRC; + pCache->cachedFrameCount = 0; + pCache->iNextFrame = 0; +} + +drmp3_uint64 drmp3_src_cache_read_frames(drmp3_src_cache* pCache, drmp3_uint64 frameCount, float* pFramesOut) +{ + drmp3_assert(pCache != NULL); + drmp3_assert(pCache->pSRC != NULL); + drmp3_assert(pCache->pSRC->onRead != NULL); + drmp3_assert(frameCount > 0); + drmp3_assert(pFramesOut != NULL); + + drmp3_uint32 channels = pCache->pSRC->config.channels; + + drmp3_uint64 totalFramesRead = 0; + while (frameCount > 0) { + // If there's anything in memory go ahead and copy that over first. + drmp3_uint64 framesRemainingInMemory = pCache->cachedFrameCount - pCache->iNextFrame; + drmp3_uint64 framesToReadFromMemory = frameCount; + if (framesToReadFromMemory > framesRemainingInMemory) { + framesToReadFromMemory = framesRemainingInMemory; + } + + drmp3_copy_memory(pFramesOut, pCache->pCachedFrames + pCache->iNextFrame*channels, (drmp3_uint32)(framesToReadFromMemory * channels * sizeof(float))); + pCache->iNextFrame += (drmp3_uint32)framesToReadFromMemory; + + totalFramesRead += framesToReadFromMemory; + frameCount -= framesToReadFromMemory; + if (frameCount == 0) { + break; + } + + + // At this point there are still more frames to read from the client, so we'll need to reload the cache with fresh data. + drmp3_assert(frameCount > 0); + pFramesOut += framesToReadFromMemory * channels; + + pCache->iNextFrame = 0; + pCache->cachedFrameCount = 0; + + drmp3_uint32 framesToReadFromClient = drmp3_countof(pCache->pCachedFrames) / pCache->pSRC->config.channels; + if (framesToReadFromClient > pCache->pSRC->config.cacheSizeInFrames) { + framesToReadFromClient = pCache->pSRC->config.cacheSizeInFrames; + } + + pCache->cachedFrameCount = (drmp3_uint32)pCache->pSRC->onRead(pCache->pSRC, framesToReadFromClient, pCache->pCachedFrames, pCache->pSRC->pUserData); + + + // Get out of this loop if nothing was able to be retrieved. + if (pCache->cachedFrameCount == 0) { + break; + } + } + + return totalFramesRead; +} + + +drmp3_uint64 drmp3_src_read_frames_passthrough(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush); +drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush); + +drmp3_bool32 drmp3_src_init(const drmp3_src_config* pConfig, drmp3_src_read_proc onRead, void* pUserData, drmp3_src* pSRC) +{ + if (pSRC == NULL) return DRMP3_FALSE; + drmp3_zero_object(pSRC); + + if (pConfig == NULL || onRead == NULL) return DRMP3_FALSE; + if (pConfig->channels == 0 || pConfig->channels > 2) return DRMP3_FALSE; + + pSRC->config = *pConfig; + pSRC->onRead = onRead; + pSRC->pUserData = pUserData; + + if (pSRC->config.cacheSizeInFrames > DRMP3_SRC_CACHE_SIZE_IN_FRAMES || pSRC->config.cacheSizeInFrames == 0) { + pSRC->config.cacheSizeInFrames = DRMP3_SRC_CACHE_SIZE_IN_FRAMES; + } + + drmp3_src_cache_init(pSRC, &pSRC->cache); + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_src_set_input_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampleRateIn) +{ + if (pSRC == NULL) return DRMP3_FALSE; + + // Must have a sample rate of > 0. + if (sampleRateIn == 0) { + return DRMP3_FALSE; + } + + pSRC->config.sampleRateIn = sampleRateIn; + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_src_set_output_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampleRateOut) +{ + if (pSRC == NULL) return DRMP3_FALSE; + + // Must have a sample rate of > 0. + if (sampleRateOut == 0) { + return DRMP3_FALSE; + } + + pSRC->config.sampleRateOut = sampleRateOut; + return DRMP3_TRUE; +} + +drmp3_uint64 drmp3_src_read_frames_ex(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) +{ + if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) return 0; + + drmp3_src_algorithm algorithm = pSRC->config.algorithm; + + // Always use passthrough if the sample rates are the same. + if (pSRC->config.sampleRateIn == pSRC->config.sampleRateOut) { + algorithm = drmp3_src_algorithm_none; + } + + // Could just use a function pointer instead of a switch for this... + switch (algorithm) + { + case drmp3_src_algorithm_none: return drmp3_src_read_frames_passthrough(pSRC, frameCount, pFramesOut, flush); + case drmp3_src_algorithm_linear: return drmp3_src_read_frames_linear(pSRC, frameCount, pFramesOut, flush); + default: return 0; + } +} + +drmp3_uint64 drmp3_src_read_frames(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut) +{ + return drmp3_src_read_frames_ex(pSRC, frameCount, pFramesOut, DRMP3_FALSE); +} + +drmp3_uint64 drmp3_src_read_frames_passthrough(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) +{ + drmp3_assert(pSRC != NULL); + drmp3_assert(frameCount > 0); + drmp3_assert(pFramesOut != NULL); + + (void)flush; // Passthrough need not care about flushing. + return pSRC->onRead(pSRC, frameCount, pFramesOut, pSRC->pUserData); +} + +drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) +{ + drmp3_assert(pSRC != NULL); + drmp3_assert(frameCount > 0); + drmp3_assert(pFramesOut != NULL); + + // For linear SRC, the bin is only 2 frames: 1 prior, 1 future. + + // Load the bin if necessary. + if (!pSRC->algo.linear.isPrevFramesLoaded) { + drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin); + if (framesRead == 0) { + return 0; + } + pSRC->algo.linear.isPrevFramesLoaded = DRMP3_TRUE; + } + if (!pSRC->algo.linear.isNextFramesLoaded) { + drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin + pSRC->config.channels); + if (framesRead == 0) { + return 0; + } + pSRC->algo.linear.isNextFramesLoaded = DRMP3_TRUE; + } + + float factor = (float)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; + + drmp3_uint64 totalFramesRead = 0; + while (frameCount > 0) { + // The bin is where the previous and next frames are located. + float* pPrevFrame = pSRC->bin; + float* pNextFrame = pSRC->bin + pSRC->config.channels; + + drmp3_blend_f32((float*)pFramesOut, pPrevFrame, pNextFrame, pSRC->algo.linear.alpha, pSRC->config.channels); + + pSRC->algo.linear.alpha += factor; + + // The new alpha value is how we determine whether or not we need to read fresh frames. + drmp3_uint32 framesToReadFromClient = (drmp3_uint32)pSRC->algo.linear.alpha; + pSRC->algo.linear.alpha = pSRC->algo.linear.alpha - framesToReadFromClient; + + for (drmp3_uint32 i = 0; i < framesToReadFromClient; ++i) { + for (drmp3_uint32 j = 0; j < pSRC->config.channels; ++j) { + pPrevFrame[j] = pNextFrame[j]; + } + + drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pNextFrame); + if (framesRead == 0) { + for (drmp3_uint32 j = 0; j < pSRC->config.channels; ++j) { + pNextFrame[j] = 0; + } + + if (pSRC->algo.linear.isNextFramesLoaded) { + pSRC->algo.linear.isNextFramesLoaded = DRMP3_FALSE; + } else { + if (flush) { + pSRC->algo.linear.isPrevFramesLoaded = DRMP3_FALSE; + } + } + + break; + } + } + + pFramesOut = (drmp3_uint8*)pFramesOut + (1 * pSRC->config.channels * sizeof(float)); + frameCount -= 1; + totalFramesRead += 1; + + // If there's no frames available we need to get out of this loop. + if (!pSRC->algo.linear.isNextFramesLoaded && (!flush || !pSRC->algo.linear.isPrevFramesLoaded)) { + break; + } + } + + return totalFramesRead; +} + + +static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) +{ + return pMP3->onRead(pMP3->pUserData, pBufferOut, bytesToRead); +} + +static drmp3_bool32 drmp3__on_seek(drmp3* pMP3, int offset, drmp3_seek_origin origin) +{ + drmp3_assert(offset >= 0); + return pMP3->onSeek(pMP3->pUserData, offset, origin); +} + + +static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) +{ + drmp3_assert(pMP3 != NULL); + drmp3_assert(pMP3->onRead != NULL); + + if (pMP3->atEnd) { + return 0; + } + + drmp3_uint32 pcmFramesRead = 0; + do { + // minimp3 recommends doing data submission in 16K chunks. If we don't have at least 16K bytes available, get more. + if (pMP3->dataSize < DRMP3_DATA_CHUNK_SIZE) { + if (pMP3->dataCapacity < DRMP3_DATA_CHUNK_SIZE) { + pMP3->dataCapacity = DRMP3_DATA_CHUNK_SIZE; + drmp3_uint8* pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity); + if (pNewData == NULL) { + return 0; // Out of memory. + } + + pMP3->pData = pNewData; + } + + size_t bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + if (bytesRead == 0) { + if (pMP3->dataSize == 0) { + pMP3->atEnd = DRMP3_TRUE; + return 0; // No data. + } + } + + pMP3->dataSize += bytesRead; + } + + if (pMP3->dataSize > INT_MAX) { + pMP3->atEnd = DRMP3_TRUE; + return 0; // File too big. + } + + drmp3dec_frame_info info; + pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData, (int)pMP3->dataSize, pPCMFrames, &info); // <-- Safe size_t -> int conversion thanks to the check above. + if (pcmFramesRead != 0) { + size_t leftoverDataSize = (pMP3->dataSize - (size_t)info.frame_bytes); + for (size_t i = 0; i < leftoverDataSize; ++i) { + pMP3->pData[i] = pMP3->pData[i + (size_t)info.frame_bytes]; + } + + pMP3->dataSize = leftoverDataSize; + pMP3->pcmFramesConsumedInMP3Frame = 0; + pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; + pMP3->mp3FrameChannels = info.channels; + pMP3->mp3FrameSampleRate = info.hz; + drmp3_src_set_input_sample_rate(&pMP3->src, pMP3->mp3FrameSampleRate); + break; + } else { + // Need more data. minimp3 recommends doing data submission in 16K chunks. + if (pMP3->dataCapacity == pMP3->dataSize) { + // No room. Expand. + pMP3->dataCapacity += DRMP3_DATA_CHUNK_SIZE; + drmp3_uint8* pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity); + if (pNewData == NULL) { + return 0; // Out of memory. + } + + pMP3->pData = pNewData; + } + + // Fill in a chunk. + size_t bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); + if (bytesRead == 0) { + pMP3->atEnd = DRMP3_TRUE; + return 0; // Error reading more data. + } + + pMP3->dataSize += bytesRead; + } + } while (DRMP3_TRUE); + + return pcmFramesRead; +} + +static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3) +{ + drmp3_assert(pMP3 != NULL); + return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames); +} + +static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) +{ + drmp3_assert(pMP3 != NULL); + + drmp3_uint32 pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL); + if (pcmFrameCount == 0) { + return 0; + } + + // We have essentially just skipped past the frame, so just set the remaining samples to 0. + pMP3->currentPCMFrame += pcmFrameCount; + pMP3->pcmFramesConsumedInMP3Frame = pcmFrameCount; + pMP3->pcmFramesRemainingInMP3Frame = 0; + + return pcmFrameCount; +} + +static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData) +{ + drmp3* pMP3 = (drmp3*)pUserData; + drmp3_assert(pMP3 != NULL); + drmp3_assert(pMP3->onRead != NULL); + + float* pFramesOutF = (float*)pFramesOut; + drmp3_uint64 totalFramesRead = 0; + + while (frameCount > 0) { + // Read from the in-memory buffer first. + while (pMP3->pcmFramesRemainingInMP3Frame > 0 && frameCount > 0) { + drmp3d_sample_t* frames = (drmp3d_sample_t*)pMP3->pcmFrames; +#ifndef DR_MP3_FLOAT_OUTPUT + if (pMP3->mp3FrameChannels == 1) { + if (pMP3->channels == 1) { + // Mono -> Mono. + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; + } else { + // Mono -> Stereo. + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; + pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; + } + } else { + if (pMP3->channels == 1) { + // Stereo -> Mono + float sample = 0; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; + pFramesOutF[0] = sample * 0.5f; + } else { + // Stereo -> Stereo + pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; + pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; + } + } +#else + if (pMP3->mp3FrameChannels == 1) { + if (pMP3->channels == 1) { + // Mono -> Mono. + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; + } else { + // Mono -> Stereo. + pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; + pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame]; + } + } else { + if (pMP3->channels == 1) { + // Stereo -> Mono + float sample = 0; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; + sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; + pFramesOutF[0] = sample * 0.5f; + } else { + // Stereo -> Stereo + pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; + pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; + } + } +#endif + + pMP3->pcmFramesConsumedInMP3Frame += 1; + pMP3->pcmFramesRemainingInMP3Frame -= 1; + totalFramesRead += 1; + frameCount -= 1; + pFramesOutF += pSRC->config.channels; + } + + if (frameCount == 0) { + break; + } + + drmp3_assert(pMP3->pcmFramesRemainingInMP3Frame == 0); + + // At this point we have exhausted our in-memory buffer so we need to re-fill. Note that the sample rate may have changed + // at this point which means we'll also need to update our sample rate conversion pipeline. + if (drmp3_decode_next_frame(pMP3) == 0) { + break; + } + } + + return totalFramesRead; +} + +drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig) +{ + drmp3_assert(pMP3 != NULL); + drmp3_assert(onRead != NULL); + + // This function assumes the output object has already been reset to 0. Do not do that here, otherwise things will break. + drmp3dec_init(&pMP3->decoder); + + // The config can be null in which case we use defaults. + drmp3_config config; + if (pConfig != NULL) { + config = *pConfig; + } else { + drmp3_zero_object(&config); + } + + pMP3->channels = config.outputChannels; + if (pMP3->channels == 0) { + pMP3->channels = DR_MP3_DEFAULT_CHANNELS; + } + + // Cannot have more than 2 channels. + if (pMP3->channels > 2) { + pMP3->channels = 2; + } + + pMP3->sampleRate = config.outputSampleRate; + if (pMP3->sampleRate == 0) { + pMP3->sampleRate = DR_MP3_DEFAULT_SAMPLE_RATE; + } + + pMP3->onRead = onRead; + pMP3->onSeek = onSeek; + pMP3->pUserData = pUserData; + + // We need a sample rate converter for converting the sample rate from the MP3 frames to the requested output sample rate. + drmp3_src_config srcConfig; + drmp3_zero_object(&srcConfig); + srcConfig.sampleRateIn = DR_MP3_DEFAULT_SAMPLE_RATE; + srcConfig.sampleRateOut = pMP3->sampleRate; + srcConfig.channels = pMP3->channels; + srcConfig.algorithm = drmp3_src_algorithm_linear; + if (!drmp3_src_init(&srcConfig, drmp3_read_src, pMP3, &pMP3->src)) { + drmp3_uninit(pMP3); + return DRMP3_FALSE; + } + + // Decode the first frame to confirm that it is indeed a valid MP3 stream. + if (!drmp3_decode_next_frame(pMP3)) { + drmp3_uninit(pMP3); + return DRMP3_FALSE; // Not a valid MP3 stream. + } + + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig) +{ + if (pMP3 == NULL || onRead == NULL) { + return DRMP3_FALSE; + } + + drmp3_zero_object(pMP3); + return drmp3_init_internal(pMP3, onRead, onSeek, pUserData, pConfig); +} + + +static size_t drmp3__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + drmp3* pMP3 = (drmp3*)pUserData; + drmp3_assert(pMP3 != NULL); + drmp3_assert(pMP3->memory.dataSize >= pMP3->memory.currentReadPos); + + size_t bytesRemaining = pMP3->memory.dataSize - pMP3->memory.currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + drmp3_copy_memory(pBufferOut, pMP3->memory.pData + pMP3->memory.currentReadPos, bytesToRead); + pMP3->memory.currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +static drmp3_bool32 drmp3__on_seek_memory(void* pUserData, int byteOffset, drmp3_seek_origin origin) +{ + drmp3* pMP3 = (drmp3*)pUserData; + drmp3_assert(pMP3 != NULL); + + if (origin == drmp3_seek_origin_current) { + if (byteOffset > 0) { + if (pMP3->memory.currentReadPos + byteOffset > pMP3->memory.dataSize) { + byteOffset = (int)(pMP3->memory.dataSize - pMP3->memory.currentReadPos); // Trying to seek too far forward. + } + } else { + if (pMP3->memory.currentReadPos < (size_t)-byteOffset) { + byteOffset = -(int)pMP3->memory.currentReadPos; // Trying to seek too far backwards. + } + } + + // This will never underflow thanks to the clamps above. + pMP3->memory.currentReadPos += byteOffset; + } else { + if ((drmp3_uint32)byteOffset <= pMP3->memory.dataSize) { + pMP3->memory.currentReadPos = byteOffset; + } else { + pMP3->memory.currentReadPos = pMP3->memory.dataSize; // Trying to seek too far forward. + } + } + + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_config* pConfig) +{ + if (pMP3 == NULL) { + return DRMP3_FALSE; + } + + drmp3_zero_object(pMP3); + + if (pData == NULL || dataSize == 0) { + return DRMP3_FALSE; + } + + pMP3->memory.pData = (const drmp3_uint8*)pData; + pMP3->memory.dataSize = dataSize; + pMP3->memory.currentReadPos = 0; + + return drmp3_init_internal(pMP3, drmp3__on_read_memory, drmp3__on_seek_memory, pMP3, pConfig); +} + + +#ifndef DR_MP3_NO_STDIO +#include + +static size_t drmp3__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +static drmp3_bool32 drmp3__on_seek_stdio(void* pUserData, int offset, drmp3_seek_origin origin) +{ + return fseek((FILE*)pUserData, offset, (origin == drmp3_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* filePath, const drmp3_config* pConfig) +{ + FILE* pFile; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (fopen_s(&pFile, filePath, "rb") != 0) { + return DRMP3_FALSE; + } +#else + pFile = fopen(filePath, "rb"); + if (pFile == NULL) { + return DRMP3_FALSE; + } +#endif + + return drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pConfig); +} +#endif + +void drmp3_uninit(drmp3* pMP3) +{ + if (pMP3 == NULL) { + return; + } + +#ifndef DR_MP3_NO_STDIO + if (pMP3->onRead == drmp3__on_read_stdio) { + fclose((FILE*)pMP3->pUserData); + } +#endif + + drmp3_free(pMP3->pData); +} + +drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) +{ + if (pMP3 == NULL || pMP3->onRead == NULL) { + return 0; + } + + drmp3_uint64 totalFramesRead = 0; + + if (pBufferOut == NULL) { + float temp[4096]; + while (framesToRead > 0) { + drmp3_uint64 framesToReadRightNow = sizeof(temp)/sizeof(temp[0]) / pMP3->channels; + if (framesToReadRightNow > framesToRead) { + framesToReadRightNow = framesToRead; + } + + drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); + if (framesJustRead == 0) { + break; + } + + framesToRead -= framesJustRead; + totalFramesRead += framesJustRead; + } + } else { + totalFramesRead = drmp3_src_read_frames_ex(&pMP3->src, framesToRead, pBufferOut, DRMP3_TRUE); + pMP3->currentPCMFrame += totalFramesRead; + } + + return totalFramesRead; +} + +drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) +{ + drmp3_assert(pMP3 != NULL); + drmp3_assert(pMP3->onSeek != NULL); + + // Seek to the start of the stream to begin with. + if (!drmp3__on_seek(pMP3, 0, drmp3_seek_origin_start)) { + return DRMP3_FALSE; + } + + // Clear any cached data. + pMP3->pcmFramesConsumedInMP3Frame = 0; + pMP3->pcmFramesRemainingInMP3Frame = 0; + pMP3->currentPCMFrame = 0; + pMP3->dataSize = 0; + pMP3->atEnd = DRMP3_FALSE; + + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 frameIndex) +{ + drmp3_assert(pMP3 != NULL); + + if (frameIndex == pMP3->currentPCMFrame) { + return DRMP3_TRUE; + } + + // If we're moving foward we just read from where we're at. Otherwise we need to move back to the start of + // the stream and read from the beginning. + drmp3_uint64 framesToReadAndDiscard; + if (frameIndex >= pMP3->currentPCMFrame) { + // Moving foward. + framesToReadAndDiscard = frameIndex - pMP3->currentPCMFrame; + } else { + // Moving backward. Move to the start of the stream and then move forward. + framesToReadAndDiscard = frameIndex; + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return DRMP3_FALSE; + } + } + + // MP3 is a bit annoying when it comes to seeking because of the bit reservoir. It basically means that an MP3 frame can possibly + // depend on some of the data of prior frames. This means it's not as simple as seeking to the first byte of the MP3 frame that + // contains the sample because that MP3 frame will need the data from the previous MP3 frame (which we just seeked past!). To + // resolve this we seek past a number of MP3 frames up to a point, and then read-and-discard the remainder. + drmp3_uint64 maxFramesToReadAndDiscard = DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME * 3; + + // First get rid of anything that's still sitting in the buffer. + if (framesToReadAndDiscard > maxFramesToReadAndDiscard && framesToReadAndDiscard > pMP3->pcmFramesRemainingInMP3Frame) { + framesToReadAndDiscard -= pMP3->pcmFramesRemainingInMP3Frame; + pMP3->currentPCMFrame += pMP3->pcmFramesRemainingInMP3Frame; + pMP3->pcmFramesConsumedInMP3Frame += pMP3->pcmFramesRemainingInMP3Frame; + pMP3->pcmFramesRemainingInMP3Frame = 0; + } + + // Now get rid of leading whole frames. + while (framesToReadAndDiscard > maxFramesToReadAndDiscard) { + drmp3_uint32 pcmFramesSeeked = drmp3_seek_next_frame(pMP3); + if (pcmFramesSeeked == 0) { + break; + } + + framesToReadAndDiscard -= pcmFramesSeeked; + } + + // The last step is to read-and-discard any remaining PCM frames to make it sample-exact. + drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadAndDiscard, NULL); + if (framesRead != framesToReadAndDiscard) { + return DRMP3_FALSE; + } + + return DRMP3_TRUE; +} + +drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex) +{ + if (pMP3 == NULL || pMP3->onSeek == NULL) { + return DRMP3_FALSE; + } + + // We currently only support brute force seeking. + return drmp3_seek_to_pcm_frame__brute_force(pMP3, frameIndex); +} + +drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) +{ + if (pMP3 == NULL) { + return 0; + } + + // The way this works is we move back to the start of the stream, iterate over each MP3 frame and calculate the frame count based + // on our output sample rate, the seek back to the PCM frame we were sitting on before calling this function. + + // The stream must support seeking for this to work. + if (pMP3->onSeek == NULL) { + return 0; + } + + // We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later. + drmp3_uint64 currentPCMFrame = pMP3->currentPCMFrame; + + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return 0; + } + + drmp3_uint64 totalPCMFrameCount = 0; + float totalPCMFrameCountFractionalPart = 0; // <-- With resampling there will be a fractional part to each MP3 frame that we need to accumulate. + for (;;) { + drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); // <-- Passing in NULL here will prevent decoding of the MP3 frame which should save time. + if (pcmFramesInCurrentMP3FrameIn == 0) { + break; + } + + float srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate; + drmp3_assert(srcRatio > 0); + + float pcmFramesInCurrentMP3FrameOutF = totalPCMFrameCountFractionalPart + (pcmFramesInCurrentMP3FrameIn / srcRatio); + drmp3_uint32 pcmFramesInCurrentMP3FrameOut = (drmp3_uint32)pcmFramesInCurrentMP3FrameOutF; + totalPCMFrameCountFractionalPart = pcmFramesInCurrentMP3FrameOutF - pcmFramesInCurrentMP3FrameOut; + totalPCMFrameCount += pcmFramesInCurrentMP3FrameOut; + } + + // Finally, we need to seek back to where we were. + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return 0; + } + + if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { + return 0; + } + + return totalPCMFrameCount; +} + +drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) +{ + if (pMP3 == NULL) { + return 0; + } + + // This works the same way as drmp3_get_pcm_frame_count() - move to the start, count MP3 frames, move back to the previous position. + + // The stream must support seeking for this to work. + if (pMP3->onSeek == NULL) { + return 0; + } + + // We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later. + drmp3_uint64 currentPCMFrame = pMP3->currentPCMFrame; + + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return 0; + } + + drmp3_uint64 totalMP3FrameCount = 0; + for (;;) { + drmp3_uint32 pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); + if (pcmFramesInCurrentMP3FrameIn == 0) { + break; + } + + totalMP3FrameCount += 1; + } + + // Finally, we need to seek back to where we were. + if (!drmp3_seek_to_start_of_stream(pMP3)) { + return 0; + } + + if (!drmp3_seek_to_pcm_frame(pMP3, currentPCMFrame)) { + return 0; + } + + return totalMP3FrameCount; +} + + +float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +{ + drmp3_assert(pMP3 != NULL); + + drmp3_uint64 totalFramesRead = 0; + drmp3_uint64 framesCapacity = 0; + float* pFrames = NULL; + + float temp[4096]; + for (;;) { + drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels; + drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); + if (framesJustRead == 0) { + break; + } + + // Reallocate the output buffer if there's not enough room. + if (framesCapacity < totalFramesRead + framesJustRead) { + framesCapacity *= 2; + if (framesCapacity < totalFramesRead + framesJustRead) { + framesCapacity = totalFramesRead + framesJustRead; + } + + drmp3_uint64 newFramesBufferSize = framesCapacity*pMP3->channels*sizeof(float); + if (newFramesBufferSize > DRMP3_SIZE_MAX) { + break; + } + + float* pNewFrames = (float*)drmp3_realloc(pFrames, (size_t)newFramesBufferSize); + if (pNewFrames == NULL) { + drmp3_free(pFrames); + break; + } + + pFrames = pNewFrames; + } + + drmp3_copy_memory(pFrames + totalFramesRead*pMP3->channels, temp, (size_t)(framesJustRead*pMP3->channels*sizeof(float))); + totalFramesRead += framesJustRead; + + // If the number of frames we asked for is less that what we actually read it means we've reached the end. + if (framesJustRead != framesToReadRightNow) { + break; + } + } + + if (pConfig != NULL) { + pConfig->outputChannels = pMP3->channels; + pConfig->outputSampleRate = pMP3->sampleRate; + } + + drmp3_uninit(pMP3); + + if (pTotalFrameCount) *pTotalFrameCount = totalFramesRead; + return pFrames; +} + +float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +{ + drmp3 mp3; + if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pConfig)) { + return NULL; + } + + return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); +} + +float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +{ + drmp3 mp3; + if (!drmp3_init_memory(&mp3, pData, dataSize, pConfig)) { + return NULL; + } + + return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); +} + +#ifndef DR_MP3_NO_STDIO +float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +{ + drmp3 mp3; + if (!drmp3_init_file(&mp3, filePath, pConfig)) { + return NULL; + } + + return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); +} +#endif + +void drmp3_free(void* p) +{ + DRMP3_FREE(p); +} + +#endif /*DR_MP3_IMPLEMENTATION*/ + + +// DIFFERENCES BETWEEN minimp3 AND dr_mp3 +// ====================================== +// - First, keep in mind that minimp3 (https://github.com/lieff/minimp3) is where all the real work was done. All of the +// code relating to the actual decoding remains mostly unmodified, apart from some namespacing changes. +// - dr_mp3 adds a pulling style API which allows you to deliver raw data via callbacks. So, rather than pushing data +// to the decoder, the decoder _pulls_ data from your callbacks. +// - In addition to callbacks, a decoder can be initialized from a block of memory and a file. +// - The dr_mp3 pull API reads PCM frames rather than whole MP3 frames. +// - dr_mp3 adds convenience APIs for opening and decoding entire files in one go. +// - dr_mp3 is fully namespaced, including the implementation section, which is more suitable when compiling projects +// as a single translation unit (aka unity builds). At the time of writing this, a unity build is not possible when +// using minimp3 in conjunction with stb_vorbis. dr_mp3 addresses this. + + +// REVISION HISTORY +// ================ +// +// v0.4.0 - 2018-xx-xx +// - API CHANGE: Rename some APIs: +// - drmp3_read_f32 -> to drmp3_read_pcm_frames_f32 +// - drmp3_seek_to_frame -> drmp3_seek_to_pcm_frame +// - drmp3_open_and_decode_f32 -> drmp3_open_and_read_f32 +// - drmp3_open_and_decode_memory_f32 -> drmp3_open_memory_and_read_f32 +// - drmp3_open_and_decode_file_f32 -> drmp3_open_file_and_read_f32 +// - Add drmp3_get_pcm_frame_count(). +// - Add drmp3_get_mp3_frame_count(). +// - Improve seeking performance. +// +// v0.3.2 - 2018-09-11 +// - Fix a couple of memory leaks. +// - Bring up to date with minimp3. +// +// v0.3.1 - 2018-08-25 +// - Fix C++ build. +// +// v0.3.0 - 2018-08-25 +// - Bring up to date with minimp3. This has a minor API change: the "pcm" parameter of drmp3dec_decode_frame() has +// been changed from short* to void* because it can now output both s16 and f32 samples, depending on whether or +// not the DR_MP3_FLOAT_OUTPUT option is set. +// +// v0.2.11 - 2018-08-08 +// - Fix a bug where the last part of a file is not read. +// +// v0.2.10 - 2018-08-07 +// - Improve 64-bit detection. +// +// v0.2.9 - 2018-08-05 +// - Fix C++ build on older versions of GCC. +// - Bring up to date with minimp3. +// +// v0.2.8 - 2018-08-02 +// - Fix compilation errors with older versions of GCC. +// +// v0.2.7 - 2018-07-13 +// - Bring up to date with minimp3. +// +// v0.2.6 - 2018-07-12 +// - Bring up to date with minimp3. +// +// v0.2.5 - 2018-06-22 +// - Bring up to date with minimp3. +// +// v0.2.4 - 2018-05-12 +// - Bring up to date with minimp3. +// +// v0.2.3 - 2018-04-29 +// - Fix TCC build. +// +// v0.2.2 - 2018-04-28 +// - Fix bug when opening a decoder from memory. +// +// v0.2.1 - 2018-04-27 +// - Efficiency improvements when the decoder reaches the end of the stream. +// +// v0.2 - 2018-04-21 +// - Bring up to date with minimp3. +// - Start using major.minor.revision versioning. +// +// v0.1d - 2018-03-30 +// - Bring up to date with minimp3. +// +// v0.1c - 2018-03-11 +// - Fix C++ build error. +// +// v0.1b - 2018-03-07 +// - Bring up to date with minimp3. +// +// v0.1a - 2018-02-28 +// - Fix compilation error on GCC/Clang. +// - Fix some warnings. +// +// v0.1 - 2018-02-xx +// - Initial versioned release. + + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ + +/* + https://github.com/lieff/minimp3 + To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. + This software is distributed without any warranty. + See . +*/ diff --git a/src/soloud/src/audiosource/wav/dr_wav.h b/src/soloud/src/audiosource/wav/dr_wav.h new file mode 100644 index 0000000..79aa382 --- /dev/null +++ b/src/soloud/src/audiosource/wav/dr_wav.h @@ -0,0 +1,4254 @@ +// WAV audio loader and writer. Public domain. See "unlicense" statement at the end of this file. +// dr_wav - v0.9.0-dev - 2018-xx-xx +// +// David Reid - mackron@gmail.com + +// USAGE +// +// This is a single-file library. To use it, do something like the following in one .c file. +// #define DR_WAV_IMPLEMENTATION +// #include "dr_wav.h" +// +// You can then #include this file in other parts of the program as you would with any other header file. Do something +// like the following to read audio data: +// +// drwav wav; +// if (!drwav_init_file(&wav, "my_song.wav")) { +// // Error opening WAV file. +// } +// +// drwav_int32* pDecodedInterleavedSamples = malloc(wav.totalSampleCount * sizeof(drwav_int32)); +// size_t numberOfSamplesActuallyDecoded = drwav_read_s32(&wav, wav.totalSampleCount, pDecodedInterleavedSamples); +// +// ... +// +// drwav_uninit(&wav); +// +// You can also use drwav_open() to allocate and initialize the loader for you: +// +// drwav* pWav = drwav_open_file("my_song.wav"); +// if (pWav == NULL) { +// // Error opening WAV file. +// } +// +// ... +// +// drwav_close(pWav); +// +// If you just want to quickly open and read the audio data in a single operation you can do something like this: +// +// unsigned int channels; +// unsigned int sampleRate; +// drwav_uint64 totalSampleCount; +// float* pSampleData = drwav_open_file_and_read_f32("my_song.wav", &channels, &sampleRate, &totalSampleCount); +// if (pSampleData == NULL) { +// // Error opening and reading WAV file. +// } +// +// ... +// +// drwav_free(pSampleData); +// +// The examples above use versions of the API that convert the audio data to a consistent format (32-bit signed PCM, in +// this case), but you can still output the audio data in its internal format (see notes below for supported formats): +// +// size_t samplesRead = drwav_read(&wav, wav.totalSampleCount, pDecodedInterleavedSamples); +// +// You can also read the raw bytes of audio data, which could be useful if dr_wav does not have native support for +// a particular data format: +// +// size_t bytesRead = drwav_read_raw(&wav, bytesToRead, pRawDataBuffer); +// +// +// dr_wav has seamless support the Sony Wave64 format. The decoder will automatically detect it and it should Just Work +// without any manual intervention. +// +// +// dr_wav can also be used to output WAV files. This does not currently support compressed formats. To use this, look at +// drwav_open_write(), drwav_open_file_write(), etc. Use drwav_write() to write samples, or drwav_write_raw() to write +// raw data in the "data" chunk. +// +// drwav_data_format format; +// format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64. +// format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes. +// format.channels = 2; +// format.sampleRate = 44100; +// format.bitsPerSample = 16; +// drwav* pWav = drwav_open_file_write("data/recording.wav", &format); +// +// ... +// +// drwav_uint64 samplesWritten = drwav_write(pWav, sampleCount, pSamples); +// +// +// +// OPTIONS +// #define these options before including this file. +// +// #define DR_WAV_NO_CONVERSION_API +// Disables conversion APIs such as drwav_read_f32() and drwav_s16_to_f32(). +// +// #define DR_WAV_NO_STDIO +// Disables drwav_open_file(), drwav_open_file_write(), etc. +// +// +// +// QUICK NOTES +// - Samples are always interleaved. +// - The default read function does not do any data conversion. Use drwav_read_f32() to read and convert audio data +// to IEEE 32-bit floating point samples, drwav_read_s32() to read samples as signed 32-bit PCM and drwav_read_s16() +// to read samples as signed 16-bit PCM. Tested and supported internal formats include the following: +// - Unsigned 8-bit PCM +// - Signed 12-bit PCM +// - Signed 16-bit PCM +// - Signed 24-bit PCM +// - Signed 32-bit PCM +// - IEEE 32-bit floating point +// - IEEE 64-bit floating point +// - A-law and u-law +// - Microsoft ADPCM +// - IMA ADPCM (DVI, format code 0x11) +// - dr_wav will try to read the WAV file as best it can, even if it's not strictly conformant to the WAV format. + + +#ifndef dr_wav_h +#define dr_wav_h + +#include + +#if defined(_MSC_VER) && _MSC_VER < 1600 +typedef signed char drwav_int8; +typedef unsigned char drwav_uint8; +typedef signed short drwav_int16; +typedef unsigned short drwav_uint16; +typedef signed int drwav_int32; +typedef unsigned int drwav_uint32; +typedef signed __int64 drwav_int64; +typedef unsigned __int64 drwav_uint64; +#else +#include +typedef int8_t drwav_int8; +typedef uint8_t drwav_uint8; +typedef int16_t drwav_int16; +typedef uint16_t drwav_uint16; +typedef int32_t drwav_int32; +typedef uint32_t drwav_uint32; +typedef int64_t drwav_int64; +typedef uint64_t drwav_uint64; +#endif +typedef drwav_uint8 drwav_bool8; +typedef drwav_uint32 drwav_bool32; +#define DRWAV_TRUE 1 +#define DRWAV_FALSE 0 + +#ifdef __cplusplus +extern "C" { +#endif + +// Common data formats. +#define DR_WAVE_FORMAT_PCM 0x1 +#define DR_WAVE_FORMAT_ADPCM 0x2 +#define DR_WAVE_FORMAT_IEEE_FLOAT 0x3 +#define DR_WAVE_FORMAT_ALAW 0x6 +#define DR_WAVE_FORMAT_MULAW 0x7 +#define DR_WAVE_FORMAT_DVI_ADPCM 0x11 +#define DR_WAVE_FORMAT_EXTENSIBLE 0xFFFE + +// Constants. +#ifndef DRWAV_MAX_SMPL_LOOPS +#define DRWAV_MAX_SMPL_LOOPS 1 +#endif + +// Flags to pass into drwav_init_ex(), etc. +#define DRWAV_SEQUENTIAL 0x00000001 + +typedef enum +{ + drwav_seek_origin_start, + drwav_seek_origin_current +} drwav_seek_origin; + +typedef enum +{ + drwav_container_riff, + drwav_container_w64 +} drwav_container; + +typedef struct +{ + union + { + drwav_uint8 fourcc[4]; + drwav_uint8 guid[16]; + } id; + + // The size in bytes of the chunk. + drwav_uint64 sizeInBytes; + + // RIFF = 2 byte alignment. + // W64 = 8 byte alignment. + unsigned int paddingSize; +} drwav_chunk_header; + +// Callback for when data is read. Return value is the number of bytes actually read. +// +// pUserData [in] The user data that was passed to drwav_init(), drwav_open() and family. +// pBufferOut [out] The output buffer. +// bytesToRead [in] The number of bytes to read. +// +// Returns the number of bytes actually read. +// +// A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until +// either the entire bytesToRead is filled or you have reached the end of the stream. +typedef size_t (* drwav_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); + +// Callback for when data is written. Returns value is the number of bytes actually written. +// +// pUserData [in] The user data that was passed to drwav_init_write(), drwav_open_write() and family. +// pData [out] A pointer to the data to write. +// bytesToWrite [in] The number of bytes to write. +// +// Returns the number of bytes actually written. +// +// If the return value differs from bytesToWrite, it indicates an error. +typedef size_t (* drwav_write_proc)(void* pUserData, const void* pData, size_t bytesToWrite); + +// Callback for when data needs to be seeked. +// +// pUserData [in] The user data that was passed to drwav_init(), drwav_open() and family. +// offset [in] The number of bytes to move, relative to the origin. Will never be negative. +// origin [in] The origin of the seek - the current position or the start of the stream. +// +// Returns whether or not the seek was successful. +// +// Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which +// will be either drwav_seek_origin_start or drwav_seek_origin_current. +typedef drwav_bool32 (* drwav_seek_proc)(void* pUserData, int offset, drwav_seek_origin origin); + +// Callback for when drwav_init_ex/drwav_open_ex finds a chunk. +// +// pChunkUserData [in] The user data that was passed to the pChunkUserData parameter of drwav_init_ex(), drwav_open_ex() and family. +// onRead [in] A pointer to the function to call when reading. +// onSeek [in] A pointer to the function to call when seeking. +// pReadSeekUserData [in] The user data that was passed to the pReadSeekUserData parameter of drwav_init_ex(), drwav_open_ex() and family. +// pChunkHeader [in] A pointer to an object containing basic header information about the chunk. Use this to identify the chunk. +// +// Returns the number of bytes read + seeked. +// +// To read data from the chunk, call onRead(), passing in pReadSeekUserData as the first parameter. Do the same +// for seeking with onSeek(). The return value must be the total number of bytes you have read _plus_ seeked. +// +// You must not attempt to read beyond the boundary of the chunk. +typedef drwav_uint64 (* drwav_chunk_proc)(void* pChunkUserData, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_chunk_header* pChunkHeader); + +// Structure for internal use. Only used for loaders opened with drwav_open_memory(). +typedef struct +{ + const drwav_uint8* data; + size_t dataSize; + size_t currentReadPos; +} drwav__memory_stream; + +// Structure for internal use. Only used for writers opened with drwav_open_memory_write(). +typedef struct +{ + void** ppData; + size_t* pDataSize; + size_t dataSize; + size_t dataCapacity; + size_t currentWritePos; +} drwav__memory_stream_write; + +typedef struct +{ + drwav_container container; // RIFF, W64. + drwav_uint32 format; // DR_WAVE_FORMAT_* + drwav_uint32 channels; + drwav_uint32 sampleRate; + drwav_uint32 bitsPerSample; +} drwav_data_format; + +typedef struct +{ + // The format tag exactly as specified in the wave file's "fmt" chunk. This can be used by applications + // that require support for data formats not natively supported by dr_wav. + drwav_uint16 formatTag; + + // The number of channels making up the audio data. When this is set to 1 it is mono, 2 is stereo, etc. + drwav_uint16 channels; + + // The sample rate. Usually set to something like 44100. + drwav_uint32 sampleRate; + + // Average bytes per second. You probably don't need this, but it's left here for informational purposes. + drwav_uint32 avgBytesPerSec; + + // Block align. This is equal to the number of channels * bytes per sample. + drwav_uint16 blockAlign; + + // Bits per sample. + drwav_uint16 bitsPerSample; + + // The size of the extended data. Only used internally for validation, but left here for informational purposes. + drwav_uint16 extendedSize; + + // The number of valid bits per sample. When is equal to WAVE_FORMAT_EXTENSIBLE, + // is always rounded up to the nearest multiple of 8. This variable contains information about exactly how + // many bits a valid per sample. Mainly used for informational purposes. + drwav_uint16 validBitsPerSample; + + // The channel mask. Not used at the moment. + drwav_uint32 channelMask; + + // The sub-format, exactly as specified by the wave file. + drwav_uint8 subFormat[16]; +} drwav_fmt; + +typedef struct +{ + drwav_uint32 cuePointId; + drwav_uint32 type; + drwav_uint32 start; + drwav_uint32 end; + drwav_uint32 fraction; + drwav_uint32 playCount; +} drwav_smpl_loop; + + typedef struct +{ + drwav_uint32 manufacturer; + drwav_uint32 product; + drwav_uint32 samplePeriod; + drwav_uint32 midiUnityNotes; + drwav_uint32 midiPitchFraction; + drwav_uint32 smpteFormat; + drwav_uint32 smpteOffset; + drwav_uint32 numSampleLoops; + drwav_uint32 samplerData; + drwav_smpl_loop loops[DRWAV_MAX_SMPL_LOOPS]; +} drwav_smpl; + +typedef struct +{ + // A pointer to the function to call when more data is needed. + drwav_read_proc onRead; + + // A pointer to the function to call when data needs to be written. Only used when the drwav object is opened in write mode. + drwav_write_proc onWrite; + + // A pointer to the function to call when the wav file needs to be seeked. + drwav_seek_proc onSeek; + + // The user data to pass to callbacks. + void* pUserData; + + + // Whether or not the WAV file is formatted as a standard RIFF file or W64. + drwav_container container; + + + // Structure containing format information exactly as specified by the wav file. + drwav_fmt fmt; + + // The sample rate. Will be set to something like 44100. + drwav_uint32 sampleRate; + + // The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. + drwav_uint16 channels; + + // The bits per sample. Will be set to something like 16, 24, etc. + drwav_uint16 bitsPerSample; + + // The number of bytes per sample. + drwav_uint16 bytesPerSample; + + // Equal to fmt.formatTag, or the value specified by fmt.subFormat if fmt.formatTag is equal to 65534 (WAVE_FORMAT_EXTENSIBLE). + drwav_uint16 translatedFormatTag; + + // The total number of samples making up the audio data. Use * to calculate + // the required size of a buffer to hold the entire audio data. + drwav_uint64 totalSampleCount; + + + // The size in bytes of the data chunk. + drwav_uint64 dataChunkDataSize; + + // The position in the stream of the first byte of the data chunk. This is used for seeking. + drwav_uint64 dataChunkDataPos; + + // The number of bytes remaining in the data chunk. + drwav_uint64 bytesRemaining; + + + // Only used in sequential write mode. Keeps track of the desired size of the "data" chunk at the point of initialization time. Always + // set to 0 for non-sequential writes and when the drwav object is opened in read mode. Used for validation. + drwav_uint64 dataChunkDataSizeTargetWrite; + + // Keeps track of whether or not the wav writer was initialized in sequential mode. + drwav_bool32 isSequentialWrite; + + + // smpl chunk. + drwav_smpl smpl; + + + // A hack to avoid a DRWAV_MALLOC() when opening a decoder with drwav_open_memory(). + drwav__memory_stream memoryStream; + drwav__memory_stream_write memoryStreamWrite; + + // Generic data for compressed formats. This data is shared across all block-compressed formats. + struct + { + drwav_uint64 iCurrentSample; // The index of the next sample that will be read by drwav_read_*(). This is used with "totalSampleCount" to ensure we don't read excess samples at the end of the last block. + } compressed; + + // Microsoft ADPCM specific data. + struct + { + drwav_uint32 bytesRemainingInBlock; + drwav_uint16 predictor[2]; + drwav_int32 delta[2]; + drwav_int32 cachedSamples[4]; // Samples are stored in this cache during decoding. + drwav_uint32 cachedSampleCount; + drwav_int32 prevSamples[2][2]; // The previous 2 samples for each channel (2 channels at most). + } msadpcm; + + // IMA ADPCM specific data. + struct + { + drwav_uint32 bytesRemainingInBlock; + drwav_int32 predictor[2]; + drwav_int32 stepIndex[2]; + drwav_int32 cachedSamples[16]; // Samples are stored in this cache during decoding. + drwav_uint32 cachedSampleCount; + } ima; +} drwav; + + +// Initializes a pre-allocated drwav object. +// +// pWav [out] A pointer to the drwav object being initialized. +// onRead [in] The function to call when data needs to be read from the client. +// onSeek [in] The function to call when the read position of the client data needs to move. +// onChunk [in, optional] The function to call when a chunk is enumerated at initialized time. +// pUserData, pReadSeekUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. +// pChunkUserData [in, optional] A pointer to application defined data that will be passed to onChunk. +// flags [in, optional] A set of flags for controlling how things are loaded. +// +// Returns true if successful; false otherwise. +// +// Close the loader with drwav_uninit(). +// +// This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() +// to open the stream from a file or from a block of memory respectively. +// +// If you want dr_wav to manage the memory allocation for you, consider using drwav_open() instead. This will allocate +// a drwav object on the heap and return a pointer to it. +// +// Possible values for flags: +// DRWAV_SEQUENTIAL: Never perform a backwards seek while loading. This disables the chunk callback and will cause this function +// to return as soon as the data chunk is found. Any chunks after the data chunk will be ignored. +// +// drwav_init() is equivalent to "drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0);". +// +// The onChunk is callback is not called for the WAVE or FMT chunks. The contents of the FMT chunk can be read from pWav->fmt +// after the function returns. +// +// See also: drwav_init_file(), drwav_init_memory(), drwav_uninit() +drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData); +drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags); + +// Initializes a pre-allocated drwav object for writing. +// +// onWrite [in] The function to call when data needs to be written. +// onSeek [in] The function to call when the write position needs to move. +// pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. +// +// Returns true if successful; false otherwise. +// +// Close the writer with drwav_uninit(). +// +// This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() +// to open the stream from a file or from a block of memory respectively. +// +// If the total sample count is known, you can use drwav_init_write_sequential(). This avoids the need for dr_wav to perform +// a post-processing step for storing the total sample count and the size of the data chunk which requires a backwards seek. +// +// If you want dr_wav to manage the memory allocation for you, consider using drwav_open() instead. This will allocate +// a drwav object on the heap and return a pointer to it. +// +// See also: drwav_init_file_write(), drwav_init_memory_write(), drwav_uninit() +drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData); +drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData); + +// Uninitializes the given drwav object. +// +// Use this only for objects initialized with drwav_init(). +void drwav_uninit(drwav* pWav); + + +// Opens a wav file using the given callbacks. +// +// onRead [in] The function to call when data needs to be read from the client. +// onSeek [in] The function to call when the read position of the client data needs to move. +// pUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. +// +// Returns null on error. +// +// Close the loader with drwav_close(). +// +// You can also use drwav_open_file() and drwav_open_memory() to open the stream from a file or from a block of +// memory respectively. +// +// This is different from drwav_init() in that it will allocate the drwav object for you via DRWAV_MALLOC() before +// initializing it. +// +// See also: drwav_init(), drwav_open_file(), drwav_open_memory(), drwav_close() +drwav* drwav_open(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData); +drwav* drwav_open_ex(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags); + +// Opens a wav file for writing using the given callbacks. +// +// onWrite [in] The function to call when data needs to be written. +// onSeek [in] The function to call when the write position needs to move. +// pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. +// +// Returns null on error. +// +// Close the loader with drwav_close(). +// +// You can also use drwav_open_file_write() and drwav_open_memory_write() to open the stream from a file or from a block +// of memory respectively. +// +// This is different from drwav_init_write() in that it will allocate the drwav object for you via DRWAV_MALLOC() before +// initializing it. +// +// See also: drwav_open_file_write(), drwav_open_memory_write(), drwav_close() +drwav* drwav_open_write(const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData); +drwav* drwav_open_write_sequential(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData); + +// Uninitializes and deletes the the given drwav object. +// +// Use this only for objects created with drwav_open(). +void drwav_close(drwav* pWav); + + +// Reads raw audio data. +// +// This is the lowest level function for reading audio data. It simply reads the given number of +// bytes of the raw internal sample data. +// +// Consider using drwav_read_s16(), drwav_read_s32() or drwav_read_f32() for reading sample data in +// a consistent format. +// +// Returns the number of bytes actually read. +size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut); + +// Reads a chunk of audio data in the native internal format. +// +// This is typically the most efficient way to retrieve audio data, but it does not do any format +// conversions which means you'll need to convert the data manually if required. +// +// If the return value is less than it means the end of the file has been reached or +// you have requested more samples than can possibly fit in the output buffer. +// +// This function will only work when sample data is of a fixed size and uncompressed. If you are +// using a compressed format consider using drwav_read_raw() or drwav_read_s16/s32/f32/etc(). +drwav_uint64 drwav_read(drwav* pWav, drwav_uint64 samplesToRead, void* pBufferOut); + +// Same as drwav_read(), except works on PCM frames instead of samples. Returns the number of PCM +// freames read. +drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); + +// Seeks to the given sample. +// +// Returns true if successful; false otherwise. +drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample); + +// Same as drwav_seek_to_sample() except workd on PCM frames. +drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex); + + +// Writes raw audio data. +// +// Returns the number of bytes actually written. If this differs from bytesToWrite, it indicates an error. +size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData); + +// Writes audio data based on sample counts. +// +// Returns the number of samples written. +drwav_uint64 drwav_write(drwav* pWav, drwav_uint64 samplesToWrite, const void* pData); + +// Same as drwav_write(), but works on PCM frames instead of samples. Returns the number of PCM frames written. +drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); + + +//// Conversion Utilities //// +#ifndef DR_WAV_NO_CONVERSION_API + +// Reads a chunk of audio data and converts it to signed 16-bit PCM samples. +// +// Returns the number of samples actually read. +// +// If the return value is less than it means the end of the file has been reached. +drwav_uint64 drwav_read_s16(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); + +// Same as drwav_read_s16(), except works on PCM frames instead of samples. +drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); + +// Low-level function for converting unsigned 8-bit PCM samples to signed 16-bit PCM samples. +void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting signed 24-bit PCM samples to signed 16-bit PCM samples. +void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting signed 32-bit PCM samples to signed 16-bit PCM samples. +void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount); + +// Low-level function for converting IEEE 32-bit floating point samples to signed 16-bit PCM samples. +void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount); + +// Low-level function for converting IEEE 64-bit floating point samples to signed 16-bit PCM samples. +void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount); + +// Low-level function for converting A-law samples to signed 16-bit PCM samples. +void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting u-law samples to signed 16-bit PCM samples. +void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + + +// Reads a chunk of audio data and converts it to IEEE 32-bit floating point samples. +// +// Returns the number of samples actually read. +// +// If the return value is less than it means the end of the file has been reached. +drwav_uint64 drwav_read_f32(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut); + +// Same as drwav_read_f32(), except works on PCM frames instead of samples. +drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); + +// Low-level function for converting unsigned 8-bit PCM samples to IEEE 32-bit floating point samples. +void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting signed 16-bit PCM samples to IEEE 32-bit floating point samples. +void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount); + +// Low-level function for converting signed 24-bit PCM samples to IEEE 32-bit floating point samples. +void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting signed 32-bit PCM samples to IEEE 32-bit floating point samples. +void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount); + +// Low-level function for converting IEEE 64-bit floating point samples to IEEE 32-bit floating point samples. +void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount); + +// Low-level function for converting A-law samples to IEEE 32-bit floating point samples. +void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting u-law samples to IEEE 32-bit floating point samples. +void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + + +// Reads a chunk of audio data and converts it to signed 32-bit PCM samples. +// +// Returns the number of samples actually read. +// +// If the return value is less than it means the end of the file has been reached. +drwav_uint64 drwav_read_s32(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut); + +// Same as drwav_read_s32(), except works on PCM frames instead of samples. +drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); + +// Low-level function for converting unsigned 8-bit PCM samples to signed 32-bit PCM samples. +void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting signed 16-bit PCM samples to signed 32-bit PCM samples. +void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount); + +// Low-level function for converting signed 24-bit PCM samples to signed 32-bit PCM samples. +void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting IEEE 32-bit floating point samples to signed 32-bit PCM samples. +void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount); + +// Low-level function for converting IEEE 64-bit floating point samples to signed 32-bit PCM samples. +void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount); + +// Low-level function for converting A-law samples to signed 32-bit PCM samples. +void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting u-law samples to signed 32-bit PCM samples. +void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +#endif //DR_WAV_NO_CONVERSION_API + + +//// High-Level Convenience Helpers //// + +#ifndef DR_WAV_NO_STDIO + +// Helper for initializing a wave file using stdio. +// +// This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav +// objects because the operating system may restrict the number of file handles an application can have open at +// any given time. +drwav_bool32 drwav_init_file(drwav* pWav, const char* filename); +drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags); + +// Helper for initializing a wave file for writing using stdio. +// +// This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav +// objects because the operating system may restrict the number of file handles an application can have open at +// any given time. +drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat); +drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); + +// Helper for opening a wave file using stdio. +// +// This holds the internal FILE object until drwav_close() is called. Keep this in mind if you're caching drwav +// objects because the operating system may restrict the number of file handles an application can have open at +// any given time. +drwav* drwav_open_file(const char* filename); +drwav* drwav_open_file_ex(const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags); + +// Helper for opening a wave file for writing using stdio. +// +// This holds the internal FILE object until drwav_close() is called. Keep this in mind if you're caching drwav +// objects because the operating system may restrict the number of file handles an application can have open at +// any given time. +drwav* drwav_open_file_write(const char* filename, const drwav_data_format* pFormat); +drwav* drwav_open_file_write_sequential(const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); + +#endif //DR_WAV_NO_STDIO + +// Helper for initializing a loader from a pre-allocated memory buffer. +// +// This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for +// the lifetime of the drwav object. +// +// The buffer should contain the contents of the entire wave file, not just the sample data. +drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize); +drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags); + +// Helper for initializing a writer which outputs data to a memory buffer. +// +// dr_wav will manage the memory allocations, however it is up to the caller to free the data with drwav_free(). +// +// The buffer will remain allocated even after drwav_uninit() is called. Indeed, the buffer should not be +// considered valid until after drwav_uninit() has been called anyway. +drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat); +drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); + +// Helper for opening a loader from a pre-allocated memory buffer. +// +// This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for +// the lifetime of the drwav object. +// +// The buffer should contain the contents of the entire wave file, not just the sample data. +drwav* drwav_open_memory(const void* data, size_t dataSize); +drwav* drwav_open_memory_ex(const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags); + +// Helper for opening a writer which outputs data to a memory buffer. +// +// dr_wav will manage the memory allocations, however it is up to the caller to free the data with drwav_free(). +// +// The buffer will remain allocated even after drwav_close() is called. Indeed, the buffer should not be +// considered valid until after drwav_close() has been called anyway. +drwav* drwav_open_memory_write(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat); +drwav* drwav_open_memory_write_sequential(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); + + +#ifndef DR_WAV_NO_CONVERSION_API +// Opens and reads a wav file in a single operation. +drwav_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +float* drwav_open_and_read_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +drwav_int32* drwav_open_and_read_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +#ifndef DR_WAV_NO_STDIO +// Opens and decodes a wav file in a single operation. +drwav_int16* drwav_open_file_and_read_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +float* drwav_open_file_and_read_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +drwav_int32* drwav_open_file_and_read_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +#endif + +// Opens and decodes a wav file from a block of memory in a single operation. +drwav_int16* drwav_open_memory_and_read_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +float* drwav_open_memory_and_read_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +drwav_int32* drwav_open_memory_and_read_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +#endif + +// Frees data that was allocated internally by dr_wav. +void drwav_free(void* pDataReturnedByOpenAndRead); + +#ifdef __cplusplus +} +#endif +#endif // dr_wav_h + + +///////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +///////////////////////////////////////////////////// + +#ifdef DR_WAV_IMPLEMENTATION +#include +#include // For memcpy(), memset() +#include // For INT_MAX + +#ifndef DR_WAV_NO_STDIO +#include +#endif + +// Standard library stuff. +#ifndef DRWAV_ASSERT +#include +#define DRWAV_ASSERT(expression) assert(expression) +#endif +#ifndef DRWAV_MALLOC +#define DRWAV_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRWAV_REALLOC +#define DRWAV_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRWAV_FREE +#define DRWAV_FREE(p) free((p)) +#endif +#ifndef DRWAV_COPY_MEMORY +#define DRWAV_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRWAV_ZERO_MEMORY +#define DRWAV_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif + +#define drwav_countof(x) (sizeof(x) / sizeof(x[0])) +#define drwav_align(x, a) ((((x) + (a) - 1) / (a)) * (a)) +#define drwav_min(a, b) (((a) < (b)) ? (a) : (b)) +#define drwav_max(a, b) (((a) > (b)) ? (a) : (b)) +#define drwav_clamp(x, lo, hi) (drwav_max((lo), drwav_min((hi), (x)))) + +#define drwav_assert DRWAV_ASSERT +#define drwav_copy_memory DRWAV_COPY_MEMORY +#define drwav_zero_memory DRWAV_ZERO_MEMORY + +typedef drwav_int32 drwav_result; +#define DRWAV_SUCCESS 0 +#define DRWAV_ERROR -1 +#define DRWAV_INVALID_ARGS -2 +#define DRWAV_INVALID_OPERATION -3 +#define DRWAV_INVALID_FILE -100 +#define DRWAV_EOF -101 + +#define DRWAV_MAX_SIMD_VECTOR_SIZE 64 // 64 for AVX-512 in the future. + +#ifdef _MSC_VER +#define DRWAV_INLINE __forceinline +#else +#ifdef __GNUC__ +#define DRWAV_INLINE inline __attribute__((always_inline)) +#else +#define DRWAV_INLINE inline +#endif +#endif + +#if defined(SIZE_MAX) + #define DRWAV_SIZE_MAX SIZE_MAX +#else + #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) + #define DRWAV_SIZE_MAX ((drwav_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRWAV_SIZE_MAX 0xFFFFFFFF + #endif +#endif + +static const drwav_uint8 drwavGUID_W64_RIFF[16] = {0x72,0x69,0x66,0x66, 0x2E,0x91, 0xCF,0x11, 0xA5,0xD6, 0x28,0xDB,0x04,0xC1,0x00,0x00}; // 66666972-912E-11CF-A5D6-28DB04C10000 +static const drwav_uint8 drwavGUID_W64_WAVE[16] = {0x77,0x61,0x76,0x65, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 65766177-ACF3-11D3-8CD1-00C04F8EDB8A +static const drwav_uint8 drwavGUID_W64_JUNK[16] = {0x6A,0x75,0x6E,0x6B, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 6B6E756A-ACF3-11D3-8CD1-00C04F8EDB8A +static const drwav_uint8 drwavGUID_W64_FMT [16] = {0x66,0x6D,0x74,0x20, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 20746D66-ACF3-11D3-8CD1-00C04F8EDB8A +static const drwav_uint8 drwavGUID_W64_FACT[16] = {0x66,0x61,0x63,0x74, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 74636166-ACF3-11D3-8CD1-00C04F8EDB8A +static const drwav_uint8 drwavGUID_W64_DATA[16] = {0x64,0x61,0x74,0x61, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 61746164-ACF3-11D3-8CD1-00C04F8EDB8A +static const drwav_uint8 drwavGUID_W64_SMPL[16] = {0x73,0x6D,0x70,0x6C, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 6C706D73-ACF3-11D3-8CD1-00C04F8EDB8A + +static DRWAV_INLINE drwav_bool32 drwav__guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]) +{ + const drwav_uint32* a32 = (const drwav_uint32*)a; + const drwav_uint32* b32 = (const drwav_uint32*)b; + + return + a32[0] == b32[0] && + a32[1] == b32[1] && + a32[2] == b32[2] && + a32[3] == b32[3]; +} + +static DRWAV_INLINE drwav_bool32 drwav__fourcc_equal(const unsigned char* a, const char* b) +{ + return + a[0] == b[0] && + a[1] == b[1] && + a[2] == b[2] && + a[3] == b[3]; +} + + + +static DRWAV_INLINE int drwav__is_little_endian() +{ + int n = 1; + return (*(char*)&n) == 1; +} + +static DRWAV_INLINE unsigned short drwav__bytes_to_u16(const unsigned char* data) +{ + return (data[0] << 0) | (data[1] << 8); +} + +static DRWAV_INLINE short drwav__bytes_to_s16(const unsigned char* data) +{ + return (short)drwav__bytes_to_u16(data); +} + +static DRWAV_INLINE unsigned int drwav__bytes_to_u32(const unsigned char* data) +{ + return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); +} + +static DRWAV_INLINE drwav_uint64 drwav__bytes_to_u64(const unsigned char* data) +{ + return + ((drwav_uint64)data[0] << 0) | ((drwav_uint64)data[1] << 8) | ((drwav_uint64)data[2] << 16) | ((drwav_uint64)data[3] << 24) | + ((drwav_uint64)data[4] << 32) | ((drwav_uint64)data[5] << 40) | ((drwav_uint64)data[6] << 48) | ((drwav_uint64)data[7] << 56); +} + +static DRWAV_INLINE void drwav__bytes_to_guid(const unsigned char* data, drwav_uint8* guid) +{ + for (int i = 0; i < 16; ++i) { + guid[i] = data[i]; + } +} + + +static DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 formatTag) +{ + return + formatTag == DR_WAVE_FORMAT_ADPCM || + formatTag == DR_WAVE_FORMAT_DVI_ADPCM; +} + + +drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +drwav_uint64 drwav_read_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData); +drwav* drwav_open_write__internal(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData); + +static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) +{ + if (container == drwav_container_riff) { + if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { + return DRWAV_EOF; + } + + unsigned char sizeInBytes[4]; + if (onRead(pUserData, sizeInBytes, 4) != 4) { + return DRWAV_INVALID_FILE; + } + + pHeaderOut->sizeInBytes = drwav__bytes_to_u32(sizeInBytes); + pHeaderOut->paddingSize = (unsigned int)(pHeaderOut->sizeInBytes % 2); + *pRunningBytesReadOut += 8; + } else { + if (onRead(pUserData, pHeaderOut->id.guid, 16) != 16) { + return DRWAV_EOF; + } + + unsigned char sizeInBytes[8]; + if (onRead(pUserData, sizeInBytes, 8) != 8) { + return DRWAV_INVALID_FILE; + } + + pHeaderOut->sizeInBytes = drwav__bytes_to_u64(sizeInBytes) - 24; // <-- Subtract 24 because w64 includes the size of the header. + pHeaderOut->paddingSize = (unsigned int)(pHeaderOut->sizeInBytes % 8); + *pRunningBytesReadOut += 24; + } + + return DRWAV_SUCCESS; +} + +static drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +{ + drwav_uint64 bytesRemainingToSeek = offset; + while (bytesRemainingToSeek > 0) { + if (bytesRemainingToSeek > 0x7FFFFFFF) { + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + bytesRemainingToSeek -= 0x7FFFFFFF; + } else { + if (!onSeek(pUserData, (int)bytesRemainingToSeek, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + bytesRemainingToSeek = 0; + } + } + + return DRWAV_TRUE; +} + +static drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +{ + if (offset <= 0x7FFFFFFF) { + return onSeek(pUserData, (int)offset, drwav_seek_origin_start); + } + + // Larger than 32-bit seek. + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_start)) { + return DRWAV_FALSE; + } + offset -= 0x7FFFFFFF; + + for (;;) { + if (offset <= 0x7FFFFFFF) { + return onSeek(pUserData, (int)offset, drwav_seek_origin_current); + } + + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + offset -= 0x7FFFFFFF; + } + + // Should never get here. + //return DRWAV_TRUE; +} + + +static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_fmt* fmtOut) +{ + drwav_chunk_header header; + if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + + // Skip non-fmt chunks. + while ((container == drwav_container_riff && !drwav__fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT))) { + if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) { + return DRWAV_FALSE; + } + *pRunningBytesReadOut += header.sizeInBytes + header.paddingSize; + + // Try the next header. + if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + } + + + // Validation. + if (container == drwav_container_riff) { + if (!drwav__fourcc_equal(header.id.fourcc, "fmt ")) { + return DRWAV_FALSE; + } + } else { + if (!drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT)) { + return DRWAV_FALSE; + } + } + + + unsigned char fmt[16]; + if (onRead(pUserData, fmt, sizeof(fmt)) != sizeof(fmt)) { + return DRWAV_FALSE; + } + *pRunningBytesReadOut += sizeof(fmt); + + fmtOut->formatTag = drwav__bytes_to_u16(fmt + 0); + fmtOut->channels = drwav__bytes_to_u16(fmt + 2); + fmtOut->sampleRate = drwav__bytes_to_u32(fmt + 4); + fmtOut->avgBytesPerSec = drwav__bytes_to_u32(fmt + 8); + fmtOut->blockAlign = drwav__bytes_to_u16(fmt + 12); + fmtOut->bitsPerSample = drwav__bytes_to_u16(fmt + 14); + + fmtOut->extendedSize = 0; + fmtOut->validBitsPerSample = 0; + fmtOut->channelMask = 0; + memset(fmtOut->subFormat, 0, sizeof(fmtOut->subFormat)); + + if (header.sizeInBytes > 16) { + unsigned char fmt_cbSize[2]; + if (onRead(pUserData, fmt_cbSize, sizeof(fmt_cbSize)) != sizeof(fmt_cbSize)) { + return DRWAV_FALSE; // Expecting more data. + } + *pRunningBytesReadOut += sizeof(fmt_cbSize); + + int bytesReadSoFar = 18; + + fmtOut->extendedSize = drwav__bytes_to_u16(fmt_cbSize); + if (fmtOut->extendedSize > 0) { + // Simple validation. + if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + if (fmtOut->extendedSize != 22) { + return DRWAV_FALSE; + } + } + + if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + unsigned char fmtext[22]; + if (onRead(pUserData, fmtext, fmtOut->extendedSize) != fmtOut->extendedSize) { + return DRWAV_FALSE; // Expecting more data. + } + + fmtOut->validBitsPerSample = drwav__bytes_to_u16(fmtext + 0); + fmtOut->channelMask = drwav__bytes_to_u32(fmtext + 2); + drwav__bytes_to_guid(fmtext + 6, fmtOut->subFormat); + } else { + if (!onSeek(pUserData, fmtOut->extendedSize, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + } + *pRunningBytesReadOut += fmtOut->extendedSize; + + bytesReadSoFar += fmtOut->extendedSize; + } + + // Seek past any leftover bytes. For w64 the leftover will be defined based on the chunk size. + if (!onSeek(pUserData, (int)(header.sizeInBytes - bytesReadSoFar), drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + *pRunningBytesReadOut += (header.sizeInBytes - bytesReadSoFar); + } + + if (header.paddingSize > 0) { + if (!onSeek(pUserData, header.paddingSize, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + *pRunningBytesReadOut += header.paddingSize; + } + + return DRWAV_TRUE; +} + + +#ifndef DR_WAV_NO_STDIO +FILE* drwav_fopen(const char* filePath, const char* openMode) +{ + FILE* pFile; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (fopen_s(&pFile, filePath, openMode) != 0) { + return DRWAV_FALSE; + } +#else + pFile = fopen(filePath, openMode); + if (pFile == NULL) { + return DRWAV_FALSE; + } +#endif + + return pFile; +} + +static size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +static size_t drwav__on_write_stdio(void* pUserData, const void* pData, size_t bytesToWrite) +{ + return fwrite(pData, 1, bytesToWrite, (FILE*)pUserData); +} + +static drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin) +{ + return fseek((FILE*)pUserData, offset, (origin == drwav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +drwav_bool32 drwav_init_file(drwav* pWav, const char* filename) +{ + return drwav_init_file_ex(pWav, filename, NULL, NULL, 0); +} + +drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + FILE* pFile = drwav_fopen(filename, "rb"); + if (pFile == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_ex(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, onChunk, (void*)pFile, pChunkUserData, flags); +} + + +drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential) +{ + FILE* pFile = drwav_fopen(filename, "wb"); + if (pFile == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_write__internal(pWav, pFormat, totalSampleCount, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile); +} + +drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat) +{ + return drwav_init_file_write__internal(pWav, filename, pFormat, 0, DRWAV_FALSE); +} + +drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +{ + return drwav_init_file_write__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE); +} + +drwav* drwav_open_file(const char* filename) +{ + return drwav_open_file_ex(filename, NULL, NULL, 0); +} + +drwav* drwav_open_file_ex(const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + FILE* pFile = drwav_fopen(filename, "rb"); + if (pFile == NULL) { + return DRWAV_FALSE; + } + + drwav* pWav = drwav_open_ex(drwav__on_read_stdio, drwav__on_seek_stdio, onChunk, (void*)pFile, pChunkUserData, flags); + if (pWav == NULL) { + fclose(pFile); + return NULL; + } + + return pWav; +} + + +drwav* drwav_open_file_write__internal(const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential) +{ + FILE* pFile = drwav_fopen(filename, "wb"); + if (pFile == NULL) { + return DRWAV_FALSE; + } + + drwav* pWav = drwav_open_write__internal(pFormat, totalSampleCount, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile); + if (pWav == NULL) { + fclose(pFile); + return NULL; + } + + return pWav; +} + +drwav* drwav_open_file_write(const char* filename, const drwav_data_format* pFormat) +{ + return drwav_open_file_write__internal(filename, pFormat, 0, DRWAV_FALSE); +} + +drwav* drwav_open_file_write_sequential(const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +{ + return drwav_open_file_write__internal(filename, pFormat, totalSampleCount, DRWAV_TRUE); +} +#endif //DR_WAV_NO_STDIO + + +static size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + drwav__memory_stream* memory = (drwav__memory_stream*)pUserData; + drwav_assert(memory != NULL); + drwav_assert(memory->dataSize >= memory->currentReadPos); + + size_t bytesRemaining = memory->dataSize - memory->currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + DRWAV_COPY_MEMORY(pBufferOut, memory->data + memory->currentReadPos, bytesToRead); + memory->currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +static drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin) +{ + drwav__memory_stream* memory = (drwav__memory_stream*)pUserData; + drwav_assert(memory != NULL); + + if (origin == drwav_seek_origin_current) { + if (offset > 0) { + if (memory->currentReadPos + offset > memory->dataSize) { + return DRWAV_FALSE; // Trying to seek too far forward. + } + } else { + if (memory->currentReadPos < (size_t)-offset) { + return DRWAV_FALSE; // Trying to seek too far backwards. + } + } + + // This will never underflow thanks to the clamps above. + memory->currentReadPos += offset; + } else { + if ((drwav_uint32)offset <= memory->dataSize) { + memory->currentReadPos = offset; + } else { + return DRWAV_FALSE; // Trying to seek too far forward. + } + } + + return DRWAV_TRUE; +} + +static size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) +{ + drwav__memory_stream_write* memory = (drwav__memory_stream_write*)pUserData; + drwav_assert(memory != NULL); + drwav_assert(memory->dataCapacity >= memory->currentWritePos); + + size_t bytesRemaining = memory->dataCapacity - memory->currentWritePos; + if (bytesRemaining < bytesToWrite) { + // Need to reallocate. + size_t newDataCapacity = (memory->dataCapacity == 0) ? 256 : memory->dataCapacity * 2; + + // If doubling wasn't enough, just make it the minimum required size to write the data. + if ((newDataCapacity - memory->currentWritePos) < bytesToWrite) { + newDataCapacity = memory->currentWritePos + bytesToWrite; + } + + void* pNewData = DRWAV_REALLOC(*memory->ppData, newDataCapacity); + if (pNewData == NULL) { + return 0; + } + + *memory->ppData = pNewData; + memory->dataCapacity = newDataCapacity; + } + + drwav_uint8* pDataOut = (drwav_uint8*)(*memory->ppData); + DRWAV_COPY_MEMORY(pDataOut + memory->currentWritePos, pDataIn, bytesToWrite); + + memory->currentWritePos += bytesToWrite; + if (memory->dataSize < memory->currentWritePos) { + memory->dataSize = memory->currentWritePos; + } + + *memory->pDataSize = memory->dataSize; + + return bytesToWrite; +} + +static drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin) +{ + drwav__memory_stream_write* memory = (drwav__memory_stream_write*)pUserData; + drwav_assert(memory != NULL); + + if (origin == drwav_seek_origin_current) { + if (offset > 0) { + if (memory->currentWritePos + offset > memory->dataSize) { + offset = (int)(memory->dataSize - memory->currentWritePos); // Trying to seek too far forward. + } + } else { + if (memory->currentWritePos < (size_t)-offset) { + offset = -(int)memory->currentWritePos; // Trying to seek too far backwards. + } + } + + // This will never underflow thanks to the clamps above. + memory->currentWritePos += offset; + } else { + if ((drwav_uint32)offset <= memory->dataSize) { + memory->currentWritePos = offset; + } else { + memory->currentWritePos = memory->dataSize; // Trying to seek too far forward. + } + } + + return DRWAV_TRUE; +} + +drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize) +{ + return drwav_init_memory_ex(pWav, data, dataSize, NULL, NULL, 0); +} + +drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + if (data == NULL || dataSize == 0) { + return DRWAV_FALSE; + } + + drwav__memory_stream memoryStream; + drwav_zero_memory(&memoryStream, sizeof(memoryStream)); + memoryStream.data = (const unsigned char*)data; + memoryStream.dataSize = dataSize; + memoryStream.currentReadPos = 0; + + if (!drwav_init_ex(pWav, drwav__on_read_memory, drwav__on_seek_memory, onChunk, (void*)&memoryStream, pChunkUserData, flags)) { + return DRWAV_FALSE; + } + + pWav->memoryStream = memoryStream; + pWav->pUserData = &pWav->memoryStream; + return DRWAV_TRUE; +} + + +drwav_bool32 drwav_init_memory_write__internal(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential) +{ + if (ppData == NULL) { + return DRWAV_FALSE; + } + + *ppData = NULL; // Important because we're using realloc()! + *pDataSize = 0; + + drwav__memory_stream_write memoryStreamWrite; + drwav_zero_memory(&memoryStreamWrite, sizeof(memoryStreamWrite)); + memoryStreamWrite.ppData = ppData; + memoryStreamWrite.pDataSize = pDataSize; + memoryStreamWrite.dataSize = 0; + memoryStreamWrite.dataCapacity = 0; + memoryStreamWrite.currentWritePos = 0; + + if (!drwav_init_write__internal(pWav, pFormat, totalSampleCount, isSequential, drwav__on_write_memory, drwav__on_seek_memory_write, (void*)&memoryStreamWrite)) { + return DRWAV_FALSE; + } + + pWav->memoryStreamWrite = memoryStreamWrite; + pWav->pUserData = &pWav->memoryStreamWrite; + return DRWAV_TRUE; +} + +drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat) +{ + return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, 0, DRWAV_FALSE); +} + +drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +{ + return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, totalSampleCount, DRWAV_TRUE); +} + + +drwav* drwav_open_memory(const void* data, size_t dataSize) +{ + return drwav_open_memory_ex(data, dataSize, NULL, NULL, 0); +} + +drwav* drwav_open_memory_ex(const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + if (data == NULL || dataSize == 0) { + return NULL; + } + + drwav__memory_stream memoryStream; + drwav_zero_memory(&memoryStream, sizeof(memoryStream)); + memoryStream.data = (const unsigned char*)data; + memoryStream.dataSize = dataSize; + memoryStream.currentReadPos = 0; + + drwav* pWav = drwav_open_ex(drwav__on_read_memory, drwav__on_seek_memory, onChunk, (void*)&memoryStream, pChunkUserData, flags); + if (pWav == NULL) { + return NULL; + } + + pWav->memoryStream = memoryStream; + pWav->pUserData = &pWav->memoryStream; + return pWav; +} + + +drwav* drwav_open_memory_write__internal(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential) +{ + if (ppData == NULL) { + return NULL; + } + + *ppData = NULL; // Important because we're using realloc()! + *pDataSize = 0; + + drwav__memory_stream_write memoryStreamWrite; + drwav_zero_memory(&memoryStreamWrite, sizeof(memoryStreamWrite)); + memoryStreamWrite.ppData = ppData; + memoryStreamWrite.pDataSize = pDataSize; + memoryStreamWrite.dataSize = 0; + memoryStreamWrite.dataCapacity = 0; + memoryStreamWrite.currentWritePos = 0; + + drwav* pWav = drwav_open_write__internal(pFormat, totalSampleCount, isSequential, drwav__on_write_memory, drwav__on_seek_memory_write, (void*)&memoryStreamWrite); + if (pWav == NULL) { + return NULL; + } + + pWav->memoryStreamWrite = memoryStreamWrite; + pWav->pUserData = &pWav->memoryStreamWrite; + return pWav; +} + +drwav* drwav_open_memory_write(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat) +{ + return drwav_open_memory_write__internal(ppData, pDataSize, pFormat, 0, DRWAV_FALSE); +} + +drwav* drwav_open_memory_write_sequential(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +{ + return drwav_open_memory_write__internal(ppData, pDataSize, pFormat, totalSampleCount, DRWAV_TRUE); +} + + +size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) +{ + drwav_assert(onRead != NULL); + drwav_assert(pCursor != NULL); + + size_t bytesRead = onRead(pUserData, pBufferOut, bytesToRead); + *pCursor += bytesRead; + return bytesRead; +} + +drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor) +{ + drwav_assert(onSeek != NULL); + drwav_assert(pCursor != NULL); + + if (!onSeek(pUserData, offset, origin)) { + return DRWAV_FALSE; + } + + if (origin == drwav_seek_origin_start) { + *pCursor = offset; + } else { + *pCursor += offset; + } + + return DRWAV_TRUE; +} + + +drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData) +{ + return drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0); +} + +drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags) +{ + if (onRead == NULL || onSeek == NULL) { + return DRWAV_FALSE; + } + + drwav_uint64 cursor = 0; // <-- Keeps track of the byte position so we can seek to specific locations. + drwav_bool32 sequential = (flags & DRWAV_SEQUENTIAL) != 0; + + drwav_zero_memory(pWav, sizeof(*pWav)); + pWav->onRead = onRead; + pWav->onSeek = onSeek; + pWav->pUserData = pReadSeekUserData; + + // The first 4 bytes should be the RIFF identifier. + unsigned char riff[4]; + if (drwav__on_read(onRead, pReadSeekUserData, riff, sizeof(riff), &cursor) != sizeof(riff)) { + return DRWAV_FALSE; + } + + // The first 4 bytes can be used to identify the container. For RIFF files it will start with "RIFF" and for + // w64 it will start with "riff". + if (drwav__fourcc_equal(riff, "RIFF")) { + pWav->container = drwav_container_riff; + } else if (drwav__fourcc_equal(riff, "riff")) { + pWav->container = drwav_container_w64; + + // Check the rest of the GUID for validity. + drwav_uint8 riff2[12]; + if (drwav__on_read(onRead, pReadSeekUserData, riff2, sizeof(riff2), &cursor) != sizeof(riff2)) { + return DRWAV_FALSE; + } + + for (int i = 0; i < 12; ++i) { + if (riff2[i] != drwavGUID_W64_RIFF[i+4]) { + return DRWAV_FALSE; + } + } + } else { + return DRWAV_FALSE; // Unknown or unsupported container. + } + + + if (pWav->container == drwav_container_riff) { + // RIFF/WAVE + unsigned char chunkSizeBytes[4]; + if (drwav__on_read(onRead, pReadSeekUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { + return DRWAV_FALSE; + } + + unsigned int chunkSize = drwav__bytes_to_u32(chunkSizeBytes); + if (chunkSize < 36) { + return DRWAV_FALSE; // Chunk size should always be at least 36 bytes. + } + + unsigned char wave[4]; + if (drwav__on_read(onRead, pReadSeekUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { + return DRWAV_FALSE; + } + + if (!drwav__fourcc_equal(wave, "WAVE")) { + return DRWAV_FALSE; // Expecting "WAVE". + } + } else { + // W64 + unsigned char chunkSize[8]; + if (drwav__on_read(onRead, pReadSeekUserData, chunkSize, sizeof(chunkSize), &cursor) != sizeof(chunkSize)) { + return DRWAV_FALSE; + } + + if (drwav__bytes_to_u64(chunkSize) < 80) { + return DRWAV_FALSE; + } + + drwav_uint8 wave[16]; + if (drwav__on_read(onRead, pReadSeekUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { + return DRWAV_FALSE; + } + + if (!drwav__guid_equal(wave, drwavGUID_W64_WAVE)) { + return DRWAV_FALSE; + } + } + + + // The next bytes should be the "fmt " chunk. + drwav_fmt fmt; + if (!drwav__read_fmt(onRead, onSeek, pReadSeekUserData, pWav->container, &cursor, &fmt)) { + return DRWAV_FALSE; // Failed to read the "fmt " chunk. + } + + // Basic validation. + if (fmt.sampleRate == 0 || fmt.channels == 0 || fmt.bitsPerSample == 0 || fmt.blockAlign == 0) { + return DRWAV_FALSE; // Invalid channel count. Probably an invalid WAV file. + } + + + // Translate the internal format. + unsigned short translatedFormatTag = fmt.formatTag; + if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + translatedFormatTag = drwav__bytes_to_u16(fmt.subFormat + 0); + } + + + + drwav_uint64 sampleCountFromFactChunk = 0; + + // We need to enumerate over each chunk for two reasons: + // 1) The "data" chunk may not be the next one + // 2) We may want to report each chunk back to the client + // + // In order to correctly report each chunk back to the client we will need to keep looping until the end of the file. + drwav_bool32 foundDataChunk = DRWAV_FALSE; + drwav_uint64 dataChunkSize = 0; + + // The next chunk we care about is the "data" chunk. This is not necessarily the next chunk so we'll need to loop. + drwav_uint64 chunkSize = 0; + for (;;) + { + drwav_chunk_header header; + drwav_result result = drwav__read_chunk_header(onRead, pReadSeekUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + if (!foundDataChunk) { + return DRWAV_FALSE; + } else { + break; // Probably at the end of the file. Get out of the loop. + } + } + + // Tell the client about this chunk. + if (!sequential && onChunk != NULL) { + drwav_uint64 callbackBytesRead = onChunk(pChunkUserData, onRead, onSeek, pReadSeekUserData, &header); + + // dr_wav may need to read the contents of the chunk, so we now need to seek back to the position before + // we called the callback. + if (callbackBytesRead > 0) { + if (!drwav__seek_from_start(onSeek, cursor, pReadSeekUserData)) { + return DRWAV_FALSE; + } + } + } + + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + + chunkSize = header.sizeInBytes; + if (pWav->container == drwav_container_riff) { + if (drwav__fourcc_equal(header.id.fourcc, "data")) { + foundDataChunk = DRWAV_TRUE; + dataChunkSize = chunkSize; + } + } else { + if (drwav__guid_equal(header.id.guid, drwavGUID_W64_DATA)) { + foundDataChunk = DRWAV_TRUE; + dataChunkSize = chunkSize; + } + } + + // If at this point we have found the data chunk and we're running in sequential mode, we need to break out of this loop. The reason for + // this is that we would otherwise require a backwards seek which sequential mode forbids. + if (foundDataChunk && sequential) { + break; + } + + // Optional. Get the total sample count from the FACT chunk. This is useful for compressed formats. + if (pWav->container == drwav_container_riff) { + if (drwav__fourcc_equal(header.id.fourcc, "fact")) { + drwav_uint32 sampleCount; + if (drwav__on_read(onRead, pReadSeekUserData, &sampleCount, 4, &cursor) != 4) { + return DRWAV_FALSE; + } + chunkSize -= 4; + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + + // The sample count in the "fact" chunk is either unreliable, or I'm not understanding it properly. For now I am only enabling this + // for Microsoft ADPCM formats. + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + sampleCountFromFactChunk = sampleCount; + } else { + sampleCountFromFactChunk = 0; + } + } + } else { + if (drwav__guid_equal(header.id.guid, drwavGUID_W64_FACT)) { + if (drwav__on_read(onRead, pReadSeekUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { + return DRWAV_FALSE; + } + chunkSize -= 8; + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + } + } + + // "smpl" chunk. + if (pWav->container == drwav_container_riff) { + if (drwav__fourcc_equal(header.id.fourcc, "smpl")) { + unsigned char smplHeaderData[36]; // 36 = size of the smpl header section, not including the loop data. + if (chunkSize >= sizeof(smplHeaderData)) { + drwav_uint64 bytesJustRead = drwav__on_read(onRead, pReadSeekUserData, smplHeaderData, sizeof(smplHeaderData), &cursor); + chunkSize -= bytesJustRead; + + if (bytesJustRead == sizeof(smplHeaderData)) { + pWav->smpl.manufacturer = drwav__bytes_to_u32(smplHeaderData+0); + pWav->smpl.product = drwav__bytes_to_u32(smplHeaderData+4); + pWav->smpl.samplePeriod = drwav__bytes_to_u32(smplHeaderData+8); + pWav->smpl.midiUnityNotes = drwav__bytes_to_u32(smplHeaderData+12); + pWav->smpl.midiPitchFraction = drwav__bytes_to_u32(smplHeaderData+16); + pWav->smpl.smpteFormat = drwav__bytes_to_u32(smplHeaderData+20); + pWav->smpl.smpteOffset = drwav__bytes_to_u32(smplHeaderData+24); + pWav->smpl.numSampleLoops = drwav__bytes_to_u32(smplHeaderData+28); + pWav->smpl.samplerData = drwav__bytes_to_u32(smplHeaderData+32); + + for (drwav_uint32 iLoop = 0; iLoop < pWav->smpl.numSampleLoops && iLoop < drwav_countof(pWav->smpl.loops); ++iLoop) { + unsigned char smplLoopData[24]; // 24 = size of a loop section in the smpl chunk. + bytesJustRead = drwav__on_read(onRead, pReadSeekUserData, smplLoopData, sizeof(smplLoopData), &cursor); + chunkSize -= bytesJustRead; + + if (bytesJustRead == sizeof(smplLoopData)) { + pWav->smpl.loops[iLoop].cuePointId = drwav__bytes_to_u32(smplLoopData+0); + pWav->smpl.loops[iLoop].type = drwav__bytes_to_u32(smplLoopData+4); + pWav->smpl.loops[iLoop].start = drwav__bytes_to_u32(smplLoopData+8); + pWav->smpl.loops[iLoop].end = drwav__bytes_to_u32(smplLoopData+12); + pWav->smpl.loops[iLoop].fraction = drwav__bytes_to_u32(smplLoopData+16); + pWav->smpl.loops[iLoop].playCount = drwav__bytes_to_u32(smplLoopData+20); + } else { + break; // Break from the smpl loop for loop. + } + } + } + } else { + // Looks like invalid data. Ignore the chunk. + } + } + } else { + if (drwav__guid_equal(header.id.guid, drwavGUID_W64_SMPL)) { + // This path will be hit when a W64 WAV file contains a smpl chunk. I don't have a sample file to test this path, so a contribution + // is welcome to add support for this. + } + } + + // Make sure we seek past the padding. + chunkSize += header.paddingSize; + if (!drwav__seek_forward(onSeek, chunkSize, pReadSeekUserData)) { + break; + } + cursor += chunkSize; + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + } + + // If we haven't found a data chunk, return an error. + if (!foundDataChunk) { + return DRWAV_FALSE; + } + + // We may have moved passed the data chunk. If so we need to move back. If running in sequential mode we can assume we are already sitting on the data chunk. + if (!sequential) { + if (!drwav__seek_from_start(onSeek, pWav->dataChunkDataPos, pReadSeekUserData)) { + return DRWAV_FALSE; + } + cursor = pWav->dataChunkDataPos; + } + + + // At this point we should be sitting on the first byte of the raw audio data. + + pWav->fmt = fmt; + pWav->sampleRate = fmt.sampleRate; + pWav->channels = fmt.channels; + pWav->bitsPerSample = fmt.bitsPerSample; + pWav->bytesPerSample = fmt.blockAlign / fmt.channels; + pWav->bytesRemaining = dataChunkSize; + pWav->translatedFormatTag = translatedFormatTag; + pWav->dataChunkDataSize = dataChunkSize; + + // The bytes per sample should never be 0 at this point. This would indicate an invalid WAV file. + if (pWav->bytesPerSample == 0) { + return DRWAV_FALSE; + } + + if (sampleCountFromFactChunk != 0) { + pWav->totalSampleCount = sampleCountFromFactChunk * fmt.channels; + } else { + pWav->totalSampleCount = dataChunkSize / pWav->bytesPerSample; + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalSampleCount = (blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2; // x2 because two samples per byte. + } + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalSampleCount = ((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels); + } + } + + // The way we calculate the bytes per sample does not make sense for compressed formats so we just set it to 0. + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + pWav->bytesPerSample = 0; + } + + // Some formats only support a certain number of channels. + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + if (pWav->channels > 2) { + return DRWAV_FALSE; + } + } + +#ifdef DR_WAV_LIBSNDFILE_COMPAT + // I use libsndfile as a benchmark for testing, however in the version I'm using (from the Windows installer on the libsndfile website), + // it appears the total sample count libsndfile uses for MS-ADPCM is incorrect. It would seem they are computing the total sample count + // from the number of blocks, however this results in the inclusion of extra silent samples at the end of the last block. The correct + // way to know the total sample count is to inspect the "fact" chunk, which should always be present for compressed formats, and should + // always include the sample count. This little block of code below is only used to emulate the libsndfile logic so I can properly run my + // correctness tests against libsndfile, and is disabled by default. + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalSampleCount = (blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2; // x2 because two samples per byte. + } + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalSampleCount = ((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels); + } +#endif + + return DRWAV_TRUE; +} + + +drwav_uint32 drwav_riff_chunk_size_riff(drwav_uint64 dataChunkSize) +{ + if (dataChunkSize <= (0xFFFFFFFF - 36)) { + return 36 + (drwav_uint32)dataChunkSize; + } else { + return 0xFFFFFFFF; + } +} + +drwav_uint32 drwav_data_chunk_size_riff(drwav_uint64 dataChunkSize) +{ + if (dataChunkSize <= 0xFFFFFFFF) { + return (drwav_uint32)dataChunkSize; + } else { + return 0xFFFFFFFF; + } +} + +drwav_uint64 drwav_riff_chunk_size_w64(drwav_uint64 dataChunkSize) +{ + return 80 + 24 + dataChunkSize; // +24 because W64 includes the size of the GUID and size fields. +} + +drwav_uint64 drwav_data_chunk_size_w64(drwav_uint64 dataChunkSize) +{ + return 24 + dataChunkSize; // +24 because W64 includes the size of the GUID and size fields. +} + + +drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData) +{ + if (pWav == NULL) { + return DRWAV_FALSE; + } + + if (onWrite == NULL) { + return DRWAV_FALSE; + } + + if (!isSequential && onSeek == NULL) { + return DRWAV_FALSE; // <-- onSeek is required when in non-sequential mode. + } + + + // Not currently supporting compressed formats. Will need to add support for the "fact" chunk before we enable this. + if (pFormat->format == DR_WAVE_FORMAT_EXTENSIBLE) { + return DRWAV_FALSE; + } + if (pFormat->format == DR_WAVE_FORMAT_ADPCM || pFormat->format == DR_WAVE_FORMAT_DVI_ADPCM) { + return DRWAV_FALSE; + } + + + drwav_zero_memory(pWav, sizeof(*pWav)); + pWav->onWrite = onWrite; + pWav->onSeek = onSeek; + pWav->pUserData = pUserData; + pWav->fmt.formatTag = (drwav_uint16)pFormat->format; + pWav->fmt.channels = (drwav_uint16)pFormat->channels; + pWav->fmt.sampleRate = pFormat->sampleRate; + pWav->fmt.avgBytesPerSec = (drwav_uint32)((pFormat->bitsPerSample * pFormat->sampleRate * pFormat->channels) / 8); + pWav->fmt.blockAlign = (drwav_uint16)((pFormat->channels * pFormat->bitsPerSample) / 8); + pWav->fmt.bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; + pWav->fmt.extendedSize = 0; + pWav->isSequentialWrite = isSequential; + + + size_t runningPos = 0; + + // The initial values for the "RIFF" and "data" chunks depends on whether or not we are initializing in sequential mode or not. In + // sequential mode we set this to its final values straight away since they can be calculated from the total sample count. In non- + // sequential mode we initialize it all to zero and fill it out in drwav_uninit() using a backwards seek. + drwav_uint64 initialDataChunkSize = 0; + if (isSequential) { + initialDataChunkSize = (totalSampleCount * pWav->fmt.bitsPerSample) / 8; + + // The RIFF container has a limit on the number of samples. drwav is not allowing this. There's no practical limits for Wave64 + // so for the sake of simplicity I'm not doing any validation for that. + if (pFormat->container == drwav_container_riff) { + if (initialDataChunkSize > (0xFFFFFFFF - 36)) { + return DRWAV_FALSE; // Not enough room to store every sample. + } + } + } + + pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize; + + + // "RIFF" chunk. + if (pFormat->container == drwav_container_riff) { + drwav_uint32 chunkSizeRIFF = 36 + (drwav_uint32)initialDataChunkSize; // +36 = "RIFF"+[RIFF Chunk Size]+"WAVE" + [sizeof "fmt " chunk] + runningPos += pWav->onWrite(pUserData, "RIFF", 4); + runningPos += pWav->onWrite(pUserData, &chunkSizeRIFF, 4); + runningPos += pWav->onWrite(pUserData, "WAVE", 4); + } else { + drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; // +24 because W64 includes the size of the GUID and size fields. + runningPos += pWav->onWrite(pUserData, drwavGUID_W64_RIFF, 16); + runningPos += pWav->onWrite(pUserData, &chunkSizeRIFF, 8); + runningPos += pWav->onWrite(pUserData, drwavGUID_W64_WAVE, 16); + } + + // "fmt " chunk. + drwav_uint64 chunkSizeFMT; + if (pFormat->container == drwav_container_riff) { + chunkSizeFMT = 16; + runningPos += pWav->onWrite(pUserData, "fmt ", 4); + runningPos += pWav->onWrite(pUserData, &chunkSizeFMT, 4); + } else { + chunkSizeFMT = 40; + runningPos += pWav->onWrite(pUserData, drwavGUID_W64_FMT, 16); + runningPos += pWav->onWrite(pUserData, &chunkSizeFMT, 8); + } + + runningPos += pWav->onWrite(pUserData, &pWav->fmt.formatTag, 2); + runningPos += pWav->onWrite(pUserData, &pWav->fmt.channels, 2); + runningPos += pWav->onWrite(pUserData, &pWav->fmt.sampleRate, 4); + runningPos += pWav->onWrite(pUserData, &pWav->fmt.avgBytesPerSec, 4); + runningPos += pWav->onWrite(pUserData, &pWav->fmt.blockAlign, 2); + runningPos += pWav->onWrite(pUserData, &pWav->fmt.bitsPerSample, 2); + + pWav->dataChunkDataPos = runningPos; + + // "data" chunk. + if (pFormat->container == drwav_container_riff) { + drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; + runningPos += pWav->onWrite(pUserData, "data", 4); + runningPos += pWav->onWrite(pUserData, &chunkSizeDATA, 4); + } else { + drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; // +24 because W64 includes the size of the GUID and size fields. + runningPos += pWav->onWrite(pUserData, drwavGUID_W64_DATA, 16); + runningPos += pWav->onWrite(pUserData, &chunkSizeDATA, 8); + } + + + // Simple validation. + if (pFormat->container == drwav_container_riff) { + if (runningPos != 20 + chunkSizeFMT + 8) { + return DRWAV_FALSE; + } + } else { + if (runningPos != 40 + chunkSizeFMT + 24) { + return DRWAV_FALSE; + } + } + + + + // Set some properties for the client's convenience. + pWav->container = pFormat->container; + pWav->channels = (drwav_uint16)pFormat->channels; + pWav->sampleRate = pFormat->sampleRate; + pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; + pWav->bytesPerSample = (drwav_uint16)(pFormat->bitsPerSample >> 3); + pWav->translatedFormatTag = (drwav_uint16)pFormat->format; + + return DRWAV_TRUE; +} + + +drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData) +{ + return drwav_init_write__internal(pWav, pFormat, 0, DRWAV_FALSE, onWrite, onSeek, pUserData); // DRWAV_FALSE = Not Sequential +} + +drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData) +{ + return drwav_init_write__internal(pWav, pFormat, totalSampleCount, DRWAV_TRUE, onWrite, NULL, pUserData); // DRWAV_TRUE = Sequential +} + +void drwav_uninit(drwav* pWav) +{ + if (pWav == NULL) { + return; + } + + // If the drwav object was opened in write mode we'll need to finalize a few things: + // - Make sure the "data" chunk is aligned to 16-bits for RIFF containers, or 64 bits for W64 containers. + // - Set the size of the "data" chunk. + if (pWav->onWrite != NULL) { + // Validation for sequential mode. + if (pWav->isSequentialWrite) { + drwav_assert(pWav->dataChunkDataSize == pWav->dataChunkDataSizeTargetWrite); + } + + // Padding. Do not adjust pWav->dataChunkDataSize - this should not include the padding. + drwav_uint32 paddingSize = 0; + if (pWav->container == drwav_container_riff) { + paddingSize = (drwav_uint32)(pWav->dataChunkDataSize % 2); + } else { + paddingSize = (drwav_uint32)(pWav->dataChunkDataSize % 8); + } + + if (paddingSize > 0) { + drwav_uint64 paddingData = 0; + pWav->onWrite(pWav->pUserData, &paddingData, paddingSize); + } + + + // Chunk sizes. When using sequential mode, these will have been filled in at initialization time. We only need + // to do this when using non-sequential mode. + if (pWav->onSeek && !pWav->isSequentialWrite) { + if (pWav->container == drwav_container_riff) { + // The "RIFF" chunk size. + if (pWav->onSeek(pWav->pUserData, 4, drwav_seek_origin_start)) { + drwav_uint32 riffChunkSize = drwav_riff_chunk_size_riff(pWav->dataChunkDataSize); + pWav->onWrite(pWav->pUserData, &riffChunkSize, 4); + } + + // the "data" chunk size. + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 4, drwav_seek_origin_start)) { + drwav_uint32 dataChunkSize = drwav_data_chunk_size_riff(pWav->dataChunkDataSize); + pWav->onWrite(pWav->pUserData, &dataChunkSize, 4); + } + } else { + // The "RIFF" chunk size. + if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav_riff_chunk_size_w64(pWav->dataChunkDataSize); + pWav->onWrite(pWav->pUserData, &riffChunkSize, 8); + } + + // The "data" chunk size. + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 16, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav_data_chunk_size_w64(pWav->dataChunkDataSize); + pWav->onWrite(pWav->pUserData, &dataChunkSize, 8); + } + } + } + } + +#ifndef DR_WAV_NO_STDIO + // If we opened the file with drwav_open_file() we will want to close the file handle. We can know whether or not drwav_open_file() + // was used by looking at the onRead and onSeek callbacks. + if (pWav->onRead == drwav__on_read_stdio || pWav->onWrite == drwav__on_write_stdio) { + fclose((FILE*)pWav->pUserData); + } +#endif +} + + +drwav* drwav_open(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData) +{ + return drwav_open_ex(onRead, onSeek, NULL, pUserData, NULL, 0); +} + +drwav* drwav_open_ex(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags) +{ + drwav* pWav = (drwav*)DRWAV_MALLOC(sizeof(*pWav)); + if (pWav == NULL) { + return NULL; + } + + if (!drwav_init_ex(pWav, onRead, onSeek, onChunk, pReadSeekUserData, pChunkUserData, flags)) { + DRWAV_FREE(pWav); + return NULL; + } + + return pWav; +} + + +drwav* drwav_open_write__internal(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData) +{ + drwav* pWav = (drwav*)DRWAV_MALLOC(sizeof(*pWav)); + if (pWav == NULL) { + return NULL; + } + + if (!drwav_init_write__internal(pWav, pFormat, totalSampleCount, isSequential, onWrite, onSeek, pUserData)) { + DRWAV_FREE(pWav); + return NULL; + } + + return pWav; +} + +drwav* drwav_open_write(const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData) +{ + return drwav_open_write__internal(pFormat, 0, DRWAV_FALSE, onWrite, onSeek, pUserData); +} + +drwav* drwav_open_write_sequential(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData) +{ + return drwav_open_write__internal(pFormat, totalSampleCount, DRWAV_TRUE, onWrite, NULL, pUserData); +} + +void drwav_close(drwav* pWav) +{ + drwav_uninit(pWav); + DRWAV_FREE(pWav); +} + + +size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut) +{ + if (pWav == NULL || bytesToRead == 0 || pBufferOut == NULL) { + return 0; + } + + if (bytesToRead > pWav->bytesRemaining) { + bytesToRead = (size_t)pWav->bytesRemaining; + } + + size_t bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); + + pWav->bytesRemaining -= bytesRead; + return bytesRead; +} + +drwav_uint64 drwav_read(drwav* pWav, drwav_uint64 samplesToRead, void* pBufferOut) +{ + if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) { + return 0; + } + + // Cannot use this function for compressed formats. + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + return 0; + } + + // Don't try to read more samples than can potentially fit in the output buffer. + if (samplesToRead * pWav->bytesPerSample > DRWAV_SIZE_MAX) { + samplesToRead = DRWAV_SIZE_MAX / pWav->bytesPerSample; + } + + size_t bytesRead = drwav_read_raw(pWav, (size_t)(samplesToRead * pWav->bytesPerSample), pBufferOut); + return bytesRead / pWav->bytesPerSample; +} + +drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) +{ + return drwav_read(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels; +} + +drwav_bool32 drwav_seek_to_first_sample(drwav* pWav) +{ + if (pWav->onWrite != NULL) { + return DRWAV_FALSE; // No seeking in write mode. + } + + if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, drwav_seek_origin_start)) { + return DRWAV_FALSE; + } + + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + pWav->compressed.iCurrentSample = 0; + } + + pWav->bytesRemaining = pWav->dataChunkDataSize; + return DRWAV_TRUE; +} + +drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample) +{ + // Seeking should be compatible with wave files > 2GB. + + if (pWav->onWrite != NULL) { + return DRWAV_FALSE; // No seeking in write mode. + } + + if (pWav == NULL || pWav->onSeek == NULL) { + return DRWAV_FALSE; + } + + // If there are no samples, just return DRWAV_TRUE without doing anything. + if (pWav->totalSampleCount == 0) { + return DRWAV_TRUE; + } + + // Make sure the sample is clamped. + if (sample >= pWav->totalSampleCount) { + sample = pWav->totalSampleCount - 1; + } + + + // For compressed formats we just use a slow generic seek. If we are seeking forward we just seek forward. If we are going backwards we need + // to seek back to the start. + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + // TODO: This can be optimized. + + // If we're seeking forward it's simple - just keep reading samples until we hit the sample we're requesting. If we're seeking backwards, + // we first need to seek back to the start and then just do the same thing as a forward seek. + if (sample < pWav->compressed.iCurrentSample) { + if (!drwav_seek_to_first_sample(pWav)) { + return DRWAV_FALSE; + } + } + + if (sample > pWav->compressed.iCurrentSample) { + drwav_uint64 offset = sample - pWav->compressed.iCurrentSample; + + drwav_int16 devnull[2048]; + while (offset > 0) { + drwav_uint64 samplesToRead = offset; + if (samplesToRead > 2048) { + samplesToRead = 2048; + } + + drwav_uint64 samplesRead = 0; + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + samplesRead = drwav_read_s16__msadpcm(pWav, samplesToRead, devnull); + } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + samplesRead = drwav_read_s16__ima(pWav, samplesToRead, devnull); + } else { + assert(DRWAV_FALSE); // If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. + } + + if (samplesRead != samplesToRead) { + return DRWAV_FALSE; + } + + offset -= samplesRead; + } + } + } else { + drwav_uint64 totalSizeInBytes = pWav->totalSampleCount * pWav->bytesPerSample; + drwav_assert(totalSizeInBytes >= pWav->bytesRemaining); + + drwav_uint64 currentBytePos = totalSizeInBytes - pWav->bytesRemaining; + drwav_uint64 targetBytePos = sample * pWav->bytesPerSample; + + drwav_uint64 offset; + if (currentBytePos < targetBytePos) { + // Offset forwards. + offset = (targetBytePos - currentBytePos); + } else { + // Offset backwards. + if (!drwav_seek_to_first_sample(pWav)) { + return DRWAV_FALSE; + } + offset = targetBytePos; + } + + while (offset > 0) { + int offset32 = ((offset > INT_MAX) ? INT_MAX : (int)offset); + if (!pWav->onSeek(pWav->pUserData, offset32, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + + pWav->bytesRemaining -= offset32; + offset -= offset32; + } + } + + return DRWAV_TRUE; +} + +drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex) +{ + return drwav_seek_to_sample(pWav, targetFrameIndex * pWav->channels); +} + + +size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData) +{ + if (pWav == NULL || bytesToWrite == 0 || pData == NULL) { + return 0; + } + + size_t bytesWritten = pWav->onWrite(pWav->pUserData, pData, bytesToWrite); + pWav->dataChunkDataSize += bytesWritten; + + return bytesWritten; +} + +drwav_uint64 drwav_write(drwav* pWav, drwav_uint64 samplesToWrite, const void* pData) +{ + if (pWav == NULL || samplesToWrite == 0 || pData == NULL) { + return 0; + } + + drwav_uint64 bytesToWrite = ((samplesToWrite * pWav->bitsPerSample) / 8); + if (bytesToWrite > DRWAV_SIZE_MAX) { + return 0; + } + + drwav_uint64 bytesWritten = 0; + const drwav_uint8* pRunningData = (const drwav_uint8*)pData; + while (bytesToWrite > 0) { + drwav_uint64 bytesToWriteThisIteration = bytesToWrite; + if (bytesToWriteThisIteration > DRWAV_SIZE_MAX) { + bytesToWriteThisIteration = DRWAV_SIZE_MAX; + } + + size_t bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, pRunningData); + if (bytesJustWritten == 0) { + break; + } + + bytesToWrite -= bytesJustWritten; + bytesWritten += bytesJustWritten; + pRunningData += bytesJustWritten; + } + + return (bytesWritten * 8) / pWav->bitsPerSample; +} + +drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) +{ + return drwav_write(pWav, framesToWrite * pWav->channels, pData) / pWav->channels; +} + + + +drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + drwav_assert(pWav != NULL); + drwav_assert(samplesToRead > 0); + drwav_assert(pBufferOut != NULL); + + // TODO: Lots of room for optimization here. + + drwav_uint64 totalSamplesRead = 0; + + while (samplesToRead > 0 && pWav->compressed.iCurrentSample < pWav->totalSampleCount) { + // If there are no cached samples we need to load a new block. + if (pWav->msadpcm.cachedSampleCount == 0 && pWav->msadpcm.bytesRemainingInBlock == 0) { + if (pWav->channels == 1) { + // Mono. + drwav_uint8 header[7]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalSamplesRead; + } + pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->msadpcm.predictor[0] = header[0]; + pWav->msadpcm.delta[0] = drwav__bytes_to_s16(header + 1); + pWav->msadpcm.prevSamples[0][1] = (drwav_int32)drwav__bytes_to_s16(header + 3); + pWav->msadpcm.prevSamples[0][0] = (drwav_int32)drwav__bytes_to_s16(header + 5); + pWav->msadpcm.cachedSamples[2] = pWav->msadpcm.prevSamples[0][0]; + pWav->msadpcm.cachedSamples[3] = pWav->msadpcm.prevSamples[0][1]; + pWav->msadpcm.cachedSampleCount = 2; + } else { + // Stereo. + drwav_uint8 header[14]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalSamplesRead; + } + pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->msadpcm.predictor[0] = header[0]; + pWav->msadpcm.predictor[1] = header[1]; + pWav->msadpcm.delta[0] = drwav__bytes_to_s16(header + 2); + pWav->msadpcm.delta[1] = drwav__bytes_to_s16(header + 4); + pWav->msadpcm.prevSamples[0][1] = (drwav_int32)drwav__bytes_to_s16(header + 6); + pWav->msadpcm.prevSamples[1][1] = (drwav_int32)drwav__bytes_to_s16(header + 8); + pWav->msadpcm.prevSamples[0][0] = (drwav_int32)drwav__bytes_to_s16(header + 10); + pWav->msadpcm.prevSamples[1][0] = (drwav_int32)drwav__bytes_to_s16(header + 12); + + pWav->msadpcm.cachedSamples[0] = pWav->msadpcm.prevSamples[0][0]; + pWav->msadpcm.cachedSamples[1] = pWav->msadpcm.prevSamples[1][0]; + pWav->msadpcm.cachedSamples[2] = pWav->msadpcm.prevSamples[0][1]; + pWav->msadpcm.cachedSamples[3] = pWav->msadpcm.prevSamples[1][1]; + pWav->msadpcm.cachedSampleCount = 4; + } + } + + // Output anything that's cached. + while (samplesToRead > 0 && pWav->msadpcm.cachedSampleCount > 0 && pWav->compressed.iCurrentSample < pWav->totalSampleCount) { + pBufferOut[0] = (drwav_int16)pWav->msadpcm.cachedSamples[drwav_countof(pWav->msadpcm.cachedSamples) - pWav->msadpcm.cachedSampleCount]; + pWav->msadpcm.cachedSampleCount -= 1; + + pBufferOut += 1; + samplesToRead -= 1; + totalSamplesRead += 1; + pWav->compressed.iCurrentSample += 1; + } + + if (samplesToRead == 0) { + return totalSamplesRead; + } + + + // If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next + // loop iteration which will trigger the loading of a new block. + if (pWav->msadpcm.cachedSampleCount == 0) { + if (pWav->msadpcm.bytesRemainingInBlock == 0) { + continue; + } else { + drwav_uint8 nibbles; + if (pWav->onRead(pWav->pUserData, &nibbles, 1) != 1) { + return totalSamplesRead; + } + pWav->msadpcm.bytesRemainingInBlock -= 1; + + // TODO: Optimize away these if statements. + drwav_int32 nibble0 = ((nibbles & 0xF0) >> 4); if ((nibbles & 0x80)) { nibble0 |= 0xFFFFFFF0UL; } + drwav_int32 nibble1 = ((nibbles & 0x0F) >> 0); if ((nibbles & 0x08)) { nibble1 |= 0xFFFFFFF0UL; } + + static drwav_int32 adaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 + }; + static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; + static drwav_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; + + if (pWav->channels == 1) { + // Mono. + drwav_int32 newSample0; + newSample0 = ((pWav->msadpcm.prevSamples[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevSamples[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample0 += nibble0 * pWav->msadpcm.delta[0]; + newSample0 = drwav_clamp(newSample0, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevSamples[0][0] = pWav->msadpcm.prevSamples[0][1]; + pWav->msadpcm.prevSamples[0][1] = newSample0; + + + drwav_int32 newSample1; + newSample1 = ((pWav->msadpcm.prevSamples[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevSamples[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample1 += nibble1 * pWav->msadpcm.delta[0]; + newSample1 = drwav_clamp(newSample1, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevSamples[0][0] = pWav->msadpcm.prevSamples[0][1]; + pWav->msadpcm.prevSamples[0][1] = newSample1; + + + pWav->msadpcm.cachedSamples[2] = newSample0; + pWav->msadpcm.cachedSamples[3] = newSample1; + pWav->msadpcm.cachedSampleCount = 2; + } else { + // Stereo. + + // Left. + drwav_int32 newSample0; + newSample0 = ((pWav->msadpcm.prevSamples[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevSamples[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample0 += nibble0 * pWav->msadpcm.delta[0]; + newSample0 = drwav_clamp(newSample0, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevSamples[0][0] = pWav->msadpcm.prevSamples[0][1]; + pWav->msadpcm.prevSamples[0][1] = newSample0; + + + // Right. + drwav_int32 newSample1; + newSample1 = ((pWav->msadpcm.prevSamples[1][1] * coeff1Table[pWav->msadpcm.predictor[1]]) + (pWav->msadpcm.prevSamples[1][0] * coeff2Table[pWav->msadpcm.predictor[1]])) >> 8; + newSample1 += nibble1 * pWav->msadpcm.delta[1]; + newSample1 = drwav_clamp(newSample1, -32768, 32767); + + pWav->msadpcm.delta[1] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[1]) >> 8; + if (pWav->msadpcm.delta[1] < 16) { + pWav->msadpcm.delta[1] = 16; + } + + pWav->msadpcm.prevSamples[1][0] = pWav->msadpcm.prevSamples[1][1]; + pWav->msadpcm.prevSamples[1][1] = newSample1; + + pWav->msadpcm.cachedSamples[2] = newSample0; + pWav->msadpcm.cachedSamples[3] = newSample1; + pWav->msadpcm.cachedSampleCount = 2; + } + } + } + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + drwav_assert(pWav != NULL); + drwav_assert(samplesToRead > 0); + drwav_assert(pBufferOut != NULL); + + // TODO: Lots of room for optimization here. + + drwav_uint64 totalSamplesRead = 0; + + while (samplesToRead > 0 && pWav->compressed.iCurrentSample < pWav->totalSampleCount) { + // If there are no cached samples we need to load a new block. + if (pWav->ima.cachedSampleCount == 0 && pWav->ima.bytesRemainingInBlock == 0) { + if (pWav->channels == 1) { + // Mono. + drwav_uint8 header[4]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalSamplesRead; + } + pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->ima.predictor[0] = drwav__bytes_to_s16(header + 0); + pWav->ima.stepIndex[0] = header[2]; + pWav->ima.cachedSamples[drwav_countof(pWav->ima.cachedSamples) - 1] = pWav->ima.predictor[0]; + pWav->ima.cachedSampleCount = 1; + } else { + // Stereo. + drwav_uint8 header[8]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalSamplesRead; + } + pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->ima.predictor[0] = drwav__bytes_to_s16(header + 0); + pWav->ima.stepIndex[0] = header[2]; + pWav->ima.predictor[1] = drwav__bytes_to_s16(header + 4); + pWav->ima.stepIndex[1] = header[6]; + + pWav->ima.cachedSamples[drwav_countof(pWav->ima.cachedSamples) - 2] = pWav->ima.predictor[0]; + pWav->ima.cachedSamples[drwav_countof(pWav->ima.cachedSamples) - 1] = pWav->ima.predictor[1]; + pWav->ima.cachedSampleCount = 2; + } + } + + // Output anything that's cached. + while (samplesToRead > 0 && pWav->ima.cachedSampleCount > 0 && pWav->compressed.iCurrentSample < pWav->totalSampleCount) { + pBufferOut[0] = (drwav_int16)pWav->ima.cachedSamples[drwav_countof(pWav->ima.cachedSamples) - pWav->ima.cachedSampleCount]; + pWav->ima.cachedSampleCount -= 1; + + pBufferOut += 1; + samplesToRead -= 1; + totalSamplesRead += 1; + pWav->compressed.iCurrentSample += 1; + } + + if (samplesToRead == 0) { + return totalSamplesRead; + } + + // If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next + // loop iteration which will trigger the loading of a new block. + if (pWav->ima.cachedSampleCount == 0) { + if (pWav->ima.bytesRemainingInBlock == 0) { + continue; + } else { + static drwav_int32 indexTable[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + + static drwav_int32 stepTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; + + // From what I can tell with stereo streams, it looks like every 4 bytes (8 samples) is for one channel. So it goes 4 bytes for the + // left channel, 4 bytes for the right channel. + pWav->ima.cachedSampleCount = 8 * pWav->channels; + for (drwav_uint32 iChannel = 0; iChannel < pWav->channels; ++iChannel) { + drwav_uint8 nibbles[4]; + if (pWav->onRead(pWav->pUserData, &nibbles, 4) != 4) { + return totalSamplesRead; + } + pWav->ima.bytesRemainingInBlock -= 4; + + for (drwav_uint32 iByte = 0; iByte < 4; ++iByte) { + drwav_uint8 nibble0 = ((nibbles[iByte] & 0x0F) >> 0); + drwav_uint8 nibble1 = ((nibbles[iByte] & 0xF0) >> 4); + + drwav_int32 step = stepTable[pWav->ima.stepIndex[iChannel]]; + drwav_int32 predictor = pWav->ima.predictor[iChannel]; + + drwav_int32 diff = step >> 3; + if (nibble0 & 1) diff += step >> 2; + if (nibble0 & 2) diff += step >> 1; + if (nibble0 & 4) diff += step; + if (nibble0 & 8) diff = -diff; + + predictor = drwav_clamp(predictor + diff, -32768, 32767); + pWav->ima.predictor[iChannel] = predictor; + pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble0], 0, (drwav_int32)drwav_countof(stepTable)-1); + pWav->ima.cachedSamples[(drwav_countof(pWav->ima.cachedSamples) - pWav->ima.cachedSampleCount) + (iByte*2+0)*pWav->channels + iChannel] = predictor; + + + step = stepTable[pWav->ima.stepIndex[iChannel]]; + predictor = pWav->ima.predictor[iChannel]; + + diff = step >> 3; + if (nibble1 & 1) diff += step >> 2; + if (nibble1 & 2) diff += step >> 1; + if (nibble1 & 4) diff += step; + if (nibble1 & 8) diff = -diff; + + predictor = drwav_clamp(predictor + diff, -32768, 32767); + pWav->ima.predictor[iChannel] = predictor; + pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble1], 0, (drwav_int32)drwav_countof(stepTable)-1); + pWav->ima.cachedSamples[(drwav_countof(pWav->ima.cachedSamples) - pWav->ima.cachedSampleCount) + (iByte*2+1)*pWav->channels + iChannel] = predictor; + } + } + } + } + } + + return totalSamplesRead; +} + + +#ifndef DR_WAV_NO_CONVERSION_API +static unsigned short g_drwavAlawTable[256] = { + 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, + 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, + 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, + 0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00, + 0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58, + 0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58, + 0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960, + 0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0, + 0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80, + 0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40, + 0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00, + 0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500, + 0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8, + 0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8, + 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, + 0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350 +}; + +static unsigned short g_drwavMulawTable[256] = { + 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, + 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, + 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, + 0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844, + 0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64, + 0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74, + 0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C, + 0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000, + 0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C, + 0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C, + 0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC, + 0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC, + 0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C, + 0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C, + 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084, + 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 +}; + +static DRWAV_INLINE drwav_int16 drwav__alaw_to_s16(drwav_uint8 sampleIn) +{ + return (short)g_drwavAlawTable[sampleIn]; +} + +static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) +{ + return (short)g_drwavMulawTable[sampleIn]; +} + + + +static void drwav__pcm_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample) +{ + // Special case for 8-bit sample data because it's treated as unsigned. + if (bytesPerSample == 1) { + drwav_u8_to_s16(pOut, pIn, totalSampleCount); + return; + } + + + // Slightly more optimal implementation for common formats. + if (bytesPerSample == 2) { + for (unsigned int i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((const drwav_int16*)pIn)[i]; + } + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_s16(pOut, pIn, totalSampleCount); + return; + } + if (bytesPerSample == 4) { + drwav_s32_to_s16(pOut, (const drwav_int32*)pIn, totalSampleCount); + return; + } + + + // Anything more than 64 bits per sample is not supported. + if (bytesPerSample > 8) { + drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); + return; + } + + + // Generic, slow converter. + for (unsigned int i = 0; i < totalSampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample && j < 8; j += 1) { + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (drwav_int16)((drwav_int64)sample >> 48); + } +} + +static void drwav__ieee_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample) +{ + if (bytesPerSample == 4) { + drwav_f32_to_s16(pOut, (const float*)pIn, totalSampleCount); + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_s16(pOut, (const double*)pIn, totalSampleCount); + return; + } else { + // Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. + drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); + return; + } +} + +drwav_uint64 drwav_read_s16__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + // Fast path. + if (pWav->bytesPerSample == 2) { + return drwav_read(pWav, samplesToRead, pBufferOut); + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s16__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s16__alaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav_alaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s16__mulaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav_mulaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s16(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) { + return 0; + } + + // Don't try to read more samples than can potentially fit in the output buffer. + if (samplesToRead * sizeof(drwav_int16) > DRWAV_SIZE_MAX) { + samplesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int16); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_s16__pcm(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + return drwav_read_s16__msadpcm(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_s16__ieee(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_s16__alaw(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_s16__mulaw(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_s16__ima(pWav, samplesToRead, pBufferOut); + } + + return 0; +} + +drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + return drwav_read_s16(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels; +} + +void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + int r; + for (size_t i = 0; i < sampleCount; ++i) { + int x = pIn[i]; + r = x - 128; + r = r << 8; + pOut[i] = (short)r; + } +} + +void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + int r; + for (size_t i = 0; i < sampleCount; ++i) { + int x = ((int)(((unsigned int)(((const unsigned char*)pIn)[i*3+0]) << 8) | ((unsigned int)(((const unsigned char*)pIn)[i*3+1]) << 16) | ((unsigned int)(((const unsigned char*)pIn)[i*3+2])) << 24)) >> 8; + r = x >> 8; + pOut[i] = (short)r; + } +} + +void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount) +{ + int r; + for (size_t i = 0; i < sampleCount; ++i) { + int x = pIn[i]; + r = x >> 16; + pOut[i] = (short)r; + } +} + +void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount) +{ + int r; + for (size_t i = 0; i < sampleCount; ++i) { + float x = pIn[i]; + float c; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + c = c + 1; + r = (int)(c * 32767.5f); + r = r - 32768; + pOut[i] = (short)r; + } +} + +void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount) +{ + int r; + for (size_t i = 0; i < sampleCount; ++i) { + double x = pIn[i]; + double c; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + c = c + 1; + r = (int)(c * 32767.5); + r = r - 32768; + pOut[i] = (short)r; + } +} + +void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + for (size_t i = 0; i < sampleCount; ++i) { + pOut[i] = drwav__alaw_to_s16(pIn[i]); + } +} + +void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + for (size_t i = 0; i < sampleCount; ++i) { + pOut[i] = drwav__mulaw_to_s16(pIn[i]); + } +} + + + +static void drwav__pcm_to_f32(float* pOut, const unsigned char* pIn, size_t sampleCount, unsigned short bytesPerSample) +{ + // Special case for 8-bit sample data because it's treated as unsigned. + if (bytesPerSample == 1) { + drwav_u8_to_f32(pOut, pIn, sampleCount); + return; + } + + // Slightly more optimal implementation for common formats. + if (bytesPerSample == 2) { + drwav_s16_to_f32(pOut, (const drwav_int16*)pIn, sampleCount); + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_f32(pOut, pIn, sampleCount); + return; + } + if (bytesPerSample == 4) { + drwav_s32_to_f32(pOut, (const drwav_int32*)pIn, sampleCount); + return; + } + + + // Anything more than 64 bits per sample is not supported. + if (bytesPerSample > 8) { + drwav_zero_memory(pOut, sampleCount * sizeof(*pOut)); + return; + } + + + // Generic, slow converter. + for (unsigned int i = 0; i < sampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample && j < 8; j += 1) { + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (float)((drwav_int64)sample / 9223372036854775807.0); + } +} + +static void drwav__ieee_to_f32(float* pOut, const unsigned char* pIn, size_t sampleCount, unsigned short bytesPerSample) +{ + if (bytesPerSample == 4) { + for (unsigned int i = 0; i < sampleCount; ++i) { + *pOut++ = ((const float*)pIn)[i]; + } + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_f32(pOut, (const double*)pIn, sampleCount); + return; + } else { + // Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. + drwav_zero_memory(pOut, sampleCount * sizeof(*pOut)); + return; + } +} + + +drwav_uint64 drwav_read_f32__pcm(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + if (pWav->bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + pBufferOut += samplesRead; + + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_f32__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + // We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't + // want to duplicate that code. + drwav_uint64 totalSamplesRead = 0; + drwav_int16 samples16[2048]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read_s16(pWav, drwav_min(samplesToRead, 2048), samples16); + if (samplesRead == 0) { + break; + } + + drwav_s16_to_f32(pBufferOut, samples16, (size_t)samplesRead); // <-- Safe cast because we're clamping to 2048. + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_f32__ima(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + // We're just going to borrow the implementation from the drwav_read_s16() since IMA-ADPCM is a little bit more complicated than other formats and I don't + // want to duplicate that code. + drwav_uint64 totalSamplesRead = 0; + drwav_int16 samples16[2048]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read_s16(pWav, drwav_min(samplesToRead, 2048), samples16); + if (samplesRead == 0) { + break; + } + + drwav_s16_to_f32(pBufferOut, samples16, (size_t)samplesRead); // <-- Safe cast because we're clamping to 2048. + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_f32__ieee(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + // Fast path. + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bytesPerSample == 4) { + return drwav_read(pWav, samplesToRead, pBufferOut); + } + + if (pWav->bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_f32__alaw(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + if (pWav->bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_f32__mulaw(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + if (pWav->bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav_mulaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_f32(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) { + return 0; + } + + // Don't try to read more samples than can potentially fit in the output buffer. + if (samplesToRead * sizeof(float) > DRWAV_SIZE_MAX) { + samplesToRead = DRWAV_SIZE_MAX / sizeof(float); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_f32__pcm(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + return drwav_read_f32__msadpcm(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_f32__ieee(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_f32__alaw(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_f32__mulaw(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_f32__ima(pWav, samplesToRead, pBufferOut); + } + + return 0; +} + +drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + return drwav_read_f32(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels; +} + +void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + +#ifdef DR_WAV_LIBSNDFILE_COMPAT + // It appears libsndfile uses slightly different logic for the u8 -> f32 conversion to dr_wav, which in my opinion is incorrect. It appears + // libsndfile performs the conversion something like "f32 = (u8 / 256) * 2 - 1", however I think it should be "f32 = (u8 / 255) * 2 - 1" (note + // the divisor of 256 vs 255). I use libsndfile as a benchmark for testing, so I'm therefore leaving this block here just for my automated + // correctness testing. This is disabled by default. + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = (pIn[i] / 256.0f) * 2 - 1; + } +#else + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = (pIn[i] / 255.0f) * 2 - 1; + } +#endif +} + +void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = pIn[i] / 32768.0f; + } +} + +void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + unsigned int s0 = pIn[i*3 + 0]; + unsigned int s1 = pIn[i*3 + 1]; + unsigned int s2 = pIn[i*3 + 2]; + + int sample32 = (int)((s0 << 8) | (s1 << 16) | (s2 << 24)); + *pOut++ = (float)(sample32 / 2147483648.0); + } +} + +void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = (float)(pIn[i] / 2147483648.0); + } +} + +void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = (float)pIn[i]; + } +} + +void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = drwav__alaw_to_s16(pIn[i]) / 32768.0f; + } +} + +void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = drwav__mulaw_to_s16(pIn[i]) / 32768.0f; + } +} + + + +static void drwav__pcm_to_s32(drwav_int32* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample) +{ + // Special case for 8-bit sample data because it's treated as unsigned. + if (bytesPerSample == 1) { + drwav_u8_to_s32(pOut, pIn, totalSampleCount); + return; + } + + // Slightly more optimal implementation for common formats. + if (bytesPerSample == 2) { + drwav_s16_to_s32(pOut, (const drwav_int16*)pIn, totalSampleCount); + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_s32(pOut, pIn, totalSampleCount); + return; + } + if (bytesPerSample == 4) { + for (unsigned int i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((const drwav_int32*)pIn)[i]; + } + return; + } + + + // Anything more than 64 bits per sample is not supported. + if (bytesPerSample > 8) { + drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); + return; + } + + + // Generic, slow converter. + for (unsigned int i = 0; i < totalSampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample && j < 8; j += 1) { + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (drwav_int32)((drwav_int64)sample >> 32); + } +} + +static void drwav__ieee_to_s32(drwav_int32* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned short bytesPerSample) +{ + if (bytesPerSample == 4) { + drwav_f32_to_s32(pOut, (const float*)pIn, totalSampleCount); + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_s32(pOut, (const double*)pIn, totalSampleCount); + return; + } else { + // Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. + drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); + return; + } +} + + +drwav_uint64 drwav_read_s32__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + // Fast path. + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bytesPerSample == 4) { + return drwav_read(pWav, samplesToRead, pBufferOut); + } + + if (pWav->bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s32__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + // We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't + // want to duplicate that code. + drwav_uint64 totalSamplesRead = 0; + drwav_int16 samples16[2048]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read_s16(pWav, drwav_min(samplesToRead, 2048), samples16); + if (samplesRead == 0) { + break; + } + + drwav_s16_to_s32(pBufferOut, samples16, (size_t)samplesRead); // <-- Safe cast because we're clamping to 2048. + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s32__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + // We're just going to borrow the implementation from the drwav_read_s16() since IMA-ADPCM is a little bit more complicated than other formats and I don't + // want to duplicate that code. + drwav_uint64 totalSamplesRead = 0; + drwav_int16 samples16[2048]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read_s16(pWav, drwav_min(samplesToRead, 2048), samples16); + if (samplesRead == 0) { + break; + } + + drwav_s16_to_s32(pBufferOut, samples16, (size_t)samplesRead); // <-- Safe cast because we're clamping to 2048. + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s32__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + if (pWav->bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)samplesRead, pWav->bytesPerSample); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s32__alaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + if (pWav->bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s32__mulaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + if (pWav->bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/pWav->bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav_mulaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s32(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) { + return 0; + } + + // Don't try to read more samples than can potentially fit in the output buffer. + if (samplesToRead * sizeof(drwav_int32) > DRWAV_SIZE_MAX) { + samplesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int32); + } + + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_s32__pcm(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + return drwav_read_s32__msadpcm(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_s32__ieee(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_s32__alaw(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_s32__mulaw(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_s32__ima(pWav, samplesToRead, pBufferOut); + } + + return 0; +} + +drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + return drwav_read_s32(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels; +} + +void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = ((int)pIn[i] - 128) << 24; + } +} + +void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = pIn[i] << 16; + } +} + +void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + unsigned int s0 = pIn[i*3 + 0]; + unsigned int s1 = pIn[i*3 + 1]; + unsigned int s2 = pIn[i*3 + 2]; + + drwav_int32 sample32 = (drwav_int32)((s0 << 8) | (s1 << 16) | (s2 << 24)); + *pOut++ = sample32; + } +} + +void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); + } +} + +void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); + } +} + +void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = ((drwav_int32)drwav__alaw_to_s16(pIn[i])) << 16; + } +} + +void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i= 0; i < sampleCount; ++i) { + *pOut++ = ((drwav_int32)drwav__mulaw_to_s16(pIn[i])) << 16; + } +} + + + +drwav_int16* drwav__read_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + drwav_assert(pWav != NULL); + + drwav_uint64 sampleDataSize = pWav->totalSampleCount * sizeof(drwav_int16); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; // File's too big. + } + + drwav_int16* pSampleData = (drwav_int16*)DRWAV_MALLOC((size_t)sampleDataSize); // <-- Safe cast due to the check above. + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; // Failed to allocate memory. + } + + drwav_uint64 samplesRead = drwav_read_s16(pWav, (size_t)pWav->totalSampleCount, pSampleData); + if (samplesRead != pWav->totalSampleCount) { + DRWAV_FREE(pSampleData); + drwav_uninit(pWav); + return NULL; // There was an error reading the samples. + } + + drwav_uninit(pWav); + + if (sampleRate) *sampleRate = pWav->sampleRate; + if (channels) *channels = pWav->channels; + if (totalSampleCount) *totalSampleCount = pWav->totalSampleCount; + return pSampleData; +} + +float* drwav__read_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + drwav_assert(pWav != NULL); + + drwav_uint64 sampleDataSize = pWav->totalSampleCount * sizeof(float); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; // File's too big. + } + + float* pSampleData = (float*)DRWAV_MALLOC((size_t)sampleDataSize); // <-- Safe cast due to the check above. + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; // Failed to allocate memory. + } + + drwav_uint64 samplesRead = drwav_read_f32(pWav, (size_t)pWav->totalSampleCount, pSampleData); + if (samplesRead != pWav->totalSampleCount) { + DRWAV_FREE(pSampleData); + drwav_uninit(pWav); + return NULL; // There was an error reading the samples. + } + + drwav_uninit(pWav); + + if (sampleRate) *sampleRate = pWav->sampleRate; + if (channels) *channels = pWav->channels; + if (totalSampleCount) *totalSampleCount = pWav->totalSampleCount; + return pSampleData; +} + +drwav_int32* drwav__read_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + drwav_assert(pWav != NULL); + + drwav_uint64 sampleDataSize = pWav->totalSampleCount * sizeof(drwav_int32); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; // File's too big. + } + + drwav_int32* pSampleData = (drwav_int32*)DRWAV_MALLOC((size_t)sampleDataSize); // <-- Safe cast due to the check above. + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; // Failed to allocate memory. + } + + drwav_uint64 samplesRead = drwav_read_s32(pWav, (size_t)pWav->totalSampleCount, pSampleData); + if (samplesRead != pWav->totalSampleCount) { + DRWAV_FREE(pSampleData); + drwav_uninit(pWav); + return NULL; // There was an error reading the samples. + } + + drwav_uninit(pWav); + + if (sampleRate) *sampleRate = pWav->sampleRate; + if (channels) *channels = pWav->channels; + if (totalSampleCount) *totalSampleCount = pWav->totalSampleCount; + return pSampleData; +} + + +drwav_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (channels) *channels = 0; + if (sampleRate) *sampleRate = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init(&wav, onRead, onSeek, pUserData)) { + return NULL; + } + + return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount); +} + +drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int16* result = drwav_open_and_read_s16(onRead, onSeek, pUserData, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +float* drwav_open_and_read_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init(&wav, onRead, onSeek, pUserData)) { + return NULL; + } + + return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount); +} + +float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + float* result = drwav_open_and_read_f32(onRead, onSeek, pUserData, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +drwav_int32* drwav_open_and_read_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init(&wav, onRead, onSeek, pUserData)) { + return NULL; + } + + return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount); +} + +drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int32* result = drwav_open_and_read_s32(onRead, onSeek, pUserData, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +#ifndef DR_WAV_NO_STDIO +drwav_int16* drwav_open_file_and_read_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init_file(&wav, filename)) { + return NULL; + } + + return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount); +} + +drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int16* result = drwav_open_file_and_read_s16(filename, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +float* drwav_open_file_and_read_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init_file(&wav, filename)) { + return NULL; + } + + return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount); +} + +float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + float* result = drwav_open_file_and_read_f32(filename, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +drwav_int32* drwav_open_file_and_read_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init_file(&wav, filename)) { + return NULL; + } + + return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount); +} + +drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int32* result = drwav_open_file_and_read_s32(filename, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} +#endif + +drwav_int16* drwav_open_memory_and_read_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init_memory(&wav, data, dataSize)) { + return NULL; + } + + return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount); +} + +drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int16* result = drwav_open_memory_and_read_s16(data, dataSize, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +float* drwav_open_memory_and_read_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init_memory(&wav, data, dataSize)) { + return NULL; + } + + return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount); +} + +float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + float* result = drwav_open_memory_and_read_f32(data, dataSize, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +drwav_int32* drwav_open_memory_and_read_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init_memory(&wav, data, dataSize)) { + return NULL; + } + + return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount); +} + +drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int32* result = drwav_open_memory_and_read_s32(data, dataSize, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} +#endif //DR_WAV_NO_CONVERSION_API + + +void drwav_free(void* pDataReturnedByOpenAndRead) +{ + DRWAV_FREE(pDataReturnedByOpenAndRead); +} + +#endif //DR_WAV_IMPLEMENTATION + + +// REVISION HISTORY +// +// v0.9.0 - 2018-xx-xx +// - API CHANGE: Rename drwav_open_and_read_file_*() to drwav_open_file_and_read_*(). +// - API CHANGE: Rename drwav_open_and_read_memory_*() to drwav_open_memory_and_read_*(). +// - Add built-in support for smpl chunks. +// - Add support for firing a callback for each chunk in the file at initialization time. +// - This is enabled through the drwav_init_ex(), etc. family of APIs. +// - Add new reading APIs for reading by PCM frames instead of samples. +// +// v0.8.5 - 2018-09-11 +// - Const correctness. +// - Fix a potential stack overflow. +// +// v0.8.4 - 2018-08-07 +// - Improve 64-bit detection. +// +// v0.8.3 - 2018-08-05 +// - Fix C++ build on older versions of GCC. +// +// v0.8.2 - 2018-08-02 +// - Fix some big-endian bugs. +// +// v0.8.1 - 2018-06-29 +// - Add support for sequential writing APIs. +// - Disable seeking in write mode. +// - Fix bugs with Wave64. +// - Fix typos. +// +// v0.8 - 2018-04-27 +// - Bug fix. +// - Start using major.minor.revision versioning. +// +// v0.7f - 2018-02-05 +// - Restrict ADPCM formats to a maximum of 2 channels. +// +// v0.7e - 2018-02-02 +// - Fix a crash. +// +// v0.7d - 2018-02-01 +// - Fix a crash. +// +// v0.7c - 2018-02-01 +// - Set drwav.bytesPerSample to 0 for all compressed formats. +// - Fix a crash when reading 16-bit floating point WAV files. In this case dr_wav will output silence for +// all format conversion reading APIs (*_s16, *_s32, *_f32 APIs). +// - Fix some divide-by-zero errors. +// +// v0.7b - 2018-01-22 +// - Fix errors with seeking of compressed formats. +// - Fix compilation error when DR_WAV_NO_CONVERSION_API +// +// v0.7a - 2017-11-17 +// - Fix some GCC warnings. +// +// v0.7 - 2017-11-04 +// - Add writing APIs. +// +// v0.6 - 2017-08-16 +// - API CHANGE: Rename dr_* types to drwav_*. +// - Add support for custom implementations of malloc(), realloc(), etc. +// - Add support for Microsoft ADPCM. +// - Add support for IMA ADPCM (DVI, format code 0x11). +// - Optimizations to drwav_read_s16(). +// - Bug fixes. +// +// v0.5g - 2017-07-16 +// - Change underlying type for booleans to unsigned. +// +// v0.5f - 2017-04-04 +// - Fix a minor bug with drwav_open_and_read_s16() and family. +// +// v0.5e - 2016-12-29 +// - Added support for reading samples as signed 16-bit integers. Use the _s16() family of APIs for this. +// - Minor fixes to documentation. +// +// v0.5d - 2016-12-28 +// - Use drwav_int*/drwav_uint* sized types to improve compiler support. +// +// v0.5c - 2016-11-11 +// - Properly handle JUNK chunks that come before the FMT chunk. +// +// v0.5b - 2016-10-23 +// - A minor change to drwav_bool8 and drwav_bool32 types. +// +// v0.5a - 2016-10-11 +// - Fixed a bug with drwav_open_and_read() and family due to incorrect argument ordering. +// - Improve A-law and mu-law efficiency. +// +// v0.5 - 2016-09-29 +// - API CHANGE. Swap the order of "channels" and "sampleRate" parameters in drwav_open_and_read*(). Rationale for this is to +// keep it consistent with dr_audio and dr_flac. +// +// v0.4b - 2016-09-18 +// - Fixed a typo in documentation. +// +// v0.4a - 2016-09-18 +// - Fixed a typo. +// - Change date format to ISO 8601 (YYYY-MM-DD) +// +// v0.4 - 2016-07-13 +// - API CHANGE. Make onSeek consistent with dr_flac. +// - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with dr_flac. +// - Added support for Sony Wave64. +// +// v0.3a - 2016-05-28 +// - API CHANGE. Return drwav_bool32 instead of int in onSeek callback. +// - Fixed a memory leak. +// +// v0.3 - 2016-05-22 +// - Lots of API changes for consistency. +// +// v0.2a - 2016-05-16 +// - Fixed Linux/GCC build. +// +// v0.2 - 2016-05-11 +// - Added support for reading data as signed 32-bit PCM for consistency with dr_flac. +// +// v0.1a - 2016-05-07 +// - Fixed a bug in drwav_open_file() where the file handle would not be closed if the loader failed to initialize. +// +// v0.1 - 2016-05-04 +// - Initial versioned release. + + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/src/soloud/src/audiosource/wav/soloud_wav.cpp b/src/soloud/src/audiosource/wav/soloud_wav.cpp new file mode 100644 index 0000000..fd9b257 --- /dev/null +++ b/src/soloud/src/audiosource/wav/soloud_wav.cpp @@ -0,0 +1,396 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include +#include +#include "soloud.h" +#include "soloud_wav.h" +#include "soloud_file.h" +#include "stb_vorbis.h" +#include "dr_mp3.h" +#include "dr_wav.h" +#include "dr_flac.h" + +namespace SoLoud +{ + WavInstance::WavInstance(Wav *aParent) + { + mParent = aParent; + mOffset = 0; + } + + unsigned int WavInstance::getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize) + { + if (mParent->mData == NULL) + return 0; + + unsigned int dataleft = mParent->mSampleCount - mOffset; + unsigned int copylen = dataleft; + if (copylen > aSamplesToRead) + copylen = aSamplesToRead; + + unsigned int i; + for (i = 0; i < mChannels; i++) + { + memcpy(aBuffer + i * aBufferSize, mParent->mData + mOffset + i * mParent->mSampleCount, sizeof(float) * copylen); + } + + mOffset += copylen; + return copylen; + } + + result WavInstance::rewind() + { + mOffset = 0; + mStreamPosition = 0.0f; + return 0; + } + + bool WavInstance::hasEnded() + { + if (!(mFlags & AudioSourceInstance::LOOPING) && mOffset >= mParent->mSampleCount) + { + return 1; + } + return 0; + } + + Wav::Wav() + { + mData = NULL; + mSampleCount = 0; + } + + Wav::~Wav() + { + stop(); + delete[] mData; + } + +#define MAKEDWORD(a,b,c,d) (((d) << 24) | ((c) << 16) | ((b) << 8) | (a)) + + result Wav::loadwav(MemoryFile *aReader) + { + drwav decoder; + + if (!drwav_init_memory(&decoder, aReader->getMemPtr(), aReader->length())) + { + return FILE_LOAD_FAILED; + } + + drwav_uint64 samples = decoder.totalSampleCount / decoder.channels; + + if (!samples) + { + drwav_uninit(&decoder); + return FILE_LOAD_FAILED; + } + + mData = new float[(unsigned int)(samples * decoder.channels)]; + mBaseSamplerate = (float)decoder.sampleRate; + mSampleCount = (unsigned int)samples; + mChannels = decoder.channels; + + unsigned int i, j, k; + for (i = 0; i < mSampleCount; i += 512) + { + float tmp[512 * MAX_CHANNELS]; + unsigned int blockSize = (mSampleCount - i) > 512 ? 512 : mSampleCount - i; + drwav_read_pcm_frames_f32(&decoder, blockSize, tmp); + for (j = 0; j < blockSize; j++) + { + for (k = 0; k < decoder.channels; k++) + { + mData[k * mSampleCount + i + j] = tmp[j * decoder.channels + k]; + } + } + } + drwav_uninit(&decoder); + + return SO_NO_ERROR; + } + + result Wav::loadogg(MemoryFile *aReader) + { + int e = 0; + stb_vorbis *vorbis = 0; + vorbis = stb_vorbis_open_memory(aReader->getMemPtr(), aReader->length(), &e, 0); + + if (0 == vorbis) + { + return FILE_LOAD_FAILED; + } + + stb_vorbis_info info = stb_vorbis_get_info(vorbis); + mBaseSamplerate = (float)info.sample_rate; + int samples = stb_vorbis_stream_length_in_samples(vorbis); + + if (info.channels > MAX_CHANNELS) + { + mChannels = MAX_CHANNELS; + } + else + { + mChannels = info.channels; + } + mData = new float[samples * mChannels]; + mSampleCount = samples; + samples = 0; + while(1) + { + float **outputs; + int n = stb_vorbis_get_frame_float(vorbis, NULL, &outputs); + if (n == 0) + { + break; + } + + unsigned int ch; + for (ch = 0; ch < mChannels; ch++) + memcpy(mData + samples + mSampleCount * ch, outputs[ch], sizeof(float) * n); + + samples += n; + } + stb_vorbis_close(vorbis); + + return 0; + } + + result Wav::loadmp3(MemoryFile *aReader) + { + drmp3 decoder; + + if (!drmp3_init_memory(&decoder, aReader->getMemPtr(), aReader->length(), NULL)) + { + return FILE_LOAD_FAILED; + } + + drmp3_uint64 samples = drmp3_get_pcm_frame_count(&decoder); + + if (!samples) + { + drmp3_uninit(&decoder); + return FILE_LOAD_FAILED; + } + + mData = new float[(unsigned int)(samples * decoder.channels)]; + mBaseSamplerate = (float)decoder.sampleRate; + mSampleCount = (unsigned int)samples; + mChannels = decoder.channels; + drmp3_seek_to_pcm_frame(&decoder, 0); + + unsigned int i, j, k; + for (i = 0; i 512 ? 512 : mSampleCount - i; + drmp3_read_pcm_frames_f32(&decoder, blockSize, tmp); + for (j = 0; j < blockSize; j++) + { + for (k = 0; k < decoder.channels; k++) + { + mData[k * mSampleCount + i + j] = tmp[j * decoder.channels + k]; + } + } + } + drmp3_uninit(&decoder); + + return SO_NO_ERROR; + } + + result Wav::loadflac(MemoryFile *aReader) + { + drflac *decoder = drflac_open_memory(aReader->mDataPtr, aReader->mDataLength); + + if (!decoder) + { + return FILE_LOAD_FAILED; + } + + drflac_uint64 samples = decoder->totalSampleCount; + + if (!samples) + { + drflac_close(decoder); + return FILE_LOAD_FAILED; + } + + mData = new float[(unsigned int)(samples * decoder->channels)]; + mBaseSamplerate = (float)decoder->sampleRate; + mSampleCount = (unsigned int)samples; + mChannels = decoder->channels; + drflac_seek_to_pcm_frame(decoder, 0); + + unsigned int i, j, k; + for (i = 0; i < mSampleCount; i += 512) + { + float tmp[512 * MAX_CHANNELS]; + unsigned int blockSize = (mSampleCount - i) > 512 ? 512 : mSampleCount - i; + drflac_read_pcm_frames_f32(decoder, blockSize, tmp); + for (j = 0; j < blockSize; j++) + { + for (k = 0; k < decoder->channels; k++) + { + mData[k * mSampleCount + i + j] = tmp[j * decoder->channels + k]; + } + } + } + drflac_close(decoder); + + return SO_NO_ERROR; + } + + result Wav::testAndLoadFile(MemoryFile *aReader) + { + delete[] mData; + mData = 0; + mSampleCount = 0; + mChannels = 1; + int tag = aReader->read32(); + if (tag == MAKEDWORD('O','g','g','S')) + { + return loadogg(aReader); + + } + else if (tag == MAKEDWORD('R','I','F','F')) + { + return loadwav(aReader); + } + else if (tag == MAKEDWORD('f', 'L', 'a', 'C')) + { + return loadflac(aReader); + } + else if (loadmp3(aReader) == SO_NO_ERROR) + { + return SO_NO_ERROR; + } + + return FILE_LOAD_FAILED; + } + + result Wav::load(const char *aFilename) + { + if (aFilename == 0) + return INVALID_PARAMETER; + stop(); + DiskFile dr; + int res = dr.open(aFilename); + if (res == SO_NO_ERROR) + return loadFile(&dr); + return FILE_LOAD_FAILED; + } + + result Wav::loadMem(unsigned char *aMem, unsigned int aLength, bool aCopy, bool aTakeOwnership) + { + if (aMem == NULL || aLength == 0) + return INVALID_PARAMETER; + stop(); + + MemoryFile dr; + dr.openMem(aMem, aLength, aCopy, aTakeOwnership); + return testAndLoadFile(&dr); + } + + result Wav::loadFile(File *aFile) + { + if (!aFile) + return INVALID_PARAMETER; + stop(); + + MemoryFile mr; + result res = mr.openFileToMem(aFile); + + if (res != SO_NO_ERROR) + { + return res; + } + return testAndLoadFile(&mr); + } + + AudioSourceInstance *Wav::createInstance() + { + return new WavInstance(this); + } + + double Wav::getLength() + { + if (mBaseSamplerate == 0) + return 0; + return mSampleCount / mBaseSamplerate; + } + + result Wav::loadRawWave8(unsigned char *aMem, unsigned int aLength, float aSamplerate, unsigned int aChannels) + { + if (aMem == 0 || aLength == 0 || aSamplerate <= 0 || aChannels < 1) + return INVALID_PARAMETER; + stop(); + delete[] mData; + mData = new float[aLength]; + mSampleCount = aLength / aChannels; + mChannels = aChannels; + mBaseSamplerate = aSamplerate; + unsigned int i; + for (i = 0; i < aLength; i++) + mData[i] = ((signed)aMem[i] - 128) / (float)0x80; + return SO_NO_ERROR; + } + + result Wav::loadRawWave16(short *aMem, unsigned int aLength, float aSamplerate, unsigned int aChannels) + { + if (aMem == 0 || aLength == 0 || aSamplerate <= 0 || aChannels < 1) + return INVALID_PARAMETER; + stop(); + delete[] mData; + mData = new float[aLength]; + mSampleCount = aLength / aChannels; + mChannels = aChannels; + mBaseSamplerate = aSamplerate; + unsigned int i; + for (i = 0; i < aLength; i++) + mData[i] = ((signed short)aMem[i]) / (float)0x8000; + return SO_NO_ERROR; + } + + result Wav::loadRawWave(float *aMem, unsigned int aLength, float aSamplerate, unsigned int aChannels, bool aCopy, bool aTakeOwndership) + { + if (aMem == 0 || aLength == 0 || aSamplerate <= 0 || aChannels < 1) + return INVALID_PARAMETER; + stop(); + delete[] mData; + if (aCopy == true || aTakeOwndership == false) + { + mData = new float[aLength]; + memcpy(mData, aMem, sizeof(float) * aLength); + } + else + { + mData = aMem; + } + mSampleCount = aLength / aChannels; + mChannels = aChannels; + mBaseSamplerate = aSamplerate; + return SO_NO_ERROR; + } +}; diff --git a/src/soloud/src/audiosource/wav/soloud_wavstream.cpp b/src/soloud/src/audiosource/wav/soloud_wavstream.cpp new file mode 100644 index 0000000..bef887e --- /dev/null +++ b/src/soloud/src/audiosource/wav/soloud_wavstream.cpp @@ -0,0 +1,658 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include +#include +#include "soloud.h" +#include "dr_flac.h" +#include "dr_mp3.h" +#include "dr_wav.h" +#include "soloud_wavstream.h" +#include "soloud_file.h" +#include "stb_vorbis.h" + +namespace SoLoud +{ + size_t drflac_read_func(void* pUserData, void* pBufferOut, size_t bytesToRead) + { + File *fp = (File*)pUserData; + return fp->read((unsigned char*)pBufferOut, (unsigned int)bytesToRead); + } + + size_t drmp3_read_func(void* pUserData, void* pBufferOut, size_t bytesToRead) + { + File *fp = (File*)pUserData; + return fp->read((unsigned char*)pBufferOut, (unsigned int)bytesToRead); + } + + size_t drwav_read_func(void* pUserData, void* pBufferOut, size_t bytesToRead) + { + File *fp = (File*)pUserData; + return fp->read((unsigned char*)pBufferOut, (unsigned int)bytesToRead); + } + + drflac_bool32 drflac_seek_func(void* pUserData, int offset, drflac_seek_origin origin) + { + File *fp = (File*)pUserData; + if (origin != drflac_seek_origin_start) + offset += fp->pos(); + fp->seek(offset); + return 1; + } + + drmp3_bool32 drmp3_seek_func(void* pUserData, int offset, drmp3_seek_origin origin) + { + File *fp = (File*)pUserData; + if (origin != drmp3_seek_origin_start) + offset += fp->pos(); + fp->seek(offset); + return 1; + } + + drmp3_bool32 drwav_seek_func(void* pUserData, int offset, drwav_seek_origin origin) + { + File *fp = (File*)pUserData; + if (origin != drwav_seek_origin_start) + offset += fp->pos(); + fp->seek(offset); + return 1; + } + + WavStreamInstance::WavStreamInstance(WavStream *aParent) + { + mParent = aParent; + mOffset = 0; + mCodec.mOgg = 0; + mCodec.mFlac = 0; + mCodec.mMp3 = 0; + mCodec.mWav = 0; + mFile = 0; + if (aParent->mMemFile) + { + MemoryFile *mf = new MemoryFile(); + mFile = mf; + mf->openMem(aParent->mMemFile->getMemPtr(), aParent->mMemFile->length(), false, false); + } + else + if (aParent->mFilename) + { + DiskFile *df = new DiskFile; + mFile = df; + df->open(aParent->mFilename); + } + else + if (aParent->mStreamFile) + { + mFile = aParent->mStreamFile; + mFile->seek(0); // stb_vorbis assumes file offset to be at start of ogg + } + else + { + return; + } + + if (mFile) + { + if (mParent->mFiletype == WAVSTREAM_WAV) + { + mCodec.mWav = drwav_open(drwav_read_func, drwav_seek_func, (void*)mFile); + if (!mCodec.mWav) + { + if (mFile != mParent->mStreamFile) + delete mFile; + mFile = 0; + } + } + else + if (mParent->mFiletype == WAVSTREAM_OGG) + { + int e; + + mCodec.mOgg = stb_vorbis_open_file((Soloud_Filehack *)mFile, 0, &e, 0); + + if (!mCodec.mOgg) + { + if (mFile != mParent->mStreamFile) + delete mFile; + mFile = 0; + } + mOggFrameSize = 0; + mOggFrameOffset = 0; + mOggOutputs = 0; + } + else + if (mParent->mFiletype == WAVSTREAM_FLAC) + { + mCodec.mFlac = drflac_open(drflac_read_func, drflac_seek_func, (void*)mFile); + if (!mCodec.mFlac) + { + if (mFile != mParent->mStreamFile) + delete mFile; + mFile = 0; + } + } + else + if (mParent->mFiletype == WAVSTREAM_MP3) + { + mCodec.mMp3 = new drmp3; + if (!drmp3_init(mCodec.mMp3, drmp3_read_func, drmp3_seek_func, (void*)mFile, NULL)) + { + delete mCodec.mMp3; + mCodec.mMp3 = 0; + if (mFile != mParent->mStreamFile) + delete mFile; + mFile = 0; + } + } + else + { + if (mFile != mParent->mStreamFile) + delete mFile; + mFile = NULL; + return; + } + } + } + + WavStreamInstance::~WavStreamInstance() + { + switch (mParent->mFiletype) + { + case WAVSTREAM_OGG: + if (mCodec.mOgg) + { + stb_vorbis_close(mCodec.mOgg); + } + break; + case WAVSTREAM_FLAC: + if (mCodec.mFlac) + { + drflac_close(mCodec.mFlac); + } + break; + case WAVSTREAM_MP3: + if (mCodec.mMp3) + { + drmp3_uninit(mCodec.mMp3); + delete mCodec.mMp3; + } + break; + case WAVSTREAM_WAV: + if (mCodec.mWav) + { + drwav_close(mCodec.mWav); + } + } + if (mFile != mParent->mStreamFile) + { + delete mFile; + } + } + + static int getOggData(float **aOggOutputs, float *aBuffer, int aSamples, int aPitch, int aFrameSize, int aFrameOffset, int aChannels) + { + if (aFrameSize <= 0) + return 0; + + int samples = aSamples; + if (aFrameSize - aFrameOffset < samples) + { + samples = aFrameSize - aFrameOffset; + } + + int i; + for (i = 0; i < aChannels; i++) + { + memcpy(aBuffer + aPitch * i, aOggOutputs[i] + aFrameOffset, sizeof(float) * samples); + } + return samples; + } + + + + unsigned int WavStreamInstance::getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize) + { + unsigned int offset = 0; + if (mFile == NULL) + return 0; + switch (mParent->mFiletype) + { + case WAVSTREAM_FLAC: + { + unsigned int i, j, k; + + for (i = 0; i < aSamplesToRead; i += 512) + { + float tmp[512 * MAX_CHANNELS]; + unsigned int blockSize = (aSamplesToRead - i) > 512 ? 512 : aSamplesToRead - i; + offset += (unsigned int)drflac_read_pcm_frames_f32(mCodec.mFlac, blockSize, tmp); + + for (j = 0; j < blockSize; j++) + { + for (k = 0; k < mChannels; k++) + { + aBuffer[k * aSamplesToRead + i + j] = tmp[j * mCodec.mFlac->channels + k]; + } + } + } + mOffset += offset; + return offset; + } + break; + case WAVSTREAM_MP3: + { + unsigned int i, j, k; + + for (i = 0; i < aSamplesToRead; i += 512) + { + float tmp[512 * MAX_CHANNELS]; + unsigned int blockSize = (aSamplesToRead - i) > 512 ? 512 : aSamplesToRead - i; + offset += (unsigned int)drmp3_read_pcm_frames_f32(mCodec.mMp3, blockSize, tmp); + + for (j = 0; j < blockSize; j++) + { + for (k = 0; k < mChannels; k++) + { + aBuffer[k * aSamplesToRead + i + j] = tmp[j * mCodec.mMp3->channels + k]; + } + } + } + mOffset += offset; + return offset; + } + break; + case WAVSTREAM_OGG: + { + if (mOggFrameOffset < mOggFrameSize) + { + int b = getOggData(mOggOutputs, aBuffer, aSamplesToRead, aBufferSize, mOggFrameSize, mOggFrameOffset, mChannels); + mOffset += b; + offset += b; + mOggFrameOffset += b; + } + + while (offset < aSamplesToRead) + { + mOggFrameSize = stb_vorbis_get_frame_float(mCodec.mOgg, NULL, &mOggOutputs); + mOggFrameOffset = 0; + int b = getOggData(mOggOutputs, aBuffer + offset, aSamplesToRead - offset, aBufferSize, mOggFrameSize, mOggFrameOffset, mChannels); + mOffset += b; + offset += b; + mOggFrameOffset += b; + + if (mOffset >= mParent->mSampleCount || b == 0) + { + mOffset += offset; + return offset; + } + } + } + break; + case WAVSTREAM_WAV: + { + unsigned int i, j, k; + + for (i = 0; i < aSamplesToRead; i += 512) + { + float tmp[512 * MAX_CHANNELS]; + unsigned int blockSize = (aSamplesToRead - i) > 512 ? 512 : aSamplesToRead - i; + offset += (unsigned int)drwav_read_pcm_frames_f32(mCodec.mWav, blockSize, tmp); + + for (j = 0; j < blockSize; j++) + { + for (k = 0; k < mChannels; k++) + { + aBuffer[k * aSamplesToRead + i + j] = tmp[j * mCodec.mWav->channels + k]; + } + } + } + mOffset += offset; + return offset; + } + break; + } + return aSamplesToRead; + } + + result WavStreamInstance::rewind() + { + switch (mParent->mFiletype) + { + case WAVSTREAM_OGG: + if (mCodec.mOgg) + { + stb_vorbis_seek_start(mCodec.mOgg); + } + break; + case WAVSTREAM_FLAC: + if (mCodec.mFlac) + { + drflac_seek_to_pcm_frame(mCodec.mFlac, 0); + } + break; + case WAVSTREAM_MP3: + if (mCodec.mMp3) + { + drmp3_seek_to_pcm_frame(mCodec.mMp3, 0); + } + break; + case WAVSTREAM_WAV: + if (mCodec.mWav) + { + drwav_seek_to_pcm_frame(mCodec.mWav, 0); + } + break; + } + mOffset = 0; + mStreamPosition = 0.0f; + return 0; + } + + bool WavStreamInstance::hasEnded() + { + if (mOffset >= mParent->mSampleCount) + { + return 1; + } + return 0; + } + + WavStream::WavStream() + { + mFilename = 0; + mSampleCount = 0; + mFiletype = WAVSTREAM_WAV; + mMemFile = 0; + mStreamFile = 0; + } + + WavStream::~WavStream() + { + stop(); + delete[] mFilename; + delete mMemFile; + } + +#define MAKEDWORD(a,b,c,d) (((d) << 24) | ((c) << 16) | ((b) << 8) | (a)) + + result WavStream::loadwav(File * fp) + { + fp->seek(0); + drwav* decoder = drwav_open(drwav_read_func, drwav_seek_func, (void*)fp); + + if (decoder == NULL) + return FILE_LOAD_FAILED; + + mChannels = decoder->channels; + if (mChannels > MAX_CHANNELS) + { + mChannels = MAX_CHANNELS; + } + + mBaseSamplerate = (float)decoder->sampleRate; + mSampleCount = (unsigned int)decoder->totalSampleCount; + mFiletype = WAVSTREAM_WAV; + drwav_close(decoder); + + return SO_NO_ERROR; + } + + result WavStream::loadogg(File * fp) + { + fp->seek(0); + int e; + stb_vorbis *v; + v = stb_vorbis_open_file((Soloud_Filehack *)fp, 0, &e, 0); + if (v == NULL) + return FILE_LOAD_FAILED; + stb_vorbis_info info = stb_vorbis_get_info(v); + mChannels = info.channels; + if (info.channels > MAX_CHANNELS) + { + mChannels = MAX_CHANNELS; + } + mBaseSamplerate = (float)info.sample_rate; + int samples = stb_vorbis_stream_length_in_samples(v); + stb_vorbis_close(v); + mFiletype = WAVSTREAM_OGG; + + mSampleCount = samples; + + return 0; + } + + result WavStream::loadflac(File * fp) + { + fp->seek(0); + drflac* decoder = drflac_open(drflac_read_func, drflac_seek_func, (void*)fp); + + if (decoder == NULL) + return FILE_LOAD_FAILED; + + mChannels = decoder->channels; + if (mChannels > MAX_CHANNELS) + { + mChannels = MAX_CHANNELS; + } + + mBaseSamplerate = (float)decoder->sampleRate; + mSampleCount = (unsigned int)decoder->totalPCMFrameCount; + mFiletype = WAVSTREAM_FLAC; + drflac_close(decoder); + + return SO_NO_ERROR; + } + + result WavStream::loadmp3(File * fp) + { + fp->seek(0); + drmp3 decoder; + if (!drmp3_init(&decoder, drmp3_read_func, drmp3_seek_func, (void*)fp, NULL)) + return FILE_LOAD_FAILED; + + + mChannels = decoder.channels; + if (mChannels > MAX_CHANNELS) + { + mChannels = MAX_CHANNELS; + } + + drmp3_uint64 samples = drmp3_get_pcm_frame_count(&decoder); + + mBaseSamplerate = (float)decoder.sampleRate; + mSampleCount = (unsigned int)samples; + mFiletype = WAVSTREAM_MP3; + drmp3_uninit(&decoder); + + return SO_NO_ERROR; + } + + result WavStream::load(const char *aFilename) + { + delete[] mFilename; + delete mMemFile; + mMemFile = 0; + mFilename = 0; + mSampleCount = 0; + DiskFile fp; + int res = fp.open(aFilename); + if (res != SO_NO_ERROR) + return res; + + int len = (int)strlen(aFilename); + mFilename = new char[len+1]; + memcpy(mFilename, aFilename, len); + mFilename[len] = 0; + + res = parse(&fp); + + if (res != SO_NO_ERROR) + { + delete[] mFilename; + mFilename = 0; + return res; + } + + return 0; + } + + result WavStream::loadMem(unsigned char *aData, unsigned int aDataLen, bool aCopy, bool aTakeOwnership) + { + delete[] mFilename; + delete mMemFile; + mStreamFile = 0; + mMemFile = 0; + mFilename = 0; + mSampleCount = 0; + + if (aData == NULL || aDataLen == 0) + return INVALID_PARAMETER; + + MemoryFile *mf = new MemoryFile(); + int res = mf->openMem(aData, aDataLen, aCopy, aTakeOwnership); + if (res != SO_NO_ERROR) + { + delete mf; + return res; + } + + res = parse(mf); + + if (res != SO_NO_ERROR) + { + delete mf; + return res; + } + + mMemFile = mf; + + return 0; + } + + result WavStream::loadToMem(const char *aFilename) + { + DiskFile df; + int res = df.open(aFilename); + if (res == SO_NO_ERROR) + { + res = loadFileToMem(&df); + } + return res; + } + + result WavStream::loadFile(File *aFile) + { + delete[] mFilename; + delete mMemFile; + mStreamFile = 0; + mMemFile = 0; + mFilename = 0; + mSampleCount = 0; + + int res = parse(aFile); + + if (res != SO_NO_ERROR) + { + return res; + } + + mStreamFile = aFile; + + return 0; + } + + result WavStream::loadFileToMem(File *aFile) + { + delete[] mFilename; + delete mMemFile; + mStreamFile = 0; + mMemFile = 0; + mFilename = 0; + mSampleCount = 0; + + MemoryFile *mf = new MemoryFile(); + int res = mf->openFileToMem(aFile); + if (res != SO_NO_ERROR) + { + delete mf; + return res; + } + + res = parse(mf); + + if (res != SO_NO_ERROR) + { + delete mf; + return res; + } + + mMemFile = mf; + + return res; + } + + + result WavStream::parse(File *aFile) + { + int tag = aFile->read32(); + int res = SO_NO_ERROR; + if (tag == MAKEDWORD('O', 'g', 'g', 'S')) + { + res = loadogg(aFile); + } + else + if (tag == MAKEDWORD('R', 'I', 'F', 'F')) + { + res = loadwav(aFile); + } + else + if (tag == MAKEDWORD('f', 'L', 'a', 'C')) + { + res = loadflac(aFile); + } + else + if (loadmp3(aFile) == SO_NO_ERROR) + { + res = SO_NO_ERROR; + } + else + { + res = FILE_LOAD_FAILED; + } + return res; + } + + AudioSourceInstance *WavStream::createInstance() + { + return new WavStreamInstance(this); + } + + double WavStream::getLength() + { + if (mBaseSamplerate == 0) + return 0; + return mSampleCount / mBaseSamplerate; + } +}; diff --git a/src/soloud/src/audiosource/wav/stb_vorbis.c b/src/soloud/src/audiosource/wav/stb_vorbis.c new file mode 100644 index 0000000..e7c92b1 --- /dev/null +++ b/src/soloud/src/audiosource/wav/stb_vorbis.c @@ -0,0 +1,5465 @@ +// Ogg Vorbis audio decoder - v1.14 - public domain +// http://nothings.org/stb_vorbis/ +// +// Original version written by Sean Barrett in 2007. +// +// Originally sponsored by RAD Game Tools. Seeking implementation +// sponsored by Phillip Bennefall, Marc Andersen, Aaron Baker, +// Elias Software, Aras Pranckevicius, and Sean Barrett. +// +// LICENSE +// +// See end of file for license information. +// +// Limitations: +// +// - floor 0 not supported (used in old ogg vorbis files pre-2004) +// - lossless sample-truncation at beginning ignored +// - cannot concatenate multiple vorbis streams +// - sample positions are 32-bit, limiting seekable 192Khz +// files to around 6 hours (Ogg supports 64-bit) +// +// Feature contributors: +// Dougall Johnson (sample-exact seeking) +// +// Bugfix/warning contributors: +// Terje Mathisen Niklas Frykholm Andy Hill +// Casey Muratori John Bolton Gargaj +// Laurent Gomila Marc LeBlanc Ronny Chevalier +// Bernhard Wodo Evan Balster alxprd@github +// Tom Beaumont Ingo Leitgeb Nicolas Guillemot +// Phillip Bennefall Rohit Thiago Goulart +// manxorist@github saga musix github:infatum +// Timur Gagiev +// +// Partial history: +// 1.14 - 2018-02-11 - delete bogus dealloca usage +// 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) +// 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files +// 1.11 - 2017-07-23 - fix MinGW compilation +// 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory +// 1.09 - 2016-04-04 - back out 'truncation of last frame' fix from previous version +// 1.08 - 2016-04-02 - warnings; setup memory leaks; truncation of last frame +// 1.07 - 2015-01-16 - fixes for crashes on invalid files; warning fixes; const +// 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) +// some crash fixes when out of memory or with corrupt files +// fix some inappropriately signed shifts +// 1.05 - 2015-04-19 - don't define __forceinline if it's redundant +// 1.04 - 2014-08-27 - fix missing const-correct case in API +// 1.03 - 2014-08-07 - warning fixes +// 1.02 - 2014-07-09 - declare qsort comparison as explicitly _cdecl in Windows +// 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float (interleaved was correct) +// 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in >2-channel; +// (API change) report sample rate for decode-full-file funcs +// +// See end of file for full version history. + + +////////////////////////////////////////////////////////////////////////////// +// +// HEADER BEGINS HERE +// + +#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H +#define STB_VORBIS_INCLUDE_STB_VORBIS_H + +#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) +#define STB_VORBIS_NO_STDIO 1 +#endif + +#ifndef STB_VORBIS_NO_STDIO +#include +#endif + +#include "soloud_file_hack_on.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/////////// THREAD SAFETY + +// Individual stb_vorbis* handles are not thread-safe; you cannot decode from +// them from multiple threads at the same time. However, you can have multiple +// stb_vorbis* handles and decode from them independently in multiple thrads. + + +/////////// MEMORY ALLOCATION + +// normally stb_vorbis uses malloc() to allocate memory at startup, +// and alloca() to allocate temporary memory during a frame on the +// stack. (Memory consumption will depend on the amount of setup +// data in the file and how you set the compile flags for speed +// vs. size. In my test files the maximal-size usage is ~150KB.) +// +// You can modify the wrapper functions in the source (setup_malloc, +// setup_temp_malloc, temp_malloc) to change this behavior, or you +// can use a simpler allocation model: you pass in a buffer from +// which stb_vorbis will allocate _all_ its memory (including the +// temp memory). "open" may fail with a VORBIS_outofmem if you +// do not pass in enough data; there is no way to determine how +// much you do need except to succeed (at which point you can +// query get_info to find the exact amount required. yes I know +// this is lame). +// +// If you pass in a non-NULL buffer of the type below, allocation +// will occur from it as described above. Otherwise just pass NULL +// to use malloc()/alloca() + +typedef struct +{ + char *alloc_buffer; + int alloc_buffer_length_in_bytes; +} stb_vorbis_alloc; + + +/////////// FUNCTIONS USEABLE WITH ALL INPUT MODES + +typedef struct stb_vorbis stb_vorbis; + +typedef struct +{ + unsigned int sample_rate; + int channels; + + unsigned int setup_memory_required; + unsigned int setup_temp_memory_required; + unsigned int temp_memory_required; + + int max_frame_size; +} stb_vorbis_info; + +// get general information about the file +extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f); + +// get the last error detected (clears it, too) +extern int stb_vorbis_get_error(stb_vorbis *f); + +// close an ogg vorbis file and free all memory in use +extern void stb_vorbis_close(stb_vorbis *f); + +// this function returns the offset (in samples) from the beginning of the +// file that will be returned by the next decode, if it is known, or -1 +// otherwise. after a flush_pushdata() call, this may take a while before +// it becomes valid again. +// NOT WORKING YET after a seek with PULLDATA API +extern int stb_vorbis_get_sample_offset(stb_vorbis *f); + +// returns the current seek point within the file, or offset from the beginning +// of the memory buffer. In pushdata mode it returns 0. +extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f); + +/////////// PUSHDATA API + +#ifndef STB_VORBIS_NO_PUSHDATA_API + +// this API allows you to get blocks of data from any source and hand +// them to stb_vorbis. you have to buffer them; stb_vorbis will tell +// you how much it used, and you have to give it the rest next time; +// and stb_vorbis may not have enough data to work with and you will +// need to give it the same data again PLUS more. Note that the Vorbis +// specification does not bound the size of an individual frame. + +extern stb_vorbis *stb_vorbis_open_pushdata( + const unsigned char * datablock, int datablock_length_in_bytes, + int *datablock_memory_consumed_in_bytes, + int *error, + const stb_vorbis_alloc *alloc_buffer); +// create a vorbis decoder by passing in the initial data block containing +// the ogg&vorbis headers (you don't need to do parse them, just provide +// the first N bytes of the file--you're told if it's not enough, see below) +// on success, returns an stb_vorbis *, does not set error, returns the amount of +// data parsed/consumed on this call in *datablock_memory_consumed_in_bytes; +// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed +// if returns NULL and *error is VORBIS_need_more_data, then the input block was +// incomplete and you need to pass in a larger block from the start of the file + +extern int stb_vorbis_decode_frame_pushdata( + stb_vorbis *f, + const unsigned char *datablock, int datablock_length_in_bytes, + int *channels, // place to write number of float * buffers + float ***output, // place to write float ** array of float * buffers + int *samples // place to write number of output samples + ); +// decode a frame of audio sample data if possible from the passed-in data block +// +// return value: number of bytes we used from datablock +// +// possible cases: +// 0 bytes used, 0 samples output (need more data) +// N bytes used, 0 samples output (resynching the stream, keep going) +// N bytes used, M samples output (one frame of data) +// note that after opening a file, you will ALWAYS get one N-bytes,0-sample +// frame, because Vorbis always "discards" the first frame. +// +// Note that on resynch, stb_vorbis will rarely consume all of the buffer, +// instead only datablock_length_in_bytes-3 or less. This is because it wants +// to avoid missing parts of a page header if they cross a datablock boundary, +// without writing state-machiney code to record a partial detection. +// +// The number of channels returned are stored in *channels (which can be +// NULL--it is always the same as the number of channels reported by +// get_info). *output will contain an array of float* buffers, one per +// channel. In other words, (*output)[0][0] contains the first sample from +// the first channel, and (*output)[1][0] contains the first sample from +// the second channel. + +extern void stb_vorbis_flush_pushdata(stb_vorbis *f); +// inform stb_vorbis that your next datablock will not be contiguous with +// previous ones (e.g. you've seeked in the data); future attempts to decode +// frames will cause stb_vorbis to resynchronize (as noted above), and +// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it +// will begin decoding the _next_ frame. +// +// if you want to seek using pushdata, you need to seek in your file, then +// call stb_vorbis_flush_pushdata(), then start calling decoding, then once +// decoding is returning you data, call stb_vorbis_get_sample_offset, and +// if you don't like the result, seek your file again and repeat. +#endif + + +////////// PULLING INPUT API + +#ifndef STB_VORBIS_NO_PULLDATA_API +// This API assumes stb_vorbis is allowed to pull data from a source-- +// either a block of memory containing the _entire_ vorbis stream, or a +// FILE * that you or it create, or possibly some other reading mechanism +// if you go modify the source to replace the FILE * case with some kind +// of callback to your code. (But if you don't support seeking, you may +// just want to go ahead and use pushdata.) + +#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION) +extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output); +#endif +#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION) +extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output); +#endif +// decode an entire file and output the data interleaved into a malloc()ed +// buffer stored in *output. The return value is the number of samples +// decoded, or -1 if the file could not be opened or was not an ogg vorbis file. +// When you're done with it, just free() the pointer returned in *output. + +extern stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from an ogg vorbis stream in memory (note +// this must be the entire stream!). on failure, returns NULL and sets *error + +#ifndef STB_VORBIS_NO_STDIO +extern stb_vorbis * stb_vorbis_open_filename(const char *filename, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from a filename via fopen(). on failure, +// returns NULL and sets *error (possibly to VORBIS_file_open_failure). + +extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from an open FILE *, looking for a stream at +// the _current_ seek point (ftell). on failure, returns NULL and sets *error. +// note that stb_vorbis must "own" this stream; if you seek it in between +// calls to stb_vorbis, it will become confused. Morever, if you attempt to +// perform stb_vorbis_seek_*() operations on this file, it will assume it +// owns the _entire_ rest of the file after the start point. Use the next +// function, stb_vorbis_open_file_section(), to limit it. + +extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close, + int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len); +// create an ogg vorbis decoder from an open FILE *, looking for a stream at +// the _current_ seek point (ftell); the stream will be of length 'len' bytes. +// on failure, returns NULL and sets *error. note that stb_vorbis must "own" +// this stream; if you seek it in between calls to stb_vorbis, it will become +// confused. +#endif + +extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number); +extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number); +// these functions seek in the Vorbis file to (approximately) 'sample_number'. +// after calling seek_frame(), the next call to get_frame_*() will include +// the specified sample. after calling stb_vorbis_seek(), the next call to +// stb_vorbis_get_samples_* will start with the specified sample. If you +// do not need to seek to EXACTLY the target sample when using get_samples_*, +// you can also use seek_frame(). + +extern int stb_vorbis_seek_start(stb_vorbis *f); +// this function is equivalent to stb_vorbis_seek(f,0) + +extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f); +extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f); +// these functions return the total length of the vorbis stream + +extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output); +// decode the next frame and return the number of samples. the number of +// channels returned are stored in *channels (which can be NULL--it is always +// the same as the number of channels reported by get_info). *output will +// contain an array of float* buffers, one per channel. These outputs will +// be overwritten on the next call to stb_vorbis_get_frame_*. +// +// You generally should not intermix calls to stb_vorbis_get_frame_*() +// and stb_vorbis_get_samples_*(), since the latter calls the former. + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts); +extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples); +#endif +// decode the next frame and return the number of *samples* per channel. +// Note that for interleaved data, you pass in the number of shorts (the +// size of your array), but the return value is the number of samples per +// channel, not the total number of samples. +// +// The data is coerced to the number of channels you request according to the +// channel coercion rules (see below). You must pass in the size of your +// buffer(s) so that stb_vorbis will not overwrite the end of the buffer. +// The maximum buffer size needed can be gotten from get_info(); however, +// the Vorbis I specification implies an absolute maximum of 4096 samples +// per channel. + +// Channel coercion rules: +// Let M be the number of channels requested, and N the number of channels present, +// and Cn be the nth channel; let stereo L be the sum of all L and center channels, +// and stereo R be the sum of all R and center channels (channel assignment from the +// vorbis spec). +// M N output +// 1 k sum(Ck) for all k +// 2 * stereo L, stereo R +// k l k > l, the first l channels, then 0s +// k l k <= l, the first k channels +// Note that this is not _good_ surround etc. mixing at all! It's just so +// you get something useful. + +extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats); +extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples); +// gets num_samples samples, not necessarily on a frame boundary--this requires +// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES. +// Returns the number of samples stored per channel; it may be less than requested +// at the end of the file. If there are no more samples in the file, returns 0. + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts); +extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples); +#endif +// gets num_samples samples, not necessarily on a frame boundary--this requires +// buffering so you have to supply the buffers. Applies the coercion rules above +// to produce 'channels' channels. Returns the number of samples stored per channel; +// it may be less than requested at the end of the file. If there are no more +// samples in the file, returns 0. + +#endif + +//////// ERROR CODES + +enum STBVorbisError +{ + VORBIS__no_error, + + VORBIS_need_more_data=1, // not a real error + + VORBIS_invalid_api_mixing, // can't mix API modes + VORBIS_outofmem, // not enough memory + VORBIS_feature_not_supported, // uses floor 0 + VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small + VORBIS_file_open_failure, // fopen() failed + VORBIS_seek_without_length, // can't seek in unknown-length file + + VORBIS_unexpected_eof=10, // file is truncated? + VORBIS_seek_invalid, // seek past EOF + + // decoding errors (corrupt/invalid stream) -- you probably + // don't care about the exact details of these + + // vorbis errors: + VORBIS_invalid_setup=20, + VORBIS_invalid_stream, + + // ogg errors: + VORBIS_missing_capture_pattern=30, + VORBIS_invalid_stream_structure_version, + VORBIS_continued_packet_flag_invalid, + VORBIS_incorrect_stream_serial_number, + VORBIS_invalid_first_page, + VORBIS_bad_packet_type, + VORBIS_cant_find_last_page, + VORBIS_seek_failed +}; + + +#ifdef __cplusplus +} +#endif + +#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H +// +// HEADER ENDS HERE +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef STB_VORBIS_HEADER_ONLY + +// global configuration settings (e.g. set these in the project/makefile), +// or just set them in this file at the top (although ideally the first few +// should be visible when the header file is compiled too, although it's not +// crucial) + +// STB_VORBIS_NO_PUSHDATA_API +// does not compile the code for the various stb_vorbis_*_pushdata() +// functions +// #define STB_VORBIS_NO_PUSHDATA_API + +// STB_VORBIS_NO_PULLDATA_API +// does not compile the code for the non-pushdata APIs +// #define STB_VORBIS_NO_PULLDATA_API + +// STB_VORBIS_NO_STDIO +// does not compile the code for the APIs that use FILE *s internally +// or externally (implied by STB_VORBIS_NO_PULLDATA_API) +// #define STB_VORBIS_NO_STDIO + +// STB_VORBIS_NO_INTEGER_CONVERSION +// does not compile the code for converting audio sample data from +// float to integer (implied by STB_VORBIS_NO_PULLDATA_API) +// #define STB_VORBIS_NO_INTEGER_CONVERSION + +// STB_VORBIS_NO_FAST_SCALED_FLOAT +// does not use a fast float-to-int trick to accelerate float-to-int on +// most platforms which requires endianness be defined correctly. +//#define STB_VORBIS_NO_FAST_SCALED_FLOAT + + +// STB_VORBIS_MAX_CHANNELS [number] +// globally define this to the maximum number of channels you need. +// The spec does not put a restriction on channels except that +// the count is stored in a byte, so 255 is the hard limit. +// Reducing this saves about 16 bytes per value, so using 16 saves +// (255-16)*16 or around 4KB. Plus anything other memory usage +// I forgot to account for. Can probably go as low as 8 (7.1 audio), +// 6 (5.1 audio), or 2 (stereo only). +#ifndef STB_VORBIS_MAX_CHANNELS +#define STB_VORBIS_MAX_CHANNELS 16 // enough for anyone? +#endif + +// STB_VORBIS_PUSHDATA_CRC_COUNT [number] +// after a flush_pushdata(), stb_vorbis begins scanning for the +// next valid page, without backtracking. when it finds something +// that looks like a page, it streams through it and verifies its +// CRC32. Should that validation fail, it keeps scanning. But it's +// possible that _while_ streaming through to check the CRC32 of +// one candidate page, it sees another candidate page. This #define +// determines how many "overlapping" candidate pages it can search +// at once. Note that "real" pages are typically ~4KB to ~8KB, whereas +// garbage pages could be as big as 64KB, but probably average ~16KB. +// So don't hose ourselves by scanning an apparent 64KB page and +// missing a ton of real ones in the interim; so minimum of 2 +#ifndef STB_VORBIS_PUSHDATA_CRC_COUNT +#define STB_VORBIS_PUSHDATA_CRC_COUNT 4 +#endif + +// STB_VORBIS_FAST_HUFFMAN_LENGTH [number] +// sets the log size of the huffman-acceleration table. Maximum +// supported value is 24. with larger numbers, more decodings are O(1), +// but the table size is larger so worse cache missing, so you'll have +// to probe (and try multiple ogg vorbis files) to find the sweet spot. +#ifndef STB_VORBIS_FAST_HUFFMAN_LENGTH +#define STB_VORBIS_FAST_HUFFMAN_LENGTH 10 +#endif + +// STB_VORBIS_FAST_BINARY_LENGTH [number] +// sets the log size of the binary-search acceleration table. this +// is used in similar fashion to the fast-huffman size to set initial +// parameters for the binary search + +// STB_VORBIS_FAST_HUFFMAN_INT +// The fast huffman tables are much more efficient if they can be +// stored as 16-bit results instead of 32-bit results. This restricts +// the codebooks to having only 65535 possible outcomes, though. +// (At least, accelerated by the huffman table.) +#ifndef STB_VORBIS_FAST_HUFFMAN_INT +#define STB_VORBIS_FAST_HUFFMAN_SHORT +#endif + +// STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH +// If the 'fast huffman' search doesn't succeed, then stb_vorbis falls +// back on binary searching for the correct one. This requires storing +// extra tables with the huffman codes in sorted order. Defining this +// symbol trades off space for speed by forcing a linear search in the +// non-fast case, except for "sparse" codebooks. +// #define STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH + +// STB_VORBIS_DIVIDES_IN_RESIDUE +// stb_vorbis precomputes the result of the scalar residue decoding +// that would otherwise require a divide per chunk. you can trade off +// space for time by defining this symbol. +// #define STB_VORBIS_DIVIDES_IN_RESIDUE + +// STB_VORBIS_DIVIDES_IN_CODEBOOK +// vorbis VQ codebooks can be encoded two ways: with every case explicitly +// stored, or with all elements being chosen from a small range of values, +// and all values possible in all elements. By default, stb_vorbis expands +// this latter kind out to look like the former kind for ease of decoding, +// because otherwise an integer divide-per-vector-element is required to +// unpack the index. If you define STB_VORBIS_DIVIDES_IN_CODEBOOK, you can +// trade off storage for speed. +//#define STB_VORBIS_DIVIDES_IN_CODEBOOK + +#ifdef STB_VORBIS_CODEBOOK_SHORTS +#error "STB_VORBIS_CODEBOOK_SHORTS is no longer supported as it produced incorrect results for some input formats" +#endif + +// STB_VORBIS_DIVIDE_TABLE +// this replaces small integer divides in the floor decode loop with +// table lookups. made less than 1% difference, so disabled by default. + +// STB_VORBIS_NO_INLINE_DECODE +// disables the inlining of the scalar codebook fast-huffman decode. +// might save a little codespace; useful for debugging +// #define STB_VORBIS_NO_INLINE_DECODE + +// STB_VORBIS_NO_DEFER_FLOOR +// Normally we only decode the floor without synthesizing the actual +// full curve. We can instead synthesize the curve immediately. This +// requires more memory and is very likely slower, so I don't think +// you'd ever want to do it except for debugging. +// #define STB_VORBIS_NO_DEFER_FLOOR + + + + +////////////////////////////////////////////////////////////////////////////// + +#ifdef STB_VORBIS_NO_PULLDATA_API + #define STB_VORBIS_NO_INTEGER_CONVERSION + #define STB_VORBIS_NO_STDIO +#endif + +#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) + #define STB_VORBIS_NO_STDIO 1 +#endif + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT + + // only need endianness for fast-float-to-int, which we don't + // use for pushdata + + #ifndef STB_VORBIS_BIG_ENDIAN + #define STB_VORBIS_ENDIAN 0 + #else + #define STB_VORBIS_ENDIAN 1 + #endif + +#endif +#endif + + +#ifndef STB_VORBIS_NO_STDIO +#include +#endif + +#ifndef STB_VORBIS_NO_CRT + #include + #include + #include + #include + + // find definition of alloca if it's not in stdlib.h: + #if defined(_MSC_VER) || defined(__MINGW32__) + #include + #endif + #if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__) + #include + #endif +#else // STB_VORBIS_NO_CRT + #define NULL 0 + #define malloc(s) 0 + #define free(s) ((void) 0) + #define realloc(s) 0 +#endif // STB_VORBIS_NO_CRT + +#include + +#ifdef __MINGW32__ + // eff you mingw: + // "fixed": + // http://sourceforge.net/p/mingw-w64/mailman/message/32882927/ + // "no that broke the build, reverted, who cares about C": + // http://sourceforge.net/p/mingw-w64/mailman/message/32890381/ + #ifdef __forceinline + #undef __forceinline + #endif + #define __forceinline + #define alloca __builtin_alloca +#elif !defined(_MSC_VER) + #if __GNUC__ + #define __forceinline inline + #else + #define __forceinline + #endif +#endif + +#if STB_VORBIS_MAX_CHANNELS > 256 +#error "Value of STB_VORBIS_MAX_CHANNELS outside of allowed range" +#endif + +#if STB_VORBIS_FAST_HUFFMAN_LENGTH > 24 +#error "Value of STB_VORBIS_FAST_HUFFMAN_LENGTH outside of allowed range" +#endif + + +#if 0 +#include +#define CHECK(f) _CrtIsValidHeapPointer(f->channel_buffers[1]) +#else +#define CHECK(f) ((void) 0) +#endif + +#define MAX_BLOCKSIZE_LOG 13 // from specification +#define MAX_BLOCKSIZE (1 << MAX_BLOCKSIZE_LOG) + + +typedef unsigned char uint8; +typedef signed char int8; +typedef unsigned short uint16; +typedef signed short int16; +typedef unsigned int uint32; +typedef signed int int32; + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +typedef float codetype; + +// @NOTE +// +// Some arrays below are tagged "//varies", which means it's actually +// a variable-sized piece of data, but rather than malloc I assume it's +// small enough it's better to just allocate it all together with the +// main thing +// +// Most of the variables are specified with the smallest size I could pack +// them into. It might give better performance to make them all full-sized +// integers. It should be safe to freely rearrange the structures or change +// the sizes larger--nothing relies on silently truncating etc., nor the +// order of variables. + +#define FAST_HUFFMAN_TABLE_SIZE (1 << STB_VORBIS_FAST_HUFFMAN_LENGTH) +#define FAST_HUFFMAN_TABLE_MASK (FAST_HUFFMAN_TABLE_SIZE - 1) + +typedef struct +{ + int dimensions, entries; + uint8 *codeword_lengths; + float minimum_value; + float delta_value; + uint8 value_bits; + uint8 lookup_type; + uint8 sequence_p; + uint8 sparse; + uint32 lookup_values; + codetype *multiplicands; + uint32 *codewords; + #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT + int16 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; + #else + int32 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; + #endif + uint32 *sorted_codewords; + int *sorted_values; + int sorted_entries; +} Codebook; + +typedef struct +{ + uint8 order; + uint16 rate; + uint16 bark_map_size; + uint8 amplitude_bits; + uint8 amplitude_offset; + uint8 number_of_books; + uint8 book_list[16]; // varies +} Floor0; + +typedef struct +{ + uint8 partitions; + uint8 partition_class_list[32]; // varies + uint8 class_dimensions[16]; // varies + uint8 class_subclasses[16]; // varies + uint8 class_masterbooks[16]; // varies + int16 subclass_books[16][8]; // varies + uint16 Xlist[31*8+2]; // varies + uint8 sorted_order[31*8+2]; + uint8 neighbors[31*8+2][2]; + uint8 floor1_multiplier; + uint8 rangebits; + int values; +} Floor1; + +typedef union +{ + Floor0 floor0; + Floor1 floor1; +} Floor; + +typedef struct +{ + uint32 begin, end; + uint32 part_size; + uint8 classifications; + uint8 classbook; + uint8 **classdata; + int16 (*residue_books)[8]; +} Residue; + +typedef struct +{ + uint8 magnitude; + uint8 angle; + uint8 mux; +} MappingChannel; + +typedef struct +{ + uint16 coupling_steps; + MappingChannel *chan; + uint8 submaps; + uint8 submap_floor[15]; // varies + uint8 submap_residue[15]; // varies +} Mapping; + +typedef struct +{ + uint8 blockflag; + uint8 mapping; + uint16 windowtype; + uint16 transformtype; +} Mode; + +typedef struct +{ + uint32 goal_crc; // expected crc if match + int bytes_left; // bytes left in packet + uint32 crc_so_far; // running crc + int bytes_done; // bytes processed in _current_ chunk + uint32 sample_loc; // granule pos encoded in page +} CRCscan; + +typedef struct +{ + uint32 page_start, page_end; + uint32 last_decoded_sample; +} ProbedPage; + +struct stb_vorbis +{ + // user-accessible info + unsigned int sample_rate; + int channels; + + unsigned int setup_memory_required; + unsigned int temp_memory_required; + unsigned int setup_temp_memory_required; + + // input config +#ifndef STB_VORBIS_NO_STDIO + FILE *f; + uint32 f_start; + int close_on_free; +#endif + + uint8 *stream; + uint8 *stream_start; + uint8 *stream_end; + + uint32 stream_len; + + uint8 push_mode; + + uint32 first_audio_page_offset; + + ProbedPage p_first, p_last; + + // memory management + stb_vorbis_alloc alloc; + int setup_offset; + int temp_offset; + + // run-time results + int eof; + enum STBVorbisError error; + + // user-useful data + + // header info + int blocksize[2]; + int blocksize_0, blocksize_1; + int codebook_count; + Codebook *codebooks; + int floor_count; + uint16 floor_types[64]; // varies + Floor *floor_config; + int residue_count; + uint16 residue_types[64]; // varies + Residue *residue_config; + int mapping_count; + Mapping *mapping; + int mode_count; + Mode mode_config[64]; // varies + + uint32 total_samples; + + // decode buffer + float *channel_buffers[STB_VORBIS_MAX_CHANNELS]; + float *outputs [STB_VORBIS_MAX_CHANNELS]; + + float *previous_window[STB_VORBIS_MAX_CHANNELS]; + int previous_length; + + #ifndef STB_VORBIS_NO_DEFER_FLOOR + int16 *finalY[STB_VORBIS_MAX_CHANNELS]; + #else + float *floor_buffers[STB_VORBIS_MAX_CHANNELS]; + #endif + + uint32 current_loc; // sample location of next frame to decode + int current_loc_valid; + + // per-blocksize precomputed data + + // twiddle factors + float *A[2],*B[2],*C[2]; + float *window[2]; + uint16 *bit_reverse[2]; + + // current page/packet/segment streaming info + uint32 serial; // stream serial number for verification + int last_page; + int segment_count; + uint8 segments[255]; + uint8 page_flag; + uint8 bytes_in_seg; + uint8 first_decode; + int next_seg; + int last_seg; // flag that we're on the last segment + int last_seg_which; // what was the segment number of the last seg? + uint32 acc; + int valid_bits; + int packet_bytes; + int end_seg_with_known_loc; + uint32 known_loc_for_packet; + int discard_samples_deferred; + uint32 samples_output; + + // push mode scanning + int page_crc_tests; // only in push_mode: number of tests active; -1 if not searching +#ifndef STB_VORBIS_NO_PUSHDATA_API + CRCscan scan[STB_VORBIS_PUSHDATA_CRC_COUNT]; +#endif + + // sample-access + int channel_buffer_start; + int channel_buffer_end; +}; + +#if defined(STB_VORBIS_NO_PUSHDATA_API) + #define IS_PUSH_MODE(f) FALSE +#elif defined(STB_VORBIS_NO_PULLDATA_API) + #define IS_PUSH_MODE(f) TRUE +#else + #define IS_PUSH_MODE(f) ((f)->push_mode) +#endif + +typedef struct stb_vorbis vorb; + +static int error(vorb *f, enum STBVorbisError e) +{ + f->error = e; + if (!f->eof && e != VORBIS_need_more_data) { + f->error=e; // breakpoint for debugging + } + return 0; +} + + +// these functions are used for allocating temporary memory +// while decoding. if you can afford the stack space, use +// alloca(); otherwise, provide a temp buffer and it will +// allocate out of those. + +#define array_size_required(count,size) (count*(sizeof(void *)+(size))) + +#define temp_alloc(f,size) (f->alloc.alloc_buffer ? setup_temp_malloc(f,size) : alloca(size)) +#define temp_free(f,p) 0 +#define temp_alloc_save(f) ((f)->temp_offset) +#define temp_alloc_restore(f,p) ((f)->temp_offset = (p)) + +#define temp_block_array(f,count,size) make_block_array(temp_alloc(f,array_size_required(count,size)), count, size) + +// given a sufficiently large block of memory, make an array of pointers to subblocks of it +static void *make_block_array(void *mem, int count, int size) +{ + int i; + void ** p = (void **) mem; + char *q = (char *) (p + count); + for (i=0; i < count; ++i) { + p[i] = q; + q += size; + } + return p; +} + +static void *setup_malloc(vorb *f, int sz) +{ + sz = (sz+3) & ~3; + f->setup_memory_required += sz; + if (f->alloc.alloc_buffer) { + void *p = (char *) f->alloc.alloc_buffer + f->setup_offset; + if (f->setup_offset + sz > f->temp_offset) return NULL; + f->setup_offset += sz; + return p; + } + return sz ? malloc(sz) : NULL; +} + +static void setup_free(vorb *f, void *p) +{ + if (f->alloc.alloc_buffer) return; // do nothing; setup mem is a stack + free(p); +} + +static void *setup_temp_malloc(vorb *f, int sz) +{ + sz = (sz+3) & ~3; + if (f->alloc.alloc_buffer) { + if (f->temp_offset - sz < f->setup_offset) return NULL; + f->temp_offset -= sz; + return (char *) f->alloc.alloc_buffer + f->temp_offset; + } + return malloc(sz); +} + +static void setup_temp_free(vorb *f, void *p, int sz) +{ + if (f->alloc.alloc_buffer) { + f->temp_offset += (sz+3)&~3; + return; + } + free(p); +} + +#define CRC32_POLY 0x04c11db7 // from spec + +static uint32 crc_table[256]; +static void crc32_init(void) +{ + int i,j; + uint32 s; + for(i=0; i < 256; i++) { + for (s=(uint32) i << 24, j=0; j < 8; ++j) + s = (s << 1) ^ (s >= (1U<<31) ? CRC32_POLY : 0); + crc_table[i] = s; + } +} + +static __forceinline uint32 crc32_update(uint32 crc, uint8 byte) +{ + return (crc << 8) ^ crc_table[byte ^ (crc >> 24)]; +} + + +// used in setup, and for huffman that doesn't go fast path +static unsigned int bit_reverse(unsigned int n) +{ + n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1); + n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2); + n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4); + n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8); + return (n >> 16) | (n << 16); +} + +static float square(float x) +{ + return x*x; +} + +// this is a weird definition of log2() for which log2(1) = 1, log2(2) = 2, log2(4) = 3 +// as required by the specification. fast(?) implementation from stb.h +// @OPTIMIZE: called multiple times per-packet with "constants"; move to setup +static int ilog(int32 n) +{ + static signed char log2_4[16] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4 }; + + if (n < 0) return 0; // signed n returns 0 + + // 2 compares if n < 16, 3 compares otherwise (4 if signed or n > 1<<29) + if (n < (1 << 14)) + if (n < (1 << 4)) return 0 + log2_4[n ]; + else if (n < (1 << 9)) return 5 + log2_4[n >> 5]; + else return 10 + log2_4[n >> 10]; + else if (n < (1 << 24)) + if (n < (1 << 19)) return 15 + log2_4[n >> 15]; + else return 20 + log2_4[n >> 20]; + else if (n < (1 << 29)) return 25 + log2_4[n >> 25]; + else return 30 + log2_4[n >> 30]; +} + +#ifndef M_PI + #define M_PI 3.14159265358979323846264f // from CRC +#endif + +// code length assigned to a value with no huffman encoding +#define NO_CODE 255 + +/////////////////////// LEAF SETUP FUNCTIONS ////////////////////////// +// +// these functions are only called at setup, and only a few times +// per file + +static float float32_unpack(uint32 x) +{ + // from the specification + uint32 mantissa = x & 0x1fffff; + uint32 sign = x & 0x80000000; + uint32 exp = (x & 0x7fe00000) >> 21; + double res = sign ? -(double)mantissa : (double)mantissa; + return (float) ldexp((float)res, exp-788); +} + + +// zlib & jpeg huffman tables assume that the output symbols +// can either be arbitrarily arranged, or have monotonically +// increasing frequencies--they rely on the lengths being sorted; +// this makes for a very simple generation algorithm. +// vorbis allows a huffman table with non-sorted lengths. This +// requires a more sophisticated construction, since symbols in +// order do not map to huffman codes "in order". +static void add_entry(Codebook *c, uint32 huff_code, int symbol, int count, int len, uint32 *values) +{ + if (!c->sparse) { + c->codewords [symbol] = huff_code; + } else { + c->codewords [count] = huff_code; + c->codeword_lengths[count] = len; + values [count] = symbol; + } +} + +static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values) +{ + int i,k,m=0; + uint32 available[32]; + + memset(available, 0, sizeof(available)); + // find the first entry + for (k=0; k < n; ++k) if (len[k] < NO_CODE) break; + if (k == n) { assert(c->sorted_entries == 0); return TRUE; } + // add to the list + add_entry(c, 0, k, m++, len[k], values); + // add all available leaves + for (i=1; i <= len[k]; ++i) + available[i] = 1U << (32-i); + // note that the above code treats the first case specially, + // but it's really the same as the following code, so they + // could probably be combined (except the initial code is 0, + // and I use 0 in available[] to mean 'empty') + for (i=k+1; i < n; ++i) { + uint32 res; + int z = len[i], y; + if (z == NO_CODE) continue; + // find lowest available leaf (should always be earliest, + // which is what the specification calls for) + // note that this property, and the fact we can never have + // more than one free leaf at a given level, isn't totally + // trivial to prove, but it seems true and the assert never + // fires, so! + while (z > 0 && !available[z]) --z; + if (z == 0) { return FALSE; } + res = available[z]; + assert(z >= 0 && z < 32); + available[z] = 0; + add_entry(c, bit_reverse(res), i, m++, len[i], values); + // propogate availability up the tree + if (z != len[i]) { + assert(len[i] >= 0 && len[i] < 32); + for (y=len[i]; y > z; --y) { + assert(available[y] == 0); + available[y] = res + (1 << (32-y)); + } + } + } + return TRUE; +} + +// accelerated huffman table allows fast O(1) match of all symbols +// of length <= STB_VORBIS_FAST_HUFFMAN_LENGTH +static void compute_accelerated_huffman(Codebook *c) +{ + int i, len; + for (i=0; i < FAST_HUFFMAN_TABLE_SIZE; ++i) + c->fast_huffman[i] = -1; + + len = c->sparse ? c->sorted_entries : c->entries; + #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT + if (len > 32767) len = 32767; // largest possible value we can encode! + #endif + for (i=0; i < len; ++i) { + if (c->codeword_lengths[i] <= STB_VORBIS_FAST_HUFFMAN_LENGTH) { + uint32 z = c->sparse ? bit_reverse(c->sorted_codewords[i]) : c->codewords[i]; + // set table entries for all bit combinations in the higher bits + while (z < FAST_HUFFMAN_TABLE_SIZE) { + c->fast_huffman[z] = i; + z += 1 << c->codeword_lengths[i]; + } + } + } +} + +#ifdef _MSC_VER +#define STBV_CDECL __cdecl +#else +#define STBV_CDECL +#endif + +static int STBV_CDECL uint32_compare(const void *p, const void *q) +{ + uint32 x = * (uint32 *) p; + uint32 y = * (uint32 *) q; + return x < y ? -1 : x > y; +} + +static int include_in_sort(Codebook *c, uint8 len) +{ + if (c->sparse) { assert(len != NO_CODE); return TRUE; } + if (len == NO_CODE) return FALSE; + if (len > STB_VORBIS_FAST_HUFFMAN_LENGTH) return TRUE; + return FALSE; +} + +// if the fast table above doesn't work, we want to binary +// search them... need to reverse the bits +static void compute_sorted_huffman(Codebook *c, uint8 *lengths, uint32 *values) +{ + int i, len; + // build a list of all the entries + // OPTIMIZATION: don't include the short ones, since they'll be caught by FAST_HUFFMAN. + // this is kind of a frivolous optimization--I don't see any performance improvement, + // but it's like 4 extra lines of code, so. + if (!c->sparse) { + int k = 0; + for (i=0; i < c->entries; ++i) + if (include_in_sort(c, lengths[i])) + c->sorted_codewords[k++] = bit_reverse(c->codewords[i]); + assert(k == c->sorted_entries); + } else { + for (i=0; i < c->sorted_entries; ++i) + c->sorted_codewords[i] = bit_reverse(c->codewords[i]); + } + + qsort(c->sorted_codewords, c->sorted_entries, sizeof(c->sorted_codewords[0]), uint32_compare); + c->sorted_codewords[c->sorted_entries] = 0xffffffff; + + len = c->sparse ? c->sorted_entries : c->entries; + // now we need to indicate how they correspond; we could either + // #1: sort a different data structure that says who they correspond to + // #2: for each sorted entry, search the original list to find who corresponds + // #3: for each original entry, find the sorted entry + // #1 requires extra storage, #2 is slow, #3 can use binary search! + for (i=0; i < len; ++i) { + int huff_len = c->sparse ? lengths[values[i]] : lengths[i]; + if (include_in_sort(c,huff_len)) { + uint32 code = bit_reverse(c->codewords[i]); + int x=0, n=c->sorted_entries; + while (n > 1) { + // invariant: sc[x] <= code < sc[x+n] + int m = x + (n >> 1); + if (c->sorted_codewords[m] <= code) { + x = m; + n -= (n>>1); + } else { + n >>= 1; + } + } + assert(c->sorted_codewords[x] == code); + if (c->sparse) { + c->sorted_values[x] = values[i]; + c->codeword_lengths[x] = huff_len; + } else { + c->sorted_values[x] = i; + } + } + } +} + +// only run while parsing the header (3 times) +static int vorbis_validate(uint8 *data) +{ + static uint8 vorbis[6] = { 'v', 'o', 'r', 'b', 'i', 's' }; + return memcmp(data, vorbis, 6) == 0; +} + +// called from setup only, once per code book +// (formula implied by specification) +static int lookup1_values(int entries, int dim) +{ + int r = (int) floor(exp((float) log((float) entries) / dim)); + if ((int) floor(pow((float) r+1, dim)) <= entries) // (int) cast for MinGW warning; + ++r; // floor() to avoid _ftol() when non-CRT + assert(pow((float) r+1, dim) > entries); + assert((int) floor(pow((float) r, dim)) <= entries); // (int),floor() as above + return r; +} + +// called twice per file +static void compute_twiddle_factors(int n, float *A, float *B, float *C) +{ + int n4 = n >> 2, n8 = n >> 3; + int k,k2; + + for (k=k2=0; k < n4; ++k,k2+=2) { + A[k2 ] = (float) cos(4*k*M_PI/n); + A[k2+1] = (float) -sin(4*k*M_PI/n); + B[k2 ] = (float) cos((k2+1)*M_PI/n/2) * 0.5f; + B[k2+1] = (float) sin((k2+1)*M_PI/n/2) * 0.5f; + } + for (k=k2=0; k < n8; ++k,k2+=2) { + C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); + C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); + } +} + +static void compute_window(int n, float *window) +{ + int n2 = n >> 1, i; + for (i=0; i < n2; ++i) + window[i] = (float) sin(0.5 * M_PI * square((float) sin((i - 0 + 0.5) / n2 * 0.5 * M_PI))); +} + +static void compute_bitreverse(int n, uint16 *rev) +{ + int ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + int i, n8 = n >> 3; + for (i=0; i < n8; ++i) + rev[i] = (bit_reverse(i) >> (32-ld+3)) << 2; +} + +static int init_blocksize(vorb *f, int b, int n) +{ + int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3; + f->A[b] = (float *) setup_malloc(f, sizeof(float) * n2); + f->B[b] = (float *) setup_malloc(f, sizeof(float) * n2); + f->C[b] = (float *) setup_malloc(f, sizeof(float) * n4); + if (!f->A[b] || !f->B[b] || !f->C[b]) return error(f, VORBIS_outofmem); + compute_twiddle_factors(n, f->A[b], f->B[b], f->C[b]); + f->window[b] = (float *) setup_malloc(f, sizeof(float) * n2); + if (!f->window[b]) return error(f, VORBIS_outofmem); + compute_window(n, f->window[b]); + f->bit_reverse[b] = (uint16 *) setup_malloc(f, sizeof(uint16) * n8); + if (!f->bit_reverse[b]) return error(f, VORBIS_outofmem); + compute_bitreverse(n, f->bit_reverse[b]); + return TRUE; +} + +static void neighbors(uint16 *x, int n, int *plow, int *phigh) +{ + int low = -1; + int high = 65536; + int i; + for (i=0; i < n; ++i) { + if (x[i] > low && x[i] < x[n]) { *plow = i; low = x[i]; } + if (x[i] < high && x[i] > x[n]) { *phigh = i; high = x[i]; } + } +} + +// this has been repurposed so y is now the original index instead of y +typedef struct +{ + uint16 x,id; +} stbv__floor_ordering; + +static int STBV_CDECL point_compare(const void *p, const void *q) +{ + stbv__floor_ordering *a = (stbv__floor_ordering *) p; + stbv__floor_ordering *b = (stbv__floor_ordering *) q; + return a->x < b->x ? -1 : a->x > b->x; +} + +// +/////////////////////// END LEAF SETUP FUNCTIONS ////////////////////////// + + +#if defined(STB_VORBIS_NO_STDIO) + #define USE_MEMORY(z) TRUE +#else + #define USE_MEMORY(z) ((z)->stream) +#endif + +static uint8 get8(vorb *z) +{ + if (USE_MEMORY(z)) { + if (z->stream >= z->stream_end) { z->eof = TRUE; return 0; } + return *z->stream++; + } + + #ifndef STB_VORBIS_NO_STDIO + { + int c = fgetc(z->f); + if (c == EOF) { z->eof = TRUE; return 0; } + return c; + } + #endif +} + +static uint32 get32(vorb *f) +{ + uint32 x; + x = get8(f); + x += get8(f) << 8; + x += get8(f) << 16; + x += (uint32) get8(f) << 24; + return x; +} + +static int getn(vorb *z, uint8 *data, int n) +{ + if (USE_MEMORY(z)) { + if (z->stream+n > z->stream_end) { z->eof = 1; return 0; } + memcpy(data, z->stream, n); + z->stream += n; + return 1; + } + + #ifndef STB_VORBIS_NO_STDIO + if (fread(data, n, 1, z->f) == 1) + return 1; + else { + z->eof = 1; + return 0; + } + #endif +} + +static void skip(vorb *z, int n) +{ + if (USE_MEMORY(z)) { + z->stream += n; + if (z->stream >= z->stream_end) z->eof = 1; + return; + } + #ifndef STB_VORBIS_NO_STDIO + { + long x = ftell(z->f); + fseek(z->f, x+n, SEEK_SET); + } + #endif +} + +static int set_file_offset(stb_vorbis *f, unsigned int loc) +{ + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (f->push_mode) return 0; + #endif + f->eof = 0; + if (USE_MEMORY(f)) { + if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) { + f->stream = f->stream_end; + f->eof = 1; + return 0; + } else { + f->stream = f->stream_start + loc; + return 1; + } + } + #ifndef STB_VORBIS_NO_STDIO + if (loc + f->f_start < loc || loc >= 0x80000000) { + loc = 0x7fffffff; + f->eof = 1; + } else { + loc += f->f_start; + } + if (!fseek(f->f, loc, SEEK_SET)) + return 1; + f->eof = 1; + fseek(f->f, f->f_start, SEEK_END); + return 0; + #endif +} + + +static uint8 ogg_page_header[4] = { 0x4f, 0x67, 0x67, 0x53 }; + +static int capture_pattern(vorb *f) +{ + if (0x4f != get8(f)) return FALSE; + if (0x67 != get8(f)) return FALSE; + if (0x67 != get8(f)) return FALSE; + if (0x53 != get8(f)) return FALSE; + return TRUE; +} + +#define PAGEFLAG_continued_packet 1 +#define PAGEFLAG_first_page 2 +#define PAGEFLAG_last_page 4 + +static int start_page_no_capturepattern(vorb *f) +{ + uint32 loc0,loc1,n; + // stream structure version + if (0 != get8(f)) return error(f, VORBIS_invalid_stream_structure_version); + // header flag + f->page_flag = get8(f); + // absolute granule position + loc0 = get32(f); + loc1 = get32(f); + // @TODO: validate loc0,loc1 as valid positions? + // stream serial number -- vorbis doesn't interleave, so discard + get32(f); + //if (f->serial != get32(f)) return error(f, VORBIS_incorrect_stream_serial_number); + // page sequence number + n = get32(f); + f->last_page = n; + // CRC32 + get32(f); + // page_segments + f->segment_count = get8(f); + if (!getn(f, f->segments, f->segment_count)) + return error(f, VORBIS_unexpected_eof); + // assume we _don't_ know any the sample position of any segments + f->end_seg_with_known_loc = -2; + if (loc0 != ~0U || loc1 != ~0U) { + int i; + // determine which packet is the last one that will complete + for (i=f->segment_count-1; i >= 0; --i) + if (f->segments[i] < 255) + break; + // 'i' is now the index of the _last_ segment of a packet that ends + if (i >= 0) { + f->end_seg_with_known_loc = i; + f->known_loc_for_packet = loc0; + } + } + if (f->first_decode) { + int i,len; + ProbedPage p; + len = 0; + for (i=0; i < f->segment_count; ++i) + len += f->segments[i]; + len += 27 + f->segment_count; + p.page_start = f->first_audio_page_offset; + p.page_end = p.page_start + len; + p.last_decoded_sample = loc0; + f->p_first = p; + } + f->next_seg = 0; + return TRUE; +} + +static int start_page(vorb *f) +{ + if (!capture_pattern(f)) return error(f, VORBIS_missing_capture_pattern); + return start_page_no_capturepattern(f); +} + +static int start_packet(vorb *f) +{ + while (f->next_seg == -1) { + if (!start_page(f)) return FALSE; + if (f->page_flag & PAGEFLAG_continued_packet) + return error(f, VORBIS_continued_packet_flag_invalid); + } + f->last_seg = FALSE; + f->valid_bits = 0; + f->packet_bytes = 0; + f->bytes_in_seg = 0; + // f->next_seg is now valid + return TRUE; +} + +static int maybe_start_packet(vorb *f) +{ + if (f->next_seg == -1) { + int x = get8(f); + if (f->eof) return FALSE; // EOF at page boundary is not an error! + if (0x4f != x ) return error(f, VORBIS_missing_capture_pattern); + if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (0x53 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (!start_page_no_capturepattern(f)) return FALSE; + if (f->page_flag & PAGEFLAG_continued_packet) { + // set up enough state that we can read this packet if we want, + // e.g. during recovery + f->last_seg = FALSE; + f->bytes_in_seg = 0; + return error(f, VORBIS_continued_packet_flag_invalid); + } + } + return start_packet(f); +} + +static int next_segment(vorb *f) +{ + int len; + if (f->last_seg) return 0; + if (f->next_seg == -1) { + f->last_seg_which = f->segment_count-1; // in case start_page fails + if (!start_page(f)) { f->last_seg = 1; return 0; } + if (!(f->page_flag & PAGEFLAG_continued_packet)) return error(f, VORBIS_continued_packet_flag_invalid); + } + len = f->segments[f->next_seg++]; + if (len < 255) { + f->last_seg = TRUE; + f->last_seg_which = f->next_seg-1; + } + if (f->next_seg >= f->segment_count) + f->next_seg = -1; + assert(f->bytes_in_seg == 0); + f->bytes_in_seg = len; + return len; +} + +#define EOP (-1) +#define INVALID_BITS (-1) + +static int get8_packet_raw(vorb *f) +{ + if (!f->bytes_in_seg) { // CLANG! + if (f->last_seg) return EOP; + else if (!next_segment(f)) return EOP; + } + assert(f->bytes_in_seg > 0); + --f->bytes_in_seg; + ++f->packet_bytes; + return get8(f); +} + +static int get8_packet(vorb *f) +{ + int x = get8_packet_raw(f); + f->valid_bits = 0; + return x; +} + +static void flush_packet(vorb *f) +{ + while (get8_packet_raw(f) != EOP); +} + +// @OPTIMIZE: this is the secondary bit decoder, so it's probably not as important +// as the huffman decoder? +static uint32 get_bits(vorb *f, int n) +{ + uint32 z; + + if (f->valid_bits < 0) return 0; + if (f->valid_bits < n) { + if (n > 24) { + // the accumulator technique below would not work correctly in this case + z = get_bits(f, 24); + z += get_bits(f, n-24) << 24; + return z; + } + if (f->valid_bits == 0) f->acc = 0; + while (f->valid_bits < n) { + int z = get8_packet_raw(f); + if (z == EOP) { + f->valid_bits = INVALID_BITS; + return 0; + } + f->acc += z << f->valid_bits; + f->valid_bits += 8; + } + } + if (f->valid_bits < 0) return 0; + z = f->acc & ((1 << n)-1); + f->acc >>= n; + f->valid_bits -= n; + return z; +} + +// @OPTIMIZE: primary accumulator for huffman +// expand the buffer to as many bits as possible without reading off end of packet +// it might be nice to allow f->valid_bits and f->acc to be stored in registers, +// e.g. cache them locally and decode locally +static __forceinline void prep_huffman(vorb *f) +{ + if (f->valid_bits <= 24) { + if (f->valid_bits == 0) f->acc = 0; + do { + int z; + if (f->last_seg && !f->bytes_in_seg) return; + z = get8_packet_raw(f); + if (z == EOP) return; + f->acc += (unsigned) z << f->valid_bits; + f->valid_bits += 8; + } while (f->valid_bits <= 24); + } +} + +enum +{ + VORBIS_packet_id = 1, + VORBIS_packet_comment = 3, + VORBIS_packet_setup = 5 +}; + +static int codebook_decode_scalar_raw(vorb *f, Codebook *c) +{ + int i; + prep_huffman(f); + + if (c->codewords == NULL && c->sorted_codewords == NULL) + return -1; + + // cases to use binary search: sorted_codewords && !c->codewords + // sorted_codewords && c->entries > 8 + if (c->entries > 8 ? c->sorted_codewords!=NULL : !c->codewords) { + // binary search + uint32 code = bit_reverse(f->acc); + int x=0, n=c->sorted_entries, len; + + while (n > 1) { + // invariant: sc[x] <= code < sc[x+n] + int m = x + (n >> 1); + if (c->sorted_codewords[m] <= code) { + x = m; + n -= (n>>1); + } else { + n >>= 1; + } + } + // x is now the sorted index + if (!c->sparse) x = c->sorted_values[x]; + // x is now sorted index if sparse, or symbol otherwise + len = c->codeword_lengths[x]; + if (f->valid_bits >= len) { + f->acc >>= len; + f->valid_bits -= len; + return x; + } + + f->valid_bits = 0; + return -1; + } + + // if small, linear search + assert(!c->sparse); + for (i=0; i < c->entries; ++i) { + if (c->codeword_lengths[i] == NO_CODE) continue; + if (c->codewords[i] == (f->acc & ((1 << c->codeword_lengths[i])-1))) { + if (f->valid_bits >= c->codeword_lengths[i]) { + f->acc >>= c->codeword_lengths[i]; + f->valid_bits -= c->codeword_lengths[i]; + return i; + } + f->valid_bits = 0; + return -1; + } + } + + error(f, VORBIS_invalid_stream); + f->valid_bits = 0; + return -1; +} + +#ifndef STB_VORBIS_NO_INLINE_DECODE + +#define DECODE_RAW(var, f,c) \ + if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) \ + prep_huffman(f); \ + var = f->acc & FAST_HUFFMAN_TABLE_MASK; \ + var = c->fast_huffman[var]; \ + if (var >= 0) { \ + int n = c->codeword_lengths[var]; \ + f->acc >>= n; \ + f->valid_bits -= n; \ + if (f->valid_bits < 0) { f->valid_bits = 0; var = -1; } \ + } else { \ + var = codebook_decode_scalar_raw(f,c); \ + } + +#else + +static int codebook_decode_scalar(vorb *f, Codebook *c) +{ + int i; + if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) + prep_huffman(f); + // fast huffman table lookup + i = f->acc & FAST_HUFFMAN_TABLE_MASK; + i = c->fast_huffman[i]; + if (i >= 0) { + f->acc >>= c->codeword_lengths[i]; + f->valid_bits -= c->codeword_lengths[i]; + if (f->valid_bits < 0) { f->valid_bits = 0; return -1; } + return i; + } + return codebook_decode_scalar_raw(f,c); +} + +#define DECODE_RAW(var,f,c) var = codebook_decode_scalar(f,c); + +#endif + +#define DECODE(var,f,c) \ + DECODE_RAW(var,f,c) \ + if (c->sparse) var = c->sorted_values[var]; + +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + #define DECODE_VQ(var,f,c) DECODE_RAW(var,f,c) +#else + #define DECODE_VQ(var,f,c) DECODE(var,f,c) +#endif + + + + + + +// CODEBOOK_ELEMENT_FAST is an optimization for the CODEBOOK_FLOATS case +// where we avoid one addition +#define CODEBOOK_ELEMENT(c,off) (c->multiplicands[off]) +#define CODEBOOK_ELEMENT_FAST(c,off) (c->multiplicands[off]) +#define CODEBOOK_ELEMENT_BASE(c) (0) + +static int codebook_decode_start(vorb *f, Codebook *c) +{ + int z = -1; + + // type 0 is only legal in a scalar context + if (c->lookup_type == 0) + error(f, VORBIS_invalid_stream); + else { + DECODE_VQ(z,f,c); + if (c->sparse) assert(z < c->sorted_entries); + if (z < 0) { // check for EOP + if (!f->bytes_in_seg) + if (f->last_seg) + return z; + error(f, VORBIS_invalid_stream); + } + } + return z; +} + +static int codebook_decode(vorb *f, Codebook *c, float *output, int len) +{ + int i,z = codebook_decode_start(f,c); + if (z < 0) return FALSE; + if (len > c->dimensions) len = c->dimensions; + +#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + float last = CODEBOOK_ELEMENT_BASE(c); + int div = 1; + for (i=0; i < len; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + output[i] += val; + if (c->sequence_p) last = val + c->minimum_value; + div *= c->lookup_values; + } + return TRUE; + } +#endif + + z *= c->dimensions; + if (c->sequence_p) { + float last = CODEBOOK_ELEMENT_BASE(c); + for (i=0; i < len; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + output[i] += val; + last = val + c->minimum_value; + } + } else { + float last = CODEBOOK_ELEMENT_BASE(c); + for (i=0; i < len; ++i) { + output[i] += CODEBOOK_ELEMENT_FAST(c,z+i) + last; + } + } + + return TRUE; +} + +static int codebook_decode_step(vorb *f, Codebook *c, float *output, int len, int step) +{ + int i,z = codebook_decode_start(f,c); + float last = CODEBOOK_ELEMENT_BASE(c); + if (z < 0) return FALSE; + if (len > c->dimensions) len = c->dimensions; + +#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int div = 1; + for (i=0; i < len; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + output[i*step] += val; + if (c->sequence_p) last = val; + div *= c->lookup_values; + } + return TRUE; + } +#endif + + z *= c->dimensions; + for (i=0; i < len; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + output[i*step] += val; + if (c->sequence_p) last = val; + } + + return TRUE; +} + +static int codebook_decode_deinterleave_repeat(vorb *f, Codebook *c, float **outputs, int ch, int *c_inter_p, int *p_inter_p, int len, int total_decode) +{ + int c_inter = *c_inter_p; + int p_inter = *p_inter_p; + int i,z, effective = c->dimensions; + + // type 0 is only legal in a scalar context + if (c->lookup_type == 0) return error(f, VORBIS_invalid_stream); + + while (total_decode > 0) { + float last = CODEBOOK_ELEMENT_BASE(c); + DECODE_VQ(z,f,c); + #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + assert(!c->sparse || z < c->sorted_entries); + #endif + if (z < 0) { + if (!f->bytes_in_seg) + if (f->last_seg) return FALSE; + return error(f, VORBIS_invalid_stream); + } + + // if this will take us off the end of the buffers, stop short! + // we check by computing the length of the virtual interleaved + // buffer (len*ch), our current offset within it (p_inter*ch)+(c_inter), + // and the length we'll be using (effective) + if (c_inter + p_inter*ch + effective > len * ch) { + effective = len*ch - (p_inter*ch - c_inter); + } + + #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int div = 1; + for (i=0; i < effective; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + if (c->sequence_p) last = val; + div *= c->lookup_values; + } + } else + #endif + { + z *= c->dimensions; + if (c->sequence_p) { + for (i=0; i < effective; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + last = val; + } + } else { + for (i=0; i < effective; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + } + } + } + + total_decode -= effective; + } + *c_inter_p = c_inter; + *p_inter_p = p_inter; + return TRUE; +} + +static int predict_point(int x, int x0, int x1, int y0, int y1) +{ + int dy = y1 - y0; + int adx = x1 - x0; + // @OPTIMIZE: force int division to round in the right direction... is this necessary on x86? + int err = abs(dy) * (x - x0); + int off = err / adx; + return dy < 0 ? y0 - off : y0 + off; +} + +// the following table is block-copied from the specification +static float inverse_db_table[256] = +{ + 1.0649863e-07f, 1.1341951e-07f, 1.2079015e-07f, 1.2863978e-07f, + 1.3699951e-07f, 1.4590251e-07f, 1.5538408e-07f, 1.6548181e-07f, + 1.7623575e-07f, 1.8768855e-07f, 1.9988561e-07f, 2.1287530e-07f, + 2.2670913e-07f, 2.4144197e-07f, 2.5713223e-07f, 2.7384213e-07f, + 2.9163793e-07f, 3.1059021e-07f, 3.3077411e-07f, 3.5226968e-07f, + 3.7516214e-07f, 3.9954229e-07f, 4.2550680e-07f, 4.5315863e-07f, + 4.8260743e-07f, 5.1396998e-07f, 5.4737065e-07f, 5.8294187e-07f, + 6.2082472e-07f, 6.6116941e-07f, 7.0413592e-07f, 7.4989464e-07f, + 7.9862701e-07f, 8.5052630e-07f, 9.0579828e-07f, 9.6466216e-07f, + 1.0273513e-06f, 1.0941144e-06f, 1.1652161e-06f, 1.2409384e-06f, + 1.3215816e-06f, 1.4074654e-06f, 1.4989305e-06f, 1.5963394e-06f, + 1.7000785e-06f, 1.8105592e-06f, 1.9282195e-06f, 2.0535261e-06f, + 2.1869758e-06f, 2.3290978e-06f, 2.4804557e-06f, 2.6416497e-06f, + 2.8133190e-06f, 2.9961443e-06f, 3.1908506e-06f, 3.3982101e-06f, + 3.6190449e-06f, 3.8542308e-06f, 4.1047004e-06f, 4.3714470e-06f, + 4.6555282e-06f, 4.9580707e-06f, 5.2802740e-06f, 5.6234160e-06f, + 5.9888572e-06f, 6.3780469e-06f, 6.7925283e-06f, 7.2339451e-06f, + 7.7040476e-06f, 8.2047000e-06f, 8.7378876e-06f, 9.3057248e-06f, + 9.9104632e-06f, 1.0554501e-05f, 1.1240392e-05f, 1.1970856e-05f, + 1.2748789e-05f, 1.3577278e-05f, 1.4459606e-05f, 1.5399272e-05f, + 1.6400004e-05f, 1.7465768e-05f, 1.8600792e-05f, 1.9809576e-05f, + 2.1096914e-05f, 2.2467911e-05f, 2.3928002e-05f, 2.5482978e-05f, + 2.7139006e-05f, 2.8902651e-05f, 3.0780908e-05f, 3.2781225e-05f, + 3.4911534e-05f, 3.7180282e-05f, 3.9596466e-05f, 4.2169667e-05f, + 4.4910090e-05f, 4.7828601e-05f, 5.0936773e-05f, 5.4246931e-05f, + 5.7772202e-05f, 6.1526565e-05f, 6.5524908e-05f, 6.9783085e-05f, + 7.4317983e-05f, 7.9147585e-05f, 8.4291040e-05f, 8.9768747e-05f, + 9.5602426e-05f, 0.00010181521f, 0.00010843174f, 0.00011547824f, + 0.00012298267f, 0.00013097477f, 0.00013948625f, 0.00014855085f, + 0.00015820453f, 0.00016848555f, 0.00017943469f, 0.00019109536f, + 0.00020351382f, 0.00021673929f, 0.00023082423f, 0.00024582449f, + 0.00026179955f, 0.00027881276f, 0.00029693158f, 0.00031622787f, + 0.00033677814f, 0.00035866388f, 0.00038197188f, 0.00040679456f, + 0.00043323036f, 0.00046138411f, 0.00049136745f, 0.00052329927f, + 0.00055730621f, 0.00059352311f, 0.00063209358f, 0.00067317058f, + 0.00071691700f, 0.00076350630f, 0.00081312324f, 0.00086596457f, + 0.00092223983f, 0.00098217216f, 0.0010459992f, 0.0011139742f, + 0.0011863665f, 0.0012634633f, 0.0013455702f, 0.0014330129f, + 0.0015261382f, 0.0016253153f, 0.0017309374f, 0.0018434235f, + 0.0019632195f, 0.0020908006f, 0.0022266726f, 0.0023713743f, + 0.0025254795f, 0.0026895994f, 0.0028643847f, 0.0030505286f, + 0.0032487691f, 0.0034598925f, 0.0036847358f, 0.0039241906f, + 0.0041792066f, 0.0044507950f, 0.0047400328f, 0.0050480668f, + 0.0053761186f, 0.0057254891f, 0.0060975636f, 0.0064938176f, + 0.0069158225f, 0.0073652516f, 0.0078438871f, 0.0083536271f, + 0.0088964928f, 0.009474637f, 0.010090352f, 0.010746080f, + 0.011444421f, 0.012188144f, 0.012980198f, 0.013823725f, + 0.014722068f, 0.015678791f, 0.016697687f, 0.017782797f, + 0.018938423f, 0.020169149f, 0.021479854f, 0.022875735f, + 0.024362330f, 0.025945531f, 0.027631618f, 0.029427276f, + 0.031339626f, 0.033376252f, 0.035545228f, 0.037855157f, + 0.040315199f, 0.042935108f, 0.045725273f, 0.048696758f, + 0.051861348f, 0.055231591f, 0.058820850f, 0.062643361f, + 0.066714279f, 0.071049749f, 0.075666962f, 0.080584227f, + 0.085821044f, 0.091398179f, 0.097337747f, 0.10366330f, + 0.11039993f, 0.11757434f, 0.12521498f, 0.13335215f, + 0.14201813f, 0.15124727f, 0.16107617f, 0.17154380f, + 0.18269168f, 0.19456402f, 0.20720788f, 0.22067342f, + 0.23501402f, 0.25028656f, 0.26655159f, 0.28387361f, + 0.30232132f, 0.32196786f, 0.34289114f, 0.36517414f, + 0.38890521f, 0.41417847f, 0.44109412f, 0.46975890f, + 0.50028648f, 0.53279791f, 0.56742212f, 0.60429640f, + 0.64356699f, 0.68538959f, 0.72993007f, 0.77736504f, + 0.82788260f, 0.88168307f, 0.9389798f, 1.0f +}; + + +// @OPTIMIZE: if you want to replace this bresenham line-drawing routine, +// note that you must produce bit-identical output to decode correctly; +// this specific sequence of operations is specified in the spec (it's +// drawing integer-quantized frequency-space lines that the encoder +// expects to be exactly the same) +// ... also, isn't the whole point of Bresenham's algorithm to NOT +// have to divide in the setup? sigh. +#ifndef STB_VORBIS_NO_DEFER_FLOOR +#define LINE_OP(a,b) a *= b +#else +#define LINE_OP(a,b) a = b +#endif + +#ifdef STB_VORBIS_DIVIDE_TABLE +#define DIVTAB_NUMER 32 +#define DIVTAB_DENOM 64 +int8 integer_divide_table[DIVTAB_NUMER][DIVTAB_DENOM]; // 2KB +#endif + +static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y1, int n) +{ + int dy = y1 - y0; + int adx = x1 - x0; + int ady = abs(dy); + int base; + int x=x0,y=y0; + int err = 0; + int sy; + +#ifdef STB_VORBIS_DIVIDE_TABLE + if (adx < DIVTAB_DENOM && ady < DIVTAB_NUMER) { + if (dy < 0) { + base = -integer_divide_table[ady][adx]; + sy = base-1; + } else { + base = integer_divide_table[ady][adx]; + sy = base+1; + } + } else { + base = dy / adx; + if (dy < 0) + sy = base - 1; + else + sy = base+1; + } +#else + base = dy / adx; + if (dy < 0) + sy = base - 1; + else + sy = base+1; +#endif + ady -= abs(base) * adx; + if (x1 > n) x1 = n; + if (x < x1) { + LINE_OP(output[x], inverse_db_table[y]); + for (++x; x < x1; ++x) { + err += ady; + if (err >= adx) { + err -= adx; + y += sy; + } else + y += base; + LINE_OP(output[x], inverse_db_table[y]); + } + } +} + +static int residue_decode(vorb *f, Codebook *book, float *target, int offset, int n, int rtype) +{ + int k; + if (rtype == 0) { + int step = n / book->dimensions; + for (k=0; k < step; ++k) + if (!codebook_decode_step(f, book, target+offset+k, n-offset-k, step)) + return FALSE; + } else { + for (k=0; k < n; ) { + if (!codebook_decode(f, book, target+offset, n-k)) + return FALSE; + k += book->dimensions; + offset += book->dimensions; + } + } + return TRUE; +} + +// n is 1/2 of the blocksize -- +// specification: "Correct per-vector decode length is [n]/2" +static void decode_residue(vorb *f, float *residue_buffers[], int ch, int n, int rn, uint8 *do_not_decode) +{ + int i,j,pass; + Residue *r = f->residue_config + rn; + int rtype = f->residue_types[rn]; + int c = r->classbook; + int classwords = f->codebooks[c].dimensions; + unsigned int actual_size = rtype == 2 ? n*2 : n; + unsigned int limit_r_begin = (r->begin < actual_size ? r->begin : actual_size); + unsigned int limit_r_end = (r->end < actual_size ? r->end : actual_size); + int n_read = limit_r_end - limit_r_begin; + int part_read = n_read / r->part_size; + int temp_alloc_point = temp_alloc_save(f); + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + uint8 ***part_classdata = (uint8 ***) temp_block_array(f,f->channels, part_read * sizeof(**part_classdata)); + #else + int **classifications = (int **) temp_block_array(f,f->channels, part_read * sizeof(**classifications)); + #endif + + CHECK(f); + + for (i=0; i < ch; ++i) + if (!do_not_decode[i]) + memset(residue_buffers[i], 0, sizeof(float) * n); + + if (rtype == 2 && ch != 1) { + for (j=0; j < ch; ++j) + if (!do_not_decode[j]) + break; + if (j == ch) + goto done; + + for (pass=0; pass < 8; ++pass) { + int pcount = 0, class_set = 0; + if (ch == 2) { + while (pcount < part_read) { + int z = r->begin + pcount*r->part_size; + int c_inter = (z & 1), p_inter = z>>1; + if (pass == 0) { + Codebook *c = f->codebooks+r->classbook; + int q; + DECODE(q,f,c); + if (q == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[0][class_set] = r->classdata[q]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[0][i+pcount] = q % r->classifications; + q /= r->classifications; + } + #endif + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + int z = r->begin + pcount*r->part_size; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[0][class_set][i]; + #else + int c = classifications[0][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + Codebook *book = f->codebooks + b; + #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + #else + // saves 1% + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + #endif + } else { + z += r->part_size; + c_inter = z & 1; + p_inter = z >> 1; + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } else if (ch == 1) { + while (pcount < part_read) { + int z = r->begin + pcount*r->part_size; + int c_inter = 0, p_inter = z; + if (pass == 0) { + Codebook *c = f->codebooks+r->classbook; + int q; + DECODE(q,f,c); + if (q == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[0][class_set] = r->classdata[q]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[0][i+pcount] = q % r->classifications; + q /= r->classifications; + } + #endif + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + int z = r->begin + pcount*r->part_size; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[0][class_set][i]; + #else + int c = classifications[0][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + Codebook *book = f->codebooks + b; + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + } else { + z += r->part_size; + c_inter = 0; + p_inter = z; + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } else { + while (pcount < part_read) { + int z = r->begin + pcount*r->part_size; + int c_inter = z % ch, p_inter = z/ch; + if (pass == 0) { + Codebook *c = f->codebooks+r->classbook; + int q; + DECODE(q,f,c); + if (q == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[0][class_set] = r->classdata[q]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[0][i+pcount] = q % r->classifications; + q /= r->classifications; + } + #endif + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + int z = r->begin + pcount*r->part_size; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[0][class_set][i]; + #else + int c = classifications[0][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + Codebook *book = f->codebooks + b; + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + } else { + z += r->part_size; + c_inter = z % ch; + p_inter = z / ch; + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } + } + goto done; + } + CHECK(f); + + for (pass=0; pass < 8; ++pass) { + int pcount = 0, class_set=0; + while (pcount < part_read) { + if (pass == 0) { + for (j=0; j < ch; ++j) { + if (!do_not_decode[j]) { + Codebook *c = f->codebooks+r->classbook; + int temp; + DECODE(temp,f,c); + if (temp == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[j][class_set] = r->classdata[temp]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[j][i+pcount] = temp % r->classifications; + temp /= r->classifications; + } + #endif + } + } + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + for (j=0; j < ch; ++j) { + if (!do_not_decode[j]) { + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[j][class_set][i]; + #else + int c = classifications[j][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + float *target = residue_buffers[j]; + int offset = r->begin + pcount * r->part_size; + int n = r->part_size; + Codebook *book = f->codebooks + b; + if (!residue_decode(f, book, target, offset, n, rtype)) + goto done; + } + } + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } + done: + CHECK(f); + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + temp_free(f,part_classdata); + #else + temp_free(f,classifications); + #endif + temp_alloc_restore(f,temp_alloc_point); +} + + +#if 0 +// slow way for debugging +void inverse_mdct_slow(float *buffer, int n) +{ + int i,j; + int n2 = n >> 1; + float *x = (float *) malloc(sizeof(*x) * n2); + memcpy(x, buffer, sizeof(*x) * n2); + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n2; ++j) + // formula from paper: + //acc += n/4.0f * x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); + // formula from wikipedia + //acc += 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); + // these are equivalent, except the formula from the paper inverts the multiplier! + // however, what actually works is NO MULTIPLIER!?! + //acc += 64 * 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); + acc += x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); + buffer[i] = acc; + } + free(x); +} +#elif 0 +// same as above, but just barely able to run in real time on modern machines +void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) +{ + float mcos[16384]; + int i,j; + int n2 = n >> 1, nmask = (n << 2) -1; + float *x = (float *) malloc(sizeof(*x) * n2); + memcpy(x, buffer, sizeof(*x) * n2); + for (i=0; i < 4*n; ++i) + mcos[i] = (float) cos(M_PI / 2 * i / n); + + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n2; ++j) + acc += x[j] * mcos[(2 * i + 1 + n2)*(2*j+1) & nmask]; + buffer[i] = acc; + } + free(x); +} +#elif 0 +// transform to use a slow dct-iv; this is STILL basically trivial, +// but only requires half as many ops +void dct_iv_slow(float *buffer, int n) +{ + float mcos[16384]; + float x[2048]; + int i,j; + int n2 = n >> 1, nmask = (n << 3) - 1; + memcpy(x, buffer, sizeof(*x) * n); + for (i=0; i < 8*n; ++i) + mcos[i] = (float) cos(M_PI / 4 * i / n); + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n; ++j) + acc += x[j] * mcos[((2 * i + 1)*(2*j+1)) & nmask]; + buffer[i] = acc; + } +} + +void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) +{ + int i, n4 = n >> 2, n2 = n >> 1, n3_4 = n - n4; + float temp[4096]; + + memcpy(temp, buffer, n2 * sizeof(float)); + dct_iv_slow(temp, n2); // returns -c'-d, a-b' + + for (i=0; i < n4 ; ++i) buffer[i] = temp[i+n4]; // a-b' + for ( ; i < n3_4; ++i) buffer[i] = -temp[n3_4 - i - 1]; // b-a', c+d' + for ( ; i < n ; ++i) buffer[i] = -temp[i - n3_4]; // c'+d +} +#endif + +#ifndef LIBVORBIS_MDCT +#define LIBVORBIS_MDCT 0 +#endif + +#if LIBVORBIS_MDCT +// directly call the vorbis MDCT using an interface documented +// by Jeff Roberts... useful for performance comparison +typedef struct +{ + int n; + int log2n; + + float *trig; + int *bitrev; + + float scale; +} mdct_lookup; + +extern void mdct_init(mdct_lookup *lookup, int n); +extern void mdct_clear(mdct_lookup *l); +extern void mdct_backward(mdct_lookup *init, float *in, float *out); + +mdct_lookup M1,M2; + +void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) +{ + mdct_lookup *M; + if (M1.n == n) M = &M1; + else if (M2.n == n) M = &M2; + else if (M1.n == 0) { mdct_init(&M1, n); M = &M1; } + else { + if (M2.n) __asm int 3; + mdct_init(&M2, n); + M = &M2; + } + + mdct_backward(M, buffer, buffer); +} +#endif + + +// the following were split out into separate functions while optimizing; +// they could be pushed back up but eh. __forceinline showed no change; +// they're probably already being inlined. +static void imdct_step3_iter0_loop(int n, float *e, int i_off, int k_off, float *A) +{ + float *ee0 = e + i_off; + float *ee2 = ee0 + k_off; + int i; + + assert((n & 3) == 0); + for (i=(n>>2); i > 0; --i) { + float k00_20, k01_21; + k00_20 = ee0[ 0] - ee2[ 0]; + k01_21 = ee0[-1] - ee2[-1]; + ee0[ 0] += ee2[ 0];//ee0[ 0] = ee0[ 0] + ee2[ 0]; + ee0[-1] += ee2[-1];//ee0[-1] = ee0[-1] + ee2[-1]; + ee2[ 0] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-1] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-2] - ee2[-2]; + k01_21 = ee0[-3] - ee2[-3]; + ee0[-2] += ee2[-2];//ee0[-2] = ee0[-2] + ee2[-2]; + ee0[-3] += ee2[-3];//ee0[-3] = ee0[-3] + ee2[-3]; + ee2[-2] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-3] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-4] - ee2[-4]; + k01_21 = ee0[-5] - ee2[-5]; + ee0[-4] += ee2[-4];//ee0[-4] = ee0[-4] + ee2[-4]; + ee0[-5] += ee2[-5];//ee0[-5] = ee0[-5] + ee2[-5]; + ee2[-4] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-5] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-6] - ee2[-6]; + k01_21 = ee0[-7] - ee2[-7]; + ee0[-6] += ee2[-6];//ee0[-6] = ee0[-6] + ee2[-6]; + ee0[-7] += ee2[-7];//ee0[-7] = ee0[-7] + ee2[-7]; + ee2[-6] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-7] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + ee0 -= 8; + ee2 -= 8; + } +} + +static void imdct_step3_inner_r_loop(int lim, float *e, int d0, int k_off, float *A, int k1) +{ + int i; + float k00_20, k01_21; + + float *e0 = e + d0; + float *e2 = e0 + k_off; + + for (i=lim >> 2; i > 0; --i) { + k00_20 = e0[-0] - e2[-0]; + k01_21 = e0[-1] - e2[-1]; + e0[-0] += e2[-0];//e0[-0] = e0[-0] + e2[-0]; + e0[-1] += e2[-1];//e0[-1] = e0[-1] + e2[-1]; + e2[-0] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-1] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-2] - e2[-2]; + k01_21 = e0[-3] - e2[-3]; + e0[-2] += e2[-2];//e0[-2] = e0[-2] + e2[-2]; + e0[-3] += e2[-3];//e0[-3] = e0[-3] + e2[-3]; + e2[-2] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-3] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-4] - e2[-4]; + k01_21 = e0[-5] - e2[-5]; + e0[-4] += e2[-4];//e0[-4] = e0[-4] + e2[-4]; + e0[-5] += e2[-5];//e0[-5] = e0[-5] + e2[-5]; + e2[-4] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-5] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-6] - e2[-6]; + k01_21 = e0[-7] - e2[-7]; + e0[-6] += e2[-6];//e0[-6] = e0[-6] + e2[-6]; + e0[-7] += e2[-7];//e0[-7] = e0[-7] + e2[-7]; + e2[-6] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-7] = (k01_21)*A[0] + (k00_20) * A[1]; + + e0 -= 8; + e2 -= 8; + + A += k1; + } +} + +static void imdct_step3_inner_s_loop(int n, float *e, int i_off, int k_off, float *A, int a_off, int k0) +{ + int i; + float A0 = A[0]; + float A1 = A[0+1]; + float A2 = A[0+a_off]; + float A3 = A[0+a_off+1]; + float A4 = A[0+a_off*2+0]; + float A5 = A[0+a_off*2+1]; + float A6 = A[0+a_off*3+0]; + float A7 = A[0+a_off*3+1]; + + float k00,k11; + + float *ee0 = e +i_off; + float *ee2 = ee0+k_off; + + for (i=n; i > 0; --i) { + k00 = ee0[ 0] - ee2[ 0]; + k11 = ee0[-1] - ee2[-1]; + ee0[ 0] = ee0[ 0] + ee2[ 0]; + ee0[-1] = ee0[-1] + ee2[-1]; + ee2[ 0] = (k00) * A0 - (k11) * A1; + ee2[-1] = (k11) * A0 + (k00) * A1; + + k00 = ee0[-2] - ee2[-2]; + k11 = ee0[-3] - ee2[-3]; + ee0[-2] = ee0[-2] + ee2[-2]; + ee0[-3] = ee0[-3] + ee2[-3]; + ee2[-2] = (k00) * A2 - (k11) * A3; + ee2[-3] = (k11) * A2 + (k00) * A3; + + k00 = ee0[-4] - ee2[-4]; + k11 = ee0[-5] - ee2[-5]; + ee0[-4] = ee0[-4] + ee2[-4]; + ee0[-5] = ee0[-5] + ee2[-5]; + ee2[-4] = (k00) * A4 - (k11) * A5; + ee2[-5] = (k11) * A4 + (k00) * A5; + + k00 = ee0[-6] - ee2[-6]; + k11 = ee0[-7] - ee2[-7]; + ee0[-6] = ee0[-6] + ee2[-6]; + ee0[-7] = ee0[-7] + ee2[-7]; + ee2[-6] = (k00) * A6 - (k11) * A7; + ee2[-7] = (k11) * A6 + (k00) * A7; + + ee0 -= k0; + ee2 -= k0; + } +} + +static __forceinline void iter_54(float *z) +{ + float k00,k11,k22,k33; + float y0,y1,y2,y3; + + k00 = z[ 0] - z[-4]; + y0 = z[ 0] + z[-4]; + y2 = z[-2] + z[-6]; + k22 = z[-2] - z[-6]; + + z[-0] = y0 + y2; // z0 + z4 + z2 + z6 + z[-2] = y0 - y2; // z0 + z4 - z2 - z6 + + // done with y0,y2 + + k33 = z[-3] - z[-7]; + + z[-4] = k00 + k33; // z0 - z4 + z3 - z7 + z[-6] = k00 - k33; // z0 - z4 - z3 + z7 + + // done with k33 + + k11 = z[-1] - z[-5]; + y1 = z[-1] + z[-5]; + y3 = z[-3] + z[-7]; + + z[-1] = y1 + y3; // z1 + z5 + z3 + z7 + z[-3] = y1 - y3; // z1 + z5 - z3 - z7 + z[-5] = k11 - k22; // z1 - z5 + z2 - z6 + z[-7] = k11 + k22; // z1 - z5 - z2 + z6 +} + +static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A, int base_n) +{ + int a_off = base_n >> 3; + float A2 = A[0+a_off]; + float *z = e + i_off; + float *base = z - 16 * n; + + while (z > base) { + float k00,k11; + + k00 = z[-0] - z[-8]; + k11 = z[-1] - z[-9]; + z[-0] = z[-0] + z[-8]; + z[-1] = z[-1] + z[-9]; + z[-8] = k00; + z[-9] = k11 ; + + k00 = z[ -2] - z[-10]; + k11 = z[ -3] - z[-11]; + z[ -2] = z[ -2] + z[-10]; + z[ -3] = z[ -3] + z[-11]; + z[-10] = (k00+k11) * A2; + z[-11] = (k11-k00) * A2; + + k00 = z[-12] - z[ -4]; // reverse to avoid a unary negation + k11 = z[ -5] - z[-13]; + z[ -4] = z[ -4] + z[-12]; + z[ -5] = z[ -5] + z[-13]; + z[-12] = k11; + z[-13] = k00; + + k00 = z[-14] - z[ -6]; // reverse to avoid a unary negation + k11 = z[ -7] - z[-15]; + z[ -6] = z[ -6] + z[-14]; + z[ -7] = z[ -7] + z[-15]; + z[-14] = (k00+k11) * A2; + z[-15] = (k00-k11) * A2; + + iter_54(z); + iter_54(z-8); + z -= 16; + } +} + +static void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) +{ + int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; + int ld; + // @OPTIMIZE: reduce register pressure by using fewer variables? + int save_point = temp_alloc_save(f); + float *buf2 = (float *) temp_alloc(f, n2 * sizeof(*buf2)); + float *u=NULL,*v=NULL; + // twiddle factors + float *A = f->A[blocktype]; + + // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" + // See notes about bugs in that paper in less-optimal implementation 'inverse_mdct_old' after this function. + + // kernel from paper + + + // merged: + // copy and reflect spectral data + // step 0 + + // note that it turns out that the items added together during + // this step are, in fact, being added to themselves (as reflected + // by step 0). inexplicable inefficiency! this became obvious + // once I combined the passes. + + // so there's a missing 'times 2' here (for adding X to itself). + // this propogates through linearly to the end, where the numbers + // are 1/2 too small, and need to be compensated for. + + { + float *d,*e, *AA, *e_stop; + d = &buf2[n2-2]; + AA = A; + e = &buffer[0]; + e_stop = &buffer[n2]; + while (e != e_stop) { + d[1] = (e[0] * AA[0] - e[2]*AA[1]); + d[0] = (e[0] * AA[1] + e[2]*AA[0]); + d -= 2; + AA += 2; + e += 4; + } + + e = &buffer[n2-3]; + while (d >= buf2) { + d[1] = (-e[2] * AA[0] - -e[0]*AA[1]); + d[0] = (-e[2] * AA[1] + -e[0]*AA[0]); + d -= 2; + AA += 2; + e -= 4; + } + } + + // now we use symbolic names for these, so that we can + // possibly swap their meaning as we change which operations + // are in place + + u = buffer; + v = buf2; + + // step 2 (paper output is w, now u) + // this could be in place, but the data ends up in the wrong + // place... _somebody_'s got to swap it, so this is nominated + { + float *AA = &A[n2-8]; + float *d0,*d1, *e0, *e1; + + e0 = &v[n4]; + e1 = &v[0]; + + d0 = &u[n4]; + d1 = &u[0]; + + while (AA >= A) { + float v40_20, v41_21; + + v41_21 = e0[1] - e1[1]; + v40_20 = e0[0] - e1[0]; + d0[1] = e0[1] + e1[1]; + d0[0] = e0[0] + e1[0]; + d1[1] = v41_21*AA[4] - v40_20*AA[5]; + d1[0] = v40_20*AA[4] + v41_21*AA[5]; + + v41_21 = e0[3] - e1[3]; + v40_20 = e0[2] - e1[2]; + d0[3] = e0[3] + e1[3]; + d0[2] = e0[2] + e1[2]; + d1[3] = v41_21*AA[0] - v40_20*AA[1]; + d1[2] = v40_20*AA[0] + v41_21*AA[1]; + + AA -= 8; + + d0 += 4; + d1 += 4; + e0 += 4; + e1 += 4; + } + } + + // step 3 + ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + + // optimized step 3: + + // the original step3 loop can be nested r inside s or s inside r; + // it's written originally as s inside r, but this is dumb when r + // iterates many times, and s few. So I have two copies of it and + // switch between them halfway. + + // this is iteration 0 of step 3 + imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*0, -(n >> 3), A); + imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*1, -(n >> 3), A); + + // this is iteration 1 of step 3 + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*0, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*1, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*2, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*3, -(n >> 4), A, 16); + + l=2; + for (; l < (ld-3)>>1; ++l) { + int k0 = n >> (l+2), k0_2 = k0>>1; + int lim = 1 << (l+1); + int i; + for (i=0; i < lim; ++i) + imdct_step3_inner_r_loop(n >> (l+4), u, n2-1 - k0*i, -k0_2, A, 1 << (l+3)); + } + + for (; l < ld-6; ++l) { + int k0 = n >> (l+2), k1 = 1 << (l+3), k0_2 = k0>>1; + int rlim = n >> (l+6), r; + int lim = 1 << (l+1); + int i_off; + float *A0 = A; + i_off = n2-1; + for (r=rlim; r > 0; --r) { + imdct_step3_inner_s_loop(lim, u, i_off, -k0_2, A0, k1, k0); + A0 += k1*4; + i_off -= 8; + } + } + + // iterations with count: + // ld-6,-5,-4 all interleaved together + // the big win comes from getting rid of needless flops + // due to the constants on pass 5 & 4 being all 1 and 0; + // combining them to be simultaneous to improve cache made little difference + imdct_step3_inner_s_loop_ld654(n >> 5, u, n2-1, A, n); + + // output is u + + // step 4, 5, and 6 + // cannot be in-place because of step 5 + { + uint16 *bitrev = f->bit_reverse[blocktype]; + // weirdly, I'd have thought reading sequentially and writing + // erratically would have been better than vice-versa, but in + // fact that's not what my testing showed. (That is, with + // j = bitreverse(i), do you read i and write j, or read j and write i.) + + float *d0 = &v[n4-4]; + float *d1 = &v[n2-4]; + while (d0 >= v) { + int k4; + + k4 = bitrev[0]; + d1[3] = u[k4+0]; + d1[2] = u[k4+1]; + d0[3] = u[k4+2]; + d0[2] = u[k4+3]; + + k4 = bitrev[1]; + d1[1] = u[k4+0]; + d1[0] = u[k4+1]; + d0[1] = u[k4+2]; + d0[0] = u[k4+3]; + + d0 -= 4; + d1 -= 4; + bitrev += 2; + } + } + // (paper output is u, now v) + + + // data must be in buf2 + assert(v == buf2); + + // step 7 (paper output is v, now v) + // this is now in place + { + float *C = f->C[blocktype]; + float *d, *e; + + d = v; + e = v + n2 - 4; + + while (d < e) { + float a02,a11,b0,b1,b2,b3; + + a02 = d[0] - e[2]; + a11 = d[1] + e[3]; + + b0 = C[1]*a02 + C[0]*a11; + b1 = C[1]*a11 - C[0]*a02; + + b2 = d[0] + e[ 2]; + b3 = d[1] - e[ 3]; + + d[0] = b2 + b0; + d[1] = b3 + b1; + e[2] = b2 - b0; + e[3] = b1 - b3; + + a02 = d[2] - e[0]; + a11 = d[3] + e[1]; + + b0 = C[3]*a02 + C[2]*a11; + b1 = C[3]*a11 - C[2]*a02; + + b2 = d[2] + e[ 0]; + b3 = d[3] - e[ 1]; + + d[2] = b2 + b0; + d[3] = b3 + b1; + e[0] = b2 - b0; + e[1] = b1 - b3; + + C += 4; + d += 4; + e -= 4; + } + } + + // data must be in buf2 + + + // step 8+decode (paper output is X, now buffer) + // this generates pairs of data a la 8 and pushes them directly through + // the decode kernel (pushing rather than pulling) to avoid having + // to make another pass later + + // this cannot POSSIBLY be in place, so we refer to the buffers directly + + { + float *d0,*d1,*d2,*d3; + + float *B = f->B[blocktype] + n2 - 8; + float *e = buf2 + n2 - 8; + d0 = &buffer[0]; + d1 = &buffer[n2-4]; + d2 = &buffer[n2]; + d3 = &buffer[n-4]; + while (e >= v) { + float p0,p1,p2,p3; + + p3 = e[6]*B[7] - e[7]*B[6]; + p2 = -e[6]*B[6] - e[7]*B[7]; + + d0[0] = p3; + d1[3] = - p3; + d2[0] = p2; + d3[3] = p2; + + p1 = e[4]*B[5] - e[5]*B[4]; + p0 = -e[4]*B[4] - e[5]*B[5]; + + d0[1] = p1; + d1[2] = - p1; + d2[1] = p0; + d3[2] = p0; + + p3 = e[2]*B[3] - e[3]*B[2]; + p2 = -e[2]*B[2] - e[3]*B[3]; + + d0[2] = p3; + d1[1] = - p3; + d2[2] = p2; + d3[1] = p2; + + p1 = e[0]*B[1] - e[1]*B[0]; + p0 = -e[0]*B[0] - e[1]*B[1]; + + d0[3] = p1; + d1[0] = - p1; + d2[3] = p0; + d3[0] = p0; + + B -= 8; + e -= 8; + d0 += 4; + d2 += 4; + d1 -= 4; + d3 -= 4; + } + } + + temp_free(f,buf2); + temp_alloc_restore(f,save_point); +} + +#if 0 +// this is the original version of the above code, if you want to optimize it from scratch +void inverse_mdct_naive(float *buffer, int n) +{ + float s; + float A[1 << 12], B[1 << 12], C[1 << 11]; + int i,k,k2,k4, n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; + int n3_4 = n - n4, ld; + // how can they claim this only uses N words?! + // oh, because they're only used sparsely, whoops + float u[1 << 13], X[1 << 13], v[1 << 13], w[1 << 13]; + // set up twiddle factors + + for (k=k2=0; k < n4; ++k,k2+=2) { + A[k2 ] = (float) cos(4*k*M_PI/n); + A[k2+1] = (float) -sin(4*k*M_PI/n); + B[k2 ] = (float) cos((k2+1)*M_PI/n/2); + B[k2+1] = (float) sin((k2+1)*M_PI/n/2); + } + for (k=k2=0; k < n8; ++k,k2+=2) { + C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); + C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); + } + + // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" + // Note there are bugs in that pseudocode, presumably due to them attempting + // to rename the arrays nicely rather than representing the way their actual + // implementation bounces buffers back and forth. As a result, even in the + // "some formulars corrected" version, a direct implementation fails. These + // are noted below as "paper bug". + + // copy and reflect spectral data + for (k=0; k < n2; ++k) u[k] = buffer[k]; + for ( ; k < n ; ++k) u[k] = -buffer[n - k - 1]; + // kernel from paper + // step 1 + for (k=k2=k4=0; k < n4; k+=1, k2+=2, k4+=4) { + v[n-k4-1] = (u[k4] - u[n-k4-1]) * A[k2] - (u[k4+2] - u[n-k4-3])*A[k2+1]; + v[n-k4-3] = (u[k4] - u[n-k4-1]) * A[k2+1] + (u[k4+2] - u[n-k4-3])*A[k2]; + } + // step 2 + for (k=k4=0; k < n8; k+=1, k4+=4) { + w[n2+3+k4] = v[n2+3+k4] + v[k4+3]; + w[n2+1+k4] = v[n2+1+k4] + v[k4+1]; + w[k4+3] = (v[n2+3+k4] - v[k4+3])*A[n2-4-k4] - (v[n2+1+k4]-v[k4+1])*A[n2-3-k4]; + w[k4+1] = (v[n2+1+k4] - v[k4+1])*A[n2-4-k4] + (v[n2+3+k4]-v[k4+3])*A[n2-3-k4]; + } + // step 3 + ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + for (l=0; l < ld-3; ++l) { + int k0 = n >> (l+2), k1 = 1 << (l+3); + int rlim = n >> (l+4), r4, r; + int s2lim = 1 << (l+2), s2; + for (r=r4=0; r < rlim; r4+=4,++r) { + for (s2=0; s2 < s2lim; s2+=2) { + u[n-1-k0*s2-r4] = w[n-1-k0*s2-r4] + w[n-1-k0*(s2+1)-r4]; + u[n-3-k0*s2-r4] = w[n-3-k0*s2-r4] + w[n-3-k0*(s2+1)-r4]; + u[n-1-k0*(s2+1)-r4] = (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1] + - (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1+1]; + u[n-3-k0*(s2+1)-r4] = (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1] + + (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1+1]; + } + } + if (l+1 < ld-3) { + // paper bug: ping-ponging of u&w here is omitted + memcpy(w, u, sizeof(u)); + } + } + + // step 4 + for (i=0; i < n8; ++i) { + int j = bit_reverse(i) >> (32-ld+3); + assert(j < n8); + if (i == j) { + // paper bug: original code probably swapped in place; if copying, + // need to directly copy in this case + int i8 = i << 3; + v[i8+1] = u[i8+1]; + v[i8+3] = u[i8+3]; + v[i8+5] = u[i8+5]; + v[i8+7] = u[i8+7]; + } else if (i < j) { + int i8 = i << 3, j8 = j << 3; + v[j8+1] = u[i8+1], v[i8+1] = u[j8 + 1]; + v[j8+3] = u[i8+3], v[i8+3] = u[j8 + 3]; + v[j8+5] = u[i8+5], v[i8+5] = u[j8 + 5]; + v[j8+7] = u[i8+7], v[i8+7] = u[j8 + 7]; + } + } + // step 5 + for (k=0; k < n2; ++k) { + w[k] = v[k*2+1]; + } + // step 6 + for (k=k2=k4=0; k < n8; ++k, k2 += 2, k4 += 4) { + u[n-1-k2] = w[k4]; + u[n-2-k2] = w[k4+1]; + u[n3_4 - 1 - k2] = w[k4+2]; + u[n3_4 - 2 - k2] = w[k4+3]; + } + // step 7 + for (k=k2=0; k < n8; ++k, k2 += 2) { + v[n2 + k2 ] = ( u[n2 + k2] + u[n-2-k2] + C[k2+1]*(u[n2+k2]-u[n-2-k2]) + C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; + v[n-2 - k2] = ( u[n2 + k2] + u[n-2-k2] - C[k2+1]*(u[n2+k2]-u[n-2-k2]) - C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; + v[n2+1+ k2] = ( u[n2+1+k2] - u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; + v[n-1 - k2] = (-u[n2+1+k2] + u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; + } + // step 8 + for (k=k2=0; k < n4; ++k,k2 += 2) { + X[k] = v[k2+n2]*B[k2 ] + v[k2+1+n2]*B[k2+1]; + X[n2-1-k] = v[k2+n2]*B[k2+1] - v[k2+1+n2]*B[k2 ]; + } + + // decode kernel to output + // determined the following value experimentally + // (by first figuring out what made inverse_mdct_slow work); then matching that here + // (probably vorbis encoder premultiplies by n or n/2, to save it on the decoder?) + s = 0.5; // theoretically would be n4 + + // [[[ note! the s value of 0.5 is compensated for by the B[] in the current code, + // so it needs to use the "old" B values to behave correctly, or else + // set s to 1.0 ]]] + for (i=0; i < n4 ; ++i) buffer[i] = s * X[i+n4]; + for ( ; i < n3_4; ++i) buffer[i] = -s * X[n3_4 - i - 1]; + for ( ; i < n ; ++i) buffer[i] = -s * X[i - n3_4]; +} +#endif + +static float *get_window(vorb *f, int len) +{ + len <<= 1; + if (len == f->blocksize_0) return f->window[0]; + if (len == f->blocksize_1) return f->window[1]; + assert(0); + return NULL; +} + +#ifndef STB_VORBIS_NO_DEFER_FLOOR +typedef int16 YTYPE; +#else +typedef int YTYPE; +#endif +static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *finalY, uint8 *step2_flag) +{ + int n2 = n >> 1; + int s = map->chan[i].mux, floor; + floor = map->submap_floor[s]; + if (f->floor_types[floor] == 0) { + return error(f, VORBIS_invalid_stream); + } else { + Floor1 *g = &f->floor_config[floor].floor1; + int j,q; + int lx = 0, ly = finalY[0] * g->floor1_multiplier; + for (q=1; q < g->values; ++q) { + j = g->sorted_order[q]; + #ifndef STB_VORBIS_NO_DEFER_FLOOR + if (finalY[j] >= 0) + #else + if (step2_flag[j]) + #endif + { + int hy = finalY[j] * g->floor1_multiplier; + int hx = g->Xlist[j]; + if (lx != hx) + draw_line(target, lx,ly, hx,hy, n2); + CHECK(f); + lx = hx, ly = hy; + } + } + if (lx < n2) { + // optimization of: draw_line(target, lx,ly, n,ly, n2); + for (j=lx; j < n2; ++j) + LINE_OP(target[j], inverse_db_table[ly]); + CHECK(f); + } + } + return TRUE; +} + +// The meaning of "left" and "right" +// +// For a given frame: +// we compute samples from 0..n +// window_center is n/2 +// we'll window and mix the samples from left_start to left_end with data from the previous frame +// all of the samples from left_end to right_start can be output without mixing; however, +// this interval is 0-length except when transitioning between short and long frames +// all of the samples from right_start to right_end need to be mixed with the next frame, +// which we don't have, so those get saved in a buffer +// frame N's right_end-right_start, the number of samples to mix with the next frame, +// has to be the same as frame N+1's left_end-left_start (which they are by +// construction) + +static int vorbis_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) +{ + Mode *m; + int i, n, prev, next, window_center; + f->channel_buffer_start = f->channel_buffer_end = 0; + + retry: + if (f->eof) return FALSE; + if (!maybe_start_packet(f)) + return FALSE; + // check packet type + if (get_bits(f,1) != 0) { + if (IS_PUSH_MODE(f)) + return error(f,VORBIS_bad_packet_type); + while (EOP != get8_packet(f)); + goto retry; + } + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + + i = get_bits(f, ilog(f->mode_count-1)); + if (i == EOP) return FALSE; + if (i >= f->mode_count) return FALSE; + *mode = i; + m = f->mode_config + i; + if (m->blockflag) { + n = f->blocksize_1; + prev = get_bits(f,1); + next = get_bits(f,1); + } else { + prev = next = 0; + n = f->blocksize_0; + } + +// WINDOWING + + window_center = n >> 1; + if (m->blockflag && !prev) { + *p_left_start = (n - f->blocksize_0) >> 2; + *p_left_end = (n + f->blocksize_0) >> 2; + } else { + *p_left_start = 0; + *p_left_end = window_center; + } + if (m->blockflag && !next) { + *p_right_start = (n*3 - f->blocksize_0) >> 2; + *p_right_end = (n*3 + f->blocksize_0) >> 2; + } else { + *p_right_start = window_center; + *p_right_end = n; + } + + return TRUE; +} + +static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start, int left_end, int right_start, int right_end, int *p_left) +{ + Mapping *map; + int i,j,k,n,n2; + int zero_channel[256]; + int really_zero_channel[256]; + +// WINDOWING + + n = f->blocksize[m->blockflag]; + map = &f->mapping[m->mapping]; + +// FLOORS + n2 = n >> 1; + + CHECK(f); + + for (i=0; i < f->channels; ++i) { + int s = map->chan[i].mux, floor; + zero_channel[i] = FALSE; + floor = map->submap_floor[s]; + if (f->floor_types[floor] == 0) { + return error(f, VORBIS_invalid_stream); + } else { + Floor1 *g = &f->floor_config[floor].floor1; + if (get_bits(f, 1)) { + short *finalY; + uint8 step2_flag[256]; + static int range_list[4] = { 256, 128, 86, 64 }; + int range = range_list[g->floor1_multiplier-1]; + int offset = 2; + finalY = f->finalY[i]; + finalY[0] = get_bits(f, ilog(range)-1); + finalY[1] = get_bits(f, ilog(range)-1); + for (j=0; j < g->partitions; ++j) { + int pclass = g->partition_class_list[j]; + int cdim = g->class_dimensions[pclass]; + int cbits = g->class_subclasses[pclass]; + int csub = (1 << cbits)-1; + int cval = 0; + if (cbits) { + Codebook *c = f->codebooks + g->class_masterbooks[pclass]; + DECODE(cval,f,c); + } + for (k=0; k < cdim; ++k) { + int book = g->subclass_books[pclass][cval & csub]; + cval = cval >> cbits; + if (book >= 0) { + int temp; + Codebook *c = f->codebooks + book; + DECODE(temp,f,c); + finalY[offset++] = temp; + } else + finalY[offset++] = 0; + } + } + if (f->valid_bits == INVALID_BITS) goto error; // behavior according to spec + step2_flag[0] = step2_flag[1] = 1; + for (j=2; j < g->values; ++j) { + int low, high, pred, highroom, lowroom, room, val; + low = g->neighbors[j][0]; + high = g->neighbors[j][1]; + //neighbors(g->Xlist, j, &low, &high); + pred = predict_point(g->Xlist[j], g->Xlist[low], g->Xlist[high], finalY[low], finalY[high]); + val = finalY[j]; + highroom = range - pred; + lowroom = pred; + if (highroom < lowroom) + room = highroom * 2; + else + room = lowroom * 2; + if (val) { + step2_flag[low] = step2_flag[high] = 1; + step2_flag[j] = 1; + if (val >= room) + if (highroom > lowroom) + finalY[j] = val - lowroom + pred; + else + finalY[j] = pred - val + highroom - 1; + else + if (val & 1) + finalY[j] = pred - ((val+1)>>1); + else + finalY[j] = pred + (val>>1); + } else { + step2_flag[j] = 0; + finalY[j] = pred; + } + } + +#ifdef STB_VORBIS_NO_DEFER_FLOOR + do_floor(f, map, i, n, f->floor_buffers[i], finalY, step2_flag); +#else + // defer final floor computation until _after_ residue + for (j=0; j < g->values; ++j) { + if (!step2_flag[j]) + finalY[j] = -1; + } +#endif + } else { + error: + zero_channel[i] = TRUE; + } + // So we just defer everything else to later + + // at this point we've decoded the floor into buffer + } + } + CHECK(f); + // at this point we've decoded all floors + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + + // re-enable coupled channels if necessary + memcpy(really_zero_channel, zero_channel, sizeof(really_zero_channel[0]) * f->channels); + for (i=0; i < map->coupling_steps; ++i) + if (!zero_channel[map->chan[i].magnitude] || !zero_channel[map->chan[i].angle]) { + zero_channel[map->chan[i].magnitude] = zero_channel[map->chan[i].angle] = FALSE; + } + + CHECK(f); +// RESIDUE DECODE + for (i=0; i < map->submaps; ++i) { + float *residue_buffers[STB_VORBIS_MAX_CHANNELS]; + int r; + uint8 do_not_decode[256]; + int ch = 0; + for (j=0; j < f->channels; ++j) { + if (map->chan[j].mux == i) { + if (zero_channel[j]) { + do_not_decode[ch] = TRUE; + residue_buffers[ch] = NULL; + } else { + do_not_decode[ch] = FALSE; + residue_buffers[ch] = f->channel_buffers[j]; + } + ++ch; + } + } + r = map->submap_residue[i]; + decode_residue(f, residue_buffers, ch, n2, r, do_not_decode); + } + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + CHECK(f); + +// INVERSE COUPLING + for (i = map->coupling_steps-1; i >= 0; --i) { + int n2 = n >> 1; + float *m = f->channel_buffers[map->chan[i].magnitude]; + float *a = f->channel_buffers[map->chan[i].angle ]; + for (j=0; j < n2; ++j) { + float a2,m2; + if (m[j] > 0) + if (a[j] > 0) + m2 = m[j], a2 = m[j] - a[j]; + else + a2 = m[j], m2 = m[j] + a[j]; + else + if (a[j] > 0) + m2 = m[j], a2 = m[j] + a[j]; + else + a2 = m[j], m2 = m[j] - a[j]; + m[j] = m2; + a[j] = a2; + } + } + CHECK(f); + + // finish decoding the floors +#ifndef STB_VORBIS_NO_DEFER_FLOOR + for (i=0; i < f->channels; ++i) { + if (really_zero_channel[i]) { + memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); + } else { + do_floor(f, map, i, n, f->channel_buffers[i], f->finalY[i], NULL); + } + } +#else + for (i=0; i < f->channels; ++i) { + if (really_zero_channel[i]) { + memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); + } else { + for (j=0; j < n2; ++j) + f->channel_buffers[i][j] *= f->floor_buffers[i][j]; + } + } +#endif + +// INVERSE MDCT + CHECK(f); + for (i=0; i < f->channels; ++i) + inverse_mdct(f->channel_buffers[i], n, f, m->blockflag); + CHECK(f); + + // this shouldn't be necessary, unless we exited on an error + // and want to flush to get to the next packet + flush_packet(f); + + if (f->first_decode) { + // assume we start so first non-discarded sample is sample 0 + // this isn't to spec, but spec would require us to read ahead + // and decode the size of all current frames--could be done, + // but presumably it's not a commonly used feature + f->current_loc = -n2; // start of first frame is positioned for discard + // we might have to discard samples "from" the next frame too, + // if we're lapping a large block then a small at the start? + f->discard_samples_deferred = n - right_end; + f->current_loc_valid = TRUE; + f->first_decode = FALSE; + } else if (f->discard_samples_deferred) { + if (f->discard_samples_deferred >= right_start - left_start) { + f->discard_samples_deferred -= (right_start - left_start); + left_start = right_start; + *p_left = left_start; + } else { + left_start += f->discard_samples_deferred; + *p_left = left_start; + f->discard_samples_deferred = 0; + } + } else if (f->previous_length == 0 && f->current_loc_valid) { + // we're recovering from a seek... that means we're going to discard + // the samples from this packet even though we know our position from + // the last page header, so we need to update the position based on + // the discarded samples here + // but wait, the code below is going to add this in itself even + // on a discard, so we don't need to do it here... + } + + // check if we have ogg information about the sample # for this packet + if (f->last_seg_which == f->end_seg_with_known_loc) { + // if we have a valid current loc, and this is final: + if (f->current_loc_valid && (f->page_flag & PAGEFLAG_last_page)) { + uint32 current_end = f->known_loc_for_packet; + // then let's infer the size of the (probably) short final frame + if (current_end < f->current_loc + (right_end-left_start)) { + if (current_end < f->current_loc) { + // negative truncation, that's impossible! + *len = 0; + } else { + *len = current_end - f->current_loc; + } + *len += left_start; // this doesn't seem right, but has no ill effect on my test files + if (*len > right_end) *len = right_end; // this should never happen + f->current_loc += *len; + return TRUE; + } + } + // otherwise, just set our sample loc + // guess that the ogg granule pos refers to the _middle_ of the + // last frame? + // set f->current_loc to the position of left_start + f->current_loc = f->known_loc_for_packet - (n2-left_start); + f->current_loc_valid = TRUE; + } + if (f->current_loc_valid) + f->current_loc += (right_start - left_start); + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + *len = right_end; // ignore samples after the window goes to 0 + CHECK(f); + + return TRUE; +} + +static int vorbis_decode_packet(vorb *f, int *len, int *p_left, int *p_right) +{ + int mode, left_end, right_end; + if (!vorbis_decode_initial(f, p_left, &left_end, p_right, &right_end, &mode)) return 0; + return vorbis_decode_packet_rest(f, len, f->mode_config + mode, *p_left, left_end, *p_right, right_end, p_left); +} + +static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right) +{ + int prev,i,j; + // we use right&left (the start of the right- and left-window sin()-regions) + // to determine how much to return, rather than inferring from the rules + // (same result, clearer code); 'left' indicates where our sin() window + // starts, therefore where the previous window's right edge starts, and + // therefore where to start mixing from the previous buffer. 'right' + // indicates where our sin() ending-window starts, therefore that's where + // we start saving, and where our returned-data ends. + + // mixin from previous window + if (f->previous_length) { + int i,j, n = f->previous_length; + float *w = get_window(f, n); + for (i=0; i < f->channels; ++i) { + for (j=0; j < n; ++j) + f->channel_buffers[i][left+j] = + f->channel_buffers[i][left+j]*w[ j] + + f->previous_window[i][ j]*w[n-1-j]; + } + } + + prev = f->previous_length; + + // last half of this data becomes previous window + f->previous_length = len - right; + + // @OPTIMIZE: could avoid this copy by double-buffering the + // output (flipping previous_window with channel_buffers), but + // then previous_window would have to be 2x as large, and + // channel_buffers couldn't be temp mem (although they're NOT + // currently temp mem, they could be (unless we want to level + // performance by spreading out the computation)) + for (i=0; i < f->channels; ++i) + for (j=0; right+j < len; ++j) + f->previous_window[i][j] = f->channel_buffers[i][right+j]; + + if (!prev) + // there was no previous packet, so this data isn't valid... + // this isn't entirely true, only the would-have-overlapped data + // isn't valid, but this seems to be what the spec requires + return 0; + + // truncate a short frame + if (len < right) right = len; + + f->samples_output += right-left; + + return right - left; +} + +static int vorbis_pump_first_frame(stb_vorbis *f) +{ + int len, right, left, res; + res = vorbis_decode_packet(f, &len, &left, &right); + if (res) + vorbis_finish_frame(f, len, left, right); + return res; +} + +#ifndef STB_VORBIS_NO_PUSHDATA_API +static int is_whole_packet_present(stb_vorbis *f, int end_page) +{ + // make sure that we have the packet available before continuing... + // this requires a full ogg parse, but we know we can fetch from f->stream + + // instead of coding this out explicitly, we could save the current read state, + // read the next packet with get8() until end-of-packet, check f->eof, then + // reset the state? but that would be slower, esp. since we'd have over 256 bytes + // of state to restore (primarily the page segment table) + + int s = f->next_seg, first = TRUE; + uint8 *p = f->stream; + + if (s != -1) { // if we're not starting the packet with a 'continue on next page' flag + for (; s < f->segment_count; ++s) { + p += f->segments[s]; + if (f->segments[s] < 255) // stop at first short segment + break; + } + // either this continues, or it ends it... + if (end_page) + if (s < f->segment_count-1) return error(f, VORBIS_invalid_stream); + if (s == f->segment_count) + s = -1; // set 'crosses page' flag + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + first = FALSE; + } + for (; s == -1;) { + uint8 *q; + int n; + + // check that we have the page header ready + if (p + 26 >= f->stream_end) return error(f, VORBIS_need_more_data); + // validate the page + if (memcmp(p, ogg_page_header, 4)) return error(f, VORBIS_invalid_stream); + if (p[4] != 0) return error(f, VORBIS_invalid_stream); + if (first) { // the first segment must NOT have 'continued_packet', later ones MUST + if (f->previous_length) + if ((p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); + // if no previous length, we're resynching, so we can come in on a continued-packet, + // which we'll just drop + } else { + if (!(p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); + } + n = p[26]; // segment counts + q = p+27; // q points to segment table + p = q + n; // advance past header + // make sure we've read the segment table + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + for (s=0; s < n; ++s) { + p += q[s]; + if (q[s] < 255) + break; + } + if (end_page) + if (s < n-1) return error(f, VORBIS_invalid_stream); + if (s == n) + s = -1; // set 'crosses page' flag + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + first = FALSE; + } + return TRUE; +} +#endif // !STB_VORBIS_NO_PUSHDATA_API + +static int start_decoder(vorb *f) +{ + uint8 header[6], x,y; + int len,i,j,k, max_submaps = 0; + int longest_floorlist=0; + + // first page, first packet + + if (!start_page(f)) return FALSE; + // validate page flag + if (!(f->page_flag & PAGEFLAG_first_page)) return error(f, VORBIS_invalid_first_page); + if (f->page_flag & PAGEFLAG_last_page) return error(f, VORBIS_invalid_first_page); + if (f->page_flag & PAGEFLAG_continued_packet) return error(f, VORBIS_invalid_first_page); + // check for expected packet length + if (f->segment_count != 1) return error(f, VORBIS_invalid_first_page); + if (f->segments[0] != 30) return error(f, VORBIS_invalid_first_page); + // read packet + // check packet header + if (get8(f) != VORBIS_packet_id) return error(f, VORBIS_invalid_first_page); + if (!getn(f, header, 6)) return error(f, VORBIS_unexpected_eof); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_first_page); + // vorbis_version + if (get32(f) != 0) return error(f, VORBIS_invalid_first_page); + f->channels = get8(f); if (!f->channels) return error(f, VORBIS_invalid_first_page); + if (f->channels > STB_VORBIS_MAX_CHANNELS) return error(f, VORBIS_too_many_channels); + f->sample_rate = get32(f); if (!f->sample_rate) return error(f, VORBIS_invalid_first_page); + get32(f); // bitrate_maximum + get32(f); // bitrate_nominal + get32(f); // bitrate_minimum + x = get8(f); + { + int log0,log1; + log0 = x & 15; + log1 = x >> 4; + f->blocksize_0 = 1 << log0; + f->blocksize_1 = 1 << log1; + if (log0 < 6 || log0 > 13) return error(f, VORBIS_invalid_setup); + if (log1 < 6 || log1 > 13) return error(f, VORBIS_invalid_setup); + if (log0 > log1) return error(f, VORBIS_invalid_setup); + } + + // framing_flag + x = get8(f); + if (!(x & 1)) return error(f, VORBIS_invalid_first_page); + + // second packet! + if (!start_page(f)) return FALSE; + + if (!start_packet(f)) return FALSE; + do { + len = next_segment(f); + skip(f, len); + f->bytes_in_seg = 0; + } while (len); + + // third packet! + if (!start_packet(f)) return FALSE; + + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (IS_PUSH_MODE(f)) { + if (!is_whole_packet_present(f, TRUE)) { + // convert error in ogg header to write type + if (f->error == VORBIS_invalid_stream) + f->error = VORBIS_invalid_setup; + return FALSE; + } + } + #endif + + crc32_init(); // always init it, to avoid multithread race conditions + + if (get8_packet(f) != VORBIS_packet_setup) return error(f, VORBIS_invalid_setup); + for (i=0; i < 6; ++i) header[i] = get8_packet(f); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); + + // codebooks + + f->codebook_count = get_bits(f,8) + 1; + f->codebooks = (Codebook *) setup_malloc(f, sizeof(*f->codebooks) * f->codebook_count); + if (f->codebooks == NULL) return error(f, VORBIS_outofmem); + memset(f->codebooks, 0, sizeof(*f->codebooks) * f->codebook_count); + for (i=0; i < f->codebook_count; ++i) { + uint32 *values; + int ordered, sorted_count; + int total=0; + uint8 *lengths; + Codebook *c = f->codebooks+i; + CHECK(f); + x = get_bits(f, 8); if (x != 0x42) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); if (x != 0x43) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); if (x != 0x56) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); + c->dimensions = (get_bits(f, 8)<<8) + x; + x = get_bits(f, 8); + y = get_bits(f, 8); + c->entries = (get_bits(f, 8)<<16) + (y<<8) + x; + ordered = get_bits(f,1); + c->sparse = ordered ? 0 : get_bits(f,1); + + if (c->dimensions == 0 && c->entries != 0) return error(f, VORBIS_invalid_setup); + + if (c->sparse) + lengths = (uint8 *) setup_temp_malloc(f, c->entries); + else + lengths = c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); + + if (!lengths) return error(f, VORBIS_outofmem); + + if (ordered) { + int current_entry = 0; + int current_length = get_bits(f,5) + 1; + while (current_entry < c->entries) { + int limit = c->entries - current_entry; + int n = get_bits(f, ilog(limit)); + if (current_entry + n > (int) c->entries) { return error(f, VORBIS_invalid_setup); } + memset(lengths + current_entry, current_length, n); + current_entry += n; + ++current_length; + } + } else { + for (j=0; j < c->entries; ++j) { + int present = c->sparse ? get_bits(f,1) : 1; + if (present) { + lengths[j] = get_bits(f, 5) + 1; + ++total; + if (lengths[j] == 32) + return error(f, VORBIS_invalid_setup); + } else { + lengths[j] = NO_CODE; + } + } + } + + if (c->sparse && total >= c->entries >> 2) { + // convert sparse items to non-sparse! + if (c->entries > (int) f->setup_temp_memory_required) + f->setup_temp_memory_required = c->entries; + + c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); + if (c->codeword_lengths == NULL) return error(f, VORBIS_outofmem); + memcpy(c->codeword_lengths, lengths, c->entries); + setup_temp_free(f, lengths, c->entries); // note this is only safe if there have been no intervening temp mallocs! + lengths = c->codeword_lengths; + c->sparse = 0; + } + + // compute the size of the sorted tables + if (c->sparse) { + sorted_count = total; + } else { + sorted_count = 0; + #ifndef STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH + for (j=0; j < c->entries; ++j) + if (lengths[j] > STB_VORBIS_FAST_HUFFMAN_LENGTH && lengths[j] != NO_CODE) + ++sorted_count; + #endif + } + + c->sorted_entries = sorted_count; + values = NULL; + + CHECK(f); + if (!c->sparse) { + c->codewords = (uint32 *) setup_malloc(f, sizeof(c->codewords[0]) * c->entries); + if (!c->codewords) return error(f, VORBIS_outofmem); + } else { + unsigned int size; + if (c->sorted_entries) { + c->codeword_lengths = (uint8 *) setup_malloc(f, c->sorted_entries); + if (!c->codeword_lengths) return error(f, VORBIS_outofmem); + c->codewords = (uint32 *) setup_temp_malloc(f, sizeof(*c->codewords) * c->sorted_entries); + if (!c->codewords) return error(f, VORBIS_outofmem); + values = (uint32 *) setup_temp_malloc(f, sizeof(*values) * c->sorted_entries); + if (!values) return error(f, VORBIS_outofmem); + } + size = c->entries + (sizeof(*c->codewords) + sizeof(*values)) * c->sorted_entries; + if (size > f->setup_temp_memory_required) + f->setup_temp_memory_required = size; + } + + if (!compute_codewords(c, lengths, c->entries, values)) { + if (c->sparse) setup_temp_free(f, values, 0); + return error(f, VORBIS_invalid_setup); + } + + if (c->sorted_entries) { + // allocate an extra slot for sentinels + c->sorted_codewords = (uint32 *) setup_malloc(f, sizeof(*c->sorted_codewords) * (c->sorted_entries+1)); + if (c->sorted_codewords == NULL) return error(f, VORBIS_outofmem); + // allocate an extra slot at the front so that c->sorted_values[-1] is defined + // so that we can catch that case without an extra if + c->sorted_values = ( int *) setup_malloc(f, sizeof(*c->sorted_values ) * (c->sorted_entries+1)); + if (c->sorted_values == NULL) return error(f, VORBIS_outofmem); + ++c->sorted_values; + c->sorted_values[-1] = -1; + compute_sorted_huffman(c, lengths, values); + } + + if (c->sparse) { + setup_temp_free(f, values, sizeof(*values)*c->sorted_entries); + setup_temp_free(f, c->codewords, sizeof(*c->codewords)*c->sorted_entries); + setup_temp_free(f, lengths, c->entries); + c->codewords = NULL; + } + + compute_accelerated_huffman(c); + + CHECK(f); + c->lookup_type = get_bits(f, 4); + if (c->lookup_type > 2) return error(f, VORBIS_invalid_setup); + if (c->lookup_type > 0) { + uint16 *mults; + c->minimum_value = float32_unpack(get_bits(f, 32)); + c->delta_value = float32_unpack(get_bits(f, 32)); + c->value_bits = get_bits(f, 4)+1; + c->sequence_p = get_bits(f,1); + if (c->lookup_type == 1) { + c->lookup_values = lookup1_values(c->entries, c->dimensions); + } else { + c->lookup_values = c->entries * c->dimensions; + } + if (c->lookup_values == 0) return error(f, VORBIS_invalid_setup); + mults = (uint16 *) setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values); + if (mults == NULL) return error(f, VORBIS_outofmem); + for (j=0; j < (int) c->lookup_values; ++j) { + int q = get_bits(f, c->value_bits); + if (q == EOP) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_invalid_setup); } + mults[j] = q; + } + +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int len, sparse = c->sparse; + float last=0; + // pre-expand the lookup1-style multiplicands, to avoid a divide in the inner loop + if (sparse) { + if (c->sorted_entries == 0) goto skip; + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->sorted_entries * c->dimensions); + } else + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->entries * c->dimensions); + if (c->multiplicands == NULL) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } + len = sparse ? c->sorted_entries : c->entries; + for (j=0; j < len; ++j) { + unsigned int z = sparse ? c->sorted_values[j] : j; + unsigned int div=1; + for (k=0; k < c->dimensions; ++k) { + int off = (z / div) % c->lookup_values; + float val = mults[off]; + val = mults[off]*c->delta_value + c->minimum_value + last; + c->multiplicands[j*c->dimensions + k] = val; + if (c->sequence_p) + last = val; + if (k+1 < c->dimensions) { + if (div > UINT_MAX / (unsigned int) c->lookup_values) { + setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); + return error(f, VORBIS_invalid_setup); + } + div *= c->lookup_values; + } + } + } + c->lookup_type = 2; + } + else +#endif + { + float last=0; + CHECK(f); + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->lookup_values); + if (c->multiplicands == NULL) { setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } + for (j=0; j < (int) c->lookup_values; ++j) { + float val = mults[j] * c->delta_value + c->minimum_value + last; + c->multiplicands[j] = val; + if (c->sequence_p) + last = val; + } + } +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + skip:; +#endif + setup_temp_free(f, mults, sizeof(mults[0])*c->lookup_values); + + CHECK(f); + } + CHECK(f); + } + + // time domain transfers (notused) + + x = get_bits(f, 6) + 1; + for (i=0; i < x; ++i) { + uint32 z = get_bits(f, 16); + if (z != 0) return error(f, VORBIS_invalid_setup); + } + + // Floors + f->floor_count = get_bits(f, 6)+1; + f->floor_config = (Floor *) setup_malloc(f, f->floor_count * sizeof(*f->floor_config)); + if (f->floor_config == NULL) return error(f, VORBIS_outofmem); + for (i=0; i < f->floor_count; ++i) { + f->floor_types[i] = get_bits(f, 16); + if (f->floor_types[i] > 1) return error(f, VORBIS_invalid_setup); + if (f->floor_types[i] == 0) { + Floor0 *g = &f->floor_config[i].floor0; + g->order = get_bits(f,8); + g->rate = get_bits(f,16); + g->bark_map_size = get_bits(f,16); + g->amplitude_bits = get_bits(f,6); + g->amplitude_offset = get_bits(f,8); + g->number_of_books = get_bits(f,4) + 1; + for (j=0; j < g->number_of_books; ++j) + g->book_list[j] = get_bits(f,8); + return error(f, VORBIS_feature_not_supported); + } else { + stbv__floor_ordering p[31*8+2]; + Floor1 *g = &f->floor_config[i].floor1; + int max_class = -1; + g->partitions = get_bits(f, 5); + for (j=0; j < g->partitions; ++j) { + g->partition_class_list[j] = get_bits(f, 4); + if (g->partition_class_list[j] > max_class) + max_class = g->partition_class_list[j]; + } + for (j=0; j <= max_class; ++j) { + g->class_dimensions[j] = get_bits(f, 3)+1; + g->class_subclasses[j] = get_bits(f, 2); + if (g->class_subclasses[j]) { + g->class_masterbooks[j] = get_bits(f, 8); + if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } + for (k=0; k < 1 << g->class_subclasses[j]; ++k) { + g->subclass_books[j][k] = get_bits(f,8)-1; + if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } + } + g->floor1_multiplier = get_bits(f,2)+1; + g->rangebits = get_bits(f,4); + g->Xlist[0] = 0; + g->Xlist[1] = 1 << g->rangebits; + g->values = 2; + for (j=0; j < g->partitions; ++j) { + int c = g->partition_class_list[j]; + for (k=0; k < g->class_dimensions[c]; ++k) { + g->Xlist[g->values] = get_bits(f, g->rangebits); + ++g->values; + } + } + // precompute the sorting + for (j=0; j < g->values; ++j) { + p[j].x = g->Xlist[j]; + p[j].id = j; + } + qsort(p, g->values, sizeof(p[0]), point_compare); + for (j=0; j < g->values; ++j) + g->sorted_order[j] = (uint8) p[j].id; + // precompute the neighbors + for (j=2; j < g->values; ++j) { + int low,hi; + neighbors(g->Xlist, j, &low,&hi); + g->neighbors[j][0] = low; + g->neighbors[j][1] = hi; + } + + if (g->values > longest_floorlist) + longest_floorlist = g->values; + } + } + + // Residue + f->residue_count = get_bits(f, 6)+1; + f->residue_config = (Residue *) setup_malloc(f, f->residue_count * sizeof(f->residue_config[0])); + if (f->residue_config == NULL) return error(f, VORBIS_outofmem); + memset(f->residue_config, 0, f->residue_count * sizeof(f->residue_config[0])); + for (i=0; i < f->residue_count; ++i) { + uint8 residue_cascade[64]; + Residue *r = f->residue_config+i; + f->residue_types[i] = get_bits(f, 16); + if (f->residue_types[i] > 2) return error(f, VORBIS_invalid_setup); + r->begin = get_bits(f, 24); + r->end = get_bits(f, 24); + if (r->end < r->begin) return error(f, VORBIS_invalid_setup); + r->part_size = get_bits(f,24)+1; + r->classifications = get_bits(f,6)+1; + r->classbook = get_bits(f,8); + if (r->classbook >= f->codebook_count) return error(f, VORBIS_invalid_setup); + for (j=0; j < r->classifications; ++j) { + uint8 high_bits=0; + uint8 low_bits=get_bits(f,3); + if (get_bits(f,1)) + high_bits = get_bits(f,5); + residue_cascade[j] = high_bits*8 + low_bits; + } + r->residue_books = (short (*)[8]) setup_malloc(f, sizeof(r->residue_books[0]) * r->classifications); + if (r->residue_books == NULL) return error(f, VORBIS_outofmem); + for (j=0; j < r->classifications; ++j) { + for (k=0; k < 8; ++k) { + if (residue_cascade[j] & (1 << k)) { + r->residue_books[j][k] = get_bits(f, 8); + if (r->residue_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } else { + r->residue_books[j][k] = -1; + } + } + } + // precompute the classifications[] array to avoid inner-loop mod/divide + // call it 'classdata' since we already have r->classifications + r->classdata = (uint8 **) setup_malloc(f, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); + if (!r->classdata) return error(f, VORBIS_outofmem); + memset(r->classdata, 0, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); + for (j=0; j < f->codebooks[r->classbook].entries; ++j) { + int classwords = f->codebooks[r->classbook].dimensions; + int temp = j; + r->classdata[j] = (uint8 *) setup_malloc(f, sizeof(r->classdata[j][0]) * classwords); + if (r->classdata[j] == NULL) return error(f, VORBIS_outofmem); + for (k=classwords-1; k >= 0; --k) { + r->classdata[j][k] = temp % r->classifications; + temp /= r->classifications; + } + } + } + + f->mapping_count = get_bits(f,6)+1; + f->mapping = (Mapping *) setup_malloc(f, f->mapping_count * sizeof(*f->mapping)); + if (f->mapping == NULL) return error(f, VORBIS_outofmem); + memset(f->mapping, 0, f->mapping_count * sizeof(*f->mapping)); + for (i=0; i < f->mapping_count; ++i) { + Mapping *m = f->mapping + i; + int mapping_type = get_bits(f,16); + if (mapping_type != 0) return error(f, VORBIS_invalid_setup); + m->chan = (MappingChannel *) setup_malloc(f, f->channels * sizeof(*m->chan)); + if (m->chan == NULL) return error(f, VORBIS_outofmem); + if (get_bits(f,1)) + m->submaps = get_bits(f,4)+1; + else + m->submaps = 1; + if (m->submaps > max_submaps) + max_submaps = m->submaps; + if (get_bits(f,1)) { + m->coupling_steps = get_bits(f,8)+1; + for (k=0; k < m->coupling_steps; ++k) { + m->chan[k].magnitude = get_bits(f, ilog(f->channels-1)); + m->chan[k].angle = get_bits(f, ilog(f->channels-1)); + if (m->chan[k].magnitude >= f->channels) return error(f, VORBIS_invalid_setup); + if (m->chan[k].angle >= f->channels) return error(f, VORBIS_invalid_setup); + if (m->chan[k].magnitude == m->chan[k].angle) return error(f, VORBIS_invalid_setup); + } + } else + m->coupling_steps = 0; + + // reserved field + if (get_bits(f,2)) return error(f, VORBIS_invalid_setup); + if (m->submaps > 1) { + for (j=0; j < f->channels; ++j) { + m->chan[j].mux = get_bits(f, 4); + if (m->chan[j].mux >= m->submaps) return error(f, VORBIS_invalid_setup); + } + } else + // @SPECIFICATION: this case is missing from the spec + for (j=0; j < f->channels; ++j) + m->chan[j].mux = 0; + + for (j=0; j < m->submaps; ++j) { + get_bits(f,8); // discard + m->submap_floor[j] = get_bits(f,8); + m->submap_residue[j] = get_bits(f,8); + if (m->submap_floor[j] >= f->floor_count) return error(f, VORBIS_invalid_setup); + if (m->submap_residue[j] >= f->residue_count) return error(f, VORBIS_invalid_setup); + } + } + + // Modes + f->mode_count = get_bits(f, 6)+1; + for (i=0; i < f->mode_count; ++i) { + Mode *m = f->mode_config+i; + m->blockflag = get_bits(f,1); + m->windowtype = get_bits(f,16); + m->transformtype = get_bits(f,16); + m->mapping = get_bits(f,8); + if (m->windowtype != 0) return error(f, VORBIS_invalid_setup); + if (m->transformtype != 0) return error(f, VORBIS_invalid_setup); + if (m->mapping >= f->mapping_count) return error(f, VORBIS_invalid_setup); + } + + flush_packet(f); + + f->previous_length = 0; + + for (i=0; i < f->channels; ++i) { + f->channel_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1); + f->previous_window[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); + f->finalY[i] = (int16 *) setup_malloc(f, sizeof(int16) * longest_floorlist); + if (f->channel_buffers[i] == NULL || f->previous_window[i] == NULL || f->finalY[i] == NULL) return error(f, VORBIS_outofmem); + memset(f->channel_buffers[i], 0, sizeof(float) * f->blocksize_1); + #ifdef STB_VORBIS_NO_DEFER_FLOOR + f->floor_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); + if (f->floor_buffers[i] == NULL) return error(f, VORBIS_outofmem); + #endif + } + + if (!init_blocksize(f, 0, f->blocksize_0)) return FALSE; + if (!init_blocksize(f, 1, f->blocksize_1)) return FALSE; + f->blocksize[0] = f->blocksize_0; + f->blocksize[1] = f->blocksize_1; + +#ifdef STB_VORBIS_DIVIDE_TABLE + if (integer_divide_table[1][1]==0) + for (i=0; i < DIVTAB_NUMER; ++i) + for (j=1; j < DIVTAB_DENOM; ++j) + integer_divide_table[i][j] = i / j; +#endif + + // compute how much temporary memory is needed + + // 1. + { + uint32 imdct_mem = (f->blocksize_1 * sizeof(float) >> 1); + uint32 classify_mem; + int i,max_part_read=0; + for (i=0; i < f->residue_count; ++i) { + Residue *r = f->residue_config + i; + unsigned int actual_size = f->blocksize_1 / 2; + unsigned int limit_r_begin = r->begin < actual_size ? r->begin : actual_size; + unsigned int limit_r_end = r->end < actual_size ? r->end : actual_size; + int n_read = limit_r_end - limit_r_begin; + int part_read = n_read / r->part_size; + if (part_read > max_part_read) + max_part_read = part_read; + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(uint8 *)); + #else + classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(int *)); + #endif + + // maximum reasonable partition size is f->blocksize_1 + + f->temp_memory_required = classify_mem; + if (imdct_mem > f->temp_memory_required) + f->temp_memory_required = imdct_mem; + } + + f->first_decode = TRUE; + + if (f->alloc.alloc_buffer) { + assert(f->temp_offset == f->alloc.alloc_buffer_length_in_bytes); + // check if there's enough temp memory so we don't error later + if (f->setup_offset + sizeof(*f) + f->temp_memory_required > (unsigned) f->temp_offset) + return error(f, VORBIS_outofmem); + } + + f->first_audio_page_offset = stb_vorbis_get_file_offset(f); + + return TRUE; +} + +static void vorbis_deinit(stb_vorbis *p) +{ + int i,j; + if (p->residue_config) { + for (i=0; i < p->residue_count; ++i) { + Residue *r = p->residue_config+i; + if (r->classdata) { + for (j=0; j < p->codebooks[r->classbook].entries; ++j) + setup_free(p, r->classdata[j]); + setup_free(p, r->classdata); + } + setup_free(p, r->residue_books); + } + } + + if (p->codebooks) { + CHECK(p); + for (i=0; i < p->codebook_count; ++i) { + Codebook *c = p->codebooks + i; + setup_free(p, c->codeword_lengths); + setup_free(p, c->multiplicands); + setup_free(p, c->codewords); + setup_free(p, c->sorted_codewords); + // c->sorted_values[-1] is the first entry in the array + setup_free(p, c->sorted_values ? c->sorted_values-1 : NULL); + } + setup_free(p, p->codebooks); + } + setup_free(p, p->floor_config); + setup_free(p, p->residue_config); + if (p->mapping) { + for (i=0; i < p->mapping_count; ++i) + setup_free(p, p->mapping[i].chan); + setup_free(p, p->mapping); + } + CHECK(p); + for (i=0; i < p->channels && i < STB_VORBIS_MAX_CHANNELS; ++i) { + setup_free(p, p->channel_buffers[i]); + setup_free(p, p->previous_window[i]); + #ifdef STB_VORBIS_NO_DEFER_FLOOR + setup_free(p, p->floor_buffers[i]); + #endif + setup_free(p, p->finalY[i]); + } + for (i=0; i < 2; ++i) { + setup_free(p, p->A[i]); + setup_free(p, p->B[i]); + setup_free(p, p->C[i]); + setup_free(p, p->window[i]); + setup_free(p, p->bit_reverse[i]); + } + #ifndef STB_VORBIS_NO_STDIO + if (p->close_on_free) fclose(p->f); + #endif +} + +void stb_vorbis_close(stb_vorbis *p) +{ + if (p == NULL) return; + vorbis_deinit(p); + setup_free(p,p); +} + +static void vorbis_init(stb_vorbis *p, const stb_vorbis_alloc *z) +{ + memset(p, 0, sizeof(*p)); // NULL out all malloc'd pointers to start + if (z) { + p->alloc = *z; + p->alloc.alloc_buffer_length_in_bytes = (p->alloc.alloc_buffer_length_in_bytes+3) & ~3; + p->temp_offset = p->alloc.alloc_buffer_length_in_bytes; + } + p->eof = 0; + p->error = VORBIS__no_error; + p->stream = NULL; + p->codebooks = NULL; + p->page_crc_tests = -1; + #ifndef STB_VORBIS_NO_STDIO + p->close_on_free = FALSE; + p->f = NULL; + #endif +} + +int stb_vorbis_get_sample_offset(stb_vorbis *f) +{ + if (f->current_loc_valid) + return f->current_loc; + else + return -1; +} + +stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f) +{ + stb_vorbis_info d; + d.channels = f->channels; + d.sample_rate = f->sample_rate; + d.setup_memory_required = f->setup_memory_required; + d.setup_temp_memory_required = f->setup_temp_memory_required; + d.temp_memory_required = f->temp_memory_required; + d.max_frame_size = f->blocksize_1 >> 1; + return d; +} + +int stb_vorbis_get_error(stb_vorbis *f) +{ + int e = f->error; + f->error = VORBIS__no_error; + return e; +} + +static stb_vorbis * vorbis_alloc(stb_vorbis *f) +{ + stb_vorbis *p = (stb_vorbis *) setup_malloc(f, sizeof(*p)); + return p; +} + +#ifndef STB_VORBIS_NO_PUSHDATA_API + +void stb_vorbis_flush_pushdata(stb_vorbis *f) +{ + f->previous_length = 0; + f->page_crc_tests = 0; + f->discard_samples_deferred = 0; + f->current_loc_valid = FALSE; + f->first_decode = FALSE; + f->samples_output = 0; + f->channel_buffer_start = 0; + f->channel_buffer_end = 0; +} + +static int vorbis_search_for_page_pushdata(vorb *f, uint8 *data, int data_len) +{ + int i,n; + for (i=0; i < f->page_crc_tests; ++i) + f->scan[i].bytes_done = 0; + + // if we have room for more scans, search for them first, because + // they may cause us to stop early if their header is incomplete + if (f->page_crc_tests < STB_VORBIS_PUSHDATA_CRC_COUNT) { + if (data_len < 4) return 0; + data_len -= 3; // need to look for 4-byte sequence, so don't miss + // one that straddles a boundary + for (i=0; i < data_len; ++i) { + if (data[i] == 0x4f) { + if (0==memcmp(data+i, ogg_page_header, 4)) { + int j,len; + uint32 crc; + // make sure we have the whole page header + if (i+26 >= data_len || i+27+data[i+26] >= data_len) { + // only read up to this page start, so hopefully we'll + // have the whole page header start next time + data_len = i; + break; + } + // ok, we have it all; compute the length of the page + len = 27 + data[i+26]; + for (j=0; j < data[i+26]; ++j) + len += data[i+27+j]; + // scan everything up to the embedded crc (which we must 0) + crc = 0; + for (j=0; j < 22; ++j) + crc = crc32_update(crc, data[i+j]); + // now process 4 0-bytes + for ( ; j < 26; ++j) + crc = crc32_update(crc, 0); + // len is the total number of bytes we need to scan + n = f->page_crc_tests++; + f->scan[n].bytes_left = len-j; + f->scan[n].crc_so_far = crc; + f->scan[n].goal_crc = data[i+22] + (data[i+23] << 8) + (data[i+24]<<16) + (data[i+25]<<24); + // if the last frame on a page is continued to the next, then + // we can't recover the sample_loc immediately + if (data[i+27+data[i+26]-1] == 255) + f->scan[n].sample_loc = ~0; + else + f->scan[n].sample_loc = data[i+6] + (data[i+7] << 8) + (data[i+ 8]<<16) + (data[i+ 9]<<24); + f->scan[n].bytes_done = i+j; + if (f->page_crc_tests == STB_VORBIS_PUSHDATA_CRC_COUNT) + break; + // keep going if we still have room for more + } + } + } + } + + for (i=0; i < f->page_crc_tests;) { + uint32 crc; + int j; + int n = f->scan[i].bytes_done; + int m = f->scan[i].bytes_left; + if (m > data_len - n) m = data_len - n; + // m is the bytes to scan in the current chunk + crc = f->scan[i].crc_so_far; + for (j=0; j < m; ++j) + crc = crc32_update(crc, data[n+j]); + f->scan[i].bytes_left -= m; + f->scan[i].crc_so_far = crc; + if (f->scan[i].bytes_left == 0) { + // does it match? + if (f->scan[i].crc_so_far == f->scan[i].goal_crc) { + // Houston, we have page + data_len = n+m; // consumption amount is wherever that scan ended + f->page_crc_tests = -1; // drop out of page scan mode + f->previous_length = 0; // decode-but-don't-output one frame + f->next_seg = -1; // start a new page + f->current_loc = f->scan[i].sample_loc; // set the current sample location + // to the amount we'd have decoded had we decoded this page + f->current_loc_valid = f->current_loc != ~0U; + return data_len; + } + // delete entry + f->scan[i] = f->scan[--f->page_crc_tests]; + } else { + ++i; + } + } + + return data_len; +} + +// return value: number of bytes we used +int stb_vorbis_decode_frame_pushdata( + stb_vorbis *f, // the file we're decoding + const uint8 *data, int data_len, // the memory available for decoding + int *channels, // place to write number of float * buffers + float ***output, // place to write float ** array of float * buffers + int *samples // place to write number of output samples + ) +{ + int i; + int len,right,left; + + if (!IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + if (f->page_crc_tests >= 0) { + *samples = 0; + return vorbis_search_for_page_pushdata(f, (uint8 *) data, data_len); + } + + f->stream = (uint8 *) data; + f->stream_end = (uint8 *) data + data_len; + f->error = VORBIS__no_error; + + // check that we have the entire packet in memory + if (!is_whole_packet_present(f, FALSE)) { + *samples = 0; + return 0; + } + + if (!vorbis_decode_packet(f, &len, &left, &right)) { + // save the actual error we encountered + enum STBVorbisError error = f->error; + if (error == VORBIS_bad_packet_type) { + // flush and resynch + f->error = VORBIS__no_error; + while (get8_packet(f) != EOP) + if (f->eof) break; + *samples = 0; + return (int) (f->stream - data); + } + if (error == VORBIS_continued_packet_flag_invalid) { + if (f->previous_length == 0) { + // we may be resynching, in which case it's ok to hit one + // of these; just discard the packet + f->error = VORBIS__no_error; + while (get8_packet(f) != EOP) + if (f->eof) break; + *samples = 0; + return (int) (f->stream - data); + } + } + // if we get an error while parsing, what to do? + // well, it DEFINITELY won't work to continue from where we are! + stb_vorbis_flush_pushdata(f); + // restore the error that actually made us bail + f->error = error; + *samples = 0; + return 1; + } + + // success! + len = vorbis_finish_frame(f, len, left, right); + for (i=0; i < f->channels; ++i) + f->outputs[i] = f->channel_buffers[i] + left; + + if (channels) *channels = f->channels; + *samples = len; + *output = f->outputs; + return (int) (f->stream - data); +} + +stb_vorbis *stb_vorbis_open_pushdata( + const unsigned char *data, int data_len, // the memory available for decoding + int *data_used, // only defined if result is not NULL + int *error, const stb_vorbis_alloc *alloc) +{ + stb_vorbis *f, p; + vorbis_init(&p, alloc); + p.stream = (uint8 *) data; + p.stream_end = (uint8 *) data + data_len; + p.push_mode = TRUE; + if (!start_decoder(&p)) { + if (p.eof) + *error = VORBIS_need_more_data; + else + *error = p.error; + return NULL; + } + f = vorbis_alloc(&p); + if (f) { + *f = p; + *data_used = (int) (f->stream - data); + *error = 0; + return f; + } else { + vorbis_deinit(&p); + return NULL; + } +} +#endif // STB_VORBIS_NO_PUSHDATA_API + +unsigned int stb_vorbis_get_file_offset(stb_vorbis *f) +{ + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (f->push_mode) return 0; + #endif + if (USE_MEMORY(f)) return (unsigned int) (f->stream - f->stream_start); + #ifndef STB_VORBIS_NO_STDIO + return (unsigned int) (ftell(f->f) - f->f_start); + #endif +} + +#ifndef STB_VORBIS_NO_PULLDATA_API +// +// DATA-PULLING API +// + +static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last) +{ + for(;;) { + int n; + if (f->eof) return 0; + n = get8(f); + if (n == 0x4f) { // page header candidate + unsigned int retry_loc = stb_vorbis_get_file_offset(f); + int i; + // check if we're off the end of a file_section stream + if (retry_loc - 25 > f->stream_len) + return 0; + // check the rest of the header + for (i=1; i < 4; ++i) + if (get8(f) != ogg_page_header[i]) + break; + if (f->eof) return 0; + if (i == 4) { + uint8 header[27]; + uint32 i, crc, goal, len; + for (i=0; i < 4; ++i) + header[i] = ogg_page_header[i]; + for (; i < 27; ++i) + header[i] = get8(f); + if (f->eof) return 0; + if (header[4] != 0) goto invalid; + goal = header[22] + (header[23] << 8) + (header[24]<<16) + (header[25]<<24); + for (i=22; i < 26; ++i) + header[i] = 0; + crc = 0; + for (i=0; i < 27; ++i) + crc = crc32_update(crc, header[i]); + len = 0; + for (i=0; i < header[26]; ++i) { + int s = get8(f); + crc = crc32_update(crc, s); + len += s; + } + if (len && f->eof) return 0; + for (i=0; i < len; ++i) + crc = crc32_update(crc, get8(f)); + // finished parsing probable page + if (crc == goal) { + // we could now check that it's either got the last + // page flag set, OR it's followed by the capture + // pattern, but I guess TECHNICALLY you could have + // a file with garbage between each ogg page and recover + // from it automatically? So even though that paranoia + // might decrease the chance of an invalid decode by + // another 2^32, not worth it since it would hose those + // invalid-but-useful files? + if (end) + *end = stb_vorbis_get_file_offset(f); + if (last) { + if (header[5] & 0x04) + *last = 1; + else + *last = 0; + } + set_file_offset(f, retry_loc-1); + return 1; + } + } + invalid: + // not a valid page, so rewind and look for next one + set_file_offset(f, retry_loc); + } + } +} + + +#define SAMPLE_unknown 0xffffffff + +// seeking is implemented with a binary search, which narrows down the range to +// 64K, before using a linear search (because finding the synchronization +// pattern can be expensive, and the chance we'd find the end page again is +// relatively high for small ranges) +// +// two initial interpolation-style probes are used at the start of the search +// to try to bound either side of the binary search sensibly, while still +// working in O(log n) time if they fail. + +static int get_seek_page_info(stb_vorbis *f, ProbedPage *z) +{ + uint8 header[27], lacing[255]; + int i,len; + + // record where the page starts + z->page_start = stb_vorbis_get_file_offset(f); + + // parse the header + getn(f, header, 27); + if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S') + return 0; + getn(f, lacing, header[26]); + + // determine the length of the payload + len = 0; + for (i=0; i < header[26]; ++i) + len += lacing[i]; + + // this implies where the page ends + z->page_end = z->page_start + 27 + header[26] + len; + + // read the last-decoded sample out of the data + z->last_decoded_sample = header[6] + (header[7] << 8) + (header[8] << 16) + (header[9] << 24); + + // restore file state to where we were + set_file_offset(f, z->page_start); + return 1; +} + +// rarely used function to seek back to the preceeding page while finding the +// start of a packet +static int go_to_page_before(stb_vorbis *f, unsigned int limit_offset) +{ + unsigned int previous_safe, end; + + // now we want to seek back 64K from the limit + if (limit_offset >= 65536 && limit_offset-65536 >= f->first_audio_page_offset) + previous_safe = limit_offset - 65536; + else + previous_safe = f->first_audio_page_offset; + + set_file_offset(f, previous_safe); + + while (vorbis_find_page(f, &end, NULL)) { + if (end >= limit_offset && stb_vorbis_get_file_offset(f) < limit_offset) + return 1; + set_file_offset(f, end); + } + + return 0; +} + +// implements the search logic for finding a page and starting decoding. if +// the function succeeds, current_loc_valid will be true and current_loc will +// be less than or equal to the provided sample number (the closer the +// better). +static int seek_to_sample_coarse(stb_vorbis *f, uint32 sample_number) +{ + ProbedPage left, right, mid; + int i, start_seg_with_known_loc, end_pos, page_start; + uint32 delta, stream_length, padding; + double offset, bytes_per_sample; + int probe = 0; + + // find the last page and validate the target sample + stream_length = stb_vorbis_stream_length_in_samples(f); + if (stream_length == 0) return error(f, VORBIS_seek_without_length); + if (sample_number > stream_length) return error(f, VORBIS_seek_invalid); + + // this is the maximum difference between the window-center (which is the + // actual granule position value), and the right-start (which the spec + // indicates should be the granule position (give or take one)). + padding = ((f->blocksize_1 - f->blocksize_0) >> 2); + if (sample_number < padding) + sample_number = 0; + else + sample_number -= padding; + + left = f->p_first; + while (left.last_decoded_sample == ~0U) { + // (untested) the first page does not have a 'last_decoded_sample' + set_file_offset(f, left.page_end); + if (!get_seek_page_info(f, &left)) goto error; + } + + right = f->p_last; + assert(right.last_decoded_sample != ~0U); + + // starting from the start is handled differently + if (sample_number <= left.last_decoded_sample) { + if (stb_vorbis_seek_start(f)) + return 1; + return 0; + } + + while (left.page_end != right.page_start) { + assert(left.page_end < right.page_start); + // search range in bytes + delta = right.page_start - left.page_end; + if (delta <= 65536) { + // there's only 64K left to search - handle it linearly + set_file_offset(f, left.page_end); + } else { + if (probe < 2) { + if (probe == 0) { + // first probe (interpolate) + double data_bytes = right.page_end - left.page_start; + bytes_per_sample = data_bytes / right.last_decoded_sample; + offset = left.page_start + bytes_per_sample * (sample_number - left.last_decoded_sample); + } else { + // second probe (try to bound the other side) + double error = ((double) sample_number - mid.last_decoded_sample) * bytes_per_sample; + if (error >= 0 && error < 8000) error = 8000; + if (error < 0 && error > -8000) error = -8000; + offset += error * 2; + } + + // ensure the offset is valid + if (offset < left.page_end) + offset = left.page_end; + if (offset > right.page_start - 65536) + offset = right.page_start - 65536; + + set_file_offset(f, (unsigned int) offset); + } else { + // binary search for large ranges (offset by 32K to ensure + // we don't hit the right page) + set_file_offset(f, left.page_end + (delta / 2) - 32768); + } + + if (!vorbis_find_page(f, NULL, NULL)) goto error; + } + + for (;;) { + if (!get_seek_page_info(f, &mid)) goto error; + if (mid.last_decoded_sample != ~0U) break; + // (untested) no frames end on this page + set_file_offset(f, mid.page_end); + assert(mid.page_start < right.page_start); + } + + // if we've just found the last page again then we're in a tricky file, + // and we're close enough. + if (mid.page_start == right.page_start) + break; + + if (sample_number < mid.last_decoded_sample) + right = mid; + else + left = mid; + + ++probe; + } + + // seek back to start of the last packet + page_start = left.page_start; + set_file_offset(f, page_start); + if (!start_page(f)) return error(f, VORBIS_seek_failed); + end_pos = f->end_seg_with_known_loc; + assert(end_pos >= 0); + + for (;;) { + for (i = end_pos; i > 0; --i) + if (f->segments[i-1] != 255) + break; + + start_seg_with_known_loc = i; + + if (start_seg_with_known_loc > 0 || !(f->page_flag & PAGEFLAG_continued_packet)) + break; + + // (untested) the final packet begins on an earlier page + if (!go_to_page_before(f, page_start)) + goto error; + + page_start = stb_vorbis_get_file_offset(f); + if (!start_page(f)) goto error; + end_pos = f->segment_count - 1; + } + + // prepare to start decoding + f->current_loc_valid = FALSE; + f->last_seg = FALSE; + f->valid_bits = 0; + f->packet_bytes = 0; + f->bytes_in_seg = 0; + f->previous_length = 0; + f->next_seg = start_seg_with_known_loc; + + for (i = 0; i < start_seg_with_known_loc; i++) + skip(f, f->segments[i]); + + // start decoding (optimizable - this frame is generally discarded) + if (!vorbis_pump_first_frame(f)) + return 0; + if (f->current_loc > sample_number) + return error(f, VORBIS_seek_failed); + return 1; + +error: + // try to restore the file to a valid state + stb_vorbis_seek_start(f); + return error(f, VORBIS_seek_failed); +} + +// the same as vorbis_decode_initial, but without advancing +static int peek_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) +{ + int bits_read, bytes_read; + + if (!vorbis_decode_initial(f, p_left_start, p_left_end, p_right_start, p_right_end, mode)) + return 0; + + // either 1 or 2 bytes were read, figure out which so we can rewind + bits_read = 1 + ilog(f->mode_count-1); + if (f->mode_config[*mode].blockflag) + bits_read += 2; + bytes_read = (bits_read + 7) / 8; + + f->bytes_in_seg += bytes_read; + f->packet_bytes -= bytes_read; + skip(f, -bytes_read); + if (f->next_seg == -1) + f->next_seg = f->segment_count - 1; + else + f->next_seg--; + f->valid_bits = 0; + + return 1; +} + +int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number) +{ + uint32 max_frame_samples; + + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + // fast page-level search + if (!seek_to_sample_coarse(f, sample_number)) + return 0; + + assert(f->current_loc_valid); + assert(f->current_loc <= sample_number); + + // linear search for the relevant packet + max_frame_samples = (f->blocksize_1*3 - f->blocksize_0) >> 2; + while (f->current_loc < sample_number) { + int left_start, left_end, right_start, right_end, mode, frame_samples; + if (!peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode)) + return error(f, VORBIS_seek_failed); + // calculate the number of samples returned by the next frame + frame_samples = right_start - left_start; + if (f->current_loc + frame_samples > sample_number) { + return 1; // the next frame will contain the sample + } else if (f->current_loc + frame_samples + max_frame_samples > sample_number) { + // there's a chance the frame after this could contain the sample + vorbis_pump_first_frame(f); + } else { + // this frame is too early to be relevant + f->current_loc += frame_samples; + f->previous_length = 0; + maybe_start_packet(f); + flush_packet(f); + } + } + // the next frame will start with the sample + assert(f->current_loc == sample_number); + return 1; +} + +int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number) +{ + if (!stb_vorbis_seek_frame(f, sample_number)) + return 0; + + if (sample_number != f->current_loc) { + int n; + uint32 frame_start = f->current_loc; + stb_vorbis_get_frame_float(f, &n, NULL); + assert(sample_number > frame_start); + assert(f->channel_buffer_start + (int) (sample_number-frame_start) <= f->channel_buffer_end); + f->channel_buffer_start += (sample_number - frame_start); + } + + return 1; +} + +int stb_vorbis_seek_start(stb_vorbis *f) +{ + if (IS_PUSH_MODE(f)) { return error(f, VORBIS_invalid_api_mixing); } + set_file_offset(f, f->first_audio_page_offset); + f->previous_length = 0; + f->first_decode = TRUE; + f->next_seg = -1; + return vorbis_pump_first_frame(f); +} + +unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f) +{ + unsigned int restore_offset, previous_safe; + unsigned int end, last_page_loc; + + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + if (!f->total_samples) { + unsigned int last; + uint32 lo,hi; + char header[6]; + + // first, store the current decode position so we can restore it + restore_offset = stb_vorbis_get_file_offset(f); + + // now we want to seek back 64K from the end (the last page must + // be at most a little less than 64K, but let's allow a little slop) + if (f->stream_len >= 65536 && f->stream_len-65536 >= f->first_audio_page_offset) + previous_safe = f->stream_len - 65536; + else + previous_safe = f->first_audio_page_offset; + + set_file_offset(f, previous_safe); + // previous_safe is now our candidate 'earliest known place that seeking + // to will lead to the final page' + + if (!vorbis_find_page(f, &end, &last)) { + // if we can't find a page, we're hosed! + f->error = VORBIS_cant_find_last_page; + f->total_samples = 0xffffffff; + goto done; + } + + // check if there are more pages + last_page_loc = stb_vorbis_get_file_offset(f); + + // stop when the last_page flag is set, not when we reach eof; + // this allows us to stop short of a 'file_section' end without + // explicitly checking the length of the section + while (!last) { + set_file_offset(f, end); + if (!vorbis_find_page(f, &end, &last)) { + // the last page we found didn't have the 'last page' flag + // set. whoops! + break; + } + previous_safe = last_page_loc+1; + last_page_loc = stb_vorbis_get_file_offset(f); + } + + set_file_offset(f, last_page_loc); + + // parse the header + getn(f, (unsigned char *)header, 6); + // extract the absolute granule position + lo = get32(f); + hi = get32(f); + if (lo == 0xffffffff && hi == 0xffffffff) { + f->error = VORBIS_cant_find_last_page; + f->total_samples = SAMPLE_unknown; + goto done; + } + if (hi) + lo = 0xfffffffe; // saturate + f->total_samples = lo; + + f->p_last.page_start = last_page_loc; + f->p_last.page_end = end; + f->p_last.last_decoded_sample = lo; + + done: + set_file_offset(f, restore_offset); + } + return f->total_samples == SAMPLE_unknown ? 0 : f->total_samples; +} + +float stb_vorbis_stream_length_in_seconds(stb_vorbis *f) +{ + return stb_vorbis_stream_length_in_samples(f) / (float) f->sample_rate; +} + + + +int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output) +{ + int len, right,left,i; + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + if (!vorbis_decode_packet(f, &len, &left, &right)) { + f->channel_buffer_start = f->channel_buffer_end = 0; + return 0; + } + + len = vorbis_finish_frame(f, len, left, right); + for (i=0; i < f->channels; ++i) + f->outputs[i] = f->channel_buffers[i] + left; + + f->channel_buffer_start = left; + f->channel_buffer_end = left+len; + + if (channels) *channels = f->channels; + if (output) *output = f->outputs; + return len; +} + +#ifndef STB_VORBIS_NO_STDIO + +stb_vorbis * stb_vorbis_open_file_section(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc, unsigned int length) +{ + stb_vorbis *f, p; + vorbis_init(&p, alloc); + p.f = file; + p.f_start = (uint32) ftell(file); + p.stream_len = length; + p.close_on_free = close_on_free; + if (start_decoder(&p)) { + f = vorbis_alloc(&p); + if (f) { + *f = p; + vorbis_pump_first_frame(f); + return f; + } + } + if (error) *error = p.error; + vorbis_deinit(&p); + return NULL; +} + +stb_vorbis * stb_vorbis_open_file(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc) +{ + unsigned int len, start; + start = (unsigned int) ftell(file); + fseek(file, 0, SEEK_END); + len = (unsigned int) (ftell(file) - start); + fseek(file, start, SEEK_SET); + return stb_vorbis_open_file_section(file, close_on_free, error, alloc, len); +} + +stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc) +{ + FILE *f = fopen(filename, "rb"); + if (f) + return stb_vorbis_open_file(f, TRUE, error, alloc); + if (error) *error = VORBIS_file_open_failure; + return NULL; +} +#endif // STB_VORBIS_NO_STDIO + +stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc) +{ + stb_vorbis *f, p; + if (data == NULL) return NULL; + vorbis_init(&p, alloc); + p.stream = (uint8 *) data; + p.stream_end = (uint8 *) data + len; + p.stream_start = (uint8 *) p.stream; + p.stream_len = len; + p.push_mode = FALSE; + if (start_decoder(&p)) { + f = vorbis_alloc(&p); + if (f) { + *f = p; + vorbis_pump_first_frame(f); + if (error) *error = VORBIS__no_error; + return f; + } + } + if (error) *error = p.error; + vorbis_deinit(&p); + return NULL; +} + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +#define PLAYBACK_MONO 1 +#define PLAYBACK_LEFT 2 +#define PLAYBACK_RIGHT 4 + +#define L (PLAYBACK_LEFT | PLAYBACK_MONO) +#define C (PLAYBACK_LEFT | PLAYBACK_RIGHT | PLAYBACK_MONO) +#define R (PLAYBACK_RIGHT | PLAYBACK_MONO) + +static int8 channel_position[7][6] = +{ + { 0 }, + { C }, + { L, R }, + { L, C, R }, + { L, R, L, R }, + { L, C, R, L, R }, + { L, C, R, L, R, C }, +}; + + +#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT + typedef union { + float f; + int i; + } float_conv; + typedef char stb_vorbis_float_size_test[sizeof(float)==4 && sizeof(int) == 4]; + #define FASTDEF(x) float_conv x + // add (1<<23) to convert to int, then divide by 2^SHIFT, then add 0.5/2^SHIFT to round + #define MAGIC(SHIFT) (1.5f * (1 << (23-SHIFT)) + 0.5f/(1 << SHIFT)) + #define ADDEND(SHIFT) (((150-SHIFT) << 23) + (1 << 22)) + #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) (temp.f = (x) + MAGIC(s), temp.i - ADDEND(s)) + #define check_endianness() +#else + #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) ((int) ((x) * (1 << (s)))) + #define check_endianness() + #define FASTDEF(x) +#endif + +static void copy_samples(short *dest, float *src, int len) +{ + int i; + check_endianness(); + for (i=0; i < len; ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp, src[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + dest[i] = v; + } +} + +static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len) +{ + #define BUFFER_SIZE 32 + float buffer[BUFFER_SIZE]; + int i,j,o,n = BUFFER_SIZE; + check_endianness(); + for (o = 0; o < len; o += BUFFER_SIZE) { + memset(buffer, 0, sizeof(buffer)); + if (o + n > len) n = len - o; + for (j=0; j < num_c; ++j) { + if (channel_position[num_c][j] & mask) { + for (i=0; i < n; ++i) + buffer[i] += data[j][d_offset+o+i]; + } + } + for (i=0; i < n; ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + output[o+i] = v; + } + } +} + +static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len) +{ + #define BUFFER_SIZE 32 + float buffer[BUFFER_SIZE]; + int i,j,o,n = BUFFER_SIZE >> 1; + // o is the offset in the source data + check_endianness(); + for (o = 0; o < len; o += BUFFER_SIZE >> 1) { + // o2 is the offset in the output data + int o2 = o << 1; + memset(buffer, 0, sizeof(buffer)); + if (o + n > len) n = len - o; + for (j=0; j < num_c; ++j) { + int m = channel_position[num_c][j] & (PLAYBACK_LEFT | PLAYBACK_RIGHT); + if (m == (PLAYBACK_LEFT | PLAYBACK_RIGHT)) { + for (i=0; i < n; ++i) { + buffer[i*2+0] += data[j][d_offset+o+i]; + buffer[i*2+1] += data[j][d_offset+o+i]; + } + } else if (m == PLAYBACK_LEFT) { + for (i=0; i < n; ++i) { + buffer[i*2+0] += data[j][d_offset+o+i]; + } + } else if (m == PLAYBACK_RIGHT) { + for (i=0; i < n; ++i) { + buffer[i*2+1] += data[j][d_offset+o+i]; + } + } + } + for (i=0; i < (n<<1); ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + output[o2+i] = v; + } + } +} + +static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples) +{ + int i; + if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { + static int channel_selector[3][2] = { {0}, {PLAYBACK_MONO}, {PLAYBACK_LEFT, PLAYBACK_RIGHT} }; + for (i=0; i < buf_c; ++i) + compute_samples(channel_selector[buf_c][i], buffer[i]+b_offset, data_c, data, d_offset, samples); + } else { + int limit = buf_c < data_c ? buf_c : data_c; + for (i=0; i < limit; ++i) + copy_samples(buffer[i]+b_offset, data[i]+d_offset, samples); + for ( ; i < buf_c; ++i) + memset(buffer[i]+b_offset, 0, sizeof(short) * samples); + } +} + +int stb_vorbis_get_frame_short(stb_vorbis *f, int num_c, short **buffer, int num_samples) +{ + float **output; + int len = stb_vorbis_get_frame_float(f, NULL, &output); + if (len > num_samples) len = num_samples; + if (len) + convert_samples_short(num_c, buffer, 0, f->channels, output, 0, len); + return len; +} + +static void convert_channels_short_interleaved(int buf_c, short *buffer, int data_c, float **data, int d_offset, int len) +{ + int i; + check_endianness(); + if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { + assert(buf_c == 2); + for (i=0; i < buf_c; ++i) + compute_stereo_samples(buffer, data_c, data, d_offset, len); + } else { + int limit = buf_c < data_c ? buf_c : data_c; + int j; + for (j=0; j < len; ++j) { + for (i=0; i < limit; ++i) { + FASTDEF(temp); + float f = data[i][d_offset+j]; + int v = FAST_SCALED_FLOAT_TO_INT(temp, f,15);//data[i][d_offset+j],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + *buffer++ = v; + } + for ( ; i < buf_c; ++i) + *buffer++ = 0; + } + } +} + +int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts) +{ + float **output; + int len; + if (num_c == 1) return stb_vorbis_get_frame_short(f,num_c,&buffer, num_shorts); + len = stb_vorbis_get_frame_float(f, NULL, &output); + if (len) { + if (len*num_c > num_shorts) len = num_shorts / num_c; + convert_channels_short_interleaved(num_c, buffer, f->channels, output, 0, len); + } + return len; +} + +int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts) +{ + float **outputs; + int len = num_shorts / channels; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < len) { + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + if (k) + convert_channels_short_interleaved(channels, buffer, f->channels, f->channel_buffers, f->channel_buffer_start, k); + buffer += k*channels; + n += k; + f->channel_buffer_start += k; + if (n == len) break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; + } + return n; +} + +int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int len) +{ + float **outputs; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < len) { + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + if (k) + convert_samples_short(channels, buffer, n, f->channels, f->channel_buffers, f->channel_buffer_start, k); + n += k; + f->channel_buffer_start += k; + if (n == len) break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; + } + return n; +} + +#ifndef STB_VORBIS_NO_STDIO +int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output) +{ + int data_len, offset, total, limit, error; + short *data; + stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL); + if (v == NULL) return -1; + limit = v->channels * 4096; + *channels = v->channels; + if (sample_rate) + *sample_rate = v->sample_rate; + offset = data_len = 0; + total = limit; + data = (short *) malloc(total * sizeof(*data)); + if (data == NULL) { + stb_vorbis_close(v); + return -2; + } + for (;;) { + int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); + if (n == 0) break; + data_len += n; + offset += n * v->channels; + if (offset + limit > total) { + short *data2; + total *= 2; + data2 = (short *) realloc(data, total * sizeof(*data)); + if (data2 == NULL) { + free(data); + stb_vorbis_close(v); + return -2; + } + data = data2; + } + } + *output = data; + stb_vorbis_close(v); + return data_len; +} +#endif // NO_STDIO + +int stb_vorbis_decode_memory(const uint8 *mem, int len, int *channels, int *sample_rate, short **output) +{ + int data_len, offset, total, limit, error; + short *data; + stb_vorbis *v = stb_vorbis_open_memory(mem, len, &error, NULL); + if (v == NULL) return -1; + limit = v->channels * 4096; + *channels = v->channels; + if (sample_rate) + *sample_rate = v->sample_rate; + offset = data_len = 0; + total = limit; + data = (short *) malloc(total * sizeof(*data)); + if (data == NULL) { + stb_vorbis_close(v); + return -2; + } + for (;;) { + int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); + if (n == 0) break; + data_len += n; + offset += n * v->channels; + if (offset + limit > total) { + short *data2; + total *= 2; + data2 = (short *) realloc(data, total * sizeof(*data)); + if (data2 == NULL) { + free(data); + stb_vorbis_close(v); + return -2; + } + data = data2; + } + } + *output = data; + stb_vorbis_close(v); + return data_len; +} +#endif // STB_VORBIS_NO_INTEGER_CONVERSION + +int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats) +{ + float **outputs; + int len = num_floats / channels; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < len) { + int i,j; + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + for (j=0; j < k; ++j) { + for (i=0; i < z; ++i) + *buffer++ = f->channel_buffers[i][f->channel_buffer_start+j]; + for ( ; i < channels; ++i) + *buffer++ = 0; + } + n += k; + f->channel_buffer_start += k; + if (n == len) + break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) + break; + } + return n; +} + +int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples) +{ + float **outputs; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < num_samples) { + int i; + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= num_samples) k = num_samples - n; + if (k) { + for (i=0; i < z; ++i) + memcpy(buffer[i]+n, f->channel_buffers[i]+f->channel_buffer_start, sizeof(float)*k); + for ( ; i < channels; ++i) + memset(buffer[i]+n, 0, sizeof(float) * k); + } + n += k; + f->channel_buffer_start += k; + if (n == num_samples) + break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) + break; + } + return n; +} +#endif // STB_VORBIS_NO_PULLDATA_API + +/* Version history + 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files + 1.11 - 2017-07-23 - fix MinGW compilation + 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory + 1.09 - 2016-04-04 - back out 'avoid discarding last frame' fix from previous version + 1.08 - 2016-04-02 - fixed multiple warnings; fix setup memory leaks; + avoid discarding last frame of audio data + 1.07 - 2015-01-16 - fixed some warnings, fix mingw, const-correct API + some more crash fixes when out of memory or with corrupt files + 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) + some crash fixes when out of memory or with corrupt files + 1.05 - 2015-04-19 - don't define __forceinline if it's redundant + 1.04 - 2014-08-27 - fix missing const-correct case in API + 1.03 - 2014-08-07 - Warning fixes + 1.02 - 2014-07-09 - Declare qsort compare function _cdecl on windows + 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float + 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in multichannel + (API change) report sample rate for decode-full-file funcs + 0.99996 - bracket #include for macintosh compilation by Laurent Gomila + 0.99995 - use union instead of pointer-cast for fast-float-to-int to avoid alias-optimization problem + 0.99994 - change fast-float-to-int to work in single-precision FPU mode, remove endian-dependence + 0.99993 - remove assert that fired on legal files with empty tables + 0.99992 - rewind-to-start + 0.99991 - bugfix to stb_vorbis_get_samples_short by Bernhard Wodo + 0.9999 - (should have been 0.99990) fix no-CRT support, compiling as C++ + 0.9998 - add a full-decode function with a memory source + 0.9997 - fix a bug in the read-from-FILE case in 0.9996 addition + 0.9996 - query length of vorbis stream in samples/seconds + 0.9995 - bugfix to another optimization that only happened in certain files + 0.9994 - bugfix to one of the optimizations that caused significant (but inaudible?) errors + 0.9993 - performance improvements; runs in 99% to 104% of time of reference implementation + 0.9992 - performance improvement of IMDCT; now performs close to reference implementation + 0.9991 - performance improvement of IMDCT + 0.999 - (should have been 0.9990) performance improvement of IMDCT + 0.998 - no-CRT support from Casey Muratori + 0.997 - bugfixes for bugs found by Terje Mathisen + 0.996 - bugfix: fast-huffman decode initialized incorrectly for sparse codebooks; fixing gives 10% speedup - found by Terje Mathisen + 0.995 - bugfix: fix to 'effective' overrun detection - found by Terje Mathisen + 0.994 - bugfix: garbage decode on final VQ symbol of a non-multiple - found by Terje Mathisen + 0.993 - bugfix: pushdata API required 1 extra byte for empty page (failed to consume final page if empty) - found by Terje Mathisen + 0.992 - fixes for MinGW warning + 0.991 - turn fast-float-conversion on by default + 0.990 - fix push-mode seek recovery if you seek into the headers + 0.98b - fix to bad release of 0.98 + 0.98 - fix push-mode seek recovery; robustify float-to-int and support non-fast mode + 0.97 - builds under c++ (typecasting, don't use 'class' keyword) + 0.96 - somehow MY 0.95 was right, but the web one was wrong, so here's my 0.95 rereleased as 0.96, fixes a typo in the clamping code + 0.95 - clamping code for 16-bit functions + 0.94 - not publically released + 0.93 - fixed all-zero-floor case (was decoding garbage) + 0.92 - fixed a memory leak + 0.91 - conditional compiles to omit parts of the API and the infrastructure to support them: STB_VORBIS_NO_PULLDATA_API, STB_VORBIS_NO_PUSHDATA_API, STB_VORBIS_NO_STDIO, STB_VORBIS_NO_INTEGER_CONVERSION + 0.90 - first public release +*/ + +#endif // STB_VORBIS_HEADER_ONLY + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ +#include "soloud_file_hack_off.h" \ No newline at end of file diff --git a/src/soloud/src/audiosource/wav/stb_vorbis.h b/src/soloud/src/audiosource/wav/stb_vorbis.h new file mode 100644 index 0000000..90a1570 --- /dev/null +++ b/src/soloud/src/audiosource/wav/stb_vorbis.h @@ -0,0 +1,2 @@ +#define STB_VORBIS_HEADER_ONLY +#include "stb_vorbis.c" \ No newline at end of file diff --git a/src/soloud/src/backend/alsa/soloud_alsa.cpp b/src/soloud/src/backend/alsa/soloud_alsa.cpp new file mode 100644 index 0000000..cc8609a --- /dev/null +++ b/src/soloud/src/backend/alsa/soloud_alsa.cpp @@ -0,0 +1,168 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" +#include "soloud_thread.h" + +#if !defined(WITH_ALSA) + +namespace SoLoud +{ + result alsa_init(Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer) + { + return NOT_IMPLEMENTED; + } +}; + +#else + +#define ALSA_PCM_NEW_HW_PARAMS_API +#include + +#include +#include +#include + +namespace SoLoud +{ + struct ALSAData + { + float *buffer; + short *sampleBuffer; + snd_pcm_t *alsaDeviceHandle; + Soloud *soloud; + int samples; + int channels; + bool audioProcessingDone; + Thread::ThreadHandle threadHandle; + }; + + + static void alsaThread(void *aParam) + { + + ALSAData *data = static_cast(aParam); + while (!data->audioProcessingDone) + { + data->soloud->mix(data->buffer, data->samples); + for (int i=0;isamples*data->channels;++i) + { + data->sampleBuffer[i] = static_cast(floor(data->buffer[i] + * static_cast(0x7fff))); + } + if (snd_pcm_writei(data->alsaDeviceHandle, data->sampleBuffer, data->samples) == -EPIPE) + snd_pcm_prepare(data->alsaDeviceHandle); + + } + + } + + static void alsaCleanup(Soloud *aSoloud) + { + if (0 == aSoloud->mBackendData) + { + return; + } + ALSAData *data = static_cast(aSoloud->mBackendData); + data->audioProcessingDone = true; + if (data->threadHandle) + { + Thread::wait(data->threadHandle); + Thread::release(data->threadHandle); + } + snd_pcm_drain(data->alsaDeviceHandle); + snd_pcm_close(data->alsaDeviceHandle); + if (0 != data->sampleBuffer) + { + delete[] data->sampleBuffer; + } + if (0 != data->buffer) + { + delete[] data->buffer; + } + delete data; + aSoloud->mBackendData = 0; + } + + result alsa_init(Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer, unsigned int aChannels) + { + ALSAData *data = new ALSAData; + memset(data, 0, sizeof(ALSAData)); + aSoloud->mBackendData = data; + aSoloud->mBackendCleanupFunc = alsaCleanup; + data->samples = aBuffer; + data->channels = 2; + data->soloud = aSoloud; + + int rc; + snd_pcm_t *handle; + rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0); + if (rc < 0) + { + return UNKNOWN_ERROR; + } + + data->alsaDeviceHandle = handle; + + snd_pcm_hw_params_t *params; + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(handle, params); + + snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_channels(handle, params, 2); + snd_pcm_hw_params_set_buffer_size(handle, params, aBuffer); + + unsigned int val = aSamplerate; + int dir = 0; + rc = snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); + if (rc < 0) + { + return UNKNOWN_ERROR; + } + + rc = snd_pcm_hw_params(handle, params); + if (rc < 0) + { + return UNKNOWN_ERROR; + } + + snd_pcm_hw_params_get_rate(params, &val, &dir); + aSamplerate = val; + snd_pcm_hw_params_get_channels(params, &val); + data->channels = val; + + data->buffer = new float[data->samples*data->channels]; + data->sampleBuffer = new short[data->samples*data->channels]; + aSoloud->postinit(aSamplerate, data->samples * data->channels, aFlags, 2); + data->threadHandle = Thread::createThread(alsaThread, data); + if (0 == data->threadHandle) + { + return UNKNOWN_ERROR; + } + aSoloud->mBackendString = "ALSA"; + return 0; + } +}; +#endif \ No newline at end of file diff --git a/src/soloud/src/backend/coreaudio/soloud_coreaudio.cpp b/src/soloud/src/backend/coreaudio/soloud_coreaudio.cpp new file mode 100644 index 0000000..158a4d7 --- /dev/null +++ b/src/soloud/src/backend/coreaudio/soloud_coreaudio.cpp @@ -0,0 +1,127 @@ +/* +SoLoud audio engine +Copyright (c) 2015 Jari Komppa + +Core Audio backend for Mac OS X +Copyright (c) 2015 Petri Häkkinen + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include + +#include "soloud.h" +#include "soloud_thread.h" + +#if !defined(WITH_COREAUDIO) + +namespace SoLoud +{ + result coreaudio_init(SoLoud::Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer) + { + return NOT_IMPLEMENTED; + } +} +#else + +#include + +#define NUM_BUFFERS 2 + +static AudioQueueRef audioQueue = 0; + +namespace SoLoud +{ + void soloud_coreaudio_deinit(SoLoud::Soloud *aSoloud) + { + AudioQueueStop(audioQueue, true); + AudioQueueDispose(audioQueue, false); + } + + static void coreaudio_mutex_lock(void *mutex) + { + Thread::lockMutex(mutex); + } + + static void coreaudio_mutex_unlock(void *mutex) + { + Thread::unlockMutex(mutex); + } + + static void coreaudio_fill_buffer(void *context, AudioQueueRef queue, AudioQueueBufferRef buffer) + { + SoLoud::Soloud *aSoloud = (SoLoud::Soloud*)context; + aSoloud->mixSigned16((short*)buffer->mAudioData, buffer->mAudioDataByteSize / 4); + AudioQueueEnqueueBuffer(queue, buffer, 0, NULL); + } + + result coreaudio_init(SoLoud::Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer, unsigned int aChannels) + { + aSoloud->postinit(aSamplerate, aBuffer, aFlags, 2); + aSoloud->mBackendCleanupFunc = soloud_coreaudio_deinit; + + AudioStreamBasicDescription audioFormat; + audioFormat.mSampleRate = aSamplerate; + audioFormat.mFormatID = kAudioFormatLinearPCM; + audioFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + audioFormat.mBytesPerPacket = 4; + audioFormat.mFramesPerPacket = 1; + audioFormat.mBytesPerFrame = 4; + audioFormat.mChannelsPerFrame = 2; + audioFormat.mBitsPerChannel = 16; + audioFormat.mReserved = 0; + + // create the audio queue + OSStatus result = AudioQueueNewOutput(&audioFormat, coreaudio_fill_buffer, aSoloud, NULL, NULL, 0, &audioQueue); + if(result) + { + //printf("AudioQueueNewOutput failed (%d)\n", result); + return UNKNOWN_ERROR; + } + + // allocate and prime audio buffers + for(int i = 0; i < NUM_BUFFERS; ++i) + { + AudioQueueBufferRef buffer; + result = AudioQueueAllocateBuffer(audioQueue, aBuffer * 4, &buffer); + if(result) + { + //printf("AudioQueueAllocateBuffer failed (%d)\n", result); + return UNKNOWN_ERROR; + } + buffer->mAudioDataByteSize = aBuffer * 4; + memset(buffer->mAudioData, 0, buffer->mAudioDataByteSize); + AudioQueueEnqueueBuffer(audioQueue, buffer, 0, NULL); + } + + // start playback + result = AudioQueueStart(audioQueue, NULL); + if(result) + { + //printf("AudioQueueStart failed (%d)\n", result); + return UNKNOWN_ERROR; + } + + aSoloud->mBackendString = "CoreAudio"; + return 0; + } +}; +#endif diff --git a/src/soloud/src/backend/null/soloud_null.cpp b/src/soloud/src/backend/null/soloud_null.cpp new file mode 100644 index 0000000..330d077 --- /dev/null +++ b/src/soloud/src/backend/null/soloud_null.cpp @@ -0,0 +1,57 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" + +#if !defined(WITH_NULL) + +namespace SoLoud +{ + result null_init(Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer) + { + return NOT_IMPLEMENTED; + } +}; + +#else + +namespace SoLoud +{ + static void nullCleanup(Soloud *aSoloud) + { + } + + result null_init(Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer, unsigned int aChannels) + { + if (aChannels == 0 || aChannels == 3 || aChannels == 5 || aChannels == 7 || aChannels > MAX_CHANNELS || aBuffer < SAMPLE_GRANULARITY) + return INVALID_PARAMETER; + aSoloud->mBackendData = 0; + aSoloud->mBackendCleanupFunc = nullCleanup; + + aSoloud->postinit(aSamplerate, aBuffer, aFlags, aChannels); + aSoloud->mBackendString = "null driver"; + return SO_NO_ERROR; + } +}; +#endif \ No newline at end of file diff --git a/src/soloud/src/backend/opensles/soloud_opensles.cpp b/src/soloud/src/backend/opensles/soloud_opensles.cpp new file mode 100644 index 0000000..e3bf9fe --- /dev/null +++ b/src/soloud/src/backend/opensles/soloud_opensles.cpp @@ -0,0 +1,301 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include +#include +#include + +#include "soloud.h" +#include "soloud_thread.h" + +#if !defined(WITH_OPENSLES) +namespace SoLoud +{ + result opensles_init(SoLoud::Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer) + { + return NOT_IMPLEMENTED; + } +} +#else + +#include "SLES/OpenSLES.h" +#include "SLES/OpenSLES_Platform.h" + +#if defined( __ANDROID__ ) +#include "SLES/OpenSLES_Android.h" +#endif + +// Error logging. +#if defined( __ANDROID__ ) +# include +# define LOG_ERROR( _msg ) \ + __android_log_print( ANDROID_LOG_ERROR, "SoLoud", _msg ) +# define LOG_INFO( _msg ) \ + __android_log_print( ANDROID_LOG_INFO, "SoLoud", _msg ) + +#else + printf( _msg ) +#endif + +#define NUM_BUFFERS 2 + +namespace SoLoud +{ + struct BackendData + { + BackendData() + { + memset(this, 0, sizeof(BackendData)); + } + + ~BackendData() + { + // Wait until thread is done. + threadrun++; + while (threadrun == 1) + { + Thread::sleep(10); + } + + if(playerObj) + { + (*playerObj)->Destroy(playerObj); + } + + if(outputMixObj) + { + (*outputMixObj)->Destroy(outputMixObj); + } + + if(engineObj) + { + (*engineObj)->Destroy(engineObj); + } + + for(int idx = 0; idx < NUM_BUFFERS; ++idx) + { + delete [] outputBuffers[idx]; + } + } + + // Engine. + SLObjectItf engineObj; + SLEngineItf engine; + + // Output mix. + SLObjectItf outputMixObj; + SLVolumeItf outputMixVol; + + // Data. + SLDataLocator_OutputMix outLocator; + SLDataSink dstDataSink; + + // Player. + SLObjectItf playerObj; + SLPlayItf player; + SLVolumeItf playerVol; + SLAndroidSimpleBufferQueueItf playerBufferQueue; + + unsigned int bufferSize; + unsigned int channels; + short *outputBuffers[ NUM_BUFFERS ]; + int buffersQueued; + int activeBuffer; + volatile int threadrun; + + SLDataLocator_AndroidSimpleBufferQueue inLocator; + }; + + void soloud_opensles_deinit(SoLoud::Soloud *aSoloud) + { + BackendData *data = static_cast(aSoloud->mBackendData); + delete data; + aSoloud->mBackendData = NULL; + } + + static void opensles_iterate(SoLoud::Soloud *aSoloud) + { + BackendData *data = static_cast(aSoloud->mBackendData); + + // If we have no buffered queued, queue one up for playback. + if(data->buffersQueued == 0) + { + // Get next output buffer, advance, next buffer. + short * outputBuffer = data->outputBuffers[data->activeBuffer]; + data->activeBuffer = (data->activeBuffer + 1) % NUM_BUFFERS; + short * nextBuffer = data->outputBuffers[data->activeBuffer]; + + // Mix this buffer. + const int bufferSizeBytes = data->bufferSize * data->channels * sizeof(short); + (*data->playerBufferQueue)->Enqueue(data->playerBufferQueue, outputBuffer, bufferSizeBytes); + ++data->buffersQueued; + + aSoloud->mixSigned16(nextBuffer,data->bufferSize); + } + } + + static void opensles_thread(void *aParam) + { + Soloud *soloud = static_cast(aParam); + BackendData *data = static_cast(soloud->mBackendData); + while (data->threadrun == 0) + { + opensles_iterate(soloud); + + // TODO: Condition var? + Thread::sleep(1); + } + data->threadrun++; + } + + static void SLAPIENTRY soloud_opensles_play_callback(SLPlayItf player, void *context, SLuint32 event) + { + Soloud *soloud = static_cast(context); + BackendData *data = static_cast(soloud->mBackendData); + if( event & SL_PLAYEVENT_HEADATEND ) + { + data->buffersQueued--; + } + } + + result opensles_init(SoLoud::Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer, unsigned int aChannels) + { + BackendData *data = new BackendData(); + + // Allocate output buffer to mix into. + data->bufferSize = aBuffer; + data->channels = aChannels; + const int bufferSizeBytes = data->bufferSize * data->channels * sizeof(short); + for(int idx = 0; idx < NUM_BUFFERS; ++idx) + { + data->outputBuffers[idx] = new short[ data->bufferSize * data->channels ]; + memset(data->outputBuffers[idx], 0, bufferSizeBytes); + } + + // Create engine. + const SLEngineOption opts[] = { SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE }; + if(slCreateEngine( &data->engineObj, 1, opts, 0, NULL, NULL ) != SL_RESULT_SUCCESS) + { + LOG_ERROR( "Failed to create engine." ); + return UNKNOWN_ERROR; + } + + // Realize and get engine interfaxce. + (*data->engineObj)->Realize(data->engineObj, SL_BOOLEAN_FALSE); + if((*data->engineObj)->GetInterface(data->engineObj, SL_IID_ENGINE, &data->engine) != SL_RESULT_SUCCESS) + { + LOG_ERROR( "Failed to get engine interface." ); + return UNKNOWN_ERROR; + } + + // Create output mix. + const SLInterfaceID ids[] = { SL_IID_VOLUME }; + const SLboolean req[] = { SL_BOOLEAN_FALSE }; + + if((*data->engine)->CreateOutputMix(data->engine, &data->outputMixObj, 1, ids, req) != SL_RESULT_SUCCESS) + { + LOG_ERROR( "Failed to create output mix object." ); + return UNKNOWN_ERROR; + } + (*data->outputMixObj)->Realize(data->outputMixObj, SL_BOOLEAN_FALSE); + + if((*data->outputMixObj)->GetInterface(data->outputMixObj, SL_IID_VOLUME, &data->outputMixVol) != SL_RESULT_SUCCESS) + { + LOG_INFO( "Failed to get output mix volume interface." ); + } + + // Create android buffer queue. + data->inLocator.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; + data->inLocator.numBuffers = NUM_BUFFERS; + + // Setup data format. + SLDataFormat_PCM format; + format.formatType = SL_DATAFORMAT_PCM; + format.numChannels = data->channels; + format.samplesPerSec = aSamplerate * 1000; //mHz + format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format.containerSize = 16; + format.endianness = SL_BYTEORDER_LITTLEENDIAN; + + // Determine channel mask. + if(data->channels == 2) + { + format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + } + else + { + format.channelMask = SL_SPEAKER_FRONT_CENTER; + } + + SLDataSource src; + src.pLocator = &data->inLocator; + src.pFormat = &format; + + // Output mix. + data->outLocator.locatorType = SL_DATALOCATOR_OUTPUTMIX; + data->outLocator.outputMix = data->outputMixObj; + + data->dstDataSink.pLocator = &data->outLocator; + data->dstDataSink.pFormat = NULL; + + // Setup player. + { + const SLInterfaceID ids[] = { SL_IID_VOLUME, SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; + const SLboolean req[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; + + (*data->engine)->CreateAudioPlayer(data->engine, &data->playerObj, &src, &data->dstDataSink, sizeof(ids) / sizeof(ids[0]), ids, req); + + (*data->playerObj)->Realize(data->playerObj, SL_BOOLEAN_FALSE); + + (*data->playerObj)->GetInterface(data->playerObj, SL_IID_PLAY, &data->player); + (*data->playerObj)->GetInterface(data->playerObj, SL_IID_VOLUME, &data->playerVol); + + (*data->playerObj)->GetInterface(data->playerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &data->playerBufferQueue); + } + + aSoloud->mBackendData = data; // Must be set before callback + + // Begin playing. + { + const int bufferSizeBytes = data->bufferSize * data->channels * sizeof(short); + (*data->playerBufferQueue)->Enqueue(data->playerBufferQueue, data->outputBuffers[0], bufferSizeBytes); + data->activeBuffer = (data->activeBuffer + 1) % NUM_BUFFERS; + + (*data->player)->RegisterCallback(data->player, soloud_opensles_play_callback, aSoloud); + (*data->player)->SetCallbackEventsMask(data->player, SL_PLAYEVENT_HEADATEND); + (*data->player)->SetPlayState(data->player, SL_PLAYSTATE_PLAYING); + + } + + // + aSoloud->postinit(aSamplerate,data->bufferSize,aFlags,2); + aSoloud->mBackendCleanupFunc = soloud_opensles_deinit; + + LOG_INFO( "Creating audio thread." ); + Thread::createThread(opensles_thread, (void*)aSoloud); + + aSoloud->mBackendString = "OpenSL ES"; + return SO_NO_ERROR; + } +}; +#endif diff --git a/src/soloud/src/backend/oss/soloud_oss.cpp b/src/soloud/src/backend/oss/soloud_oss.cpp new file mode 100644 index 0000000..66495f5 --- /dev/null +++ b/src/soloud/src/backend/oss/soloud_oss.cpp @@ -0,0 +1,179 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" +#include "soloud_thread.h" + +#if !defined(WITH_OSS) + +namespace SoLoud +{ + result oss_init(Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer) + { + return NOT_IMPLEMENTED; + } +}; + +#else +#include +#include +#include +#include +#include + +namespace SoLoud +{ + static const int OSS_DEVICE_COUNT = 4; + static const char *OSS_DEVICES[OSS_DEVICE_COUNT] = + { + "/dev/dsp", + "/dev/dsp0.0", + "/dev/dsp1.0", + "/dev/dsp2.0" + }; + + struct OSSData + { + float *buffer; + short *sampleBuffer; + int ossDeviceHandle; + Soloud *soloud; + int samples; + int channels; + bool audioProcessingDone; + Thread::ThreadHandle threadHandle; + }; + + static void ossThread(void *aParam) + { + OSSData *data = static_cast(aParam); + while (!data->audioProcessingDone) + { + data->soloud->mix(data->buffer, data->samples); + for (int i=0;isamples*data->channels;++i) + { + data->sampleBuffer[i] = static_cast(floor(data->buffer[i] + * static_cast(0x7fff))); + } + write(data->ossDeviceHandle, data->sampleBuffer, + sizeof(short)*data->samples*data->channels); + } + } + + static void ossCleanup(Soloud *aSoloud) + { + if (0 == aSoloud->mBackendData) + { + return; + } + OSSData *data = static_cast(aSoloud->mBackendData); + data->audioProcessingDone = true; + if (data->threadHandle) + { + Thread::wait(data->threadHandle); + Thread::release(data->threadHandle); + } + ioctl(data->ossDeviceHandle, SNDCTL_DSP_RESET, 0); + if (0 != data->sampleBuffer) + { + delete[] data->sampleBuffer; + } + if (0 != data->buffer) + { + delete[] data->buffer; + } + close(data->ossDeviceHandle); + delete data; + aSoloud->mBackendData = 0; + } + + result oss_init(Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer, unsigned int aChannels) + { + OSSData *data = new OSSData; + memset(data, 0, sizeof(OSSData)); + aSoloud->mBackendData = data; + aSoloud->mBackendCleanupFunc = ossCleanup; + data->samples = aBuffer; + data->soloud = aSoloud; + bool deviceIsOpen = false; + for (int i=0;iossDeviceHandle = open(OSS_DEVICES[i], O_WRONLY, 0); + if (-1 != data->ossDeviceHandle) + { + deviceIsOpen = true; + break; + } + } + if (!deviceIsOpen) + { + return UNKNOWN_ERROR; + } + int flags = fcntl(data->ossDeviceHandle, F_GETFL); + flags &= ~O_NONBLOCK; + if (-1 == fcntl(data->ossDeviceHandle, F_SETFL, flags)) + { + return UNKNOWN_ERROR; + } + int format = AFMT_S16_NE; + if (-1 == ioctl(data->ossDeviceHandle, SNDCTL_DSP_SETFMT, &format)) + { + return UNKNOWN_ERROR; + } + if (format != AFMT_S16_NE) + { + return UNKNOWN_ERROR; + } + int channels = 2; + data->channels = channels; + if (-1 == ioctl(data->ossDeviceHandle, SNDCTL_DSP_CHANNELS, &data->channels)) + { + return UNKNOWN_ERROR; + } + if (channels != data->channels) + { + return UNKNOWN_ERROR; + } + int speed = aSamplerate; + if (-1 == ioctl(data->ossDeviceHandle, SNDCTL_DSP_SPEED, &speed)) + { + return UNKNOWN_ERROR; + } + if (speed != aSamplerate) + { + return UNKNOWN_ERROR; + } + data->buffer = new float[data->samples*data->channels]; + data->sampleBuffer = new short[data->samples*data->channels]; + aSoloud->postinit(aSamplerate, data->samples * data->channels, aFlags, 2); + data->threadHandle = Thread::createThread(ossThread, data); + if (0 == data->threadHandle) + { + return UNKNOWN_ERROR; + } + aSoloud->mBackendString = "OSS"; + return 0; + } +}; +#endif \ No newline at end of file diff --git a/src/soloud/src/backend/sdl/soloud_sdl1.cpp b/src/soloud/src/backend/sdl/soloud_sdl1.cpp new file mode 100644 index 0000000..1d74a74 --- /dev/null +++ b/src/soloud/src/backend/sdl/soloud_sdl1.cpp @@ -0,0 +1,102 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include + +#include "soloud.h" + +#if !defined(WITH_SDL1) + +namespace SoLoud +{ + result sdl1_init(SoLoud::Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer) + { + return NOT_IMPLEMENTED; + } +} + +#else + +#if defined(_MSC_VER) +#include +#else +#include +#endif +#include + + +extern "C" +{ + int dll_SDL1_found(); + int dll_SDL1_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained); + void dll_SDL1_CloseAudio(); + void dll_SDL1_PauseAudio(int pause_on); +}; + + +namespace SoLoud +{ + static SDL_AudioSpec gActiveAudioSpec; + + void soloud_sdl1_audiomixer(void *userdata, Uint8 *stream, int len) + { + short *buf = (short*)stream; + SoLoud::Soloud *soloud = (SoLoud::Soloud *)userdata; + int samples = len / (gActiveAudioSpec.channels * sizeof(short)); + soloud->mixSigned16(buf, samples); + } + + static void soloud_sdl1_deinit(SoLoud::Soloud *aSoloud) + { + dll_SDL1_CloseAudio(); + } + + result sdl1_init(SoLoud::Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer, unsigned int aChannels) + { + if (!dll_SDL1_found()) + return DLL_NOT_FOUND; + + SDL_AudioSpec as; + as.freq = aSamplerate; + as.format = AUDIO_S16; + as.channels = aChannels; + as.samples = aBuffer; + as.callback = soloud_sdl1_audiomixer; + as.userdata = (void*)aSoloud; + + if (dll_SDL1_OpenAudio(&as, &gActiveAudioSpec) < 0) + { + return UNKNOWN_ERROR; + } + + aSoloud->postinit(gActiveAudioSpec.freq, gActiveAudioSpec.samples, aFlags, gActiveAudioSpec.channels); + + aSoloud->mBackendCleanupFunc = soloud_sdl1_deinit; + + dll_SDL1_PauseAudio(0); + aSoloud->mBackendString = "SDL (dynamic)"; + return 0; + } + +}; +#endif \ No newline at end of file diff --git a/src/soloud/src/backend/sdl/soloud_sdl1_dll.c b/src/soloud/src/backend/sdl/soloud_sdl1_dll.c new file mode 100644 index 0000000..7f0d2cb --- /dev/null +++ b/src/soloud/src/backend/sdl/soloud_sdl1_dll.c @@ -0,0 +1,128 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include +#if defined(_MSC_VER) +#define WINDOWS_VERSION +#include +#else +#include +#endif +#include + +typedef int (*SDLOpenAudio)(SDL_AudioSpec *desired, SDL_AudioSpec *obtained); +typedef void (*SDLCloseAudio)(); +typedef void (*SDLPauseAudio)(int pause_on); + +static SDLOpenAudio dSDL1_OpenAudio = NULL; +static SDLCloseAudio dSDL1_CloseAudio = NULL; +static SDLPauseAudio dSDL1_PauseAudio = NULL; + +#ifdef WINDOWS_VERSION +#include + +static HMODULE openDll() +{ + HMODULE res = LoadLibraryA("SDL.dll"); + return res; +} + +static void* getDllProc(HMODULE aDllHandle, const char *aProcName) +{ + return GetProcAddress(aDllHandle, aProcName); +} + +#else +#include // dll functions + +static void * openDll() +{ + void * res; + res = dlopen("/Library/Frameworks/SDL.framework/SDL", RTLD_LAZY); + if (!res) res = dlopen("SDL.so", RTLD_LAZY); + return res; +} + +static void* getDllProc(void * aLibrary, const char *aProcName) +{ + return dlsym(aLibrary, aProcName); +} + +#endif + +static int load_dll() +{ +#ifdef WINDOWS_VERSION + HMODULE dll = NULL; +#else + void * dll = NULL; +#endif + + if (dSDL1_OpenAudio != NULL) + { + return 1; + } + + dll = openDll(); + + if (dll) + { + dSDL1_OpenAudio = (SDLOpenAudio)getDllProc(dll, "SDL_OpenAudio"); + dSDL1_CloseAudio = (SDLCloseAudio)getDllProc(dll, "SDL_CloseAudio"); + dSDL1_PauseAudio = (SDLPauseAudio)getDllProc(dll, "SDL_PauseAudio"); + + + if (dSDL1_OpenAudio && + dSDL1_CloseAudio && + dSDL1_PauseAudio) + { + return 1; + } + } + dSDL1_OpenAudio = NULL; + return 0; +} + +int dll_SDL1_found() +{ + return load_dll(); +} + +int dll_SDL1_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained) +{ + if (load_dll()) + return dSDL1_OpenAudio(desired, obtained); + return 0; +} + +void dll_SDL1_CloseAudio() +{ + if (load_dll()) + dSDL1_CloseAudio(); +} + +void dll_SDL1_PauseAudio(int pause_on) +{ + if (load_dll()) + dSDL1_PauseAudio(pause_on); +} diff --git a/src/soloud/src/backend/sdl/soloud_sdl2.cpp b/src/soloud/src/backend/sdl/soloud_sdl2.cpp new file mode 100644 index 0000000..1ff1414 --- /dev/null +++ b/src/soloud/src/backend/sdl/soloud_sdl2.cpp @@ -0,0 +1,133 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include + +#include "soloud.h" + +#if !defined(WITH_SDL2) + +namespace SoLoud +{ + result sdl2_init(SoLoud::Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer) + { + return NOT_IMPLEMENTED; + } +} + +#else + +#if defined(_MSC_VER) +#include +#else +#include +#endif +#include + + +extern "C" +{ + int dll_SDL2_found(); + + Uint32 dll_SDL2_WasInit(Uint32 flags); + int dll_SDL2_InitSubSystem(Uint32 flags); + SDL_AudioDeviceID dll_SDL2_OpenAudioDevice(const char* device, + int iscapture, + const SDL_AudioSpec* desired, + SDL_AudioSpec* obtained, + int allowed_changes); + void dll_SDL2_CloseAudioDevice(SDL_AudioDeviceID dev); + void dll_SDL2_PauseAudioDevice(SDL_AudioDeviceID dev, + int pause_on); +}; + + +namespace SoLoud +{ + static SDL_AudioSpec gActiveAudioSpec; + static SDL_AudioDeviceID gAudioDeviceID; + + void soloud_sdl2_audiomixer(void *userdata, Uint8 *stream, int len) + { + short *buf = (short*)stream; + SoLoud::Soloud *soloud = (SoLoud::Soloud *)userdata; + if (gActiveAudioSpec.format == AUDIO_F32) + { + int samples = len / (gActiveAudioSpec.channels * sizeof(float)); + soloud->mix((float *)buf, samples); + } + else // assume s16 if not float + { + int samples = len / (gActiveAudioSpec.channels * sizeof(short)); + soloud->mixSigned16(buf, samples); + } + } + + static void soloud_sdl2_deinit(SoLoud::Soloud *aSoloud) + { + dll_SDL2_CloseAudioDevice(gAudioDeviceID); + } + + result sdl2_init(SoLoud::Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer, unsigned int aChannels) + { + if (!dll_SDL2_found()) + return DLL_NOT_FOUND; + + if (!dll_SDL2_WasInit(SDL_INIT_AUDIO)) + { + if (dll_SDL2_InitSubSystem(SDL_INIT_AUDIO) < 0) + { + return UNKNOWN_ERROR; + } + } + + SDL_AudioSpec as; + as.freq = aSamplerate; + as.format = AUDIO_F32; + as.channels = aChannels; + as.samples = aBuffer; + as.callback = soloud_sdl2_audiomixer; + as.userdata = (void*)aSoloud; + + gAudioDeviceID = dll_SDL2_OpenAudioDevice(NULL, 0, &as, &gActiveAudioSpec, SDL_AUDIO_ALLOW_ANY_CHANGE & ~(SDL_AUDIO_ALLOW_FORMAT_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE)); + if (gAudioDeviceID == 0) + { + as.format = AUDIO_S16; + gAudioDeviceID = dll_SDL2_OpenAudioDevice(NULL, 0, &as, &gActiveAudioSpec, SDL_AUDIO_ALLOW_ANY_CHANGE & ~(SDL_AUDIO_ALLOW_FORMAT_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE)); + if (gAudioDeviceID == 0) + { + return UNKNOWN_ERROR; + } + } + + aSoloud->postinit(gActiveAudioSpec.freq, gActiveAudioSpec.samples, aFlags, gActiveAudioSpec.channels); + + aSoloud->mBackendCleanupFunc = soloud_sdl2_deinit; + + dll_SDL2_PauseAudioDevice(gAudioDeviceID, 0); + aSoloud->mBackendString = "SDL2 (dynamic)"; + return 0; + } + +}; +#endif \ No newline at end of file diff --git a/src/soloud/src/backend/sdl/soloud_sdl2_dll.c b/src/soloud/src/backend/sdl/soloud_sdl2_dll.c new file mode 100644 index 0000000..d67ba66 --- /dev/null +++ b/src/soloud/src/backend/sdl/soloud_sdl2_dll.c @@ -0,0 +1,160 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include +#if defined(_MSC_VER) +#define WINDOWS_VERSION +#include +#else +#include +#endif +#include + + +typedef Uint32 (*SDL2_WasInit_t)(Uint32 flags); +typedef int (*SDL2_InitSubSystem_t)(Uint32 flags); +typedef SDL_AudioDeviceID (*SDL2_OpenAudioDevice_t)(const char* device, + int iscapture, + const SDL_AudioSpec* desired, + SDL_AudioSpec* obtained, + int allowed_changes); +typedef void (*SDL2_CloseAudioDevice_t)(SDL_AudioDeviceID dev); +typedef void (*SDL2_PauseAudioDevice_t)(SDL_AudioDeviceID dev, + int pause_on); + +static SDL2_WasInit_t SDL2_WasInit = NULL; +static SDL2_InitSubSystem_t SDL2_InitSubSystem = NULL; +static SDL2_OpenAudioDevice_t SDL2_OpenAudioDevice = NULL; +static SDL2_CloseAudioDevice_t SDL2_CloseAudioDevice = NULL; +static SDL2_PauseAudioDevice_t SDL2_PauseAudioDevice = NULL; + +#ifdef WINDOWS_VERSION +#include + +static HMODULE openDll() +{ + HMODULE res = LoadLibraryA("SDL2.dll"); + return res; +} + +static void* getDllProc(HMODULE aDllHandle, const char *aProcName) +{ + return GetProcAddress(aDllHandle, aProcName); +} + +#else +#include // dll functions + +static void * openDll() +{ + void * res; + res = dlopen("/Library/Frameworks/SDL2.framework/SDL2", RTLD_LAZY); + if (!res) res = dlopen("SDL2.so", RTLD_LAZY); + return res; +} + +static void* getDllProc(void * aLibrary, const char *aProcName) +{ + return dlsym(aLibrary, aProcName); +} + +#endif + +static int load_dll() +{ +#ifdef WINDOWS_VERSION + HMODULE dll = NULL; +#else + void * dll = NULL; +#endif + + if (SDL2_OpenAudioDevice != NULL) + { + return 1; + } + + dll = openDll(); + + if (dll) + { + SDL2_WasInit = (SDL2_WasInit_t)getDllProc(dll, "SDL_WasInit"); + SDL2_InitSubSystem = (SDL2_InitSubSystem_t)getDllProc(dll, "SDL_InitSubSystem"); + SDL2_OpenAudioDevice = (SDL2_OpenAudioDevice_t)getDllProc(dll, "SDL_OpenAudioDevice"); + SDL2_CloseAudioDevice = (SDL2_CloseAudioDevice_t)getDllProc(dll, "SDL_CloseAudioDevice"); + SDL2_PauseAudioDevice = (SDL2_PauseAudioDevice_t)getDllProc(dll, "SDL_PauseAudioDevice"); + + if (SDL2_WasInit && + SDL2_InitSubSystem && + SDL2_OpenAudioDevice && + SDL2_CloseAudioDevice && + SDL2_PauseAudioDevice) + { + return 1; + } + } + SDL2_OpenAudioDevice = NULL; + return 0; +} + +int dll_SDL2_found() +{ + return load_dll(); +} + +Uint32 dll_SDL2_WasInit(Uint32 flags) +{ + if (SDL2_WasInit) + return SDL2_WasInit(flags); + return 0; +} + +int dll_SDL2_InitSubSystem(Uint32 flags) +{ + if (SDL2_InitSubSystem) + return SDL2_InitSubSystem(flags); + return -1; +} + +SDL_AudioDeviceID dll_SDL2_OpenAudioDevice(const char* device, + int iscapture, + const SDL_AudioSpec* desired, + SDL_AudioSpec* obtained, + int allowed_changes) +{ + if (SDL2_OpenAudioDevice) + return SDL2_OpenAudioDevice(device, iscapture, desired, obtained, allowed_changes); + return 0; +} + +void dll_SDL2_CloseAudioDevice(SDL_AudioDeviceID dev) +{ + if (SDL2_CloseAudioDevice) + SDL2_CloseAudioDevice(dev); +} + +void dll_SDL2_PauseAudioDevice(SDL_AudioDeviceID dev, + int pause_on) +{ + if (SDL2_PauseAudioDevice) + SDL2_PauseAudioDevice(dev, pause_on); +} diff --git a/src/soloud/src/backend/sdl2_static/soloud_sdl2_static.cpp b/src/soloud/src/backend/sdl2_static/soloud_sdl2_static.cpp new file mode 100644 index 0000000..25744f3 --- /dev/null +++ b/src/soloud/src/backend/sdl2_static/soloud_sdl2_static.cpp @@ -0,0 +1,111 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include + +#include "soloud.h" + +#if !defined(WITH_SDL2_STATIC) + +namespace SoLoud +{ + result sdl2static_init(SoLoud::Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer) + { + return NOT_IMPLEMENTED; + } +} + +#else + +#if defined(_MSC_VER) +#include +#else +#include +#endif +#include + +namespace SoLoud +{ + static SDL_AudioSpec gActiveAudioSpec; + static SDL_AudioDeviceID gAudioDeviceID; + + void soloud_sdl2static_audiomixer(void *userdata, Uint8 *stream, int len) + { + short *buf = (short*)stream; + SoLoud::Soloud *soloud = (SoLoud::Soloud *)userdata; + if (gActiveAudioSpec.format == AUDIO_F32) + { + int samples = len / (gActiveAudioSpec.channels * sizeof(float)); + soloud->mix((float *)buf, samples); + } + else // assume s16 if not float + { + int samples = len / (gActiveAudioSpec.channels * sizeof(short)); + soloud->mixSigned16(buf, samples); + } + } + + static void soloud_sdl2static_deinit(SoLoud::Soloud *aSoloud) + { + SDL_CloseAudioDevice(gAudioDeviceID); + } + + result sdl2static_init(SoLoud::Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer, unsigned int aChannels) + { + if (!SDL_WasInit(SDL_INIT_AUDIO)) + { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) + { + return UNKNOWN_ERROR; + } + } + + SDL_AudioSpec as; + as.freq = aSamplerate; + as.format = AUDIO_F32; + as.channels = aChannels; + as.samples = aBuffer; + as.callback = soloud_sdl2static_audiomixer; + as.userdata = (void*)aSoloud; + + gAudioDeviceID = SDL_OpenAudioDevice(NULL, 0, &as, &gActiveAudioSpec, SDL_AUDIO_ALLOW_ANY_CHANGE & ~(SDL_AUDIO_ALLOW_FORMAT_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE)); + if (gAudioDeviceID == 0) + { + as.format = AUDIO_S16; + gAudioDeviceID = SDL_OpenAudioDevice(NULL, 0, &as, &gActiveAudioSpec, SDL_AUDIO_ALLOW_ANY_CHANGE & ~(SDL_AUDIO_ALLOW_FORMAT_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE)); + if (gAudioDeviceID == 0) + { + return UNKNOWN_ERROR; + } + } + + aSoloud->postinit(gActiveAudioSpec.freq, gActiveAudioSpec.samples, aFlags, gActiveAudioSpec.channels); + + aSoloud->mBackendCleanupFunc = soloud_sdl2static_deinit; + + SDL_PauseAudioDevice(gAudioDeviceID, 0); + aSoloud->mBackendString = "SDL2 (static)"; + return 0; + } +}; +#endif \ No newline at end of file diff --git a/src/soloud/src/backend/sdl_static/soloud_sdl_static.cpp b/src/soloud/src/backend/sdl_static/soloud_sdl_static.cpp new file mode 100644 index 0000000..f7d9408 --- /dev/null +++ b/src/soloud/src/backend/sdl_static/soloud_sdl_static.cpp @@ -0,0 +1,89 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include + +#include "soloud.h" + +#if !defined(WITH_SDL_STATIC) + +namespace SoLoud +{ + result sdlstatic_init(SoLoud::Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer) + { + return NOT_IMPLEMENTED; + } +} + +#else + +#if defined(_MSC_VER) +#include +#else +#include +#endif +#include + + +namespace SoLoud +{ + static SDL_AudioSpec gActiveAudioSpec; + void soloud_sdlstatic_audiomixer(void *userdata, Uint8 *stream, int len) + { + short *buf = (short*)stream; + SoLoud::Soloud *soloud = (SoLoud::Soloud *)userdata; + int samples = len / (gActiveAudioSpec.channels * sizeof(short)); + soloud->mixSigned16(buf, samples); + } + + static void soloud_sdlstatic_deinit(SoLoud::Soloud *aSoloud) + { + SDL_CloseAudio(); + } + + result sdlstatic_init(SoLoud::Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer, unsigned int aChannels) + { + SDL_AudioSpec as; + as.freq = aSamplerate; + as.format = AUDIO_S16; + as.channels = aChannels; + as.samples = aBuffer; + as.callback = soloud_sdlstatic_audiomixer; + as.userdata = (void*)aSoloud; + + if (SDL_OpenAudio(&as, &gActiveAudioSpec) < 0) + { + return UNKNOWN_ERROR; + } + + aSoloud->postinit(gActiveAudioSpec.freq, gActiveAudioSpec.samples, aFlags, gActiveAudioSpec.channels); + + aSoloud->mBackendCleanupFunc = soloud_sdlstatic_deinit; + + SDL_PauseAudio(0); + aSoloud->mBackendString = "SDL1 (static)"; + return 0; + } + +}; +#endif \ No newline at end of file diff --git a/src/soloud/src/backend/wasapi/soloud_wasapi.cpp b/src/soloud/src/backend/wasapi/soloud_wasapi.cpp new file mode 100644 index 0000000..5e87844 --- /dev/null +++ b/src/soloud/src/backend/wasapi/soloud_wasapi.cpp @@ -0,0 +1,203 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" +#include "soloud_thread.h" + +#if !defined(WITH_WASAPI) + +namespace SoLoud +{ + result wasapi_init(Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer) + { + return NOT_IMPLEMENTED; + } +}; + +#else + +#include +#include +#include + +#define SAFE_RELEASE(x) \ + if (0 != (x)) \ + { \ + (x)->Release(); \ + (x) = 0; \ + } + +namespace SoLoud +{ + struct WASAPIData + { + IMMDeviceEnumerator *deviceEnumerator; + IMMDevice *device; + IAudioClient *audioClient; + IAudioRenderClient *renderClient; + HANDLE bufferEndEvent; + HANDLE audioProcessingDoneEvent; + Thread::ThreadHandle thread; + Soloud *soloud; + UINT32 bufferFrames; + int channels; + }; + + static void wasapiSubmitBuffer(WASAPIData *aData, UINT32 aFrames) + { + BYTE *buffer = 0; + if (FAILED(aData->renderClient->GetBuffer(aFrames, &buffer))) + { + return; + } + aData->soloud->mixSigned16((short *)buffer, aFrames); + aData->renderClient->ReleaseBuffer(aFrames, 0); + } + + static void wasapiThread(LPVOID aParam) + { + WASAPIData *data = static_cast(aParam); + wasapiSubmitBuffer(data, data->bufferFrames); + data->audioClient->Start(); + while (WAIT_OBJECT_0 != WaitForSingleObject(data->audioProcessingDoneEvent, 0)) + { + WaitForSingleObject(data->bufferEndEvent, INFINITE); + UINT32 padding = 0; + if (FAILED(data->audioClient->GetCurrentPadding(&padding))) + { + continue; + } + UINT32 frames = data->bufferFrames - padding; + wasapiSubmitBuffer(data, frames); + } + } + + static void wasapiCleanup(Soloud *aSoloud) + { + if (0 == aSoloud->mBackendData) + { + return; + } + WASAPIData *data = static_cast(aSoloud->mBackendData); + SetEvent(data->audioProcessingDoneEvent); + SetEvent(data->bufferEndEvent); + if (data->thread) + { + Thread::wait(data->thread); + Thread::release(data->thread); + } + CloseHandle(data->bufferEndEvent); + CloseHandle(data->audioProcessingDoneEvent); + if (0 != data->audioClient) + { + data->audioClient->Stop(); + } + SAFE_RELEASE(data->renderClient); + SAFE_RELEASE(data->audioClient); + SAFE_RELEASE(data->device); + SAFE_RELEASE(data->deviceEnumerator); + delete data; + aSoloud->mBackendData = 0; + CoUninitialize(); + } + + result wasapi_init(Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer, unsigned int aChannels) + { + return UNKNOWN_ERROR; + CoInitializeEx(0, COINIT_MULTITHREADED); + WASAPIData *data = new WASAPIData; + ZeroMemory(data, sizeof(WASAPIData)); + aSoloud->mBackendData = data; + aSoloud->mBackendCleanupFunc = wasapiCleanup; + + data->bufferEndEvent = CreateEvent(0, FALSE, FALSE, 0); + if (0 == data->bufferEndEvent) + { + return UNKNOWN_ERROR; + } + data->audioProcessingDoneEvent = CreateEvent(0, FALSE, FALSE, 0); + if (0 == data->audioProcessingDoneEvent) + { + return UNKNOWN_ERROR; + } + if (FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL, + __uuidof(IMMDeviceEnumerator), + reinterpret_cast(&data->deviceEnumerator)))) + { + return UNKNOWN_ERROR; + } + if (FAILED(data->deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, + &data->device))) + { + return UNKNOWN_ERROR; + } + if (FAILED(data->device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, 0, + reinterpret_cast(&data->audioClient)))) + { + return UNKNOWN_ERROR; + } + WAVEFORMATEX format; + ZeroMemory(&format, sizeof(WAVEFORMATEX)); + format.nChannels = aChannels; + format.nSamplesPerSec = aSamplerate; + format.wFormatTag = WAVE_FORMAT_PCM; + format.wBitsPerSample = sizeof(short)*8; + format.nBlockAlign = (format.nChannels*format.wBitsPerSample)/8; + format.nAvgBytesPerSec = format.nSamplesPerSec*format.nBlockAlign; + REFERENCE_TIME dur = static_cast(static_cast(aBuffer) + / (static_cast(aSamplerate)*(1.0/10000000.0))); + HRESULT res = data->audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + dur, 0, &format, 0); + if (FAILED(res)) + { + return UNKNOWN_ERROR; + } + data->bufferFrames = 0; + if (FAILED(data->audioClient->GetBufferSize(&data->bufferFrames))) + { + return UNKNOWN_ERROR; + } + if (FAILED(data->audioClient->GetService(__uuidof(IAudioRenderClient), + reinterpret_cast(&data->renderClient)))) + { + return UNKNOWN_ERROR; + } + if (FAILED(data->audioClient->SetEventHandle(data->bufferEndEvent))) + { + return UNKNOWN_ERROR; + } + data->channels = format.nChannels; + data->soloud = aSoloud; + aSoloud->postinit(aSamplerate, data->bufferFrames * format.nChannels, aFlags, 2); + data->thread = Thread::createThread(wasapiThread, data); + if (0 == data->thread) + { + return UNKNOWN_ERROR; + } + aSoloud->mBackendString = "WASAPI"; + return 0; + } +}; +#endif \ No newline at end of file diff --git a/src/soloud/src/backend/winmm/soloud_winmm.cpp b/src/soloud/src/backend/winmm/soloud_winmm.cpp new file mode 100644 index 0000000..dd78134 --- /dev/null +++ b/src/soloud/src/backend/winmm/soloud_winmm.cpp @@ -0,0 +1,174 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" +#include "soloud_thread.h" + +#if !defined(WITH_WINMM) + +namespace SoLoud +{ + result winmm_init(Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer) + { + return NOT_IMPLEMENTED; + } +}; + +#else + +#include +#include + +#ifdef _MSC_VER +#pragma comment(lib, "winmm.lib") +#endif + +namespace SoLoud +{ + static const int BUFFER_COUNT = 2; + + struct SoLoudWinMMData + { + AlignedFloatBuffer buffer; + short *sampleBuffer[BUFFER_COUNT]; + WAVEHDR header[BUFFER_COUNT]; + HWAVEOUT waveOut; + HANDLE bufferEndEvent; + HANDLE audioProcessingDoneEvent; + Soloud *soloud; + int samples; + Thread::ThreadHandle threadHandle; + }; + + static void winMMThread(LPVOID aParam) + { + SoLoudWinMMData *data = static_cast(aParam); + while (WAIT_OBJECT_0 != WaitForSingleObject(data->audioProcessingDoneEvent, 0)) + { + for (int i=0;iheader[i].dwFlags & WHDR_INQUEUE)) + { + continue; + } + short *tgtBuf = data->sampleBuffer[i]; + + data->soloud->mixSigned16(tgtBuf, data->samples); + + if (MMSYSERR_NOERROR != waveOutWrite(data->waveOut, &data->header[i], + sizeof(WAVEHDR))) + { + return; + } + } + WaitForSingleObject(data->bufferEndEvent, INFINITE); + } + } + + static void winMMCleanup(Soloud *aSoloud) + { + if (0 == aSoloud->mBackendData) + { + return; + } + SoLoudWinMMData *data = static_cast(aSoloud->mBackendData); + SetEvent(data->audioProcessingDoneEvent); + SetEvent(data->bufferEndEvent); + if (data->threadHandle) + { + Thread::wait(data->threadHandle); + Thread::release(data->threadHandle); + } + waveOutReset(data->waveOut); + for (int i=0;iwaveOut, &data->header[i], sizeof(WAVEHDR)); + if (0 != data->sampleBuffer[i]) + { + delete[] data->sampleBuffer[i]; + } + } + waveOutClose(data->waveOut); + CloseHandle(data->audioProcessingDoneEvent); + CloseHandle(data->bufferEndEvent); + delete data; + aSoloud->mBackendData = 0; + } + + result winmm_init(Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer, unsigned int aChannels) + { + SoLoudWinMMData *data = new SoLoudWinMMData; + ZeroMemory(data, sizeof(SoLoudWinMMData)); + aSoloud->mBackendData = data; + aSoloud->mBackendCleanupFunc = winMMCleanup; + data->samples = aBuffer; + data->soloud = aSoloud; + data->bufferEndEvent = CreateEvent(0, FALSE, FALSE, 0); + if (0 == data->bufferEndEvent) + { + return UNKNOWN_ERROR; + } + data->audioProcessingDoneEvent = CreateEvent(0, FALSE, FALSE, 0); + if (0 == data->audioProcessingDoneEvent) + { + return UNKNOWN_ERROR; + } + WAVEFORMATEX format; + ZeroMemory(&format, sizeof(WAVEFORMATEX)); + format.nChannels = aChannels; + format.nSamplesPerSec = aSamplerate; + format.wFormatTag = WAVE_FORMAT_PCM; + format.wBitsPerSample = sizeof(short)*8; + format.nBlockAlign = (format.nChannels*format.wBitsPerSample)/8; + format.nAvgBytesPerSec = format.nSamplesPerSec*format.nBlockAlign; + if (MMSYSERR_NOERROR != waveOutOpen(&data->waveOut, WAVE_MAPPER, &format, + reinterpret_cast(data->bufferEndEvent), 0, CALLBACK_EVENT)) + { + return UNKNOWN_ERROR; + } + data->buffer.init(data->samples*format.nChannels); + for (int i=0;isampleBuffer[i] = new short[data->samples*format.nChannels]; + ZeroMemory(&data->header[i], sizeof(WAVEHDR)); + data->header[i].dwBufferLength = data->samples*sizeof(short)*format.nChannels; + data->header[i].lpData = reinterpret_cast(data->sampleBuffer[i]); + if (MMSYSERR_NOERROR != waveOutPrepareHeader(data->waveOut, &data->header[i], + sizeof(WAVEHDR))) + { + return UNKNOWN_ERROR; + } + } + aSoloud->postinit(aSamplerate, data->samples * format.nChannels, aFlags, aChannels); + data->threadHandle = Thread::createThread(winMMThread, data); + if (0 == data->threadHandle) + { + return UNKNOWN_ERROR; + } + aSoloud->mBackendString = "WinMM"; + return 0; + } +}; + +#endif \ No newline at end of file diff --git a/src/soloud/src/backend/xaudio2/soloud_xaudio2.cpp b/src/soloud/src/backend/xaudio2/soloud_xaudio2.cpp new file mode 100644 index 0000000..1dd1ed9 --- /dev/null +++ b/src/soloud/src/backend/xaudio2/soloud_xaudio2.cpp @@ -0,0 +1,247 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" +#include "soloud_thread.h" + +#if !defined(WITH_XAUDIO2) + +namespace SoLoud +{ + result xaudio2_init(Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer) + { + return NOT_IMPLEMENTED; + } +}; + +#else + +#include + +#ifdef _MSC_VER +#include +#pragma comment(lib, "xaudio2.lib") +#else +#include "backend/xaudio2/xaudio2.h" +#endif + +namespace SoLoud +{ + static const int BUFFER_COUNT = 2; + + struct XAudio2Data + { + float *buffer[BUFFER_COUNT]; + IXAudio2 *xaudio2; + IXAudio2MasteringVoice *masteringVoice; + IXAudio2SourceVoice *sourceVoice; + HANDLE bufferEndEvent; + HANDLE audioProcessingDoneEvent; + class VoiceCallback *voiceCb; + Thread::ThreadHandle thread; + Soloud *soloud; + int samples; + UINT32 bufferLengthBytes; + }; + + class VoiceCallback : public IXAudio2VoiceCallback + { + public: + VoiceCallback(HANDLE aBufferEndEvent) + : IXAudio2VoiceCallback(), mBufferEndEvent(aBufferEndEvent) {} + virtual ~VoiceCallback() {} + + private: + // Called just before this voice's processing pass begins. + void __stdcall OnVoiceProcessingPassStart(UINT32 aBytesRequired) {} + + // Called just after this voice's processing pass ends. + void __stdcall OnVoiceProcessingPassEnd() {} + + // Called when this voice has just finished playing a buffer stream + // (as marked with the XAUDIO2_END_OF_STREAM flag on the last buffer). + void __stdcall OnStreamEnd() {} + + // Called when this voice is about to start processing a new buffer. + void __stdcall OnBufferStart(void *aBufferContext) {} + + // Called when this voice has just finished processing a buffer. + // The buffer can now be reused or destroyed. + void __stdcall OnBufferEnd(void *aBufferContext) + { + SetEvent(mBufferEndEvent); + } + + // Called when this voice has just reached the end position of a loop. + void __stdcall OnLoopEnd(void *aBufferContext) {} + + // Called in the event of a critical error during voice processing, + // such as a failing xAPO or an error from the hardware XMA decoder. + // The voice may have to be destroyed and re-created to recover from + // the error. The callback arguments report which buffer was being + // processed when the error occurred, and its HRESULT code. + void __stdcall OnVoiceError(void *aBufferContext, HRESULT aError) {} + + HANDLE mBufferEndEvent; + }; + + static void xaudio2Thread(LPVOID aParam) + { + XAudio2Data *data = static_cast(aParam); + int bufferIndex = 0; + while (WAIT_OBJECT_0 != WaitForSingleObject(data->audioProcessingDoneEvent, 0)) + { + XAUDIO2_VOICE_STATE state; + data->sourceVoice->GetState(&state); + while (state.BuffersQueued < BUFFER_COUNT) + { + data->soloud->mix(data->buffer[bufferIndex], data->samples); + XAUDIO2_BUFFER info = {0}; + info.AudioBytes = data->bufferLengthBytes; + info.pAudioData = reinterpret_cast(data->buffer[bufferIndex]); + data->sourceVoice->SubmitSourceBuffer(&info); + ++bufferIndex; + if (bufferIndex >= BUFFER_COUNT) + { + bufferIndex = 0; + } + data->sourceVoice->GetState(&state); + } + WaitForSingleObject(data->bufferEndEvent, INFINITE); + } + } + + static void xaudio2Cleanup(Soloud *aSoloud) + { + if (0 == aSoloud->mBackendData) + { + return; + } + XAudio2Data *data = static_cast(aSoloud->mBackendData); + SetEvent(data->audioProcessingDoneEvent); + SetEvent(data->bufferEndEvent); + Thread::wait(data->thread); + Thread::release(data->thread); + if (0 != data->sourceVoice) + { + data->sourceVoice->Stop(); + data->sourceVoice->FlushSourceBuffers(); + } + if (0 != data->xaudio2) + { + data->xaudio2->StopEngine(); + } + if (0 != data->sourceVoice) + { + data->sourceVoice->DestroyVoice(); + } + if (0 != data->voiceCb) + { + delete data->voiceCb; + } + if (0 != data->masteringVoice) + { + data->masteringVoice->DestroyVoice(); + } + if (0 != data->xaudio2) + { + data->xaudio2->Release(); + } + for (int i=0;ibuffer[i]) + { + delete[] data->buffer[i]; + } + } + CloseHandle(data->bufferEndEvent); + CloseHandle(data->audioProcessingDoneEvent); + delete data; + aSoloud->mBackendData = 0; + CoUninitialize(); + } + + result xaudio2_init(Soloud *aSoloud, unsigned int aFlags, unsigned int aSamplerate, unsigned int aBuffer, unsigned int aChannels) + { + if (FAILED(CoInitializeEx(0, COINIT_MULTITHREADED))) + { + return UNKNOWN_ERROR; + } + XAudio2Data *data = new XAudio2Data; + ZeroMemory(data, sizeof(XAudio2Data)); + aSoloud->mBackendData = data; + aSoloud->mBackendCleanupFunc = xaudio2Cleanup; + data->bufferEndEvent = CreateEvent(0, FALSE, FALSE, 0); + if (0 == data->bufferEndEvent) + { + return UNKNOWN_ERROR; + } + data->audioProcessingDoneEvent = CreateEvent(0, FALSE, FALSE, 0); + if (0 == data->audioProcessingDoneEvent) + { + return UNKNOWN_ERROR; + } + WAVEFORMATEX format; + ZeroMemory(&format, sizeof(WAVEFORMATEX)); + format.nChannels = 2; + format.nSamplesPerSec = aSamplerate; + format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + format.nAvgBytesPerSec = aSamplerate*sizeof(float)*format.nChannels; + format.nBlockAlign = sizeof(float)*format.nChannels; + format.wBitsPerSample = sizeof(float)*8; + if (FAILED(XAudio2Create(&data->xaudio2))) + { + return UNKNOWN_ERROR; + } + if (FAILED(data->xaudio2->CreateMasteringVoice(&data->masteringVoice, + format.nChannels, aSamplerate))) + { + return UNKNOWN_ERROR; + } + data->voiceCb = new VoiceCallback(data->bufferEndEvent); + if (FAILED(data->xaudio2->CreateSourceVoice(&data->sourceVoice, + &format, XAUDIO2_VOICE_NOSRC|XAUDIO2_VOICE_NOPITCH, 2.f, data->voiceCb))) + { + return UNKNOWN_ERROR; + } + data->bufferLengthBytes = aBuffer * format.nChannels * sizeof(float); + for (int i=0;ibuffer[i] = new float[aBuffer * format.nChannels]; + } + data->samples = aBuffer; + data->soloud = aSoloud; + aSoloud->postinit(aSamplerate, aBuffer * format.nChannels, aFlags, 2); + data->thread = Thread::createThread(xaudio2Thread, data); + if (0 == data->thread) + { + return UNKNOWN_ERROR; + } + data->sourceVoice->Start(); + aSoloud->mBackendString = "XAudio2"; + return 0; + } +}; + +#endif \ No newline at end of file diff --git a/src/soloud/src/c_api/soloud.def b/src/soloud/src/c_api/soloud.def new file mode 100644 index 0000000..490fc3f --- /dev/null +++ b/src/soloud/src/c_api/soloud.def @@ -0,0 +1,395 @@ +EXPORTS + Soloud_destroy + Soloud_create + Soloud_init + Soloud_initEx + Soloud_deinit + Soloud_getVersion + Soloud_getErrorString + Soloud_getBackendId + Soloud_getBackendString + Soloud_getBackendChannels + Soloud_getBackendSamplerate + Soloud_getBackendBufferSize + Soloud_setSpeakerPosition + Soloud_getSpeakerPosition + Soloud_play + Soloud_playEx + Soloud_playClocked + Soloud_playClockedEx + Soloud_play3d + Soloud_play3dEx + Soloud_play3dClocked + Soloud_play3dClockedEx + Soloud_playBackground + Soloud_playBackgroundEx + Soloud_seek + Soloud_stop + Soloud_stopAll + Soloud_stopAudioSource + Soloud_countAudioSource + Soloud_setFilterParameter + Soloud_getFilterParameter + Soloud_fadeFilterParameter + Soloud_oscillateFilterParameter + Soloud_getStreamTime + Soloud_getStreamPosition + Soloud_getPause + Soloud_getVolume + Soloud_getOverallVolume + Soloud_getPan + Soloud_getSamplerate + Soloud_getProtectVoice + Soloud_getActiveVoiceCount + Soloud_getVoiceCount + Soloud_isValidVoiceHandle + Soloud_getRelativePlaySpeed + Soloud_getPostClipScaler + Soloud_getGlobalVolume + Soloud_getMaxActiveVoiceCount + Soloud_getLooping + Soloud_getLoopPoint + Soloud_setLoopPoint + Soloud_setLooping + Soloud_setMaxActiveVoiceCount + Soloud_setInaudibleBehavior + Soloud_setGlobalVolume + Soloud_setPostClipScaler + Soloud_setPause + Soloud_setPauseAll + Soloud_setRelativePlaySpeed + Soloud_setProtectVoice + Soloud_setSamplerate + Soloud_setPan + Soloud_setPanAbsolute + Soloud_setPanAbsoluteEx + Soloud_setVolume + Soloud_setDelaySamples + Soloud_fadeVolume + Soloud_fadePan + Soloud_fadeRelativePlaySpeed + Soloud_fadeGlobalVolume + Soloud_schedulePause + Soloud_scheduleStop + Soloud_oscillateVolume + Soloud_oscillatePan + Soloud_oscillateRelativePlaySpeed + Soloud_oscillateGlobalVolume + Soloud_setGlobalFilter + Soloud_setVisualizationEnable + Soloud_calcFFT + Soloud_getWave + Soloud_getApproximateVolume + Soloud_getLoopCount + Soloud_getInfo + Soloud_createVoiceGroup + Soloud_destroyVoiceGroup + Soloud_addVoiceToGroup + Soloud_isVoiceGroup + Soloud_isVoiceGroupEmpty + Soloud_update3dAudio + Soloud_set3dSoundSpeed + Soloud_get3dSoundSpeed + Soloud_set3dListenerParameters + Soloud_set3dListenerParametersEx + Soloud_set3dListenerPosition + Soloud_set3dListenerAt + Soloud_set3dListenerUp + Soloud_set3dListenerVelocity + Soloud_set3dSourceParameters + Soloud_set3dSourceParametersEx + Soloud_set3dSourcePosition + Soloud_set3dSourceVelocity + Soloud_set3dSourceMinMaxDistance + Soloud_set3dSourceAttenuation + Soloud_set3dSourceDopplerFactor + Soloud_mix + Soloud_mixSigned16 + AudioAttenuator_destroy + AudioAttenuator_attenuate + BassboostFilter_destroy + BassboostFilter_setParams + BassboostFilter_create + BiquadResonantFilter_destroy + BiquadResonantFilter_create + BiquadResonantFilter_setParams + Bus_destroy + Bus_create + Bus_setFilter + Bus_play + Bus_playEx + Bus_playClocked + Bus_playClockedEx + Bus_play3d + Bus_play3dEx + Bus_play3dClocked + Bus_play3dClockedEx + Bus_setChannels + Bus_setVisualizationEnable + Bus_calcFFT + Bus_getWave + Bus_getApproximateVolume + Bus_setVolume + Bus_setLooping + Bus_set3dMinMaxDistance + Bus_set3dAttenuation + Bus_set3dDopplerFactor + Bus_set3dListenerRelative + Bus_set3dDistanceDelay + Bus_set3dCollider + Bus_set3dColliderEx + Bus_set3dAttenuator + Bus_setInaudibleBehavior + Bus_setLoopPoint + Bus_getLoopPoint + Bus_stop + DCRemovalFilter_destroy + DCRemovalFilter_create + DCRemovalFilter_setParams + DCRemovalFilter_setParamsEx + EchoFilter_destroy + EchoFilter_create + EchoFilter_setParams + EchoFilter_setParamsEx + FFTFilter_destroy + FFTFilter_create + FlangerFilter_destroy + FlangerFilter_create + FlangerFilter_setParams + LofiFilter_destroy + LofiFilter_create + LofiFilter_setParams + Monotone_destroy + Monotone_create + Monotone_setParams + Monotone_setParamsEx + Monotone_load + Monotone_loadMem + Monotone_loadMemEx + Monotone_loadFile + Monotone_setVolume + Monotone_setLooping + Monotone_set3dMinMaxDistance + Monotone_set3dAttenuation + Monotone_set3dDopplerFactor + Monotone_set3dListenerRelative + Monotone_set3dDistanceDelay + Monotone_set3dCollider + Monotone_set3dColliderEx + Monotone_set3dAttenuator + Monotone_setInaudibleBehavior + Monotone_setLoopPoint + Monotone_getLoopPoint + Monotone_setFilter + Monotone_stop + Openmpt_destroy + Openmpt_create + Openmpt_load + Openmpt_loadMem + Openmpt_loadMemEx + Openmpt_loadFile + Openmpt_setVolume + Openmpt_setLooping + Openmpt_set3dMinMaxDistance + Openmpt_set3dAttenuation + Openmpt_set3dDopplerFactor + Openmpt_set3dListenerRelative + Openmpt_set3dDistanceDelay + Openmpt_set3dCollider + Openmpt_set3dColliderEx + Openmpt_set3dAttenuator + Openmpt_setInaudibleBehavior + Openmpt_setLoopPoint + Openmpt_getLoopPoint + Openmpt_setFilter + Openmpt_stop + Queue_destroy + Queue_create + Queue_play + Queue_getQueueCount + Queue_isCurrentlyPlaying + Queue_setParamsFromAudioSource + Queue_setParams + Queue_setParamsEx + Queue_setVolume + Queue_setLooping + Queue_set3dMinMaxDistance + Queue_set3dAttenuation + Queue_set3dDopplerFactor + Queue_set3dListenerRelative + Queue_set3dDistanceDelay + Queue_set3dCollider + Queue_set3dColliderEx + Queue_set3dAttenuator + Queue_setInaudibleBehavior + Queue_setLoopPoint + Queue_getLoopPoint + Queue_setFilter + Queue_stop + RobotizeFilter_destroy + RobotizeFilter_create + Prg_destroy + Prg_create + Prg_rand + Prg_srand + Sfxr_destroy + Sfxr_create + Sfxr_resetParams + Sfxr_loadParams + Sfxr_loadParamsMem + Sfxr_loadParamsMemEx + Sfxr_loadParamsFile + Sfxr_loadPreset + Sfxr_setVolume + Sfxr_setLooping + Sfxr_set3dMinMaxDistance + Sfxr_set3dAttenuation + Sfxr_set3dDopplerFactor + Sfxr_set3dListenerRelative + Sfxr_set3dDistanceDelay + Sfxr_set3dCollider + Sfxr_set3dColliderEx + Sfxr_set3dAttenuator + Sfxr_setInaudibleBehavior + Sfxr_setLoopPoint + Sfxr_getLoopPoint + Sfxr_setFilter + Sfxr_stop + Speech_destroy + Speech_create + Speech_setText + Speech_setParams + Speech_setParamsEx + Speech_setVolume + Speech_setLooping + Speech_set3dMinMaxDistance + Speech_set3dAttenuation + Speech_set3dDopplerFactor + Speech_set3dListenerRelative + Speech_set3dDistanceDelay + Speech_set3dCollider + Speech_set3dColliderEx + Speech_set3dAttenuator + Speech_setInaudibleBehavior + Speech_setLoopPoint + Speech_getLoopPoint + Speech_setFilter + Speech_stop + TedSid_destroy + TedSid_create + TedSid_load + TedSid_loadToMem + TedSid_loadMem + TedSid_loadMemEx + TedSid_loadFileToMem + TedSid_loadFile + TedSid_setVolume + TedSid_setLooping + TedSid_set3dMinMaxDistance + TedSid_set3dAttenuation + TedSid_set3dDopplerFactor + TedSid_set3dListenerRelative + TedSid_set3dDistanceDelay + TedSid_set3dCollider + TedSid_set3dColliderEx + TedSid_set3dAttenuator + TedSid_setInaudibleBehavior + TedSid_setLoopPoint + TedSid_getLoopPoint + TedSid_setFilter + TedSid_stop + Vic_destroy + Vic_create + Vic_setModel + Vic_getModel + Vic_setRegister + Vic_getRegister + Vic_setVolume + Vic_setLooping + Vic_set3dMinMaxDistance + Vic_set3dAttenuation + Vic_set3dDopplerFactor + Vic_set3dListenerRelative + Vic_set3dDistanceDelay + Vic_set3dCollider + Vic_set3dColliderEx + Vic_set3dAttenuator + Vic_setInaudibleBehavior + Vic_setLoopPoint + Vic_getLoopPoint + Vic_setFilter + Vic_stop + Vizsn_destroy + Vizsn_create + Vizsn_setText + Vizsn_setVolume + Vizsn_setLooping + Vizsn_set3dMinMaxDistance + Vizsn_set3dAttenuation + Vizsn_set3dDopplerFactor + Vizsn_set3dListenerRelative + Vizsn_set3dDistanceDelay + Vizsn_set3dCollider + Vizsn_set3dColliderEx + Vizsn_set3dAttenuator + Vizsn_setInaudibleBehavior + Vizsn_setLoopPoint + Vizsn_getLoopPoint + Vizsn_setFilter + Vizsn_stop + Wav_destroy + Wav_create + Wav_load + Wav_loadMem + Wav_loadMemEx + Wav_loadFile + Wav_loadRawWave8 + Wav_loadRawWave8Ex + Wav_loadRawWave16 + Wav_loadRawWave16Ex + Wav_loadRawWave + Wav_loadRawWaveEx + Wav_getLength + Wav_setVolume + Wav_setLooping + Wav_set3dMinMaxDistance + Wav_set3dAttenuation + Wav_set3dDopplerFactor + Wav_set3dListenerRelative + Wav_set3dDistanceDelay + Wav_set3dCollider + Wav_set3dColliderEx + Wav_set3dAttenuator + Wav_setInaudibleBehavior + Wav_setLoopPoint + Wav_getLoopPoint + Wav_setFilter + Wav_stop + WaveShaperFilter_destroy + WaveShaperFilter_setParams + WaveShaperFilter_setParamsEx + WaveShaperFilter_create + WavStream_destroy + WavStream_create + WavStream_load + WavStream_loadMem + WavStream_loadMemEx + WavStream_loadToMem + WavStream_loadFile + WavStream_loadFileToMem + WavStream_getLength + WavStream_setVolume + WavStream_setLooping + WavStream_set3dMinMaxDistance + WavStream_set3dAttenuation + WavStream_set3dDopplerFactor + WavStream_set3dListenerRelative + WavStream_set3dDistanceDelay + WavStream_set3dCollider + WavStream_set3dColliderEx + WavStream_set3dAttenuator + WavStream_setInaudibleBehavior + WavStream_setLoopPoint + WavStream_getLoopPoint + WavStream_setFilter + WavStream_stop diff --git a/src/soloud/src/c_api/soloud_c.cpp b/src/soloud/src/c_api/soloud_c.cpp new file mode 100644 index 0000000..068c1c1 --- /dev/null +++ b/src/soloud/src/c_api/soloud_c.cpp @@ -0,0 +1,2383 @@ +/* ************************************************** + * WARNING: this is a generated file. Do not edit. * + * Any edits will be overwritten by the generator. * + ************************************************** */ + +/* +SoLoud audio engine +Copyright (c) 2013-2016 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* SoLoud C-Api Code Generator (c)2013-2018 Jari Komppa http://iki.fi/sol/ */ + +#include "../include/soloud.h" +#include "../include/soloud_audiosource.h" +#include "../include/soloud_bassboostfilter.h" +#include "../include/soloud_biquadresonantfilter.h" +#include "../include/soloud_bus.h" +#include "../include/soloud_dcremovalfilter.h" +#include "../include/soloud_echofilter.h" +#include "../include/soloud_fader.h" +#include "../include/soloud_fft.h" +#include "../include/soloud_fftfilter.h" +#include "../include/soloud_filter.h" +#include "../include/soloud_flangerfilter.h" +#include "../include/soloud_lofifilter.h" +#include "../include/soloud_monotone.h" +#include "../include/soloud_openmpt.h" +#include "../include/soloud_queue.h" +#include "../include/soloud_robotizefilter.h" +#include "../include/soloud_sfxr.h" +#include "../include/soloud_speech.h" +#include "../include/soloud_tedsid.h" +#include "../include/soloud_vic.h" +#include "../include/soloud_vizsn.h" +#include "../include/soloud_wav.h" +#include "../include/soloud_waveshaperfilter.h" +#include "../include/soloud_wavstream.h" + +using namespace SoLoud; + +extern "C" +{ + +void Soloud_destroy(void * aClassPtr) +{ + delete (Soloud *)aClassPtr; +} + +void * Soloud_create() +{ + return (void *)new Soloud; +} + +int Soloud_init(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->init(); +} + +int Soloud_initEx(void * aClassPtr, unsigned int aFlags, unsigned int aBackend, unsigned int aSamplerate, unsigned int aBufferSize, unsigned int aChannels) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->init(aFlags, aBackend, aSamplerate, aBufferSize, aChannels); +} + +void Soloud_deinit(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->deinit(); +} + +unsigned int Soloud_getVersion(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getVersion(); +} + +const char * Soloud_getErrorString(void * aClassPtr, int aErrorCode) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getErrorString(aErrorCode); +} + +unsigned int Soloud_getBackendId(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getBackendId(); +} + +const char * Soloud_getBackendString(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getBackendString(); +} + +unsigned int Soloud_getBackendChannels(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getBackendChannels(); +} + +unsigned int Soloud_getBackendSamplerate(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getBackendSamplerate(); +} + +unsigned int Soloud_getBackendBufferSize(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getBackendBufferSize(); +} + +int Soloud_setSpeakerPosition(void * aClassPtr, unsigned int aChannel, float aX, float aY, float aZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->setSpeakerPosition(aChannel, aX, aY, aZ); +} + +int Soloud_getSpeakerPosition(void * aClassPtr, unsigned int aChannel, float * aX, float * aY, float * aZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getSpeakerPosition(aChannel, *aX, *aY, *aZ); +} + +unsigned int Soloud_play(void * aClassPtr, AudioSource * aSound) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->play(*aSound); +} + +unsigned int Soloud_playEx(void * aClassPtr, AudioSource * aSound, float aVolume, float aPan, int aPaused, unsigned int aBus) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->play(*aSound, aVolume, aPan, !!aPaused, aBus); +} + +unsigned int Soloud_playClocked(void * aClassPtr, double aSoundTime, AudioSource * aSound) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->playClocked(aSoundTime, *aSound); +} + +unsigned int Soloud_playClockedEx(void * aClassPtr, double aSoundTime, AudioSource * aSound, float aVolume, float aPan, unsigned int aBus) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->playClocked(aSoundTime, *aSound, aVolume, aPan, aBus); +} + +unsigned int Soloud_play3d(void * aClassPtr, AudioSource * aSound, float aPosX, float aPosY, float aPosZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->play3d(*aSound, aPosX, aPosY, aPosZ); +} + +unsigned int Soloud_play3dEx(void * aClassPtr, AudioSource * aSound, float aPosX, float aPosY, float aPosZ, float aVelX, float aVelY, float aVelZ, float aVolume, int aPaused, unsigned int aBus) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->play3d(*aSound, aPosX, aPosY, aPosZ, aVelX, aVelY, aVelZ, aVolume, !!aPaused, aBus); +} + +unsigned int Soloud_play3dClocked(void * aClassPtr, double aSoundTime, AudioSource * aSound, float aPosX, float aPosY, float aPosZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->play3dClocked(aSoundTime, *aSound, aPosX, aPosY, aPosZ); +} + +unsigned int Soloud_play3dClockedEx(void * aClassPtr, double aSoundTime, AudioSource * aSound, float aPosX, float aPosY, float aPosZ, float aVelX, float aVelY, float aVelZ, float aVolume, unsigned int aBus) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->play3dClocked(aSoundTime, *aSound, aPosX, aPosY, aPosZ, aVelX, aVelY, aVelZ, aVolume, aBus); +} + +unsigned int Soloud_playBackground(void * aClassPtr, AudioSource * aSound) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->playBackground(*aSound); +} + +unsigned int Soloud_playBackgroundEx(void * aClassPtr, AudioSource * aSound, float aVolume, int aPaused, unsigned int aBus) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->playBackground(*aSound, aVolume, !!aPaused, aBus); +} + +int Soloud_seek(void * aClassPtr, unsigned int aVoiceHandle, double aSeconds) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->seek(aVoiceHandle, aSeconds); +} + +void Soloud_stop(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->stop(aVoiceHandle); +} + +void Soloud_stopAll(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->stopAll(); +} + +void Soloud_stopAudioSource(void * aClassPtr, AudioSource * aSound) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->stopAudioSource(*aSound); +} + +int Soloud_countAudioSource(void * aClassPtr, AudioSource * aSound) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->countAudioSource(*aSound); +} + +void Soloud_setFilterParameter(void * aClassPtr, unsigned int aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId, float aValue) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setFilterParameter(aVoiceHandle, aFilterId, aAttributeId, aValue); +} + +float Soloud_getFilterParameter(void * aClassPtr, unsigned int aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getFilterParameter(aVoiceHandle, aFilterId, aAttributeId); +} + +void Soloud_fadeFilterParameter(void * aClassPtr, unsigned int aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId, float aTo, double aTime) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->fadeFilterParameter(aVoiceHandle, aFilterId, aAttributeId, aTo, aTime); +} + +void Soloud_oscillateFilterParameter(void * aClassPtr, unsigned int aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId, float aFrom, float aTo, double aTime) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->oscillateFilterParameter(aVoiceHandle, aFilterId, aAttributeId, aFrom, aTo, aTime); +} + +double Soloud_getStreamTime(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getStreamTime(aVoiceHandle); +} + +double Soloud_getStreamPosition(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getStreamPosition(aVoiceHandle); +} + +int Soloud_getPause(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getPause(aVoiceHandle); +} + +float Soloud_getVolume(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getVolume(aVoiceHandle); +} + +float Soloud_getOverallVolume(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getOverallVolume(aVoiceHandle); +} + +float Soloud_getPan(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getPan(aVoiceHandle); +} + +float Soloud_getSamplerate(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getSamplerate(aVoiceHandle); +} + +int Soloud_getProtectVoice(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getProtectVoice(aVoiceHandle); +} + +unsigned int Soloud_getActiveVoiceCount(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getActiveVoiceCount(); +} + +unsigned int Soloud_getVoiceCount(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getVoiceCount(); +} + +int Soloud_isValidVoiceHandle(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->isValidVoiceHandle(aVoiceHandle); +} + +float Soloud_getRelativePlaySpeed(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getRelativePlaySpeed(aVoiceHandle); +} + +float Soloud_getPostClipScaler(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getPostClipScaler(); +} + +float Soloud_getGlobalVolume(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getGlobalVolume(); +} + +unsigned int Soloud_getMaxActiveVoiceCount(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getMaxActiveVoiceCount(); +} + +int Soloud_getLooping(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getLooping(aVoiceHandle); +} + +double Soloud_getLoopPoint(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getLoopPoint(aVoiceHandle); +} + +void Soloud_setLoopPoint(void * aClassPtr, unsigned int aVoiceHandle, double aLoopPoint) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setLoopPoint(aVoiceHandle, aLoopPoint); +} + +void Soloud_setLooping(void * aClassPtr, unsigned int aVoiceHandle, int aLooping) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setLooping(aVoiceHandle, !!aLooping); +} + +int Soloud_setMaxActiveVoiceCount(void * aClassPtr, unsigned int aVoiceCount) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->setMaxActiveVoiceCount(aVoiceCount); +} + +void Soloud_setInaudibleBehavior(void * aClassPtr, unsigned int aVoiceHandle, int aMustTick, int aKill) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setInaudibleBehavior(aVoiceHandle, !!aMustTick, !!aKill); +} + +void Soloud_setGlobalVolume(void * aClassPtr, float aVolume) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setGlobalVolume(aVolume); +} + +void Soloud_setPostClipScaler(void * aClassPtr, float aScaler) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setPostClipScaler(aScaler); +} + +void Soloud_setPause(void * aClassPtr, unsigned int aVoiceHandle, int aPause) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setPause(aVoiceHandle, !!aPause); +} + +void Soloud_setPauseAll(void * aClassPtr, int aPause) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setPauseAll(!!aPause); +} + +int Soloud_setRelativePlaySpeed(void * aClassPtr, unsigned int aVoiceHandle, float aSpeed) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->setRelativePlaySpeed(aVoiceHandle, aSpeed); +} + +void Soloud_setProtectVoice(void * aClassPtr, unsigned int aVoiceHandle, int aProtect) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setProtectVoice(aVoiceHandle, !!aProtect); +} + +void Soloud_setSamplerate(void * aClassPtr, unsigned int aVoiceHandle, float aSamplerate) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setSamplerate(aVoiceHandle, aSamplerate); +} + +void Soloud_setPan(void * aClassPtr, unsigned int aVoiceHandle, float aPan) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setPan(aVoiceHandle, aPan); +} + +void Soloud_setPanAbsolute(void * aClassPtr, unsigned int aVoiceHandle, float aLVolume, float aRVolume) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setPanAbsolute(aVoiceHandle, aLVolume, aRVolume); +} + +void Soloud_setPanAbsoluteEx(void * aClassPtr, unsigned int aVoiceHandle, float aLVolume, float aRVolume, float aLBVolume, float aRBVolume, float aCVolume, float aSVolume) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setPanAbsolute(aVoiceHandle, aLVolume, aRVolume, aLBVolume, aRBVolume, aCVolume, aSVolume); +} + +void Soloud_setVolume(void * aClassPtr, unsigned int aVoiceHandle, float aVolume) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setVolume(aVoiceHandle, aVolume); +} + +void Soloud_setDelaySamples(void * aClassPtr, unsigned int aVoiceHandle, unsigned int aSamples) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setDelaySamples(aVoiceHandle, aSamples); +} + +void Soloud_fadeVolume(void * aClassPtr, unsigned int aVoiceHandle, float aTo, double aTime) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->fadeVolume(aVoiceHandle, aTo, aTime); +} + +void Soloud_fadePan(void * aClassPtr, unsigned int aVoiceHandle, float aTo, double aTime) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->fadePan(aVoiceHandle, aTo, aTime); +} + +void Soloud_fadeRelativePlaySpeed(void * aClassPtr, unsigned int aVoiceHandle, float aTo, double aTime) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->fadeRelativePlaySpeed(aVoiceHandle, aTo, aTime); +} + +void Soloud_fadeGlobalVolume(void * aClassPtr, float aTo, double aTime) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->fadeGlobalVolume(aTo, aTime); +} + +void Soloud_schedulePause(void * aClassPtr, unsigned int aVoiceHandle, double aTime) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->schedulePause(aVoiceHandle, aTime); +} + +void Soloud_scheduleStop(void * aClassPtr, unsigned int aVoiceHandle, double aTime) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->scheduleStop(aVoiceHandle, aTime); +} + +void Soloud_oscillateVolume(void * aClassPtr, unsigned int aVoiceHandle, float aFrom, float aTo, double aTime) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->oscillateVolume(aVoiceHandle, aFrom, aTo, aTime); +} + +void Soloud_oscillatePan(void * aClassPtr, unsigned int aVoiceHandle, float aFrom, float aTo, double aTime) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->oscillatePan(aVoiceHandle, aFrom, aTo, aTime); +} + +void Soloud_oscillateRelativePlaySpeed(void * aClassPtr, unsigned int aVoiceHandle, float aFrom, float aTo, double aTime) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->oscillateRelativePlaySpeed(aVoiceHandle, aFrom, aTo, aTime); +} + +void Soloud_oscillateGlobalVolume(void * aClassPtr, float aFrom, float aTo, double aTime) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->oscillateGlobalVolume(aFrom, aTo, aTime); +} + +void Soloud_setGlobalFilter(void * aClassPtr, unsigned int aFilterId, Filter * aFilter) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setGlobalFilter(aFilterId, aFilter); +} + +void Soloud_setVisualizationEnable(void * aClassPtr, int aEnable) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->setVisualizationEnable(!!aEnable); +} + +float * Soloud_calcFFT(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->calcFFT(); +} + +float * Soloud_getWave(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getWave(); +} + +float Soloud_getApproximateVolume(void * aClassPtr, unsigned int aChannel) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getApproximateVolume(aChannel); +} + +unsigned int Soloud_getLoopCount(void * aClassPtr, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getLoopCount(aVoiceHandle); +} + +float Soloud_getInfo(void * aClassPtr, unsigned int aVoiceHandle, unsigned int aInfoKey) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->getInfo(aVoiceHandle, aInfoKey); +} + +unsigned int Soloud_createVoiceGroup(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->createVoiceGroup(); +} + +int Soloud_destroyVoiceGroup(void * aClassPtr, unsigned int aVoiceGroupHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->destroyVoiceGroup(aVoiceGroupHandle); +} + +int Soloud_addVoiceToGroup(void * aClassPtr, unsigned int aVoiceGroupHandle, unsigned int aVoiceHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->addVoiceToGroup(aVoiceGroupHandle, aVoiceHandle); +} + +int Soloud_isVoiceGroup(void * aClassPtr, unsigned int aVoiceGroupHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->isVoiceGroup(aVoiceGroupHandle); +} + +int Soloud_isVoiceGroupEmpty(void * aClassPtr, unsigned int aVoiceGroupHandle) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->isVoiceGroupEmpty(aVoiceGroupHandle); +} + +void Soloud_update3dAudio(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->update3dAudio(); +} + +int Soloud_set3dSoundSpeed(void * aClassPtr, float aSpeed) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->set3dSoundSpeed(aSpeed); +} + +float Soloud_get3dSoundSpeed(void * aClassPtr) +{ + Soloud * cl = (Soloud *)aClassPtr; + return cl->get3dSoundSpeed(); +} + +void Soloud_set3dListenerParameters(void * aClassPtr, float aPosX, float aPosY, float aPosZ, float aAtX, float aAtY, float aAtZ, float aUpX, float aUpY, float aUpZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dListenerParameters(aPosX, aPosY, aPosZ, aAtX, aAtY, aAtZ, aUpX, aUpY, aUpZ); +} + +void Soloud_set3dListenerParametersEx(void * aClassPtr, float aPosX, float aPosY, float aPosZ, float aAtX, float aAtY, float aAtZ, float aUpX, float aUpY, float aUpZ, float aVelocityX, float aVelocityY, float aVelocityZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dListenerParameters(aPosX, aPosY, aPosZ, aAtX, aAtY, aAtZ, aUpX, aUpY, aUpZ, aVelocityX, aVelocityY, aVelocityZ); +} + +void Soloud_set3dListenerPosition(void * aClassPtr, float aPosX, float aPosY, float aPosZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dListenerPosition(aPosX, aPosY, aPosZ); +} + +void Soloud_set3dListenerAt(void * aClassPtr, float aAtX, float aAtY, float aAtZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dListenerAt(aAtX, aAtY, aAtZ); +} + +void Soloud_set3dListenerUp(void * aClassPtr, float aUpX, float aUpY, float aUpZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dListenerUp(aUpX, aUpY, aUpZ); +} + +void Soloud_set3dListenerVelocity(void * aClassPtr, float aVelocityX, float aVelocityY, float aVelocityZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dListenerVelocity(aVelocityX, aVelocityY, aVelocityZ); +} + +void Soloud_set3dSourceParameters(void * aClassPtr, unsigned int aVoiceHandle, float aPosX, float aPosY, float aPosZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dSourceParameters(aVoiceHandle, aPosX, aPosY, aPosZ); +} + +void Soloud_set3dSourceParametersEx(void * aClassPtr, unsigned int aVoiceHandle, float aPosX, float aPosY, float aPosZ, float aVelocityX, float aVelocityY, float aVelocityZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dSourceParameters(aVoiceHandle, aPosX, aPosY, aPosZ, aVelocityX, aVelocityY, aVelocityZ); +} + +void Soloud_set3dSourcePosition(void * aClassPtr, unsigned int aVoiceHandle, float aPosX, float aPosY, float aPosZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dSourcePosition(aVoiceHandle, aPosX, aPosY, aPosZ); +} + +void Soloud_set3dSourceVelocity(void * aClassPtr, unsigned int aVoiceHandle, float aVelocityX, float aVelocityY, float aVelocityZ) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dSourceVelocity(aVoiceHandle, aVelocityX, aVelocityY, aVelocityZ); +} + +void Soloud_set3dSourceMinMaxDistance(void * aClassPtr, unsigned int aVoiceHandle, float aMinDistance, float aMaxDistance) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dSourceMinMaxDistance(aVoiceHandle, aMinDistance, aMaxDistance); +} + +void Soloud_set3dSourceAttenuation(void * aClassPtr, unsigned int aVoiceHandle, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dSourceAttenuation(aVoiceHandle, aAttenuationModel, aAttenuationRolloffFactor); +} + +void Soloud_set3dSourceDopplerFactor(void * aClassPtr, unsigned int aVoiceHandle, float aDopplerFactor) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->set3dSourceDopplerFactor(aVoiceHandle, aDopplerFactor); +} + +void Soloud_mix(void * aClassPtr, float * aBuffer, unsigned int aSamples) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->mix(aBuffer, aSamples); +} + +void Soloud_mixSigned16(void * aClassPtr, short * aBuffer, unsigned int aSamples) +{ + Soloud * cl = (Soloud *)aClassPtr; + cl->mixSigned16(aBuffer, aSamples); +} + +void AudioAttenuator_destroy(void * aClassPtr) +{ + delete (AudioAttenuator *)aClassPtr; +} + +float AudioAttenuator_attenuate(void * aClassPtr, float aDistance, float aMinDistance, float aMaxDistance, float aRolloffFactor) +{ + AudioAttenuator * cl = (AudioAttenuator *)aClassPtr; + return cl->attenuate(aDistance, aMinDistance, aMaxDistance, aRolloffFactor); +} + +void BassboostFilter_destroy(void * aClassPtr) +{ + delete (BassboostFilter *)aClassPtr; +} + +int BassboostFilter_setParams(void * aClassPtr, float aBoost) +{ + BassboostFilter * cl = (BassboostFilter *)aClassPtr; + return cl->setParams(aBoost); +} + +void * BassboostFilter_create() +{ + return (void *)new BassboostFilter; +} + +void BiquadResonantFilter_destroy(void * aClassPtr) +{ + delete (BiquadResonantFilter *)aClassPtr; +} + +void * BiquadResonantFilter_create() +{ + return (void *)new BiquadResonantFilter; +} + +int BiquadResonantFilter_setParams(void * aClassPtr, int aType, float aSampleRate, float aFrequency, float aResonance) +{ + BiquadResonantFilter * cl = (BiquadResonantFilter *)aClassPtr; + return cl->setParams(aType, aSampleRate, aFrequency, aResonance); +} + +void Bus_destroy(void * aClassPtr) +{ + delete (Bus *)aClassPtr; +} + +void * Bus_create() +{ + return (void *)new Bus; +} + +void Bus_setFilter(void * aClassPtr, unsigned int aFilterId, Filter * aFilter) +{ + Bus * cl = (Bus *)aClassPtr; + cl->setFilter(aFilterId, aFilter); +} + +unsigned int Bus_play(void * aClassPtr, AudioSource * aSound) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->play(*aSound); +} + +unsigned int Bus_playEx(void * aClassPtr, AudioSource * aSound, float aVolume, float aPan, int aPaused) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->play(*aSound, aVolume, aPan, !!aPaused); +} + +unsigned int Bus_playClocked(void * aClassPtr, double aSoundTime, AudioSource * aSound) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->playClocked(aSoundTime, *aSound); +} + +unsigned int Bus_playClockedEx(void * aClassPtr, double aSoundTime, AudioSource * aSound, float aVolume, float aPan) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->playClocked(aSoundTime, *aSound, aVolume, aPan); +} + +unsigned int Bus_play3d(void * aClassPtr, AudioSource * aSound, float aPosX, float aPosY, float aPosZ) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->play3d(*aSound, aPosX, aPosY, aPosZ); +} + +unsigned int Bus_play3dEx(void * aClassPtr, AudioSource * aSound, float aPosX, float aPosY, float aPosZ, float aVelX, float aVelY, float aVelZ, float aVolume, int aPaused) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->play3d(*aSound, aPosX, aPosY, aPosZ, aVelX, aVelY, aVelZ, aVolume, !!aPaused); +} + +unsigned int Bus_play3dClocked(void * aClassPtr, double aSoundTime, AudioSource * aSound, float aPosX, float aPosY, float aPosZ) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->play3dClocked(aSoundTime, *aSound, aPosX, aPosY, aPosZ); +} + +unsigned int Bus_play3dClockedEx(void * aClassPtr, double aSoundTime, AudioSource * aSound, float aPosX, float aPosY, float aPosZ, float aVelX, float aVelY, float aVelZ, float aVolume) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->play3dClocked(aSoundTime, *aSound, aPosX, aPosY, aPosZ, aVelX, aVelY, aVelZ, aVolume); +} + +int Bus_setChannels(void * aClassPtr, unsigned int aChannels) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->setChannels(aChannels); +} + +void Bus_setVisualizationEnable(void * aClassPtr, int aEnable) +{ + Bus * cl = (Bus *)aClassPtr; + cl->setVisualizationEnable(!!aEnable); +} + +float * Bus_calcFFT(void * aClassPtr) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->calcFFT(); +} + +float * Bus_getWave(void * aClassPtr) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->getWave(); +} + +float Bus_getApproximateVolume(void * aClassPtr, unsigned int aChannel) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->getApproximateVolume(aChannel); +} + +void Bus_setVolume(void * aClassPtr, float aVolume) +{ + Bus * cl = (Bus *)aClassPtr; + cl->setVolume(aVolume); +} + +void Bus_setLooping(void * aClassPtr, int aLoop) +{ + Bus * cl = (Bus *)aClassPtr; + cl->setLooping(!!aLoop); +} + +void Bus_set3dMinMaxDistance(void * aClassPtr, float aMinDistance, float aMaxDistance) +{ + Bus * cl = (Bus *)aClassPtr; + cl->set3dMinMaxDistance(aMinDistance, aMaxDistance); +} + +void Bus_set3dAttenuation(void * aClassPtr, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) +{ + Bus * cl = (Bus *)aClassPtr; + cl->set3dAttenuation(aAttenuationModel, aAttenuationRolloffFactor); +} + +void Bus_set3dDopplerFactor(void * aClassPtr, float aDopplerFactor) +{ + Bus * cl = (Bus *)aClassPtr; + cl->set3dDopplerFactor(aDopplerFactor); +} + +void Bus_set3dListenerRelative(void * aClassPtr, int aListenerRelative) +{ + Bus * cl = (Bus *)aClassPtr; + cl->set3dListenerRelative(!!aListenerRelative); +} + +void Bus_set3dDistanceDelay(void * aClassPtr, int aDistanceDelay) +{ + Bus * cl = (Bus *)aClassPtr; + cl->set3dDistanceDelay(!!aDistanceDelay); +} + +void Bus_set3dCollider(void * aClassPtr, AudioCollider * aCollider) +{ + Bus * cl = (Bus *)aClassPtr; + cl->set3dCollider(aCollider); +} + +void Bus_set3dColliderEx(void * aClassPtr, AudioCollider * aCollider, int aUserData) +{ + Bus * cl = (Bus *)aClassPtr; + cl->set3dCollider(aCollider, aUserData); +} + +void Bus_set3dAttenuator(void * aClassPtr, AudioAttenuator * aAttenuator) +{ + Bus * cl = (Bus *)aClassPtr; + cl->set3dAttenuator(aAttenuator); +} + +void Bus_setInaudibleBehavior(void * aClassPtr, int aMustTick, int aKill) +{ + Bus * cl = (Bus *)aClassPtr; + cl->setInaudibleBehavior(!!aMustTick, !!aKill); +} + +void Bus_setLoopPoint(void * aClassPtr, double aLoopPoint) +{ + Bus * cl = (Bus *)aClassPtr; + cl->setLoopPoint(aLoopPoint); +} + +double Bus_getLoopPoint(void * aClassPtr) +{ + Bus * cl = (Bus *)aClassPtr; + return cl->getLoopPoint(); +} + +void Bus_stop(void * aClassPtr) +{ + Bus * cl = (Bus *)aClassPtr; + cl->stop(); +} + +void DCRemovalFilter_destroy(void * aClassPtr) +{ + delete (DCRemovalFilter *)aClassPtr; +} + +void * DCRemovalFilter_create() +{ + return (void *)new DCRemovalFilter; +} + +int DCRemovalFilter_setParams(void * aClassPtr) +{ + DCRemovalFilter * cl = (DCRemovalFilter *)aClassPtr; + return cl->setParams(); +} + +int DCRemovalFilter_setParamsEx(void * aClassPtr, float aLength) +{ + DCRemovalFilter * cl = (DCRemovalFilter *)aClassPtr; + return cl->setParams(aLength); +} + +void EchoFilter_destroy(void * aClassPtr) +{ + delete (EchoFilter *)aClassPtr; +} + +void * EchoFilter_create() +{ + return (void *)new EchoFilter; +} + +int EchoFilter_setParams(void * aClassPtr, float aDelay) +{ + EchoFilter * cl = (EchoFilter *)aClassPtr; + return cl->setParams(aDelay); +} + +int EchoFilter_setParamsEx(void * aClassPtr, float aDelay, float aDecay, float aFilter) +{ + EchoFilter * cl = (EchoFilter *)aClassPtr; + return cl->setParams(aDelay, aDecay, aFilter); +} + +void FFTFilter_destroy(void * aClassPtr) +{ + delete (FFTFilter *)aClassPtr; +} + +void * FFTFilter_create() +{ + return (void *)new FFTFilter; +} + +void FlangerFilter_destroy(void * aClassPtr) +{ + delete (FlangerFilter *)aClassPtr; +} + +void * FlangerFilter_create() +{ + return (void *)new FlangerFilter; +} + +int FlangerFilter_setParams(void * aClassPtr, float aDelay, float aFreq) +{ + FlangerFilter * cl = (FlangerFilter *)aClassPtr; + return cl->setParams(aDelay, aFreq); +} + +void LofiFilter_destroy(void * aClassPtr) +{ + delete (LofiFilter *)aClassPtr; +} + +void * LofiFilter_create() +{ + return (void *)new LofiFilter; +} + +int LofiFilter_setParams(void * aClassPtr, float aSampleRate, float aBitdepth) +{ + LofiFilter * cl = (LofiFilter *)aClassPtr; + return cl->setParams(aSampleRate, aBitdepth); +} + +void Monotone_destroy(void * aClassPtr) +{ + delete (Monotone *)aClassPtr; +} + +void * Monotone_create() +{ + return (void *)new Monotone; +} + +int Monotone_setParams(void * aClassPtr, int aHardwareChannels) +{ + Monotone * cl = (Monotone *)aClassPtr; + return cl->setParams(aHardwareChannels); +} + +int Monotone_setParamsEx(void * aClassPtr, int aHardwareChannels, int aWaveform) +{ + Monotone * cl = (Monotone *)aClassPtr; + return cl->setParams(aHardwareChannels, aWaveform); +} + +int Monotone_load(void * aClassPtr, const char * aFilename) +{ + Monotone * cl = (Monotone *)aClassPtr; + return cl->load(aFilename); +} + +int Monotone_loadMem(void * aClassPtr, unsigned char * aMem, unsigned int aLength) +{ + Monotone * cl = (Monotone *)aClassPtr; + return cl->loadMem(aMem, aLength); +} + +int Monotone_loadMemEx(void * aClassPtr, unsigned char * aMem, unsigned int aLength, int aCopy, int aTakeOwnership) +{ + Monotone * cl = (Monotone *)aClassPtr; + return cl->loadMem(aMem, aLength, !!aCopy, !!aTakeOwnership); +} + +int Monotone_loadFile(void * aClassPtr, File * aFile) +{ + Monotone * cl = (Monotone *)aClassPtr; + return cl->loadFile(aFile); +} + +void Monotone_setVolume(void * aClassPtr, float aVolume) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->setVolume(aVolume); +} + +void Monotone_setLooping(void * aClassPtr, int aLoop) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->setLooping(!!aLoop); +} + +void Monotone_set3dMinMaxDistance(void * aClassPtr, float aMinDistance, float aMaxDistance) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->set3dMinMaxDistance(aMinDistance, aMaxDistance); +} + +void Monotone_set3dAttenuation(void * aClassPtr, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->set3dAttenuation(aAttenuationModel, aAttenuationRolloffFactor); +} + +void Monotone_set3dDopplerFactor(void * aClassPtr, float aDopplerFactor) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->set3dDopplerFactor(aDopplerFactor); +} + +void Monotone_set3dListenerRelative(void * aClassPtr, int aListenerRelative) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->set3dListenerRelative(!!aListenerRelative); +} + +void Monotone_set3dDistanceDelay(void * aClassPtr, int aDistanceDelay) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->set3dDistanceDelay(!!aDistanceDelay); +} + +void Monotone_set3dCollider(void * aClassPtr, AudioCollider * aCollider) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->set3dCollider(aCollider); +} + +void Monotone_set3dColliderEx(void * aClassPtr, AudioCollider * aCollider, int aUserData) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->set3dCollider(aCollider, aUserData); +} + +void Monotone_set3dAttenuator(void * aClassPtr, AudioAttenuator * aAttenuator) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->set3dAttenuator(aAttenuator); +} + +void Monotone_setInaudibleBehavior(void * aClassPtr, int aMustTick, int aKill) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->setInaudibleBehavior(!!aMustTick, !!aKill); +} + +void Monotone_setLoopPoint(void * aClassPtr, double aLoopPoint) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->setLoopPoint(aLoopPoint); +} + +double Monotone_getLoopPoint(void * aClassPtr) +{ + Monotone * cl = (Monotone *)aClassPtr; + return cl->getLoopPoint(); +} + +void Monotone_setFilter(void * aClassPtr, unsigned int aFilterId, Filter * aFilter) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->setFilter(aFilterId, aFilter); +} + +void Monotone_stop(void * aClassPtr) +{ + Monotone * cl = (Monotone *)aClassPtr; + cl->stop(); +} + +void Openmpt_destroy(void * aClassPtr) +{ + delete (Openmpt *)aClassPtr; +} + +void * Openmpt_create() +{ + return (void *)new Openmpt; +} + +int Openmpt_load(void * aClassPtr, const char * aFilename) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + return cl->load(aFilename); +} + +int Openmpt_loadMem(void * aClassPtr, unsigned char * aMem, unsigned int aLength) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + return cl->loadMem(aMem, aLength); +} + +int Openmpt_loadMemEx(void * aClassPtr, unsigned char * aMem, unsigned int aLength, int aCopy, int aTakeOwnership) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + return cl->loadMem(aMem, aLength, !!aCopy, !!aTakeOwnership); +} + +int Openmpt_loadFile(void * aClassPtr, File * aFile) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + return cl->loadFile(aFile); +} + +void Openmpt_setVolume(void * aClassPtr, float aVolume) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->setVolume(aVolume); +} + +void Openmpt_setLooping(void * aClassPtr, int aLoop) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->setLooping(!!aLoop); +} + +void Openmpt_set3dMinMaxDistance(void * aClassPtr, float aMinDistance, float aMaxDistance) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->set3dMinMaxDistance(aMinDistance, aMaxDistance); +} + +void Openmpt_set3dAttenuation(void * aClassPtr, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->set3dAttenuation(aAttenuationModel, aAttenuationRolloffFactor); +} + +void Openmpt_set3dDopplerFactor(void * aClassPtr, float aDopplerFactor) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->set3dDopplerFactor(aDopplerFactor); +} + +void Openmpt_set3dListenerRelative(void * aClassPtr, int aListenerRelative) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->set3dListenerRelative(!!aListenerRelative); +} + +void Openmpt_set3dDistanceDelay(void * aClassPtr, int aDistanceDelay) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->set3dDistanceDelay(!!aDistanceDelay); +} + +void Openmpt_set3dCollider(void * aClassPtr, AudioCollider * aCollider) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->set3dCollider(aCollider); +} + +void Openmpt_set3dColliderEx(void * aClassPtr, AudioCollider * aCollider, int aUserData) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->set3dCollider(aCollider, aUserData); +} + +void Openmpt_set3dAttenuator(void * aClassPtr, AudioAttenuator * aAttenuator) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->set3dAttenuator(aAttenuator); +} + +void Openmpt_setInaudibleBehavior(void * aClassPtr, int aMustTick, int aKill) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->setInaudibleBehavior(!!aMustTick, !!aKill); +} + +void Openmpt_setLoopPoint(void * aClassPtr, double aLoopPoint) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->setLoopPoint(aLoopPoint); +} + +double Openmpt_getLoopPoint(void * aClassPtr) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + return cl->getLoopPoint(); +} + +void Openmpt_setFilter(void * aClassPtr, unsigned int aFilterId, Filter * aFilter) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->setFilter(aFilterId, aFilter); +} + +void Openmpt_stop(void * aClassPtr) +{ + Openmpt * cl = (Openmpt *)aClassPtr; + cl->stop(); +} + +void Queue_destroy(void * aClassPtr) +{ + delete (Queue *)aClassPtr; +} + +void * Queue_create() +{ + return (void *)new Queue; +} + +int Queue_play(void * aClassPtr, AudioSource * aSound) +{ + Queue * cl = (Queue *)aClassPtr; + return cl->play(*aSound); +} + +unsigned int Queue_getQueueCount(void * aClassPtr) +{ + Queue * cl = (Queue *)aClassPtr; + return cl->getQueueCount(); +} + +int Queue_isCurrentlyPlaying(void * aClassPtr, AudioSource * aSound) +{ + Queue * cl = (Queue *)aClassPtr; + return cl->isCurrentlyPlaying(*aSound); +} + +int Queue_setParamsFromAudioSource(void * aClassPtr, AudioSource * aSound) +{ + Queue * cl = (Queue *)aClassPtr; + return cl->setParamsFromAudioSource(*aSound); +} + +int Queue_setParams(void * aClassPtr, float aSamplerate) +{ + Queue * cl = (Queue *)aClassPtr; + return cl->setParams(aSamplerate); +} + +int Queue_setParamsEx(void * aClassPtr, float aSamplerate, unsigned int aChannels) +{ + Queue * cl = (Queue *)aClassPtr; + return cl->setParams(aSamplerate, aChannels); +} + +void Queue_setVolume(void * aClassPtr, float aVolume) +{ + Queue * cl = (Queue *)aClassPtr; + cl->setVolume(aVolume); +} + +void Queue_setLooping(void * aClassPtr, int aLoop) +{ + Queue * cl = (Queue *)aClassPtr; + cl->setLooping(!!aLoop); +} + +void Queue_set3dMinMaxDistance(void * aClassPtr, float aMinDistance, float aMaxDistance) +{ + Queue * cl = (Queue *)aClassPtr; + cl->set3dMinMaxDistance(aMinDistance, aMaxDistance); +} + +void Queue_set3dAttenuation(void * aClassPtr, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) +{ + Queue * cl = (Queue *)aClassPtr; + cl->set3dAttenuation(aAttenuationModel, aAttenuationRolloffFactor); +} + +void Queue_set3dDopplerFactor(void * aClassPtr, float aDopplerFactor) +{ + Queue * cl = (Queue *)aClassPtr; + cl->set3dDopplerFactor(aDopplerFactor); +} + +void Queue_set3dListenerRelative(void * aClassPtr, int aListenerRelative) +{ + Queue * cl = (Queue *)aClassPtr; + cl->set3dListenerRelative(!!aListenerRelative); +} + +void Queue_set3dDistanceDelay(void * aClassPtr, int aDistanceDelay) +{ + Queue * cl = (Queue *)aClassPtr; + cl->set3dDistanceDelay(!!aDistanceDelay); +} + +void Queue_set3dCollider(void * aClassPtr, AudioCollider * aCollider) +{ + Queue * cl = (Queue *)aClassPtr; + cl->set3dCollider(aCollider); +} + +void Queue_set3dColliderEx(void * aClassPtr, AudioCollider * aCollider, int aUserData) +{ + Queue * cl = (Queue *)aClassPtr; + cl->set3dCollider(aCollider, aUserData); +} + +void Queue_set3dAttenuator(void * aClassPtr, AudioAttenuator * aAttenuator) +{ + Queue * cl = (Queue *)aClassPtr; + cl->set3dAttenuator(aAttenuator); +} + +void Queue_setInaudibleBehavior(void * aClassPtr, int aMustTick, int aKill) +{ + Queue * cl = (Queue *)aClassPtr; + cl->setInaudibleBehavior(!!aMustTick, !!aKill); +} + +void Queue_setLoopPoint(void * aClassPtr, double aLoopPoint) +{ + Queue * cl = (Queue *)aClassPtr; + cl->setLoopPoint(aLoopPoint); +} + +double Queue_getLoopPoint(void * aClassPtr) +{ + Queue * cl = (Queue *)aClassPtr; + return cl->getLoopPoint(); +} + +void Queue_setFilter(void * aClassPtr, unsigned int aFilterId, Filter * aFilter) +{ + Queue * cl = (Queue *)aClassPtr; + cl->setFilter(aFilterId, aFilter); +} + +void Queue_stop(void * aClassPtr) +{ + Queue * cl = (Queue *)aClassPtr; + cl->stop(); +} + +void RobotizeFilter_destroy(void * aClassPtr) +{ + delete (RobotizeFilter *)aClassPtr; +} + +void * RobotizeFilter_create() +{ + return (void *)new RobotizeFilter; +} + +void Prg_destroy(void * aClassPtr) +{ + delete (Prg *)aClassPtr; +} + +void * Prg_create() +{ + return (void *)new Prg; +} + +unsigned int Prg_rand(void * aClassPtr) +{ + Prg * cl = (Prg *)aClassPtr; + return cl->rand(); +} + +void Prg_srand(void * aClassPtr, int aSeed) +{ + Prg * cl = (Prg *)aClassPtr; + cl->srand(aSeed); +} + +void Sfxr_destroy(void * aClassPtr) +{ + delete (Sfxr *)aClassPtr; +} + +void * Sfxr_create() +{ + return (void *)new Sfxr; +} + +void Sfxr_resetParams(void * aClassPtr) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->resetParams(); +} + +int Sfxr_loadParams(void * aClassPtr, const char * aFilename) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + return cl->loadParams(aFilename); +} + +int Sfxr_loadParamsMem(void * aClassPtr, unsigned char * aMem, unsigned int aLength) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + return cl->loadParamsMem(aMem, aLength); +} + +int Sfxr_loadParamsMemEx(void * aClassPtr, unsigned char * aMem, unsigned int aLength, int aCopy, int aTakeOwnership) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + return cl->loadParamsMem(aMem, aLength, !!aCopy, !!aTakeOwnership); +} + +int Sfxr_loadParamsFile(void * aClassPtr, File * aFile) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + return cl->loadParamsFile(aFile); +} + +int Sfxr_loadPreset(void * aClassPtr, int aPresetNo, int aRandSeed) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + return cl->loadPreset(aPresetNo, aRandSeed); +} + +void Sfxr_setVolume(void * aClassPtr, float aVolume) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->setVolume(aVolume); +} + +void Sfxr_setLooping(void * aClassPtr, int aLoop) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->setLooping(!!aLoop); +} + +void Sfxr_set3dMinMaxDistance(void * aClassPtr, float aMinDistance, float aMaxDistance) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->set3dMinMaxDistance(aMinDistance, aMaxDistance); +} + +void Sfxr_set3dAttenuation(void * aClassPtr, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->set3dAttenuation(aAttenuationModel, aAttenuationRolloffFactor); +} + +void Sfxr_set3dDopplerFactor(void * aClassPtr, float aDopplerFactor) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->set3dDopplerFactor(aDopplerFactor); +} + +void Sfxr_set3dListenerRelative(void * aClassPtr, int aListenerRelative) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->set3dListenerRelative(!!aListenerRelative); +} + +void Sfxr_set3dDistanceDelay(void * aClassPtr, int aDistanceDelay) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->set3dDistanceDelay(!!aDistanceDelay); +} + +void Sfxr_set3dCollider(void * aClassPtr, AudioCollider * aCollider) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->set3dCollider(aCollider); +} + +void Sfxr_set3dColliderEx(void * aClassPtr, AudioCollider * aCollider, int aUserData) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->set3dCollider(aCollider, aUserData); +} + +void Sfxr_set3dAttenuator(void * aClassPtr, AudioAttenuator * aAttenuator) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->set3dAttenuator(aAttenuator); +} + +void Sfxr_setInaudibleBehavior(void * aClassPtr, int aMustTick, int aKill) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->setInaudibleBehavior(!!aMustTick, !!aKill); +} + +void Sfxr_setLoopPoint(void * aClassPtr, double aLoopPoint) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->setLoopPoint(aLoopPoint); +} + +double Sfxr_getLoopPoint(void * aClassPtr) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + return cl->getLoopPoint(); +} + +void Sfxr_setFilter(void * aClassPtr, unsigned int aFilterId, Filter * aFilter) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->setFilter(aFilterId, aFilter); +} + +void Sfxr_stop(void * aClassPtr) +{ + Sfxr * cl = (Sfxr *)aClassPtr; + cl->stop(); +} + +void Speech_destroy(void * aClassPtr) +{ + delete (Speech *)aClassPtr; +} + +void * Speech_create() +{ + return (void *)new Speech; +} + +int Speech_setText(void * aClassPtr, const char * aText) +{ + Speech * cl = (Speech *)aClassPtr; + return cl->setText(aText); +} + +int Speech_setParams(void * aClassPtr) +{ + Speech * cl = (Speech *)aClassPtr; + return cl->setParams(); +} + +int Speech_setParamsEx(void * aClassPtr, unsigned int aBaseFrequency, float aBaseSpeed, float aBaseDeclination, int aBaseWaveform) +{ + Speech * cl = (Speech *)aClassPtr; + return cl->setParams(aBaseFrequency, aBaseSpeed, aBaseDeclination, aBaseWaveform); +} + +void Speech_setVolume(void * aClassPtr, float aVolume) +{ + Speech * cl = (Speech *)aClassPtr; + cl->setVolume(aVolume); +} + +void Speech_setLooping(void * aClassPtr, int aLoop) +{ + Speech * cl = (Speech *)aClassPtr; + cl->setLooping(!!aLoop); +} + +void Speech_set3dMinMaxDistance(void * aClassPtr, float aMinDistance, float aMaxDistance) +{ + Speech * cl = (Speech *)aClassPtr; + cl->set3dMinMaxDistance(aMinDistance, aMaxDistance); +} + +void Speech_set3dAttenuation(void * aClassPtr, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) +{ + Speech * cl = (Speech *)aClassPtr; + cl->set3dAttenuation(aAttenuationModel, aAttenuationRolloffFactor); +} + +void Speech_set3dDopplerFactor(void * aClassPtr, float aDopplerFactor) +{ + Speech * cl = (Speech *)aClassPtr; + cl->set3dDopplerFactor(aDopplerFactor); +} + +void Speech_set3dListenerRelative(void * aClassPtr, int aListenerRelative) +{ + Speech * cl = (Speech *)aClassPtr; + cl->set3dListenerRelative(!!aListenerRelative); +} + +void Speech_set3dDistanceDelay(void * aClassPtr, int aDistanceDelay) +{ + Speech * cl = (Speech *)aClassPtr; + cl->set3dDistanceDelay(!!aDistanceDelay); +} + +void Speech_set3dCollider(void * aClassPtr, AudioCollider * aCollider) +{ + Speech * cl = (Speech *)aClassPtr; + cl->set3dCollider(aCollider); +} + +void Speech_set3dColliderEx(void * aClassPtr, AudioCollider * aCollider, int aUserData) +{ + Speech * cl = (Speech *)aClassPtr; + cl->set3dCollider(aCollider, aUserData); +} + +void Speech_set3dAttenuator(void * aClassPtr, AudioAttenuator * aAttenuator) +{ + Speech * cl = (Speech *)aClassPtr; + cl->set3dAttenuator(aAttenuator); +} + +void Speech_setInaudibleBehavior(void * aClassPtr, int aMustTick, int aKill) +{ + Speech * cl = (Speech *)aClassPtr; + cl->setInaudibleBehavior(!!aMustTick, !!aKill); +} + +void Speech_setLoopPoint(void * aClassPtr, double aLoopPoint) +{ + Speech * cl = (Speech *)aClassPtr; + cl->setLoopPoint(aLoopPoint); +} + +double Speech_getLoopPoint(void * aClassPtr) +{ + Speech * cl = (Speech *)aClassPtr; + return cl->getLoopPoint(); +} + +void Speech_setFilter(void * aClassPtr, unsigned int aFilterId, Filter * aFilter) +{ + Speech * cl = (Speech *)aClassPtr; + cl->setFilter(aFilterId, aFilter); +} + +void Speech_stop(void * aClassPtr) +{ + Speech * cl = (Speech *)aClassPtr; + cl->stop(); +} + +void TedSid_destroy(void * aClassPtr) +{ + delete (TedSid *)aClassPtr; +} + +void * TedSid_create() +{ + return (void *)new TedSid; +} + +int TedSid_load(void * aClassPtr, const char * aFilename) +{ + TedSid * cl = (TedSid *)aClassPtr; + return cl->load(aFilename); +} + +int TedSid_loadToMem(void * aClassPtr, const char * aFilename) +{ + TedSid * cl = (TedSid *)aClassPtr; + return cl->loadToMem(aFilename); +} + +int TedSid_loadMem(void * aClassPtr, unsigned char * aMem, unsigned int aLength) +{ + TedSid * cl = (TedSid *)aClassPtr; + return cl->loadMem(aMem, aLength); +} + +int TedSid_loadMemEx(void * aClassPtr, unsigned char * aMem, unsigned int aLength, int aCopy, int aTakeOwnership) +{ + TedSid * cl = (TedSid *)aClassPtr; + return cl->loadMem(aMem, aLength, !!aCopy, !!aTakeOwnership); +} + +int TedSid_loadFileToMem(void * aClassPtr, File * aFile) +{ + TedSid * cl = (TedSid *)aClassPtr; + return cl->loadFileToMem(aFile); +} + +int TedSid_loadFile(void * aClassPtr, File * aFile) +{ + TedSid * cl = (TedSid *)aClassPtr; + return cl->loadFile(aFile); +} + +void TedSid_setVolume(void * aClassPtr, float aVolume) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->setVolume(aVolume); +} + +void TedSid_setLooping(void * aClassPtr, int aLoop) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->setLooping(!!aLoop); +} + +void TedSid_set3dMinMaxDistance(void * aClassPtr, float aMinDistance, float aMaxDistance) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->set3dMinMaxDistance(aMinDistance, aMaxDistance); +} + +void TedSid_set3dAttenuation(void * aClassPtr, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->set3dAttenuation(aAttenuationModel, aAttenuationRolloffFactor); +} + +void TedSid_set3dDopplerFactor(void * aClassPtr, float aDopplerFactor) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->set3dDopplerFactor(aDopplerFactor); +} + +void TedSid_set3dListenerRelative(void * aClassPtr, int aListenerRelative) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->set3dListenerRelative(!!aListenerRelative); +} + +void TedSid_set3dDistanceDelay(void * aClassPtr, int aDistanceDelay) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->set3dDistanceDelay(!!aDistanceDelay); +} + +void TedSid_set3dCollider(void * aClassPtr, AudioCollider * aCollider) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->set3dCollider(aCollider); +} + +void TedSid_set3dColliderEx(void * aClassPtr, AudioCollider * aCollider, int aUserData) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->set3dCollider(aCollider, aUserData); +} + +void TedSid_set3dAttenuator(void * aClassPtr, AudioAttenuator * aAttenuator) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->set3dAttenuator(aAttenuator); +} + +void TedSid_setInaudibleBehavior(void * aClassPtr, int aMustTick, int aKill) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->setInaudibleBehavior(!!aMustTick, !!aKill); +} + +void TedSid_setLoopPoint(void * aClassPtr, double aLoopPoint) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->setLoopPoint(aLoopPoint); +} + +double TedSid_getLoopPoint(void * aClassPtr) +{ + TedSid * cl = (TedSid *)aClassPtr; + return cl->getLoopPoint(); +} + +void TedSid_setFilter(void * aClassPtr, unsigned int aFilterId, Filter * aFilter) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->setFilter(aFilterId, aFilter); +} + +void TedSid_stop(void * aClassPtr) +{ + TedSid * cl = (TedSid *)aClassPtr; + cl->stop(); +} + +void Vic_destroy(void * aClassPtr) +{ + delete (Vic *)aClassPtr; +} + +void * Vic_create() +{ + return (void *)new Vic; +} + +void Vic_setModel(void * aClassPtr, int model) +{ + Vic * cl = (Vic *)aClassPtr; + cl->setModel(model); +} + +int Vic_getModel(void * aClassPtr) +{ + Vic * cl = (Vic *)aClassPtr; + return cl->getModel(); +} + +void Vic_setRegister(void * aClassPtr, int reg, unsigned char value) +{ + Vic * cl = (Vic *)aClassPtr; + cl->setRegister(reg, value); +} + +unsigned char Vic_getRegister(void * aClassPtr, int reg) +{ + Vic * cl = (Vic *)aClassPtr; + return cl->getRegister(reg); +} + +void Vic_setVolume(void * aClassPtr, float aVolume) +{ + Vic * cl = (Vic *)aClassPtr; + cl->setVolume(aVolume); +} + +void Vic_setLooping(void * aClassPtr, int aLoop) +{ + Vic * cl = (Vic *)aClassPtr; + cl->setLooping(!!aLoop); +} + +void Vic_set3dMinMaxDistance(void * aClassPtr, float aMinDistance, float aMaxDistance) +{ + Vic * cl = (Vic *)aClassPtr; + cl->set3dMinMaxDistance(aMinDistance, aMaxDistance); +} + +void Vic_set3dAttenuation(void * aClassPtr, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) +{ + Vic * cl = (Vic *)aClassPtr; + cl->set3dAttenuation(aAttenuationModel, aAttenuationRolloffFactor); +} + +void Vic_set3dDopplerFactor(void * aClassPtr, float aDopplerFactor) +{ + Vic * cl = (Vic *)aClassPtr; + cl->set3dDopplerFactor(aDopplerFactor); +} + +void Vic_set3dListenerRelative(void * aClassPtr, int aListenerRelative) +{ + Vic * cl = (Vic *)aClassPtr; + cl->set3dListenerRelative(!!aListenerRelative); +} + +void Vic_set3dDistanceDelay(void * aClassPtr, int aDistanceDelay) +{ + Vic * cl = (Vic *)aClassPtr; + cl->set3dDistanceDelay(!!aDistanceDelay); +} + +void Vic_set3dCollider(void * aClassPtr, AudioCollider * aCollider) +{ + Vic * cl = (Vic *)aClassPtr; + cl->set3dCollider(aCollider); +} + +void Vic_set3dColliderEx(void * aClassPtr, AudioCollider * aCollider, int aUserData) +{ + Vic * cl = (Vic *)aClassPtr; + cl->set3dCollider(aCollider, aUserData); +} + +void Vic_set3dAttenuator(void * aClassPtr, AudioAttenuator * aAttenuator) +{ + Vic * cl = (Vic *)aClassPtr; + cl->set3dAttenuator(aAttenuator); +} + +void Vic_setInaudibleBehavior(void * aClassPtr, int aMustTick, int aKill) +{ + Vic * cl = (Vic *)aClassPtr; + cl->setInaudibleBehavior(!!aMustTick, !!aKill); +} + +void Vic_setLoopPoint(void * aClassPtr, double aLoopPoint) +{ + Vic * cl = (Vic *)aClassPtr; + cl->setLoopPoint(aLoopPoint); +} + +double Vic_getLoopPoint(void * aClassPtr) +{ + Vic * cl = (Vic *)aClassPtr; + return cl->getLoopPoint(); +} + +void Vic_setFilter(void * aClassPtr, unsigned int aFilterId, Filter * aFilter) +{ + Vic * cl = (Vic *)aClassPtr; + cl->setFilter(aFilterId, aFilter); +} + +void Vic_stop(void * aClassPtr) +{ + Vic * cl = (Vic *)aClassPtr; + cl->stop(); +} + +void Vizsn_destroy(void * aClassPtr) +{ + delete (Vizsn *)aClassPtr; +} + +void * Vizsn_create() +{ + return (void *)new Vizsn; +} + +void Vizsn_setText(void * aClassPtr, char * aText) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->setText(aText); +} + +void Vizsn_setVolume(void * aClassPtr, float aVolume) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->setVolume(aVolume); +} + +void Vizsn_setLooping(void * aClassPtr, int aLoop) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->setLooping(!!aLoop); +} + +void Vizsn_set3dMinMaxDistance(void * aClassPtr, float aMinDistance, float aMaxDistance) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->set3dMinMaxDistance(aMinDistance, aMaxDistance); +} + +void Vizsn_set3dAttenuation(void * aClassPtr, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->set3dAttenuation(aAttenuationModel, aAttenuationRolloffFactor); +} + +void Vizsn_set3dDopplerFactor(void * aClassPtr, float aDopplerFactor) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->set3dDopplerFactor(aDopplerFactor); +} + +void Vizsn_set3dListenerRelative(void * aClassPtr, int aListenerRelative) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->set3dListenerRelative(!!aListenerRelative); +} + +void Vizsn_set3dDistanceDelay(void * aClassPtr, int aDistanceDelay) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->set3dDistanceDelay(!!aDistanceDelay); +} + +void Vizsn_set3dCollider(void * aClassPtr, AudioCollider * aCollider) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->set3dCollider(aCollider); +} + +void Vizsn_set3dColliderEx(void * aClassPtr, AudioCollider * aCollider, int aUserData) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->set3dCollider(aCollider, aUserData); +} + +void Vizsn_set3dAttenuator(void * aClassPtr, AudioAttenuator * aAttenuator) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->set3dAttenuator(aAttenuator); +} + +void Vizsn_setInaudibleBehavior(void * aClassPtr, int aMustTick, int aKill) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->setInaudibleBehavior(!!aMustTick, !!aKill); +} + +void Vizsn_setLoopPoint(void * aClassPtr, double aLoopPoint) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->setLoopPoint(aLoopPoint); +} + +double Vizsn_getLoopPoint(void * aClassPtr) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + return cl->getLoopPoint(); +} + +void Vizsn_setFilter(void * aClassPtr, unsigned int aFilterId, Filter * aFilter) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->setFilter(aFilterId, aFilter); +} + +void Vizsn_stop(void * aClassPtr) +{ + Vizsn * cl = (Vizsn *)aClassPtr; + cl->stop(); +} + +void Wav_destroy(void * aClassPtr) +{ + delete (Wav *)aClassPtr; +} + +void * Wav_create() +{ + return (void *)new Wav; +} + +int Wav_load(void * aClassPtr, const char * aFilename) +{ + Wav * cl = (Wav *)aClassPtr; + return cl->load(aFilename); +} + +int Wav_loadMem(void * aClassPtr, unsigned char * aMem, unsigned int aLength) +{ + Wav * cl = (Wav *)aClassPtr; + return cl->loadMem(aMem, aLength); +} + +int Wav_loadMemEx(void * aClassPtr, unsigned char * aMem, unsigned int aLength, int aCopy, int aTakeOwnership) +{ + Wav * cl = (Wav *)aClassPtr; + return cl->loadMem(aMem, aLength, !!aCopy, !!aTakeOwnership); +} + +int Wav_loadFile(void * aClassPtr, File * aFile) +{ + Wav * cl = (Wav *)aClassPtr; + return cl->loadFile(aFile); +} + +int Wav_loadRawWave8(void * aClassPtr, unsigned char * aMem, unsigned int aLength) +{ + Wav * cl = (Wav *)aClassPtr; + return cl->loadRawWave8(aMem, aLength); +} + +int Wav_loadRawWave8Ex(void * aClassPtr, unsigned char * aMem, unsigned int aLength, float aSamplerate, unsigned int aChannels) +{ + Wav * cl = (Wav *)aClassPtr; + return cl->loadRawWave8(aMem, aLength, aSamplerate, aChannels); +} + +int Wav_loadRawWave16(void * aClassPtr, short * aMem, unsigned int aLength) +{ + Wav * cl = (Wav *)aClassPtr; + return cl->loadRawWave16(aMem, aLength); +} + +int Wav_loadRawWave16Ex(void * aClassPtr, short * aMem, unsigned int aLength, float aSamplerate, unsigned int aChannels) +{ + Wav * cl = (Wav *)aClassPtr; + return cl->loadRawWave16(aMem, aLength, aSamplerate, aChannels); +} + +int Wav_loadRawWave(void * aClassPtr, float * aMem, unsigned int aLength) +{ + Wav * cl = (Wav *)aClassPtr; + return cl->loadRawWave(aMem, aLength); +} + +int Wav_loadRawWaveEx(void * aClassPtr, float * aMem, unsigned int aLength, float aSamplerate, unsigned int aChannels, int aCopy, int aTakeOwnership) +{ + Wav * cl = (Wav *)aClassPtr; + return cl->loadRawWave(aMem, aLength, aSamplerate, aChannels, !!aCopy, !!aTakeOwnership); +} + +double Wav_getLength(void * aClassPtr) +{ + Wav * cl = (Wav *)aClassPtr; + return cl->getLength(); +} + +void Wav_setVolume(void * aClassPtr, float aVolume) +{ + Wav * cl = (Wav *)aClassPtr; + cl->setVolume(aVolume); +} + +void Wav_setLooping(void * aClassPtr, int aLoop) +{ + Wav * cl = (Wav *)aClassPtr; + cl->setLooping(!!aLoop); +} + +void Wav_set3dMinMaxDistance(void * aClassPtr, float aMinDistance, float aMaxDistance) +{ + Wav * cl = (Wav *)aClassPtr; + cl->set3dMinMaxDistance(aMinDistance, aMaxDistance); +} + +void Wav_set3dAttenuation(void * aClassPtr, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) +{ + Wav * cl = (Wav *)aClassPtr; + cl->set3dAttenuation(aAttenuationModel, aAttenuationRolloffFactor); +} + +void Wav_set3dDopplerFactor(void * aClassPtr, float aDopplerFactor) +{ + Wav * cl = (Wav *)aClassPtr; + cl->set3dDopplerFactor(aDopplerFactor); +} + +void Wav_set3dListenerRelative(void * aClassPtr, int aListenerRelative) +{ + Wav * cl = (Wav *)aClassPtr; + cl->set3dListenerRelative(!!aListenerRelative); +} + +void Wav_set3dDistanceDelay(void * aClassPtr, int aDistanceDelay) +{ + Wav * cl = (Wav *)aClassPtr; + cl->set3dDistanceDelay(!!aDistanceDelay); +} + +void Wav_set3dCollider(void * aClassPtr, AudioCollider * aCollider) +{ + Wav * cl = (Wav *)aClassPtr; + cl->set3dCollider(aCollider); +} + +void Wav_set3dColliderEx(void * aClassPtr, AudioCollider * aCollider, int aUserData) +{ + Wav * cl = (Wav *)aClassPtr; + cl->set3dCollider(aCollider, aUserData); +} + +void Wav_set3dAttenuator(void * aClassPtr, AudioAttenuator * aAttenuator) +{ + Wav * cl = (Wav *)aClassPtr; + cl->set3dAttenuator(aAttenuator); +} + +void Wav_setInaudibleBehavior(void * aClassPtr, int aMustTick, int aKill) +{ + Wav * cl = (Wav *)aClassPtr; + cl->setInaudibleBehavior(!!aMustTick, !!aKill); +} + +void Wav_setLoopPoint(void * aClassPtr, double aLoopPoint) +{ + Wav * cl = (Wav *)aClassPtr; + cl->setLoopPoint(aLoopPoint); +} + +double Wav_getLoopPoint(void * aClassPtr) +{ + Wav * cl = (Wav *)aClassPtr; + return cl->getLoopPoint(); +} + +void Wav_setFilter(void * aClassPtr, unsigned int aFilterId, Filter * aFilter) +{ + Wav * cl = (Wav *)aClassPtr; + cl->setFilter(aFilterId, aFilter); +} + +void Wav_stop(void * aClassPtr) +{ + Wav * cl = (Wav *)aClassPtr; + cl->stop(); +} + +void WaveShaperFilter_destroy(void * aClassPtr) +{ + delete (WaveShaperFilter *)aClassPtr; +} + +int WaveShaperFilter_setParams(void * aClassPtr, float aAmount) +{ + WaveShaperFilter * cl = (WaveShaperFilter *)aClassPtr; + return cl->setParams(aAmount); +} + +int WaveShaperFilter_setParamsEx(void * aClassPtr, float aAmount, float aWet) +{ + WaveShaperFilter * cl = (WaveShaperFilter *)aClassPtr; + return cl->setParams(aAmount, aWet); +} + +void * WaveShaperFilter_create() +{ + return (void *)new WaveShaperFilter; +} + +void WavStream_destroy(void * aClassPtr) +{ + delete (WavStream *)aClassPtr; +} + +void * WavStream_create() +{ + return (void *)new WavStream; +} + +int WavStream_load(void * aClassPtr, const char * aFilename) +{ + WavStream * cl = (WavStream *)aClassPtr; + return cl->load(aFilename); +} + +int WavStream_loadMem(void * aClassPtr, unsigned char * aData, unsigned int aDataLen) +{ + WavStream * cl = (WavStream *)aClassPtr; + return cl->loadMem(aData, aDataLen); +} + +int WavStream_loadMemEx(void * aClassPtr, unsigned char * aData, unsigned int aDataLen, int aCopy, int aTakeOwnership) +{ + WavStream * cl = (WavStream *)aClassPtr; + return cl->loadMem(aData, aDataLen, !!aCopy, !!aTakeOwnership); +} + +int WavStream_loadToMem(void * aClassPtr, const char * aFilename) +{ + WavStream * cl = (WavStream *)aClassPtr; + return cl->loadToMem(aFilename); +} + +int WavStream_loadFile(void * aClassPtr, File * aFile) +{ + WavStream * cl = (WavStream *)aClassPtr; + return cl->loadFile(aFile); +} + +int WavStream_loadFileToMem(void * aClassPtr, File * aFile) +{ + WavStream * cl = (WavStream *)aClassPtr; + return cl->loadFileToMem(aFile); +} + +double WavStream_getLength(void * aClassPtr) +{ + WavStream * cl = (WavStream *)aClassPtr; + return cl->getLength(); +} + +void WavStream_setVolume(void * aClassPtr, float aVolume) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->setVolume(aVolume); +} + +void WavStream_setLooping(void * aClassPtr, int aLoop) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->setLooping(!!aLoop); +} + +void WavStream_set3dMinMaxDistance(void * aClassPtr, float aMinDistance, float aMaxDistance) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->set3dMinMaxDistance(aMinDistance, aMaxDistance); +} + +void WavStream_set3dAttenuation(void * aClassPtr, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->set3dAttenuation(aAttenuationModel, aAttenuationRolloffFactor); +} + +void WavStream_set3dDopplerFactor(void * aClassPtr, float aDopplerFactor) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->set3dDopplerFactor(aDopplerFactor); +} + +void WavStream_set3dListenerRelative(void * aClassPtr, int aListenerRelative) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->set3dListenerRelative(!!aListenerRelative); +} + +void WavStream_set3dDistanceDelay(void * aClassPtr, int aDistanceDelay) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->set3dDistanceDelay(!!aDistanceDelay); +} + +void WavStream_set3dCollider(void * aClassPtr, AudioCollider * aCollider) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->set3dCollider(aCollider); +} + +void WavStream_set3dColliderEx(void * aClassPtr, AudioCollider * aCollider, int aUserData) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->set3dCollider(aCollider, aUserData); +} + +void WavStream_set3dAttenuator(void * aClassPtr, AudioAttenuator * aAttenuator) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->set3dAttenuator(aAttenuator); +} + +void WavStream_setInaudibleBehavior(void * aClassPtr, int aMustTick, int aKill) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->setInaudibleBehavior(!!aMustTick, !!aKill); +} + +void WavStream_setLoopPoint(void * aClassPtr, double aLoopPoint) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->setLoopPoint(aLoopPoint); +} + +double WavStream_getLoopPoint(void * aClassPtr) +{ + WavStream * cl = (WavStream *)aClassPtr; + return cl->getLoopPoint(); +} + +void WavStream_setFilter(void * aClassPtr, unsigned int aFilterId, Filter * aFilter) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->setFilter(aFilterId, aFilter); +} + +void WavStream_stop(void * aClassPtr) +{ + WavStream * cl = (WavStream *)aClassPtr; + cl->stop(); +} + +} // extern "C" + diff --git a/src/soloud/src/core/soloud.cpp b/src/soloud/src/core/soloud.cpp new file mode 100644 index 0000000..8406368 --- /dev/null +++ b/src/soloud/src/core/soloud.cpp @@ -0,0 +1,2005 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include +#include // sin +#include "soloud_internal.h" +#include "soloud_thread.h" +#include "soloud_fft.h" + +#ifdef SOLOUD_SSE_INTRINSICS +#include +#endif + +//#define FLOATING_POINT_DEBUG + +#ifdef FLOATING_POINT_DEBUG +#include +#endif + + + +#if !defined(WITH_SDL2) && !defined(WITH_SDL1) && !defined(WITH_PORTAUDIO) && \ + !defined(WITH_OPENAL) && !defined(WITH_XAUDIO2) && !defined(WITH_WINMM) && \ + !defined(WITH_WASAPI) && !defined(WITH_OSS) && !defined(WITH_SDL1_STATIC) && \ + !defined(WITH_SDL2_STATIC) && !defined(WITH_ALSA) && !defined(WITH_OPENSLES) && \ + !defined(WITH_NULL) && !defined(WITH_COREAUDIO) && !defined(WITH_VITA_HOMEBREW) +#error It appears you haven't enabled any of the back-ends. Please #define one or more of the WITH_ defines (or use premake) ' +#endif + + +namespace SoLoud +{ + AlignedFloatBuffer::AlignedFloatBuffer() + { + mBasePtr = 0; + mData = 0; + } + + result AlignedFloatBuffer::init(unsigned int aFloats) + { + delete[] mBasePtr; + mBasePtr = 0; + mData = 0; + mFloats = aFloats; +#ifdef DISABLE_SIMD + mBasePtr = new unsigned char[aFloats * sizeof(float)]; + if (mBasePtr == NULL) + return OUT_OF_MEMORY; + mData = mBasePtr; +#else + mBasePtr = new unsigned char[aFloats * sizeof(float) + 16]; + if (mBasePtr == NULL) + return OUT_OF_MEMORY; + mData = (float *)(((size_t)mBasePtr + 15)&~15); +#endif + return SO_NO_ERROR; + } + + void AlignedFloatBuffer::clear() + { + memset(mData, 0, sizeof(float) * mFloats); + } + + AlignedFloatBuffer::~AlignedFloatBuffer() + { + delete[] mBasePtr; + } + + TinyAlignedFloatBuffer::TinyAlignedFloatBuffer() + { + unsigned char * basePtr = &mActualData[0]; + mData = (float *)(((size_t)basePtr + 15)&~15); + } + + Soloud::Soloud() + { +#ifdef FLOATING_POINT_DEBUG + unsigned int u; + u = _controlfp(0, 0); + u = u & ~(_EM_INVALID | /*_EM_DENORMAL |*/ _EM_ZERODIVIDE | _EM_OVERFLOW /*| _EM_UNDERFLOW | _EM_INEXACT*/); + _controlfp(u, _MCW_EM); +#endif + mInsideAudioThreadMutex = false; + mScratchSize = 0; + mScratchNeeded = 0; + mSamplerate = 0; + mBufferSize = 0; + mFlags = 0; + mGlobalVolume = 0; + mPlayIndex = 0; + mBackendData = NULL; + mAudioThreadMutex = NULL; + mPostClipScaler = 0; + mBackendCleanupFunc = NULL; + mChannels = 2; + mStreamTime = 0; + mLastClockedTime = 0; + mAudioSourceID = 1; + mBackendString = 0; + mBackendID = 0; + mActiveVoiceDirty = true; + mActiveVoiceCount = 0; + int i; + for (i = 0; i < VOICE_COUNT; i++) + mActiveVoice[i] = 0; + for (i = 0; i < FILTERS_PER_STREAM; i++) + { + mFilter[i] = NULL; + mFilterInstance[i] = NULL; + } + for (i = 0; i < 256; i++) + { + mFFTData[i] = 0; + mVisualizationWaveData[i] = 0; + mWaveData[i] = 0; + } + for (i = 0; i < MAX_CHANNELS; i++) + { + mVisualizationChannelVolume[i] = 0; + } + for (i = 0; i < VOICE_COUNT; i++) + { + mVoice[i] = 0; + } + mVoiceGroup = 0; + mVoiceGroupCount = 0; + + m3dPosition[0] = 0; + m3dPosition[1] = 0; + m3dPosition[2] = 0; + m3dAt[0] = 0; + m3dAt[1] = 0; + m3dAt[2] = -1; + m3dUp[0] = 0; + m3dUp[1] = 1; + m3dUp[2] = 0; + m3dVelocity[0] = 0; + m3dVelocity[1] = 0; + m3dVelocity[2] = 0; + m3dSoundSpeed = 343.3f; + mMaxActiveVoices = 16; + mHighestVoice = 0; + mActiveVoiceDirty = true; + mResampleData = NULL; + } + + Soloud::~Soloud() + { + // let's stop all sounds before deinit, so we don't mess up our mutexes + stopAll(); + deinit(); + unsigned int i; + for (i = 0; i < FILTERS_PER_STREAM; i++) + { + delete mFilterInstance[i]; + } + for (i = 0; i < mVoiceGroupCount; i++) + delete[] mVoiceGroup[i]; + delete[] mVoiceGroup; + delete[] mResampleData; + } + + void Soloud::deinit() + { + SOLOUD_ASSERT(!mInsideAudioThreadMutex); + if (mBackendCleanupFunc) + mBackendCleanupFunc(this); + mBackendCleanupFunc = 0; + if (mAudioThreadMutex) + Thread::destroyMutex(mAudioThreadMutex); + mAudioThreadMutex = NULL; + } + + result Soloud::init(unsigned int aFlags, unsigned int aBackend, unsigned int aSamplerate, unsigned int aBufferSize, unsigned int aChannels) + { + if (aBackend >= BACKEND_MAX || aChannels == 3 || aChannels == 5 || aChannels == 7 || aChannels > MAX_CHANNELS) + return INVALID_PARAMETER; + + deinit(); + + mAudioThreadMutex = Thread::createMutex(); + + mBackendID = 0; + mBackendString = 0; + + int samplerate = 44100; + int buffersize = 2048; + int inited = 0; + + if (aSamplerate != Soloud::AUTO) samplerate = aSamplerate; + if (aBufferSize != Soloud::AUTO) buffersize = aBufferSize; + +#if defined(WITH_SDL1_STATIC) + if (!inited && + (aBackend == Soloud::SDL1 || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 2048; + + int ret = sdl1static_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::SDL1; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_SDL2_STATIC) + if (!inited && + (aBackend == Soloud::SDL2 || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 2048; + + int ret = sdl2static_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::SDL2; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_SDL2) + if (!inited && + (aBackend == Soloud::SDL2 || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 2048; + + int ret = sdl2_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::SDL2; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_SDL1) + if (!inited && + (aBackend == Soloud::SDL1 || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 2048; + + int ret = sdl1_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::SDL1; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_PORTAUDIO) + if (!inited && + (aBackend == Soloud::PORTAUDIO || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 2048; + + int ret = portaudio_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::PORTAUDIO; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_XAUDIO2) + if (!inited && + (aBackend == Soloud::XAUDIO2 || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 4096; + + int ret = xaudio2_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::XAUDIO2; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_WINMM) + if (!inited && + (aBackend == Soloud::WINMM || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 4096; + + int ret = winmm_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::WINMM; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_WASAPI) + if (!inited && + (aBackend == Soloud::WASAPI || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 4096; + if (aSamplerate == Soloud::AUTO) samplerate = 48000; + + int ret = wasapi_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::WASAPI; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_ALSA) + if (!inited && + (aBackend == Soloud::ALSA || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 2048; + + int ret = alsa_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::ALSA; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_OSS) + if (!inited && + (aBackend == Soloud::OSS || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 2048; + + int ret = oss_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::OSS; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_OPENAL) + if (!inited && + (aBackend == Soloud::OPENAL || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 4096; + + int ret = openal_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::OPENAL; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_COREAUDIO) + if (!inited && + (aBackend == Soloud::COREAUDIO || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 2048; + + int ret = coreaudio_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::COREAUDIO; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_OPENSLES) + if (!inited && + (aBackend == Soloud::OPENSLES || + aBackend == Soloud::AUTO)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 4096; + + int ret = opensles_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::OPENSLES; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_VITA_HOMEBREW) + if (!inited && + (aBackend == Soloud::VITA_HOMEBREW || + aBackend == Soloud::AUTO)) + { + int ret = vita_homebrew_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::VITA_HOMEBREW; + } + + if (ret != 0 && aBackend != Soloud::AUTO) + return ret; + } +#endif + +#if defined(WITH_NULL) + if (!inited && + (aBackend == Soloud::NULLDRIVER)) + { + if (aBufferSize == Soloud::AUTO) buffersize = 2048; + + int ret = null_init(this, aFlags, samplerate, buffersize, aChannels); + if (ret == 0) + { + inited = 1; + mBackendID = Soloud::NULLDRIVER; + } + + if (ret != 0) + return ret; + } +#endif + + if (!inited && aBackend != Soloud::AUTO) + return NOT_IMPLEMENTED; + if (!inited) + return UNKNOWN_ERROR; + return 0; + } + + void Soloud::postinit(unsigned int aSamplerate, unsigned int aBufferSize, unsigned int aFlags, unsigned int aChannels) + { + mGlobalVolume = 1; + mChannels = aChannels; + mSamplerate = aSamplerate; + mBufferSize = aBufferSize; + mScratchSize = aBufferSize; + if (mScratchSize < SAMPLE_GRANULARITY * 2) mScratchSize = SAMPLE_GRANULARITY * 2; + if (mScratchSize < 4096) mScratchSize = 4096; + mScratchNeeded = mScratchSize; + mScratch.init(mScratchSize * MAX_CHANNELS); + mOutputScratch.init(mScratchSize * MAX_CHANNELS); + mResampleData = new AlignedFloatBuffer[mMaxActiveVoices * 2]; + mResampleDataOwner = new AudioSourceInstance*[mMaxActiveVoices]; + unsigned int i; + for (i = 0; i < mMaxActiveVoices * 2; i++) + mResampleData[i].init(SAMPLE_GRANULARITY * MAX_CHANNELS); + for (i = 0; i < mMaxActiveVoices; i++) + mResampleDataOwner[i] = NULL; + mFlags = aFlags; + mPostClipScaler = 0.95f; + switch (mChannels) + { + case 1: + m3dSpeakerPosition[0 * 3 + 0] = 0; + m3dSpeakerPosition[0 * 3 + 1] = 0; + m3dSpeakerPosition[0 * 3 + 2] = 1; + break; + case 2: + m3dSpeakerPosition[0 * 3 + 0] = 2; + m3dSpeakerPosition[0 * 3 + 1] = 0; + m3dSpeakerPosition[0 * 3 + 2] = 1; + m3dSpeakerPosition[1 * 3 + 0] = -2; + m3dSpeakerPosition[1 * 3 + 1] = 0; + m3dSpeakerPosition[1 * 3 + 2] = 1; + break; + case 4: + m3dSpeakerPosition[0 * 3 + 0] = 2; + m3dSpeakerPosition[0 * 3 + 1] = 0; + m3dSpeakerPosition[0 * 3 + 2] = 1; + m3dSpeakerPosition[1 * 3 + 0] = -2; + m3dSpeakerPosition[1 * 3 + 1] = 0; + m3dSpeakerPosition[1 * 3 + 2] = 1; + // I suppose technically the second pair should be straight left & right, + // but I prefer moving them a bit back to mirror the front speakers. + m3dSpeakerPosition[2 * 3 + 0] = 2; + m3dSpeakerPosition[2 * 3 + 1] = 0; + m3dSpeakerPosition[2 * 3 + 2] = -1; + m3dSpeakerPosition[3 * 3 + 0] = -2; + m3dSpeakerPosition[3 * 3 + 1] = 0; + m3dSpeakerPosition[3 * 3 + 2] = -1; + break; + case 6: + m3dSpeakerPosition[0 * 3 + 0] = 2; + m3dSpeakerPosition[0 * 3 + 1] = 0; + m3dSpeakerPosition[0 * 3 + 2] = 1; + m3dSpeakerPosition[1 * 3 + 0] = -2; + m3dSpeakerPosition[1 * 3 + 1] = 0; + m3dSpeakerPosition[1 * 3 + 2] = 1; + + // center and subwoofer. + m3dSpeakerPosition[2 * 3 + 0] = 0; + m3dSpeakerPosition[2 * 3 + 1] = 0; + m3dSpeakerPosition[2 * 3 + 2] = 1; + // Sub should be "mix of everything". We'll handle it as a special case and make it a null vector. + m3dSpeakerPosition[3 * 3 + 0] = 0; + m3dSpeakerPosition[3 * 3 + 1] = 0; + m3dSpeakerPosition[3 * 3 + 2] = 0; + + // I suppose technically the second pair should be straight left & right, + // but I prefer moving them a bit back to mirror the front speakers. + m3dSpeakerPosition[4 * 3 + 0] = 2; + m3dSpeakerPosition[4 * 3 + 1] = 0; + m3dSpeakerPosition[4 * 3 + 2] = -1; + m3dSpeakerPosition[5 * 3 + 0] = -2; + m3dSpeakerPosition[5 * 3 + 1] = 0; + m3dSpeakerPosition[5 * 3 + 2] = -1; + break; + case 8: + m3dSpeakerPosition[0 * 3 + 0] = 2; + m3dSpeakerPosition[0 * 3 + 1] = 0; + m3dSpeakerPosition[0 * 3 + 2] = 1; + m3dSpeakerPosition[1 * 3 + 0] = -2; + m3dSpeakerPosition[1 * 3 + 1] = 0; + m3dSpeakerPosition[1 * 3 + 2] = 1; + + // center and subwoofer. + m3dSpeakerPosition[2 * 3 + 0] = 0; + m3dSpeakerPosition[2 * 3 + 1] = 0; + m3dSpeakerPosition[2 * 3 + 2] = 1; + // Sub should be "mix of everything". We'll handle it as a special case and make it a null vector. + m3dSpeakerPosition[3 * 3 + 0] = 0; + m3dSpeakerPosition[3 * 3 + 1] = 0; + m3dSpeakerPosition[3 * 3 + 2] = 0; + + // side + m3dSpeakerPosition[4 * 3 + 0] = 2; + m3dSpeakerPosition[4 * 3 + 1] = 0; + m3dSpeakerPosition[4 * 3 + 2] = 0; + m3dSpeakerPosition[5 * 3 + 0] = -2; + m3dSpeakerPosition[5 * 3 + 1] = 0; + m3dSpeakerPosition[5 * 3 + 2] = 0; + + // back + m3dSpeakerPosition[6 * 3 + 0] = 2; + m3dSpeakerPosition[6 * 3 + 1] = 0; + m3dSpeakerPosition[6 * 3 + 2] = -1; + m3dSpeakerPosition[7 * 3 + 0] = -2; + m3dSpeakerPosition[7 * 3 + 1] = 0; + m3dSpeakerPosition[7 * 3 + 2] = -1; + break; + } + } + + const char * Soloud::getErrorString(result aErrorCode) const + { + switch (aErrorCode) + { + case SO_NO_ERROR: return "No error"; + case INVALID_PARAMETER: return "Some parameter is invalid"; + case FILE_NOT_FOUND: return "File not found"; + case FILE_LOAD_FAILED: return "File found, but could not be loaded"; + case DLL_NOT_FOUND: return "DLL not found, or wrong DLL"; + case OUT_OF_MEMORY: return "Out of memory"; + case NOT_IMPLEMENTED: return "Feature not implemented"; + /*case UNKNOWN_ERROR: return "Other error";*/ + } + return "Other error"; + } + + + float * Soloud::getWave() + { + int i; + lockAudioMutex(); + for (i = 0; i < 256; i++) + mWaveData[i] = mVisualizationWaveData[i]; + unlockAudioMutex(); + return mWaveData; + } + + float Soloud::getApproximateVolume(unsigned int aChannel) + { + if (aChannel > mChannels) + return 0; + float vol = 0; + lockAudioMutex(); + vol = mVisualizationChannelVolume[aChannel]; + unlockAudioMutex(); + return vol; + } + + + float * Soloud::calcFFT() + { + lockAudioMutex(); + float temp[1024]; + int i; + for (i = 0; i < 256; i++) + { + temp[i*2] = mVisualizationWaveData[i]; + temp[i*2+1] = 0; + temp[i+512] = 0; + temp[i+768] = 0; + } + unlockAudioMutex(); + + SoLoud::FFT::fft1024(temp); + + for (i = 0; i < 256; i++) + { + float real = temp[i * 2]; + float imag = temp[i * 2 + 1]; + mFFTData[i] = (float)sqrt(real*real+imag*imag); + } + + return mFFTData; + } + +#ifdef SOLOUD_SSE_INTRINSICS + void Soloud::clip(AlignedFloatBuffer &aBuffer, AlignedFloatBuffer &aDestBuffer, unsigned int aSamples, float aVolume0, float aVolume1) + { + float vd = (aVolume1 - aVolume0) / aSamples; + float v = aVolume0; + unsigned int i, j, c, d; + unsigned int samplequads = (aSamples + 3) / 4; // rounded up + + // Clip + if (mFlags & CLIP_ROUNDOFF) + { + float nb = -1.65f; __m128 negbound = _mm_load_ps1(&nb); + float pb = 1.65f; __m128 posbound = _mm_load_ps1(&pb); + float ls = 0.87f; __m128 linearscale = _mm_load_ps1(&ls); + float cs = -0.1f; __m128 cubicscale = _mm_load_ps1(&cs); + float nw = -0.9862875f; __m128 negwall = _mm_load_ps1(&nw); + float pw = 0.9862875f; __m128 poswall = _mm_load_ps1(&pw); + __m128 postscale = _mm_load_ps1(&mPostClipScaler); + TinyAlignedFloatBuffer volumes; + volumes.mData[0] = v; + volumes.mData[1] = v + vd; + volumes.mData[2] = v + vd + vd; + volumes.mData[3] = v + vd + vd + vd; + vd *= 4; + __m128 vdelta = _mm_load_ps1(&vd); + c = 0; + d = 0; + for (j = 0; j < mChannels; j++) + { + __m128 vol = _mm_load_ps(volumes.mData); + + for (i = 0; i < samplequads; i++) + { + //float f1 = origdata[c] * v; c++; v += vd; + __m128 f = _mm_load_ps(&aBuffer.mData[c]); + c += 4; + f = _mm_mul_ps(f, vol); + vol = _mm_add_ps(vol, vdelta); + + //float u1 = (f1 > -1.65f); + __m128 u = _mm_cmpgt_ps(f, negbound); + + //float o1 = (f1 < 1.65f); + __m128 o = _mm_cmplt_ps(f, posbound); + + //f1 = (0.87f * f1 - 0.1f * f1 * f1 * f1) * u1 * o1; + __m128 lin = _mm_mul_ps(f, linearscale); + __m128 cubic = _mm_mul_ps(f, f); + cubic = _mm_mul_ps(cubic, f); + cubic = _mm_mul_ps(cubic, cubicscale); + f = _mm_add_ps(cubic, lin); + + //f1 = f1 * u1 + !u1 * -0.9862875f; + __m128 lowmask = _mm_andnot_ps(u, negwall); + __m128 ilowmask = _mm_and_ps(u, f); + f = _mm_add_ps(lowmask, ilowmask); + + //f1 = f1 * o1 + !o1 * 0.9862875f; + __m128 himask = _mm_andnot_ps(o, poswall); + __m128 ihimask = _mm_and_ps(o, f); + f = _mm_add_ps(himask, ihimask); + + // outdata[d] = f1 * postclip; d++; + f = _mm_mul_ps(f, postscale); + _mm_store_ps(&aDestBuffer.mData[d], f); + d += 4; + } + } + } + else + { + float nb = -1.0f; __m128 negbound = _mm_load_ps1(&nb); + float pb = 1.0f; __m128 posbound = _mm_load_ps1(&pb); + __m128 postscale = _mm_load_ps1(&mPostClipScaler); + TinyAlignedFloatBuffer volumes; + volumes.mData[0] = v; + volumes.mData[1] = v + vd; + volumes.mData[2] = v + vd + vd; + volumes.mData[3] = v + vd + vd + vd; + vd *= 4; + __m128 vdelta = _mm_load_ps1(&vd); + c = 0; + d = 0; + for (j = 0; j < mChannels; j++) + { + __m128 vol = _mm_load_ps(volumes.mData); + for (i = 0; i < samplequads; i++) + { + //float f1 = aBuffer.mData[c] * v; c++; v += vd; + __m128 f = _mm_load_ps(&aBuffer.mData[c]); + c += 4; + f = _mm_mul_ps(f, vol); + vol = _mm_add_ps(vol, vdelta); + + //f1 = (f1 <= -1) ? -1 : (f1 >= 1) ? 1 : f1; + f = _mm_max_ps(f, negbound); + f = _mm_min_ps(f, posbound); + + //aDestBuffer.mData[d] = f1 * mPostClipScaler; d++; + f = _mm_mul_ps(f, postscale); + _mm_store_ps(&aDestBuffer.mData[d], f); + d += 4; + } + } + } + } +#else // fallback code + void Soloud::clip(AlignedFloatBuffer &aBuffer, AlignedFloatBuffer &aDestBuffer, unsigned int aSamples, float aVolume0, float aVolume1) + { + float vd = (aVolume1 - aVolume0) / aSamples; + float v = aVolume0; + unsigned int i, j, c, d; + unsigned int samplequads = (aSamples + 3) / 4; // rounded up + // Clip + if (mFlags & CLIP_ROUNDOFF) + { + c = 0; + d = 0; + for (j = 0; j < mChannels; j++) + { + v = aVolume0; + for (i = 0; i < samplequads; i++) + { + float f1 = aBuffer.mData[c] * v; c++; v += vd; + float f2 = aBuffer.mData[c] * v; c++; v += vd; + float f3 = aBuffer.mData[c] * v; c++; v += vd; + float f4 = aBuffer.mData[c] * v; c++; v += vd; + + f1 = (f1 <= -1.65f) ? -0.9862875f : (f1 >= 1.65f) ? 0.9862875f : (0.87f * f1 - 0.1f * f1 * f1 * f1); + f2 = (f2 <= -1.65f) ? -0.9862875f : (f2 >= 1.65f) ? 0.9862875f : (0.87f * f2 - 0.1f * f2 * f2 * f2); + f3 = (f3 <= -1.65f) ? -0.9862875f : (f3 >= 1.65f) ? 0.9862875f : (0.87f * f3 - 0.1f * f3 * f3 * f3); + f4 = (f4 <= -1.65f) ? -0.9862875f : (f4 >= 1.65f) ? 0.9862875f : (0.87f * f4 - 0.1f * f4 * f4 * f4); + + aDestBuffer.mData[d] = f1 * mPostClipScaler; d++; + aDestBuffer.mData[d] = f2 * mPostClipScaler; d++; + aDestBuffer.mData[d] = f3 * mPostClipScaler; d++; + aDestBuffer.mData[d] = f4 * mPostClipScaler; d++; + } + } + } + else + { + c = 0; + d = 0; + for (j = 0; j < mChannels; j++) + { + v = aVolume0; + for (i = 0; i < samplequads; i++) + { + float f1 = aBuffer.mData[c] * v; c++; v += vd; + float f2 = aBuffer.mData[c] * v; c++; v += vd; + float f3 = aBuffer.mData[c] * v; c++; v += vd; + float f4 = aBuffer.mData[c] * v; c++; v += vd; + + f1 = (f1 <= -1) ? -1 : (f1 >= 1) ? 1 : f1; + f2 = (f2 <= -1) ? -1 : (f2 >= 1) ? 1 : f2; + f3 = (f3 <= -1) ? -1 : (f3 >= 1) ? 1 : f3; + f4 = (f4 <= -1) ? -1 : (f4 >= 1) ? 1 : f4; + + aDestBuffer.mData[d] = f1 * mPostClipScaler; d++; + aDestBuffer.mData[d] = f2 * mPostClipScaler; d++; + aDestBuffer.mData[d] = f3 * mPostClipScaler; d++; + aDestBuffer.mData[d] = f4 * mPostClipScaler; d++; + } + } + } +} +#endif + +#define FIXPOINT_FRAC_BITS 20 +#define FIXPOINT_FRAC_MUL (1 << FIXPOINT_FRAC_BITS) +#define FIXPOINT_FRAC_MASK ((1 << FIXPOINT_FRAC_BITS) - 1) + + void resample(float *aSrc, + float *aSrc1, + float *aDst, + int aSrcOffset, + int aDstSampleCount, + float aSrcSamplerate, + float aDstSamplerate, + int aStepFixed) + { +#if 0 + +#elif defined(RESAMPLER_LINEAR) + int i; + int pos = aSrcOffset; + + for (i = 0; i < aDstSampleCount; i++, pos += aStepFixed) + { + int p = pos >> FIXPOINT_FRAC_BITS; + int f = pos & FIXPOINT_FRAC_MASK; +#ifdef _DEBUG + if (p >= SAMPLE_GRANULARITY || p < 0) + { + // This should never actually happen + p = SAMPLE_GRANULARITY - 1; + } +#endif + float s1 = aSrc1[SAMPLE_GRANULARITY - 1]; + float s2 = aSrc[p]; + if (p != 0) + { + s1 = aSrc[p-1]; + } + aDst[i] = s1 + (s2 - s1) * f * (1 / (float)FIXPOINT_FRAC_MUL); + } +#else // Point sample + int i; + int pos = aSrcOffset; + + for (i = 0; i < aDstSampleCount; i++, pos += aStepFixed) + { + int p = pos >> FIXPOINT_FRAC_BITS; + aDst[i] = aSrc[p]; + } +#endif + } + + void panAndExpand(AudioSourceInstance *aVoice, float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize, float *aScratch, unsigned int aChannels) + { + float pan[MAX_CHANNELS]; // current speaker volume + float pand[MAX_CHANNELS]; // destination speaker volume + float pani[MAX_CHANNELS]; // speaker volume increment per sample + unsigned int j, k; + for (k = 0; k < aChannels; k++) + { + pan[k] = aVoice->mCurrentChannelVolume[k]; + pand[k] = aVoice->mChannelVolume[k] * aVoice->mOverallVolume; + pani[k] = (pand[k] - pan[k]) / aSamplesToRead; // TODO: this is a bit inconsistent.. but it's a hack to begin with + } + + int ofs = 0; + switch (aChannels) + { + case 1: // Target is mono. Sum everything. (1->1, 2->1, 4->1, 6->1, 8->1) + for (j = 0, ofs = 0; j < aVoice->mChannels; j++, ofs += aBufferSize) + { + pan[0] = aVoice->mCurrentChannelVolume[0]; + for (k = 0; k < aSamplesToRead; k++) + { + pan[0] += pani[0]; + aBuffer[k] += aScratch[ofs + k] * pan[0]; + } + } + break; + case 2: + switch (aVoice->mChannels) + { + case 8: // 8->2, just sum lefties and righties, add a bit of center and sub? + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + float s3 = aScratch[aBufferSize * 2 + j]; + float s4 = aScratch[aBufferSize * 3 + j]; + float s5 = aScratch[aBufferSize * 4 + j]; + float s6 = aScratch[aBufferSize * 5 + j]; + float s7 = aScratch[aBufferSize * 6 + j]; + float s8 = aScratch[aBufferSize * 7 + j]; + aBuffer[j + 0] += 0.2f * (s1 + s3 + s4 + s5 + s7) * pan[0]; + aBuffer[j + aBufferSize] += 0.2f * (s2 + s3 + s4 + s6 + s8) * pan[1]; + } + break; + case 6: // 6->2, just sum lefties and righties, add a bit of center and sub? + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + float s3 = aScratch[aBufferSize * 2 + j]; + float s4 = aScratch[aBufferSize * 3 + j]; + float s5 = aScratch[aBufferSize * 4 + j]; + float s6 = aScratch[aBufferSize * 5 + j]; + aBuffer[j + 0] += 0.3f * (s1 + s3 + s4 + s5) * pan[0]; + aBuffer[j + aBufferSize] += 0.3f * (s2 + s3 + s4 + s6) * pan[1]; + } + break; + case 4: // 4->2, just sum lefties and righties + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + float s3 = aScratch[aBufferSize * 2 + j]; + float s4 = aScratch[aBufferSize * 3 + j]; + aBuffer[j + 0] += 0.5f * (s1 + s3) * pan[0]; + aBuffer[j + aBufferSize] += 0.5f * (s2 + s4) * pan[1]; + } + break; + case 2: // 2->2 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + aBuffer[j + 0] += s1 * pan[0]; + aBuffer[j + aBufferSize] += s2 * pan[1]; + } + break; + case 1: // 1->2 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + float s = aScratch[j]; + aBuffer[j + 0] += s * pan[0]; + aBuffer[j + aBufferSize] += s * pan[1]; + } + break; + } + break; + case 4: + switch (aVoice->mChannels) + { + case 8: // 8->4, add a bit of center, sub? + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + float s3 = aScratch[aBufferSize * 2 + j]; + float s4 = aScratch[aBufferSize * 3 + j]; + float s5 = aScratch[aBufferSize * 4 + j]; + float s6 = aScratch[aBufferSize * 5 + j]; + float s7 = aScratch[aBufferSize * 6 + j]; + float s8 = aScratch[aBufferSize * 7 + j]; + float c = (s3 + s4) * 0.7f; + aBuffer[j + 0] += s1 * pan[0] + c; + aBuffer[j + aBufferSize] += s2 * pan[1] + c; + aBuffer[j + aBufferSize * 2] += 0.5f * (s5 + s7) * pan[2]; + aBuffer[j + aBufferSize * 3] += 0.5f * (s6 + s8) * pan[3]; + } + break; + case 6: // 6->4, add a bit of center, sub? + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + float s3 = aScratch[aBufferSize * 2 + j]; + float s4 = aScratch[aBufferSize * 3 + j]; + float s5 = aScratch[aBufferSize * 4 + j]; + float s6 = aScratch[aBufferSize * 5 + j]; + float c = (s3 + s4) * 0.7f; + aBuffer[j + 0] += s1 * pan[0] + c; + aBuffer[j + aBufferSize] += s2 * pan[1] + c; + aBuffer[j + aBufferSize * 2] += s5 * pan[2]; + aBuffer[j + aBufferSize * 3] += s6 * pan[3]; + } + break; + case 4: // 4->4 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + float s3 = aScratch[aBufferSize * 2 + j]; + float s4 = aScratch[aBufferSize * 3 + j]; + aBuffer[j + 0] += s1 * pan[0]; + aBuffer[j + aBufferSize] += s2 * pan[1]; + aBuffer[j + aBufferSize * 2] += s3 * pan[2]; + aBuffer[j + aBufferSize * 3] += s4 * pan[3]; + } + break; + case 2: // 2->4 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + aBuffer[j + 0] += s1 * pan[0]; + aBuffer[j + aBufferSize] += s2 * pan[1]; + aBuffer[j + aBufferSize * 2] += s1 * pan[2]; + aBuffer[j + aBufferSize * 3] += s2 * pan[3]; + } + break; + case 1: // 1->4 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + float s = aScratch[j]; + aBuffer[j + 0] += s * pan[0]; + aBuffer[j + aBufferSize] += s * pan[1]; + aBuffer[j + aBufferSize * 2] += s * pan[2]; + aBuffer[j + aBufferSize * 3] += s * pan[3]; + } + break; + } + break; + case 6: + switch (aVoice->mChannels) + { + case 8: // 8->6 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + pan[4] += pani[4]; + pan[5] += pani[5]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + float s3 = aScratch[aBufferSize * 2 + j]; + float s4 = aScratch[aBufferSize * 3 + j]; + float s5 = aScratch[aBufferSize * 4 + j]; + float s6 = aScratch[aBufferSize * 5 + j]; + float s7 = aScratch[aBufferSize * 6 + j]; + float s8 = aScratch[aBufferSize * 7 + j]; + aBuffer[j + 0] += s1 * pan[0]; + aBuffer[j + aBufferSize] += s2 * pan[1]; + aBuffer[j + aBufferSize * 2] += s3 * pan[2]; + aBuffer[j + aBufferSize * 3] += s4 * pan[3]; + aBuffer[j + aBufferSize * 4] += 0.5f * (s5 + s7) * pan[4]; + aBuffer[j + aBufferSize * 5] += 0.5f * (s6 + s8) * pan[5]; + } + break; + case 6: // 6->6 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + pan[4] += pani[4]; + pan[5] += pani[5]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + float s3 = aScratch[aBufferSize * 2 + j]; + float s4 = aScratch[aBufferSize * 3 + j]; + float s5 = aScratch[aBufferSize * 4 + j]; + float s6 = aScratch[aBufferSize * 5 + j]; + aBuffer[j + 0] += s1 * pan[0]; + aBuffer[j + aBufferSize] += s2 * pan[1]; + aBuffer[j + aBufferSize * 2] += s3 * pan[2]; + aBuffer[j + aBufferSize * 3] += s4 * pan[3]; + aBuffer[j + aBufferSize * 4] += s5 * pan[4]; + aBuffer[j + aBufferSize * 5] += s6 * pan[5]; + } + break; + case 4: // 4->6 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + pan[4] += pani[4]; + pan[5] += pani[5]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + float s3 = aScratch[aBufferSize * 2 + j]; + float s4 = aScratch[aBufferSize * 3 + j]; + aBuffer[j + 0] += s1 * pan[0]; + aBuffer[j + aBufferSize] += s2 * pan[1]; + aBuffer[j + aBufferSize * 2] += 0.5f * (s1 + s2) * pan[2]; + aBuffer[j + aBufferSize * 3] += 0.25f * (s1 + s2 + s3 + s4) * pan[3]; + aBuffer[j + aBufferSize * 4] += s3 * pan[4]; + aBuffer[j + aBufferSize * 5] += s4 * pan[5]; + } + break; + case 2: // 2->6 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + pan[4] += pani[4]; + pan[5] += pani[5]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + aBuffer[j + 0] += s1 * pan[0]; + aBuffer[j + aBufferSize] += s2 * pan[1]; + aBuffer[j + aBufferSize * 2] += 0.5f * (s1 + s2) * pan[2]; + aBuffer[j + aBufferSize * 3] += 0.5f * (s1 + s2) * pan[3]; + aBuffer[j + aBufferSize * 4] += s1 * pan[4]; + aBuffer[j + aBufferSize * 5] += s2 * pan[5]; + } + break; + case 1: // 1->6 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + pan[4] += pani[4]; + pan[5] += pani[5]; + float s = aScratch[j]; + aBuffer[j + 0] += s * pan[0]; + aBuffer[j + aBufferSize] += s * pan[1]; + aBuffer[j + aBufferSize * 2] += s * pan[2]; + aBuffer[j + aBufferSize * 3] += s * pan[3]; + aBuffer[j + aBufferSize * 4] += s * pan[4]; + aBuffer[j + aBufferSize * 5] += s * pan[5]; + } + break; + } + break; + case 8: + switch (aVoice->mChannels) + { + case 8: // 8->8 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + pan[4] += pani[4]; + pan[5] += pani[5]; + pan[6] += pani[6]; + pan[7] += pani[7]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + float s3 = aScratch[aBufferSize * 2 + j]; + float s4 = aScratch[aBufferSize * 3 + j]; + float s5 = aScratch[aBufferSize * 4 + j]; + float s6 = aScratch[aBufferSize * 5 + j]; + float s7 = aScratch[aBufferSize * 6 + j]; + float s8 = aScratch[aBufferSize * 7 + j]; + aBuffer[j + 0] += s1 * pan[0]; + aBuffer[j + aBufferSize] += s2 * pan[1]; + aBuffer[j + aBufferSize * 2] += s3 * pan[2]; + aBuffer[j + aBufferSize * 3] += s4 * pan[3]; + aBuffer[j + aBufferSize * 4] += s5 * pan[4]; + aBuffer[j + aBufferSize * 5] += s6 * pan[5]; + aBuffer[j + aBufferSize * 6] += s7 * pan[6]; + aBuffer[j + aBufferSize * 7] += s8 * pan[7]; + } + break; + case 6: // 6->8 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + pan[4] += pani[4]; + pan[5] += pani[5]; + pan[6] += pani[6]; + pan[7] += pani[7]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + float s3 = aScratch[aBufferSize * 2 + j]; + float s4 = aScratch[aBufferSize * 3 + j]; + float s5 = aScratch[aBufferSize * 4 + j]; + float s6 = aScratch[aBufferSize * 5 + j]; + aBuffer[j + 0] += s1 * pan[0]; + aBuffer[j + aBufferSize] += s2 * pan[1]; + aBuffer[j + aBufferSize * 2] += s3 * pan[2]; + aBuffer[j + aBufferSize * 3] += s4 * pan[3]; + aBuffer[j + aBufferSize * 4] += 0.5f * (s5 + s1) * pan[4]; + aBuffer[j + aBufferSize * 5] += 0.5f * (s6 + s2) * pan[5]; + aBuffer[j + aBufferSize * 6] += s5 * pan[6]; + aBuffer[j + aBufferSize * 7] += s6 * pan[7]; + } + break; + case 4: // 4->8 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + pan[4] += pani[4]; + pan[5] += pani[5]; + pan[6] += pani[6]; + pan[7] += pani[7]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + float s3 = aScratch[aBufferSize * 2 + j]; + float s4 = aScratch[aBufferSize * 3 + j]; + aBuffer[j + 0] += s1 * pan[0]; + aBuffer[j + aBufferSize] += s2 * pan[1]; + aBuffer[j + aBufferSize * 2] += 0.5f * (s1 + s2) * pan[2]; + aBuffer[j + aBufferSize * 3] += 0.25f * (s1 + s2 + s3 + s4) * pan[3]; + aBuffer[j + aBufferSize * 4] += 0.5f * (s1 + s3) * pan[4]; + aBuffer[j + aBufferSize * 5] += 0.5f * (s2 + s4) * pan[5]; + aBuffer[j + aBufferSize * 6] += s3 * pan[4]; + aBuffer[j + aBufferSize * 7] += s4 * pan[5]; + } + break; + case 2: // 2->8 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + pan[4] += pani[4]; + pan[5] += pani[5]; + pan[6] += pani[6]; + pan[7] += pani[7]; + float s1 = aScratch[j]; + float s2 = aScratch[aBufferSize + j]; + aBuffer[j + 0] += s1 * pan[0]; + aBuffer[j + aBufferSize] += s2 * pan[1]; + aBuffer[j + aBufferSize * 2] += 0.5f * (s1 + s2) * pan[2]; + aBuffer[j + aBufferSize * 3] += 0.5f * (s1 + s2) * pan[3]; + aBuffer[j + aBufferSize * 4] += s1 * pan[4]; + aBuffer[j + aBufferSize * 5] += s2 * pan[5]; + aBuffer[j + aBufferSize * 6] += s1 * pan[6]; + aBuffer[j + aBufferSize * 7] += s2 * pan[7]; + } + break; + case 1: // 1->8 + for (j = 0; j < aSamplesToRead; j++) + { + pan[0] += pani[0]; + pan[1] += pani[1]; + pan[2] += pani[2]; + pan[3] += pani[3]; + pan[4] += pani[4]; + pan[5] += pani[5]; + pan[6] += pani[6]; + pan[7] += pani[7]; + float s = aScratch[j]; + aBuffer[j + 0] += s * pan[0]; + aBuffer[j + aBufferSize] += s * pan[1]; + aBuffer[j + aBufferSize * 2] += s * pan[2]; + aBuffer[j + aBufferSize * 3] += s * pan[3]; + aBuffer[j + aBufferSize * 4] += s * pan[4]; + aBuffer[j + aBufferSize * 5] += s * pan[5]; + aBuffer[j + aBufferSize * 6] += s * pan[6]; + aBuffer[j + aBufferSize * 7] += s * pan[7]; + } + break; + } + break; + } + + for (k = 0; k < aChannels; k++) + aVoice->mCurrentChannelVolume[k] = pand[k]; + } + + void Soloud::mixBus(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize, float *aScratch, unsigned int aBus, float aSamplerate, unsigned int aChannels) + { + unsigned int i, j; + // Clear accumulation buffer + for (i = 0; i < aSamplesToRead; i++) + { + for (j = 0; j < aChannels; j++) + { + aBuffer[i + j * aBufferSize] = 0; + } + } + + // Accumulate sound sources + for (i = 0; i < mActiveVoiceCount; i++) + { + AudioSourceInstance *voice = mVoice[mActiveVoice[i]]; + if (voice && + voice->mBusHandle == aBus && + !(voice->mFlags & AudioSourceInstance::PAUSED) && + !(voice->mFlags & AudioSourceInstance::INAUDIBLE)) + { + unsigned int j; + float step = voice->mSamplerate / aSamplerate; + // avoid step overflow + if (step > (1 << (32 - FIXPOINT_FRAC_BITS))) + step = 0; + unsigned int step_fixed = (int)floor(step * FIXPOINT_FRAC_MUL); + unsigned int outofs = 0; + + if (voice->mDelaySamples) + { + if (voice->mDelaySamples > aSamplesToRead) + { + outofs = aSamplesToRead; + voice->mDelaySamples -= aSamplesToRead; + } + else + { + outofs = voice->mDelaySamples; + voice->mDelaySamples = 0; + } + + // Clear scratch where we're skipping + for (j = 0; j < voice->mChannels; j++) + { + memset(aScratch + j * aBufferSize, 0, sizeof(float) * outofs); + } + } + + while (step_fixed != 0 && outofs < aSamplesToRead) + { + if (voice->mLeftoverSamples == 0) + { + // Swap resample buffers (ping-pong) + AlignedFloatBuffer * t = voice->mResampleData[0]; + voice->mResampleData[0] = voice->mResampleData[1]; + voice->mResampleData[1] = t; + + // Get a block of source data + + int readcount = 0; + if (!voice->hasEnded() || voice->mFlags & AudioSourceInstance::LOOPING) + { + readcount = voice->getAudio(voice->mResampleData[0]->mData, SAMPLE_GRANULARITY, SAMPLE_GRANULARITY); + if (readcount < SAMPLE_GRANULARITY) + { + if (voice->mFlags & AudioSourceInstance::LOOPING) + { + while (readcount < SAMPLE_GRANULARITY && voice->seek(voice->mLoopPoint, mScratch.mData, mScratchSize) == SO_NO_ERROR) + { + voice->mLoopCount++; + int inc = voice->getAudio(voice->mResampleData[0]->mData + readcount, SAMPLE_GRANULARITY - readcount, SAMPLE_GRANULARITY); + readcount += inc; + if (inc == 0) break; + } + } + } + } + + // Clear remaining of the resample data if the full scratch wasn't used + if (readcount < SAMPLE_GRANULARITY) + { + unsigned int i; + for (i = 0; i < voice->mChannels; i++) + memset(voice->mResampleData[0]->mData + readcount + SAMPLE_GRANULARITY * i, 0, sizeof(float) * (SAMPLE_GRANULARITY - readcount)); + } + + // If we go past zero, crop to zero (a bit of a kludge) + if (voice->mSrcOffset < SAMPLE_GRANULARITY * FIXPOINT_FRAC_MUL) + { + voice->mSrcOffset = 0; + } + else + { + // We have new block of data, move pointer backwards + voice->mSrcOffset -= SAMPLE_GRANULARITY * FIXPOINT_FRAC_MUL; + } + + + // Run the per-stream filters to get our source data + + for (j = 0; j < FILTERS_PER_STREAM; j++) + { + if (voice->mFilter[j]) + { + voice->mFilter[j]->filter( + voice->mResampleData[0]->mData, + SAMPLE_GRANULARITY, + voice->mChannels, + voice->mSamplerate, + mStreamTime); + } + } + } + else + { + voice->mLeftoverSamples = 0; + } + + // Figure out how many samples we can generate from this source data. + // The value may be zero. + + unsigned int writesamples = 0; + + if (voice->mSrcOffset < SAMPLE_GRANULARITY * FIXPOINT_FRAC_MUL) + { + writesamples = ((SAMPLE_GRANULARITY * FIXPOINT_FRAC_MUL) - voice->mSrcOffset) / step_fixed + 1; + + // avoid reading past the current buffer.. + if (((writesamples * step_fixed + voice->mSrcOffset) >> FIXPOINT_FRAC_BITS) >= SAMPLE_GRANULARITY) + writesamples--; + } + + + // If this is too much for our output buffer, don't write that many: + if (writesamples + outofs > aSamplesToRead) + { + voice->mLeftoverSamples = (writesamples + outofs) - aSamplesToRead; + writesamples = aSamplesToRead - outofs; + } + + // Call resampler to generate the samples, once per channel + if (writesamples) + { + for (j = 0; j < voice->mChannels; j++) + { + resample(voice->mResampleData[0]->mData + SAMPLE_GRANULARITY * j, + voice->mResampleData[1]->mData + SAMPLE_GRANULARITY * j, + aScratch + aBufferSize * j + outofs, + voice->mSrcOffset, + writesamples, + voice->mSamplerate, + aSamplerate, + step_fixed); + } + } + + // Keep track of how many samples we've written so far + outofs += writesamples; + + // Move source pointer onwards (writesamples may be zero) + voice->mSrcOffset += writesamples * step_fixed; + } + + // Handle panning and channel expansion (and/or shrinking) + panAndExpand(voice, aBuffer, aSamplesToRead, aBufferSize, aScratch, aChannels); + + // clear voice if the sound is over + if (!(voice->mFlags & AudioSourceInstance::LOOPING) && voice->hasEnded()) + { + stopVoice(mActiveVoice[i]); + } + } + else + if (voice && + voice->mBusHandle == aBus && + !(voice->mFlags & AudioSourceInstance::PAUSED) && + (voice->mFlags & AudioSourceInstance::INAUDIBLE) && + (voice->mFlags & AudioSourceInstance::INAUDIBLE_TICK)) + { + // Inaudible but needs ticking. Do minimal work (keep counters up to date and ask audiosource for data) + float step = voice->mSamplerate / aSamplerate; + int step_fixed = (int)floor(step * FIXPOINT_FRAC_MUL); + unsigned int outofs = 0; + + if (voice->mDelaySamples) + { + if (voice->mDelaySamples > aSamplesToRead) + { + outofs = aSamplesToRead; + voice->mDelaySamples -= aSamplesToRead; + } + else + { + outofs = voice->mDelaySamples; + voice->mDelaySamples = 0; + } + } + + while (step_fixed != 0 && outofs < aSamplesToRead) + { + if (voice->mLeftoverSamples == 0) + { + // Swap resample buffers (ping-pong) + AlignedFloatBuffer * t = voice->mResampleData[0]; + voice->mResampleData[0] = voice->mResampleData[1]; + voice->mResampleData[1] = t; + + // Get a block of source data + + int readcount = 0; + if (!voice->hasEnded() || voice->mFlags & AudioSourceInstance::LOOPING) + { + readcount = voice->getAudio(voice->mResampleData[0]->mData, SAMPLE_GRANULARITY, SAMPLE_GRANULARITY); + if (readcount < SAMPLE_GRANULARITY) + { + if (voice->mFlags & AudioSourceInstance::LOOPING) + { + while (readcount < SAMPLE_GRANULARITY && voice->seek(voice->mLoopPoint, mScratch.mData, mScratchSize) == SO_NO_ERROR) + { + voice->mLoopCount++; + readcount += voice->getAudio(voice->mResampleData[0]->mData + readcount, SAMPLE_GRANULARITY - readcount, SAMPLE_GRANULARITY); + } + } + } + } + + // If we go past zero, crop to zero (a bit of a kludge) + if (voice->mSrcOffset < SAMPLE_GRANULARITY * FIXPOINT_FRAC_MUL) + { + voice->mSrcOffset = 0; + } + else + { + // We have new block of data, move pointer backwards + voice->mSrcOffset -= SAMPLE_GRANULARITY * FIXPOINT_FRAC_MUL; + } + + // Skip filters + } + else + { + voice->mLeftoverSamples = 0; + } + + // Figure out how many samples we can generate from this source data. + // The value may be zero. + + unsigned int writesamples = 0; + + if (voice->mSrcOffset < SAMPLE_GRANULARITY * FIXPOINT_FRAC_MUL) + { + writesamples = ((SAMPLE_GRANULARITY * FIXPOINT_FRAC_MUL) - voice->mSrcOffset) / step_fixed + 1; + + // avoid reading past the current buffer.. + if (((writesamples * step_fixed + voice->mSrcOffset) >> FIXPOINT_FRAC_BITS) >= SAMPLE_GRANULARITY) + writesamples--; + } + + + // If this is too much for our output buffer, don't write that many: + if (writesamples + outofs > aSamplesToRead) + { + voice->mLeftoverSamples = (writesamples + outofs) - aSamplesToRead; + writesamples = aSamplesToRead - outofs; + } + + // Skip resampler + + // Keep track of how many samples we've written so far + outofs += writesamples; + + // Move source pointer onwards (writesamples may be zero) + voice->mSrcOffset += writesamples * step_fixed; + } + + // clear voice if the sound is over + if (!(voice->mFlags & AudioSourceInstance::LOOPING) && voice->hasEnded()) + { + stopVoice(mActiveVoice[i]); + } + } + } + } + + void Soloud::mapResampleBuffers() + { + SOLOUD_ASSERT(mMaxActiveVoices < 256); + char live[256]; + memset(live, 0, mMaxActiveVoices); + unsigned int i, j; + for (i = 0; i < mMaxActiveVoices; i++) + { + for (j = 0; j < mMaxActiveVoices; j++) + { + if (mResampleDataOwner[i] && mResampleDataOwner[i] == mVoice[mActiveVoice[j]]) + { + live[i] |= 1; // Live channel + live[j] |= 2; // Live voice + } + } + } + + for (i = 0; i < mMaxActiveVoices; i++) + { + if (!(live[i] & 1) && mResampleDataOwner[i]) // For all dead channels with owners.. + { + mResampleDataOwner[i]->mResampleData[0] = 0; + mResampleDataOwner[i]->mResampleData[1] = 0; + mResampleDataOwner[i] = 0; + } + } + + int latestfree = 0; + for (i = 0; i < mActiveVoiceCount; i++) + { + if (!(live[i] & 2) && mVoice[mActiveVoice[i]]) // For all live voices with no channel.. + { + int found = -1; + for (j = latestfree; found == -1 && j < mMaxActiveVoices; j++) + { + if (mResampleDataOwner[j] == 0) + { + found = j; + } + } + SOLOUD_ASSERT(found != -1); + mResampleDataOwner[found] = mVoice[mActiveVoice[i]]; + mResampleDataOwner[found]->mResampleData[0] = &mResampleData[found * 2 + 0]; + mResampleDataOwner[found]->mResampleData[1] = &mResampleData[found * 2 + 1]; + mResampleDataOwner[found]->mResampleData[0]->clear(); + mResampleDataOwner[found]->mResampleData[1]->clear(); + latestfree = found + 1; + } + } + } + + void Soloud::calcActiveVoices() + { + // TODO: consider whether we need to re-evaluate the active voices all the time. + // It is a must when new voices are started, but otherwise we could get away + // with postponing it sometimes.. + + mActiveVoiceDirty = false; + + // Populate + unsigned int i, candidates, mustlive; + candidates = 0; + mustlive = 0; + for (i = 0; i < mHighestVoice; i++) + { + if (mVoice[i] && (!(mVoice[i]->mFlags & (AudioSourceInstance::INAUDIBLE | AudioSourceInstance::PAUSED)) || (mVoice[i]->mFlags & AudioSourceInstance::INAUDIBLE_TICK))) + { + mActiveVoice[candidates] = i; + candidates++; + if (mVoice[i]->mFlags & AudioSourceInstance::INAUDIBLE_TICK) + { + mActiveVoice[candidates - 1] = mActiveVoice[mustlive]; + mActiveVoice[mustlive] = i; + mustlive++; + } + } + } + + // Check for early out + if (candidates <= mMaxActiveVoices) + { + // everything is audible, early out + mActiveVoiceCount = candidates; + mapResampleBuffers(); + return; + } + + mActiveVoiceCount = mMaxActiveVoices; + + if (mustlive >= mMaxActiveVoices) + { + // Oopsie. Well, nothing to sort, since the "must live" voices already + // ate all our active voice slots. + // This is a potentially an error situation, but we have no way to report + // error from here. And asserting could be bad, too. + return; + } + + // If we get this far, there's nothing to it: we'll have to sort the voices to find the most audible. + + // Iterative partial quicksort: + int left = 0, stack[24], pos = 0, right; + int len = candidates - mustlive; + unsigned int *data = mActiveVoice + mustlive; + int k = mActiveVoiceCount; + for (;;) + { + for (; left + 1 < len; len++) + { + if (pos == 24) len = stack[pos = 0]; + int pivot = data[left]; + float pivotvol = mVoice[pivot]->mOverallVolume; + stack[pos++] = len; + for (right = left - 1;;) + { + do + { + right++; + } + while (mVoice[data[right]]->mOverallVolume > pivotvol); + do + { + len--; + } + while (pivotvol > mVoice[data[len]]->mOverallVolume); + if (right >= len) break; + int temp = data[right]; + data[right] = data[len]; + data[len] = temp; + } + } + if (pos == 0) break; + if (left >= k) break; + left = len; + len = stack[--pos]; + } + // TODO: should the rest of the voices be flagged INAUDIBLE? + mapResampleBuffers(); + } + + void Soloud::mix_internal(unsigned int aSamples) + { +#ifdef FLOATING_POINT_DEBUG + // This needs to be done in the audio thread as well.. + static int done = 0; + if (!done) + { + unsigned int u; + u = _controlfp(0, 0); + u = u & ~(_EM_INVALID | /*_EM_DENORMAL |*/ _EM_ZERODIVIDE | _EM_OVERFLOW /*| _EM_UNDERFLOW | _EM_INEXACT*/); + _controlfp(u, _MCW_EM); + done = 1; + } +#endif + + float buffertime = aSamples / (float)mSamplerate; + float globalVolume[2]; + mStreamTime += buffertime; + mLastClockedTime = 0; + + globalVolume[0] = mGlobalVolume; + if (mGlobalVolumeFader.mActive) + { + mGlobalVolume = mGlobalVolumeFader.get(mStreamTime); + } + globalVolume[1] = mGlobalVolume; + + lockAudioMutex(); + + // Process faders. May change scratch size. + int i; + for (i = 0; i < (signed)mHighestVoice; i++) + { + if (mVoice[i] && !(mVoice[i]->mFlags & AudioSourceInstance::PAUSED)) + { + float volume[2]; + + mVoice[i]->mActiveFader = 0; + + if (mGlobalVolumeFader.mActive > 0) + { + mVoice[i]->mActiveFader = 1; + } + + mVoice[i]->mStreamTime += buffertime; + mVoice[i]->mStreamPosition += buffertime; + + // TODO: this is actually unstable, because mStreamTime depends on the relative + // play speed. + if (mVoice[i]->mRelativePlaySpeedFader.mActive > 0) + { + float speed = mVoice[i]->mRelativePlaySpeedFader.get(mVoice[i]->mStreamTime); + setVoiceRelativePlaySpeed(i, speed); + } + + volume[0] = mVoice[i]->mOverallVolume; + if (mVoice[i]->mVolumeFader.mActive > 0) + { + mVoice[i]->mSetVolume = mVoice[i]->mVolumeFader.get(mVoice[i]->mStreamTime); + mVoice[i]->mActiveFader = 1; + updateVoiceVolume(i); + mActiveVoiceDirty = true; + } + volume[1] = mVoice[i]->mOverallVolume; + + if (mVoice[i]->mPanFader.mActive > 0) + { + float pan = mVoice[i]->mPanFader.get(mVoice[i]->mStreamTime); + setVoicePan(i, pan); + mVoice[i]->mActiveFader = 1; + } + + if (mVoice[i]->mPauseScheduler.mActive) + { + mVoice[i]->mPauseScheduler.get(mVoice[i]->mStreamTime); + if (mVoice[i]->mPauseScheduler.mActive == -1) + { + mVoice[i]->mPauseScheduler.mActive = 0; + setVoicePause(i, 1); + } + } + + if (mVoice[i]->mStopScheduler.mActive) + { + mVoice[i]->mStopScheduler.get(mVoice[i]->mStreamTime); + if (mVoice[i]->mStopScheduler.mActive == -1) + { + mVoice[i]->mStopScheduler.mActive = 0; + stopVoice(i); + } + } + } + } + + if (mActiveVoiceDirty) + calcActiveVoices(); + + // Resize scratch if needed. + if (mScratchSize < mScratchNeeded) + { + mScratchSize = mScratchNeeded; + mScratch.init(mScratchSize * MAX_CHANNELS); + } + + mixBus(mOutputScratch.mData, aSamples, aSamples, mScratch.mData, 0, (float)mSamplerate, mChannels); + + for (i = 0; i < FILTERS_PER_STREAM; i++) + { + if (mFilterInstance[i]) + { + mFilterInstance[i]->filter(mOutputScratch.mData, aSamples, mChannels, (float)mSamplerate, mStreamTime); + } + } + + unlockAudioMutex(); + + clip(mOutputScratch, mScratch, aSamples, globalVolume[0], globalVolume[1]); + + if (mFlags & ENABLE_VISUALIZATION) + { + for (i = 0; i < MAX_CHANNELS; i++) + { + mVisualizationChannelVolume[i] = 0; + } + if (aSamples > 255) + { + for (i = 0; i < 256; i++) + { + int j; + mVisualizationWaveData[i] = 0; + for (j = 0; j < (signed)mChannels; j++) + { + float sample = mScratch.mData[i + j * aSamples]; + float absvol = (float)fabs(sample); + if (mVisualizationChannelVolume[j] < absvol) + mVisualizationChannelVolume[j] = absvol; + mVisualizationWaveData[i] += sample; + } + } + } + else + { + // Very unlikely failsafe branch + for (i = 0; i < 256; i++) + { + int j; + mVisualizationWaveData[i] = 0; + for (j = 0; j < (signed)mChannels; j++) + { + float sample = mScratch.mData[(i % aSamples) + j * aSamples]; + float absvol = (float)fabs(sample); + if (mVisualizationChannelVolume[j] < absvol) + mVisualizationChannelVolume[j] = absvol; + mVisualizationWaveData[i] += sample; + } + } + } + } + } + + void Soloud::mix(float *aBuffer, unsigned int aSamples) + { + mix_internal(aSamples); + interlace_samples_float(mScratch.mData, aBuffer, aSamples, mChannels); + } + + void Soloud::mixSigned16(short *aBuffer, unsigned int aSamples) + { + mix_internal(aSamples); + interlace_samples_s16(mScratch.mData, aBuffer, aSamples, mChannels); + } + + void deinterlace_samples_float(const float *aSourceBuffer, float *aDestBuffer, unsigned int aSamples, unsigned int aChannels) + { + // 121212 -> 111222 + unsigned int i, j, c; + c = 0; + for (j = 0; j < aChannels; j++) + { + for (i = j; i < aSamples; i += aChannels) + { + aDestBuffer[c] = aSourceBuffer[i + j]; + c++; + } + } + } + + void interlace_samples_float(const float *aSourceBuffer, float *aDestBuffer, unsigned int aSamples, unsigned int aChannels) + { + // 111222 -> 121212 + unsigned int i, j, c; + c = 0; + for (j = 0; j < aChannels; j++) + { + for (i = j; i < aSamples * aChannels; i += aChannels) + { + aDestBuffer[i] = aSourceBuffer[c]; + c++; + } + } + } + + void interlace_samples_s16(const float *aSourceBuffer, short *aDestBuffer, unsigned int aSamples, unsigned int aChannels) + { + // 111222 -> 121212 + unsigned int i, j, c; + c = 0; + for (j = 0; j < aChannels; j++) + { + for (i = j; i < aSamples * aChannels; i += aChannels) + { + aDestBuffer[i] = (short)(aSourceBuffer[c] * 0x7fff); + c++; + } + } + } + + void Soloud::lockAudioMutex() + { + if (mAudioThreadMutex) + { + Thread::lockMutex(mAudioThreadMutex); + } + SOLOUD_ASSERT(!mInsideAudioThreadMutex); + mInsideAudioThreadMutex = true; + } + + void Soloud::unlockAudioMutex() + { + SOLOUD_ASSERT(mInsideAudioThreadMutex); + mInsideAudioThreadMutex = false; + if (mAudioThreadMutex) + { + Thread::unlockMutex(mAudioThreadMutex); + } + } + +}; diff --git a/src/soloud/src/core/soloud_audiosource.cpp b/src/soloud/src/core/soloud_audiosource.cpp new file mode 100644 index 0000000..1e89260 --- /dev/null +++ b/src/soloud/src/core/soloud_audiosource.cpp @@ -0,0 +1,330 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" + +namespace SoLoud +{ + + AudioSourceInstance3dData::AudioSourceInstance3dData() + { + m3dAttenuationModel = 0; + m3dAttenuationRolloff = 1; + m3dDopplerFactor = 1.0; + m3dMaxDistance = 1000000.0f; + m3dMinDistance = 0.0f; + m3dPosition[0] = 0; + m3dPosition[1] = 0; + m3dPosition[2] = 0; + m3dVelocity[0] = 0; + m3dVelocity[1] = 0; + m3dVelocity[2] = 0; + mCollider = 0; + mColliderData = 0; + mAttenuator = 0; + } + + void AudioSourceInstance3dData::init(AudioSource &aSource) + { + m3dAttenuationModel = aSource.m3dAttenuationModel; + m3dAttenuationRolloff = aSource.m3dAttenuationRolloff; + m3dDopplerFactor = aSource.m3dDopplerFactor; + m3dMaxDistance = aSource.m3dMaxDistance; + m3dMinDistance = aSource.m3dMinDistance; + mCollider = aSource.mCollider; + mColliderData = aSource.mColliderData; + mAttenuator = aSource.mAttenuator; + m3dVolume = 1.0f; + mDopplerValue = 1.0f; + } + + AudioSourceInstance::AudioSourceInstance() + { + mPlayIndex = 0; + mFlags = 0; + mPan = 0; + // Default all volumes to 1.0 so sound behind N mix busses isn't super quiet. + int i; + for (i = 0; i < MAX_CHANNELS; i++) + mChannelVolume[i] = 1.0f; + mSetVolume = 1.0f; + mBaseSamplerate = 44100.0f; + mSamplerate = 44100.0f; + mSetRelativePlaySpeed = 1.0f; + mStreamTime = 0.0f; + mStreamPosition = 0.0f; + mAudioSourceID = 0; + mActiveFader = 0; + mChannels = 1; + mBusHandle = ~0u; + mLoopCount = 0; + mLoopPoint = 0; + for (i = 0; i < FILTERS_PER_STREAM; i++) + { + mFilter[i] = NULL; + } + for (i = 0; i < MAX_CHANNELS; i++) + { + mCurrentChannelVolume[i] = 0; + } + // behind pointers because we swap between the two buffers + mResampleData[0] = 0; + mResampleData[1] = 0; + mSrcOffset = 0; + mLeftoverSamples = 0; + mDelaySamples = 0; + + } + + AudioSourceInstance::~AudioSourceInstance() + { + int i; + for (i = 0; i < FILTERS_PER_STREAM; i++) + { + delete mFilter[i]; + } + } + + void AudioSourceInstance::init(AudioSource &aSource, int aPlayIndex) + { + mPlayIndex = aPlayIndex; + mBaseSamplerate = aSource.mBaseSamplerate; + mSamplerate = mBaseSamplerate; + mChannels = aSource.mChannels; + mStreamTime = 0.0f; + mStreamPosition = 0.0f; + mLoopPoint = aSource.mLoopPoint; + + if (aSource.mFlags & AudioSource::SHOULD_LOOP) + { + mFlags |= AudioSourceInstance::LOOPING; + } + if (aSource.mFlags & AudioSource::PROCESS_3D) + { + mFlags |= AudioSourceInstance::PROCESS_3D; + } + if (aSource.mFlags & AudioSource::LISTENER_RELATIVE) + { + mFlags |= AudioSourceInstance::LISTENER_RELATIVE; + } + if (aSource.mFlags & AudioSource::INAUDIBLE_KILL) + { + mFlags |= AudioSourceInstance::INAUDIBLE_KILL; + } + if (aSource.mFlags & AudioSource::INAUDIBLE_TICK) + { + mFlags |= AudioSourceInstance::INAUDIBLE_TICK; + } + } + + result AudioSourceInstance::rewind() + { + return NOT_IMPLEMENTED; + } + + result AudioSourceInstance::seek(double aSeconds, float *mScratch, unsigned int mScratchSize) + { + double offset = aSeconds - mStreamPosition; + if (offset <= 0) + { + if (rewind() != SO_NO_ERROR) + { + // can't do generic seek backwards unless we can rewind. + return NOT_IMPLEMENTED; + } + offset = aSeconds; + } + int samples_to_discard = (int)floor(mSamplerate * offset); + + while (samples_to_discard) + { + int samples = mScratchSize / mChannels; + if (samples > samples_to_discard) + samples = samples_to_discard; + getAudio(mScratch, samples, samples); + samples_to_discard -= samples; + } + mStreamPosition = offset; + return SO_NO_ERROR; + } + + + AudioSource::AudioSource() + { + int i; + for (i = 0; i < FILTERS_PER_STREAM; i++) + { + mFilter[i] = 0; + } + mFlags = 0; + mBaseSamplerate = 44100; + mAudioSourceID = 0; + mSoloud = 0; + mChannels = 1; + m3dMinDistance = 1; + m3dMaxDistance = 1000000.0f; + m3dAttenuationRolloff = 1.0f; + m3dAttenuationModel = NO_ATTENUATION; + m3dDopplerFactor = 1.0f; + mCollider = 0; + mAttenuator = 0; + mColliderData = 0; + mVolume = 1; + mLoopPoint = 0; + } + + AudioSource::~AudioSource() + { + stop(); + } + + void AudioSource::setVolume(float aVolume) + { + mVolume = aVolume; + } + + void AudioSource::setLoopPoint(time aLoopPoint) + { + mLoopPoint = aLoopPoint; + } + + time AudioSource::getLoopPoint() + { + return mLoopPoint; + } + + void AudioSource::setLooping(bool aLoop) + { + if (aLoop) + { + mFlags |= SHOULD_LOOP; + } + else + { + mFlags &= ~SHOULD_LOOP; + } + } + + void AudioSource::setSingleInstance(bool aSingleInstance) + { + if (aSingleInstance) + { + mFlags |= SINGLE_INSTANCE; + } + else + { + mFlags &= ~SINGLE_INSTANCE; + } + } + + void AudioSource::setFilter(unsigned int aFilterId, Filter *aFilter) + { + if (aFilterId >= FILTERS_PER_STREAM) + return; + mFilter[aFilterId] = aFilter; + } + + void AudioSource::stop() + { + if (mSoloud) + { + mSoloud->stopAudioSource(*this); + } + } + + void AudioSource::set3dMinMaxDistance(float aMinDistance, float aMaxDistance) + { + m3dMinDistance = aMinDistance; + m3dMaxDistance = aMaxDistance; + } + + void AudioSource::set3dAttenuation(unsigned int aAttenuationModel, float aAttenuationRolloffFactor) + { + m3dAttenuationModel = aAttenuationModel; + m3dAttenuationRolloff = aAttenuationRolloffFactor; + } + + void AudioSource::set3dDopplerFactor(float aDopplerFactor) + { + m3dDopplerFactor = aDopplerFactor; + } + + void AudioSource::set3dListenerRelative(bool aListenerRelative) + { + if (aListenerRelative) + { + mFlags |= LISTENER_RELATIVE; + } + else + { + mFlags &= ~LISTENER_RELATIVE; + } + } + + + void AudioSource::set3dDistanceDelay(bool aDistanceDelay) + { + if (aDistanceDelay) + { + mFlags |= DISTANCE_DELAY; + } + else + { + mFlags &= ~DISTANCE_DELAY; + } + } + + void AudioSource::set3dCollider(AudioCollider *aCollider, int aUserData) + { + mCollider = aCollider; + mColliderData = aUserData; + } + + void AudioSource::set3dAttenuator(AudioAttenuator *aAttenuator) + { + mAttenuator = aAttenuator; + } + + void AudioSource::setInaudibleBehavior(bool aMustTick, bool aKill) + { + mFlags &= ~(AudioSource::INAUDIBLE_KILL | AudioSource::INAUDIBLE_TICK); + if (aMustTick) + { + mFlags |= AudioSource::INAUDIBLE_TICK; + } + if (aKill) + { + mFlags |= AudioSource::INAUDIBLE_KILL; + } + } + + + float AudioSourceInstance::getInfo(unsigned int aInfoKey) + { + return 0; + } + + +}; + diff --git a/src/soloud/src/core/soloud_bus.cpp b/src/soloud/src/core/soloud_bus.cpp new file mode 100644 index 0000000..f1793a2 --- /dev/null +++ b/src/soloud/src/core/soloud_bus.cpp @@ -0,0 +1,318 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" +#include "soloud_fft.h" + +namespace SoLoud +{ + BusInstance::BusInstance(Bus *aParent) + { + mParent = aParent; + mScratchSize = 0; + mFlags |= PROTECTED | INAUDIBLE_TICK; + } + + unsigned int BusInstance::getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize) + { + int handle = mParent->mChannelHandle; + if (handle == 0) + { + // Avoid reuse of scratch data if this bus hasn't played anything yet + unsigned int i; + for (i = 0; i < aBufferSize * mChannels; i++) + aBuffer[i] = 0; + return aSamplesToRead; + } + + Soloud *s = mParent->mSoloud; + if (s->mScratchNeeded != mScratchSize) + { + mScratchSize = s->mScratchNeeded; + mScratch.init(mScratchSize * MAX_CHANNELS); + } + + s->mixBus(aBuffer, aSamplesToRead, aBufferSize, mScratch.mData, handle, mSamplerate, mChannels); + + int i; + if (mParent->mFlags & AudioSource::VISUALIZATION_DATA) + { + for (i = 0; i < MAX_CHANNELS; i++) + mVisualizationChannelVolume[i] = 0; + + if (aSamplesToRead > 255) + { + for (i = 0; i < 256; i++) + { + int j; + mVisualizationWaveData[i] = 0; + for (j = 0; j < (signed)mChannels; j++) + { + float sample = aBuffer[i + aBufferSize * j]; + float absvol = (float)fabs(sample); + if (absvol > mVisualizationChannelVolume[j]) + mVisualizationChannelVolume[j] = absvol; + mVisualizationWaveData[i] += sample; + } + } + } + else + { + // Very unlikely failsafe branch + for (i = 0; i < 256; i++) + { + int j; + mVisualizationWaveData[i] = 0; + for (j = 0; j < (signed)mChannels; j++) + { + float sample = aBuffer[(i % aSamplesToRead) + aBufferSize * j]; + float absvol = (float)fabs(sample); + if (absvol > mVisualizationChannelVolume[j]) + mVisualizationChannelVolume[j] = absvol; + mVisualizationWaveData[i] += sample; + } + } + } + } + return aSamplesToRead; + } + + bool BusInstance::hasEnded() + { + // Busses never stop for fear of going under 50mph. + return 0; + } + + BusInstance::~BusInstance() + { + Soloud *s = mParent->mSoloud; + int i; + for (i = 0; i < (signed)s->mHighestVoice; i++) + { + if (s->mVoice[i] && s->mVoice[i]->mBusHandle == mParent->mChannelHandle) + { + s->stopVoice(i); + } + } + } + + Bus::Bus() + { + mChannelHandle = 0; + mInstance = 0; + mChannels = 2; + } + + BusInstance * Bus::createInstance() + { + if (mChannelHandle) + { + stop(); + mChannelHandle = 0; + mInstance = 0; + } + mInstance = new BusInstance(this); + return mInstance; + } + + void Bus::findBusHandle() + { + if (mChannelHandle == 0) + { + // Find the channel the bus is playing on to calculate handle.. + int i; + for (i = 0; mChannelHandle == 0 && i < (signed)mSoloud->mHighestVoice; i++) + { + if (mSoloud->mVoice[i] == mInstance) + { + mChannelHandle = mSoloud->getHandleFromVoice(i); + } + } + } + } + + handle Bus::play(AudioSource &aSound, float aVolume, float aPan, bool aPaused) + { + if (!mInstance || !mSoloud) + { + return 0; + } + + findBusHandle(); + + if (mChannelHandle == 0) + { + return 0; + } + return mSoloud->play(aSound, aVolume, aPan, aPaused, mChannelHandle); + } + + + handle Bus::playClocked(time aSoundTime, AudioSource &aSound, float aVolume, float aPan) + { + if (!mInstance || !mSoloud) + { + return 0; + } + + findBusHandle(); + + if (mChannelHandle == 0) + { + return 0; + } + + return mSoloud->playClocked(aSoundTime, aSound, aVolume, aPan, mChannelHandle); + } + + handle Bus::play3d(AudioSource &aSound, float aPosX, float aPosY, float aPosZ, float aVelX, float aVelY, float aVelZ, float aVolume, bool aPaused) + { + if (!mInstance || !mSoloud) + { + return 0; + } + + findBusHandle(); + + if (mChannelHandle == 0) + { + return 0; + } + return mSoloud->play3d(aSound, aPosX, aPosY, aPosZ, aVelX, aVelY, aVelZ, aVolume, aPaused, mChannelHandle); + } + + handle Bus::play3dClocked(time aSoundTime, AudioSource &aSound, float aPosX, float aPosY, float aPosZ, float aVelX, float aVelY, float aVelZ, float aVolume) + { + if (!mInstance || !mSoloud) + { + return 0; + } + + findBusHandle(); + + if (mChannelHandle == 0) + { + return 0; + } + return mSoloud->play3dClocked(aSoundTime, aSound, aPosX, aPosY, aPosZ, aVelX, aVelY, aVelZ, aVolume, mChannelHandle); + } + + + void Bus::setFilter(unsigned int aFilterId, Filter *aFilter) + { + if (aFilterId >= FILTERS_PER_STREAM) + return; + + mFilter[aFilterId] = aFilter; + + if (mInstance) + { + mSoloud->lockAudioMutex(); + delete mInstance->mFilter[aFilterId]; + mInstance->mFilter[aFilterId] = 0; + + if (aFilter) + { + mInstance->mFilter[aFilterId] = mFilter[aFilterId]->createInstance(); + } + mSoloud->unlockAudioMutex(); + } + } + + result Bus::setChannels(unsigned int aChannels) + { + if (aChannels == 0 || aChannels == 3 || aChannels == 5 || aChannels > 7 || aChannels > MAX_CHANNELS) + return INVALID_PARAMETER; + mChannels = aChannels; + return SO_NO_ERROR; + } + + void Bus::setVisualizationEnable(bool aEnable) + { + if (aEnable) + { + mFlags |= AudioSource::VISUALIZATION_DATA; + } + else + { + mFlags &= ~AudioSource::VISUALIZATION_DATA; + } + } + + float * Bus::calcFFT() + { + if (mInstance && mSoloud) + { + mSoloud->lockAudioMutex(); + float temp[1024]; + int i; + for (i = 0; i < 256; i++) + { + temp[i*2] = mInstance->mVisualizationWaveData[i]; + temp[i*2+1] = 0; + temp[i+512] = 0; + temp[i+768] = 0; + } + mSoloud->unlockAudioMutex(); + + SoLoud::FFT::fft1024(temp); + + for (i = 0; i < 256; i++) + { + float real = temp[i * 2]; + float imag = temp[i * 2 + 1]; + mFFTData[i] = (float)sqrt(real*real+imag*imag); + } + } + + return mFFTData; + } + + float * Bus::getWave() + { + if (mInstance && mSoloud) + { + int i; + mSoloud->lockAudioMutex(); + for (i = 0; i < 256; i++) + mWaveData[i] = mInstance->mVisualizationWaveData[i]; + mSoloud->unlockAudioMutex(); + } + return mWaveData; + } + + float Bus::getApproximateVolume(unsigned int aChannel) + { + if (aChannel > mChannels) + return 0; + float vol = 0; + if (mInstance && mSoloud) + { + mSoloud->lockAudioMutex(); + vol = mInstance->mVisualizationChannelVolume[aChannel]; + mSoloud->unlockAudioMutex(); + } + return vol; + } +}; diff --git a/src/soloud/src/core/soloud_core_3d.cpp b/src/soloud/src/core/soloud_core_3d.cpp new file mode 100644 index 0000000..7d2e435 --- /dev/null +++ b/src/soloud/src/core/soloud_core_3d.cpp @@ -0,0 +1,631 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include "soloud_internal.h" + +// 3d audio operations + +namespace SoLoud +{ + struct vec3 + { + float mX, mY, mZ; + + bool null() + { + if (mX == 0 && mY == 0 && mZ == 0) + return true; + return false; + } + + void neg() + { + mX = -mX; + mY = -mY; + mZ = -mZ; + } + + float mag() + { + return (float)sqrt(mX * mX + mY * mY + mZ * mZ); + } + + void normalize() + { + float m = mag(); + if (m == 0) + { + mX = mY = mZ = 0; + return; + } + mX /= m; + mY /= m; + mZ /= m; + } + + float dot(vec3 a) + { + return mX * a.mX + mY * a.mY + mZ * a.mZ; + } + + vec3 sub(vec3 a) + { + vec3 r; + r.mX = mX - a.mX; + r.mY = mY - a.mY; + r.mZ = mZ - a.mZ; + return r; + } + + vec3 cross(vec3 a) + { + vec3 r; + + r.mX = mY * a.mZ - a.mY * mZ; + r.mY = mZ * a.mX - a.mZ * mX; + r.mZ = mX * a.mY - a.mX * mY; + + return r; + } + }; + + struct mat3 + { + vec3 m[3]; + + vec3 mul(vec3 a) + { + vec3 r; + + r.mX = m[0].mX * a.mX + m[0].mY * a.mY + m[0].mZ * a.mZ; + r.mY = m[1].mX * a.mX + m[1].mY * a.mY + m[1].mZ * a.mZ; + r.mZ = m[2].mX * a.mX + m[2].mY * a.mY + m[2].mZ * a.mZ; + + return r; + } + + void lookatRH(vec3 at, vec3 up) + { + vec3 z = at; + z.normalize(); + vec3 x = up.cross(z); + x.normalize(); + vec3 y = z.cross(x); + m[0] = x; + m[1] = y; + m[2] = z; + } + + void lookatLH(vec3 at, vec3 up) + { + vec3 z = at; + z.normalize(); + vec3 x = up.cross(z); + x.normalize(); + vec3 y = z.cross(x); + x.neg(); // flip x + m[0] = x; + m[1] = y; + m[2] = z; + } + }; + +#ifndef MIN +#define MIN(a,b) ((a) < (b)) ? (a) : (b) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b)) ? (a) : (b) +#endif + + float doppler(vec3 aDeltaPos, vec3 aSrcVel, vec3 aDstVel, float aFactor, float aSoundSpeed) + { + float deltamag = aDeltaPos.mag(); + if (deltamag == 0) + return 1.0f; + float vls = aDeltaPos.dot(aDstVel) / deltamag; + float vss = aDeltaPos.dot(aSrcVel) / deltamag; + float maxspeed = aSoundSpeed / aFactor; + vss = MIN(vss, maxspeed); + vls = MIN(vls, maxspeed); + return (aSoundSpeed - aFactor * vls) / (aSoundSpeed - aFactor * vss); + } + + float attenuateInvDistance(float aDistance, float aMinDistance, float aMaxDistance, float aRolloffFactor) + { + float distance = MAX(aDistance, aMinDistance); + distance = MIN(distance, aMaxDistance); + return aMinDistance / (aMinDistance + aRolloffFactor * (distance - aMinDistance)); + } + + float attenuateLinearDistance(float aDistance, float aMinDistance, float aMaxDistance, float aRolloffFactor) + { + float distance = MAX(aDistance, aMinDistance); + distance = MIN(distance, aMaxDistance); + return 1 - aRolloffFactor * (distance - aMinDistance) / (aMaxDistance - aMinDistance); + } + + float attenuateExponentialDistance(float aDistance, float aMinDistance, float aMaxDistance, float aRolloffFactor) + { + float distance = MAX(aDistance, aMinDistance); + distance = MIN(distance, aMaxDistance); + return (float)pow(distance / aMinDistance, -aRolloffFactor); + } + + void Soloud::update3dVoices(unsigned int *aVoiceArray, unsigned int aVoiceCount) + { + vec3 speaker[MAX_CHANNELS]; + + int i; + for (i = 0; i < (signed)mChannels; i++) + { + speaker[i].mX = m3dSpeakerPosition[3 * i + 0]; + speaker[i].mY = m3dSpeakerPosition[3 * i + 1]; + speaker[i].mZ = m3dSpeakerPosition[3 * i + 2]; + speaker[i].normalize(); + } + for (; i < MAX_CHANNELS; i++) + { + speaker[i].mX = 0; + speaker[i].mY = 0; + speaker[i].mZ = 0; + } + + vec3 lpos, lvel, at, up; + at.mX = m3dAt[0]; + at.mY = m3dAt[1]; + at.mZ = m3dAt[2]; + up.mX = m3dUp[0]; + up.mY = m3dUp[1]; + up.mZ = m3dUp[2]; + lpos.mX = m3dPosition[0]; + lpos.mY = m3dPosition[1]; + lpos.mZ = m3dPosition[2]; + lvel.mX = m3dVelocity[0]; + lvel.mY = m3dVelocity[1]; + lvel.mZ = m3dVelocity[2]; + mat3 m; + if (mFlags & LEFT_HANDED_3D) + { + m.lookatLH(at, up); + } + else + { + m.lookatRH(at, up); + } + + for (i = 0; i < (signed)aVoiceCount; i++) + { + AudioSourceInstance3dData * v = &m3dData[aVoiceArray[i]]; + + float vol = 1; + + // custom collider + if (v->mCollider) + { + vol *= v->mCollider->collide(this, v, v->mColliderData); + } + + vec3 pos, vel; + pos.mX = v->m3dPosition[0]; + pos.mY = v->m3dPosition[1]; + pos.mZ = v->m3dPosition[2]; + + vel.mX = v->m3dVelocity[0]; + vel.mY = v->m3dVelocity[1]; + vel.mZ = v->m3dVelocity[2]; + + if (!(v->mFlags & AudioSourceInstance::LISTENER_RELATIVE)) + { + pos = pos.sub(lpos); + } + + float dist = pos.mag(); + + // attenuation + + if (v->mAttenuator) + { + vol *= v->mAttenuator->attenuate(dist, v->m3dMinDistance, v->m3dMaxDistance, v->m3dAttenuationRolloff); + } + else + { + switch (v->m3dAttenuationModel) + { + case AudioSource::INVERSE_DISTANCE: + vol *= attenuateInvDistance(dist, v->m3dMinDistance, v->m3dMaxDistance, v->m3dAttenuationRolloff); + break; + case AudioSource::LINEAR_DISTANCE: + vol *= attenuateLinearDistance(dist, v->m3dMinDistance, v->m3dMaxDistance, v->m3dAttenuationRolloff); + break; + case AudioSource::EXPONENTIAL_DISTANCE: + vol *= attenuateExponentialDistance(dist, v->m3dMinDistance, v->m3dMaxDistance, v->m3dAttenuationRolloff); + break; + default: + //case AudioSource::NO_ATTENUATION: + break; + } + } + + // cone + + // (todo) vol *= conev; + + // doppler + v->mDopplerValue = doppler(pos, vel, lvel, v->m3dDopplerFactor, m3dSoundSpeed); + + // panning + pos = m.mul(pos); + pos.normalize(); + + // Apply volume to channels based on speaker vectors + int j; + for (j = 0; j < (signed)mChannels; j++) + { + float speakervol = (speaker[j].dot(pos) + 1) / 2; + if (speaker[j].null()) + speakervol = 1; + // Different speaker "focus" calculations to try, if the default "bleeds" too much.. + //speakervol = (speakervol * speakervol + speakervol) / 2; + //speakervol = speakervol * speakervol; + v->mChannelVolume[j] = vol * speakervol; + } + for (; j < MAX_CHANNELS; j++) + { + v->mChannelVolume[j] = 0; + } + + v->m3dVolume = vol; + } + } + + void Soloud::update3dAudio() + { + unsigned int voicecount = 0; + unsigned int voices[VOICE_COUNT]; + + // Step 1 - find voices that need 3d processing + lockAudioMutex(); + int i; + for (i = 0; i < (signed)mHighestVoice; i++) + { + if (mVoice[i] && mVoice[i]->mFlags & AudioSourceInstance::PROCESS_3D) + { + voices[voicecount] = i; + voicecount++; + m3dData[i].mFlags = mVoice[i]->mFlags; + } + } + unlockAudioMutex(); + + // Step 2 - do 3d processing + + update3dVoices(voices, voicecount); + + // Step 3 - update SoLoud voices + + lockAudioMutex(); + for (i = 0; i < (int)voicecount; i++) + { + AudioSourceInstance3dData * v = &m3dData[voices[i]]; + AudioSourceInstance * vi = mVoice[voices[i]]; + if (vi) + { + updateVoiceRelativePlaySpeed(voices[i]); + updateVoiceVolume(voices[i]); + int j; + for (j = 0; j < MAX_CHANNELS; j++) + { + vi->mChannelVolume[j] = v->mChannelVolume[j]; + } + + if (vi->mOverallVolume < 0.01f) + { + // Inaudible. + vi->mFlags |= AudioSourceInstance::INAUDIBLE; + + if (vi->mFlags & AudioSourceInstance::INAUDIBLE_KILL) + { + stopVoice(voices[i]); + } + } + else + { + vi->mFlags &= ~AudioSourceInstance::INAUDIBLE; + } + } + } + + mActiveVoiceDirty = true; + unlockAudioMutex(); + } + + + handle Soloud::play3d(AudioSource &aSound, float aPosX, float aPosY, float aPosZ, float aVelX, float aVelY, float aVelZ, float aVolume, bool aPaused, unsigned int aBus) + { + handle h = play(aSound, aVolume, 0, 1, aBus); + lockAudioMutex(); + int v = getVoiceFromHandle(h); + if (v < 0) + { + unlockAudioMutex(); + return h; + } + m3dData[v].mHandle = h; + mVoice[v]->mFlags |= AudioSourceInstance::PROCESS_3D; + set3dSourceParameters(h, aPosX, aPosY, aPosZ, aVelX, aVelY, aVelZ); + + int samples = 0; + if (aSound.mFlags & AudioSource::DISTANCE_DELAY) + { + vec3 pos; + pos.mX = aPosX; + pos.mY = aPosY; + pos.mZ = aPosZ; + if (!(mVoice[v]->mFlags & AudioSource::LISTENER_RELATIVE)) + { + pos.mX -= m3dPosition[0]; + pos.mY -= m3dPosition[1]; + pos.mZ -= m3dPosition[2]; + } + float dist = pos.mag(); + samples += (int)floor((dist / m3dSoundSpeed) * mSamplerate); + } + + update3dVoices((unsigned int *)&v, 1); + updateVoiceRelativePlaySpeed(v); + int j; + for (j = 0; j < MAX_CHANNELS; j++) + { + mVoice[v]->mChannelVolume[j] = m3dData[v].mChannelVolume[j]; + } + + updateVoiceVolume(v); + + // Fix initial voice volume ramp up + int i; + for (i = 0; i < MAX_CHANNELS; i++) + { + mVoice[v]->mCurrentChannelVolume[i] = mVoice[v]->mChannelVolume[i] * mVoice[v]->mOverallVolume; + } + + if (mVoice[v]->mOverallVolume < 0.01f) + { + // Inaudible. + mVoice[v]->mFlags |= AudioSourceInstance::INAUDIBLE; + + if (mVoice[v]->mFlags & AudioSourceInstance::INAUDIBLE_KILL) + { + stopVoice(v); + } + } + else + { + mVoice[v]->mFlags &= ~AudioSourceInstance::INAUDIBLE; + } + mActiveVoiceDirty = true; + + unlockAudioMutex(); + setDelaySamples(h, samples); + setPause(h, aPaused); + return h; + } + + handle Soloud::play3dClocked(time aSoundTime, AudioSource &aSound, float aPosX, float aPosY, float aPosZ, float aVelX, float aVelY, float aVelZ, float aVolume, unsigned int aBus) + { + handle h = play(aSound, aVolume, 0, 1, aBus); + lockAudioMutex(); + int v = getVoiceFromHandle(h); + if (v < 0) + { + unlockAudioMutex(); + return h; + } + m3dData[v].mHandle = h; + mVoice[v]->mFlags |= AudioSourceInstance::PROCESS_3D; + set3dSourceParameters(h, aPosX, aPosY, aPosZ, aVelX, aVelY, aVelZ); + time lasttime = mLastClockedTime; + if (lasttime == 0) + mLastClockedTime = aSoundTime; + vec3 pos; + pos.mX = aPosX; + pos.mY = aPosY; + pos.mZ = aPosZ; + unlockAudioMutex(); + int samples = 0; + if (lasttime != 0) + { + samples = (int)floor((aSoundTime - lasttime) * mSamplerate); + } + if (aSound.mFlags & AudioSource::DISTANCE_DELAY) + { + float dist = pos.mag(); + samples += (int)floor((dist / m3dSoundSpeed) * mSamplerate); + } + + update3dVoices((unsigned int *)&v, 1); + lockAudioMutex(); + updateVoiceRelativePlaySpeed(v); + int j; + for (j = 0; j < MAX_CHANNELS; j++) + { + mVoice[v]->mChannelVolume[j] = m3dData[v].mChannelVolume[j]; + } + + updateVoiceVolume(v); + + // Fix initial voice volume ramp up + int i; + for (i = 0; i < MAX_CHANNELS; i++) + { + mVoice[v]->mCurrentChannelVolume[i] = mVoice[v]->mChannelVolume[i] * mVoice[v]->mOverallVolume; + } + + if (mVoice[v]->mOverallVolume < 0.01f) + { + // Inaudible. + mVoice[v]->mFlags |= AudioSourceInstance::INAUDIBLE; + + if (mVoice[v]->mFlags & AudioSourceInstance::INAUDIBLE_KILL) + { + stopVoice(v); + } + } + else + { + mVoice[v]->mFlags &= ~AudioSourceInstance::INAUDIBLE; + } + mActiveVoiceDirty = true; + unlockAudioMutex(); + + setDelaySamples(h, samples); + setPause(h, 0); + return h; + } + + + + result Soloud::set3dSoundSpeed(float aSpeed) + { + if (aSpeed <= 0) + return INVALID_PARAMETER; + m3dSoundSpeed = aSpeed; + return SO_NO_ERROR; + } + + + float Soloud::get3dSoundSpeed() + { + return m3dSoundSpeed; + } + + + void Soloud::set3dListenerParameters(float aPosX, float aPosY, float aPosZ, float aAtX, float aAtY, float aAtZ, float aUpX, float aUpY, float aUpZ, float aVelocityX, float aVelocityY, float aVelocityZ) + { + m3dPosition[0] = aPosX; + m3dPosition[1] = aPosY; + m3dPosition[2] = aPosZ; + m3dAt[0] = aAtX; + m3dAt[1] = aAtY; + m3dAt[2] = aAtZ; + m3dUp[0] = aUpX; + m3dUp[1] = aUpY; + m3dUp[2] = aUpZ; + m3dVelocity[0] = aVelocityX; + m3dVelocity[1] = aVelocityY; + m3dVelocity[2] = aVelocityZ; + } + + + void Soloud::set3dListenerPosition(float aPosX, float aPosY, float aPosZ) + { + m3dPosition[0] = aPosX; + m3dPosition[1] = aPosY; + m3dPosition[2] = aPosZ; + } + + + void Soloud::set3dListenerAt(float aAtX, float aAtY, float aAtZ) + { + m3dAt[0] = aAtX; + m3dAt[1] = aAtY; + m3dAt[2] = aAtZ; + } + + + void Soloud::set3dListenerUp(float aUpX, float aUpY, float aUpZ) + { + m3dUp[0] = aUpX; + m3dUp[1] = aUpY; + m3dUp[2] = aUpZ; + } + + + void Soloud::set3dListenerVelocity(float aVelocityX, float aVelocityY, float aVelocityZ) + { + m3dVelocity[0] = aVelocityX; + m3dVelocity[1] = aVelocityY; + m3dVelocity[2] = aVelocityZ; + } + + + void Soloud::set3dSourceParameters(handle aVoiceHandle, float aPosX, float aPosY, float aPosZ, float aVelocityX, float aVelocityY, float aVelocityZ) + { + FOR_ALL_VOICES_PRE_3D + m3dData[ch].m3dPosition[0] = aPosX; + m3dData[ch].m3dPosition[1] = aPosY; + m3dData[ch].m3dPosition[2] = aPosZ; + m3dData[ch].m3dVelocity[0] = aVelocityX; + m3dData[ch].m3dVelocity[1] = aVelocityY; + m3dData[ch].m3dVelocity[2] = aVelocityZ; + FOR_ALL_VOICES_POST_3D + } + + + void Soloud::set3dSourcePosition(handle aVoiceHandle, float aPosX, float aPosY, float aPosZ) + { + FOR_ALL_VOICES_PRE_3D + m3dData[ch].m3dPosition[0] = aPosX; + m3dData[ch].m3dPosition[1] = aPosY; + m3dData[ch].m3dPosition[2] = aPosZ; + FOR_ALL_VOICES_POST_3D + } + + + void Soloud::set3dSourceVelocity(handle aVoiceHandle, float aVelocityX, float aVelocityY, float aVelocityZ) + { + FOR_ALL_VOICES_PRE_3D + m3dData[ch].m3dVelocity[0] = aVelocityX; + m3dData[ch].m3dVelocity[1] = aVelocityY; + m3dData[ch].m3dVelocity[2] = aVelocityZ; + FOR_ALL_VOICES_POST_3D + } + + + void Soloud::set3dSourceMinMaxDistance(handle aVoiceHandle, float aMinDistance, float aMaxDistance) + { + FOR_ALL_VOICES_PRE_3D + m3dData[ch].m3dMinDistance = aMinDistance; + m3dData[ch].m3dMaxDistance = aMaxDistance; + FOR_ALL_VOICES_POST_3D + } + + + void Soloud::set3dSourceAttenuation(handle aVoiceHandle, unsigned int aAttenuationModel, float aAttenuationRolloffFactor) + { + FOR_ALL_VOICES_PRE_3D + m3dData[ch].m3dAttenuationModel = aAttenuationModel; + m3dData[ch].m3dAttenuationRolloff = aAttenuationRolloffFactor; + FOR_ALL_VOICES_POST_3D + } + + + void Soloud::set3dSourceDopplerFactor(handle aVoiceHandle, float aDopplerFactor) + { + FOR_ALL_VOICES_PRE_3D + m3dData[ch].m3dDopplerFactor = aDopplerFactor; + FOR_ALL_VOICES_POST_3D + } +}; diff --git a/src/soloud/src/core/soloud_core_basicops.cpp b/src/soloud/src/core/soloud_core_basicops.cpp new file mode 100644 index 0000000..26b025f --- /dev/null +++ b/src/soloud/src/core/soloud_core_basicops.cpp @@ -0,0 +1,206 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include "soloud_internal.h" + +// Core "basic" operations - play, stop, etc + +namespace SoLoud +{ + handle Soloud::play(AudioSource &aSound, float aVolume, float aPan, bool aPaused, unsigned int aBus) + { + if (aSound.mFlags & AudioSource::SINGLE_INSTANCE) + { + // Only one instance allowed, stop others + aSound.stop(); + } + + // Creation of an audio instance may take significant amount of time, + // so let's not do it inside the audio thread mutex. + aSound.mSoloud = this; + SoLoud::AudioSourceInstance *instance = aSound.createInstance(); + + lockAudioMutex(); + int ch = findFreeVoice(); + if (ch < 0) + { + unlockAudioMutex(); + delete instance; + return UNKNOWN_ERROR; + } + if (!aSound.mAudioSourceID) + { + aSound.mAudioSourceID = mAudioSourceID; + mAudioSourceID++; + } + mVoice[ch] = instance; + mVoice[ch]->mAudioSourceID = aSound.mAudioSourceID; + mVoice[ch]->mBusHandle = aBus; + mVoice[ch]->init(aSound, mPlayIndex); + m3dData[ch].init(aSound); + + mPlayIndex++; + + // 20 bits, skip the last one (top bits full = voice group) + if (mPlayIndex == 0xfffff) + { + mPlayIndex = 0; + } + + if (aPaused) + { + mVoice[ch]->mFlags |= AudioSourceInstance::PAUSED; + } + + setVoicePan(ch, aPan); + if (aVolume < 0) + { + setVoiceVolume(ch, aSound.mVolume); + } + else + { + setVoiceVolume(ch, aVolume); + } + + // Fix initial voice volume ramp up + int i; + for (i = 0; i < MAX_CHANNELS; i++) + { + mVoice[ch]->mCurrentChannelVolume[i] = mVoice[ch]->mChannelVolume[i] * mVoice[ch]->mOverallVolume; + } + + setVoiceRelativePlaySpeed(ch, 1); + + for (i = 0; i < FILTERS_PER_STREAM; i++) + { + if (aSound.mFilter[i]) + { + mVoice[ch]->mFilter[i] = aSound.mFilter[i]->createInstance(); + } + } + + mActiveVoiceDirty = true; + + unlockAudioMutex(); + + int handle = getHandleFromVoice(ch); + return handle; + } + + handle Soloud::playClocked(time aSoundTime, AudioSource &aSound, float aVolume, float aPan, unsigned int aBus) + { + handle h = play(aSound, aVolume, aPan, 1, aBus); + lockAudioMutex(); + time lasttime = mLastClockedTime; + if (lasttime == 0) + mLastClockedTime = aSoundTime; + unlockAudioMutex(); + int samples = 0; + if (aSoundTime > lasttime) + { + samples = (int)floor((aSoundTime - lasttime) * mSamplerate); + } + setDelaySamples(h, samples); + setPause(h, 0); + return h; + } + + handle Soloud::playBackground(AudioSource &aSound, float aVolume, bool aPaused, unsigned int aBus) + { + handle h = play(aSound, aVolume, 0.0f, aPaused, aBus); + setPanAbsolute(h, 1.0f, 1.0f); + return h; + } + + result Soloud::seek(handle aVoiceHandle, time aSeconds) + { + result res = SO_NO_ERROR; + result singleres = SO_NO_ERROR; + FOR_ALL_VOICES_PRE + singleres = mVoice[ch]->seek(aSeconds, mScratch.mData, mScratchSize); + if (singleres != SO_NO_ERROR) + res = singleres; + FOR_ALL_VOICES_POST + return res; + } + + + void Soloud::stop(handle aVoiceHandle) + { + FOR_ALL_VOICES_PRE + stopVoice(ch); + FOR_ALL_VOICES_POST + } + + void Soloud::stopAudioSource(AudioSource &aSound) + { + if (aSound.mAudioSourceID) + { + lockAudioMutex(); + + int i; + for (i = 0; i < (signed)mHighestVoice; i++) + { + if (mVoice[i] && mVoice[i]->mAudioSourceID == aSound.mAudioSourceID) + { + stopVoice(i); + } + } + unlockAudioMutex(); + } + } + + void Soloud::stopAll() + { + int i; + lockAudioMutex(); + for (i = 0; i < (signed)mHighestVoice; i++) + { + stopVoice(i); + } + unlockAudioMutex(); + } + + int Soloud::countAudioSource(AudioSource &aSound) + { + int count = 0; + if (aSound.mAudioSourceID) + { + lockAudioMutex(); + + int i; + for (i = 0; i < (signed)mHighestVoice; i++) + { + if (mVoice[i] && mVoice[i]->mAudioSourceID == aSound.mAudioSourceID) + { + count++; + } + } + unlockAudioMutex(); + } + return count; + } + +} diff --git a/src/soloud/src/core/soloud_core_faderops.cpp b/src/soloud/src/core/soloud_core_faderops.cpp new file mode 100644 index 0000000..1f68847 --- /dev/null +++ b/src/soloud/src/core/soloud_core_faderops.cpp @@ -0,0 +1,156 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud_internal.h" + +// Core operations related to faders (not including filters) + +namespace SoLoud +{ + void Soloud::schedulePause(handle aVoiceHandle, time aTime) + { + if (aTime <= 0) + { + setPause(aVoiceHandle, 1); + return; + } + FOR_ALL_VOICES_PRE + mVoice[ch]->mPauseScheduler.set(1, 0, aTime, mVoice[ch]->mStreamTime); + FOR_ALL_VOICES_POST + } + + void Soloud::scheduleStop(handle aVoiceHandle, time aTime) + { + if (aTime <= 0) + { + stop(aVoiceHandle); + return; + } + FOR_ALL_VOICES_PRE + mVoice[ch]->mStopScheduler.set(1, 0, aTime, mVoice[ch]->mStreamTime); + FOR_ALL_VOICES_POST + } + + void Soloud::fadeVolume(handle aVoiceHandle, float aTo, time aTime) + { + float from = getVolume(aVoiceHandle); + if (aTime <= 0 || aTo == from) + { + setVolume(aVoiceHandle, aTo); + return; + } + + FOR_ALL_VOICES_PRE + mVoice[ch]->mVolumeFader.set(from, aTo, aTime, mVoice[ch]->mStreamTime); + FOR_ALL_VOICES_POST + } + + void Soloud::fadePan(handle aVoiceHandle, float aTo, time aTime) + { + float from = getPan(aVoiceHandle); + if (aTime <= 0 || aTo == from) + { + setPan(aVoiceHandle, aTo); + return; + } + + FOR_ALL_VOICES_PRE + mVoice[ch]->mPanFader.set(from, aTo, aTime, mVoice[ch]->mStreamTime); + FOR_ALL_VOICES_POST + } + + void Soloud::fadeRelativePlaySpeed(handle aVoiceHandle, float aTo, time aTime) + { + float from = getRelativePlaySpeed(aVoiceHandle); + if (aTime <= 0 || aTo == from) + { + setRelativePlaySpeed(aVoiceHandle, aTo); + return; + } + FOR_ALL_VOICES_PRE + mVoice[ch]->mRelativePlaySpeedFader.set(from, aTo, aTime, mVoice[ch]->mStreamTime); + FOR_ALL_VOICES_POST + } + + void Soloud::fadeGlobalVolume(float aTo, time aTime) + { + float from = getGlobalVolume(); + if (aTime <= 0 || aTo == from) + { + setGlobalVolume(aTo); + return; + } + mGlobalVolumeFader.set(from, aTo, aTime, mStreamTime); + } + + + void Soloud::oscillateVolume(handle aVoiceHandle, float aFrom, float aTo, time aTime) + { + if (aTime <= 0 || aTo == aFrom) + { + setVolume(aVoiceHandle, aTo); + return; + } + + FOR_ALL_VOICES_PRE + mVoice[ch]->mVolumeFader.setLFO(aFrom, aTo, aTime, mVoice[ch]->mStreamTime); + FOR_ALL_VOICES_POST + } + + void Soloud::oscillatePan(handle aVoiceHandle, float aFrom, float aTo, time aTime) + { + if (aTime <= 0 || aTo == aFrom) + { + setPan(aVoiceHandle, aTo); + return; + } + + FOR_ALL_VOICES_PRE + mVoice[ch]->mPanFader.setLFO(aFrom, aTo, aTime, mVoice[ch]->mStreamTime); + FOR_ALL_VOICES_POST + } + + void Soloud::oscillateRelativePlaySpeed(handle aVoiceHandle, float aFrom, float aTo, time aTime) + { + if (aTime <= 0 || aTo == aFrom) + { + setRelativePlaySpeed(aVoiceHandle, aTo); + return; + } + + FOR_ALL_VOICES_PRE + mVoice[ch]->mRelativePlaySpeedFader.setLFO(aFrom, aTo, aTime, mVoice[ch]->mStreamTime); + FOR_ALL_VOICES_POST + } + + void Soloud::oscillateGlobalVolume(float aFrom, float aTo, time aTime) + { + if (aTime <= 0 || aTo == aFrom) + { + setGlobalVolume(aTo); + return; + } + mGlobalVolumeFader.setLFO(aFrom, aTo, aTime, mStreamTime); + } +} diff --git a/src/soloud/src/core/soloud_core_filterops.cpp b/src/soloud/src/core/soloud_core_filterops.cpp new file mode 100644 index 0000000..7a8f264 --- /dev/null +++ b/src/soloud/src/core/soloud_core_filterops.cpp @@ -0,0 +1,156 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud_internal.h" + +// Core operations related to filters + +namespace SoLoud +{ + void Soloud::setGlobalFilter(unsigned int aFilterId, Filter *aFilter) + { + if (aFilterId >= FILTERS_PER_STREAM) + return; + + lockAudioMutex(); + delete mFilterInstance[aFilterId]; + mFilterInstance[aFilterId] = 0; + + mFilter[aFilterId] = aFilter; + if (aFilter) + { + mFilterInstance[aFilterId] = mFilter[aFilterId]->createInstance(); + } + unlockAudioMutex(); + } + + float Soloud::getFilterParameter(handle aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId) + { + float ret = INVALID_PARAMETER; + if (aFilterId >= FILTERS_PER_STREAM) + return ret; + + if (aVoiceHandle == 0) + { + lockAudioMutex(); + if (mFilterInstance[aFilterId]) + { + ret = mFilterInstance[aFilterId]->getFilterParameter(aAttributeId); + } + unlockAudioMutex(); + return ret; + } + + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + return ret; + } + lockAudioMutex(); + if (mVoice[ch] && + mVoice[ch]->mFilter[aFilterId]) + { + ret = mVoice[ch]->mFilter[aFilterId]->getFilterParameter(aAttributeId); + } + unlockAudioMutex(); + + return ret; + } + + void Soloud::setFilterParameter(handle aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId, float aValue) + { + if (aFilterId >= FILTERS_PER_STREAM) + return; + + if (aVoiceHandle == 0) + { + lockAudioMutex(); + if (mFilterInstance[aFilterId]) + { + mFilterInstance[aFilterId]->setFilterParameter(aAttributeId, aValue); + } + unlockAudioMutex(); + return; + } + + FOR_ALL_VOICES_PRE + if (mVoice[ch] && + mVoice[ch]->mFilter[aFilterId]) + { + mVoice[ch]->mFilter[aFilterId]->setFilterParameter(aAttributeId, aValue); + } + FOR_ALL_VOICES_POST + } + + void Soloud::fadeFilterParameter(handle aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId, float aTo, double aTime) + { + if (aFilterId >= FILTERS_PER_STREAM) + return; + + if (aVoiceHandle == 0) + { + lockAudioMutex(); + if (mFilterInstance[aFilterId]) + { + mFilterInstance[aFilterId]->fadeFilterParameter(aAttributeId, aTo, aTime, mStreamTime); + } + unlockAudioMutex(); + return; + } + + FOR_ALL_VOICES_PRE + if (mVoice[ch] && + mVoice[ch]->mFilter[aFilterId]) + { + mVoice[ch]->mFilter[aFilterId]->fadeFilterParameter(aAttributeId, aTo, aTime, mStreamTime); + } + FOR_ALL_VOICES_POST + } + + void Soloud::oscillateFilterParameter(handle aVoiceHandle, unsigned int aFilterId, unsigned int aAttributeId, float aFrom, float aTo, double aTime) + { + if (aFilterId >= FILTERS_PER_STREAM) + return; + + if (aVoiceHandle == 0) + { + lockAudioMutex(); + if (mFilterInstance[aFilterId]) + { + mFilterInstance[aFilterId]->oscillateFilterParameter(aAttributeId, aFrom, aTo, aTime, mStreamTime); + } + unlockAudioMutex(); + return; + } + + FOR_ALL_VOICES_PRE + if (mVoice[ch] && + mVoice[ch]->mFilter[aFilterId]) + { + mVoice[ch]->mFilter[aFilterId]->oscillateFilterParameter(aAttributeId, aFrom, aTo, aTime, mStreamTime); + } + FOR_ALL_VOICES_POST + } + +} diff --git a/src/soloud/src/core/soloud_core_getters.cpp b/src/soloud/src/core/soloud_core_getters.cpp new file mode 100644 index 0000000..74b93eb --- /dev/null +++ b/src/soloud/src/core/soloud_core_getters.cpp @@ -0,0 +1,378 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" + +// Getters - return information about SoLoud state + +namespace SoLoud +{ + unsigned int Soloud::getVersion() const + { + return SOLOUD_VERSION; + } + + float Soloud::getPostClipScaler() const + { + return mPostClipScaler; + } + + float Soloud::getGlobalVolume() const + { + return mGlobalVolume; + } + + handle Soloud::getHandleFromVoice(unsigned int aVoice) const + { + if (mVoice[aVoice] == 0) + return 0; + return (aVoice + 1) | (mVoice[aVoice]->mPlayIndex << 12); + } + + int Soloud::getVoiceFromHandle(handle aVoiceHandle) const + { + // If this is a voice group handle, pick the first handle from the group + handle *h = voiceGroupHandleToArray(aVoiceHandle); + if (h != NULL) aVoiceHandle = *h; + + if (aVoiceHandle == 0) + { + return -1; + } + + int ch = (aVoiceHandle & 0xfff) - 1; + unsigned int idx = aVoiceHandle >> 12; + if (mVoice[ch] && + (mVoice[ch]->mPlayIndex & 0xfffff) == idx) + { + return ch; + } + return -1; + } + + unsigned int Soloud::getMaxActiveVoiceCount() const + { + return mMaxActiveVoices; + } + + unsigned int Soloud::getActiveVoiceCount() + { + lockAudioMutex(); + if (mActiveVoiceDirty) + calcActiveVoices(); + unsigned int c = mActiveVoiceCount; + unlockAudioMutex(); + return c; + } + + unsigned int Soloud::getVoiceCount() + { + lockAudioMutex(); + int i; + int c = 0; + for (i = 0; i < (signed)mHighestVoice; i++) + { + if (mVoice[i]) + { + c++; + } + } + unlockAudioMutex(); + return c; + } + + bool Soloud::isValidVoiceHandle(handle aVoiceHandle) + { + // voice groups are not valid voice handles + if ((aVoiceHandle & 0xfffff000) == 0xfffff000) + return 0; + + lockAudioMutex(); + if (getVoiceFromHandle(aVoiceHandle) != -1) + { + unlockAudioMutex(); + return 1; + } + unlockAudioMutex(); + return 0; + } + + + time Soloud::getLoopPoint(handle aVoiceHandle) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 0; + } + time v = mVoice[ch]->mLoopPoint; + unlockAudioMutex(); + return v; + } + + bool Soloud::getLooping(handle aVoiceHandle) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 0; + } + bool v = (mVoice[ch]->mFlags & AudioSourceInstance::LOOPING) != 0; + unlockAudioMutex(); + return v; + } + + float Soloud::getInfo(handle aVoiceHandle, unsigned int mInfoKey) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 0; + } + float v = mVoice[ch]->getInfo(mInfoKey); + unlockAudioMutex(); + return v; + } + + float Soloud::getVolume(handle aVoiceHandle) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 0; + } + float v = mVoice[ch]->mSetVolume; + unlockAudioMutex(); + return v; + } + + float Soloud::getOverallVolume(handle aVoiceHandle) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 0; + } + float v = mVoice[ch]->mOverallVolume; + unlockAudioMutex(); + return v; + } + + float Soloud::getPan(handle aVoiceHandle) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 0; + } + float v = mVoice[ch]->mPan; + unlockAudioMutex(); + return v; + } + + time Soloud::getStreamTime(handle aVoiceHandle) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 0; + } + double v = mVoice[ch]->mStreamTime; + unlockAudioMutex(); + return v; + } + + time Soloud::getStreamPosition(handle aVoiceHandle) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 0; + } + double v = mVoice[ch]->mStreamPosition; + unlockAudioMutex(); + return v; + } + + float Soloud::getRelativePlaySpeed(handle aVoiceHandle) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 1; + } + float v = mVoice[ch]->mSetRelativePlaySpeed; + unlockAudioMutex(); + return v; + } + + float Soloud::getSamplerate(handle aVoiceHandle) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 0; + } + float v = mVoice[ch]->mBaseSamplerate; + unlockAudioMutex(); + return v; + } + + bool Soloud::getPause(handle aVoiceHandle) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 0; + } + int v = !!(mVoice[ch]->mFlags & AudioSourceInstance::PAUSED); + unlockAudioMutex(); + return v != 0; + } + + bool Soloud::getProtectVoice(handle aVoiceHandle) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 0; + } + int v = !!(mVoice[ch]->mFlags & AudioSourceInstance::PROTECTED); + unlockAudioMutex(); + return v != 0; + } + + int Soloud::findFreeVoice() + { + int i; + unsigned int lowest_play_index_value = 0xffffffff; + int lowest_play_index = -1; + + // (slowly) drag the highest active voice index down + if (mHighestVoice > 0 && mVoice[mHighestVoice - 1] == NULL) + mHighestVoice--; + + for (i = 0; i < VOICE_COUNT; i++) + { + if (mVoice[i] == NULL) + { + if (i+1 > (signed)mHighestVoice) + { + mHighestVoice = i + 1; + } + return i; + } + if (((mVoice[i]->mFlags & AudioSourceInstance::PROTECTED) == 0) && + mVoice[i]->mPlayIndex < lowest_play_index_value) + { + lowest_play_index_value = mVoice[i]->mPlayIndex; + lowest_play_index = i; + } + } + stopVoice(lowest_play_index); + return lowest_play_index; + } + + unsigned int Soloud::getLoopCount(handle aVoiceHandle) + { + lockAudioMutex(); + int ch = getVoiceFromHandle(aVoiceHandle); + if (ch == -1) + { + unlockAudioMutex(); + return 0; + } + int v = mVoice[ch]->mLoopCount; + unlockAudioMutex(); + return v; + } + + // Returns current backend ID + unsigned int Soloud::getBackendId() + { + return mBackendID; + + } + + // Returns current backend string + const char * Soloud::getBackendString() + { + return mBackendString; + } + + // Returns current backend channel count (1 mono, 2 stereo, etc) + unsigned int Soloud::getBackendChannels() + { + return mChannels; + } + + // Returns current backend sample rate + unsigned int Soloud::getBackendSamplerate() + { + return mSamplerate; + } + + // Returns current backend buffer size + unsigned int Soloud::getBackendBufferSize() + { + return mBufferSize; + } + + // Get speaker position in 3d space + result Soloud::getSpeakerPosition(unsigned int aChannel, float &aX, float &aY, float &aZ) + { + if (aChannel >= mChannels) + return INVALID_PARAMETER; + aX = m3dSpeakerPosition[3 * aChannel + 0]; + aY = m3dSpeakerPosition[3 * aChannel + 1]; + aZ = m3dSpeakerPosition[3 * aChannel + 2]; + return SO_NO_ERROR; + } + + +} diff --git a/src/soloud/src/core/soloud_core_setters.cpp b/src/soloud/src/core/soloud_core_setters.cpp new file mode 100644 index 0000000..21c20ba --- /dev/null +++ b/src/soloud/src/core/soloud_core_setters.cpp @@ -0,0 +1,223 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud_internal.h" + +// Setters - set various bits of SoLoud state + +namespace SoLoud +{ + void Soloud::setPostClipScaler(float aScaler) + { + mPostClipScaler = aScaler; + } + + void Soloud::setGlobalVolume(float aVolume) + { + mGlobalVolumeFader.mActive = 0; + mGlobalVolume = aVolume; + } + + result Soloud::setRelativePlaySpeed(handle aVoiceHandle, float aSpeed) + { + result retVal = 0; + FOR_ALL_VOICES_PRE + mVoice[ch]->mRelativePlaySpeedFader.mActive = 0; + retVal = setVoiceRelativePlaySpeed(ch, aSpeed); + FOR_ALL_VOICES_POST + return retVal; + } + + void Soloud::setSamplerate(handle aVoiceHandle, float aSamplerate) + { + FOR_ALL_VOICES_PRE + mVoice[ch]->mBaseSamplerate = aSamplerate; + updateVoiceRelativePlaySpeed(ch); + FOR_ALL_VOICES_POST + } + + void Soloud::setPause(handle aVoiceHandle, bool aPause) + { + FOR_ALL_VOICES_PRE + setVoicePause(ch, aPause); + FOR_ALL_VOICES_POST + } + + result Soloud::setMaxActiveVoiceCount(unsigned int aVoiceCount) + { + if (aVoiceCount == 0 || aVoiceCount >= VOICE_COUNT) + return INVALID_PARAMETER; + lockAudioMutex(); + mMaxActiveVoices = aVoiceCount; + delete[] mResampleData; + delete[] mResampleDataOwner; + mResampleData = new AlignedFloatBuffer[aVoiceCount * 2]; + mResampleDataOwner = new AudioSourceInstance*[aVoiceCount]; + unsigned int i; + for (i = 0; i < aVoiceCount * 2; i++) + mResampleData[i].init(SAMPLE_GRANULARITY * MAX_CHANNELS); + for (i = 0; i < aVoiceCount; i++) + mResampleDataOwner[i] = NULL; + mActiveVoiceDirty = true; + unlockAudioMutex(); + return SO_NO_ERROR; + } + + void Soloud::setPauseAll(bool aPause) + { + lockAudioMutex(); + int ch; + for (ch = 0; ch < (signed)mHighestVoice; ch++) + { + setVoicePause(ch, aPause); + } + unlockAudioMutex(); + } + + void Soloud::setProtectVoice(handle aVoiceHandle, bool aProtect) + { + FOR_ALL_VOICES_PRE + if (aProtect) + { + mVoice[ch]->mFlags |= AudioSourceInstance::PROTECTED; + } + else + { + mVoice[ch]->mFlags &= ~AudioSourceInstance::PROTECTED; + } + FOR_ALL_VOICES_POST + } + + void Soloud::setPan(handle aVoiceHandle, float aPan) + { + FOR_ALL_VOICES_PRE + setVoicePan(ch, aPan); + FOR_ALL_VOICES_POST + } + + void Soloud::setPanAbsolute(handle aVoiceHandle, float aLVolume, float aRVolume, float aLBVolume, float aRBVolume, float aCVolume, float aSVolume) + { + FOR_ALL_VOICES_PRE + mVoice[ch]->mPanFader.mActive = 0; + mVoice[ch]->mChannelVolume[0] = aLVolume; + mVoice[ch]->mChannelVolume[1] = aRVolume; + if (mVoice[ch]->mChannels == 4) + { + mVoice[ch]->mChannelVolume[2] = aLBVolume; + mVoice[ch]->mChannelVolume[3] = aRBVolume; + } + if (mVoice[ch]->mChannels == 6) + { + mVoice[ch]->mChannelVolume[2] = aCVolume; + mVoice[ch]->mChannelVolume[3] = aSVolume; + mVoice[ch]->mChannelVolume[4] = aLBVolume; + mVoice[ch]->mChannelVolume[5] = aRBVolume; + } + if (mVoice[ch]->mChannels == 8) + { + mVoice[ch]->mChannelVolume[2] = aCVolume; + mVoice[ch]->mChannelVolume[3] = aSVolume; + mVoice[ch]->mChannelVolume[4] = (aLVolume + aLBVolume) * 0.5f; + mVoice[ch]->mChannelVolume[5] = (aRVolume + aRBVolume) * 0.5f; + mVoice[ch]->mChannelVolume[6] = aLBVolume; + mVoice[ch]->mChannelVolume[7] = aRBVolume; + } + FOR_ALL_VOICES_POST + } + + void Soloud::setInaudibleBehavior(handle aVoiceHandle, bool aMustTick, bool aKill) + { + FOR_ALL_VOICES_PRE + mVoice[ch]->mFlags &= ~(AudioSourceInstance::INAUDIBLE_KILL | AudioSourceInstance::INAUDIBLE_TICK); + if (aMustTick) + { + mVoice[ch]->mFlags |= AudioSourceInstance::INAUDIBLE_TICK; + } + if (aKill) + { + mVoice[ch]->mFlags |= AudioSourceInstance::INAUDIBLE_KILL; + } + FOR_ALL_VOICES_POST + } + + void Soloud::setLoopPoint(handle aVoiceHandle, time aLoopPoint) + { + FOR_ALL_VOICES_PRE + mVoice[ch]->mLoopPoint = aLoopPoint; + FOR_ALL_VOICES_POST + } + + void Soloud::setLooping(handle aVoiceHandle, bool aLooping) + { + FOR_ALL_VOICES_PRE + if (aLooping) + { + mVoice[ch]->mFlags |= AudioSourceInstance::LOOPING; + } + else + { + mVoice[ch]->mFlags &= ~AudioSourceInstance::LOOPING; + } + FOR_ALL_VOICES_POST + } + + + void Soloud::setVolume(handle aVoiceHandle, float aVolume) + { + FOR_ALL_VOICES_PRE + mVoice[ch]->mVolumeFader.mActive = 0; + setVoiceVolume(ch, aVolume); + FOR_ALL_VOICES_POST + } + + void Soloud::setDelaySamples(handle aVoiceHandle, unsigned int aSamples) + { + FOR_ALL_VOICES_PRE + mVoice[ch]->mDelaySamples = aSamples; + FOR_ALL_VOICES_POST + } + + void Soloud::setVisualizationEnable(bool aEnable) + { + if (aEnable) + { + mFlags |= ENABLE_VISUALIZATION; + } + else + { + mFlags &= ~ENABLE_VISUALIZATION; + } + } + + result Soloud::setSpeakerPosition(unsigned int aChannel, float aX, float aY, float aZ) + { + if (aChannel >= mChannels) + return INVALID_PARAMETER; + m3dSpeakerPosition[3 * aChannel + 0] = aX; + m3dSpeakerPosition[3 * aChannel + 1] = aY; + m3dSpeakerPosition[3 * aChannel + 2] = aZ; + return SO_NO_ERROR; + } + +} diff --git a/src/soloud/src/core/soloud_core_voicegroup.cpp b/src/soloud/src/core/soloud_core_voicegroup.cpp new file mode 100644 index 0000000..7a3a571 --- /dev/null +++ b/src/soloud/src/core/soloud_core_voicegroup.cpp @@ -0,0 +1,255 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" + +// Voice group operations + +namespace SoLoud +{ + // Create a voice group. Returns 0 if unable (out of voice groups / out of memory) + handle Soloud::createVoiceGroup() + { + lockAudioMutex(); + + unsigned int i; + // Check if there's any deleted voice groups and re-use if found + for (i = 0; i < mVoiceGroupCount; i++) + { + if (mVoiceGroup[i] == NULL) + { + mVoiceGroup[i] = new unsigned int[16]; + if (mVoiceGroup[i] == NULL) + { + unlockAudioMutex(); + return 0; + } + mVoiceGroup[i][0] = 16; + mVoiceGroup[i][1] = 0; + unlockAudioMutex(); + return 0xfffff000 | i; + } + } + if (mVoiceGroupCount == 4096) + { + unlockAudioMutex(); + return 0; + } + unsigned int oldcount = mVoiceGroupCount; + if (mVoiceGroupCount == 0) + { + mVoiceGroupCount = 4; + } + mVoiceGroupCount *= 2; + unsigned int **vg = new unsigned int * [mVoiceGroupCount]; + if (vg == NULL) + { + mVoiceGroupCount = oldcount; + unlockAudioMutex(); + return 0; + } + for (i = 0; i < oldcount; i++) + { + vg[i] = mVoiceGroup[i]; + } + + for (; i < mVoiceGroupCount; i++) + { + vg[i] = NULL; + } + + delete[] mVoiceGroup; + mVoiceGroup = vg; + i = oldcount; + mVoiceGroup[i] = new unsigned int[17]; + if (mVoiceGroup[i] == NULL) + { + unlockAudioMutex(); + return 0; + } + mVoiceGroup[i][0] = 16; + mVoiceGroup[i][1] = 0; + unlockAudioMutex(); + return 0xfffff000 | i; + } + + // Destroy a voice group. + result Soloud::destroyVoiceGroup(handle aVoiceGroupHandle) + { + if (!isVoiceGroup(aVoiceGroupHandle)) + return INVALID_PARAMETER; + int c = aVoiceGroupHandle & 0xfff; + + lockAudioMutex(); + delete[] mVoiceGroup[c]; + mVoiceGroup[c] = NULL; + unlockAudioMutex(); + return SO_NO_ERROR; + } + + // Add a voice handle to a voice group + result Soloud::addVoiceToGroup(handle aVoiceGroupHandle, handle aVoiceHandle) + { + if (!isVoiceGroup(aVoiceGroupHandle)) + return INVALID_PARAMETER; + + // Don't consider adding invalid voice handles as an error, since the voice may just have ended. + if (!isValidVoiceHandle(aVoiceHandle)) + return SO_NO_ERROR; + + trimVoiceGroup(aVoiceGroupHandle); + + int c = aVoiceGroupHandle & 0xfff; + unsigned int i; + + lockAudioMutex(); + + for (i = 1; i < mVoiceGroup[c][0]; i++) + { + if (mVoiceGroup[c][i] == aVoiceHandle) + { + unlockAudioMutex(); + return SO_NO_ERROR; // already there + } + + if (mVoiceGroup[c][i] == 0) + { + mVoiceGroup[c][i] = aVoiceHandle; + mVoiceGroup[c][i + 1] = 0; + + unlockAudioMutex(); + return SO_NO_ERROR; + } + } + + // Full group, allocate more memory + unsigned int * n = new unsigned int[mVoiceGroup[c][0] * 2 + 1]; + if (n == NULL) + { + unlockAudioMutex(); + return OUT_OF_MEMORY; + } + for (i = 0; i < mVoiceGroup[c][0]; i++) + n[i] = mVoiceGroup[c][i]; + n[n[0]] = aVoiceHandle; + n[n[0]+1] = 0; + n[0] *= 2; + delete[] mVoiceGroup[c]; + mVoiceGroup[c] = n; + unlockAudioMutex(); + return SO_NO_ERROR; + } + + // Is this handle a valid voice group? + bool Soloud::isVoiceGroup(handle aVoiceGroupHandle) + { + if ((aVoiceGroupHandle & 0xfffff000) != 0xfffff000) + return 0; + unsigned int c = aVoiceGroupHandle & 0xfff; + if (c >= mVoiceGroupCount) + return 0; + + lockAudioMutex(); + bool res = mVoiceGroup[c] != NULL; + unlockAudioMutex(); + + return res; + } + + // Is this voice group empty? + bool Soloud::isVoiceGroupEmpty(handle aVoiceGroupHandle) + { + // If not a voice group, yeah, we're empty alright.. + if (!isVoiceGroup(aVoiceGroupHandle)) + return 1; + trimVoiceGroup(aVoiceGroupHandle); + int c = aVoiceGroupHandle & 0xfff; + + lockAudioMutex(); + bool res = mVoiceGroup[c][1] == 0; + unlockAudioMutex(); + + return res; + } + + // Remove all non-active voices from group + void Soloud::trimVoiceGroup(handle aVoiceGroupHandle) + { + if (!isVoiceGroup(aVoiceGroupHandle)) + return; + int c = aVoiceGroupHandle & 0xfff; + + lockAudioMutex(); + // empty group + if (mVoiceGroup[c][1] == 0) + { + unlockAudioMutex(); + return; + } + + unsigned int i; + for (i = 1; i < mVoiceGroup[c][0]; i++) + { + if (mVoiceGroup[c][i] == 0) + { + unlockAudioMutex(); + return; + } + + unlockAudioMutex(); + while (!isValidVoiceHandle(mVoiceGroup[c][i])) // function locks mutex, so we need to unlock it before the call + { + lockAudioMutex(); + unsigned int j; + for (j = i; j < mVoiceGroup[c][0] - 1; j++) + { + mVoiceGroup[c][j] = mVoiceGroup[c][j + 1]; + if (mVoiceGroup[c][j] == 0) + break; + } + mVoiceGroup[c][mVoiceGroup[c][0] - 1] = 0; + if (mVoiceGroup[c][i] == 0) + { + unlockAudioMutex(); + return; + } + } + lockAudioMutex(); + } + unlockAudioMutex(); + } + + handle *Soloud::voiceGroupHandleToArray(handle aVoiceGroupHandle) const + { + if ((aVoiceGroupHandle & 0xfffff000) != 0xfffff000) + return NULL; + unsigned int c = aVoiceGroupHandle & 0xfff; + if (c >= mVoiceGroupCount) + return NULL; + if (mVoiceGroup[c] == NULL) + return NULL; + return mVoiceGroup[c] + 1; + } + +} diff --git a/src/soloud/src/core/soloud_core_voiceops.cpp b/src/soloud/src/core/soloud_core_voiceops.cpp new file mode 100644 index 0000000..27a66b5 --- /dev/null +++ b/src/soloud/src/core/soloud_core_voiceops.cpp @@ -0,0 +1,153 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" + +// Direct voice operations (no mutexes - called from other functions) + +namespace SoLoud +{ + result Soloud::setVoiceRelativePlaySpeed(unsigned int aVoice, float aSpeed) + { + SOLOUD_ASSERT(aVoice < VOICE_COUNT); + SOLOUD_ASSERT(mInsideAudioThreadMutex); + if (aSpeed <= 0.0f) + { + return INVALID_PARAMETER; + } + + if (mVoice[aVoice]) + { + mVoice[aVoice]->mSetRelativePlaySpeed = aSpeed; + updateVoiceRelativePlaySpeed(aVoice); + } + + return 0; + } + + void Soloud::setVoicePause(unsigned int aVoice, int aPause) + { + SOLOUD_ASSERT(aVoice < VOICE_COUNT); + SOLOUD_ASSERT(mInsideAudioThreadMutex); + mActiveVoiceDirty = true; + if (mVoice[aVoice]) + { + mVoice[aVoice]->mPauseScheduler.mActive = 0; + + if (aPause) + { + mVoice[aVoice]->mFlags |= AudioSourceInstance::PAUSED; + } + else + { + mVoice[aVoice]->mFlags &= ~AudioSourceInstance::PAUSED; + } + } + } + + void Soloud::setVoicePan(unsigned int aVoice, float aPan) + { + SOLOUD_ASSERT(aVoice < VOICE_COUNT); + SOLOUD_ASSERT(mInsideAudioThreadMutex); + if (mVoice[aVoice]) + { + mVoice[aVoice]->mPan = aPan; + float l = (float)cos((aPan + 1) * M_PI / 4); + float r = (float)sin((aPan + 1) * M_PI / 4); + mVoice[aVoice]->mChannelVolume[0] = l; + mVoice[aVoice]->mChannelVolume[1] = r; + if (mVoice[aVoice]->mChannels == 4) + { + mVoice[aVoice]->mChannelVolume[2] = l; + mVoice[aVoice]->mChannelVolume[3] = r; + } + if (mVoice[aVoice]->mChannels == 6) + { + mVoice[aVoice]->mChannelVolume[2] = 1.0f / (float)sqrt(2.0f); + mVoice[aVoice]->mChannelVolume[3] = 1; + mVoice[aVoice]->mChannelVolume[4] = l; + mVoice[aVoice]->mChannelVolume[5] = r; + } + if (mVoice[aVoice]->mChannels == 8) + { + mVoice[aVoice]->mChannelVolume[2] = 1.0f / (float)sqrt(2.0f); + mVoice[aVoice]->mChannelVolume[3] = 1; + mVoice[aVoice]->mChannelVolume[4] = l; + mVoice[aVoice]->mChannelVolume[5] = r; + mVoice[aVoice]->mChannelVolume[6] = l; + mVoice[aVoice]->mChannelVolume[7] = r; + } + } + } + + void Soloud::setVoiceVolume(unsigned int aVoice, float aVolume) + { + SOLOUD_ASSERT(aVoice < VOICE_COUNT); + SOLOUD_ASSERT(mInsideAudioThreadMutex); + mActiveVoiceDirty = true; + if (mVoice[aVoice]) + { + mVoice[aVoice]->mSetVolume = aVolume; + updateVoiceVolume(aVoice); + } + } + + void Soloud::stopVoice(unsigned int aVoice) + { + SOLOUD_ASSERT(aVoice < VOICE_COUNT); + SOLOUD_ASSERT(mInsideAudioThreadMutex); + mActiveVoiceDirty = true; + if (mVoice[aVoice]) + { + // Delete via temporary variable to avoid recursion + AudioSourceInstance * v = mVoice[aVoice]; + mVoice[aVoice] = 0; + delete v; + + unsigned int i; + for (i = 0; i < mMaxActiveVoices; i++) + { + if (mResampleDataOwner[i] == v) + { + mResampleDataOwner[i] = NULL; + } + } + } + } + + void Soloud::updateVoiceRelativePlaySpeed(unsigned int aVoice) + { + SOLOUD_ASSERT(aVoice < VOICE_COUNT); + SOLOUD_ASSERT(mInsideAudioThreadMutex); + mVoice[aVoice]->mOverallRelativePlaySpeed = m3dData[aVoice].mDopplerValue * mVoice[aVoice]->mSetRelativePlaySpeed; + mVoice[aVoice]->mSamplerate = mVoice[aVoice]->mBaseSamplerate * mVoice[aVoice]->mOverallRelativePlaySpeed; + } + + void Soloud::updateVoiceVolume(unsigned int aVoice) + { + SOLOUD_ASSERT(aVoice < VOICE_COUNT); + SOLOUD_ASSERT(mInsideAudioThreadMutex); + mVoice[aVoice]->mOverallVolume = mVoice[aVoice]->mSetVolume * m3dData[aVoice].m3dVolume; + } +} diff --git a/src/soloud/src/core/soloud_fader.cpp b/src/soloud/src/core/soloud_fader.cpp new file mode 100644 index 0000000..79f1278 --- /dev/null +++ b/src/soloud/src/core/soloud_fader.cpp @@ -0,0 +1,94 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" + +namespace SoLoud +{ + Fader::Fader() + { + mCurrent = mFrom = mTo = mDelta = 0; + mTime = mStartTime = mEndTime = 0; + mActive = 0; + } + + void Fader::set(float aFrom, float aTo, double aTime, double aStartTime) + { + mCurrent = mFrom; + mFrom = aFrom; + mTo = aTo; + mTime = aTime; + mStartTime = aStartTime; + mDelta = aTo - aFrom; + mEndTime = mStartTime + mTime; + mActive = 1; + } + + void Fader::setLFO(float aFrom, float aTo, double aTime, double aStartTime) + { + mActive = 2; + mCurrent = 0; + mFrom = aFrom; + mTo = aTo; + mTime = aTime; + mDelta = (aTo - aFrom) / 2; + if (mDelta < 0) mDelta = -mDelta; + mStartTime = aStartTime; + mEndTime = (float)M_PI * 2 / mTime; + } + + float Fader::get(double aCurrentTime) + { + if (mActive == 2) + { + // LFO mode + if (mStartTime > aCurrentTime) + { + // Time rolled over. + mStartTime = aCurrentTime; + } + double t = aCurrentTime - mStartTime; + return (float)(sin(t * mEndTime) * mDelta + (mFrom + mDelta)); + + } + if (mStartTime > aCurrentTime) + { + // Time rolled over. + // Figure out where we were.. + float p = (mCurrent - mFrom) / mDelta; // 0..1 + mFrom = mCurrent; + mStartTime = aCurrentTime; + mTime = mTime * (1 - p); // time left + mDelta = mTo - mFrom; + mEndTime = mStartTime + mTime; + } + if (aCurrentTime > mEndTime) + { + mActive = -1; + return mTo; + } + mCurrent = (float)(mFrom + mDelta * ((aCurrentTime - mStartTime) / mTime)); + return mCurrent; + } +}; diff --git a/src/soloud/src/core/soloud_fft.cpp b/src/soloud/src/core/soloud_fft.cpp new file mode 100644 index 0000000..fb87be1 --- /dev/null +++ b/src/soloud/src/core/soloud_fft.cpp @@ -0,0 +1,2474 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +// FFT based on fftreal by Laurent de Soras, under WTFPL + +#include "soloud.h" +#include "soloud_fft.h" +#include + +namespace fftimpl +{ +// Based on http://www.kurims.kyoto-u.ac.jp/~ooura/fft.html +// "Copyright Takuya OOURA, 1996-2001 +// You may use, copy, modify and distribute this code for any purpose +// (include commercial use) and without fee. Please refer to this package +// when you modify this code." + + +#ifndef M_PI_2 +#define M_PI_2 1.570796326794896619231321691639751442098584699687f +#endif +#ifndef WR5000 /* cos(M_PI_2*0.5000) */ +#define WR5000 0.707106781186547524400844362104849039284835937688f +#endif +#ifndef WR2500 /* cos(M_PI_2*0.2500) */ +#define WR2500 0.923879532511286756128183189396788286822416625863f +#endif +#ifndef WI2500 /* sin(M_PI_2*0.2500) */ +#define WI2500 0.382683432365089771728459984030398866761344562485f +#endif +#ifndef WR1250 /* cos(M_PI_2*0.1250) */ +#define WR1250 0.980785280403230449126182236134239036973933730893f +#endif +#ifndef WI1250 /* sin(M_PI_2*0.1250) */ +#define WI1250 0.195090322016128267848284868477022240927691617751f +#endif +#ifndef WR3750 /* cos(M_PI_2*0.3750) */ +#define WR3750 0.831469612302545237078788377617905756738560811987f +#endif +#ifndef WI3750 /* sin(M_PI_2*0.3750) */ +#define WI3750 0.555570233019602224742830813948532874374937190754f +#endif + +#ifndef CDFT_LOOP_DIV /* control of the CDFT's speed & tolerance */ +#define CDFT_LOOP_DIV 32 +#endif + +#ifndef RDFT_LOOP_DIV /* control of the RDFT's speed & tolerance */ +#define RDFT_LOOP_DIV 64 +#endif + +#ifndef DCST_LOOP_DIV /* control of the DCT,DST's speed & tolerance */ +#define DCST_LOOP_DIV 64 +#endif + + void bitrv2(int n, float *a) + { + int j0, k0, j1, k1, l, m, i, j, k, nh; + float xr, xi, yr, yi; + + m = 4; + for (l = n >> 2; l > 8; l >>= 2) { + m <<= 1; + } + nh = n >> 1; + if (l == 8) { + j0 = 0; + for (k0 = 0; k0 < m; k0 += 4) { + k = k0; + for (j = j0; j < j0 + k0; j += 4) { + xr = a[j]; + xi = a[j + 1]; + yr = a[k]; + yi = a[k + 1]; + a[j] = yr; + a[j + 1] = yi; + a[k] = xr; + a[k + 1] = xi; + j1 = j + m; + k1 = k + 2 * m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 -= m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 += 2 * m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 -= 2 * m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 += m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 -= 2 * m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 += 2 * m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 -= m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 += 2 * m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 -= 2 * m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 += m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 -= 2 * m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + for (i = nh >> 1; i > (k ^= i); i >>= 1); + } + k1 = j0 + k0; + j1 = k1 + 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 += 2 * m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 -= m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= 2; + k1 -= nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh + 2; + k1 += nh + 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh - m; + k1 += 2 * m - 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + for (i = nh >> 1; i > (j0 ^= i); i >>= 1); + } + } + else { + j0 = 0; + for (k0 = 0; k0 < m; k0 += 4) { + k = k0; + for (j = j0; j < j0 + k0; j += 4) { + xr = a[j]; + xi = a[j + 1]; + yr = a[k]; + yi = a[k + 1]; + a[j] = yr; + a[j + 1] = yi; + a[k] = xr; + a[k + 1] = xi; + j1 = j + m; + k1 = k + m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 -= m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 += m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 -= m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + for (i = nh >> 1; i > (k ^= i); i >>= 1); + } + k1 = j0 + k0; + j1 = k1 + 2; + k1 += nh; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 += m; + xr = a[j1]; + xi = a[j1 + 1]; + yr = a[k1]; + yi = a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + for (i = nh >> 1; i > (j0 ^= i); i >>= 1); + } + } + } + + + void bitrv2conj(int n, float *a) + { + int j0, k0, j1, k1, l, m, i, j, k, nh; + float xr, xi, yr, yi; + + m = 4; + for (l = n >> 2; l > 8; l >>= 2) { + m <<= 1; + } + nh = n >> 1; + if (l == 8) { + j0 = 0; + for (k0 = 0; k0 < m; k0 += 4) { + k = k0; + for (j = j0; j < j0 + k0; j += 4) { + xr = a[j]; + xi = -a[j + 1]; + yr = a[k]; + yi = -a[k + 1]; + a[j] = yr; + a[j + 1] = yi; + a[k] = xr; + a[k + 1] = xi; + j1 = j + m; + k1 = k + 2 * m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 -= m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 += 2 * m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 -= 2 * m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 += m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 -= 2 * m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 += 2 * m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 -= m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 += 2 * m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 -= 2 * m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 += m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 -= 2 * m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + for (i = nh >> 1; i > (k ^= i); i >>= 1); + } + k1 = j0 + k0; + j1 = k1 + 2; + k1 += nh; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + j1 += m; + k1 += 2 * m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 -= m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= 2; + k1 -= nh; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh + 2; + k1 += nh + 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh - m; + k1 += 2 * m - 2; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + for (i = nh >> 1; i > (j0 ^= i); i >>= 1); + } + } + else { + j0 = 0; + for (k0 = 0; k0 < m; k0 += 4) { + k = k0; + for (j = j0; j < j0 + k0; j += 4) { + xr = a[j]; + xi = -a[j + 1]; + yr = a[k]; + yi = -a[k + 1]; + a[j] = yr; + a[j + 1] = yi; + a[k] = xr; + a[k + 1] = xi; + j1 = j + m; + k1 = k + m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += nh; + k1 += 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 -= m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += 2; + k1 += nh; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 += m; + k1 += m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= nh; + k1 -= 2; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + j1 -= m; + k1 -= m; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + for (i = nh >> 1; i > (k ^= i); i >>= 1); + } + k1 = j0 + k0; + j1 = k1 + 2; + k1 += nh; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + j1 += m; + k1 += m; + a[j1 - 1] = -a[j1 - 1]; + xr = a[j1]; + xi = -a[j1 + 1]; + yr = a[k1]; + yi = -a[k1 + 1]; + a[j1] = yr; + a[j1 + 1] = yi; + a[k1] = xr; + a[k1 + 1] = xi; + a[k1 + 3] = -a[k1 + 3]; + for (i = nh >> 1; i > (j0 ^= i); i >>= 1); + } + } + } + + + void bitrv216(float *a) + { + float x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i, + x5r, x5i, x7r, x7i, x8r, x8i, x10r, x10i, + x11r, x11i, x12r, x12i, x13r, x13i, x14r, x14i; + + x1r = a[2]; + x1i = a[3]; + x2r = a[4]; + x2i = a[5]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x5r = a[10]; + x5i = a[11]; + x7r = a[14]; + x7i = a[15]; + x8r = a[16]; + x8i = a[17]; + x10r = a[20]; + x10i = a[21]; + x11r = a[22]; + x11i = a[23]; + x12r = a[24]; + x12i = a[25]; + x13r = a[26]; + x13i = a[27]; + x14r = a[28]; + x14i = a[29]; + a[2] = x8r; + a[3] = x8i; + a[4] = x4r; + a[5] = x4i; + a[6] = x12r; + a[7] = x12i; + a[8] = x2r; + a[9] = x2i; + a[10] = x10r; + a[11] = x10i; + a[14] = x14r; + a[15] = x14i; + a[16] = x1r; + a[17] = x1i; + a[20] = x5r; + a[21] = x5i; + a[22] = x13r; + a[23] = x13i; + a[24] = x3r; + a[25] = x3i; + a[26] = x11r; + a[27] = x11i; + a[28] = x7r; + a[29] = x7i; + } + + + void bitrv216neg(float *a) + { + float x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i, + x5r, x5i, x6r, x6i, x7r, x7i, x8r, x8i, + x9r, x9i, x10r, x10i, x11r, x11i, x12r, x12i, + x13r, x13i, x14r, x14i, x15r, x15i; + + x1r = a[2]; + x1i = a[3]; + x2r = a[4]; + x2i = a[5]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x5r = a[10]; + x5i = a[11]; + x6r = a[12]; + x6i = a[13]; + x7r = a[14]; + x7i = a[15]; + x8r = a[16]; + x8i = a[17]; + x9r = a[18]; + x9i = a[19]; + x10r = a[20]; + x10i = a[21]; + x11r = a[22]; + x11i = a[23]; + x12r = a[24]; + x12i = a[25]; + x13r = a[26]; + x13i = a[27]; + x14r = a[28]; + x14i = a[29]; + x15r = a[30]; + x15i = a[31]; + a[2] = x15r; + a[3] = x15i; + a[4] = x7r; + a[5] = x7i; + a[6] = x11r; + a[7] = x11i; + a[8] = x3r; + a[9] = x3i; + a[10] = x13r; + a[11] = x13i; + a[12] = x5r; + a[13] = x5i; + a[14] = x9r; + a[15] = x9i; + a[16] = x1r; + a[17] = x1i; + a[18] = x14r; + a[19] = x14i; + a[20] = x6r; + a[21] = x6i; + a[22] = x10r; + a[23] = x10i; + a[24] = x2r; + a[25] = x2i; + a[26] = x12r; + a[27] = x12i; + a[28] = x4r; + a[29] = x4i; + a[30] = x8r; + a[31] = x8i; + } + + + void bitrv208(float *a) + { + float x1r, x1i, x3r, x3i, x4r, x4i, x6r, x6i; + + x1r = a[2]; + x1i = a[3]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x6r = a[12]; + x6i = a[13]; + a[2] = x4r; + a[3] = x4i; + a[6] = x6r; + a[7] = x6i; + a[8] = x1r; + a[9] = x1i; + a[12] = x3r; + a[13] = x3i; + } + + + void bitrv208neg(float *a) + { + float x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i, + x5r, x5i, x6r, x6i, x7r, x7i; + + x1r = a[2]; + x1i = a[3]; + x2r = a[4]; + x2i = a[5]; + x3r = a[6]; + x3i = a[7]; + x4r = a[8]; + x4i = a[9]; + x5r = a[10]; + x5i = a[11]; + x6r = a[12]; + x6i = a[13]; + x7r = a[14]; + x7i = a[15]; + a[2] = x7r; + a[3] = x7i; + a[4] = x3r; + a[5] = x3i; + a[6] = x5r; + a[7] = x5i; + a[8] = x1r; + a[9] = x1i; + a[10] = x6r; + a[11] = x6i; + a[12] = x2r; + a[13] = x2i; + a[14] = x4r; + a[15] = x4i; + } + + void cftb1st(int n, float *a) + { + int i, i0, j, j0, j1, j2, j3, m, mh; + float ew, w1r, w1i, wk1r, wk1i, wk3r, wk3i, + wd1r, wd1i, wd3r, wd3i, ss1, ss3; + float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + mh = n >> 3; + m = 2 * mh; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] + a[j2]; + x0i = -a[1] - a[j2 + 1]; + x1r = a[0] - a[j2]; + x1i = -a[1] + a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[0] = x0r + x2r; + a[1] = x0i - x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + a[j2] = x1r + x3i; + a[j2 + 1] = x1i + x3r; + a[j3] = x1r - x3i; + a[j3 + 1] = x1i - x3r; + wd1r = 1; + wd1i = 0; + wd3r = 1; + wd3i = 0; + ew = M_PI_2 / m; + w1r = (float)cos(2 * ew); + w1i = (float)sin(2 * ew); + wk1r = w1r; + wk1i = w1i; + ss1 = 2 * w1i; + wk3i = 2 * ss1 * wk1r; + wk3r = wk1r - wk3i * wk1i; + wk3i = wk1i - wk3i * wk1r; + ss3 = 2 * wk3i; + i = 0; + for (;;) { + i0 = i + 4 * CDFT_LOOP_DIV; + if (i0 > mh - 4) { + i0 = mh - 4; + } + for (j = i + 2; j < i0; j += 4) { + wd1r -= ss1 * wk1i; + wd1i += ss1 * wk1r; + wd3r -= ss3 * wk3i; + wd3i += ss3 * wk3r; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] + a[j2]; + x0i = -a[j + 1] - a[j2 + 1]; + x1r = a[j] - a[j2]; + x1i = -a[j + 1] + a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i - x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2] = wk1r * x0r - wk1i * x0i; + a[j2 + 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r + wk3i * x0i; + a[j3 + 1] = wk3r * x0i - wk3i * x0r; + x0r = a[j + 2] + a[j2 + 2]; + x0i = -a[j + 3] - a[j2 + 3]; + x1r = a[j + 2] - a[j2 + 2]; + x1i = -a[j + 3] + a[j2 + 3]; + x2r = a[j1 + 2] + a[j3 + 2]; + x2i = a[j1 + 3] + a[j3 + 3]; + x3r = a[j1 + 2] - a[j3 + 2]; + x3i = a[j1 + 3] - a[j3 + 3]; + a[j + 2] = x0r + x2r; + a[j + 3] = x0i - x2i; + a[j1 + 2] = x0r - x2r; + a[j1 + 3] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2 + 2] = wd1r * x0r - wd1i * x0i; + a[j2 + 3] = wd1r * x0i + wd1i * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3 + 2] = wd3r * x0r + wd3i * x0i; + a[j3 + 3] = wd3r * x0i - wd3i * x0r; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = -a[j0 + 1] - a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = -a[j0 + 1] + a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i - x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2] = wk1i * x0r - wk1r * x0i; + a[j2 + 1] = wk1i * x0i + wk1r * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3] = wk3i * x0r + wk3r * x0i; + a[j3 + 1] = wk3i * x0i - wk3r * x0r; + x0r = a[j0 - 2] + a[j2 - 2]; + x0i = -a[j0 - 1] - a[j2 - 1]; + x1r = a[j0 - 2] - a[j2 - 2]; + x1i = -a[j0 - 1] + a[j2 - 1]; + x2r = a[j1 - 2] + a[j3 - 2]; + x2i = a[j1 - 1] + a[j3 - 1]; + x3r = a[j1 - 2] - a[j3 - 2]; + x3i = a[j1 - 1] - a[j3 - 1]; + a[j0 - 2] = x0r + x2r; + a[j0 - 1] = x0i - x2i; + a[j1 - 2] = x0r - x2r; + a[j1 - 1] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2 - 2] = wd1i * x0r - wd1r * x0i; + a[j2 - 1] = wd1i * x0i + wd1r * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3 - 2] = wd3i * x0r + wd3r * x0i; + a[j3 - 1] = wd3i * x0i - wd3r * x0r; + wk1r -= ss1 * wd1i; + wk1i += ss1 * wd1r; + wk3r -= ss3 * wd3i; + wk3i += ss3 * wd3r; + } + if (i0 == mh - 4) { + break; + } + wd1r = (float)cos(ew * i0); + wd1i = (float)sin(ew * i0); + wd3i = 4 * wd1i * wd1r; + wd3r = wd1r - wd3i * wd1i; + wd3i = wd1i - wd3i * wd1r; + wk1r = w1r * wd1r - w1i * wd1i; + wk1i = w1r * wd1i + w1i * wd1r; + wk3i = 4 * wk1i * wk1r; + wk3r = wk1r - wk3i * wk1i; + wk3i = wk1i - wk3i * wk1r; + i = i0; + } + wd1r = WR5000; + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0 - 2] + a[j2 - 2]; + x0i = -a[j0 - 1] - a[j2 - 1]; + x1r = a[j0 - 2] - a[j2 - 2]; + x1i = -a[j0 - 1] + a[j2 - 1]; + x2r = a[j1 - 2] + a[j3 - 2]; + x2i = a[j1 - 1] + a[j3 - 1]; + x3r = a[j1 - 2] - a[j3 - 2]; + x3i = a[j1 - 1] - a[j3 - 1]; + a[j0 - 2] = x0r + x2r; + a[j0 - 1] = x0i - x2i; + a[j1 - 2] = x0r - x2r; + a[j1 - 1] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2 - 2] = wk1r * x0r - wk1i * x0i; + a[j2 - 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3 - 2] = wk3r * x0r + wk3i * x0i; + a[j3 - 1] = wk3r * x0i - wk3i * x0r; + x0r = a[j0] + a[j2]; + x0i = -a[j0 + 1] - a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = -a[j0 + 1] + a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i - x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2] = wd1r * (x0r - x0i); + a[j2 + 1] = wd1r * (x0i + x0r); + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3] = -wd1r * (x0r + x0i); + a[j3 + 1] = -wd1r * (x0i - x0r); + x0r = a[j0 + 2] + a[j2 + 2]; + x0i = -a[j0 + 3] - a[j2 + 3]; + x1r = a[j0 + 2] - a[j2 + 2]; + x1i = -a[j0 + 3] + a[j2 + 3]; + x2r = a[j1 + 2] + a[j3 + 2]; + x2i = a[j1 + 3] + a[j3 + 3]; + x3r = a[j1 + 2] - a[j3 + 2]; + x3i = a[j1 + 3] - a[j3 + 3]; + a[j0 + 2] = x0r + x2r; + a[j0 + 3] = x0i - x2i; + a[j1 + 2] = x0r - x2r; + a[j1 + 3] = x0i + x2i; + x0r = x1r + x3i; + x0i = x1i + x3r; + a[j2 + 2] = wk1i * x0r - wk1r * x0i; + a[j2 + 3] = wk1i * x0i + wk1r * x0r; + x0r = x1r - x3i; + x0i = x1i - x3r; + a[j3 + 2] = wk3i * x0r + wk3r * x0i; + a[j3 + 3] = wk3i * x0i - wk3r * x0r; + } + + void cftmdl1(int n, float *a) + { + int i, i0, j, j0, j1, j2, j3, m, mh; + float ew, w1r, w1i, wk1r, wk1i, wk3r, wk3i, + wd1r, wd1i, wd3r, wd3i, ss1, ss3; + float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + mh = n >> 3; + m = 2 * mh; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] + a[j2]; + x0i = a[1] + a[j2 + 1]; + x1r = a[0] - a[j2]; + x1i = a[1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + a[j2] = x1r - x3i; + a[j2 + 1] = x1i + x3r; + a[j3] = x1r + x3i; + a[j3 + 1] = x1i - x3r; + wd1r = 1; + wd1i = 0; + wd3r = 1; + wd3i = 0; + ew = M_PI_2 / m; + w1r = (float)cos(2 * ew); + w1i = (float)sin(2 * ew); + wk1r = w1r; + wk1i = w1i; + ss1 = 2 * w1i; + wk3i = 2 * ss1 * wk1r; + wk3r = wk1r - wk3i * wk1i; + wk3i = wk1i - wk3i * wk1r; + ss3 = 2 * wk3i; + i = 0; + for (;;) { + i0 = i + 4 * CDFT_LOOP_DIV; + if (i0 > mh - 4) { + i0 = mh - 4; + } + for (j = i + 2; j < i0; j += 4) { + wd1r -= ss1 * wk1i; + wd1i += ss1 * wk1r; + wd3r -= ss3 * wk3i; + wd3i += ss3 * wk3r; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] + a[j2]; + x0i = a[j + 1] + a[j2 + 1]; + x1r = a[j] - a[j2]; + x1i = a[j + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j] = x0r + x2r; + a[j + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1r * x0r - wk1i * x0i; + a[j2 + 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3r * x0r + wk3i * x0i; + a[j3 + 1] = wk3r * x0i - wk3i * x0r; + x0r = a[j + 2] + a[j2 + 2]; + x0i = a[j + 3] + a[j2 + 3]; + x1r = a[j + 2] - a[j2 + 2]; + x1i = a[j + 3] - a[j2 + 3]; + x2r = a[j1 + 2] + a[j3 + 2]; + x2i = a[j1 + 3] + a[j3 + 3]; + x3r = a[j1 + 2] - a[j3 + 2]; + x3i = a[j1 + 3] - a[j3 + 3]; + a[j + 2] = x0r + x2r; + a[j + 3] = x0i + x2i; + a[j1 + 2] = x0r - x2r; + a[j1 + 3] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2 + 2] = wd1r * x0r - wd1i * x0i; + a[j2 + 3] = wd1r * x0i + wd1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3 + 2] = wd3r * x0r + wd3i * x0i; + a[j3 + 3] = wd3r * x0i - wd3i * x0r; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wk1i * x0r - wk1r * x0i; + a[j2 + 1] = wk1i * x0i + wk1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = wk3i * x0r + wk3r * x0i; + a[j3 + 1] = wk3i * x0i - wk3r * x0r; + x0r = a[j0 - 2] + a[j2 - 2]; + x0i = a[j0 - 1] + a[j2 - 1]; + x1r = a[j0 - 2] - a[j2 - 2]; + x1i = a[j0 - 1] - a[j2 - 1]; + x2r = a[j1 - 2] + a[j3 - 2]; + x2i = a[j1 - 1] + a[j3 - 1]; + x3r = a[j1 - 2] - a[j3 - 2]; + x3i = a[j1 - 1] - a[j3 - 1]; + a[j0 - 2] = x0r + x2r; + a[j0 - 1] = x0i + x2i; + a[j1 - 2] = x0r - x2r; + a[j1 - 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2 - 2] = wd1i * x0r - wd1r * x0i; + a[j2 - 1] = wd1i * x0i + wd1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3 - 2] = wd3i * x0r + wd3r * x0i; + a[j3 - 1] = wd3i * x0i - wd3r * x0r; + wk1r -= ss1 * wd1i; + wk1i += ss1 * wd1r; + wk3r -= ss3 * wd3i; + wk3i += ss3 * wd3r; + } + if (i0 == mh - 4) { + break; + } + wd1r = (float)cos(ew * i0); + wd1i = (float)sin(ew * i0); + wd3i = 4 * wd1i * wd1r; + wd3r = wd1r - wd3i * wd1i; + wd3i = wd1i - wd3i * wd1r; + wk1r = w1r * wd1r - w1i * wd1i; + wk1i = w1r * wd1i + w1i * wd1r; + wk3i = 4 * wk1i * wk1r; + wk3r = wk1r - wk3i * wk1i; + wk3i = wk1i - wk3i * wk1r; + i = i0; + } + wd1r = WR5000; + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0 - 2] + a[j2 - 2]; + x0i = a[j0 - 1] + a[j2 - 1]; + x1r = a[j0 - 2] - a[j2 - 2]; + x1i = a[j0 - 1] - a[j2 - 1]; + x2r = a[j1 - 2] + a[j3 - 2]; + x2i = a[j1 - 1] + a[j3 - 1]; + x3r = a[j1 - 2] - a[j3 - 2]; + x3i = a[j1 - 1] - a[j3 - 1]; + a[j0 - 2] = x0r + x2r; + a[j0 - 1] = x0i + x2i; + a[j1 - 2] = x0r - x2r; + a[j1 - 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2 - 2] = wk1r * x0r - wk1i * x0i; + a[j2 - 1] = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3 - 2] = wk3r * x0r + wk3i * x0i; + a[j3 - 1] = wk3r * x0i - wk3i * x0r; + x0r = a[j0] + a[j2]; + x0i = a[j0 + 1] + a[j2 + 1]; + x1r = a[j0] - a[j2]; + x1i = a[j0 + 1] - a[j2 + 1]; + x2r = a[j1] + a[j3]; + x2i = a[j1 + 1] + a[j3 + 1]; + x3r = a[j1] - a[j3]; + x3i = a[j1 + 1] - a[j3 + 1]; + a[j0] = x0r + x2r; + a[j0 + 1] = x0i + x2i; + a[j1] = x0r - x2r; + a[j1 + 1] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2] = wd1r * (x0r - x0i); + a[j2 + 1] = wd1r * (x0i + x0r); + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3] = -wd1r * (x0r + x0i); + a[j3 + 1] = -wd1r * (x0i - x0r); + x0r = a[j0 + 2] + a[j2 + 2]; + x0i = a[j0 + 3] + a[j2 + 3]; + x1r = a[j0 + 2] - a[j2 + 2]; + x1i = a[j0 + 3] - a[j2 + 3]; + x2r = a[j1 + 2] + a[j3 + 2]; + x2i = a[j1 + 3] + a[j3 + 3]; + x3r = a[j1 + 2] - a[j3 + 2]; + x3i = a[j1 + 3] - a[j3 + 3]; + a[j0 + 2] = x0r + x2r; + a[j0 + 3] = x0i + x2i; + a[j1 + 2] = x0r - x2r; + a[j1 + 3] = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + a[j2 + 2] = wk1i * x0r - wk1r * x0i; + a[j2 + 3] = wk1i * x0i + wk1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + a[j3 + 2] = wk3i * x0r + wk3r * x0i; + a[j3 + 3] = wk3i * x0i - wk3r * x0r; + } + + + void cftmdl2(int n, float *a) + { + int i, i0, j, j0, j1, j2, j3, m, mh; + float ew, w1r, w1i, wn4r, wk1r, wk1i, wk3r, wk3i, + wl1r, wl1i, wl3r, wl3i, wd1r, wd1i, wd3r, wd3i, + we1r, we1i, we3r, we3i, ss1, ss3; + float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, y0r, y0i, y2r, y2i; + + mh = n >> 3; + m = 2 * mh; + wn4r = WR5000; + j1 = m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[0] - a[j2 + 1]; + x0i = a[1] + a[j2]; + x1r = a[0] + a[j2 + 1]; + x1i = a[1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wn4r * (x2r - x2i); + y0i = wn4r * (x2i + x2r); + a[0] = x0r + y0r; + a[1] = x0i + y0i; + a[j1] = x0r - y0r; + a[j1 + 1] = x0i - y0i; + y0r = wn4r * (x3r - x3i); + y0i = wn4r * (x3i + x3r); + a[j2] = x1r - y0i; + a[j2 + 1] = x1i + y0r; + a[j3] = x1r + y0i; + a[j3 + 1] = x1i - y0r; + wl1r = 1; + wl1i = 0; + wl3r = 1; + wl3i = 0; + we1r = wn4r; + we1i = wn4r; + we3r = -wn4r; + we3i = -wn4r; + ew = M_PI_2 / (2 * m); + w1r = (float)cos(2 * ew); + w1i = (float)sin(2 * ew); + wk1r = w1r; + wk1i = w1i; + wd1r = wn4r * (w1r - w1i); + wd1i = wn4r * (w1i + w1r); + ss1 = 2 * w1i; + wk3i = 2 * ss1 * wk1r; + wk3r = wk1r - wk3i * wk1i; + wk3i = wk1i - wk3i * wk1r; + ss3 = 2 * wk3i; + wd3r = -wn4r * (wk3r - wk3i); + wd3i = -wn4r * (wk3i + wk3r); + i = 0; + for (;;) { + i0 = i + 4 * CDFT_LOOP_DIV; + if (i0 > mh - 4) { + i0 = mh - 4; + } + for (j = i + 2; j < i0; j += 4) { + wl1r -= ss1 * wk1i; + wl1i += ss1 * wk1r; + wl3r -= ss3 * wk3i; + wl3i += ss3 * wk3r; + we1r -= ss1 * wd1i; + we1i += ss1 * wd1r; + we3r -= ss3 * wd3i; + we3i += ss3 * wd3r; + j1 = j + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j] - a[j2 + 1]; + x0i = a[j + 1] + a[j2]; + x1r = a[j] + a[j2 + 1]; + x1i = a[j + 1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wk1r * x0r - wk1i * x0i; + y0i = wk1r * x0i + wk1i * x0r; + y2r = wd1r * x2r - wd1i * x2i; + y2i = wd1r * x2i + wd1i * x2r; + a[j] = y0r + y2r; + a[j + 1] = y0i + y2i; + a[j1] = y0r - y2r; + a[j1 + 1] = y0i - y2i; + y0r = wk3r * x1r + wk3i * x1i; + y0i = wk3r * x1i - wk3i * x1r; + y2r = wd3r * x3r + wd3i * x3i; + y2i = wd3r * x3i - wd3i * x3r; + a[j2] = y0r + y2r; + a[j2 + 1] = y0i + y2i; + a[j3] = y0r - y2r; + a[j3 + 1] = y0i - y2i; + x0r = a[j + 2] - a[j2 + 3]; + x0i = a[j + 3] + a[j2 + 2]; + x1r = a[j + 2] + a[j2 + 3]; + x1i = a[j + 3] - a[j2 + 2]; + x2r = a[j1 + 2] - a[j3 + 3]; + x2i = a[j1 + 3] + a[j3 + 2]; + x3r = a[j1 + 2] + a[j3 + 3]; + x3i = a[j1 + 3] - a[j3 + 2]; + y0r = wl1r * x0r - wl1i * x0i; + y0i = wl1r * x0i + wl1i * x0r; + y2r = we1r * x2r - we1i * x2i; + y2i = we1r * x2i + we1i * x2r; + a[j + 2] = y0r + y2r; + a[j + 3] = y0i + y2i; + a[j1 + 2] = y0r - y2r; + a[j1 + 3] = y0i - y2i; + y0r = wl3r * x1r + wl3i * x1i; + y0i = wl3r * x1i - wl3i * x1r; + y2r = we3r * x3r + we3i * x3i; + y2i = we3r * x3i - we3i * x3r; + a[j2 + 2] = y0r + y2r; + a[j2 + 3] = y0i + y2i; + a[j3 + 2] = y0r - y2r; + a[j3 + 3] = y0i - y2i; + j0 = m - j; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0] - a[j2 + 1]; + x0i = a[j0 + 1] + a[j2]; + x1r = a[j0] + a[j2 + 1]; + x1i = a[j0 + 1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wd1i * x0r - wd1r * x0i; + y0i = wd1i * x0i + wd1r * x0r; + y2r = wk1i * x2r - wk1r * x2i; + y2i = wk1i * x2i + wk1r * x2r; + a[j0] = y0r + y2r; + a[j0 + 1] = y0i + y2i; + a[j1] = y0r - y2r; + a[j1 + 1] = y0i - y2i; + y0r = wd3i * x1r + wd3r * x1i; + y0i = wd3i * x1i - wd3r * x1r; + y2r = wk3i * x3r + wk3r * x3i; + y2i = wk3i * x3i - wk3r * x3r; + a[j2] = y0r + y2r; + a[j2 + 1] = y0i + y2i; + a[j3] = y0r - y2r; + a[j3 + 1] = y0i - y2i; + x0r = a[j0 - 2] - a[j2 - 1]; + x0i = a[j0 - 1] + a[j2 - 2]; + x1r = a[j0 - 2] + a[j2 - 1]; + x1i = a[j0 - 1] - a[j2 - 2]; + x2r = a[j1 - 2] - a[j3 - 1]; + x2i = a[j1 - 1] + a[j3 - 2]; + x3r = a[j1 - 2] + a[j3 - 1]; + x3i = a[j1 - 1] - a[j3 - 2]; + y0r = we1i * x0r - we1r * x0i; + y0i = we1i * x0i + we1r * x0r; + y2r = wl1i * x2r - wl1r * x2i; + y2i = wl1i * x2i + wl1r * x2r; + a[j0 - 2] = y0r + y2r; + a[j0 - 1] = y0i + y2i; + a[j1 - 2] = y0r - y2r; + a[j1 - 1] = y0i - y2i; + y0r = we3i * x1r + we3r * x1i; + y0i = we3i * x1i - we3r * x1r; + y2r = wl3i * x3r + wl3r * x3i; + y2i = wl3i * x3i - wl3r * x3r; + a[j2 - 2] = y0r + y2r; + a[j2 - 1] = y0i + y2i; + a[j3 - 2] = y0r - y2r; + a[j3 - 1] = y0i - y2i; + wk1r -= ss1 * wl1i; + wk1i += ss1 * wl1r; + wk3r -= ss3 * wl3i; + wk3i += ss3 * wl3r; + wd1r -= ss1 * we1i; + wd1i += ss1 * we1r; + wd3r -= ss3 * we3i; + wd3i += ss3 * we3r; + } + if (i0 == mh - 4) { + break; + } + wl1r = (float)cos(ew * i0); + wl1i = (float)sin(ew * i0); + wl3i = 4 * wl1i * wl1r; + wl3r = wl1r - wl3i * wl1i; + wl3i = wl1i - wl3i * wl1r; + we1r = wn4r * (wl1r - wl1i); + we1i = wn4r * (wl1i + wl1r); + we3r = -wn4r * (wl3r - wl3i); + we3i = -wn4r * (wl3i + wl3r); + wk1r = w1r * wl1r - w1i * wl1i; + wk1i = w1r * wl1i + w1i * wl1r; + wk3i = 4 * wk1i * wk1r; + wk3r = wk1r - wk3i * wk1i; + wk3i = wk1i - wk3i * wk1r; + wd1r = wn4r * (wk1r - wk1i); + wd1i = wn4r * (wk1i + wk1r); + wd3r = -wn4r * (wk3r - wk3i); + wd3i = -wn4r * (wk3i + wk3r); + i = i0; + } + wl1r = WR2500; + wl1i = WI2500; + j0 = mh; + j1 = j0 + m; + j2 = j1 + m; + j3 = j2 + m; + x0r = a[j0 - 2] - a[j2 - 1]; + x0i = a[j0 - 1] + a[j2 - 2]; + x1r = a[j0 - 2] + a[j2 - 1]; + x1i = a[j0 - 1] - a[j2 - 2]; + x2r = a[j1 - 2] - a[j3 - 1]; + x2i = a[j1 - 1] + a[j3 - 2]; + x3r = a[j1 - 2] + a[j3 - 1]; + x3i = a[j1 - 1] - a[j3 - 2]; + y0r = wk1r * x0r - wk1i * x0i; + y0i = wk1r * x0i + wk1i * x0r; + y2r = wd1r * x2r - wd1i * x2i; + y2i = wd1r * x2i + wd1i * x2r; + a[j0 - 2] = y0r + y2r; + a[j0 - 1] = y0i + y2i; + a[j1 - 2] = y0r - y2r; + a[j1 - 1] = y0i - y2i; + y0r = wk3r * x1r + wk3i * x1i; + y0i = wk3r * x1i - wk3i * x1r; + y2r = wd3r * x3r + wd3i * x3i; + y2i = wd3r * x3i - wd3i * x3r; + a[j2 - 2] = y0r + y2r; + a[j2 - 1] = y0i + y2i; + a[j3 - 2] = y0r - y2r; + a[j3 - 1] = y0i - y2i; + x0r = a[j0] - a[j2 + 1]; + x0i = a[j0 + 1] + a[j2]; + x1r = a[j0] + a[j2 + 1]; + x1i = a[j0 + 1] - a[j2]; + x2r = a[j1] - a[j3 + 1]; + x2i = a[j1 + 1] + a[j3]; + x3r = a[j1] + a[j3 + 1]; + x3i = a[j1 + 1] - a[j3]; + y0r = wl1r * x0r - wl1i * x0i; + y0i = wl1r * x0i + wl1i * x0r; + y2r = wl1i * x2r - wl1r * x2i; + y2i = wl1i * x2i + wl1r * x2r; + a[j0] = y0r + y2r; + a[j0 + 1] = y0i + y2i; + a[j1] = y0r - y2r; + a[j1 + 1] = y0i - y2i; + y0r = wl1i * x1r - wl1r * x1i; + y0i = wl1i * x1i + wl1r * x1r; + y2r = wl1r * x3r - wl1i * x3i; + y2i = wl1r * x3i + wl1i * x3r; + a[j2] = y0r - y2r; + a[j2 + 1] = y0i - y2i; + a[j3] = y0r + y2r; + a[j3 + 1] = y0i + y2i; + x0r = a[j0 + 2] - a[j2 + 3]; + x0i = a[j0 + 3] + a[j2 + 2]; + x1r = a[j0 + 2] + a[j2 + 3]; + x1i = a[j0 + 3] - a[j2 + 2]; + x2r = a[j1 + 2] - a[j3 + 3]; + x2i = a[j1 + 3] + a[j3 + 2]; + x3r = a[j1 + 2] + a[j3 + 3]; + x3i = a[j1 + 3] - a[j3 + 2]; + y0r = wd1i * x0r - wd1r * x0i; + y0i = wd1i * x0i + wd1r * x0r; + y2r = wk1i * x2r - wk1r * x2i; + y2i = wk1i * x2i + wk1r * x2r; + a[j0 + 2] = y0r + y2r; + a[j0 + 3] = y0i + y2i; + a[j1 + 2] = y0r - y2r; + a[j1 + 3] = y0i - y2i; + y0r = wd3i * x1r + wd3r * x1i; + y0i = wd3i * x1i - wd3r * x1r; + y2r = wk3i * x3r + wk3r * x3i; + y2i = wk3i * x3i - wk3r * x3r; + a[j2 + 2] = y0r + y2r; + a[j2 + 3] = y0i + y2i; + a[j3 + 2] = y0r - y2r; + a[j3 + 3] = y0i - y2i; + } + + void cftf161(float *a) + { + float wn4r, wk1r, wk1i, + x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i, + y8r, y8i, y9r, y9i, y10r, y10i, y11r, y11i, + y12r, y12i, y13r, y13i, y14r, y14i, y15r, y15i; + + wn4r = WR5000; + wk1r = WR2500; + wk1i = WI2500; + x0r = a[0] + a[16]; + x0i = a[1] + a[17]; + x1r = a[0] - a[16]; + x1i = a[1] - a[17]; + x2r = a[8] + a[24]; + x2i = a[9] + a[25]; + x3r = a[8] - a[24]; + x3i = a[9] - a[25]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y4r = x0r - x2r; + y4i = x0i - x2i; + y8r = x1r - x3i; + y8i = x1i + x3r; + y12r = x1r + x3i; + y12i = x1i - x3r; + x0r = a[2] + a[18]; + x0i = a[3] + a[19]; + x1r = a[2] - a[18]; + x1i = a[3] - a[19]; + x2r = a[10] + a[26]; + x2i = a[11] + a[27]; + x3r = a[10] - a[26]; + x3i = a[11] - a[27]; + y1r = x0r + x2r; + y1i = x0i + x2i; + y5r = x0r - x2r; + y5i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + y9r = wk1r * x0r - wk1i * x0i; + y9i = wk1r * x0i + wk1i * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + y13r = wk1i * x0r - wk1r * x0i; + y13i = wk1i * x0i + wk1r * x0r; + x0r = a[4] + a[20]; + x0i = a[5] + a[21]; + x1r = a[4] - a[20]; + x1i = a[5] - a[21]; + x2r = a[12] + a[28]; + x2i = a[13] + a[29]; + x3r = a[12] - a[28]; + x3i = a[13] - a[29]; + y2r = x0r + x2r; + y2i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + y10r = wn4r * (x0r - x0i); + y10i = wn4r * (x0i + x0r); + x0r = x1r + x3i; + x0i = x1i - x3r; + y14r = wn4r * (x0r + x0i); + y14i = wn4r * (x0i - x0r); + x0r = a[6] + a[22]; + x0i = a[7] + a[23]; + x1r = a[6] - a[22]; + x1i = a[7] - a[23]; + x2r = a[14] + a[30]; + x2i = a[15] + a[31]; + x3r = a[14] - a[30]; + x3i = a[15] - a[31]; + y3r = x0r + x2r; + y3i = x0i + x2i; + y7r = x0r - x2r; + y7i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + y11r = wk1i * x0r - wk1r * x0i; + y11i = wk1i * x0i + wk1r * x0r; + x0r = x1r + x3i; + x0i = x1i - x3r; + y15r = wk1r * x0r - wk1i * x0i; + y15i = wk1r * x0i + wk1i * x0r; + x0r = y12r - y14r; + x0i = y12i - y14i; + x1r = y12r + y14r; + x1i = y12i + y14i; + x2r = y13r - y15r; + x2i = y13i - y15i; + x3r = y13r + y15r; + x3i = y13i + y15i; + a[24] = x0r + x2r; + a[25] = x0i + x2i; + a[26] = x0r - x2r; + a[27] = x0i - x2i; + a[28] = x1r - x3i; + a[29] = x1i + x3r; + a[30] = x1r + x3i; + a[31] = x1i - x3r; + x0r = y8r + y10r; + x0i = y8i + y10i; + x1r = y8r - y10r; + x1i = y8i - y10i; + x2r = y9r + y11r; + x2i = y9i + y11i; + x3r = y9r - y11r; + x3i = y9i - y11i; + a[16] = x0r + x2r; + a[17] = x0i + x2i; + a[18] = x0r - x2r; + a[19] = x0i - x2i; + a[20] = x1r - x3i; + a[21] = x1i + x3r; + a[22] = x1r + x3i; + a[23] = x1i - x3r; + x0r = y5r - y7i; + x0i = y5i + y7r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + x0r = y5r + y7i; + x0i = y5i - y7r; + x3r = wn4r * (x0r - x0i); + x3i = wn4r * (x0i + x0r); + x0r = y4r - y6i; + x0i = y4i + y6r; + x1r = y4r + y6i; + x1i = y4i - y6r; + a[8] = x0r + x2r; + a[9] = x0i + x2i; + a[10] = x0r - x2r; + a[11] = x0i - x2i; + a[12] = x1r - x3i; + a[13] = x1i + x3r; + a[14] = x1r + x3i; + a[15] = x1i - x3r; + x0r = y0r + y2r; + x0i = y0i + y2i; + x1r = y0r - y2r; + x1i = y0i - y2i; + x2r = y1r + y3r; + x2i = y1i + y3i; + x3r = y1r - y3r; + x3i = y1i - y3i; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[2] = x0r - x2r; + a[3] = x0i - x2i; + a[4] = x1r - x3i; + a[5] = x1i + x3r; + a[6] = x1r + x3i; + a[7] = x1i - x3r; + } + + + void cftf162(float *a) + { + float wn4r, wk1r, wk1i, wk2r, wk2i, wk3r, wk3i, + x0r, x0i, x1r, x1i, x2r, x2i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i, + y8r, y8i, y9r, y9i, y10r, y10i, y11r, y11i, + y12r, y12i, y13r, y13i, y14r, y14i, y15r, y15i; + + wn4r = WR5000; + wk1r = WR1250; + wk1i = WI1250; + wk2r = WR2500; + wk2i = WI2500; + wk3r = WR3750; + wk3i = WI3750; + x1r = a[0] - a[17]; + x1i = a[1] + a[16]; + x0r = a[8] - a[25]; + x0i = a[9] + a[24]; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + y0r = x1r + x2r; + y0i = x1i + x2i; + y4r = x1r - x2r; + y4i = x1i - x2i; + x1r = a[0] + a[17]; + x1i = a[1] - a[16]; + x0r = a[8] + a[25]; + x0i = a[9] - a[24]; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + y8r = x1r - x2i; + y8i = x1i + x2r; + y12r = x1r + x2i; + y12i = x1i - x2r; + x0r = a[2] - a[19]; + x0i = a[3] + a[18]; + x1r = wk1r * x0r - wk1i * x0i; + x1i = wk1r * x0i + wk1i * x0r; + x0r = a[10] - a[27]; + x0i = a[11] + a[26]; + x2r = wk3i * x0r - wk3r * x0i; + x2i = wk3i * x0i + wk3r * x0r; + y1r = x1r + x2r; + y1i = x1i + x2i; + y5r = x1r - x2r; + y5i = x1i - x2i; + x0r = a[2] + a[19]; + x0i = a[3] - a[18]; + x1r = wk3r * x0r - wk3i * x0i; + x1i = wk3r * x0i + wk3i * x0r; + x0r = a[10] + a[27]; + x0i = a[11] - a[26]; + x2r = wk1r * x0r + wk1i * x0i; + x2i = wk1r * x0i - wk1i * x0r; + y9r = x1r - x2r; + y9i = x1i - x2i; + y13r = x1r + x2r; + y13i = x1i + x2i; + x0r = a[4] - a[21]; + x0i = a[5] + a[20]; + x1r = wk2r * x0r - wk2i * x0i; + x1i = wk2r * x0i + wk2i * x0r; + x0r = a[12] - a[29]; + x0i = a[13] + a[28]; + x2r = wk2i * x0r - wk2r * x0i; + x2i = wk2i * x0i + wk2r * x0r; + y2r = x1r + x2r; + y2i = x1i + x2i; + y6r = x1r - x2r; + y6i = x1i - x2i; + x0r = a[4] + a[21]; + x0i = a[5] - a[20]; + x1r = wk2i * x0r - wk2r * x0i; + x1i = wk2i * x0i + wk2r * x0r; + x0r = a[12] + a[29]; + x0i = a[13] - a[28]; + x2r = wk2r * x0r - wk2i * x0i; + x2i = wk2r * x0i + wk2i * x0r; + y10r = x1r - x2r; + y10i = x1i - x2i; + y14r = x1r + x2r; + y14i = x1i + x2i; + x0r = a[6] - a[23]; + x0i = a[7] + a[22]; + x1r = wk3r * x0r - wk3i * x0i; + x1i = wk3r * x0i + wk3i * x0r; + x0r = a[14] - a[31]; + x0i = a[15] + a[30]; + x2r = wk1i * x0r - wk1r * x0i; + x2i = wk1i * x0i + wk1r * x0r; + y3r = x1r + x2r; + y3i = x1i + x2i; + y7r = x1r - x2r; + y7i = x1i - x2i; + x0r = a[6] + a[23]; + x0i = a[7] - a[22]; + x1r = wk1i * x0r + wk1r * x0i; + x1i = wk1i * x0i - wk1r * x0r; + x0r = a[14] + a[31]; + x0i = a[15] - a[30]; + x2r = wk3i * x0r - wk3r * x0i; + x2i = wk3i * x0i + wk3r * x0r; + y11r = x1r + x2r; + y11i = x1i + x2i; + y15r = x1r - x2r; + y15i = x1i - x2i; + x1r = y0r + y2r; + x1i = y0i + y2i; + x2r = y1r + y3r; + x2i = y1i + y3i; + a[0] = x1r + x2r; + a[1] = x1i + x2i; + a[2] = x1r - x2r; + a[3] = x1i - x2i; + x1r = y0r - y2r; + x1i = y0i - y2i; + x2r = y1r - y3r; + x2i = y1i - y3i; + a[4] = x1r - x2i; + a[5] = x1i + x2r; + a[6] = x1r + x2i; + a[7] = x1i - x2r; + x1r = y4r - y6i; + x1i = y4i + y6r; + x0r = y5r - y7i; + x0i = y5i + y7r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[8] = x1r + x2r; + a[9] = x1i + x2i; + a[10] = x1r - x2r; + a[11] = x1i - x2i; + x1r = y4r + y6i; + x1i = y4i - y6r; + x0r = y5r + y7i; + x0i = y5i - y7r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[12] = x1r - x2i; + a[13] = x1i + x2r; + a[14] = x1r + x2i; + a[15] = x1i - x2r; + x1r = y8r + y10r; + x1i = y8i + y10i; + x2r = y9r - y11r; + x2i = y9i - y11i; + a[16] = x1r + x2r; + a[17] = x1i + x2i; + a[18] = x1r - x2r; + a[19] = x1i - x2i; + x1r = y8r - y10r; + x1i = y8i - y10i; + x2r = y9r + y11r; + x2i = y9i + y11i; + a[20] = x1r - x2i; + a[21] = x1i + x2r; + a[22] = x1r + x2i; + a[23] = x1i - x2r; + x1r = y12r - y14i; + x1i = y12i + y14r; + x0r = y13r + y15i; + x0i = y13i - y15r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[24] = x1r + x2r; + a[25] = x1i + x2i; + a[26] = x1r - x2r; + a[27] = x1i - x2i; + x1r = y12r + y14i; + x1i = y12i - y14r; + x0r = y13r - y15i; + x0i = y13i + y15r; + x2r = wn4r * (x0r - x0i); + x2i = wn4r * (x0i + x0r); + a[28] = x1r - x2i; + a[29] = x1i + x2r; + a[30] = x1r + x2i; + a[31] = x1i - x2r; + } + + + void cftf081(float *a) + { + float wn4r, x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i; + + wn4r = WR5000; + x0r = a[0] + a[8]; + x0i = a[1] + a[9]; + x1r = a[0] - a[8]; + x1i = a[1] - a[9]; + x2r = a[4] + a[12]; + x2i = a[5] + a[13]; + x3r = a[4] - a[12]; + x3i = a[5] - a[13]; + y0r = x0r + x2r; + y0i = x0i + x2i; + y2r = x0r - x2r; + y2i = x0i - x2i; + y1r = x1r - x3i; + y1i = x1i + x3r; + y3r = x1r + x3i; + y3i = x1i - x3r; + x0r = a[2] + a[10]; + x0i = a[3] + a[11]; + x1r = a[2] - a[10]; + x1i = a[3] - a[11]; + x2r = a[6] + a[14]; + x2i = a[7] + a[15]; + x3r = a[6] - a[14]; + x3i = a[7] - a[15]; + y4r = x0r + x2r; + y4i = x0i + x2i; + y6r = x0r - x2r; + y6i = x0i - x2i; + x0r = x1r - x3i; + x0i = x1i + x3r; + x2r = x1r + x3i; + x2i = x1i - x3r; + y5r = wn4r * (x0r - x0i); + y5i = wn4r * (x0r + x0i); + y7r = wn4r * (x2r - x2i); + y7i = wn4r * (x2r + x2i); + a[8] = y1r + y5r; + a[9] = y1i + y5i; + a[10] = y1r - y5r; + a[11] = y1i - y5i; + a[12] = y3r - y7i; + a[13] = y3i + y7r; + a[14] = y3r + y7i; + a[15] = y3i - y7r; + a[0] = y0r + y4r; + a[1] = y0i + y4i; + a[2] = y0r - y4r; + a[3] = y0i - y4i; + a[4] = y2r - y6i; + a[5] = y2i + y6r; + a[6] = y2r + y6i; + a[7] = y2i - y6r; + } + + + void cftf082(float *a) + { + float wn4r, wk1r, wk1i, x0r, x0i, x1r, x1i, + y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i, + y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i; + + wn4r = WR5000; + wk1r = WR2500; + wk1i = WI2500; + y0r = a[0] - a[9]; + y0i = a[1] + a[8]; + y1r = a[0] + a[9]; + y1i = a[1] - a[8]; + x0r = a[4] - a[13]; + x0i = a[5] + a[12]; + y2r = wn4r * (x0r - x0i); + y2i = wn4r * (x0i + x0r); + x0r = a[4] + a[13]; + x0i = a[5] - a[12]; + y3r = wn4r * (x0r - x0i); + y3i = wn4r * (x0i + x0r); + x0r = a[2] - a[11]; + x0i = a[3] + a[10]; + y4r = wk1r * x0r - wk1i * x0i; + y4i = wk1r * x0i + wk1i * x0r; + x0r = a[2] + a[11]; + x0i = a[3] - a[10]; + y5r = wk1i * x0r - wk1r * x0i; + y5i = wk1i * x0i + wk1r * x0r; + x0r = a[6] - a[15]; + x0i = a[7] + a[14]; + y6r = wk1i * x0r - wk1r * x0i; + y6i = wk1i * x0i + wk1r * x0r; + x0r = a[6] + a[15]; + x0i = a[7] - a[14]; + y7r = wk1r * x0r - wk1i * x0i; + y7i = wk1r * x0i + wk1i * x0r; + x0r = y0r + y2r; + x0i = y0i + y2i; + x1r = y4r + y6r; + x1i = y4i + y6i; + a[0] = x0r + x1r; + a[1] = x0i + x1i; + a[2] = x0r - x1r; + a[3] = x0i - x1i; + x0r = y0r - y2r; + x0i = y0i - y2i; + x1r = y4r - y6r; + x1i = y4i - y6i; + a[4] = x0r - x1i; + a[5] = x0i + x1r; + a[6] = x0r + x1i; + a[7] = x0i - x1r; + x0r = y1r - y3i; + x0i = y1i + y3r; + x1r = y5r - y7r; + x1i = y5i - y7i; + a[8] = x0r + x1r; + a[9] = x0i + x1i; + a[10] = x0r - x1r; + a[11] = x0i - x1i; + x0r = y1r + y3i; + x0i = y1i - y3r; + x1r = y5r + y7r; + x1i = y5i + y7i; + a[12] = x0r - x1i; + a[13] = x0i + x1r; + a[14] = x0r + x1i; + a[15] = x0i - x1r; + } + + void cftf040(float *a) + { + float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + x0r = a[0] + a[4]; + x0i = a[1] + a[5]; + x1r = a[0] - a[4]; + x1i = a[1] - a[5]; + x2r = a[2] + a[6]; + x2i = a[3] + a[7]; + x3r = a[2] - a[6]; + x3i = a[3] - a[7]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[2] = x1r - x3i; + a[3] = x1i + x3r; + a[4] = x0r - x2r; + a[5] = x0i - x2i; + a[6] = x1r + x3i; + a[7] = x1i - x3r; + } + + void cftb040(float *a) + { + float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; + + x0r = a[0] + a[4]; + x0i = a[1] + a[5]; + x1r = a[0] - a[4]; + x1i = a[1] - a[5]; + x2r = a[2] + a[6]; + x2i = a[3] + a[7]; + x3r = a[2] - a[6]; + x3i = a[3] - a[7]; + a[0] = x0r + x2r; + a[1] = x0i + x2i; + a[2] = x1r + x3i; + a[3] = x1i - x3r; + a[4] = x0r - x2r; + a[5] = x0i - x2i; + a[6] = x1r - x3i; + a[7] = x1i + x3r; + } + + void cftx020(float *a) + { + float x0r, x0i; + + x0r = a[0] - a[2]; + x0i = a[1] - a[3]; + a[0] += a[2]; + a[1] += a[3]; + a[2] = x0r; + a[3] = x0i; + } + + void cftfx41(int n, float *a) + { + if (n == 128) { + cftf161(a); + cftf162(&a[32]); + cftf161(&a[64]); + cftf161(&a[96]); + } + else { + cftf081(a); + cftf082(&a[16]); + cftf081(&a[32]); + cftf081(&a[48]); + } + } + + int cfttree(int n, int j, int k, float *a) + { + int i, isplt, m; + + if ((k & 3) != 0) { + isplt = k & 1; + if (isplt != 0) { + cftmdl1(n, &a[j - n]); + } + else { + cftmdl2(n, &a[j - n]); + } + } + else { + m = n; + for (i = k; (i & 3) == 0; i >>= 2) { + m <<= 2; + } + isplt = i & 1; + if (isplt != 0) { + while (m > 128) { + cftmdl1(m, &a[j - m]); + m >>= 2; + } + } + else { + while (m > 128) { + cftmdl2(m, &a[j - m]); + m >>= 2; + } + } + } + return isplt; + } + + void cftleaf(int n, int isplt, float *a) + { + if (n == 512) { + cftmdl1(128, a); + cftf161(a); + cftf162(&a[32]); + cftf161(&a[64]); + cftf161(&a[96]); + cftmdl2(128, &a[128]); + cftf161(&a[128]); + cftf162(&a[160]); + cftf161(&a[192]); + cftf162(&a[224]); + cftmdl1(128, &a[256]); + cftf161(&a[256]); + cftf162(&a[288]); + cftf161(&a[320]); + cftf161(&a[352]); + if (isplt != 0) { + cftmdl1(128, &a[384]); + cftf161(&a[480]); + } + else { + cftmdl2(128, &a[384]); + cftf162(&a[480]); + } + cftf161(&a[384]); + cftf162(&a[416]); + cftf161(&a[448]); + } + else { + cftmdl1(64, a); + cftf081(a); + cftf082(&a[16]); + cftf081(&a[32]); + cftf081(&a[48]); + cftmdl2(64, &a[64]); + cftf081(&a[64]); + cftf082(&a[80]); + cftf081(&a[96]); + cftf082(&a[112]); + cftmdl1(64, &a[128]); + cftf081(&a[128]); + cftf082(&a[144]); + cftf081(&a[160]); + cftf081(&a[176]); + if (isplt != 0) { + cftmdl1(64, &a[192]); + cftf081(&a[240]); + } + else { + cftmdl2(64, &a[192]); + cftf082(&a[240]); + } + cftf081(&a[192]); + cftf082(&a[208]); + cftf081(&a[224]); + } + } + + void cftrec4(int n, float *a) + { + int isplt, j, k, m; + + m = n; + while (m > 512) { + m >>= 2; + cftmdl1(m, &a[n - m]); + } + cftleaf(m, 1, &a[n - m]); + k = 0; + for (j = n - m; j > 0; j -= m) { + k++; + isplt = cfttree(m, j, k, a); + cftleaf(m, isplt, &a[j - m]); + } + } + + void cftfsub(int n, float *a) + { + if (n > 8) { + if (n > 32) { + cftmdl1(n, a); + if (n > 512) { + cftrec4(n, a); + } + else if (n > 128) { + cftleaf(n, 1, a); + } + else { + cftfx41(n, a); + } + bitrv2(n, a); + } + else if (n == 32) { + cftf161(a); + bitrv216(a); + } + else { + cftf081(a); + bitrv208(a); + } + } + else if (n == 8) { + cftf040(a); + } + else if (n == 4) { + cftx020(a); + } + } + + + void cftbsub(int n, float *a) + { + if (n > 8) { + if (n > 32) { + cftb1st(n, a); + if (n > 512) { + cftrec4(n, a); + } + else if (n > 128) { + cftleaf(n, 1, a); + } + else { + cftfx41(n, a); + } + bitrv2conj(n, a); + } + else if (n == 32) { + cftf161(a); + bitrv216neg(a); + } + else { + cftf081(a); + bitrv208neg(a); + } + } + else if (n == 8) { + cftb040(a); + } + else if (n == 4) { + cftx020(a); + } + } +} // fftimpl + + +namespace SoLoud +{ + namespace FFT + { + void fft1024(float *aBuffer) + { + fft(aBuffer, 1024); + } + + void fft256(float *aBuffer) + { + fft(aBuffer, 256); + } + + void ifft256(float *aBuffer) + { + ifft(aBuffer, 256); + } + + void fft(float *aBuffer, unsigned int aBufferLength) + { + fftimpl::cftbsub(aBufferLength, aBuffer); + } + + void ifft(float *aBuffer, unsigned int aBufferLength) + { + unsigned int i; + fftimpl::cftfsub(aBufferLength, aBuffer); + for (i = 0; i < aBufferLength; i++) + aBuffer[i] *= 1.0f / (aBufferLength / 2); + } + }; +}; diff --git a/src/soloud/src/core/soloud_fft_lut.cpp b/src/soloud/src/core/soloud_fft_lut.cpp new file mode 100644 index 0000000..cfbefa5 --- /dev/null +++ b/src/soloud/src/core/soloud_fft_lut.cpp @@ -0,0 +1,339 @@ +/* ************************************************** + * WARNING: this is a generated file. Do not edit. * + * Any edits will be overwritten by the generator. * + ************************************************** */ + +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* SoLoud Lookup Table Generator (c)2015 Jari Komppa http://iki.fi/sol/ */ + +int Soloud_fft_bitrev_10[1024] = { +0, 512, 256, 768, 128, 640, 384, 896, 64, 576, 320, 832, 192, 704, 448, +960, 32, 544, 288, 800, 160, 672, 416, 928, 96, 608, 352, 864, 224, 736, +480, 992, 16, 528, 272, 784, 144, 656, 400, 912, 80, 592, 336, 848, 208, +720, 464, 976, 48, 560, 304, 816, 176, 688, 432, 944, 112, 624, 368, 880, +240, 752, 496, 1008, 8, 520, 264, 776, 136, 648, 392, 904, 72, 584, 328, +840, 200, 712, 456, 968, 40, 552, 296, 808, 168, 680, 424, 936, 104, 616, +360, 872, 232, 744, 488, 1000, 24, 536, 280, 792, 152, 664, 408, 920, 88, +600, 344, 856, 216, 728, 472, 984, 56, 568, 312, 824, 184, 696, 440, 952, +120, 632, 376, 888, 248, 760, 504, 1016, 4, 516, 260, 772, 132, 644, 388, +900, 68, 580, 324, 836, 196, 708, 452, 964, 36, 548, 292, 804, 164, 676, +420, 932, 100, 612, 356, 868, 228, 740, 484, 996, 20, 532, 276, 788, 148, +660, 404, 916, 84, 596, 340, 852, 212, 724, 468, 980, 52, 564, 308, 820, +180, 692, 436, 948, 116, 628, 372, 884, 244, 756, 500, 1012, 12, 524, 268, +780, 140, 652, 396, 908, 76, 588, 332, 844, 204, 716, 460, 972, 44, 556, +300, 812, 172, 684, 428, 940, 108, 620, 364, 876, 236, 748, 492, 1004, +28, 540, 284, 796, 156, 668, 412, 924, 92, 604, 348, 860, 220, 732, 476, +988, 60, 572, 316, 828, 188, 700, 444, 956, 124, 636, 380, 892, 252, 764, +508, 1020, 2, 514, 258, 770, 130, 642, 386, 898, 66, 578, 322, 834, 194, +706, 450, 962, 34, 546, 290, 802, 162, 674, 418, 930, 98, 610, 354, 866, +226, 738, 482, 994, 18, 530, 274, 786, 146, 658, 402, 914, 82, 594, 338, +850, 210, 722, 466, 978, 50, 562, 306, 818, 178, 690, 434, 946, 114, 626, +370, 882, 242, 754, 498, 1010, 10, 522, 266, 778, 138, 650, 394, 906, 74, +586, 330, 842, 202, 714, 458, 970, 42, 554, 298, 810, 170, 682, 426, 938, +106, 618, 362, 874, 234, 746, 490, 1002, 26, 538, 282, 794, 154, 666, 410, +922, 90, 602, 346, 858, 218, 730, 474, 986, 58, 570, 314, 826, 186, 698, +442, 954, 122, 634, 378, 890, 250, 762, 506, 1018, 6, 518, 262, 774, 134, +646, 390, 902, 70, 582, 326, 838, 198, 710, 454, 966, 38, 550, 294, 806, +166, 678, 422, 934, 102, 614, 358, 870, 230, 742, 486, 998, 22, 534, 278, +790, 150, 662, 406, 918, 86, 598, 342, 854, 214, 726, 470, 982, 54, 566, +310, 822, 182, 694, 438, 950, 118, 630, 374, 886, 246, 758, 502, 1014, +14, 526, 270, 782, 142, 654, 398, 910, 78, 590, 334, 846, 206, 718, 462, +974, 46, 558, 302, 814, 174, 686, 430, 942, 110, 622, 366, 878, 238, 750, +494, 1006, 30, 542, 286, 798, 158, 670, 414, 926, 94, 606, 350, 862, 222, +734, 478, 990, 62, 574, 318, 830, 190, 702, 446, 958, 126, 638, 382, 894, +254, 766, 510, 1022, 1, 513, 257, 769, 129, 641, 385, 897, 65, 577, 321, +833, 193, 705, 449, 961, 33, 545, 289, 801, 161, 673, 417, 929, 97, 609, +353, 865, 225, 737, 481, 993, 17, 529, 273, 785, 145, 657, 401, 913, 81, +593, 337, 849, 209, 721, 465, 977, 49, 561, 305, 817, 177, 689, 433, 945, +113, 625, 369, 881, 241, 753, 497, 1009, 9, 521, 265, 777, 137, 649, 393, +905, 73, 585, 329, 841, 201, 713, 457, 969, 41, 553, 297, 809, 169, 681, +425, 937, 105, 617, 361, 873, 233, 745, 489, 1001, 25, 537, 281, 793, 153, +665, 409, 921, 89, 601, 345, 857, 217, 729, 473, 985, 57, 569, 313, 825, +185, 697, 441, 953, 121, 633, 377, 889, 249, 761, 505, 1017, 5, 517, 261, +773, 133, 645, 389, 901, 69, 581, 325, 837, 197, 709, 453, 965, 37, 549, +293, 805, 165, 677, 421, 933, 101, 613, 357, 869, 229, 741, 485, 997, 21, +533, 277, 789, 149, 661, 405, 917, 85, 597, 341, 853, 213, 725, 469, 981, +53, 565, 309, 821, 181, 693, 437, 949, 117, 629, 373, 885, 245, 757, 501, +1013, 13, 525, 269, 781, 141, 653, 397, 909, 77, 589, 333, 845, 205, 717, +461, 973, 45, 557, 301, 813, 173, 685, 429, 941, 109, 621, 365, 877, 237, +749, 493, 1005, 29, 541, 285, 797, 157, 669, 413, 925, 93, 605, 349, 861, +221, 733, 477, 989, 61, 573, 317, 829, 189, 701, 445, 957, 125, 637, 381, +893, 253, 765, 509, 1021, 3, 515, 259, 771, 131, 643, 387, 899, 67, 579, +323, 835, 195, 707, 451, 963, 35, 547, 291, 803, 163, 675, 419, 931, 99, +611, 355, 867, 227, 739, 483, 995, 19, 531, 275, 787, 147, 659, 403, 915, +83, 595, 339, 851, 211, 723, 467, 979, 51, 563, 307, 819, 179, 691, 435, +947, 115, 627, 371, 883, 243, 755, 499, 1011, 11, 523, 267, 779, 139, 651, +395, 907, 75, 587, 331, 843, 203, 715, 459, 971, 43, 555, 299, 811, 171, +683, 427, 939, 107, 619, 363, 875, 235, 747, 491, 1003, 27, 539, 283, 795, +155, 667, 411, 923, 91, 603, 347, 859, 219, 731, 475, 987, 59, 571, 315, +827, 187, 699, 443, 955, 123, 635, 379, 891, 251, 763, 507, 1019, 7, 519, +263, 775, 135, 647, 391, 903, 71, 583, 327, 839, 199, 711, 455, 967, 39, +551, 295, 807, 167, 679, 423, 935, 103, 615, 359, 871, 231, 743, 487, 999, +23, 535, 279, 791, 151, 663, 407, 919, 87, 599, 343, 855, 215, 727, 471, +983, 55, 567, 311, 823, 183, 695, 439, 951, 119, 631, 375, 887, 247, 759, +503, 1015, 15, 527, 271, 783, 143, 655, 399, 911, 79, 591, 335, 847, 207, +719, 463, 975, 47, 559, 303, 815, 175, 687, 431, 943, 111, 623, 367, 879, +239, 751, 495, 1007, 31, 543, 287, 799, 159, 671, 415, 927, 95, 607, 351, +863, 223, 735, 479, 991, 63, 575, 319, 831, 191, 703, 447, 959, 127, 639, +383, 895, 255, 767, 511, 1023}; + +int Soloud_fft_bitrev_8[256] = { +0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240, +8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248, +4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244, +12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, +2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242, +10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, +6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, +14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254, +1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, +9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, +5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, +13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, +3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243, +11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251, +7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247, +15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255 +}; + +float Soloud_fft_trig_10[508] = { +1.000000000000000000f, 0.923879504203796390f, 0.707106769084930420f, +0.382683396339416500f, 1.000000000000000000f, 0.980785250663757320f, +0.923879504203796390f, 0.831469595432281490f, 0.707106769084930420f, +0.555570185184478760f, 0.382683396339416500f, 0.195090278983116150f, +1.000000000000000000f, 0.995184719562530520f, 0.980785250663757320f, +0.956940352916717530f, 0.923879504203796390f, 0.881921231746673580f, +0.831469595432281490f, 0.773010432720184330f, 0.707106769084930420f, +0.634393274784088130f, 0.555570185184478760f, 0.471396714448928830f, +0.382683396339416500f, 0.290284633636474610f, 0.195090278983116150f, +0.098017096519470215f, 1.000000000000000000f, 0.998795449733734130f, +0.995184719562530520f, 0.989176511764526370f, 0.980785250663757320f, +0.970031261444091800f, 0.956940352916717530f, 0.941544055938720700f, +0.923879504203796390f, 0.903989315032958980f, 0.881921231746673580f, +0.857728600502014160f, 0.831469595432281490f, 0.803207516670227050f, +0.773010432720184330f, 0.740951120853424070f, 0.707106769084930420f, +0.671558916568756100f, 0.634393274784088130f, 0.595699310302734380f, +0.555570185184478760f, 0.514102697372436520f, 0.471396714448928830f, +0.427555054426193240f, 0.382683396339416500f, 0.336889833211898800f, +0.290284633636474610f, 0.242980137467384340f, 0.195090278983116150f, +0.146730437874794010f, 0.098017096519470215f, 0.049067631363868713f, +1.000000000000000000f, 0.999698817729949950f, 0.998795449733734130f, +0.997290432453155520f, 0.995184719562530520f, 0.992479562759399410f, +0.989176511764526370f, 0.985277652740478520f, 0.980785250663757320f, +0.975702106952667240f, 0.970031261444091800f, 0.963776051998138430f, +0.956940352916717530f, 0.949528157711029050f, 0.941544055938720700f, +0.932992815971374510f, 0.923879504203796390f, 0.914209723472595210f, +0.903989315032958980f, 0.893224298954010010f, 0.881921231746673580f, +0.870086967945098880f, 0.857728600502014160f, 0.844853579998016360f, +0.831469595432281490f, 0.817584812641143800f, 0.803207516670227050f, +0.788346409797668460f, 0.773010432720184330f, 0.757208824157714840f, +0.740951120853424070f, 0.724247097969055180f, 0.707106769084930420f, +0.689540505409240720f, 0.671558916568756100f, 0.653172850608825680f, +0.634393274784088130f, 0.615231573581695560f, 0.595699310302734380f, +0.575808167457580570f, 0.555570185184478760f, 0.534997582435607910f, +0.514102697372436520f, 0.492898166179656980f, 0.471396714448928830f, +0.449611306190490720f, 0.427555054426193240f, 0.405241280794143680f, +0.382683396339416500f, 0.359894990921020510f, 0.336889833211898800f, +0.313681721687316890f, 0.290284633636474610f, 0.266712725162506100f, +0.242980137467384340f, 0.219101205468177800f, 0.195090278983116150f, +0.170961856842041020f, 0.146730437874794010f, 0.122410632669925690f, +0.098017096519470215f, 0.073564521968364716f, 0.049067631363868713f, +0.024541186168789864f, 1.000000000000000000f, 0.999924719333648680f, +0.999698817729949950f, 0.999322354793548580f, 0.998795449733734130f, +0.998118102550506590f, 0.997290432453155520f, 0.996312618255615230f, +0.995184719562530520f, 0.993906974792480470f, 0.992479562759399410f, +0.990902662277221680f, 0.989176511764526370f, 0.987301409244537350f, +0.985277652740478520f, 0.983105480670928960f, 0.980785250663757320f, +0.978317379951477050f, 0.975702106952667240f, 0.972939968109130860f, +0.970031261444091800f, 0.966976463794708250f, 0.963776051998138430f, +0.960430502891540530f, 0.956940352916717530f, 0.953306019306182860f, +0.949528157711029050f, 0.945607304573059080f, 0.941544055938720700f, +0.937339007854461670f, 0.932992815971374510f, 0.928506076335906980f, +0.923879504203796390f, 0.919113874435424800f, 0.914209723472595210f, +0.909168004989624020f, 0.903989315032958980f, 0.898674488067626950f, +0.893224298954010010f, 0.887639641761779790f, 0.881921231746673580f, +0.876070082187652590f, 0.870086967945098880f, 0.863972842693328860f, +0.857728600502014160f, 0.851355195045471190f, 0.844853579998016360f, +0.838224709033966060f, 0.831469595432281490f, 0.824589312076568600f, +0.817584812641143800f, 0.810457170009613040f, 0.803207516670227050f, +0.795836865901947020f, 0.788346409797668460f, 0.780737221240997310f, +0.773010432720184330f, 0.765167236328125000f, 0.757208824157714840f, +0.749136388301849370f, 0.740951120853424070f, 0.732654273509979250f, +0.724247097969055180f, 0.715730786323547360f, 0.707106769084930420f, +0.698376238346099850f, 0.689540505409240720f, 0.680601000785827640f, +0.671558916568756100f, 0.662415742874145510f, 0.653172850608825680f, +0.643831551074981690f, 0.634393274784088130f, 0.624859452247619630f, +0.615231573581695560f, 0.605511009693145750f, 0.595699310302734380f, +0.585797846317291260f, 0.575808167457580570f, 0.565731763839721680f, +0.555570185184478760f, 0.545324981212615970f, 0.534997582435607910f, +0.524589657783508300f, 0.514102697372436520f, 0.503538370132446290f, +0.492898166179656980f, 0.482183754444122310f, 0.471396714448928830f, +0.460538685321807860f, 0.449611306190490720f, 0.438616216182708740f, +0.427555054426193240f, 0.416429519653320310f, 0.405241280794143680f, +0.393992006778717040f, 0.382683396339416500f, 0.371317178010940550f, +0.359894990921020510f, 0.348418653011322020f, 0.336889833211898800f, +0.325310260057449340f, 0.313681721687316890f, 0.302005916833877560f, +0.290284633636474610f, 0.278519660234451290f, 0.266712725162506100f, +0.254865616559982300f, 0.242980137467384340f, 0.231058076024055480f, +0.219101205468177800f, 0.207111343741416930f, 0.195090278983116150f, +0.183039844036102290f, 0.170961856842041020f, 0.158858105540275570f, +0.146730437874794010f, 0.134580671787261960f, 0.122410632669925690f, +0.110222168266773220f, 0.098017096519470215f, 0.085797272622585297f, +0.073564521968364716f, 0.061320696026086807f, 0.049067631363868713f, +0.036807179450988770f, 0.024541186168789864f, 0.012271494604647160f, +1.000000000000000000f, 0.999981164932250980f, 0.999924719333648680f, +0.999830603599548340f, 0.999698817729949950f, 0.999529421329498290f, +0.999322354793548580f, 0.999077737331390380f, 0.998795449733734130f, +0.998475551605224610f, 0.998118102550506590f, 0.997723042964935300f, +0.997290432453155520f, 0.996820271015167240f, 0.996312618255615230f, +0.995767414569854740f, 0.995184719562530520f, 0.994564592838287350f, +0.993906974792480470f, 0.993211925029754640f, 0.992479562759399410f, +0.991709768772125240f, 0.990902662277221680f, 0.990058183670043950f, +0.989176511764526370f, 0.988257586956024170f, 0.987301409244537350f, +0.986308097839355470f, 0.985277652740478520f, 0.984210073947906490f, +0.983105480670928960f, 0.981963872909545900f, 0.980785250663757320f, +0.979569792747497560f, 0.978317379951477050f, 0.977028131484985350f, +0.975702106952667240f, 0.974339365959167480f, 0.972939968109130860f, +0.971503913402557370f, 0.970031261444091800f, 0.968522071838378910f, +0.966976463794708250f, 0.965394437313079830f, 0.963776051998138430f, +0.962121427059173580f, 0.960430502891540530f, 0.958703458309173580f, +0.956940352916717530f, 0.955141186714172360f, 0.953306019306182860f, +0.951435029506683350f, 0.949528157711029050f, 0.947585582733154300f, +0.945607304573059080f, 0.943593442440032960f, 0.941544055938720700f, +0.939459204673767090f, 0.937339007854461670f, 0.935183525085449220f, +0.932992815971374510f, 0.930766940116882320f, 0.928506076335906980f, +0.926210224628448490f, 0.923879504203796390f, 0.921514034271240230f, +0.919113874435424800f, 0.916679084300994870f, 0.914209723472595210f, +0.911706030368804930f, 0.909168004989624020f, 0.906595706939697270f, +0.903989315032958980f, 0.901348829269409180f, 0.898674488067626950f, +0.895966231822967530f, 0.893224298954010010f, 0.890448689460754390f, +0.887639641761779790f, 0.884797096252441410f, 0.881921231746673580f, +0.879012227058410640f, 0.876070082187652590f, 0.873094975948333740f, +0.870086967945098880f, 0.867046236991882320f, 0.863972842693328860f, +0.860866904258728030f, 0.857728600502014160f, 0.854557991027832030f, +0.851355195045471190f, 0.848120331764221190f, 0.844853579998016360f, +0.841554939746856690f, 0.838224709033966060f, 0.834862887859344480f, +0.831469595432281490f, 0.828045010566711430f, 0.824589312076568600f, +0.821102499961853030f, 0.817584812641143800f, 0.814036309719085690f, +0.810457170009613040f, 0.806847572326660160f, 0.803207516670227050f, +0.799537241458892820f, 0.795836865901947020f, 0.792106568813323970f, +0.788346409797668460f, 0.784556567668914790f, 0.780737221240997310f, +0.776888430118560790f, 0.773010432720184330f, 0.769103348255157470f, +0.765167236328125000f, 0.761202394962310790f, 0.757208824157714840f, +0.753186762332916260f, 0.749136388301849370f, 0.745057761669158940f, +0.740951120853424070f, 0.736816525459289550f, 0.732654273509979250f, +0.728464365005493160f, 0.724247097969055180f, 0.720002472400665280f, +0.715730786323547360f, 0.711432158946990970f, 0.707106769084930420f, +0.702754735946655270f, 0.698376238346099850f, 0.693971455097198490f, +0.689540505409240720f, 0.685083627700805660f, 0.680601000785827640f, +0.676092684268951420f, 0.671558916568756100f, 0.666999876499176030f, +0.662415742874145510f, 0.657806694507598880f, 0.653172850608825680f, +0.648514389991760250f, 0.643831551074981690f, 0.639124453067779540f, +0.634393274784088130f, 0.629638195037841800f, 0.624859452247619630f, +0.620057165622711180f, 0.615231573581695560f, 0.610382795333862300f, +0.605511009693145750f, 0.600616455078125000f, 0.595699310302734380f, +0.590759694576263430f, 0.585797846317291260f, 0.580813944339752200f, +0.575808167457580570f, 0.570780694484710690f, 0.565731763839721680f, +0.560661554336547850f, 0.555570185184478760f, 0.550457954406738280f, +0.545324981212615970f, 0.540171444416046140f, 0.534997582435607910f, +0.529803574085235600f, 0.524589657783508300f, 0.519355952739715580f, +0.514102697372436520f, 0.508830130100250240f, 0.503538370132446290f, +0.498227655887603760f, 0.492898166179656980f, 0.487550139427185060f, +0.482183754444122310f, 0.476799190044403080f, 0.471396714448928830f, +0.465976476669311520f, 0.460538685321807860f, 0.455083549022674560f, +0.449611306190490720f, 0.444122105836868290f, 0.438616216182708740f, +0.433093786239624020f, 0.427555054426193240f, 0.422000229358673100f, +0.416429519653320310f, 0.410843133926391600f, 0.405241280794143680f, +0.399624168872833250f, 0.393992006778717040f, 0.388345003128051760f, +0.382683396339416500f, 0.377007365226745610f, 0.371317178010940550f, +0.365612953901290890f, 0.359894990921020510f, 0.354163497686386110f, +0.348418653011322020f, 0.342660695314407350f, 0.336889833211898800f, +0.331106275320053100f, 0.325310260057449340f, 0.319501996040344240f, +0.313681721687316890f, 0.307849615812301640f, 0.302005916833877560f, +0.296150863170623780f, 0.290284633636474610f, 0.284407496452331540f, +0.278519660234451290f, 0.272621333599090580f, 0.266712725162506100f, +0.260794073343276980f, 0.254865616559982300f, 0.248927563428878780f, +0.242980137467384340f, 0.237023577094078060f, 0.231058076024055480f, +0.225083872675895690f, 0.219101205468177800f, 0.213110283017158510f, +0.207111343741416930f, 0.201104596257209780f, 0.195090278983116150f, +0.189068630337715150f, 0.183039844036102290f, 0.177004188299179080f, +0.170961856842041020f, 0.164913088083267210f, 0.158858105540275570f, +0.152797147631645200f, 0.146730437874794010f, 0.140658199787139890f, +0.134580671787261960f, 0.128498077392578130f, 0.122410632669925690f, +0.116318590939044950f, 0.110222168266773220f, 0.104121595621109010f, +0.098017096519470215f, 0.091908916831016541f, 0.085797272622585297f, +0.079682394862174988f, 0.073564521968364716f, 0.067443877458572388f, +0.061320696026086807f, 0.055195201188325882f, 0.049067631363868713f, +0.042938213795423508f, 0.036807179450988770f, 0.030674761161208153f, +0.024541186168789864f, 0.018406687304377556f, 0.012271494604647160f, +0.006135840900242329f +}; + +float Soloud_fft_trig_8[124] = { +1.000000000000000000f, 0.923879504203796390f, 0.707106769084930420f, +0.382683396339416500f, 1.000000000000000000f, 0.980785250663757320f, +0.923879504203796390f, 0.831469595432281490f, 0.707106769084930420f, +0.555570185184478760f, 0.382683396339416500f, 0.195090278983116150f, +1.000000000000000000f, 0.995184719562530520f, 0.980785250663757320f, +0.956940352916717530f, 0.923879504203796390f, 0.881921231746673580f, +0.831469595432281490f, 0.773010432720184330f, 0.707106769084930420f, +0.634393274784088130f, 0.555570185184478760f, 0.471396714448928830f, +0.382683396339416500f, 0.290284633636474610f, 0.195090278983116150f, +0.098017096519470215f, 1.000000000000000000f, 0.998795449733734130f, +0.995184719562530520f, 0.989176511764526370f, 0.980785250663757320f, +0.970031261444091800f, 0.956940352916717530f, 0.941544055938720700f, +0.923879504203796390f, 0.903989315032958980f, 0.881921231746673580f, +0.857728600502014160f, 0.831469595432281490f, 0.803207516670227050f, +0.773010432720184330f, 0.740951120853424070f, 0.707106769084930420f, +0.671558916568756100f, 0.634393274784088130f, 0.595699310302734380f, +0.555570185184478760f, 0.514102697372436520f, 0.471396714448928830f, +0.427555054426193240f, 0.382683396339416500f, 0.336889833211898800f, +0.290284633636474610f, 0.242980137467384340f, 0.195090278983116150f, +0.146730437874794010f, 0.098017096519470215f, 0.049067631363868713f, +1.000000000000000000f, 0.999698817729949950f, 0.998795449733734130f, +0.997290432453155520f, 0.995184719562530520f, 0.992479562759399410f, +0.989176511764526370f, 0.985277652740478520f, 0.980785250663757320f, +0.975702106952667240f, 0.970031261444091800f, 0.963776051998138430f, +0.956940352916717530f, 0.949528157711029050f, 0.941544055938720700f, +0.932992815971374510f, 0.923879504203796390f, 0.914209723472595210f, +0.903989315032958980f, 0.893224298954010010f, 0.881921231746673580f, +0.870086967945098880f, 0.857728600502014160f, 0.844853579998016360f, +0.831469595432281490f, 0.817584812641143800f, 0.803207516670227050f, +0.788346409797668460f, 0.773010432720184330f, 0.757208824157714840f, +0.740951120853424070f, 0.724247097969055180f, 0.707106769084930420f, +0.689540505409240720f, 0.671558916568756100f, 0.653172850608825680f, +0.634393274784088130f, 0.615231573581695560f, 0.595699310302734380f, +0.575808167457580570f, 0.555570185184478760f, 0.534997582435607910f, +0.514102697372436520f, 0.492898166179656980f, 0.471396714448928830f, +0.449611306190490720f, 0.427555054426193240f, 0.405241280794143680f, +0.382683396339416500f, 0.359894990921020510f, 0.336889833211898800f, +0.313681721687316890f, 0.290284633636474610f, 0.266712725162506100f, +0.242980137467384340f, 0.219101205468177800f, 0.195090278983116150f, +0.170961856842041020f, 0.146730437874794010f, 0.122410632669925690f, +0.098017096519470215f, 0.073564521968364716f, 0.049067631363868713f, +0.024541186168789864f +}; + diff --git a/src/soloud/src/core/soloud_file.cpp b/src/soloud/src/core/soloud_file.cpp new file mode 100644 index 0000000..5568fce --- /dev/null +++ b/src/soloud/src/core/soloud_file.cpp @@ -0,0 +1,305 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#undef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS + +#include +#include +#include "soloud.h" +#include "soloud_file.h" + +namespace SoLoud +{ + unsigned int File::read8() + { + unsigned char d = 0; + read((unsigned char*)&d, 1); + return d; + } + + unsigned int File::read16() + { + unsigned short d = 0; + read((unsigned char*)&d, 2); + return d; + } + + unsigned int File::read32() + { + unsigned int d = 0; + read((unsigned char*)&d, 4); + return d; + } + +DiskFile::DiskFile(FILE *fp): +mFileHandle(fp) +{ + +} + + unsigned int DiskFile::read(unsigned char *aDst, unsigned int aBytes) + { + return (unsigned int)fread(aDst, 1, aBytes, mFileHandle); + } + + unsigned int DiskFile::length() + { + if (!mFileHandle) + return 0; + int pos = ftell(mFileHandle); + fseek(mFileHandle, 0, SEEK_END); + int len = ftell(mFileHandle); + fseek(mFileHandle, pos, SEEK_SET); + return len; + } + + void DiskFile::seek(int aOffset) + { + fseek(mFileHandle, aOffset, SEEK_SET); + } + + unsigned int DiskFile::pos() + { + return ftell(mFileHandle); + } + + FILE *DiskFile::getFilePtr() + { + return mFileHandle; + } + + DiskFile::~DiskFile() + { + if (mFileHandle) + fclose(mFileHandle); + } + + DiskFile::DiskFile() + { + mFileHandle = 0; + } + + result DiskFile::open(const char *aFilename) + { + if (!aFilename) + return INVALID_PARAMETER; + mFileHandle = fopen(aFilename, "rb"); + if (!mFileHandle) + return FILE_NOT_FOUND; + return SO_NO_ERROR; + } + + int DiskFile::eof() + { + return feof(mFileHandle); + } + + + + unsigned int MemoryFile::read(unsigned char *aDst, unsigned int aBytes) + { + if (mOffset + aBytes >= mDataLength) + aBytes = mDataLength - mOffset; + + memcpy(aDst, mDataPtr + mOffset, aBytes); + mOffset += aBytes; + + return aBytes; + } + + unsigned int MemoryFile::length() + { + return mDataLength; + } + + void MemoryFile::seek(int aOffset) + { + if (aOffset >= 0) + mOffset = aOffset; + else + mOffset = mDataLength + aOffset; + if (mOffset > mDataLength-1) + mOffset = mDataLength-1; + } + + unsigned int MemoryFile::pos() + { + return mOffset; + } + + unsigned char * MemoryFile::getMemPtr() + { + return mDataPtr; + } + + MemoryFile::~MemoryFile() + { + if (mDataOwned) + delete[] mDataPtr; + } + + MemoryFile::MemoryFile() + { + mDataPtr = 0; + mDataLength = 0; + mOffset = 0; + mDataOwned = false; + } + + result MemoryFile::openMem(unsigned char *aData, unsigned int aDataLength, bool aCopy, bool aTakeOwnership) + { + if (aData == NULL || aDataLength == 0) + return INVALID_PARAMETER; + + if (mDataOwned) + delete[] mDataPtr; + mDataPtr = 0; + mOffset = 0; + + mDataLength = aDataLength; + + if (aCopy) + { + mDataOwned = true; + mDataPtr = new unsigned char[aDataLength]; + if (mDataPtr == NULL) + return OUT_OF_MEMORY; + memcpy(mDataPtr, aData, aDataLength); + return SO_NO_ERROR; + } + + mDataPtr = aData; + mDataOwned = aTakeOwnership; + return SO_NO_ERROR; + } + + result MemoryFile::openToMem(const char *aFile) + { + if (!aFile) + return INVALID_PARAMETER; + if (mDataOwned) + delete[] mDataPtr; + mDataPtr = 0; + mOffset = 0; + + DiskFile df; + int res = df.open(aFile); + if (res != SO_NO_ERROR) + return res; + + mDataLength = df.length(); + mDataPtr = new unsigned char[mDataLength]; + if (mDataPtr == NULL) + return OUT_OF_MEMORY; + df.read(mDataPtr, mDataLength); + mDataOwned = true; + return SO_NO_ERROR; + } + + result MemoryFile::openFileToMem(File *aFile) + { + if (!aFile) + return INVALID_PARAMETER; + if (mDataOwned) + delete[] mDataPtr; + mDataPtr = 0; + mOffset = 0; + + mDataLength = aFile->length(); + mDataPtr = new unsigned char[mDataLength]; + if (mDataPtr == NULL) + return OUT_OF_MEMORY; + aFile->read(mDataPtr, mDataLength); + mDataOwned = true; + return SO_NO_ERROR; + } + + int MemoryFile::eof() + { + if (mOffset >= mDataLength) + return 1; + return 0; + } +} + +extern "C" +{ + int Soloud_Filehack_fgetc(Soloud_Filehack *f) + { + SoLoud::File *fp = (SoLoud::File *)f; + if (fp->eof()) + return EOF; + return fp->read8(); + } + + int Soloud_Filehack_fread(void *dst, int s, int c, Soloud_Filehack *f) + { + SoLoud::File *fp = (SoLoud::File *)f; + return fp->read((unsigned char*)dst, s*c) / s; + + } + + int Soloud_Filehack_fseek(Soloud_Filehack *f, int idx, int base) + { + SoLoud::File *fp = (SoLoud::File *)f; + switch (base) + { + case SEEK_CUR: + fp->seek(fp->pos() + idx); + break; + case SEEK_END: + fp->seek(fp->length() + idx); + break; + default: + fp->seek(idx); + } + return 0; + } + + int Soloud_Filehack_ftell(Soloud_Filehack *f) + { + SoLoud::File *fp = (SoLoud::File *)f; + return fp->pos(); + } + + int Soloud_Filehack_fclose(Soloud_Filehack *f) + { + SoLoud::File *fp = (SoLoud::File *)f; + delete fp; + return 0; + } + + Soloud_Filehack * Soloud_Filehack_fopen(const char *aFilename, char *aMode) + { + SoLoud::DiskFile *df = new SoLoud::DiskFile(); + int res = df->open(aFilename); + if (res != SoLoud::SO_NO_ERROR) + { + delete df; + df = 0; + } + return (Soloud_Filehack*)df; + } +} diff --git a/src/soloud/src/core/soloud_filter.cpp b/src/soloud/src/core/soloud_filter.cpp new file mode 100644 index 0000000..0ae5679 --- /dev/null +++ b/src/soloud/src/core/soloud_filter.cpp @@ -0,0 +1,142 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" + +namespace SoLoud +{ + + Filter::Filter() + { + } + + Filter::~Filter() + { + } + + FilterInstance::FilterInstance() + { + mNumParams = 0; + mParamChanged = 0; + mParam = 0; + mParamFader = 0; + } + + result FilterInstance::initParams(int aNumParams) + { + mNumParams = aNumParams; + delete[] mParam; + delete[] mParamFader; + mParam = new float[mNumParams]; + mParamFader = new Fader[mNumParams]; + + if (mParam == NULL || mParamFader == NULL) + { + delete[] mParam; + delete[] mParamFader; + mParam = NULL; + mParamFader = NULL; + mNumParams = 0; + return OUT_OF_MEMORY; + } + + unsigned int i; + for (i = 0; i < mNumParams; i++) + { + mParam[i] = 0; + mParamFader[i].mActive = 0; + } + mParam[0] = 1; // set 'wet' to 1 + + return 0; + } + + void FilterInstance::updateParams(double aTime) + { + unsigned int i; + for (i = 0; i < mNumParams; i++) + { + if (mParamFader[i].mActive > 0) + { + mParamChanged |= 1 << i; + mParam[i] = mParamFader[i].get(aTime); + } + } + } + + FilterInstance::~FilterInstance() + { + delete[] mParam; + delete[] mParamFader; + } + + void FilterInstance::setFilterParameter(unsigned int aAttributeId, float aValue) + { + if (aAttributeId >= mNumParams) + return; + + mParamFader[aAttributeId].mActive = 0; + mParam[aAttributeId] = aValue; + mParamChanged |= 1 << aAttributeId; + } + + void FilterInstance::fadeFilterParameter(unsigned int aAttributeId, float aTo, double aTime, double aStartTime) + { + if (aAttributeId >= mNumParams || aTime <= 0 || aTo == mParam[aAttributeId]) + return; + + mParamFader[aAttributeId].set(mParam[aAttributeId], aTo, aTime, aStartTime); + } + + void FilterInstance::oscillateFilterParameter(unsigned int aAttributeId, float aFrom, float aTo, double aTime, double aStartTime) + { + if (aAttributeId >= mNumParams || aTime <= 0 || aFrom == aTo) + return; + + mParamFader[aAttributeId].setLFO(aFrom, aTo, aTime, aStartTime); + } + + float FilterInstance::getFilterParameter(unsigned int aAttributeId) + { + if (aAttributeId >= mNumParams) + return 0; + + return mParam[aAttributeId]; + } + + void FilterInstance::filter(float *aBuffer, unsigned int aSamples, unsigned int aChannels, float aSamplerate, double aTime) + { + unsigned int i; + for (i = 0; i < aChannels; i++) + { + filterChannel(aBuffer + i * aSamples, aSamples, aSamplerate, aTime, i, aChannels); + } + } + + void FilterInstance::filterChannel(float *aBuffer, unsigned int aSamples, float aSamplerate, double aTime, unsigned int aChannel, unsigned int aChannels) + { + } + +}; + diff --git a/src/soloud/src/core/soloud_queue.cpp b/src/soloud/src/core/soloud_queue.cpp new file mode 100644 index 0000000..9f9ce4f --- /dev/null +++ b/src/soloud/src/core/soloud_queue.cpp @@ -0,0 +1,176 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" + +namespace SoLoud +{ + QueueInstance::QueueInstance(Queue *aParent) + { + mParent = aParent; + mFlags |= PROTECTED; + } + + unsigned int QueueInstance::getAudio(float *aBuffer, unsigned int aSamplesToRead, unsigned int aBufferSize) + { + if (mParent->mCount == 0) + { + return 0; + } + unsigned int copycount = aSamplesToRead; + unsigned int copyofs = 0; + while (copycount && mParent->mCount) + { + int readcount = mParent->mSource[mParent->mReadIndex]->getAudio(aBuffer + copyofs, copycount, aBufferSize); + copyofs += readcount; + copycount -= readcount; + if (mParent->mSource[mParent->mReadIndex]->hasEnded()) + { + delete mParent->mSource[mParent->mReadIndex]; + mParent->mSource[mParent->mReadIndex] = 0; + mParent->mReadIndex = (mParent->mReadIndex + 1) % SOLOUD_QUEUE_MAX; + mParent->mCount--; + } + } + return copyofs; + } + + bool QueueInstance::hasEnded() + { + return mParent->mCount == 0; + } + + QueueInstance::~QueueInstance() + { + } + + Queue::Queue() + { + mQueueHandle = 0; + mInstance = 0; + mReadIndex = 0; + mWriteIndex = 0; + mCount = 0; + } + + QueueInstance * Queue::createInstance() + { + if (mInstance) + { + stop(); + mInstance = 0; + } + mInstance = new QueueInstance(this); + return mInstance; + } + + void Queue::findQueueHandle() + { + // Find the channel the queue is playing on to calculate handle.. + int i; + for (i = 0; mQueueHandle == 0 && i < (signed)mSoloud->mHighestVoice; i++) + { + if (mSoloud->mVoice[i] == mInstance) + { + mQueueHandle = mSoloud->getHandleFromVoice(i); + } + } + } + + result Queue::play(AudioSource &aSound) + { + if (!mSoloud) + { + return INVALID_PARAMETER; + } + + findQueueHandle(); + + if (mQueueHandle == 0) + return INVALID_PARAMETER; + + if (mCount >= SOLOUD_QUEUE_MAX) + return OUT_OF_MEMORY; + + if (!aSound.mAudioSourceID) + { + aSound.mAudioSourceID = mSoloud->mAudioSourceID; + mSoloud->mAudioSourceID++; + } + + SoLoud::AudioSourceInstance *instance = aSound.createInstance(); + + if (instance == 0) + { + return OUT_OF_MEMORY; + } + + instance->mAudioSourceID = aSound.mAudioSourceID; + + mSoloud->lockAudioMutex(); + mSource[mWriteIndex] = instance; + mWriteIndex = (mWriteIndex + 1) % SOLOUD_QUEUE_MAX; + mCount++; + mSoloud->unlockAudioMutex(); + + return SO_NO_ERROR; + } + + + unsigned int Queue::getQueueCount() + { + unsigned int count; + mSoloud->lockAudioMutex(); + count = mCount; + mSoloud->unlockAudioMutex(); + return count; + } + + bool Queue::isCurrentlyPlaying(AudioSource &aSound) + { + if (mCount == 0 || aSound.mAudioSourceID == 0) + return false; + mSoloud->lockAudioMutex(); + bool res = mSource[mReadIndex]->mAudioSourceID == aSound.mAudioSourceID; + mSoloud->unlockAudioMutex(); + return res; + } + + result Queue::setParamsFromAudioSource(AudioSource &aSound) + { + mChannels = aSound.mChannels; + mBaseSamplerate = aSound.mBaseSamplerate; + + return SO_NO_ERROR; + } + + result Queue::setParams(float aSamplerate, unsigned int aChannels) + { + if (aChannels < 1 || aChannels > MAX_CHANNELS) + return INVALID_PARAMETER; + mChannels = aChannels; + mBaseSamplerate = aSamplerate; + return SO_NO_ERROR; + } +}; diff --git a/src/soloud/src/core/soloud_thread.cpp b/src/soloud/src/core/soloud_thread.cpp new file mode 100644 index 0000000..8b27c61 --- /dev/null +++ b/src/soloud/src/core/soloud_thread.cpp @@ -0,0 +1,310 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#if defined(_WIN32)||defined(_WIN64) +#include +#else +#include +#include +#endif + +#include "soloud.h" +#include "soloud_thread.h" + +namespace SoLoud +{ + namespace Thread + { +#ifdef WINDOWS_VERSION + struct ThreadHandleData + { + HANDLE thread; + }; + + void * createMutex() + { + CRITICAL_SECTION * cs = new CRITICAL_SECTION; + InitializeCriticalSectionAndSpinCount(cs, 100); + return (void*)cs; + } + + void destroyMutex(void *aHandle) + { + CRITICAL_SECTION *cs = (CRITICAL_SECTION*)aHandle; + DeleteCriticalSection(cs); + delete cs; + } + + void lockMutex(void *aHandle) + { + CRITICAL_SECTION *cs = (CRITICAL_SECTION*)aHandle; + if (cs) + { + EnterCriticalSection(cs); + } + } + + void unlockMutex(void *aHandle) + { + CRITICAL_SECTION *cs = (CRITICAL_SECTION*)aHandle; + if (cs) + { + LeaveCriticalSection(cs); + } + } + + struct soloud_thread_data + { + threadFunction mFunc; + void *mParam; + }; + + static DWORD WINAPI threadfunc(LPVOID d) + { + soloud_thread_data *p = (soloud_thread_data *)d; + p->mFunc(p->mParam); + delete p; + return 0; + } + + ThreadHandle createThread(threadFunction aThreadFunction, void *aParameter) + { + soloud_thread_data *d = new soloud_thread_data; + d->mFunc = aThreadFunction; + d->mParam = aParameter; + HANDLE h = CreateThread(NULL,0,threadfunc,d,0,NULL); + if (0 == h) + { + return 0; + } + ThreadHandleData *threadHandle = new ThreadHandleData; + threadHandle->thread = h; + return threadHandle; + } + + void sleep(int aMSec) + { + Sleep(aMSec); + } + + void wait(ThreadHandle aThreadHandle) + { + WaitForSingleObject(aThreadHandle->thread, INFINITE); + } + + void release(ThreadHandle aThreadHandle) + { + CloseHandle(aThreadHandle->thread); + delete aThreadHandle; + } + +#else // pthreads + struct ThreadHandleData + { + pthread_t thread; + }; + + void * createMutex() + { + pthread_mutex_t *mutex; + mutex = new pthread_mutex_t; + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + + pthread_mutex_init(mutex, &attr); + + return (void*)mutex; + } + + void destroyMutex(void *aHandle) + { + pthread_mutex_t *mutex = (pthread_mutex_t*)aHandle; + + if (mutex) + { + pthread_mutex_destroy(mutex); + delete mutex; + } + } + + void lockMutex(void *aHandle) + { + pthread_mutex_t *mutex = (pthread_mutex_t*)aHandle; + if (mutex) + { + pthread_mutex_lock(mutex); + } + } + + void unlockMutex(void *aHandle) + { + pthread_mutex_t *mutex = (pthread_mutex_t*)aHandle; + if (mutex) + { + pthread_mutex_unlock(mutex); + } + } + + struct soloud_thread_data + { + threadFunction mFunc; + void *mParam; + }; + + static void * threadfunc(void * d) + { + soloud_thread_data *p = (soloud_thread_data *)d; + p->mFunc(p->mParam); + delete p; + return 0; + } + + ThreadHandle createThread(threadFunction aThreadFunction, void *aParameter) + { + soloud_thread_data *d = new soloud_thread_data; + d->mFunc = aThreadFunction; + d->mParam = aParameter; + + ThreadHandleData *threadHandle = new ThreadHandleData; + pthread_create(&threadHandle->thread, NULL, threadfunc, (void*)d); + return threadHandle; + } + + void sleep(int aMSec) + { + usleep(aMSec * 1000); + } + + void wait(ThreadHandle aThreadHandle) + { + pthread_join(aThreadHandle->thread, 0); + } + + void release(ThreadHandle aThreadHandle) + { + delete aThreadHandle; + } +#endif + + static void poolWorker(void *aParam) + { + Pool *myPool = (Pool*)aParam; + while (myPool->mRunning) + { + PoolTask *t = myPool->getWork(); + if (!t) + { + sleep(1); + } + else + { + t->work(); + } + } + } + + Pool::Pool() + { + mRunning = 0; + mThreadCount = 0; + mThread = 0; + mWorkMutex = 0; + mRobin = 0; + mMaxTask = 0; + } + + Pool::~Pool() + { + mRunning = 0; + int i; + for (i = 0; i < mThreadCount; i++) + { + wait(mThread[i]); + release(mThread[i]); + } + delete[] mThread; + if (mWorkMutex) + destroyMutex(mWorkMutex); + } + + void Pool::init(int aThreadCount) + { + if (aThreadCount > 0) + { + mMaxTask = 0; + mWorkMutex = createMutex(); + mRunning = 1; + mThreadCount = aThreadCount; + mThread = new ThreadHandle[aThreadCount]; + int i; + for (i = 0; i < mThreadCount; i++) + { + mThread[i] = createThread(poolWorker, this); + } + } + } + + void Pool::addWork(PoolTask *aTask) + { + if (mThreadCount == 0) + { + aTask->work(); + } + else + { + if (mWorkMutex) lockMutex(mWorkMutex); + if (mMaxTask == MAX_THREADPOOL_TASKS) + { + // If we're at max tasks, do the task on calling thread + // (we're in trouble anyway, might as well slow down adding more work) + if (mWorkMutex) unlockMutex(mWorkMutex); + aTask->work(); + } + else + { + mTaskArray[mMaxTask] = aTask; + mMaxTask++; + if (mWorkMutex) unlockMutex(mWorkMutex); + } + } + } + + PoolTask * Pool::getWork() + { + PoolTask *t = 0; + if (mWorkMutex) lockMutex(mWorkMutex); + if (mMaxTask > 0) + { + int r = mRobin % mMaxTask; + mRobin++; + t = mTaskArray[r]; + mTaskArray[r] = mTaskArray[mMaxTask - 1]; + mMaxTask--; + } + if (mWorkMutex) unlockMutex(mWorkMutex); + return t; + } + } +} diff --git a/src/soloud/src/filter/soloud_bassboostfilter.cpp b/src/soloud/src/filter/soloud_bassboostfilter.cpp new file mode 100644 index 0000000..67c1707 --- /dev/null +++ b/src/soloud/src/filter/soloud_bassboostfilter.cpp @@ -0,0 +1,65 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include "soloud.h" +#include "soloud_bassboostfilter.h" + + +namespace SoLoud +{ + BassboostFilterInstance::BassboostFilterInstance(BassboostFilter *aParent) + { + mParent = aParent; + initParams(2); + mParam[BOOST] = aParent->mBoost; + } + + void BassboostFilterInstance::fftFilterChannel(float *aFFTBuffer, unsigned int aSamples, float aSamplerate, time aTime, unsigned int aChannel, unsigned int aChannels) + { + unsigned int i; + for (i = 0; i < 2; i++) + { + aFFTBuffer[i * 2] *= mParam[BOOST]; + } + } + + result BassboostFilter::setParams(float aBoost) + { + if (aBoost < 0) + return INVALID_PARAMETER; + mBoost = aBoost; + return SO_NO_ERROR; + } + + BassboostFilter::BassboostFilter() + { + mBoost = 2; + } + + FilterInstance *BassboostFilter::createInstance() + { + return new BassboostFilterInstance(this); + } +} diff --git a/src/soloud/src/filter/soloud_biquadresonantfilter.cpp b/src/soloud/src/filter/soloud_biquadresonantfilter.cpp new file mode 100644 index 0000000..1b7e74f --- /dev/null +++ b/src/soloud/src/filter/soloud_biquadresonantfilter.cpp @@ -0,0 +1,182 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +-- + +Based on "Using the Biquad Resonant Filter", +Phil Burk, Game Programming Gems 3, p. 606 +*/ + +#include +#include +#include "soloud.h" +#include "soloud_biquadresonantfilter.h" + +namespace SoLoud +{ + void BiquadResonantFilterInstance::calcBQRParams() + { + mDirty = 0; + + float omega = (float)((2.0f * M_PI * mParam[FREQUENCY]) / mParam[SAMPLERATE]); + float sin_omega = (float)sin(omega); + float cos_omega = (float)cos(omega); + float alpha = sin_omega / (2.0f * mParam[RESONANCE]); + float scalar = 1.0f / (1.0f + alpha); + + mActive = 1; + + switch (mFilterType) + { + case BiquadResonantFilter::NONE: + mActive = 0; + break; + case BiquadResonantFilter::LOWPASS: + mA0 = 0.5f * (1.0f - cos_omega) * scalar; + mA1 = (1.0f - cos_omega) * scalar; + mA2 = mA0; + mB1 = -2.0f * cos_omega * scalar; + mB2 = (1.0f - alpha) * scalar; + break; + case BiquadResonantFilter::HIGHPASS: + mA0 = 0.5f * (1.0f + cos_omega) * scalar; + mA1 = -(1.0f + cos_omega) * scalar; + mA2 = mA0; + mB1 = -2.0f * cos_omega * scalar; + mB2 = (1.0f - alpha) * scalar; + break; + case BiquadResonantFilter::BANDPASS: + mA0 = alpha * scalar; + mA1 = 0; + mA2 = -mA0; + mB1 = -2.0f * cos_omega * scalar; + mB2 = (1.0f - alpha) * scalar; + break; + } + } + + + BiquadResonantFilterInstance::BiquadResonantFilterInstance(BiquadResonantFilter *aParent) + { + int i; + for (i = 0; i < 2; i++) + { + mState[i].mX1 = 0; + mState[i].mY1 = 0; + mState[i].mX2 = 0; + mState[i].mY2 = 0; + } + + mParent = aParent; + mFilterType = aParent->mFilterType; + + initParams(4); + + mParam[SAMPLERATE] = aParent->mSampleRate; + mParam[RESONANCE] = aParent->mResonance; + mParam[FREQUENCY] = aParent->mFrequency; + mParam[WET] = 1; + + calcBQRParams(); + } + + void BiquadResonantFilterInstance::filterChannel(float *aBuffer, unsigned int aSamples, float aSamplerate, double aTime, unsigned int aChannel, unsigned int aChannels) + { + if (!mActive) + return; + + if (aChannel == 0) + { + updateParams(aTime); + + if (mParamChanged & ((1 << FREQUENCY) | (1 << RESONANCE) | (1 << SAMPLERATE))) + { + calcBQRParams(); + } + mParamChanged = 0; + } + + float x; + unsigned int i; + int c = 0; + + BQRStateData &s = mState[aChannel]; + + for (i = 0; i < aSamples; i +=2, c++) + { + // Generate outputs by filtering inputs. + x = aBuffer[c]; + s.mY2 = (mA0 * x) + (mA1 * s.mX1) + (mA2 * s.mX2) - (mB1 * s.mY1) - (mB2 * s.mY2); + aBuffer[c] += (s.mY2 - aBuffer[c]) * mParam[WET]; + + c++; + + // Permute filter operations to reduce data movement. + // Just substitute variables instead of doing mX1=x, etc. + s.mX2 = aBuffer[c]; + s.mY1 = (mA0 * s.mX2) + (mA1 * x) + (mA2 * s.mX1) - (mB1 * s.mY2) - (mB2 * s.mY1); + aBuffer[c] += (s.mY1 - aBuffer[c]) * mParam[WET]; + + // Only move a little data. + s.mX1 = s.mX2; + s.mX2 = x; + } + + // Apply a small impulse to filter to prevent arithmetic underflow, + // which can cause the FPU to interrupt the CPU. + s.mY1 += (float) 1.0E-26; + } + + + BiquadResonantFilterInstance::~BiquadResonantFilterInstance() + { + } + + BiquadResonantFilter::BiquadResonantFilter() + { + setParams(LOWPASS, 44100, 1000, 2); + } + + result BiquadResonantFilter::setParams(int aType, float aSampleRate, float aFrequency, float aResonance) + { + if (aType < 0 || aType > 3 || aSampleRate <= 0 || aFrequency <= 0 || aResonance <= 0) + return INVALID_PARAMETER; + + mFilterType = aType; + mSampleRate = aSampleRate; + mFrequency = aFrequency; + mResonance = aResonance; + + return 0; + } + + BiquadResonantFilter::~BiquadResonantFilter() + { + } + + + BiquadResonantFilterInstance *BiquadResonantFilter::createInstance() + { + return new BiquadResonantFilterInstance(this); + } +} diff --git a/src/soloud/src/filter/soloud_dcremovalfilter.cpp b/src/soloud/src/filter/soloud_dcremovalfilter.cpp new file mode 100644 index 0000000..f5db404 --- /dev/null +++ b/src/soloud/src/filter/soloud_dcremovalfilter.cpp @@ -0,0 +1,110 @@ +/* +SoLoud audio engine +Copyright (c) 2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" +#include "soloud_dcremovalfilter.h" + +namespace SoLoud +{ + DCRemovalFilterInstance::DCRemovalFilterInstance(DCRemovalFilter *aParent) + { + mParent = aParent; + mBuffer = 0; + mBufferLength = 0; + mTotals = 0; + mOffset = 0; + initParams(1); + + } + + void DCRemovalFilterInstance::filter(float *aBuffer, unsigned int aSamples, unsigned int aChannels, float aSamplerate, double aTime) + { + updateParams(aTime); + + if (mBuffer == 0) + { + mBufferLength = (int)ceil(mParent->mLength * aSamplerate); + mBuffer = new float[mBufferLength * aChannels]; + mTotals = new float[aChannels]; + unsigned int i; + for (i = 0; i < aChannels; i++) + { + mTotals[i] = 0; + } + for (i = 0; i < mBufferLength * aChannels; i++) + { + mBuffer[i] = 0; + } + } + + unsigned int i, j; + int prevofs = (mOffset + mBufferLength - 1) % mBufferLength; + for (i = 0; i < aSamples; i++) + { + for (j = 0; j < aChannels; j++) + { + int chofs = j * mBufferLength; + int bchofs = j * aSamples; + + float n = aBuffer[i + bchofs]; + mTotals[j] -= mBuffer[mOffset + chofs]; + mTotals[j] += n; + mBuffer[mOffset + chofs] = n; + + n -= mTotals[j] / mBufferLength; + + aBuffer[i + bchofs] += (n - aBuffer[i + bchofs]) * mParam[0]; + } + prevofs = mOffset; + mOffset = (mOffset + 1) % mBufferLength; + } + } + + DCRemovalFilterInstance::~DCRemovalFilterInstance() + { + delete[] mBuffer; + delete[] mTotals; + } + + DCRemovalFilter::DCRemovalFilter() + { + mLength = 0.1f; + } + + result DCRemovalFilter::setParams(float aLength) + { + if (aLength <= 0) + return INVALID_PARAMETER; + + mLength = aLength; + + return 0; + } + + + FilterInstance *DCRemovalFilter::createInstance() + { + return new DCRemovalFilterInstance(this); + } +} diff --git a/src/soloud/src/filter/soloud_echofilter.cpp b/src/soloud/src/filter/soloud_echofilter.cpp new file mode 100644 index 0000000..a9063f5 --- /dev/null +++ b/src/soloud/src/filter/soloud_echofilter.cpp @@ -0,0 +1,106 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "soloud.h" +#include "soloud_echofilter.h" + +namespace SoLoud +{ + EchoFilterInstance::EchoFilterInstance(EchoFilter *aParent) + { + mParent = aParent; + mBuffer = 0; + mBufferLength = 0; + mOffset = 0; + initParams(1); + + } + + void EchoFilterInstance::filter(float *aBuffer, unsigned int aSamples, unsigned int aChannels, float aSamplerate, double aTime) + { + updateParams(aTime); + + if (mBuffer == 0) + { + mBufferLength = (int)ceil(mParent->mDelay * aSamplerate); + mBuffer = new float[mBufferLength * aChannels]; + unsigned int i; + for (i = 0; i < mBufferLength * aChannels; i++) + { + mBuffer[i] = 0; + } + } + + float decay = mParent->mDecay; + unsigned int i, j; + int prevofs = (mOffset + mBufferLength - 1) % mBufferLength; + for (i = 0; i < aSamples; i++) + { + for (j = 0; j < aChannels; j++) + { + int chofs = j * mBufferLength; + int bchofs = j * aSamples; + + mBuffer[mOffset + chofs] = mParent->mFilter * mBuffer[prevofs + chofs] + (1 - mParent->mFilter) * mBuffer[mOffset + chofs]; + + float n = aBuffer[i + bchofs] + mBuffer[mOffset + chofs] * decay; + mBuffer[mOffset + chofs] = n; + + aBuffer[i + bchofs] += (n - aBuffer[i + bchofs]) * mParam[0]; + } + prevofs = mOffset; + mOffset = (mOffset + 1) % mBufferLength; + } + } + + EchoFilterInstance::~EchoFilterInstance() + { + delete[] mBuffer; + } + + EchoFilter::EchoFilter() + { + mDelay = 0.3f; + mDecay = 0.7f; + mFilter = 0.0f; + } + + result EchoFilter::setParams(float aDelay, float aDecay, float aFilter) + { + if (aDelay <= 0 || aDecay <= 0 || aFilter < 0 || aFilter >= 1.0f) + return INVALID_PARAMETER; + + mDecay = aDecay; + mDelay = aDelay; + mFilter = aFilter; + + return 0; + } + + + FilterInstance *EchoFilter::createInstance() + { + return new EchoFilterInstance(this); + } +} diff --git a/src/soloud/src/filter/soloud_fftfilter.cpp b/src/soloud/src/filter/soloud_fftfilter.cpp new file mode 100644 index 0000000..6b7920d --- /dev/null +++ b/src/soloud/src/filter/soloud_fftfilter.cpp @@ -0,0 +1,143 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2015 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include "soloud.h" +#include "soloud_fftfilter.h" +#include "soloud_fft.h" + + +namespace SoLoud +{ + FFTFilterInstance::FFTFilterInstance() + { + mParent = 0; + mInputBuffer = 0; + mMixBuffer = 0; + mTemp = 0; + int i; + for (i = 0; i < MAX_CHANNELS; i++) + mOffset[i] = 0; + } + + FFTFilterInstance::FFTFilterInstance(FFTFilter *aParent) + { + mParent = aParent; + mInputBuffer = 0; + mMixBuffer = 0; + mTemp = 0; + int i; + for (i = 0; i < MAX_CHANNELS; i++) + mOffset[i] = 0; + initParams(1); + } + + void FFTFilterInstance::filterChannel(float *aBuffer, unsigned int aSamples, float aSamplerate, double aTime, unsigned int aChannel, unsigned int aChannels) + { + if (aChannel == 0) + { + updateParams(aTime); + } + + if (mInputBuffer == 0) + { + mInputBuffer = new float[512 * aChannels]; + mMixBuffer = new float[512 * aChannels]; + mTemp = new float[256]; + memset(mInputBuffer, 0x2f, sizeof(float) * 512 * aChannels); + memset(mMixBuffer, 0, sizeof(float) * 512 * aChannels); + } + + float * b = mTemp; + + int i; + unsigned int ofs = 0; + unsigned int chofs = 512 * aChannel; + unsigned int bofs = mOffset[aChannel]; + + while (ofs < aSamples) + { + for (i = 0; i < 128; i++) + { + mInputBuffer[chofs + ((bofs + i + 128) & 511)] = aBuffer[ofs + i]; + mMixBuffer[chofs + ((bofs + i + 128) & 511)] = 0; + } + + for (i = 0; i < 256; i++) + { + b[i] = mInputBuffer[chofs + ((bofs + i) & 511)]; + } + FFT::fft256(b); + + // do magic + fftFilterChannel(b, 128, aSamplerate, aTime, aChannel, aChannels); + + FFT::ifft256(b); + + for (i = 0; i < 256; i++) + { + mMixBuffer[chofs + ((bofs + i) & 511)] += b[i] * (128 - abs(128 - i)) * (1.0f / 128.0f); + } + + for (i = 0; i < 128; i++) + { + aBuffer[ofs + i] += (mMixBuffer[chofs + ((bofs + i) & 511)] - aBuffer[ofs + i]) * mParam[0]; + } + ofs += 128; + bofs += 128; + } + mOffset[aChannel] = bofs; + } + + void FFTFilterInstance::fftFilterChannel(float *aFFTBuffer, unsigned int aSamples, float aSamplerate, time aTime, unsigned int aChannel, unsigned int aChannels) + { + unsigned int i; + for (i = 4; i < aSamples; i++) + { + aFFTBuffer[(i - 4) * 2] = aFFTBuffer[i * 2]; + aFFTBuffer[(i - 4) * 2 + 1] = aFFTBuffer[i * 2 + 1]; + } + for (i = 0; i < 4; i++) + { + aFFTBuffer[aSamples - 4 * 2 + i * 2] = 0; + aFFTBuffer[aSamples - 4 * 2 + i * 2 + 1] = 0; + } + } + + FFTFilterInstance::~FFTFilterInstance() + { + delete[] mTemp; + delete[] mInputBuffer; + delete[] mMixBuffer; + } + + FFTFilter::FFTFilter() + { + } + + FilterInstance *FFTFilter::createInstance() + { + return new FFTFilterInstance(this); + } +} diff --git a/src/soloud/src/filter/soloud_flangerfilter.cpp b/src/soloud/src/filter/soloud_flangerfilter.cpp new file mode 100644 index 0000000..f1b0c12 --- /dev/null +++ b/src/soloud/src/filter/soloud_flangerfilter.cpp @@ -0,0 +1,109 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include +#include "soloud.h" +#include "soloud_flangerfilter.h" + +namespace SoLoud +{ + FlangerFilterInstance::FlangerFilterInstance(FlangerFilter *aParent) + { + mParent = aParent; + mBuffer = 0; + mBufferLength = 0; + mOffset = 0; + mIndex = 0; + initParams(3); + mParam[FlangerFilter::WET] = 1; + mParam[FlangerFilter::FREQ] = mParent->mFreq; + mParam[FlangerFilter::DELAY] = mParent->mDelay; + } + + void FlangerFilterInstance::filter(float *aBuffer, unsigned int aSamples, unsigned int aChannels, float aSamplerate, double aTime) + { + updateParams(aTime); + + if (mBufferLength < mParam[FlangerFilter::DELAY] * aSamplerate) + { + delete[] mBuffer; + mBufferLength = (int)ceil(mParam[FlangerFilter::DELAY] * aSamplerate); + mBuffer = new float[mBufferLength * aChannels]; + if (mBuffer == NULL) + { + mBufferLength = 0; + return; + } + memset(mBuffer, 0, sizeof(float) * mBufferLength * aChannels); + } + + unsigned int i, j; + int maxsamples = (int)ceil(mParam[FlangerFilter::DELAY] * aSamplerate); + double inc = mParam[FlangerFilter::FREQ] * M_PI * 2 / aSamplerate; + for (i = 0; i < aChannels; i++) + { + int mbofs = i * mBufferLength; + int abofs = i * aSamples; + for (j = 0; j < aSamples; j++, abofs++) + { + int delay = (int)floor(maxsamples * (1 + cos(mIndex))) / 2; + mIndex += inc; + mBuffer[mbofs + mOffset % mBufferLength] = aBuffer[abofs]; + float n = 0.5f * (aBuffer[abofs] + mBuffer[mbofs + (mBufferLength - delay + mOffset) % mBufferLength]); + mOffset++; + aBuffer[abofs] += (n - aBuffer[abofs]) * mParam[FlangerFilter::WET]; + } + mOffset -= aSamples; + } + mOffset += aSamples; + mOffset %= mBufferLength; + } + + FlangerFilterInstance::~FlangerFilterInstance() + { + delete[] mBuffer; + } + + FlangerFilter::FlangerFilter() + { + mDelay = 0.005f; + mFreq = 10; + } + + result FlangerFilter::setParams(float aDelay, float aFreq) + { + if (aDelay <= 0 || aFreq <= 0) + return INVALID_PARAMETER; + + mDelay = aDelay; + mFreq = aFreq; + + return 0; + } + + + FilterInstance *FlangerFilter::createInstance() + { + return new FlangerFilterInstance(this); + } +} diff --git a/src/soloud/src/filter/soloud_lofifilter.cpp b/src/soloud/src/filter/soloud_lofifilter.cpp new file mode 100644 index 0000000..26a9e9a --- /dev/null +++ b/src/soloud/src/filter/soloud_lofifilter.cpp @@ -0,0 +1,95 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2014 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include +#include "soloud.h" +#include "soloud_lofifilter.h" + +namespace SoLoud +{ + + LofiFilterInstance::LofiFilterInstance(LofiFilter *aParent) + { + mParent = aParent; + initParams(3); + mParam[SAMPLERATE] = aParent->mSampleRate; + mParam[BITDEPTH] = aParent->mBitdepth; + mChannelData[0].mSample = 0; + mChannelData[0].mSamplesToSkip = 0; + mChannelData[1].mSample = 0; + mChannelData[1].mSamplesToSkip = 0; + } + + void LofiFilterInstance::filterChannel(float *aBuffer, unsigned int aSamples, float aSamplerate, double aTime, unsigned int aChannel, unsigned int aChannels) + { + updateParams(aTime); + + unsigned int i; + for (i = 0; i < aSamples; i++) + { + if (mChannelData[aChannel].mSamplesToSkip <= 0) + { + mChannelData[aChannel].mSamplesToSkip += (aSamplerate / mParam[SAMPLERATE]) - 1; + float q = (float)pow(2, mParam[BITDEPTH]); + mChannelData[aChannel].mSample = (float)floor(q*aBuffer[i])/q; + } + else + { + mChannelData[aChannel].mSamplesToSkip--; + } + aBuffer[i] += (mChannelData[aChannel].mSample - aBuffer[i]) * mParam[WET]; + } + + } + + LofiFilterInstance::~LofiFilterInstance() + { + } + + LofiFilter::LofiFilter() + { + setParams(4000, 3); + } + + result LofiFilter::setParams(float aSampleRate, float aBitdepth) + { + if (aSampleRate <= 0 || aBitdepth <= 0) + return INVALID_PARAMETER; + + mSampleRate = aSampleRate; + mBitdepth = aBitdepth; + return 0; + } + + LofiFilter::~LofiFilter() + { + } + + + LofiFilterInstance *LofiFilter::createInstance() + { + return new LofiFilterInstance(this); + } +} diff --git a/src/soloud/src/filter/soloud_robotizefilter.cpp b/src/soloud/src/filter/soloud_robotizefilter.cpp new file mode 100644 index 0000000..97c2e85 --- /dev/null +++ b/src/soloud/src/filter/soloud_robotizefilter.cpp @@ -0,0 +1,56 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include "soloud.h" +#include "soloud_robotizefilter.h" + + +namespace SoLoud +{ + RobotizeFilterInstance::RobotizeFilterInstance(RobotizeFilter *aParent) + { + mParent = aParent; + initParams(1); + mParam[WET] = 1.0; + } + + void RobotizeFilterInstance::fftFilterChannel(float *aFFTBuffer, unsigned int aSamples, float aSamplerate, time aTime, unsigned int aChannel, unsigned int aChannels) + { + unsigned int i; + for (i = 0; i < aSamples; i++) + { + aFFTBuffer[i*2] = 0; + } + } + + RobotizeFilter::RobotizeFilter() + { + } + + FilterInstance *RobotizeFilter::createInstance() + { + return new RobotizeFilterInstance(this); + } +} diff --git a/src/soloud/src/filter/soloud_waveshaperfilter.cpp b/src/soloud/src/filter/soloud_waveshaperfilter.cpp new file mode 100644 index 0000000..313073a --- /dev/null +++ b/src/soloud/src/filter/soloud_waveshaperfilter.cpp @@ -0,0 +1,87 @@ +/* +SoLoud audio engine +Copyright (c) 2013-2018 Jari Komppa + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include +#include +#include "soloud.h" +#include "soloud_waveshaperfilter.h" + +namespace SoLoud +{ + + WaveShaperFilterInstance::WaveShaperFilterInstance(WaveShaperFilter *aParent) + { + mParent = aParent; + initParams(2); + mParam[0] = mParent->mWet; + mParam[1] = mParent->mAmount; + } + + void WaveShaperFilterInstance::filterChannel(float *aBuffer, unsigned int aSamples, float aSamplerate, double aTime, unsigned int aChannel, unsigned int aChannels) + { + updateParams(aTime); + + unsigned int i; + float k = 0; + if (mParam[1] == 1) + k = 2 * mParam[1] / 0.01f; + else + k = 2 * mParam[1] / (1 - mParam[1]); + + for (i = 0; i < aSamples; i++) + { + float dry = aBuffer[i]; + float wet = (1 + k) * aBuffer[i] / (1 + k * (float)fabs(aBuffer[i])); + aBuffer[i] += (wet - dry) * mParam[0]; + } + } + + WaveShaperFilterInstance::~WaveShaperFilterInstance() + { + } + + result WaveShaperFilter::setParams(float aAmount, float aWet) + { + if (aAmount < -1 || aAmount > 1 || aWet < 0 || aWet > 1) + return INVALID_PARAMETER; + mAmount = aAmount; + mWet = aWet; + return 0; + } + + WaveShaperFilter::WaveShaperFilter() + { + mAmount = 0.0f; + mWet = 0.0f; + } + + WaveShaperFilter::~WaveShaperFilter() + { + } + + WaveShaperFilterInstance *WaveShaperFilter::createInstance() + { + return new WaveShaperFilterInstance(this); + } +} diff --git a/terminal.png b/terminal.png new file mode 100644 index 0000000..3cffb5b Binary files /dev/null and b/terminal.png differ diff --git a/underwater.wav b/underwater.wav new file mode 100644 index 0000000..727726e Binary files /dev/null and b/underwater.wav differ