diff --git a/sw/CMakeLists.txt b/sw/CMakeLists.txt index e214539..98f8887 100644 --- a/sw/CMakeLists.txt +++ b/sw/CMakeLists.txt @@ -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() ################################################################################ diff --git a/sw/resampler/CMakeLists.txt b/sw/resampler/CMakeLists.txt new file mode 100644 index 0000000..790c528 --- /dev/null +++ b/sw/resampler/CMakeLists.txt @@ -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 +) diff --git a/sw/resampler/resampler.hpp b/sw/resampler/resampler.hpp new file mode 100644 index 0000000..1e8c285 --- /dev/null +++ b/sw/resampler/resampler.hpp @@ -0,0 +1,121 @@ +#pragma once + +/* + * SPDX-FileCopyrightText: Korneliusz Osmenda + * + * SPDX-License-Identifier: MIT + */ + + +#include +#include +#include + + +template +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; + } +}; diff --git a/sw/resampler/taps.py b/sw/resampler/taps.py new file mode 100755 index 0000000..41db45b --- /dev/null +++ b/sw/resampler/taps.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +# SPDX-FileCopyrightText: Korneliusz Osmenda +# 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 + +static constexpr std::array fir_coeff = +{{ + {", ".join((str(int(q*(1<<15))) for q in more_itertools.interleave(flt0[::-1],flt1[::-1],flt2[::-1],flt3[::-1])))} +}}; + +""") diff --git a/sw/sbplay.cpp b/sw/sbplay.cpp index 719a44b..d18c694 100644 --- a/sw/sbplay.cpp +++ b/sw/sbplay.cpp @@ -30,6 +30,7 @@ #include "pico/stdlib.h" #include "audio_i2s_minimal.h" +#include #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 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();