#include "nofft.h"
#include "acpcm.h"
#include "nfm.h"
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <stdexcept>
namespace std {
void __throw_bad_array_new_length() {
throw bad_array_new_length();
}
}
namespace {
bool read_magic(const char* path, char magic[4]) {
FILE* f = std::fopen(path, "rb");
if (f == nullptr) {
return false;
}
const size_t got = std::fread(magic, 1, 4, f);
std::fclose(f);
return got == 4;
}
bool magic_is(const char magic[4], const char* text) {
return std::memcmp(magic, text, 4) == 0;
}
bool magic_is3(const char magic[4], const char* text) {
return std::memcmp(magic, text, 3) == 0;
}
void read_u32le(const uint8_t* d, uint32_t* out) {
*out = (uint32_t)d[0] | ((uint32_t)d[1] << 8) | ((uint32_t)d[2] << 16) |
((uint32_t)d[3] << 24);
}
void read_u16le(const uint8_t* d, uint16_t* out) {
*out = (uint16_t)((uint16_t)d[0] | ((uint16_t)d[1] << 8));
}
}
typedef Err (*nova_sink_begin_fn)(void* user, uint32_t sample_rate, uint16_t channels,
size_t total_samples);
typedef Err (*nova_sink_write_fn)(void* user, const float* samples, size_t count);
extern "C" {
int nova_codec_kind(const char* path) {
char magic[4];
if (!read_magic(path, magic)) {
return -1;
}
if (magic_is(magic, NOFFT_MAGIC)) {
return 0;
}
if (magic_is(magic, ACPCM_MAGIC) || magic_is(magic, ACPCM_MAGIC2) ||
magic_is(magic, ACPCM_MAGIC3) || magic_is(magic, ACPCM_MAGIC4) ||
magic_is(magic, ACPCM_MAGIC5) || magic_is(magic, ACPCM_MAGIC6)) {
return 1;
}
if (magic_is3(magic, NFM_MAGIC)) {
return 2;
}
return -1;
}
int nova_info(const char* path, uint32_t* out_rate, uint16_t* out_channels,
uint64_t* out_frames) {
if (out_rate == nullptr || out_channels == nullptr || out_frames == nullptr) {
return (int)ERR_BAD_ARGS;
}
const int kind = nova_codec_kind(path);
if (kind == 1) {
acpcm_info info;
const Err e = acpcm_file_info(path, &info);
if (e != ERR_OK) {
return (int)e;
}
*out_rate = info.sample_rate;
*out_channels = (uint16_t)info.channels;
*out_frames = info.frames;
return (int)ERR_OK;
}
if (kind == 0) {
FILE* f = std::fopen(path, "rb");
if (f == nullptr) {
return (int)ERR_OPEN;
}
uint8_t hdr[NOFFT_HEADER_SIZE];
const size_t got = std::fread(hdr, 1, NOFFT_HEADER_SIZE, f);
std::fclose(f);
if (got != NOFFT_HEADER_SIZE) {
return (int)ERR_IO;
}
uint32_t sr = 0, frame_len = 0, n_frames = 0, last_len = 0;
uint16_t ch = 0;
read_u32le(hdr + 8, &sr);
read_u16le(hdr + 12, &ch);
read_u32le(hdr + 16, &frame_len);
read_u32le(hdr + 20, &n_frames);
read_u32le(hdr + 24, &last_len);
if (n_frames == 0) {
return (int)ERR_BAD_NOFFT;
}
*out_rate = sr;
*out_channels = ch;
*out_frames = (uint64_t)(n_frames - 1) * (uint64_t)frame_len + (uint64_t)last_len;
return (int)ERR_OK;
}
return (int)ERR_BAD_NOFFT;
}
int nova_decode(const char* path, float** out_samples, size_t* out_count,
uint32_t* out_rate, uint16_t* out_channels) {
if (out_samples == nullptr || out_count == nullptr || out_rate == nullptr ||
out_channels == nullptr) {
return (int)ERR_BAD_ARGS;
}
const int kind = nova_codec_kind(path);
if (kind < 0) {
return (int)ERR_BAD_NOFFT;
}
pcm_buf buf;
std::memset(&buf, 0, sizeof(buf));
const Err e = (kind == 0) ? nofft_decode(path, &buf) : acpcm_decode(path, &buf);
if (e != ERR_OK) {
pcm_free(&buf);
return (int)e;
}
if (buf.samples == nullptr && buf.count != 0) {
pcm_free(&buf);
return (int)ERR_NO_MEMORY;
}
*out_samples = buf.samples;
*out_count = buf.count;
*out_rate = buf.sample_rate;
*out_channels = buf.channels;
return (int)ERR_OK;
}
int nova_decode_sink(const char* path, void* user, nova_sink_begin_fn begin,
nova_sink_write_fn write) {
if (path == nullptr) {
return (int)ERR_BAD_ARGS;
}
const int kind = nova_codec_kind(path);
if (kind < 0) {
return (int)ERR_BAD_NOFFT;
}
nofft_sink sink;
sink.user = user;
sink.begin = begin;
sink.write = write;
const Err e = (kind == 0) ? nofft_decode_sink(path, &sink) : acpcm_decode_sink(path, &sink);
return (int)e;
}
void nova_free(float* samples) {
if (samples == nullptr) {
return;
}
pcm_buf buf;
std::memset(&buf, 0, sizeof(buf));
buf.samples = samples;
pcm_free(&buf);
}
const char* nova_err_str(int code) {
if (code < 0 || code >= (int)ERR_COUNT || g_err_str == nullptr) {
return "unknown codec error";
}
const char* text = g_err_str((Err)code);
return text != nullptr ? text : "unknown codec error";
}
int nova_gen_wav(const char* path, double seconds, uint32_t channels, uint32_t rate) {
return (int)acpcm_gen(path, seconds, channels, rate);
}
int nova_encode_nofft(const char* in_wav, const char* out_path, uint32_t frame_len,
uint16_t degree) {
pcm_buf p;
std::memset(&p, 0, sizeof(p));
Err e = wav_load(in_wav, &p);
if (e != ERR_OK) {
return (int)e;
}
e = nofft_encode(out_path, &p, frame_len, degree, NOFFT_COEF_F16, NOFFT_FIT_LEAST_SQ);
pcm_free(&p);
return (int)e;
}
int nova_encode_nadc(const char* in_wav, const char* out_path, uint16_t bits,
uint32_t variant) {
pcm_buf p;
std::memset(&p, 0, sizeof(p));
Err e = wav_load(in_wav, &p);
if (e != ERR_OK) {
return (int)e;
}
switch (variant) {
case 1:
e = acpcm_encode(out_path, &p, bits);
break;
case 2:
e = acpcm_encode2(out_path, &p, bits, 1024, ACP2_DRIFT);
break;
case 3:
e = acpcm_encode3(out_path, &p, bits);
break;
case 4:
e = acpcm_encode4_baskets(out_path, &p, bits, 170);
break;
case 5:
e = acpcm_encode5(out_path, &p, bits, 2.0, 0.01);
break;
case 6:
e = acpcm_encode6(out_path, &p, bits, 1024, 2.0, 2.6);
break;
default:
e = ERR_BAD_ARGS;
break;
}
pcm_free(&p);
return (int)e;
}
int nova_encode_nadc5_rate(const char* in_wav, const char* out_path, uint16_t bits,
double rate) {
pcm_buf p;
std::memset(&p, 0, sizeof(p));
Err e = wav_load(in_wav, &p);
if (e != ERR_OK) {
return (int)e;
}
e = acpcm_encode5_auto(out_path, &p, bits, 2.0, rate);
pcm_free(&p);
return (int)e;
}
int nova_nfm_read(const char* path,
uint8_t** payload, size_t* payload_len,
const char** title, const char** artist, const char** album,
uint8_t** img, size_t* img_len, int* img_type) {
if (payload == NULL || payload_len == NULL) {
return (int)ERR_BAD_ARGS;
}
*payload = NULL;
*payload_len = 0;
if (title != NULL) *title = NULL;
if (artist != NULL) *artist = NULL;
if (album != NULL) *album = NULL;
if (img != NULL) *img = NULL;
if (img_len != NULL) *img_len = 0;
if (img_type != NULL) *img_type = 0;
nfm_meta meta;
uint8_t* p = NULL;
size_t p_len = 0;
uint8_t* im = NULL;
size_t im_len = 0;
int im_type = 0;
const Err e = nfm_read(path, &p, &p_len, &meta, &im, &im_len, &im_type);
if (e != ERR_OK) {
nfm_buf_free(p);
nfm_buf_free(im);
nfm_free_meta(&meta);
return (int)e;
}
*payload = p;
*payload_len = p_len;
if (title != NULL) *title = meta.title;
if (artist != NULL) *artist = meta.artist;
if (album != NULL) *album = meta.album;
if (img != NULL) *img = im;
if (img_len != NULL) *img_len = im_len;
if (img_type != NULL) *img_type = im_type;
return (int)ERR_OK;
}
void nova_nfm_free_meta(const char** title, const char** artist, const char** album) {
if (title != NULL && *title != NULL) {
free((void*)*title);
}
if (artist != NULL && *artist != NULL) {
free((void*)*artist);
}
if (album != NULL && *album != NULL) {
free((void*)*album);
}
}
int nova_nfm_write(const char* path,
const uint8_t* payload, size_t payload_len,
const char* title, const char* artist, const char* album,
const uint8_t* img, size_t img_len, int img_type) {
if (path == NULL || payload == NULL || payload_len == 0) {
return (int)ERR_BAD_ARGS;
}
nfm_meta meta;
meta.title = title;
meta.artist = artist;
meta.album = album;
const uint8_t* image = (img != NULL && img_len > 0 && img_type > NFM_IMG_NONE) ? img : NULL;
const size_t image_len = (image != NULL) ? img_len : 0;
const int image_type = (image != NULL) ? img_type : NFM_IMG_NONE;
return (int)nfm_write(path, payload, payload_len, &meta, image, image_len, image_type);
}
}