mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 21:27:52 -04:00
4d751af38e
Favors the `lg` namespace over `fmt` directly, as this will output the logs to a file / has log levels. I also made assertion errors go to a file, this unfortunately means importing `lg` and hence `fmt` which was attempted to be avoided before. But I'm not sure how else to do this aspect without re-inventing the file logging. We have a lot of commented out prints as well that we should probably cleanup at some point / switch them to trace level and default to `info` level. I noticed the pattern of disabling debug logs behind some boolean, something to consider cleaning up in the future -- if our logs were more structured (knowing where they are coming from) then a lot this boilerplate could be eliminated. Closes #1358
301 lines
8.8 KiB
C++
301 lines
8.8 KiB
C++
#include "audio_formats.h"
|
|
|
|
#include "common/log/log.h"
|
|
#include "common/util/BinaryWriter.h"
|
|
|
|
#include "third-party/fmt/core.h"
|
|
|
|
/*!
|
|
* Write a wave file from a vector of samples.
|
|
*/
|
|
void write_wave_file_mono(const std::vector<s16>& samples, s32 sample_rate, const fs::path& name) {
|
|
WaveFileHeader header;
|
|
memcpy(header.chunk_id, "RIFF", 4);
|
|
header.chunk_size = 36 + samples.size() * sizeof(s16);
|
|
memcpy(header.format, "WAVE", 4);
|
|
|
|
// now the format
|
|
memcpy(header.subchunk1_id, "fmt ", 4);
|
|
header.subchunk1_size = 16;
|
|
header.aud_format = 1;
|
|
header.num_channels = 1; // mono
|
|
header.sample_rate = sample_rate;
|
|
header.byte_rate = sample_rate * header.num_channels * sizeof(s16);
|
|
header.block_align = header.num_channels * sizeof(s16);
|
|
header.bits_per_sample = 16;
|
|
|
|
memcpy(header.subchunk2_id, "data", 4);
|
|
header.subchunk2_size = samples.size() * sizeof(s16);
|
|
|
|
BinaryWriter writer;
|
|
writer.add(header);
|
|
|
|
for (auto& samp : samples) {
|
|
writer.add(samp);
|
|
}
|
|
|
|
writer.write_to_file(name);
|
|
}
|
|
|
|
std::vector<s16> decode_adpcm(BinaryReader& reader) {
|
|
std::vector<s16> decoded_samples;
|
|
s32 sample_prev[2] = {0, 0};
|
|
constexpr s32 f1[5] = {0, 60, 115, 98, 122};
|
|
constexpr s32 f2[5] = {0, 0, -52, -55, -60};
|
|
|
|
int block_idx = 0;
|
|
while (true) {
|
|
if (!reader.bytes_left()) {
|
|
break;
|
|
}
|
|
u8 shift_filter = reader.read<u8>();
|
|
u8 flags = reader.read<u8>();
|
|
u8 shift = shift_filter & 0b1111;
|
|
u8 filter = shift_filter >> 4;
|
|
|
|
if (shift > 12) {
|
|
ASSERT(false);
|
|
}
|
|
|
|
if (filter > 4) {
|
|
ASSERT(false);
|
|
}
|
|
|
|
if (flags == 7) {
|
|
break;
|
|
}
|
|
|
|
u8 input_buffer[14];
|
|
|
|
for (int i = 0; i < 14; i++) {
|
|
input_buffer[i] = reader.read<u8>();
|
|
}
|
|
|
|
for (int i = 0; i < 28; i++) {
|
|
int16_t nibble = input_buffer[i / 2];
|
|
if (i % 2 == 0) {
|
|
nibble = (nibble & 0x0f);
|
|
} else {
|
|
nibble = (nibble & 0xf0) >> 4;
|
|
}
|
|
|
|
s32 sample = (s32)(s16)(nibble << 12);
|
|
sample >>= shift;
|
|
sample += (sample_prev[0] * f1[filter] + sample_prev[1] * f2[filter] + 32) / 64;
|
|
|
|
if (sample > 0x7fff) {
|
|
sample = 0x7fff;
|
|
}
|
|
|
|
if (sample < -0x8000) {
|
|
sample = -0x8000;
|
|
}
|
|
|
|
sample_prev[1] = sample_prev[0];
|
|
sample_prev[0] = sample;
|
|
|
|
decoded_samples.push_back(sample);
|
|
}
|
|
block_idx++;
|
|
}
|
|
|
|
return decoded_samples;
|
|
}
|
|
|
|
// I attempted to write an encoder below, which works, but has some limitations.
|
|
// - In some cases we can't recover the original data exactly because the decode saturates the
|
|
// the output to fit in a signed 16-bit integer.
|
|
// - There are some cases when there are multiple ways to encode the same data.
|
|
// The break_filter_ties function attempts to handle this, but doesn't work 100% of the time.
|
|
|
|
template <typename T>
|
|
T saturate(T in, T minimum, T maximum) {
|
|
if (in < minimum) {
|
|
return minimum;
|
|
}
|
|
if (in > maximum) {
|
|
return maximum;
|
|
}
|
|
return in;
|
|
}
|
|
|
|
constexpr int SAMPLES_PER_BLOCK = 28;
|
|
|
|
void encode_block_with_filter(int filter_idx,
|
|
const s16* samples_in,
|
|
s32* out,
|
|
const s32* prev_samples_in) {
|
|
constexpr s32 f1[5] = {0, 60, 115, 98, 122};
|
|
constexpr s32 f2[5] = {0, 0, -52, -55, -60};
|
|
s32 prev_samples[2] = {prev_samples_in[0], prev_samples_in[1]};
|
|
|
|
for (int sample_idx = 0; sample_idx < SAMPLES_PER_BLOCK; sample_idx++) {
|
|
s32 sample = samples_in[sample_idx];
|
|
s32 delta =
|
|
sample - (prev_samples[0] * f1[filter_idx] + prev_samples[1] * f2[filter_idx] + 32) / 64;
|
|
out[sample_idx] = delta;
|
|
prev_samples[1] = prev_samples[0];
|
|
prev_samples[0] = sample;
|
|
}
|
|
}
|
|
|
|
int get_shift_error(int shift, const s32* samples, bool /*debug*/) {
|
|
int result = 0;
|
|
|
|
for (int sample_idx = 0; sample_idx < SAMPLES_PER_BLOCK; sample_idx++) {
|
|
int left_shift = 32 - (12 + 4 - shift);
|
|
ASSERT(left_shift >= 0);
|
|
s32 sample_left = samples[sample_idx] << left_shift;
|
|
s32 sample_right = sample_left >> (32 - 4);
|
|
s32 sample_compressed = sample_right << (12 - shift);
|
|
|
|
s32 err = std::abs(sample_compressed - samples[sample_idx]);
|
|
|
|
result += err;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int get_max_bits(s32 value) {
|
|
int result = 0;
|
|
if (value >= 0) {
|
|
int last = 1;
|
|
while (value) {
|
|
result++;
|
|
last = value & 1;
|
|
value >>= 1;
|
|
}
|
|
if (last) {
|
|
result++;
|
|
}
|
|
} else {
|
|
int last = 0;
|
|
while (value != -1) {
|
|
result++;
|
|
last = value & 1;
|
|
value >>= 1;
|
|
}
|
|
if (!last) {
|
|
result++;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int break_filter_ties(s32* errors, s32* filter_shifts) {
|
|
s32 best_error = INT32_MAX;
|
|
|
|
for (int filter_idx = 0; filter_idx < 5; filter_idx++) {
|
|
if (errors[filter_idx] < best_error) {
|
|
best_error = errors[filter_idx];
|
|
}
|
|
}
|
|
|
|
s32 best_shift = INT32_MAX;
|
|
int best_filter = -1;
|
|
for (int filter_idx = 5; filter_idx-- > 0;) {
|
|
if (errors[filter_idx] == best_error) {
|
|
if (filter_shifts[filter_idx] <= best_shift) {
|
|
best_shift = filter_shifts[filter_idx];
|
|
best_filter = filter_idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
return best_filter;
|
|
}
|
|
|
|
void test_encode_adpcm(const std::vector<s16>& samples,
|
|
const std::vector<u8>& filter_debug,
|
|
const std::vector<u8>& shift_debug) {
|
|
// the data is made of blocks.
|
|
// Each block decodes to 28 samples.
|
|
// each block has a shift and FIR filter.
|
|
// the window is continuous across blocks.
|
|
|
|
// we could try all combinations of filters / shifts and pick the best, but that's slow and
|
|
// we don't know how to break ties if multiple are the same.
|
|
// we will try all 5 filters, then be smart about picking the best shift from there.
|
|
|
|
// filter coefficients.
|
|
// there are 5x FIR filters that you can pick between.
|
|
|
|
// last two samples from chosen encoding of the previous block
|
|
// init to 0, like the decoder
|
|
s32 prev_block_samples[2] = {0, 0};
|
|
|
|
// TODO - this will drop some samples at the end, if we don't use a multiple of 28.
|
|
// probably best to go back and pad with zeros or something.
|
|
int block_count = samples.size() / SAMPLES_PER_BLOCK;
|
|
|
|
for (int block_idx = 0; block_idx < block_count; block_idx++) {
|
|
// try each filter
|
|
s32 pre_shift_samples_per_filter[5][SAMPLES_PER_BLOCK];
|
|
for (int filter_idx = 0; filter_idx < 5; filter_idx++) {
|
|
encode_block_with_filter(filter_idx, samples.data() + SAMPLES_PER_BLOCK * block_idx,
|
|
pre_shift_samples_per_filter[filter_idx], prev_block_samples);
|
|
}
|
|
|
|
// this is somewhat arbitrary, but we will require that the largest delta in the previous encode
|
|
// can be represented.
|
|
|
|
s32 filter_errors[5] = {0, 0, 0, 0, 0};
|
|
s32 filter_shifts[5] = {-1, -1, -1, -1};
|
|
for (int filter_idx = 0; filter_idx < 5; filter_idx++) {
|
|
// find the largest value
|
|
s32 max_sample = INT32_MIN;
|
|
s32 min_sample = INT32_MAX;
|
|
|
|
bool debug = block_idx == 10966 && filter_idx == 4;
|
|
|
|
for (int sample_idx = 0; sample_idx < SAMPLES_PER_BLOCK; sample_idx++) {
|
|
s32 s = pre_shift_samples_per_filter[filter_idx][sample_idx];
|
|
max_sample = std::max(s, max_sample);
|
|
min_sample = std::min(s, min_sample);
|
|
}
|
|
|
|
if (debug) {
|
|
lg::debug("Range: {}", max_sample - min_sample);
|
|
}
|
|
|
|
// see how many bits we need and pick shift.
|
|
auto bits_for_max = std::max(4, std::max(get_max_bits(min_sample), get_max_bits(max_sample)));
|
|
|
|
filter_shifts[filter_idx] = 4 + 12 - bits_for_max;
|
|
|
|
filter_errors[filter_idx] = get_shift_error(filter_shifts[filter_idx],
|
|
pre_shift_samples_per_filter[filter_idx], debug);
|
|
|
|
if (filter_errors[filter_idx] == 0) {
|
|
while (filter_shifts[filter_idx] >= 0) {
|
|
int next_error = get_shift_error(filter_shifts[filter_idx] - 1,
|
|
pre_shift_samples_per_filter[filter_idx], false);
|
|
if (next_error == 0) {
|
|
filter_shifts[filter_idx]--;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int best_filter = break_filter_ties(filter_errors, filter_shifts);
|
|
s32 best_shift = filter_shifts[best_filter];
|
|
|
|
if (filter_errors[best_filter] || best_filter != filter_debug[block_idx] ||
|
|
best_shift != shift_debug[block_idx]) {
|
|
lg::error("Block {} me {}, {} : answer {} {}: ERR {}", block_idx, best_filter, best_shift,
|
|
filter_debug[block_idx], shift_debug[block_idx], filter_errors[best_filter]);
|
|
lg::error("filter errors:");
|
|
for (int i = 0; i < 5; i++) {
|
|
lg::error(" [{}] {} {}", i, filter_errors[i], filter_shifts[i]);
|
|
}
|
|
ASSERT_MSG(false, fmt::format("prev: {} {}", prev_block_samples[0], prev_block_samples[1]));
|
|
}
|
|
|
|
prev_block_samples[0] = samples.at(block_idx * 28 + 27);
|
|
prev_block_samples[1] = samples.at(block_idx * 28 + 26);
|
|
|
|
} // end loop over blocks
|
|
}
|