Issues Building UGen Plugin for Norns/RPi

Hi All,

I’ve been working on a SuperCollider extension wrapping the DSP code from the excellent Open303 project.

It builds and works absolutely fine on my (intel) MacBook Pro and an M1 iMac at work.

It builds for Windows and generic Linux machines via the GitHub action generated by the Cookiecutter SuperCollider plugin template (though I’ve not managed to test it on these platforms).

Unfortunately, though, I can’t get it working on my RPi-4-based Norns Shield. It compiles without errors, and seemingly runs without producing any errors in SuperCollider, but is sadly silent.

By commenting out parts of the code, I’ve managed to isolate the problem to the emulated TB-303 filter part (oscillator, envelopes and pre and post-“TeeBee” filter high-pass and all-pass filters all seem to work as intended).

Unfortunately, the main filter appears to be the most complicated part of the codebase, and depends on a number of other files.

Does anyone here have any experience building custom plugins for RPi? I’m afraid I’m not very experienced writing C++ (or with low-level programming in general, to be honest), so I’m not sure what my next move might be at this point, other than to throw myself on the mercy of some kind soul who might be able to spot something in the files below that might potentially cause problems on an ARM/Unix platform like the RPi.

Main “TeeBee” filter class:

Header
Implementation

Included one-pole filter:

Header
Implementation

This filter class is used in a couple of other places, and seems to work in those contexts (called at non-oversampled rate), so I’m thinking the problem probably doesn’t lie here.

My suspicions really lie with the files below, however, specifically the __asm instructions contained in both. I’ve commented those out in my code completely, but perhaps the fallback non-assembly methods don’t work as intended or don’t fully replicate the functionality of the ASM code.

NumberManipulations.h (lines 27-31, 63-67)
RealFunctions.h (lines 129-134)

I know it’s a big ask to expect anyone to comb through all these files, but I’m a bit stuck, as sadly the DSP code goes way over my head. I’ve tried contacting the OG developer, but understandably (since this is an old project) he’s not responded.

It sounds great in SuperCollider on my Mac! I’d really love to be able to use it on my Norns script, too!

I’ve been going some searching through the codebase, looking for where the methods using the __asm function are used.

It looks like all are used successfully in other places as well as in the non-working filter, so that might be a red herring.

I wonder if the CMakeCompilerConfig.cmake file (generated by the CookieCutter template) might hold some clues about what could be going wrong on RPi.

# Brian Heim
# 2018-08-26
#
# Compiler configuration help for server plugins

function(sc_do_initial_compiler_config)
    # assume we are not mixing C and C++ compiler vendors
    if(CMAKE_VERSION VERSION_LESS 3.10)
        # slower/more complicated way
        include(CheckCCompilerFlag)
        include(CheckCXXCompilerFlag)
        if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang|GNU")
            CHECK_CXX_COMPILER_FLAG(-msse has_sse)
            CHECK_CXX_COMPILER_FLAG(-msse2 has_sse2)
            CHECK_CXX_COMPILER_FLAG(-mfpmath=sse has_sse_fp)
        elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
            CHECK_CXX_COMPILER_FLAG(/arch:SSE has_sse)
            CHECK_CXX_COMPILER_FLAG(/arch:SSE2 has_sse2)
        else()
            message(WARNING "Unknown compiler: ${CMAKE_CXX_COMPILER_ID}. You may want to modify SuperColliderCompilerConfig.cmake to add checks for SIMD flags and other optimizations.")
        endif()
    else()
        cmake_host_system_information(RESULT has_sse QUERY HAS_SSE)
        cmake_host_system_information(RESULT has_sse2 QUERY HAS_SSE2)
        cmake_host_system_information(RESULT has_sse_fp QUERY HAS_SSE_FP)
    endif()
endfunction()

function(sc_config_compiler_flags target)
    if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang|GNU")
        target_compile_options(${target} PUBLIC
            $<$<BOOL:${has_sse}>:-msse>
            $<$<BOOL:${has_sse2}>:-msse2>
            $<$<BOOL:${has_sse_fp}>:-mfpmath=sse>
            $<$<BOOL:${NATIVE}>:-march=native>
            $<$<BOOL:${STRICT}>:-Wall -Wextra -Werror -Wpedantic>
            )
    elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
        # these options only apply if we're doing a 32-bit build, otherwise they cause a diagnostic
        # https://stackoverflow.com/questions/1067630/sse2-option-in-visual-c-x64
        if(CMAKE_SIZEOF_VOID_P EQUAL 4)
            target_compile_options(${target} PUBLIC
                $<$<BOOL:${has_sse}>:/arch:SSE>
                $<$<BOOL:${has_sse2}>:/arch:SSE2>
                )
        endif()
        if(NATIVE)
            message(WARNING "-DNATIVE is not supported with MSVC")
        endif()
        # C4514: inline function not used
        # C4625: copy ctor implicitly deleted
        # C4626: copy assign implicitly deleted
        # C4820: padding added after member
        # C5026: move ctor implicitly deleted
        # C5027: move assign implicitly deleted
        target_compile_options(${target} PUBLIC
            $<$<BOOL:${STRICT}>:-Wall -WX -wd4820 -wd4514 -wd5026 -wd5027 -wd4626 -wd4625>
            )
    else()
        message(WARNING "Unknown compiler: ${CMAKE_CXX_COMPILER_ID}. You may want to modify SuperColliderCompilerConfig.cmake to add checks for SIMD flags and other optimizations.")
    endif()
endfunction()

So, it appears that Norns runs a 32-bit version of Raspberry Pi OS!
I should have spotted this before!!

Raspberry Pi OS 32-bit apparently does support doubles at 64bit precision, but the 3B+ Pi model (which is what my DIY Norns Shield, and the original Monome Norns are based on) may not have/may not be able to use a hardware floating point unit.

I’m guessing if this is the case, the FPU emulation might not always produce the same results as would be expected from a real hardware FPU (as well as being slower), which is breaking something in the filter.

Are there some compiler flags I need to add to my CMake file that might help here, perhaps?

Every 32-bit system “supports” double precision floats. Word size and floating point precision are orthogonal. The question is only if they can be done in hardware or if they must be emulated in software.

may not have/may not be able to use a hardware floating point unit.

Some ARM chips do not have an FPU, but all Raspberry Pi models certainly do. In fact, the original Raspbian OS was based on Debian armhf (“hf” stands for “hard float”) which assumes that the CPU has a FPU.

1 Like

Thanks @Spacechild1.

I’ve been discussing this over at the Monome forum, too. A user there suggested some specific compiler flags I could add to my CMake configuration.

This sadly didn’t help, but at least I worked out how to detect if the compiling platform is Norns, and set flags appropriately for that platform.

I’ll add a link to the relevant CMake lines in the morning, but have to sleep now.

Thanks very much for getting back to me!

===UPDATE===

It turns out my plugin code was working all along, and the problem was elsewhere! I was pre-scaling one of the plugin parameters in my SynthDef using the linexp() function, which was causing it to become NaN (not sure why), which was breaking the filter. I switched to linlin() (exponential scaling was already being applied in the plugin anyway) and all is now well!

There’s still some distortion going on, so I need to look at level scaling somewhere in the plugin’s internal signal chain, but at least it’s now generating audio on my Norns.

Here are a couple of demos of the plugin being controlled from my bline script on the Norns:


I’ve added a naive implementation of a Low>Band>High-pass morphing filter and some distortion to the basic Open303 sound).

1 Like