Fix audio distortion (#8)

* Fix an overflow in the fix16_t usage which can lead to audio distortion.

* Add a simple test application for running the filtering stages on a PC.

* Fix readme typos.

* Rename test to tools.

* Remove volume multiplier

---------

Co-authored-by: George Norton <george.norton@mediakind.com>
This commit is contained in:
george-norton 2023-04-05 21:23:35 +01:00 committed by GitHub
parent cca0639485
commit 992df01067
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 159 additions and 16 deletions

View File

@ -22,7 +22,7 @@
#include <stdio.h>
#include <inttypes.h>
#include <math.h>
#include <limits.h>
#include "fix16.h"
fix16_t fix16_from_int(int16_t a) {
@ -30,9 +30,23 @@ fix16_t fix16_from_int(int16_t a) {
}
int16_t fix16_to_int(fix16_t a) {
if (a >= 0)
return (a + (fix16_one >> 1)) / fix16_one;
return (a - (fix16_one >> 1)) / fix16_one;
// Handle rounding up front, adding one can cause an overflow/underflow
a+=(fix16_one >> 1);
// Saturate the value if an overflow has occurred
uint32_t upper = (a >> 30);
if (a < 0) {
if (~upper)
{
return SHRT_MIN;
}
} else {
if (upper)
{
return SHRT_MAX;
}
}
return (a >> 15);
}
fix16_t fix16_from_dbl(double a) {
@ -49,16 +63,6 @@ double fix16_to_dbl(fix16_t a) {
fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1) {
int64_t product = (int64_t)inArg0 * inArg1;
uint32_t upper = (product >> 47);
if (product < 0) {
if (~upper)
return fix16_overflow;
product--;
} else {
if (upper)
return fix16_overflow;
}
fix16_t result = product >> 15;
result += (product & 0x4000) >> 14;

View File

@ -91,7 +91,7 @@ static void _as_audio_packet(struct usb_endpoint *ep) {
int samples = usb_buffer->data_len / 2;
for (int i = 0; i < samples; i++)
out[i] = in[i] / 4; // fixes digital distortion bug
out[i] = in[i];
multicore_fifo_push_blocking(CORE0_READY);
multicore_fifo_push_blocking(samples);
@ -113,7 +113,7 @@ static void _as_audio_packet(struct usb_endpoint *ep) {
// Multiply the outgoing signal with the volume multiple
for (int i = 0; i < samples; i++)
out[i] = out[i] * (int32_t) vol_mul * 4;
out[i] = out[i] * (int32_t) vol_mul;
i2s_stream_write(&i2s_write_obj, userbuf, samples * 4);

View File

@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.13)
project(ploopy_headphones_project C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
add_executable(filter_test
filter_test.c
../code/fix16.c
../code/bqf.c
../code/user.c
)
target_include_directories(filter_test PRIVATE ${CMAKE_SOURCE_DIR}/../code)
# TODO: user.c includes run.h to get the definition for SAMPLING_FREQ, but this
# pulls in the whole pico sdk as a dependency. A little refactoring would fix it all.
target_compile_definitions(filter_test PRIVATE
SAMPLING_FREQ=48000
RUN_H
)
target_link_libraries(filter_test
m
)

23
firmware/tools/README.md Normal file
View File

@ -0,0 +1,23 @@
## filter_test
This is a basic utility for testing the Ploopy headphones filtering routines on a PC.
### Usage
Find a source file and use ffmpeg to convert it to 16bit stereo PCM samples:
```
ffmpeg -i <input file> -map 0:6 -vn -f s16le -acodec pcm_s16le input.pcm
```
Run `filter_test` to process the PCM samples. The `filter_test` program takes two arguments an input file and an output file:
```
./filter_test input.pcm output.pcm
```
You can listen to the PCM files using ffplay (which is usually included with ffmpeg):
```
./ffplay -f s16le -ar 48000 -ac 2 output.pcm
```
If there are no obvious problems, go ahead and flash your firmware.

View File

@ -0,0 +1,91 @@
#include <stdio.h>
#include <stdlib.h>
#include "bqf.h"
#include "fix16.h"
#include "user.h"
bqf_coeff_t bqf_filters_left[FILTER_STAGES];
bqf_coeff_t bqf_filters_right[FILTER_STAGES];
bqf_mem_t bqf_filters_mem_left[FILTER_STAGES];
bqf_mem_t bqf_filters_mem_right[FILTER_STAGES];
const char* usage = "Usage: %s INFILE OUTFILE\n\n"
"Reads 16bit stereo PCM data from INFILE, runs it through the Ploopy headphones\n"
"filters then writes it out to OUTFILE.\n";
int main(int argc, char* argv[])
{
if (argc != 3)
{
fprintf(stdout, usage, argv[0]);
exit(1);
}
// Load the input data into a buffer
FILE* input = fopen(argv[1], "rb");
if (!input) {
fprintf(stderr, "Cannot open input file '%s'\n", argv[1]);
exit(1);
}
// Get the file size
fseek(input , 0L , SEEK_END);
size_t input_size = ftell(input);
rewind(input);
// Allocate our input and output buffers. This could be optimized
// we dont need to store the whole input and output files in memory.
int samples = input_size / 2;
int16_t *in = (int16_t *) calloc(samples, sizeof(int16_t));
int16_t *out = (int16_t *) calloc(samples, sizeof(int16_t));
fread(in, samples, sizeof(int16_t), input);
fclose(input);
// Open the output file.
FILE* output = fopen(argv[2], "wb");
if (!output)
{
fprintf(stderr, "Cannot open output file '%s'\n", argv[2]);
exit(1);
}
// The smaple proccesing code, essentially the same as the
// code in the firmware's run.c file.
define_filters();
for (int i = 0; i < samples; i++)
{
out[i] = in[i];
}
for (int j = 0; j < FILTER_STAGES; j++)
{
for (int i = 0; i < samples; i ++)
{
// Left channel filter
fix16_t x_f16 = fix16_from_int((int16_t) out[i]);
x_f16 = bqf_transform(x_f16, &bqf_filters_left[j],
&bqf_filters_mem_left[j]);
out[i] = (int32_t) fix16_to_int(x_f16);
// Right channel filter
i++;
x_f16 = fix16_from_int((int16_t) out[i]);
x_f16 = bqf_transform(x_f16, &bqf_filters_right[j],
&bqf_filters_mem_right[j]);
out[i] = (int16_t) fix16_to_int(x_f16);
}
}
// Write out the processed audio.
fwrite(out, samples, sizeof(int16_t), output);
fclose(output);
free(in);
free(out);
}