mirror of
https://github.com/polpo/picogus.git
synced 2026-10-25 21:40:16 +00:00
Use polyphase resampling
This commit is contained in:
1 parent
779a81b90e
commit
ff737af011
5 files changed
+223
-34
No files matched your search
+4
-1
@@ -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()
|
||||
|
||||
################################################################################
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
Executable
+62
@@ -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
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user