mirror of
https://github.com/ScrelliCopter/VGM-Tools
synced 2025-02-21 04:09:25 +11:00
dsptool: basic dspdecode implementation
This commit is contained in:
@@ -1,18 +1,21 @@
|
|||||||
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
|
||||||
project(DspTool LANGUAGES C)
|
project(dsptools LANGUAGES C)
|
||||||
|
|
||||||
option(BUILD_SHARED_LIBS "Build as a Shared Object or DLL" ON)
|
include(CheckIncludeFile)
|
||||||
|
check_include_file("endian.h" HAVE_ENDIAN_H)
|
||||||
set(HEADERS common.h dsptool.h)
|
if (HAVE_ENDIAN_H)
|
||||||
set(SOURCES math.c decode.c encode.c)
|
add_definitions(HAVE_ENDIAN_H)
|
||||||
|
|
||||||
add_library(DspTool ${HEADERS} ${SOURCES})
|
|
||||||
set_property(TARGET DspTool PROPERTY C_STANDARD 99)
|
|
||||||
if (BUILD_SHARED_LIBS)
|
|
||||||
target_compile_definitions(DspTool PRIVATE BUILD_SHARED)
|
|
||||||
else()
|
|
||||||
target_compile_definitions(DspTool PRIVATE BUILD_STATIC)
|
|
||||||
endif()
|
endif()
|
||||||
target_compile_options(DspTool PRIVATE
|
|
||||||
$<$<C_COMPILER_ID:AppleClang,Clang,GNU>:-Wall -Wextra -pedantic -Wno-unused-parameter>
|
add_subdirectory(libdsptool)
|
||||||
$<$<C_COMPILER_ID:MSVC>:/Wall /wd4100>)
|
|
||||||
|
set(HEADERS wave.h)
|
||||||
|
set(SOURCES wavefile.c wave.c dspdecode.c)
|
||||||
|
|
||||||
|
add_executable(dspdecode ${HEADERS} ${SOURCES})
|
||||||
|
|
||||||
|
set_property(TARGET dspdecode PROPERTY C_STANDARD 99)
|
||||||
|
target_link_libraries(dspdecode DspTool::DspTool)
|
||||||
|
target_compile_options(dspdecode PRIVATE
|
||||||
|
$<$<C_COMPILER_ID:AppleClang,Clang,GNU>:-Wall -Wextra -pedantic>
|
||||||
|
$<$<C_COMPILER_ID:MSVC>:/Wall>)
|
||||||
|
|||||||
44
dsptools/common.h
Normal file
44
dsptools/common.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef COMMON_H
|
||||||
|
#define COMMON_H
|
||||||
|
|
||||||
|
#ifdef HAVE_ENDIAN_H
|
||||||
|
# include <endian.h>
|
||||||
|
# define BYTE_ORDER __BYTE_ORDER
|
||||||
|
# define LITTLE_ENDIAN __LITTLE_ENDIAN
|
||||||
|
# define BIG_ENDIAN __BIG_ENDIAN
|
||||||
|
#else
|
||||||
|
# define LITTLE_ENDIAN 1234
|
||||||
|
# define BIG_ENDIAN 4321
|
||||||
|
# if defined( __APPLE__ ) || defined( _WIN32 )
|
||||||
|
# define BYTE_ORDER LITTLE_ENDIAN
|
||||||
|
# else
|
||||||
|
# error Can't detect endianness
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline uint32_t swap32(uint32_t v)
|
||||||
|
{
|
||||||
|
return (v << 24) | ((v << 8) & 0x00FF0000) | ((v >> 8) & 0x0000FF00) | (v >> 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t swap16(uint16_t v)
|
||||||
|
{
|
||||||
|
return (v << 8) | (v >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
||||||
|
# define SWAP_LE32(V) (V)
|
||||||
|
# define SWAP_LE16(V) (V)
|
||||||
|
# define SWAP_BE32(V) swap32((uint32_t)V)
|
||||||
|
# define SWAP_BE16(V) swap16((uint16_t)V)
|
||||||
|
#elif BYTE_ORDER == BIG_ENDIAN
|
||||||
|
# define SWAP_LE32(V) swap32((uint32_t)V)
|
||||||
|
# define SWAP_LE16(V) swap16((uint16_t)V)
|
||||||
|
# define SWAP_BE32(V) (V)
|
||||||
|
# define SWAP_BE16(V) (V)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MIN(A, B) (((A) < (B)) ? (A) : (B))
|
||||||
|
#define MAX(A, B) (((A) > (B)) ? (A) : (B))
|
||||||
|
|
||||||
|
#endif//COMMON_H
|
||||||
258
dsptools/dspdecode.c
Normal file
258
dsptools/dspdecode.c
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
/* dspdecode.c (c) 2023 a dinosaur (zlib) */
|
||||||
|
|
||||||
|
#include "dsptool.h"
|
||||||
|
#include "wave.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t numSamples;
|
||||||
|
uint32_t numNibbles;
|
||||||
|
uint32_t sampleRate;
|
||||||
|
uint16_t loopFlag;
|
||||||
|
uint16_t format; // Reserved, always 0
|
||||||
|
uint32_t loopBeg;
|
||||||
|
uint32_t loopEnd;
|
||||||
|
uint32_t curAddress; // Reserved, always 0
|
||||||
|
int16_t coefs[16];
|
||||||
|
uint16_t gain; // Always 0
|
||||||
|
uint16_t predScale;
|
||||||
|
int16_t history[2];
|
||||||
|
uint16_t loopPredScale;
|
||||||
|
int16_t loopHistory[2];
|
||||||
|
int16_t channels;
|
||||||
|
uint16_t blockSize;
|
||||||
|
uint16_t reserved1[9];
|
||||||
|
|
||||||
|
} DspHeader;
|
||||||
|
|
||||||
|
static void readBeU32(uint32_t* restrict dst, size_t count, FILE* restrict file)
|
||||||
|
{
|
||||||
|
fread(dst, sizeof(uint32_t), count, file);
|
||||||
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
dst[i] = swap32(dst[i]);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readBeU16(uint16_t* restrict dst, size_t count, FILE* restrict file)
|
||||||
|
{
|
||||||
|
fread(dst, sizeof(uint16_t), count, file);
|
||||||
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
dst[i] = swap16(dst[i]);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readBeS16(int16_t* restrict dst, size_t count, FILE* restrict file)
|
||||||
|
{
|
||||||
|
fread(dst, sizeof(int16_t), count, file);
|
||||||
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
dst[i] = (int16_t)swap16((uint16_t)dst[i]);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int16_t* pcm;
|
||||||
|
size_t pcmSize;
|
||||||
|
uint32_t rate;
|
||||||
|
} PcmFile;
|
||||||
|
|
||||||
|
#define PCMFILE_CLEAR() (PcmFile){ NULL, 0, 0 }
|
||||||
|
|
||||||
|
static int loadDsp(const char* path, PcmFile* out)
|
||||||
|
{
|
||||||
|
FILE* file = fopen(path, "rb");
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "File not found\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* adpcm = NULL;
|
||||||
|
out->pcm = NULL;
|
||||||
|
|
||||||
|
DspHeader dsp;
|
||||||
|
readBeU32(&dsp.numSamples, 1, file);
|
||||||
|
readBeU32(&dsp.numNibbles, 1, file);
|
||||||
|
readBeU32(&dsp.sampleRate, 1, file);
|
||||||
|
readBeU16(&dsp.loopFlag, 1, file);
|
||||||
|
readBeU16(&dsp.format, 1, file);
|
||||||
|
readBeU32(&dsp.loopBeg, 1, file);
|
||||||
|
readBeU32(&dsp.loopEnd, 1, file);
|
||||||
|
readBeU32(&dsp.curAddress, 1, file);
|
||||||
|
readBeS16(dsp.coefs, 16, file);
|
||||||
|
readBeU16(&dsp.gain, 1, file);
|
||||||
|
readBeU16(&dsp.predScale, 1, file);
|
||||||
|
readBeS16(dsp.history, 2, file);
|
||||||
|
readBeU16(&dsp.loopPredScale, 1, file);
|
||||||
|
readBeS16(dsp.loopHistory, 2, file);
|
||||||
|
readBeS16(&dsp.channels, 1, file);
|
||||||
|
readBeU16(&dsp.blockSize, 1, file);
|
||||||
|
readBeU16(dsp.reserved1, 9, file);
|
||||||
|
|
||||||
|
if (dsp.loopFlag > 1 || dsp.format)
|
||||||
|
goto Fail;
|
||||||
|
|
||||||
|
size_t adpcmSize = getBytesForAdpcmBuffer(dsp.numSamples);
|
||||||
|
adpcm = malloc(adpcmSize);
|
||||||
|
if (!adpcm)
|
||||||
|
goto Fail;
|
||||||
|
out->pcmSize = getBytesForPcmBuffer(dsp.numSamples);
|
||||||
|
out->pcm = malloc(out->pcmSize * sizeof(int16_t));
|
||||||
|
if (!out->pcm)
|
||||||
|
goto Fail;
|
||||||
|
|
||||||
|
fread(adpcm, 1, adpcmSize, file);
|
||||||
|
fclose(file);
|
||||||
|
file = NULL;
|
||||||
|
|
||||||
|
ADPCMINFO adpcmInfo =
|
||||||
|
{
|
||||||
|
.gain = dsp.gain,
|
||||||
|
.pred_scale = dsp.predScale,
|
||||||
|
.yn1 = dsp.history[0],
|
||||||
|
.yn2 = dsp.history[1],
|
||||||
|
.loop_pred_scale = dsp.loopPredScale,
|
||||||
|
.loop_yn1 = dsp.loopHistory[0],
|
||||||
|
.loop_yn2 = dsp.loopHistory[1]
|
||||||
|
};
|
||||||
|
memcpy(adpcmInfo.coef, dsp.coefs, sizeof(int16_t) * 16);
|
||||||
|
decode(adpcm, out->pcm, &adpcmInfo, dsp.numSamples);
|
||||||
|
|
||||||
|
free(adpcm);
|
||||||
|
|
||||||
|
out->rate = dsp.sampleRate;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
Fail:
|
||||||
|
free(out->pcm);
|
||||||
|
free(adpcm);
|
||||||
|
if (file)
|
||||||
|
fclose(file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usage(const char* argv0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s <in.dsp> [inR.dsp] [-o out.wav]\n", argv0);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
// Parse cli arguments
|
||||||
|
char* inPathL = NULL, * inPathR = NULL, * outPath = NULL;
|
||||||
|
bool opt = false, outPathAlloc = false;
|
||||||
|
for (int i = 1; i < argc; ++i)
|
||||||
|
{
|
||||||
|
if (opt)
|
||||||
|
{
|
||||||
|
if (outPath)
|
||||||
|
usage(argv[0]);
|
||||||
|
outPath = argv[i];
|
||||||
|
opt = false;
|
||||||
|
}
|
||||||
|
else if (argv[i][0] == '-')
|
||||||
|
{
|
||||||
|
if (argv[i][1] != 'o')
|
||||||
|
usage(argv[0]);
|
||||||
|
opt = true;
|
||||||
|
}
|
||||||
|
else if (!inPathL)
|
||||||
|
{
|
||||||
|
inPathL = argv[i];
|
||||||
|
}
|
||||||
|
else if (!inPathR)
|
||||||
|
{
|
||||||
|
inPathR = argv[i];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
usage(argv[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt || !inPathL)
|
||||||
|
usage(argv[0]);
|
||||||
|
|
||||||
|
// C
|
||||||
|
if (!outPath)
|
||||||
|
{
|
||||||
|
const char* base = strrchr(inPathL, '/');
|
||||||
|
#ifdef _WIN32
|
||||||
|
const char* base2 = strrchr(inPathL, '\\');
|
||||||
|
if (base2 && base2 > base)
|
||||||
|
base = base2;
|
||||||
|
#endif
|
||||||
|
if (!base)
|
||||||
|
base = inPathL;
|
||||||
|
char* ext = strrchr(base, '.');
|
||||||
|
size_t nameLen = strlen(inPathL);
|
||||||
|
if (ext)
|
||||||
|
nameLen -= strlen(ext);
|
||||||
|
outPath = malloc(nameLen + 5);
|
||||||
|
if (!outPath)
|
||||||
|
return 2;
|
||||||
|
memcpy(outPath, inPathL, nameLen);
|
||||||
|
memcpy(outPath + nameLen, ".wav", 5);
|
||||||
|
outPathAlloc = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
PcmFile left = PCMFILE_CLEAR(), right = PCMFILE_CLEAR();
|
||||||
|
if ((ret = loadDsp(inPathL, &left)))
|
||||||
|
goto Cleanup;
|
||||||
|
if (inPathR)
|
||||||
|
{
|
||||||
|
if ((ret = loadDsp(inPathR, &right)))
|
||||||
|
goto Cleanup;
|
||||||
|
if (left.pcmSize != right.pcmSize || left.rate != right.rate)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Rate or length mismatch");
|
||||||
|
ret = 1;
|
||||||
|
goto Cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
WaveSpec wav =
|
||||||
|
{
|
||||||
|
.format = WAVE_FMT_PCM,
|
||||||
|
.channels = 2,
|
||||||
|
.rate = left.rate,
|
||||||
|
.bytedepth = sizeof(int16_t)
|
||||||
|
};
|
||||||
|
if ((ret = waveWriteBlockFile(&wav, (const void*[2]){left.pcm, right.pcm}, left.pcmSize, outPath)))
|
||||||
|
goto Cleanup;
|
||||||
|
|
||||||
|
free(right.pcm);
|
||||||
|
right = PCMFILE_CLEAR();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WaveSpec wav =
|
||||||
|
{
|
||||||
|
.format = WAVE_FMT_PCM,
|
||||||
|
.channels = 1,
|
||||||
|
.rate = left.rate,
|
||||||
|
.bytedepth = sizeof(int16_t)
|
||||||
|
};
|
||||||
|
if ((ret = waveWriteFile(&wav, left.pcm, left.pcmSize, outPath)))
|
||||||
|
goto Cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(left.pcm);
|
||||||
|
left = PCMFILE_CLEAR();
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
Cleanup:
|
||||||
|
free(right.pcm);
|
||||||
|
free(left.pcm);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ project(DspTool LANGUAGES C)
|
|||||||
|
|
||||||
option(BUILD_SHARED_LIBS "Build as a Shared Object or DLL" OFF)
|
option(BUILD_SHARED_LIBS "Build as a Shared Object or DLL" OFF)
|
||||||
|
|
||||||
set(HEADERS common.h dsptool.h)
|
set(HEADERS ../common.h dsptool.h)
|
||||||
set(SOURCES math.c decode.c encode.c)
|
set(SOURCES math.c decode.c encode.c)
|
||||||
|
|
||||||
add_library(DspTool ${HEADERS} ${SOURCES})
|
add_library(DspTool ${HEADERS} ${SOURCES})
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
#ifndef COMMON_H
|
|
||||||
#define COMMON_H
|
|
||||||
|
|
||||||
#define MIN(a,b) (((a)<(b))?(a):(b))
|
|
||||||
#define MAX(a,b) (((a)>(b))?(a):(b))
|
|
||||||
|
|
||||||
#endif//COMMON_H
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/* (c) 2017 Alex Barney (MIT) */
|
/* (c) 2017 Alex Barney (MIT) */
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "common.h"
|
#include "../common.h"
|
||||||
#include "dsptool.h"
|
#include "dsptool.h"
|
||||||
|
|
||||||
static inline uint8_t GetHighNibble(uint8_t value)
|
static inline uint8_t GetHighNibble(uint8_t value)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "common.h"
|
#include "../common.h"
|
||||||
#include "dsptool.h"
|
#include "dsptool.h"
|
||||||
|
|
||||||
/* Temporal Vector
|
/* Temporal Vector
|
||||||
|
|||||||
149
dsptools/wave.c
Normal file
149
dsptools/wave.c
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
/* wave.c (c) 2023 a dinosaur (zlib) */
|
||||||
|
|
||||||
|
#include "wave.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#define FOURCC_RIFF "RIFF"
|
||||||
|
#define FOURCC_WAVE "WAVE"
|
||||||
|
#define FOURCC_FORM "fmt "
|
||||||
|
#define FOURCC_SAMP "smpl"
|
||||||
|
#define FOURCC_DATA "data"
|
||||||
|
|
||||||
|
#define FORMAT_CHUNK_SIZE 16
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint16_t format;
|
||||||
|
uint16_t channels;
|
||||||
|
uint32_t samplerate;
|
||||||
|
uint32_t byterate;
|
||||||
|
uint16_t alignment;
|
||||||
|
uint16_t bitdepth;
|
||||||
|
|
||||||
|
} FormatChunk;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t id;
|
||||||
|
uint32_t type;
|
||||||
|
uint32_t loopstart;
|
||||||
|
uint32_t loopend;
|
||||||
|
uint32_t fraction;
|
||||||
|
uint32_t playcount;
|
||||||
|
|
||||||
|
} SampleLoopChunk;
|
||||||
|
|
||||||
|
#define SMPL_CHUNK_HEAD_SIZE 24
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t manufacturer;
|
||||||
|
uint32_t product;
|
||||||
|
uint32_t samplePeriod;
|
||||||
|
uint32_t midiUniNote;
|
||||||
|
uint32_t midiPitchFrac;
|
||||||
|
uint32_t smpteFormat;
|
||||||
|
uint32_t smpteOffset;
|
||||||
|
uint32_t sampleLoopCount;
|
||||||
|
uint32_t sampleData;
|
||||||
|
|
||||||
|
} SamplerChunk;
|
||||||
|
|
||||||
|
static void writeFourcc(const WaveStreamCb* restrict cb, void* restrict user, const char fourcc[4])
|
||||||
|
{
|
||||||
|
cb->write(user, fourcc, 1, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writeU32le(const WaveStreamCb* restrict cb, void* restrict user, uint32_t v)
|
||||||
|
{
|
||||||
|
uint32_t tmp = SWAP_LE32(v);
|
||||||
|
cb->write(user, &tmp, sizeof(uint32_t), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writeU16le(const WaveStreamCb* restrict cb, void* restrict user, uint16_t v)
|
||||||
|
{
|
||||||
|
uint16_t tmp = SWAP_LE16(v);
|
||||||
|
cb->write(user, &tmp, sizeof(uint16_t), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeRiffChunk(const WaveStreamCb* restrict cb, void* restrict user, char fourcc[4], uint32_t size)
|
||||||
|
{
|
||||||
|
writeFourcc(cb, user, fourcc);
|
||||||
|
writeU32le(cb, user, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeFormatChunk(const WaveStreamCb* restrict cb, void* restrict user, const FormatChunk* fmt)
|
||||||
|
{
|
||||||
|
writeRiffChunk(cb, user, FOURCC_FORM, FORMAT_CHUNK_SIZE);
|
||||||
|
writeU16le(cb, user, fmt->format);
|
||||||
|
writeU16le(cb, user, fmt->channels);
|
||||||
|
writeU32le(cb, user, fmt->samplerate);
|
||||||
|
writeU32le(cb, user, fmt->byterate);
|
||||||
|
writeU16le(cb, user, fmt->alignment);
|
||||||
|
writeU16le(cb, user, fmt->bitdepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int waveWriteHeader(const WaveSpec* spec, size_t dataLen, const WaveStreamCb* cb, void* user)
|
||||||
|
{
|
||||||
|
if (!spec || !dataLen || dataLen >= UINT32_MAX || !cb || !cb->write)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (spec->format != WAVE_FMT_PCM)
|
||||||
|
return 1;
|
||||||
|
if (spec->channels <= 0 || spec->channels >= INT16_MAX)
|
||||||
|
return 1;
|
||||||
|
if (spec->format == WAVE_FMT_PCM && (spec->bytedepth <= 0 || spec->bytedepth > 4))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// write riff container
|
||||||
|
writeRiffChunk(cb, user, FOURCC_RIFF, sizeof(uint32_t) * 5 + FORMAT_CHUNK_SIZE + (uint32_t)dataLen);
|
||||||
|
writeFourcc(cb, user, FOURCC_WAVE);
|
||||||
|
|
||||||
|
writeFormatChunk(cb, user, &(const FormatChunk)
|
||||||
|
{
|
||||||
|
.format = spec->format,
|
||||||
|
.channels = (uint16_t)spec->channels,
|
||||||
|
.samplerate = spec->rate,
|
||||||
|
.byterate = spec->rate * spec->channels * spec->bytedepth,
|
||||||
|
.alignment = (uint16_t)(spec->channels * spec->bytedepth),
|
||||||
|
.bitdepth = (uint16_t)spec->bytedepth * 8
|
||||||
|
});
|
||||||
|
|
||||||
|
// write data chunk
|
||||||
|
writeFourcc(cb, user, FOURCC_DATA);
|
||||||
|
writeU32le(cb, user, (uint32_t)dataLen);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int waveWrite(const WaveSpec* spec, const void* data, size_t dataLen, const WaveStreamCb* cb, void* user)
|
||||||
|
{
|
||||||
|
if (!data)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// Write RIFF/Wave header and raw interleaved samples
|
||||||
|
int res = waveWriteHeader(spec, dataLen, cb, user);
|
||||||
|
if (res)
|
||||||
|
return res;
|
||||||
|
//FIXME: not endian safe
|
||||||
|
cb->write(user, data, 1, dataLen);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int waveWriteBlock(const WaveSpec* spec, const void* blocks[], size_t blockLen, const WaveStreamCb* cb, void* user)
|
||||||
|
{
|
||||||
|
if (!blocks)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// Write RIFF/Wave header to file
|
||||||
|
int res = waveWriteHeader(spec, blockLen * spec->channels, cb, user);
|
||||||
|
if (res)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
// Copy & interleave
|
||||||
|
//FIXME: not endian safe
|
||||||
|
for (size_t i = 0; i < blockLen / spec->bytedepth; ++i)
|
||||||
|
for (int j = 0; j < spec->channels; ++j)
|
||||||
|
cb->write(user, &((const char**)blocks)[j][i * spec->bytedepth], spec->bytedepth, 1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
45
dsptools/wave.h
Normal file
45
dsptools/wave.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#ifndef WAVE_H
|
||||||
|
#define WAVE_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
WAVE_FMT_PCM = 0x0001,
|
||||||
|
WAVE_FMT_IEEE_FLOAT = 0x0003,
|
||||||
|
WAVE_FMT_ALAW = 0x0006,
|
||||||
|
WAVE_FMT_MULAW = 0x0007,
|
||||||
|
WAVE_FMT_EXTENSIBLE = 0xFFFE
|
||||||
|
} WaveFormat;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
WaveFormat format;
|
||||||
|
int channels;
|
||||||
|
unsigned rate;
|
||||||
|
int bytedepth;
|
||||||
|
} WaveSpec;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
size_t (*read)(void* restrict user, void* restrict out, size_t size, size_t num);
|
||||||
|
size_t (*write)(void* restrict user, const void* restrict src, size_t size, size_t num);
|
||||||
|
void (*seek)(void* restrict user, int offset);
|
||||||
|
size_t (*tell)(void* user);
|
||||||
|
int (*eof)(void* user);
|
||||||
|
} WaveStreamCb;
|
||||||
|
|
||||||
|
int waveWrite(const WaveSpec* spec, const void* data, size_t dataLen, const WaveStreamCb* cb, void* user);
|
||||||
|
int waveWriteFile(const WaveSpec* spec, const void* data, size_t dataLen, const char* path);
|
||||||
|
int waveWriteBlock(const WaveSpec* spec, const void* blocks[], size_t blockLen, const WaveStreamCb* cb, void* user);
|
||||||
|
int waveWriteBlockFile(const WaveSpec* spec, const void* blocks[], size_t blockLen, const char* path);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif//WAVE_H
|
||||||
57
dsptools/wavefile.c
Normal file
57
dsptools/wavefile.c
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/* wavefile.c (c) 2023 a dinosaur (zlib) */
|
||||||
|
|
||||||
|
#include "wave.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static size_t waveFileRead(void* restrict user, void* restrict out, size_t size, size_t num)
|
||||||
|
{
|
||||||
|
return fread(out, size, num, (FILE*)user);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t waveFileWrite(void* restrict user, const void* restrict src, size_t size, size_t num)
|
||||||
|
{
|
||||||
|
return fwrite(src, size, num, (FILE*)user);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void waveFileSeek(void* restrict user, int offset)
|
||||||
|
{
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
fseek((FILE*)user, (off_t)offset, SEEK_CUR);
|
||||||
|
#else
|
||||||
|
fseek((FILE*)user, (long)offset, SEEK_CUR);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t waveFileTell(void* user)
|
||||||
|
{
|
||||||
|
return ftell((FILE*)user);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int waveFileEof(void* user)
|
||||||
|
{
|
||||||
|
return feof((FILE*)user) || ferror((FILE*)user);
|
||||||
|
}
|
||||||
|
|
||||||
|
int waveWriteFile(const WaveSpec* spec, const void* data, size_t dataLen, const char* path)
|
||||||
|
{
|
||||||
|
FILE* file = fopen(path, "wb");
|
||||||
|
if (!file)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
const WaveStreamCb cb = { .write = waveFileWrite };
|
||||||
|
int res = waveWrite(spec, data, dataLen, &cb, (void*)file);
|
||||||
|
fclose(file);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int waveWriteBlockFile(const WaveSpec* spec, const void* blocks[], size_t blockLen, const char* path)
|
||||||
|
{
|
||||||
|
FILE* file = fopen(path, "wb");
|
||||||
|
if (!file)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
const WaveStreamCb cb = { .write = waveFileWrite };
|
||||||
|
int res = waveWriteBlock(spec, blocks, blockLen, &cb, (void*)file);
|
||||||
|
fclose(file);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user