Use polyphase resampling

This commit is contained in:
Korneliusz Osmenda committed 2025-07-29 22:09:03 +02:00
1 parent 779a81b90e
commit ff737af011
5 files changed
+223 -34

No files matched your search

+4 -1
View File
@@ -206,6 +206,7 @@ add_subdirectory(rp2040-psram)
add_subdirectory(opl)
add_subdirectory(ne2000)
add_subdirectory(cdrom)
add_subdirectory(resampler)
################################################################################
# Build GUS firmware
@@ -265,6 +266,7 @@ function(build_sb TARGET_NAME MULTIFW)
SB_BUFFERLESS_NG=1
USE_CD_AUDIO_FIFO=1
)
target_link_libraries(${TARGET_NAME} resampler)
pico_generate_pio_header(${TARGET_NAME} ${CMAKE_CURRENT_LIST_DIR}/isa_dma.pio)
endfunction()
@@ -277,7 +279,8 @@ function(build_adlib TARGET_NAME MULTIFW)
set(SOUND_OPL TRUE)
config_target(${TARGET_NAME} ${MULTIFW})
pico_set_program_name(${TARGET_NAME} "picogus-adlib")
target_sources(${TARGET_NAME} PRIVATE sbplay.cpp)
target_sources(${TARGET_NAME} PRIVATE sbplay.cpp)
target_link_libraries(${TARGET_NAME} resampler)
endfunction()
################################################################################
+24
View File
@@ -0,0 +1,24 @@
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/taps.hpp
#WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/taps.py ${CMAKE_CURRENT_BINARY_DIR}/taps.hpp
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/taps.py
)
add_custom_target(taps_hpp DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/taps.hpp)
add_library(taps OBJECT IMPORTED GLOBAL)
add_dependencies(taps taps_hpp)
target_include_directories(taps INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
target_compile_features(taps INTERFACE cxx_std_17)
add_library(resampler INTERFACE)
target_include_directories(resampler INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(resampler
INTERFACE
taps
)
+121
View File
@@ -0,0 +1,121 @@
#pragma once
/*
* SPDX-FileCopyrightText: Korneliusz Osmenda <korneliuszo@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <taps.hpp>
#include <stdint.h>
#include <cmath>
template<int16_t (*IN_FN)()>
class Resampler {
int64_t phase; //in 31.32
uint64_t ratio;
std::size_t fir_pos;
int16_t fir[13];
int32_t c0,c1,c2,c3; //in 0.15
public:
void set_ratio(uint32_t in, uint32_t out)
{
uint64_t val = ((uint64_t)in)<<32;
ratio = val/out;
}
int16_t get_sample()
{
phase+=ratio;
while(phase>=1UL<<31) //0.5
{
fir[fir_pos]=IN_FN(); //0.15
fir_pos++;
phase-=1ULL<<32;
if(fir_pos>=13)
fir_pos=0;
const int32_t* lfir = fir_coeff.data();
int16_t* inp = &fir[fir_pos];
int16_t* inp2 = &fir[0];
int16_t* fir_split = &fir[13];
int16_t* fir_split2 = &fir[fir_pos];
uint32_t TMP,TMP2;
int32_t lc0,lc1,lc2,lc3; //in 0.15
lc0=0;
lc1=0;
lc2=0;
lc3=0;
asm (
"1:\n"
"ldrh %[TMP2], [%[INP],#0]\n"
"SXTH %[TMP2],%[TMP2]\n"
"ldr %[TMP], [%[FIR],#0]\n"
"mul %[TMP], %[TMP2], %[TMP]\n"
"add %[C0], %[C0], %[TMP]\n"
"ldr %[TMP], [%[FIR],#4]\n"
"mul %[TMP], %[TMP2], %[TMP]\n"
"add %[C1], %[C1], %[TMP]\n"
"ldr %[TMP], [%[FIR],#8]\n"
"mul %[TMP], %[TMP2], %[TMP]\n"
"add %[C2], %[C2], %[TMP]\n"
"ldr %[TMP], [%[FIR],#12]\n"
"mul %[TMP], %[TMP2], %[TMP]\n"
"add %[C3], %[C3], %[TMP]\n"
"add %[FIR],%[FIR],#16\n"
"add %[INP],%[INP],#2\n"
"cmp %[INP],%[FIR_SPLIT]\n"
"bne 1b\n"
"mov %[INP],%[INP2]\n"
"2:\n"
"cmp %[INP],%[FIR_SPLIT2]\n"
"beq 3f\n"
"ldrh %[TMP2], [%[INP],#0]\n"
"SXTH %[TMP2],%[TMP2]\n"
"ldr %[TMP], [%[FIR],#0]\n"
"mul %[TMP], %[TMP2], %[TMP]\n"
"add %[C0], %[C0], %[TMP]\n"
"ldr %[TMP], [%[FIR],#4]\n"
"mul %[TMP], %[TMP2], %[TMP]\n"
"add %[C1], %[C1], %[TMP]\n"
"ldr %[TMP], [%[FIR],#8]\n"
"mul %[TMP], %[TMP2], %[TMP]\n"
"add %[C2], %[C2], %[TMP]\n"
"ldr %[TMP], [%[FIR],#12]\n"
"mul %[TMP], %[TMP2], %[TMP]\n"
"add %[C3], %[C3], %[TMP]\n"
"add %[FIR],%[FIR],#16\n"
"add %[INP],%[INP],#2\n"
"b 2b\n"
"3:\n"
: [INP]"+l"(inp)
, [FIR]"+l"(lfir)
, [TMP]"+l"(TMP)
, [TMP2]"+l"(TMP2)
, [C0]"+r"(lc0)
, [C1]"+r"(lc1)
, [C2]"+r"(lc2)
, [C3]"+r"(lc3)
, [FIR_SPLIT]"+r"(fir_split)
, [FIR_SPLIT2]"+r"(fir_split2)
, [INP2]"+r"(inp2)
);
c0=lc0; // maxsignal 2.30 tap 2.
c1=lc1>>(30-14); // tap 2.30 -> 2.14
c2=lc2>>15; // tap 1.30 -> 1.15
c3=lc3>>15; // tap 1.30 -> 1.15
};
int32_t lphase = phase>>16; //-1.16
int32_t val = (c0 +
((lphase*(c1 +
((lphase*(c2 +
((lphase*(c3))>>(16+15-15)) //1.15
))>>(16+15-14)) //2.14
))>>(16+14-30)) //2.30
);
return val>>16;
}
};
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Korneliusz Osmenda <korneliuszo@gmail.com>
# SPDX-License-Identifier: MIT
import sympy
import numpy as np
import math
import more_itertools
x = sympy.symbols('x')
flts = sympy.cos(sympy.pi*x/12)**2*sympy.sin(sympy.pi*x/2)/(sympy.pi*x)
fltw0 = sympy.Piecewise((flts,((x>=-6)&(x<=6))),(0,True))
fltw1 = fltw0.diff()/sympy.factorial(1)
fltw2 = fltw0.diff().diff()/sympy.factorial(2)
fltw3 = fltw0.diff().diff().diff()/sympy.factorial(3)
if True:
f0 = sympy.lambdify(x, fltw0, "numpy")
flt0 = [q for q in np.nan_to_num(f0(np.array(range(-6,7))),nan=float(fltw0.limit(x,0)))]
f1 = sympy.lambdify(x, fltw1, "numpy")
flt1 = [q for q in np.nan_to_num(f1(np.array(range(-6,7))),nan=float(fltw1.limit(x,0)))]
f2 = sympy.lambdify(x, fltw2, "numpy")
flt2 = [q for q in np.nan_to_num(f2(np.array(range(-6,7))),nan=float(fltw2.limit(x,0)))]
f3 = sympy.lambdify(x, fltw3, "numpy")
flt3 = [q for q in np.nan_to_num(f3(np.array(range(-6,7))),nan=float(fltw3.limit(x,0)))]
import sys
print(sum((abs(_) for _ in flt0)))
print(sum((abs(_) for _ in flt1)))
print(sum((abs(_) for _ in flt2)))
print(sum((abs(_) for _ in flt3)))
maxsignal = \
(sum((abs(sum(_)) for _ in zip(flt0, \
list(_/2 for _ in flt1), \
list(_/4 for _ in flt2), \
list(_/8 for _ in flt3) \
))))
print(maxsignal)
assert(sum(flt0)<2)
assert(sum(flt1)<2)
assert(sum(flt2)<1)
assert(sum(flt3)<1)
assert(maxsignal<2)
f=open(sys.argv[1],'w')
f.write(f"""
#pragma once
#include <array>
static constexpr std::array<int32_t,13*4> fir_coeff =
{{
{", ".join((str(int(q*(1<<15))) for q in more_itertools.interleave(flt0[::-1],flt1[::-1],flt2[::-1],flt3[::-1])))}
}};
""")
+12 -33
View File
@@ -30,6 +30,7 @@
#include "pico/stdlib.h"
#include "audio_i2s_minimal.h"
#include <resampler.hpp>
#include "opl.h"
extern "C" void OPL_Pico_simple(int16_t *buffer, uint32_t nsamples);
@@ -95,18 +96,16 @@ static constexpr uint32_t fixed_ratio(uint16_t a, uint16_t b) {
static constexpr uint32_t opl_ratio = fixed_ratio(49716, 44100);
static audio_fifo_t opl_out_fifo;
static int16_t opl_current_sample = 0;
static int16_t opl_next_sample = 0;
static bool opl_need_new_sample = true;
/**
* Linear interpolation in fixed-point
* v0 and v1 are sample values, frac is the fractional position
*/
static inline int16_t lerp_fixed(int16_t v0, int16_t v1, uint32_t frac) {
return v0 + (int16_t)(((int32_t)(v1 - v0) * frac) >> FRAC_BITS);
static int16_t get_opl_sample()
{
int16_t opl_current_sample;
OPL_Pico_simple(&opl_current_sample, 1);
return opl_current_sample;
}
static Resampler<get_opl_sample> resampler;
// Setup values for audio sample clock
// 8390 clock cycles per sample (370MHz / 8390 ~= 44100Hz)
static constexpr uint32_t clocks_per_sample_minus_one = (SYS_CLK_HZ / 44100) - 1;
@@ -182,13 +181,12 @@ void play_adlib() {
#endif
init_audio();
resampler.set_ratio(49716,44100);
printf("opl_ratio: %x ", opl_ratio);
uint32_t opl_pos = 0;
// Initialize OPL samples
OPL_Pico_simple(&opl_current_sample, 1);
OPL_Pico_simple(&opl_next_sample, 1);
opl_need_new_sample = false;
cd_fifo = cdrom_audio_fifo_peek(&cdrom);
@@ -216,27 +214,8 @@ void play_adlib() {
// Generate OPL samples and add to output FIFO
while (fifo_free_space(&opl_out_fifo) > 0) {
// Interpolate at current position
fifo_add_sample(&opl_out_fifo, lerp_fixed(
opl_current_sample,
opl_next_sample,
opl_pos & FRAC_MASK
));
// Advance position
opl_pos += opl_ratio;
// While we've advanced past one or more OPL samples
while ((opl_pos >> FRAC_BITS) > 0) {
// Move to next sample
opl_current_sample = opl_next_sample;
// Generate new next sample
OPL_Pico_simple(&opl_next_sample, 1);
// Subtract 1.0 from position
opl_pos -= (1u << FRAC_BITS);
}
}
fifo_add_sample(&opl_out_fifo, resampler.get_sample());
}
#ifdef USB_STACK
// Service TinyUSB events
tuh_task();