1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2025-08-09 00:00:44 +00:00

[Audio 2/?] Extract audio samples to wav (#2020)

* [Audio 2/?] Extract audio samples to wav

Co-authored-by: zelda2774 <69368340+zelda2774@users.noreply.github.com>

* How

* Hopefully fix warning I don't get locally

* Pad default sample filenames, comment on the vadpcm frame encoder functions, other suggested changes

* Small tweaks to above

* Remove some obsolete code

---------

Co-authored-by: zelda2774 <69368340+zelda2774@users.noreply.github.com>
This commit is contained in:
Tharo 2024-08-09 03:39:18 +01:00 committed by GitHub
parent 1021c482af
commit ef329e633a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 4317 additions and 112 deletions

View file

@ -0,0 +1,38 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef CODEC_H_
#define CODEC_H_
#include <stdint.h>
#include "../container/container.h"
#include "vadpcm.h"
#include "uncompressed.h"
typedef struct enc_dec_opts {
// Matching
bool matching;
// VADPCM options
bool truncate;
uint32_t min_loop_length;
table_design_spec design;
} enc_dec_opts;
typedef struct codec_spec {
const char *name;
sample_data_type type;
int frame_size;
bool compressed;
int (*decode)(container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
int (*encode)(container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
} codec_spec;
#endif

View file

@ -0,0 +1,56 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include "../util.h"
#include "codec.h"
#include "../container/container.h"
int
pcm16_enc_dec(UNUSED container_data *ctnr, UNUSED const codec_spec *codec, UNUSED const enc_dec_opts *opts)
{
// Since we decode to and encode from pcm16, there's nothing to do.
return 0;
}
// TODO
int
pcm8_dec(UNUSED container_data *ctnr, UNUSED const codec_spec *codec, UNUSED const enc_dec_opts *opts)
{
#if 0
for (size_t i = 0; i < num_samples; i++) {
uint8_t insamp = ((uint8_t *)in)[i];
int16_t outsamp = insamp << 8; // - 0x80 before shift ?
((int16_t *)out)[i] = outsamp;
}
#endif
return 0;
}
// TODO
int
pcm8_enc(UNUSED container_data *ctnr, UNUSED const codec_spec *codec, UNUSED const enc_dec_opts *opts)
{
#if 0
for (size_t i = 0; i < num_samples; i++) {
uint16_t insamp = ((uint16_t *)in)[i];
uint8_t outsamp = insamp >> 8;
((uint8_t *)out)[i] = outsamp;
}
#endif
return 0;
}

View file

@ -0,0 +1,21 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef CODEC_UNCOMPRESSED_H
#define CODEC_UNCOMPRESSED_H
int
pcm16_dec(struct container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
int
pcm16_enc(struct container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
int
pcm16_enc_dec(struct container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,50 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: CC0-1.0
*/
#ifndef CODEC_VADPCM_H
#define CODEC_VADPCM_H
#include <stdbool.h>
#include <stdint.h>
#define VADPCM_BOOK_SIZE(order, npredictors) (8 * (order) * (npredictors))
#define VADPCM_BOOK_SIZE_BYTES(order, npredictors) (sizeof(int16_t) * VADPCM_BOOK_SIZE(order, npredictors))
typedef struct {
int16_t order;
int16_t npredictors;
} ALADPCMbookhead;
typedef int16_t ALADPCMbookstate[];
typedef struct {
uint32_t start;
uint32_t end;
uint32_t count;
int16_t state[16];
} ALADPCMloop;
typedef struct {
unsigned int order;
unsigned int bits;
unsigned int refine_iters;
double thresh;
unsigned int frame_size;
} table_design_spec;
int
tabledesign_run(int16_t *order_out, int16_t *npredictors_out, int16_t **book_data_out, void *sample_data,
size_t num_samples, const table_design_spec *design);
struct container_data;
struct codec_spec;
struct enc_dec_opts;
int
vadpcm_enc(struct container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
int
vadpcm_dec(struct container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
#endif

View file

@ -0,0 +1,655 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "../util.h"
#include "vadpcm.h"
// Levinson-Durbin algorithm for iteratively solving for prediction coefficients
// https://en.wikipedia.org/wiki/Levinson_recursion
static int
durbin(double *acvec, int order, double *reflection_coeffs, double *prediction_coeffs, double *error)
{
int i, j;
double sum, E;
int ret;
prediction_coeffs[0] = 1.0;
E = acvec[0]; // E[0] = r{xx}[0]
ret = 0;
for (i = 1; i <= order; i++) {
// SUM(j, a[i-1][j] * r{xx}[i - j] )
sum = 0.0;
for (j = 1; j <= i - 1; j++) {
sum += prediction_coeffs[j] * acvec[i - j];
}
// a[i][i] = -Delta[i-1] / E[i-1]
prediction_coeffs[i] = (E > 0.0) ? (-(acvec[i] + sum) / E) : 0.0;
// k[i] = a[i][i]
reflection_coeffs[i] = prediction_coeffs[i];
if (fabs(reflection_coeffs[i]) > 1.0) {
// incr when a predictor coefficient is > 1 (indicates numerical instability)
ret++;
}
for (j = 1; j < i; j++) {
// a[i][j] = a[i-1][j] + a[i-1][i - j] * a[i][i]
prediction_coeffs[j] += prediction_coeffs[i - j] * prediction_coeffs[i];
}
// E[i] = E[i-1] * (1.0 - k[i] ** 2)
// = E[i-1] * (1.0 - a[i][i] ** 2)
E *= 1.0 - prediction_coeffs[i] * prediction_coeffs[i];
}
*error = E;
return ret;
}
// Reflection coefficients (k) -> Predictor coefficients (a)
// A subset of Levinson-Durbin that only computes predictors from known reflection coefficients and previous predictors
static void
afromk(double *k, double *ai, int order)
{
int i, j;
ai[0] = 1.0;
for (i = 1; i <= order; i++) {
// a[i][i] = k[i]
ai[i] = k[i];
for (j = 1; j <= i - 1; j++) {
// a[i][j] = a[i-1][j] + a[i-1][i - j] * k[i] = a[i-1][j] + a[i-1][i - j] * a[i][i]
ai[j] += ai[i - j] * ai[i];
}
}
}
// Prediction coefficients (a) -> Reflection coefficients (k)
// Performs afromk in reverse?
// Returns 0 if numerically stable, otherwise returns non-zero
static int
kfroma(double *in, double *out, int order)
{
int i, j;
double div;
double temp;
double next[(order + 1)];
int ret = 0;
out[order] = in[order];
for (i = order - 1; i >= 1; i--) {
for (j = 0; j <= i; j++) {
temp = out[i + 1];
div = 1.0 - temp * temp;
if (div == 0.0) {
return 1;
}
next[j] = (in[j] - in[i - j + 1] * temp) / div;
}
for (j = 0; j <= i; j++) {
in[j] = next[j];
}
out[i] = next[i];
if (fabs(out[i]) > 1.0) {
// Not numerically stable
ret++;
}
}
return ret;
}
// autocorrelation (r{xx}) from predictors (a) ?
static void
rfroma(double *in, int n, double *out)
{
int i, j;
double mat[n + 1][n + 1];
double div;
mat[n][0] = 1.0;
for (i = 1; i <= n; i++) {
mat[n][i] = -in[i];
}
for (i = n; i >= 1; i--) {
div = 1.0 - mat[i][i] * mat[i][i];
for (j = 1; j <= i - 1; j++) {
mat[i - 1][j] = (mat[i][i - j] * mat[i][i] + mat[i][j]) / div;
}
}
out[0] = 1.0;
for (i = 1; i <= n; i++) {
out[i] = 0.0;
for (j = 1; j <= i; j++) {
out[i] += mat[i][j] * out[i - j];
}
}
}
static double
model_dist(double *predictors, double *data, int order)
{
double autocorrelation_data[order + 1];
double autocorrelation_predictors[order + 1];
double ret;
int i, j;
// autocorrelation from data
rfroma(data, order, autocorrelation_data);
// autocorrelation from predictors
for (i = 0; i <= order; i++) {
autocorrelation_predictors[i] = 0.0;
for (j = 0; j <= order - i; j++) {
autocorrelation_predictors[i] += predictors[j] * predictors[i + j];
}
}
// compute "model distance" (scaled L2 norm: 2 * inner(ac1, ac2) )
ret = autocorrelation_data[0] * autocorrelation_predictors[0];
for (i = 1; i <= order; i++) {
ret += 2 * autocorrelation_data[i] * autocorrelation_predictors[i];
}
return ret;
}
// Calculate the autocorrelation matrix of two vectors at x and x - xlen
// https://en.wikipedia.org/wiki/Autocorrelation
static void
acmat(int16_t *x, int order, int xlen, double **ac)
{
int i, j, k;
for (i = 1; i <= order; i++) {
for (j = 1; j <= order; j++) {
// R{xx}[i,j] = E[X[i] * X[j]]
ac[i][j] = 0.0;
for (k = 0; k < xlen; k++) {
ac[i][j] += x[k - i] * x[k - j];
}
}
}
}
// Computes the autocorrelation vector of two vectors at x and x - xlen
static void
acvect(int16_t *x, int order, int xlen, double *ac)
{
int i, j;
for (i = 0; i <= order; i++) {
ac[i] = 0.0;
// r{xx} = E(x(m)x) = SUM(j, x[j - i] * x[j])
for (j = 0; j < xlen; j++) {
ac[i] -= x[j - i] * x[j];
}
}
}
/**
* Lower-Upper (with Permutation vector) (LUP) Decomposition
*
* Replaces a real n-by-n matrix "a" with the LU decomposition of a row-wise
* permutation of itself.
*
* Input parameters:
* a: The matrix which is operated on. 1-indexed; it should be of size
* (n+1) x (n+1), and row/column index 0 is not used.
* n: The size of the matrix.
*
* Output parameters:
* indx: The row permutation performed. 1-indexed; it should be of size n+1,
* and index 0 is not used.
* d: the determinant of the permutation matrix.
*
* Returns 1 to indicate failure if the matrix is singular or has zeroes on the
* diagonal, 0 on success.
*
* Derived from ludcmp in "Numerical Recipes in C: The Art of Scientific Computing",
* with modified error handling.
*/
static int
lud(double **a, int n, int *perm, int *d)
{
int i, imax = 0, j, k;
double big, dum, sum, temp;
double min, max;
double vv[n + 1];
*d = 1;
for (i = 1; i <= n; i++) {
big = 0.0;
for (j = 1; j <= n; j++)
if ((temp = fabs(a[i][j])) > big)
big = temp;
if (big == 0.0)
return 1;
vv[i] = 1.0 / big;
}
for (j = 1; j <= n; j++) {
for (i = 1; i < j; i++) {
sum = a[i][j];
for (k = 1; k < i; k++)
sum -= a[i][k] * a[k][j];
a[i][j] = sum;
}
big = 0.0;
for (i = j; i <= n; i++) {
sum = a[i][j];
for (k = 1; k < j; k++)
sum -= a[i][k] * a[k][j];
a[i][j] = sum;
if ((dum = vv[i] * fabs(sum)) >= big) {
big = dum;
imax = i;
}
}
if (j != imax) {
for (k = 1; k <= n; k++) {
dum = a[imax][k];
a[imax][k] = a[j][k];
a[j][k] = dum;
}
*d = -(*d);
vv[imax] = vv[j];
}
perm[j] = imax;
if (a[j][j] == 0.0)
return 1;
if (j != n) {
dum = 1.0 / (a[j][j]);
for (i = j + 1; i <= n; i++)
a[i][j] *= dum;
}
}
min = 1e10;
max = 0.0;
for (i = 1; i <= n; i++) {
temp = fabs(a[i][i]);
if (temp < min)
min = temp;
if (temp > max)
max = temp;
}
return (min / max < 1e-10) ? 1 : 0;
}
/**
* Solves the set of n linear equations Ax = b, using LU decomposition back-substitution.
*
* Input parameters:
* a: The LU decomposition of a matrix, created by "lud".
* n: The size of the matrix.
* indx: Row permutation vector, created by "lud".
* b: The vector b in the equation. 1-indexed; is should be of size n+1, and index 0 is not used.
*
* Output parameters:
* b: The output vector x. 1-indexed.
*
* From "Numerical Recipes in C: The Art of Scientific Computing".
*/
static void
lubksb(double **a, int n, int *perm, double *b)
{
int i, ii = 0, ip, j;
double sum;
for (i = 1; i <= n; i++) {
ip = perm[i];
sum = b[ip];
b[ip] = b[i];
if (ii) {
for (j = ii; j <= i - 1; j++)
sum -= a[i][j] * b[j];
} else if (sum) {
ii = i;
}
b[i] = sum;
}
for (i = n; i >= 1; i--) {
sum = b[i];
for (j = i + 1; j <= n; j++)
sum -= a[i][j] * b[j];
b[i] = sum / a[i][i];
}
}
static void
split(double **predictors, double *delta, int order, int npredictors, double scale)
{
int i, j;
for (i = 0; i < npredictors; i++) {
for (j = 0; j <= order; j++) {
predictors[i + npredictors][j] = predictors[i][j] + delta[j] * scale;
}
}
}
static void
refine(double **predictors, int order, int npredictors, double *data, int data_size, int refine_iters)
{
int iter;
double dist;
double dummy;
double best_value;
int best_index;
int i, j;
double rsums[npredictors][order + 1];
int counts[npredictors];
double vec[order + 1];
for (iter = 0; iter < refine_iters; iter++) {
// For some number of refinement iterations
// Initialize averages
memset(counts, 0, npredictors * sizeof(int));
memset(rsums, 0, npredictors * (order + 1) * sizeof(double));
// Sum autocorrelations
for (i = 0; i < data_size; i++) {
best_value = 1e30;
best_index = 0;
// Find index that minimizes the "model distance"
for (j = 0; j < npredictors; j++) {
dist = model_dist(predictors[j], &data[(order + 1) * i], order);
if (dist < best_value) {
best_value = dist;
best_index = j;
}
}
counts[best_index]++;
rfroma(&data[(order + 1) * i], order, vec); // compute autocorrelation from predictors
for (j = 0; j <= order; j++)
rsums[best_index][j] += vec[j]; // add to average autocorrelation
}
// finalize average autocorrelations
for (i = 0; i < npredictors; i++) {
if (counts[i] > 0) {
for (j = 0; j <= order; j++) {
rsums[i][j] /= counts[i];
}
}
}
for (i = 0; i < npredictors; i++) {
// compute predictors from average autocorrelation
durbin(rsums[i], order, vec, predictors[i], &dummy);
// vec is reflection coeffs
// clamp reflection coeffs
for (j = 1; j <= order; j++) {
if (vec[j] >= 1.0)
vec[j] = 0.9999999999;
if (vec[j] <= -1.0)
vec[j] = -0.9999999999;
}
// clamped reflection coeffs -> predictors
afromk(vec, predictors[i], order);
}
}
}
static int
read_row(int16_t *p, double *row, int order)
{
double fval;
int ival;
int i, j, k;
int overflows;
double table[8][order];
for (i = 0; i < order; i++) {
for (j = 0; j < i; j++)
table[i][j] = 0.0;
for (j = i; j < order; j++)
table[i][j] = -row[order - j + i];
}
for (i = order; i < 8; i++)
for (j = 0; j < order; j++)
table[i][j] = 0.0;
for (i = 1; i < 8; i++)
for (j = 1; j <= order; j++)
if (i - j >= 0)
for (k = 0; k < order; k++)
table[i][k] -= row[j] * table[i - j][k];
overflows = 0;
for (i = 0; i < order; i++) {
for (j = 0; j < 8; j++) {
fval = table[j][i] * (double)(1 << 11);
if (fval < 0.0) {
ival = (int)(fval - 0.5);
if (ival < -0x8000)
overflows++;
} else {
ival = (int)(fval + 0.5);
if (ival >= 0x8000)
overflows++;
}
*(p++) = ival;
}
}
return overflows;
}
int
tabledesign_run(int16_t *order_out, int16_t *npredictors_out, int16_t **book_data_out, void *sample_data,
size_t num_samples, const table_design_spec *design)
{
static const table_design_spec default_design = {
.order = 2,
.bits = 2,
.refine_iters = 2,
.thresh = 10.0,
.frame_size = 16,
};
if (design == NULL)
design = &default_design;
int16_t order = design->order;
int16_t npredictors = 1 << design->bits;
unsigned int frame_size = design->frame_size;
int num_order = order + 1;
double vec[num_order];
int perm[num_order];
double reflection_coeffs[num_order];
int16_t *buffer = MALLOC_CHECKED_INFO(2 * frame_size * sizeof(int16_t), "frame_size=%u", frame_size);
double **predictors = MALLOC_CHECKED_INFO(npredictors * sizeof(double *), "npredictors=%d", npredictors);
for (int i = 0; i < npredictors; i++)
predictors[i] = MALLOC_CHECKED_INFO(num_order * sizeof(double), "npredictors=%d", npredictors);
double **autocorrelation_matrix = MALLOC_CHECKED_INFO(num_order * sizeof(double *), "num_order=%d", num_order);
for (int i = 0; i < num_order; i++)
autocorrelation_matrix[i] = MALLOC_CHECKED_INFO(num_order * sizeof(double), "num_order=%d", num_order);
size_t nframes = num_samples - (num_samples % frame_size);
double *data =
MALLOC_CHECKED_INFO(nframes * num_order * sizeof(double), "nframes=%lu, num_order=%d", nframes, num_order);
uint32_t data_size = 0;
int16_t *sample = sample_data;
// (back-)align to a multiple of the frame size
int16_t *sample_end = sample + nframes;
memset(buffer, 0, frame_size * sizeof(int16_t));
for (; sample < sample_end; sample += frame_size) {
// Copy sample data into second half of buffer, during the first iteration the first half is 0 while in
// later iterations the second half of the previous iteration is shifted into the first half.
memcpy(&buffer[frame_size], sample, frame_size * sizeof(int16_t));
// Compute autocorrelation vector of the two vectors in the buffer
acvect(&buffer[frame_size], order, frame_size, vec);
// First element is the largest(?)
if (fabs(vec[0]) > design->thresh) {
// Over threshold
// Computes the autocorrelation matrix of the two vectors in the buffer
acmat(&buffer[frame_size], order, frame_size, autocorrelation_matrix);
// Compute the LUP decomposition of the autocorrelation matrix
int perm_det;
if (lud(autocorrelation_matrix, order, perm, &perm_det) == 0) { // Continue only if numerically stable
// Back-substitution to solve the linear equation Ra = r
// where
// R = autocorrelation matrix
// r = autocorrelation vector
// a = linear prediction coefficients
// After this vec contains the prediction coefficients
lubksb(autocorrelation_matrix, order, perm, vec);
vec[0] = 1.0;
// Compute reflection coefficients from prediction coefficients
if (kfroma(vec, reflection_coeffs, order) == 0) { // Continue only if numerically stable
data[data_size * num_order + 0] = 1.0;
// clamp the reflection coefficients
for (int i = 1; i < num_order; i++) {
if (reflection_coeffs[i] >= 1.0)
reflection_coeffs[i] = 0.9999999999;
if (reflection_coeffs[i] <= -1.0)
reflection_coeffs[i] = -0.9999999999;
}
// Compute prediction coefficients from reflection coefficients
afromk(reflection_coeffs, &data[data_size * num_order], order);
data_size++;
}
}
}
// Move second vector to first vector
memcpy(&buffer[0], &buffer[frame_size], frame_size * sizeof(int16_t));
}
// Create a vector [1.0, 0.0, ..., 0.0]
vec[0] = 1.0;
for (int i = 1; i < num_order; i++)
vec[i] = 0.0;
for (uint32_t i = 0; i < data_size; i++) {
// Compute autocorrelation from predictors
rfroma(&data[i * num_order], order, predictors[0]);
for (int k = 1; k < num_order; k++)
vec[k] += predictors[0][k];
}
for (int i = 1; i < num_order; i++)
vec[i] /= data_size;
// vec is the average autocorrelation
// Compute predictors for average autocorrelation using Levinson-Durbin algorithm
double dummy;
durbin(vec, order, reflection_coeffs, predictors[0], &dummy);
// clamp results
for (int i = 1; i < num_order; i++) {
if (reflection_coeffs[i] >= 1.0)
reflection_coeffs[i] = 0.9999999999;
if (reflection_coeffs[i] <= -1.0)
reflection_coeffs[i] = -0.9999999999;
}
// Convert clamped reflection coefficients to predictors
afromk(reflection_coeffs, predictors[0], order);
// Split and refine predictors
for (unsigned cur_bits = 0; cur_bits < design->bits; cur_bits++) {
double split_delta[num_order];
for (int i = 0; i < num_order; i++)
split_delta[i] = 0.0;
split_delta[order - 1] = -1.0;
split(predictors, split_delta, order, 1 << cur_bits, 0.01);
refine(predictors, order, 1 << (1 + cur_bits), data, data_size, design->refine_iters);
}
int16_t *book_data = MALLOC_CHECKED_INFO((8 * order * npredictors + 2) * sizeof(int16_t),
"order=%d, npredictors=%d", order, npredictors);
*order_out = order;
*npredictors_out = npredictors;
int num_oflow = 0;
for (int i = 0; i < npredictors; i++)
num_oflow += read_row(&book_data[8 * order * i], predictors[i], order);
if (num_oflow) {
error("overflow detected in tabledesign");
}
*book_data_out = book_data;
free(buffer);
free(data);
for (int i = 0; i < num_order; i++)
free(autocorrelation_matrix[i]);
free(autocorrelation_matrix);
for (int i = 0; i < npredictors; i++)
free(predictors[i]);
free(predictors);
return 0;
}

View file

@ -0,0 +1,837 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "../util.h"
#include "../codec/vadpcm.h"
#include "aiff.h"
typedef struct {
int16_t numChannels;
uint16_t numFramesH;
uint16_t numFramesL;
int16_t sampleSize;
uint8_t sampleRate[10]; // 80-bit float
// Followed by compression type + compression name pstring
} aiff_COMM;
typedef struct {
uint16_t nMarkers;
} aiff_MARK;
typedef struct {
uint16_t MarkerID;
uint16_t positionH;
uint16_t positionL;
} Marker;
typedef enum {
LOOP_PLAYMODE_NONE = 0,
LOOP_PLAYMODE_FWD = 1,
LOOP_PLAYMODE_FWD_BWD = 2
} aiff_loop_playmode;
typedef struct {
int16_t playMode; // aiff_loop_playmode
// Marker IDs
int16_t beginLoop;
int16_t endLoop;
} Loop;
typedef struct {
int8_t baseNote;
int8_t detune;
int8_t lowNote;
int8_t highNote;
int8_t lowVelocity;
int8_t highVelocity;
int16_t gain;
Loop sustainLoop;
Loop releaseLoop;
} aiff_INST;
typedef struct {
int32_t offset;
int32_t blockSize;
} aiff_SSND;
static void
read_pstring(FILE *f, char *out)
{
unsigned char len;
// read string length
FREAD(f, &len, sizeof(len));
// read string and null-terminate it
FREAD(f, out, len);
out[len] = '\0';
// pad to 2-byte boundary
if (!(len & 1))
FREAD(f, &len, 1);
}
static char *
read_pstring_alloc(FILE *f)
{
unsigned char len;
// read string length
FREAD(f, &len, sizeof(len));
// alloc
char *out = MALLOC_CHECKED_INFO(len + 1, "len=%u", len);
// read string and null-terminate it
FREAD(f, out, len);
out[len] = '\0';
// pad to 2-byte boundary
if (!(len & 1))
FREAD(f, &len, 1);
return out;
}
static void
write_pstring(FILE *f, const char *in)
{
unsigned char zr = 0;
size_t inlen = strlen(in);
if (inlen > 255)
error("Invalid pstring length.");
unsigned char len = inlen;
CHUNK_WRITE(f, &len);
CHUNK_WRITE_RAW(f, in, len);
if (!(len & 1))
CHUNK_WRITE(f, &zr);
}
static_assert(sizeof(double) == sizeof(uint64_t), "Double is assumed to be 64-bit");
#define F64_GET_SGN(bits) (((bits) >> 63) & 1) // 1-bit
#define F64_GET_EXP(bits) ((((bits) >> 52) & 0x7FF) - 0x3FF) // 15-bit
#define F64_GET_MANT_H(bits) (((bits) >> 32) & 0xFFFFF) // 20-bit
#define F64_GET_MANT_L(bits) ((bits)&0xFFFFFFFF) // 32-bit
static void
f64_to_f80(double f64, uint8_t *f80)
{
union {
uint32_t w[3];
uint8_t b[12];
} f80tmp;
// get f64 bits
uint64_t f64_bits = *(uint64_t *)&f64;
int f64_sgn = F64_GET_SGN(f64_bits);
int f64_exponent = F64_GET_EXP(f64_bits);
uint32_t f64_mantissa_hi = F64_GET_MANT_H(f64_bits);
uint32_t f64_mantissa_lo = F64_GET_MANT_L(f64_bits);
// build f80 words
f80tmp.w[0] = (f64_sgn << 15) | (f64_exponent + 0x3FFF);
f80tmp.w[1] = (1 << 31) | (f64_mantissa_hi << 11) | (f64_mantissa_lo >> 21);
f80tmp.w[2] = f64_mantissa_lo << 11;
// byteswap to BE
f80tmp.w[0] = htobe32(f80tmp.w[0]);
f80tmp.w[1] = htobe32(f80tmp.w[1]);
f80tmp.w[2] = htobe32(f80tmp.w[2]);
// write bytes
for (size_t i = 0; i < 10; i++)
f80[i] = f80tmp.b[i + 2];
}
static void
f80_to_f64(double *f64, uint8_t *f80)
{
union {
uint32_t w[3];
uint8_t b[12];
} f80tmp;
// read bytes
f80tmp.b[0] = f80tmp.b[1] = 0;
for (size_t i = 0; i < 10; i++)
f80tmp.b[i + 2] = f80[i];
// byteswap from BE
f80tmp.w[0] = be32toh(f80tmp.w[0]);
f80tmp.w[1] = be32toh(f80tmp.w[1]);
f80tmp.w[2] = be32toh(f80tmp.w[2]);
// get f64 parts
int f64_sgn = (f80tmp.w[0] >> 15) & 1;
int f64_exponent = (f80tmp.w[0] & 0x7FFF) - 0x3FFF;
uint32_t f64_mantissa_hi = (f80tmp.w[1] >> 11) & 0xFFFFF;
uint32_t f64_mantissa_lo = ((f80tmp.w[1] & 0x7FF) << 21) | (f80tmp.w[2] >> 11);
// build bitwise f64
uint64_t f64_bits = ((uint64_t)f64_sgn << 63) | ((((uint64_t)f64_exponent + 0x3FF) & 0x7FF) << 52) |
((uint64_t)f64_mantissa_hi << 32) | ((uint64_t)f64_mantissa_lo);
// write double
*f64 = *(double *)&f64_bits;
}
int
aiff_aifc_common_read(container_data *out, FILE *in, UNUSED bool matching, uint32_t size)
{
bool has_comm = false;
bool has_inst = false;
bool has_ssnd = false;
size_t num_markers = 0;
container_marker *markers = NULL;
memset(out, 0, sizeof(*out));
while (true) {
long start = ftell(in);
if (start > 8 + size) {
error("Overran file");
}
if (start == 8 + size) {
break;
}
char cc4[4];
uint32_t chunk_size;
FREAD(in, cc4, 4);
FREAD(in, &chunk_size, 4);
chunk_size = be32toh(chunk_size);
chunk_size++;
chunk_size &= ~1;
switch (CC4(cc4[0], cc4[1], cc4[2], cc4[3])) {
case CC4('C', 'O', 'M', 'M'): {
aiff_COMM comm;
FREAD(in, &comm, sizeof(comm));
comm.numChannels = be16toh(comm.numChannels);
comm.numFramesH = be16toh(comm.numFramesH);
comm.numFramesL = be16toh(comm.numFramesL);
comm.sampleSize = be16toh(comm.sampleSize);
uint32_t num_samples = (comm.numFramesH << 16) | comm.numFramesL;
double sample_rate;
f80_to_f64(&sample_rate, comm.sampleRate);
uint32_t comp_type = CC4('N', 'O', 'N', 'E');
if (chunk_size > sizeof(aiff_COMM)) {
uint32_t compressionType;
FREAD(in, &compressionType, sizeof(compressionType));
comp_type = be32toh(compressionType);
}
out->num_channels = comm.numChannels;
out->sample_rate = sample_rate;
out->bit_depth = comm.sampleSize;
out->num_samples = num_samples;
switch (comp_type) {
case CC4('N', 'O', 'N', 'E'):
out->data_type = SAMPLE_TYPE_PCM16;
break;
case CC4('H', 'P', 'C', 'M'):
out->data_type = SAMPLE_TYPE_PCM8;
break;
case CC4('A', 'D', 'P', '9'):
out->data_type = SAMPLE_TYPE_VADPCM;
break;
case CC4('A', 'D', 'P', '5'):
out->data_type = SAMPLE_TYPE_VADPCM_HALF;
break;
default:
error("Unrecognized aiff/aifc compression type");
break;
}
if (chunk_size > sizeof(aiff_COMM) + 4) {
char compression_name[257];
read_pstring(in, compression_name);
}
has_comm = true;
} break;
case CC4('I', 'N', 'S', 'T'): {
aiff_INST inst;
FREAD(in, &inst, sizeof(inst));
inst.gain = be16toh(inst.gain);
inst.sustainLoop.playMode = be16toh(inst.sustainLoop.playMode);
inst.sustainLoop.beginLoop = be16toh(inst.sustainLoop.beginLoop);
inst.sustainLoop.endLoop = be16toh(inst.sustainLoop.endLoop);
inst.releaseLoop.playMode = be16toh(inst.releaseLoop.playMode);
inst.releaseLoop.beginLoop = be16toh(inst.releaseLoop.beginLoop);
inst.releaseLoop.endLoop = be16toh(inst.releaseLoop.endLoop);
out->base_note = inst.baseNote;
out->fine_tune = inst.detune;
out->key_low = inst.lowNote;
out->key_hi = inst.highNote;
out->vel_low = inst.lowVelocity;
out->vel_hi = inst.highVelocity;
out->gain = inst.gain;
size_t nloops = 0;
nloops += inst.sustainLoop.playMode != LOOP_PLAYMODE_NONE;
nloops += inst.releaseLoop.playMode != LOOP_PLAYMODE_NONE;
if (nloops != 0) {
out->loops = MALLOC_CHECKED_INFO(nloops * sizeof(container_loop), "nloops=%lu", nloops);
size_t i = 0;
if (inst.sustainLoop.playMode != LOOP_PLAYMODE_NONE) {
out->loops[i].id = 0;
switch (inst.sustainLoop.playMode) {
case LOOP_PLAYMODE_FWD:
out->loops[i].type = LOOP_FORWARD;
break;
case LOOP_PLAYMODE_FWD_BWD:
out->loops[i].type = LOOP_FORWARD_BACKWARD;
break;
default:
error("Unrecognized PlayMode in sustainLoop");
break;
}
out->loops[i].start = inst.sustainLoop.beginLoop;
out->loops[i].end = inst.sustainLoop.endLoop;
out->loops[i].num = 0xFFFFFFFF;
out->loops[i].fraction = 0;
i++;
}
if (inst.releaseLoop.playMode != LOOP_PLAYMODE_NONE) {
out->loops[i].id = 1;
switch (inst.sustainLoop.playMode) {
case LOOP_PLAYMODE_FWD:
out->loops[i].type = LOOP_FORWARD;
break;
case LOOP_PLAYMODE_FWD_BWD:
out->loops[i].type = LOOP_FORWARD_BACKWARD;
break;
default:
error("Unrecognized PlayMode in releaseLoop");
break;
}
out->loops[i].start = inst.releaseLoop.beginLoop;
out->loops[i].end = inst.releaseLoop.endLoop;
out->loops[i].num = 0xFFFFFFFF;
out->loops[i].fraction = 0;
i++;
}
}
has_inst = true;
} break;
case CC4('M', 'A', 'R', 'K'): {
aiff_MARK mark;
FREAD(in, &mark, sizeof(mark));
mark.nMarkers = be16toh(mark.nMarkers);
num_markers = mark.nMarkers;
markers = MALLOC_CHECKED_INFO(num_markers * sizeof(container_marker), "num_markers=%lu", num_markers);
for (size_t i = 0; i < num_markers; i++) {
Marker marker;
char *name;
FREAD(in, &marker, sizeof(marker));
name = read_pstring_alloc(in);
markers[i].id = be16toh(marker.MarkerID);
markers[i].frame_number = (be16toh(marker.positionH) << 16) | be16toh(marker.positionL);
markers[i].name = name;
}
} break;
case CC4('A', 'P', 'P', 'L'): {
char subcc4[4];
FREAD(in, subcc4, 4);
switch (CC4(subcc4[0], subcc4[1], subcc4[2], subcc4[3])) {
case CC4('s', 't', 'o', 'c'): {
char chunk_name[257];
read_pstring(in, chunk_name);
if (strequ(chunk_name, "VADPCMCODES")) {
int16_t version;
uint16_t order;
uint16_t npredictors;
FREAD(in, &version, sizeof(version));
version = be16toh(version);
FREAD(in, &order, sizeof(order));
order = be16toh(order);
FREAD(in, &npredictors, sizeof(npredictors));
npredictors = be16toh(npredictors);
size_t book_size_bytes = VADPCM_BOOK_SIZE_BYTES(order, npredictors);
int16_t *book_state =
MALLOC_CHECKED_INFO(book_size_bytes, "order=%u, npredictors=%u", order, npredictors);
FREAD(in, book_state, book_size_bytes);
out->vadpcm.book_version = version;
out->vadpcm.book_header.order = order;
out->vadpcm.book_header.npredictors = npredictors;
out->vadpcm.book_data = book_state;
for (size_t i = 0; i < VADPCM_BOOK_SIZE(order, npredictors); i++)
out->vadpcm.book_data[i] = be16toh(out->vadpcm.book_data[i]);
out->vadpcm.has_book = true;
} else if (strequ(chunk_name, "VADPCMLOOPS")) {
int16_t version;
int16_t nloops;
FREAD(in, &version, sizeof(version));
version = be16toh(version);
FREAD(in, &nloops, sizeof(nloops));
nloops = be16toh(nloops);
out->vadpcm.loop_version = version;
out->vadpcm.num_loops = nloops;
if (out->vadpcm.num_loops)
out->vadpcm.loops = MALLOC_CHECKED_INFO(out->vadpcm.num_loops * sizeof(ALADPCMloop),
"num_loops=%u", out->vadpcm.num_loops);
for (size_t i = 0; i < out->vadpcm.num_loops; i++) {
FREAD(in, &out->vadpcm.loops[i], sizeof(ALADPCMloop));
out->vadpcm.loops[i].start = be32toh(out->vadpcm.loops[i].start);
out->vadpcm.loops[i].end = be32toh(out->vadpcm.loops[i].end);
out->vadpcm.loops[i].count = be32toh(out->vadpcm.loops[i].count);
for (size_t j = 0; j < ARRAY_COUNT(out->vadpcm.loops[i].state); j++)
out->vadpcm.loops[i].state[j] = be16toh(out->vadpcm.loops[i].state[j]);
}
} else {
warning("Skipping unknown APPL::stoc subchunk: \"%s\"", chunk_name);
}
} break;
default:
warning("Skipping unknown APPL subchunk: \"%c%c%c%c\"", subcc4[0], subcc4[1], subcc4[2],
subcc4[3]);
break;
}
} break;
case CC4('S', 'S', 'N', 'D'): {
aiff_SSND ssnd;
FREAD(in, &ssnd, sizeof(ssnd));
ssnd.offset = be32toh(ssnd.offset);
ssnd.blockSize = be32toh(ssnd.blockSize);
size_t data_size = chunk_size - sizeof(ssnd);
void *data = MALLOC_CHECKED_INFO(data_size, "SSND chunk size = %lu", data_size);
FREAD(in, data, data_size);
if (ssnd.offset != 0 || ssnd.blockSize != 0)
error("Bad SSND chunk in aiff/aifc, offset/blockSize != 0");
out->data = data;
out->data_size = data_size;
has_ssnd = true;
} break;
default:
warning("Skipping unknown aiff chunk: \"%c%c%c%c\"", cc4[0], cc4[1], cc4[2], cc4[3]);
break;
}
long read_size = ftell(in) - start - 8;
if (read_size > chunk_size)
error("overran chunk: %lu vs %u\n", read_size, chunk_size);
else if (read_size < chunk_size)
warning("did not read entire %.*s chunk: %lu vs %u", 4, cc4, read_size, chunk_size);
fseek(in, start + 8 + chunk_size, SEEK_SET);
}
if (!has_comm)
error("aiff/aifc has no COMM chunk");
if (!has_inst)
error("aiff/aifc has no INST chunk");
if (!has_ssnd)
error("aiff/aifc has no SSND chunk");
if (out->data_type == SAMPLE_TYPE_PCM16) {
assert(out->data_size % 2 == 0);
assert(out->bit_depth == 16);
for (size_t i = 0; i < out->data_size / 2; i++)
((uint16_t *)(out->data))[i] = be16toh(((uint16_t *)(out->data))[i]);
}
for (size_t i = 0; i < out->num_loops; i++) {
container_marker *marker;
marker = NULL;
for (size_t j = 0; j < num_markers; j++) {
if (markers[j].id == out->loops[i].start) {
marker = &markers[j];
break;
}
}
if (marker == NULL)
error("AIFF/C loop references non-existent marker");
out->loops[i].start = marker->frame_number;
marker = NULL;
for (size_t j = 0; j < num_markers; j++) {
if (markers[j].id == out->loops[i].end) {
marker = &markers[j];
break;
}
}
if (marker == NULL)
error("AIFF/C loop references non-existent marker");
out->loops[i].end = marker->frame_number;
}
fclose(in);
return 0;
}
int
aifc_read(container_data *out, const char *path, bool matching)
{
FILE *in = fopen(path, "rb");
if (in == NULL)
error("Failed to open \"%s\" for reading", path);
char form[4];
uint32_t size;
char aifc[4];
FREAD(in, form, 4);
FREAD(in, &size, 4);
size = be32toh(size);
FREAD(in, aifc, 4);
if (!CC4_CHECK(form, "FORM") || !CC4_CHECK(aifc, "AIFC"))
error("Not an aifc file?");
return aiff_aifc_common_read(out, in, matching, size);
}
int
aiff_read(container_data *out, const char *path, bool matching)
{
FILE *in = fopen(path, "rb");
if (in == NULL)
error("Failed to open \"%s\" for reading", path);
char form[4];
uint32_t size;
char aiff[4];
FREAD(in, form, 4);
FREAD(in, &size, 4);
size = be32toh(size);
FREAD(in, aiff, 4);
if (!CC4_CHECK(form, "FORM") || !CC4_CHECK(aiff, "AIFF"))
error("Not an aiff file?");
return aiff_aifc_common_read(out, in, matching, size);
}
static int
aiff_aifc_common_write(container_data *in, const char *path, bool aifc, bool matching)
{
FILE *out = fopen(path, "wb");
if (out == NULL)
error("Failed to open %s for writing\n", path);
const char *aifc_head = "FORM\0\0\0\0AIFC";
const char *aiff_head = "FORM\0\0\0\0AIFF";
FWRITE(out, (aifc) ? aifc_head : aiff_head, 12);
long chunk_start;
uint32_t compression_type;
const char *compression_name;
switch (in->data_type) {
case SAMPLE_TYPE_PCM16:
compression_type = CC4('N', 'O', 'N', 'E');
compression_name = NULL;
break;
case SAMPLE_TYPE_PCM8:
compression_type = CC4('H', 'P', 'C', 'M');
compression_name = "Half-frame PCM";
break;
case SAMPLE_TYPE_VADPCM:
compression_type = CC4('A', 'D', 'P', '9');
compression_name = "Nintendo/SGI VADPCM 9-bytes/frame";
break;
case SAMPLE_TYPE_VADPCM_HALF:
compression_type = CC4('A', 'D', 'P', '5');
compression_name = "Nintendo/SGI VADPCM 5-bytes/frame";
break;
default:
error("Unhandled data type for aiff/aifc container");
break;
}
if (compression_type != CC4('N', 'O', 'N', 'E') && !aifc) {
error("Compressed data types must use aifc output");
}
aiff_COMM comm = {
.numChannels = htobe16(in->num_channels),
.numFramesH = htobe16(in->num_samples >> 16),
.numFramesL = htobe16(in->num_samples),
.sampleSize = htobe16(in->bit_depth),
.sampleRate = { 0 },
};
f64_to_f80(in->sample_rate, comm.sampleRate);
CHUNK_BEGIN(out, "COMM", &chunk_start);
CHUNK_WRITE(out, &comm);
if (compression_name != NULL) {
uint32_t compressionType = htobe32(compression_type);
CHUNK_WRITE(out, &compressionType);
write_pstring(out, compression_name);
}
CHUNK_END(out, chunk_start, htobe32);
if (in->num_loops > 2)
error("Only up to two loops are supported for AIFF/C");
size_t num_markers = 0;
container_marker markers[4];
static char *marker_names[4] = {
"start",
"end",
"start",
"end",
};
Loop sustainLoop = { 0 };
Loop releaseLoop = { 0 };
for (size_t i = 0; i < in->num_loops; i++) {
Loop *target;
switch (in->loops[i].id) {
case 0:
target = &sustainLoop;
break;
case 1:
target = &releaseLoop;
break;
default:
continue;
}
unsigned type;
switch (in->loops[i].type) {
case LOOP_FORWARD:
type = LOOP_PLAYMODE_FWD;
break;
case LOOP_FORWARD_BACKWARD:
type = LOOP_PLAYMODE_FWD_BWD;
break;
default:
error("Unsupported loop type in aiff/aifc");
break;
}
target->playMode = htobe16(type);
markers[num_markers].id = num_markers + 1;
markers[num_markers].name = marker_names[num_markers];
markers[num_markers].frame_number = in->loops[i].start;
target->beginLoop = htobe16(1 + num_markers++);
markers[num_markers].id = num_markers + 1;
markers[num_markers].name = marker_names[num_markers];
markers[num_markers].frame_number = in->loops[i].end;
target->endLoop = htobe16(1 + num_markers++);
}
if (num_markers != 0) {
CHUNK_BEGIN(out, "MARK", &chunk_start);
aiff_MARK mark = {
.nMarkers = htobe16(num_markers),
};
CHUNK_WRITE(out, &mark);
for (size_t i = 0; i < num_markers; i++) {
Marker marker = {
.MarkerID = htobe16(markers[i].id),
.positionH = htobe16(markers[i].frame_number >> 16),
.positionL = htobe16(markers[i].frame_number),
};
CHUNK_WRITE(out, &marker);
write_pstring(out, markers[i].name);
}
CHUNK_END(out, chunk_start, htobe32);
}
aiff_INST inst = {
.baseNote = in->base_note,
.detune = in->fine_tune,
.lowNote = in->key_low,
.highNote = in->key_hi,
.lowVelocity = in->vel_low,
.highVelocity = in->vel_hi,
.gain = htobe16(in->gain),
.sustainLoop = sustainLoop,
.releaseLoop = releaseLoop,
};
CHUNK_BEGIN(out, "INST", &chunk_start);
CHUNK_WRITE(out, &inst);
CHUNK_END(out, chunk_start, htobe32);
if (aifc || matching) {
// If we're writing an aifc, or we want to match on round-trip, emit an application-specific chunk for the
// vadpcm codebook.
if (in->vadpcm.has_book) {
// APPL::stoc::VADPCMCODES
CHUNK_BEGIN(out, "APPL", &chunk_start);
CHUNK_WRITE_RAW(out, "stoc", 4);
write_pstring(out, "VADPCMCODES");
int16_t version = htobe16(in->vadpcm.book_version);
int16_t order = htobe16(in->vadpcm.book_header.order);
int16_t npredictors = htobe16(in->vadpcm.book_header.npredictors);
CHUNK_WRITE(out, &version);
CHUNK_WRITE(out, &order);
CHUNK_WRITE(out, &npredictors);
size_t book_size = VADPCM_BOOK_SIZE(in->vadpcm.book_header.order, in->vadpcm.book_header.npredictors);
int16_t book_data_out[book_size];
for (size_t i = 0; i < book_size; i++)
book_data_out[i] = htobe16(in->vadpcm.book_data[i]);
CHUNK_WRITE_RAW(out, book_data_out, sizeof(int16_t) * book_size);
CHUNK_END(out, chunk_start, htobe32);
}
// Only write a vadpcm loop structure for compressed output. Loop states match on round-trip so we need not
// save them in uncompressed output.
if (aifc && in->vadpcm.num_loops != 0) {
// APPL::stoc::VADPCMLOOPS
CHUNK_BEGIN(out, "APPL", &chunk_start);
CHUNK_WRITE_RAW(out, "stoc", 4);
write_pstring(out, "VADPCMLOOPS");
int16_t version = htobe16(in->vadpcm.loop_version);
int16_t nloops = htobe16(in->vadpcm.num_loops);
CHUNK_WRITE(out, &version);
CHUNK_WRITE(out, &nloops);
for (size_t i = 0; i < in->vadpcm.num_loops; i++) {
ALADPCMloop outloop = in->vadpcm.loops[i];
outloop.start = htobe32(outloop.start);
outloop.end = htobe32(outloop.end);
outloop.count = htobe32(outloop.count);
for (size_t i = 0; i < ARRAY_COUNT(outloop.state); i++)
outloop.state[i] = htobe16(outloop.state[i]);
CHUNK_WRITE(out, &outloop);
}
CHUNK_END(out, chunk_start, htobe32);
}
}
if (in->data_type == SAMPLE_TYPE_PCM16) {
assert(in->data_size % 2 == 0);
assert(in->bit_depth == 16);
for (size_t i = 0; i < in->data_size / 2; i++) {
((uint16_t *)in->data)[i] = htobe16(((uint16_t *)in->data)[i]);
}
}
aiff_SSND ssnd = {
.offset = 0,
.blockSize = 0,
};
CHUNK_BEGIN(out, "SSND", &chunk_start);
CHUNK_WRITE(out, &ssnd);
CHUNK_WRITE_RAW(out, in->data, in->data_size);
CHUNK_END(out, chunk_start, htobe32);
uint32_t size = htobe32(ftell(out) - 8);
fseek(out, 4, SEEK_SET);
fwrite(&size, 4, 1, out);
fclose(out);
return 0;
}
int
aifc_write(container_data *data, const char *path, bool matching)
{
return aiff_aifc_common_write(data, path, true, matching);
}
int
aiff_write(container_data *data, const char *path, bool matching)
{
return aiff_aifc_common_write(data, path, false, matching);
}

View file

@ -0,0 +1,28 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef AIFF_H
#define AIFF_H
#include <stddef.h>
#include <stdint.h>
#include "../codec/vadpcm.h"
#include "container.h"
int
aifc_read(container_data *out, const char *path, bool matching);
int
aiff_read(container_data *out, const char *path, bool matching);
int
aifc_write(container_data *in, const char *path, bool matching);
int
aiff_write(container_data *in, const char *path, bool matching);
#endif

View file

@ -0,0 +1,25 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <stdlib.h>
#include "container.h"
int
container_destroy(container_data *data)
{
if (data->data != NULL)
free(data->data);
if (data->loops != NULL)
free(data->loops);
if (data->vadpcm.book_data != NULL)
free(data->vadpcm.book_data);
if (data->vadpcm.loops != NULL)
free(data->vadpcm.loops);
return 0;
}

View file

@ -0,0 +1,99 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef CONTAINER_H
#define CONTAINER_H
#include <stdbool.h>
#include <stdint.h>
#include "../codec/vadpcm.h"
typedef enum {
// Uncompressed
SAMPLE_TYPE_PCM16,
SAMPLE_TYPE_PCM8,
// Compressed
SAMPLE_TYPE_VADPCM,
SAMPLE_TYPE_VADPCM_HALF
} sample_data_type;
typedef struct {
const char *fext;
int (*read)(struct container_data *out, const char *path, bool matching);
int (*write)(struct container_data *out, const char *path, bool matching);
} container_spec;
typedef enum {
LOOP_NONE = 0,
LOOP_FORWARD = 1,
LOOP_FORWARD_BACKWARD = 2,
LOOP_BACKWARD = 3
} loop_type;
typedef struct {
uint16_t id;
loop_type type;
uint32_t start;
uint32_t end;
uint32_t fraction;
uint32_t num;
} container_loop;
typedef struct {
uint16_t id;
uint32_t frame_number;
char *name;
} container_marker;
typedef struct container_data {
// Sample details
double sample_rate;
unsigned int num_channels;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bit_depth; // also called sample_size
uint32_t num_samples; // also apparently called num_frames? but that's wrong
// Instrument details
int8_t base_note;
int8_t fine_tune;
int8_t gain;
int8_t key_low;
int8_t key_hi;
int8_t vel_low;
int8_t vel_hi;
// Sample data, possibly compressed
sample_data_type data_type;
void *data;
size_t data_size;
unsigned num_loops;
container_loop *loops;
// VADPCM-specific data
struct {
// VADPCM codebook
int16_t book_version;
ALADPCMbookhead book_header;
int16_t *book_data;
bool has_book;
// VADPCM loop data
int16_t loop_version;
unsigned num_loops;
ALADPCMloop *loops;
} vadpcm;
} container_data;
int
container_destroy(container_data *data);
#endif

View file

@ -0,0 +1,552 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../util.h"
#include "../codec/vadpcm.h"
#include "wav.h"
typedef enum {
WAVE_TYPE_PCM = 1,
WAVE_TYPE_FLOAT = 3,
WAVE_TYPE_ALAW = 6,
WAVE_TYPE_MULAW = 7,
WAVE_TYPE_EXTENSIBLE = 0xFFFE,
} wav_type;
typedef struct {
uint16_t type;
uint16_t num_channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bit_depth;
} wav_fmt;
typedef struct {
uint32_t num_samples;
} wav_fact;
typedef struct {
int8_t base_note;
int8_t fine_tune;
int8_t gain;
int8_t key_low;
int8_t key_hi;
int8_t vel_low;
int8_t vel_hi;
char _pad[1];
} wav_inst;
typedef struct {
uint32_t manufacturer;
uint32_t product;
uint32_t sample_period;
uint32_t unity_note;
char _pad[3];
uint8_t fine_tune;
uint32_t format;
uint32_t offset;
uint32_t num_sample_loops;
uint32_t sampler_data;
} wav_smpl;
typedef struct {
uint32_t cue_point_index;
uint32_t type;
uint32_t start;
uint32_t end;
uint32_t fraction;
uint32_t num;
} wav_loop;
static const char *
wav_type_name(int type)
{
switch (type) {
case WAVE_TYPE_PCM:
return "PCM";
case WAVE_TYPE_FLOAT:
return "Float";
case WAVE_TYPE_ALAW:
return "ALAW";
case WAVE_TYPE_MULAW:
return "MULAW";
case WAVE_TYPE_EXTENSIBLE:
return "Extensible";
default:
return "Unknown (should never be here)";
}
}
int
wav_read(container_data *out, const char *path, UNUSED bool matching)
{
bool has_fmt = false;
bool has_fact = false;
bool has_data = false;
bool has_inst = false;
bool has_smpl = false;
memset(out, 0, sizeof(*out));
FILE *in = fopen(path, "rb");
if (in == NULL)
error("Failed to open \"%s\" for reading", path);
char riff[4];
uint32_t size;
char wave[4];
FREAD(in, riff, 4);
FREAD(in, &size, 4);
size = le32toh(size);
FREAD(in, wave, 4);
if (!CC4_CHECK(riff, "RIFF") || !CC4_CHECK(wave, "WAVE"))
error("Not a wav file?");
while (true) {
bool skipped = false;
long start = ftell(in);
if (start > 8 + size) {
error("Overran file");
}
if (start == 8 + size) {
break;
}
char cc4[4];
uint32_t chunk_size;
FREAD(in, cc4, 4);
FREAD(in, &chunk_size, 4);
chunk_size = le32toh(chunk_size);
switch (CC4(cc4[0], cc4[1], cc4[2], cc4[3])) {
case CC4('f', 'm', 't', ' '): {
wav_fmt fmt;
FREAD(in, &fmt, sizeof(fmt));
fmt.type = le16toh(fmt.type);
fmt.num_channels = le16toh(fmt.num_channels);
fmt.sample_rate = le32toh(fmt.sample_rate);
fmt.byte_rate = le32toh(fmt.byte_rate);
fmt.block_align = le16toh(fmt.block_align);
fmt.bit_depth = le16toh(fmt.bit_depth);
if (fmt.bit_depth != 16)
error("Wav input format should be 16-bit PCM, was %u-bit", fmt.bit_depth);
switch (fmt.type) {
case WAVE_TYPE_PCM:
out->data_type = SAMPLE_TYPE_PCM16;
break;
case WAVE_TYPE_FLOAT:
case WAVE_TYPE_MULAW:
case WAVE_TYPE_ALAW:
case WAVE_TYPE_EXTENSIBLE:
error("Unhandled sample type: %s, should be PCM", wav_type_name(fmt.type));
break;
default:
error("Unrecognized sample type: %d, should be PCM", fmt.type);
break;
}
out->num_channels = fmt.num_channels;
out->sample_rate = fmt.sample_rate;
out->byte_rate = fmt.byte_rate;
out->block_align = fmt.block_align;
out->bit_depth = fmt.bit_depth;
has_fmt = true;
} break;
case CC4('f', 'a', 'c', 't'): {
wav_fact fact;
FREAD(in, &fact, sizeof(fact));
fact.num_samples = le32toh(fact.num_samples);
out->num_samples = fact.num_samples;
has_fact = true;
} break;
case CC4('d', 'a', 't', 'a'): {
void *data = MALLOC_CHECKED_INFO(chunk_size, "data size = %u", chunk_size);
FREAD(in, data, chunk_size);
out->data = data;
out->data_size = chunk_size;
has_data = true;
} break;
case CC4('i', 'n', 's', 't'): {
wav_inst inst;
FREAD(in, &inst, sizeof(inst));
out->base_note = inst.base_note;
out->fine_tune = inst.fine_tune;
out->gain = inst.gain;
out->key_low = inst.key_low;
out->key_hi = inst.key_hi;
out->vel_low = inst.vel_low;
out->vel_hi = inst.vel_hi;
has_inst = true;
} break;
case CC4('s', 'm', 'p', 'l'): {
wav_smpl smpl;
FREAD(in, &smpl, sizeof(smpl));
smpl.manufacturer = le32toh(smpl.manufacturer);
smpl.product = le32toh(smpl.product);
smpl.sample_period = le32toh(smpl.sample_period);
smpl.unity_note = le32toh(smpl.unity_note);
smpl.format = le32toh(smpl.format);
smpl.offset = le32toh(smpl.offset);
smpl.num_sample_loops = le32toh(smpl.num_sample_loops);
smpl.sampler_data = le32toh(smpl.sampler_data);
out->num_loops = smpl.num_sample_loops;
if (out->num_loops != 0) {
out->loops =
MALLOC_CHECKED_INFO(out->num_loops * sizeof(container_loop), "num_loops=%u", out->num_loops);
for (size_t i = 0; i < out->num_loops; i++) {
wav_loop loop;
FREAD(in, &loop, sizeof(loop));
loop.cue_point_index = le32toh(loop.cue_point_index);
loop.type = le32toh(loop.type);
loop.start = le32toh(loop.start);
loop.end = le32toh(loop.end);
loop.fraction = le32toh(loop.fraction);
loop.num = le32toh(loop.num);
loop_type type;
switch (loop.type) {
case 0:
type = LOOP_FORWARD;
break;
case 1:
type = LOOP_FORWARD_BACKWARD;
break;
case 2:
type = LOOP_BACKWARD;
break;
default:
error("Unrecognized loop type in wav");
}
out->loops[i].id = i;
out->loops[i].type = type;
out->loops[i].start = loop.start;
out->loops[i].end = loop.end;
out->loops[i].fraction = loop.fraction;
out->loops[i].num = loop.num;
}
}
has_smpl = true;
} break;
case CC4('z', 'z', 'b', 'k'): {
char vadpcmcodes[12];
uint16_t version;
uint16_t order;
uint16_t npredictors;
FREAD(in, vadpcmcodes, sizeof(vadpcmcodes));
FREAD(in, &version, sizeof(version));
version = le16toh(version);
FREAD(in, &order, sizeof(order));
order = le16toh(order);
FREAD(in, &npredictors, sizeof(npredictors));
npredictors = le16toh(npredictors);
size_t book_size = VADPCM_BOOK_SIZE(order, npredictors);
size_t book_data_size = sizeof(int16_t) * book_size;
int16_t *book_data =
MALLOC_CHECKED_INFO(book_data_size, "order=%u, npredictors=%u", order, npredictors);
FREAD(in, book_data, book_data_size);
out->vadpcm.book_header.order = order;
out->vadpcm.book_header.npredictors = npredictors;
out->vadpcm.book_data = book_data;
for (size_t i = 0; i < book_size; i++) {
out->vadpcm.book_data[i] = le16toh(out->vadpcm.book_data[i]);
}
out->vadpcm.has_book = true;
} break;
case CC4('z', 'z', 'l', 'p'): {
uint16_t version;
uint16_t nloops;
FREAD(in, &version, sizeof(version));
version = le16toh(version);
FREAD(in, &nloops, sizeof(nloops));
nloops = le16toh(nloops);
if (nloops != 0)
out->vadpcm.loops = MALLOC_CHECKED_INFO(nloops * sizeof(ALADPCMloop), "nloops=%u", nloops);
for (size_t i = 0; i < nloops; i++) {
uint32_t loop_start;
uint32_t loop_end;
uint32_t loop_num;
FREAD(in, &loop_start, sizeof(loop_start));
loop_start = le32toh(loop_start);
FREAD(in, &loop_end, sizeof(loop_end));
loop_end = le32toh(loop_end);
FREAD(in, &loop_num, sizeof(loop_num));
loop_num = le32toh(loop_num);
out->vadpcm.loops[i].start = loop_start;
out->vadpcm.loops[i].end = loop_end;
out->vadpcm.loops[i].count = loop_num;
if (out->vadpcm.loops[i].count != 0) {
FREAD(in, out->vadpcm.loops[i].state, sizeof(out->vadpcm.loops[i].state));
for (size_t j = 0; j < ARRAY_COUNT(out->vadpcm.loops[i].state); j++) {
out->vadpcm.loops[i].state[j] = le16toh(out->vadpcm.loops[i].state[j]);
}
}
}
} break;
default:
warning("Skipping unknown wav chunk: \"%c%c%c%c\"", cc4[0], cc4[1], cc4[2], cc4[3]);
skipped = true;
break;
}
long read_size = ftell(in) - start - 8;
if (read_size > chunk_size)
error("overran chunk");
else if (!skipped && read_size < chunk_size)
warning("did not read entire %*s chunk: %lu vs %u", 4, cc4, read_size, chunk_size);
fseek(in, start + 8 + chunk_size, SEEK_SET);
}
if (!has_fmt)
error("wav has no fmt chunk");
if (!has_data)
error("wav has no data chunk");
if (!has_fact) {
out->num_samples = out->data_size / (out->bit_depth / 8);
}
if (!has_inst) {
out->base_note = 60; // C4
out->fine_tune = 0;
out->gain = 0;
out->key_low = 0;
out->key_hi = 0;
out->vel_low = 0;
out->vel_hi = 0;
}
if (!has_smpl) {
out->num_loops = 0;
out->loops = NULL;
}
if (out->data_type == SAMPLE_TYPE_PCM16) {
if (out->data_size % 2 != 0)
error("wav data size is not a multiple of 2 despite being pcm16-formatted?");
for (size_t i = 0; i < out->data_size / 2; i++)
((uint16_t *)(out->data))[i] = le16toh(((uint16_t *)(out->data))[i]);
}
fclose(in);
return 0;
}
int
wav_write(container_data *in, const char *path, bool matching)
{
long chunk_start;
FILE *out = fopen(path, "wb");
FWRITE(out, "RIFF\0\0\0\0WAVE", 12);
uint16_t fmt_type;
switch (in->data_type) {
case SAMPLE_TYPE_PCM16:
fmt_type = WAVE_TYPE_PCM;
break;
default:
error("Unrecognized sample type for wav output");
break;
}
wav_fmt fmt = {
.type = htole16(fmt_type),
.num_channels = htole16(in->num_channels),
.sample_rate = htole32(in->sample_rate),
.byte_rate = htole32(in->sample_rate * (in->bit_depth / 8)),
.block_align = htole16(in->num_channels * (in->bit_depth / 8)),
.bit_depth = htole16(in->bit_depth),
};
CHUNK_BEGIN(out, "fmt ", &chunk_start);
CHUNK_WRITE(out, &fmt);
CHUNK_END(out, chunk_start, htole32);
wav_fact fact = {
.num_samples = htole32(in->num_samples),
};
CHUNK_BEGIN(out, "fact", &chunk_start);
CHUNK_WRITE(out, &fact);
CHUNK_END(out, chunk_start, htole32);
if (in->data_type == SAMPLE_TYPE_PCM16) {
assert(in->bit_depth == 16);
assert(in->data_size % 2 == 0);
for (size_t i = 0; i < in->data_size / 2; i++) {
((uint16_t *)in->data)[i] = htole16(((uint16_t *)in->data)[i]);
}
}
CHUNK_BEGIN(out, "data", &chunk_start);
CHUNK_WRITE_RAW(out, in->data, in->data_size);
CHUNK_END(out, chunk_start, htole32);
wav_inst inst = {
.base_note = in->base_note,
.fine_tune = in->fine_tune,
.gain = in->gain,
.key_low = in->key_low,
.key_hi = in->key_hi,
.vel_low = in->vel_low,
.vel_hi = in->vel_hi,
._pad = { 0 },
};
CHUNK_BEGIN(out, "inst", &chunk_start);
CHUNK_WRITE(out, &inst);
CHUNK_END(out, chunk_start, htole32);
wav_smpl smpl = {
.manufacturer = 0,
.product = 0,
.sample_period = 0,
.unity_note = 0,
._pad = {0, 0, 0},
.fine_tune = 0,
.format = 0,
.offset = 0,
.num_sample_loops = htole32(in->num_loops),
.sampler_data = 0,
};
CHUNK_BEGIN(out, "smpl", &chunk_start);
CHUNK_WRITE(out, &smpl);
for (size_t i = 0; i < in->num_loops; i++) {
uint32_t wav_loop_type;
switch (in->loops[i].type) {
case LOOP_FORWARD:
wav_loop_type = 0;
break;
case LOOP_FORWARD_BACKWARD:
wav_loop_type = 1;
break;
case LOOP_BACKWARD:
wav_loop_type = 2;
break;
default:
error("Unrecognized loop type for wav output");
}
wav_loop loop = {
.cue_point_index = 0,
.type = htole32(wav_loop_type),
.start = htole32(in->loops[i].start),
.end = htole32(in->loops[i].end),
.fraction = htole32(in->loops[i].fraction),
.num = htole32(in->loops[i].num),
};
CHUNK_WRITE(out, &loop);
}
CHUNK_END(out, chunk_start, htole32);
// Books generally don't match on round-trip, if matching we should write a vadpcm book chunk to the uncompressed
// output so it may be read back when re-encoding.
if (in->vadpcm.has_book && matching) {
uint32_t book_size = VADPCM_BOOK_SIZE(in->vadpcm.book_header.order, in->vadpcm.book_header.npredictors);
uint16_t version = htole16(in->vadpcm.book_version);
uint16_t order = htole16(in->vadpcm.book_header.order);
uint16_t npredictors = htole16(in->vadpcm.book_header.npredictors);
int16_t book_state[book_size];
for (size_t i = 0; i < book_size; i++) {
book_state[i] = htole16(in->vadpcm.book_data[i]);
}
CHUNK_BEGIN(out, "zzbk", &chunk_start);
CHUNK_WRITE_RAW(out, "VADPCMCODES", sizeof("VADPCMCODES"));
CHUNK_WRITE(out, &version);
CHUNK_WRITE(out, &order);
CHUNK_WRITE(out, &npredictors);
CHUNK_WRITE_RAW(out, book_state, sizeof(int16_t) * book_size);
CHUNK_END(out, chunk_start, htole32);
}
// Loop states match on round-trip while books don't, so don't write a vadpcm loop chunk if the output format
// is uncompressed.
if (in->vadpcm.num_loops != 0 && in->data_type != SAMPLE_TYPE_PCM16) {
CHUNK_BEGIN(out, "zzlp", &chunk_start);
for (size_t i = 0; i < in->vadpcm.num_loops; i++) {
uint32_t loop_start = htole32(in->vadpcm.loops[i].start);
uint32_t loop_end = htole32(in->vadpcm.loops[i].end);
uint32_t loop_count = htole32(in->vadpcm.loops[i].count);
int16_t loop_state[16];
for (size_t j = 0; j < ARRAY_COUNT(in->vadpcm.loops[i].state); j++) {
loop_state[j] = htole16(in->vadpcm.loops[i].state[j]);
}
CHUNK_WRITE(out, &loop_start);
CHUNK_WRITE(out, &loop_end);
CHUNK_WRITE(out, &loop_count);
CHUNK_WRITE_RAW(out, loop_state, sizeof(loop_state));
}
CHUNK_END(out, chunk_start, htole32);
}
uint32_t size = htole32(ftell(out) - 8);
fseek(out, 4, SEEK_SET);
fwrite(&size, 4, 1, out);
fclose(out);
return 0;
}

View file

@ -0,0 +1,20 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef WAV_H
#define WAV_H
#include "../codec/vadpcm.h"
#include "container.h"
int
wav_write(container_data *in, const char *path, bool matching);
int
wav_read(container_data *out, const char *path, bool matching);
#endif

View file

@ -0,0 +1,209 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <assert.h>
#include <math.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "util.h"
#include "codec/codec.h"
#include "codec/uncompressed.h"
#include "codec/vadpcm.h"
#include "container/container.h"
#include "container/wav.h"
#include "container/aiff.h"
static const codec_spec codecs[] = {
{"pcm16", SAMPLE_TYPE_PCM16, 16, false, pcm16_enc_dec, pcm16_enc_dec},
{ "vadpcm", SAMPLE_TYPE_VADPCM, 9, true, vadpcm_dec, vadpcm_enc },
{ "vadpcm-half", SAMPLE_TYPE_VADPCM_HALF, 5, true, vadpcm_dec, vadpcm_enc },
// { "pcm8", SAMPLE_TYPE_PCM8, TODO, TODO, pcm8_dec, pcm8_enc },
};
static const container_spec containers[] = {
{".wav", wav_read, wav_write },
{ ".aiff", aiff_read, aiff_write},
{ ".aifc", aifc_read, aifc_write},
};
static bool
str_endswith(const char *str1, const char *str2)
{
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
if (len2 > len1)
return false;
return strequ(str1 + len1 - len2, str2);
}
NORETURN static void
help(const char *progname)
{
fprintf(stderr, "%s [--matching] out_codec_name in_path out_path\n", progname);
fprintf(stderr, "Supported codecs:\n");
fprintf(stderr, " pcm16\n");
fprintf(stderr, " vadpcm\n");
fprintf(stderr, " vadpcm-half\n");
fprintf(stderr, "Possible input/output formats with supported codecs, automatically selected by file extension:\n");
fprintf(stderr, " wav [pcm16]\n");
fprintf(stderr, " aiff [pcm16]\n");
fprintf(stderr, " aifc [vadpcm, vadpcm-half]\n");
exit(EXIT_FAILURE);
}
NORETURN static void
usage(const char *progname)
{
fprintf(stderr, "%s [--matching] out_codec_name in_path out_path\n", progname);
exit(EXIT_FAILURE);
}
static const codec_spec *
codec_from_name(const char *name)
{
for (size_t i = 0; i < ARRAY_COUNT(codecs); i++) {
if (strequ(name, codecs[i].name))
return &codecs[i];
}
return NULL;
}
static const codec_spec *
codec_from_type(sample_data_type type)
{
for (size_t i = 0; i < ARRAY_COUNT(codecs); i++) {
if (type == codecs[i].type)
return &codecs[i];
}
return NULL;
}
static const container_spec *
container_from_name(const char *name)
{
for (size_t i = 0; i < ARRAY_COUNT(containers); i++) {
if (str_endswith(name, containers[i].fext))
return &containers[i];
}
return NULL;
}
int
main(int argc, char **argv)
{
const char *progname = argv[0];
// Required arguments
const char *in_path = NULL;
const char *out_path = NULL;
const char *out_codec_name = NULL;
// Optional arguments
enc_dec_opts opts = {
.matching = false,
// VADPCM
.truncate = false,
.min_loop_length = 800,
.design.order = 2,
.design.bits = 2,
.design.refine_iters = 2,
.design.thresh = 10.0,
.design.frame_size = 16,
};
// parse args
#define arg_error(fmt, ...) \
do { \
fprintf(stderr, fmt "\n", ##__VA_ARGS__); \
usage(progname); \
} while (0)
int argn = 0;
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-') {
// Optional args
if (strequ(argv[i], "--help")) {
help(progname);
} else if (strequ(argv[i], "--matching")) {
if (opts.matching)
arg_error("Received --matching option twice");
opts.matching = true;
continue;
}
arg_error("Unknown option \"%s\"", argv[i]);
} else {
// Required args
switch (argn) {
case 0:
out_codec_name = argv[i];
break;
case 1:
in_path = argv[i];
break;
case 2:
out_path = argv[i];
break;
default:
arg_error("Unknown positional argument \"%s\"", argv[i]);
break;
}
argn++;
}
}
if (argn != 3)
arg_error("Not enough positional arguments");
#undef arg_error
const container_spec *in_container = container_from_name(in_path);
if (in_container == NULL)
error("Unsupported input format");
const container_spec *out_container = container_from_name(out_path);
if (out_container == NULL)
error("Unsupported output format");
const codec_spec *out_codec = codec_from_name(out_codec_name);
if (out_codec == NULL)
error("Unrecognized output codec: \"%s\"", out_codec_name);
container_data ctnr;
// Read input from container
if (in_container->read(&ctnr, in_path, opts.matching))
error("Error reading input file");
// Determine input codec from input container automatically
const codec_spec *in_codec = codec_from_type(ctnr.data_type);
if (in_codec == NULL)
error("Unrecognized input codec: type=%d", ctnr.data_type);
// Decode to PCM16 (this does nothing if in_codec is PCM16)
if (in_codec->decode(&ctnr, in_codec, &opts))
error("Error in decoding");
// Encode to output (this does nothing if out_codec is PCM16)
if (out_codec->encode(&ctnr, out_codec, &opts))
error("Error in encoding");
// Write output to container
if (out_container->write(&ctnr, out_path, opts.matching))
error("Error reading output file");
container_destroy(&ctnr);
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,43 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "util.h"
NORETURN void
error(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "\x1b[91m"
"Error: "
"\x1b[97m");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\x1b[0m"
"\n");
va_end(ap);
exit(EXIT_FAILURE);
}
void
warning(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "\x1b[95m"
"Warning: "
"\x1b[97m");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\x1b[0m"
"\n");
va_end(ap);
}

View file

@ -0,0 +1,116 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef UTIL_H
#define UTIL_H
#include <stdint.h>
// Endian
#if defined(__linux__) || defined(__CYGWIN__)
#include <endian.h>
#elif defined(__APPLE__)
#include <libkern/OSByteOrder.h>
#define htobe16(x) OSSwapHostToBigInt16(x)
#define htole16(x) OSSwapHostToLittleInt16(x)
#define be16toh(x) OSSwapBigToHostInt16(x)
#define le16toh(x) OSSwapLittleToHostInt16(x)
#define htobe32(x) OSSwapHostToBigInt32(x)
#define htole32(x) OSSwapHostToLittleInt32(x)
#define be32toh(x) OSSwapBigToHostInt32(x)
#define le32toh(x) OSSwapLittleToHostInt32(x)
#define htobe64(x) OSSwapHostToBigInt64(x)
#define htole64(x) OSSwapHostToLittleInt64(x)
#define be64toh(x) OSSwapBigToHostInt64(x)
#define le64toh(x) OSSwapLittleToHostInt64(x)
#else
#error "Endian conversion unsupported, add it"
#endif
#define ARRAY_COUNT(arr) (sizeof(arr) / sizeof((arr)[0]))
#define NORETURN __attribute__((noreturn))
#define UNUSED __attribute__((unused))
#define strequ(s1, s2) ((__builtin_constant_p(s2) ? strncmp(s1, s2, sizeof(s2) - 1) : strcmp(s1, s2)) == 0)
#define MALLOC_CHECKED(length) \
({ \
size_t malloc_len_ = (size_t)(length); \
void *result_ = malloc(malloc_len_); \
if (result_ == NULL) \
error("[malloc] Failed to allocate %lu bytes @ [%s:%u]", malloc_len_, __FILE__, __LINE__); \
result_; \
})
#define MALLOC_CHECKED_INFO(length, fmt, ...) \
({ \
size_t malloc_len_ = (size_t)(length); \
void *result_ = malloc(malloc_len_); \
if (result_ == NULL) \
error("[malloc] Failed to allocate %lu bytes @ [%s:%u] (" fmt ")", malloc_len_, __FILE__, __LINE__, \
##__VA_ARGS__); \
result_; \
})
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define ABS(x) (((x) < 0) ? (-(x)) : (x))
#define FWRITE(file, data, size) \
do { \
if (fwrite((data), (size), 1, (file)) != 1) { \
error("[%s:%d] Could not write %lu bytes to file", __FILE__, __LINE__, (size_t)(size)); \
} \
} while (0)
#define FREAD(file, data, size) \
do { \
if (fread((data), (size), 1, (file)) != 1) { \
error("[%s:%d] Could not read %lu bytes from file", __FILE__, __LINE__, (size_t)(size)); \
} \
} while (0)
#define CC4_CHECK(buf, str) \
((buf)[0] == (str)[0] && (buf)[1] == (str)[1] && (buf)[2] == (str)[2] && (buf)[3] == (str)[3])
#define CC4(c1, c2, c3, c4) (((c1) << 24) | ((c2) << 16) | ((c3) << 8) | (c4))
#define CHUNK_BEGIN(file, name, start) \
do { \
*(start) = ftell(out); \
FWRITE(file, name "\0\0\0\0", 8); \
} while (0)
#define CHUNK_WRITE(file, structure) \
do { \
FWRITE(file, structure, sizeof(*(structure))); \
} while (0)
#define CHUNK_WRITE_RAW(file, data, length) FWRITE(file, data, length)
#define CHUNK_END(file, start, endian_func) \
do { \
long end = ftell(out); \
uint32_t size = endian_func(end - (start)-8); \
fseek(out, (start) + 4, SEEK_SET); \
FWRITE(out, &size, 4); \
fseek(out, end, SEEK_SET); \
} while (0)
__attribute__((format(printf, 1, 2))) NORETURN void
error(const char *fmt, ...);
__attribute__((format(printf, 1, 2))) void
warning(const char *fmt, ...);
#endif