Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ef207e6
fix UpSampler::process by copying all values into the output array at…
ddf Feb 17, 2022
0d6c6f2
Merge pull request #109 from ddf/upsampler-fix
pingdynasty Feb 17, 2022
5f08aaa
Print error message when platform not recognized (otherwise fails wit…
mcclure Jun 10, 2022
11d7cdd
return NULL for zero allocation
pingdynasty Jun 11, 2022
a49a2f4
Merge pull request #111 from mcclure/bad-platform
pingdynasty Jun 11, 2022
3d4e7b1
Update LICENSE
pingdynasty Aug 12, 2024
9f635b7
Merge pull request #122 from RebelTechnology/pingdynasty-patch-1
pingdynasty Aug 12, 2024
e224ba0
Create LICENSE_VCV.md
hemmer Oct 9, 2024
d7ca3f3
Fix MidiMessage::getProgramChange() bug
pingdynasty Nov 3, 2023
fec3dbf
Add MidiMessage::at() to create polyphonic aftertouch messages and so…
pingdynasty Nov 3, 2023
778f601
Add unit tests for MidiMessage
pingdynasty Nov 3, 2023
35e7228
Fix MidiMessage::getProgramChange() bug
pingdynasty Nov 3, 2023
7669791
Add MidiMessage::at() to create polyphonic aftertouch messages and so…
pingdynasty Nov 3, 2023
cc37dfb
Add unit tests for MidiMessage
pingdynasty Nov 3, 2023
76a0a15
Update working in LICENSE_VCV.md
pingdynasty Oct 16, 2024
3942f82
Merge branch 'hemmer-patch-1' into develop
pingdynasty Oct 16, 2024
2338f4d
Add reproducible Docker build env (pinned old GCC) + project knowledg…
pingdynasty Oct 23, 2026
7577d9b
Dockerfile: add FAUST, SOUL and FirmwareSender toolchains
pingdynasty Oct 23, 2026
f9b22d1
gitignore the local knowledge base (kb/)
pingdynasty Oct 24, 2026
c17dca9
Dockerfile: install emsdk 2.0.10 by SDK hash to avoid release-tag res…
pingdynasty Oct 24, 2026
9d1c8ac
Dockerfile: pin base image to linux/amd64 (Apple Silicon fix)
pingdynasty Oct 24, 2026
454c710
Dockerfile: add PLATFORM build-arg and pre-build libs into the image
pingdynasty Oct 24, 2026
34171bb
Dockerfile: silence FromPlatformFlagConstDisallowed lint
pingdynasty Oct 24, 2026
52c0b04
ci: publish per-platform Docker image to GHCR on push to develop
pingdynasty Oct 24, 2026
45e3fd9
Dockerfile: shallow-clone emsdk at its tag (--depth 1 --branch)
pingdynasty Oct 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.git
Build/*
!Build/.gitignore
Libraries/**/*.a
Libraries/**/*.o
95 changes: 95 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: Publish Docker image

# Build the OwlProgram build-environment image and publish it to the GitHub
# Container Registry (GHCR) at ghcr.io/<owner>/owlprogram.
#
# Triggers on a push to develop that touches the Dockerfile (or the build
# makefiles / sources it bakes in), and on manual dispatch. No secrets to
# configure: the built-in GITHUB_TOKEN authenticates to GHCR for this repo.
#
# The image is platform-pinned per OWL target via the PLATFORM build-arg
# (OWL1/OWL2/OWL3 produce Cortex-M4/M4/M7 libraries), so we publish one tagged
# image per target plus a default `latest` (= OWL2). The base image is pinned
# to linux/amd64 in the Dockerfile; the x86_64-only toolchain runs under
# emulation on arm64 hosts.

on:
push:
branches: [ develop ]
paths:
- 'Dockerfile'
- '.dockerignore'
- 'compile.mk'
- 'web.mk'
- 'daisysp.mk'
- 'sources.mk'
- 'libs.mk'
- '.github/workflows/docker-publish.yml'
workflow_dispatch:
inputs:
platforms:
description: 'Space-separated OWL platforms to build (e.g. "OWL2 OWL3")'
required: false
default: 'OWL2 OWL3'

env:
REGISTRY: ghcr.io
# ${{ github.repository }} is owner/repo; the image name is the lowercased repo.
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # required to push to GHCR
strategy:
fail-fast: false
matrix:
platform: [ OWL1, OWL2, OWL3 ]

steps:
- name: Checkout (with submodules)
uses: actions/checkout@v4
with:
submodules: recursive # DaisySP must be present or `make libs` fails

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Lowercase image name
id: img
run: echo "name=${IMAGE_NAME,,}" >> "$GITHUB_OUTPUT"

- name: Compute tags
id: meta
run: |
P="${{ matrix.platform }}"
plc=$(echo "$P" | tr '[:upper:]' '[:lower:]')
base="${{ env.REGISTRY }}/${{ steps.img.outputs.name }}"
tags="${base}:${plc}"
# Tag OWL2 also as the default moving `latest`.
if [ "$P" = "OWL2" ]; then
tags="${tags},${base}:latest"
fi
echo "tags=${tags}" >> "$GITHUB_OUTPUT"

- name: Set up Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push ${{ matrix.platform }} image
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64
build-args: |
PLATFORM=${{ matrix.platform }}
tags: ${{ steps.meta.outputs.tags }}
# Cache layers across runs to speed up the slow FAUST/emsdk layers.
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ Source/progname.s

#Unignore this - because .lib extension is ignored for libraries
!FaustCode/owl.lib

# project knowledge base — kept locally, not committed
kb/
204 changes: 204 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# check=skip=FromPlatformFlagConstDisallowed
# OwlProgram — reproducible build environment ("last stable state")
#
# Why these versions:
# * Ubuntu 20.04 (focal) — period-appropriate userland for the OWL "last
# stable" era (the README targets Ubuntu 18.04; focal is the closest LTS
# that still carries the old ARM toolchain in apt and a C++17 host gcc).
# * arm-none-eabi-gcc 9-2019-q4 (apt: 15:9-2019-q4) — the ARM cross compiler
# from ~2019, i.e. the "right, now-outdated gcc from 4-5 years ago". This
# PRE-DATES the GCC 13 freestanding-mode breakage reported in upstream
# issue #120 ("Compiling anything fails on latest arm-none-eabi-gcc"),
# which is exactly why a pinned old toolchain is required. The project's
# own CI pins the neighbouring 10-2020-q4 release; both build green.
# * host gcc/g++ 9.3.0 — for the native build + unit tests (native.mk needs
# C++17; gcc 9 supports it).
# * Python + hvcc requirements — for Pure Data (HEAVY=) patches.
# * emscripten / emsdk 2.0.10 (the exact version the CI pins) — for the
# JavaScript/WASM `make web` build. This is what the OwlServer online
# compiler (https://github.com/RebelTechnology/OwlServer) uses to turn a
# patch into a browser-runnable build, so it is installed by default here.
#
# This image compiles ARM patches, builds the web (emscripten) patch, builds
# FAUST and SOUL patches, runs the native unit tests, and bundles
# FirmwareSender for MIDI sysex packaging / device upload. See the usage notes
# at the bottom.

# The pinned toolchain (emsdk 2.0.10, and in v2 the ARM-published GCC) ships
# ONLY x86_64 binaries, so we pin the platform to linux/amd64. On an arm64 host
# (Apple Silicon) this builds under Docker Desktop's emulation; without the pin,
# emsdk fails with "No tool or SDK found by name 'sdk-releases-upstream-...'"
# because the 2.0.10 tag has no arm64 SDK bundle.
FROM --platform=linux/amd64 ubuntu:20.04

# Avoid interactive tzdata prompts during apt installs.
ENV DEBIAN_FRONTEND=noninteractive

# Target OWL platform the baked-in libraries are built for. Override at build
# time, e.g. `docker build --build-arg PLATFORM=OWL3 -t owlprogram:owl3-v1 .`.
# The libs (libowlprg.a/libdaisysp.a) are platform-specific (Cortex-M4 vs M7),
# so each image is pinned to ONE platform; build a separate image per target.
ARG PLATFORM=OWL2

# ---- Toolchain + build prerequisites -------------------------------------
# build-essential -> host gcc/g++/make (native build, unit tests)
# gcc-arm-none-eabi -> ARM cross toolchain, 2019-q4 (the pinned old GCC)
# binutils-arm-none-eabi + libnewlib -> assembler/linker + freestanding libc
# python2 / python3 + pip -> Heavy (hvcc) PD compiler
# libasound2-dev libcurl4-openssl-dev pkg-config -> FirmwareSender build deps
# libfreetype6-dev libx11-dev libxext-dev libxinerama-dev libxrandr-dev
# libxcursor-dev libgl1-mesa-dev -> JUCE module compile deps pulled in by
# FirmwareSender's juce_audio_devices/juce_events modules (even though the
# app itself is a headless ConsoleApp, the JUCE sources #include these).
# faust -> FAUST DSP compiler for `make FAUST=...` patches (focal ships
# 2.20.2, a 2019-era release that matches the "last stable" window).
# OwlProgram's faust.mk calls the `faust` binary directly with its
# own FaustSource/owl.cpp architecture file, so no faust2owl needed.
# curl unzip -> fetch + unpack the SOUL compiler release (binary distribution)
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
make \
gcc-arm-none-eabi=15:9-2019-q4-0ubuntu1 \
binutils-arm-none-eabi \
libnewlib-arm-none-eabi \
libstdc++-arm-none-eabi-newlib \
git \
ca-certificates \
python3 \
python3-pip \
python2 \
libasound2-dev \
libcurl4-openssl-dev \
pkg-config \
faust \
curl \
unzip \
libfreetype6-dev \
libx11-dev \
libxext-dev \
libxinerama-dev \
libxrandr-dev \
libxcursor-dev \
libgl1-mesa-dev \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /owl

# ---- Emscripten (emsdk) 2.0.10 — pinned to match OwlProgram CI --------------
# Installed before copying the source so edits to the patch don't invalidate
# this (slow) layer. emsdk 2.0.10 ships its own clang/node, no system node
# needed. Shallow-clone the 2.0.10 TAG directly (--depth 1 --branch) so we pull
# only that revision's tree, not the whole emsdk history. emsdk git tip now
# requires Python 3.10+, but Ubuntu 20.04 ships Python 3.8, so the old tag is
# what runs on this base image. `make web` finds emcc via the PATH set below.
# (The base image is pinned to linux/amd64 above — emsdk 2.0.10 has no arm64
# SDK bundle, so the build must be x86_64.)
RUN git clone --depth 1 --branch 2.0.10 \
https://github.com/emscripten-core/emsdk.git /opt/emsdk \
&& cd /opt/emsdk \
&& ./emsdk install 2.0.10 \
&& ./emsdk activate 2.0.10

# Put emcc et al. on PATH for every shell so plain `make web` / `make libs`
# find emcc without a manual `source emsdk_env.sh`.
ENV EMSDK=/opt/emsdk \
PATH=/opt/emsdk:/opt/emsdk/upstream/emscripten:/opt/emsdk/node/12.18.1_64bit/bin:$PATH

# ---- SOUL compiler 1.0.82 — for `make SOUL=...` patches --------------------
# ROLI/soul-lang shipped SOUL as a prebuilt binary (the compiler is not
# open-source-buildable). 1.0.82 is the last 1.0.x release (2020), matching
# the "last stable" era. The release zip (binaries-linux-combined.zip) carries
# per-arch CLIs under linux/{x64,arm32,arm64,bela}/soul — we must pick the x64
# one (a bare `find -name soul` can grab an ARM build that won't exec here).
# OwlProgram's soul.mk invokes it as `Tools/soul generate --cpp ...`, so we
# drop the binary at /usr/local/bin/soul AND symlink it into the source tree's
# Tools/ after COPY (see below). Network fetch is confined to this layer so it
# doesn't invalidate the source COPY layer.
RUN curl -sSLf -o /tmp/soul.zip \
https://github.com/soul-lang/SOUL/releases/download/1.0.82/binaries-linux-combined.zip \
&& unzip -o /tmp/soul.zip -d /tmp/soul \
&& install -m 0755 /tmp/soul/linux/x64/soul /usr/local/bin/soul \
&& rm -rf /tmp/soul /tmp/soul.zip \
&& soul --version 2>&1 | head -1

# ---- FirmwareSender — for `make load/store/sysex` device upload ------------
# Built from pingdynasty/FirmwareSender (JUCE app). Needs ALSA + curl dev libs
# (installed above). Produces a CLI that packs a patch .bin into MIDI sysex and
# sends it to an attached OWL device over USB-MIDI. The OwlProgram Makefile
# expects it at Tools/FirmwareSender; we build to /usr/local/bin/FirmwareSender
# and symlink into Tools/ after COPY. Actual device upload also needs USB/MIDI
# passthrough into the container (e.g. `docker run --device=/dev/snd ...`).
RUN git clone --depth 1 https://github.com/pingdynasty/FirmwareSender.git /opt/FirmwareSender \
&& make -C /opt/FirmwareSender/Builds/Linux CONFIG=Release \
&& install -m 0755 \
"$(find /opt/FirmwareSender/Builds/Linux/build -type f -name FirmwareSender | head -1)" \
/usr/local/bin/FirmwareSender \
&& FirmwareSender --help >/dev/null 2>&1; echo "FirmwareSender installed"

# Copy the already-checked-out source (submodules included) into the image.
COPY . /owl

# Wire the SOUL + FirmwareSender binaries into the paths OwlProgram's Makefiles
# expect (Tools/soul and Tools/FirmwareSender), pointing at the versions
# installed above. Symlinks keep the single source of truth in /usr/local/bin.
RUN ln -sf /usr/local/bin/soul /owl/Tools/soul \
&& ln -sf /usr/local/bin/FirmwareSender /owl/Tools/FirmwareSender

# Point the ARM Makefiles at the apt-installed cross toolchain. The apt
# package puts arm-none-eabi-* on PATH under /usr/bin, so TOOLROOT is /usr/bin/
# (trailing slash required by compile.mk: CC=$(TOOLROOT)arm-none-eabi-gcc).
ENV TOOLROOT=/usr/bin/

# Install the Heavy (hvcc) Python requirements so `make check` can build PD
# patches. hvcc of this era runs under Python; use pip3. Non-fatal if the
# pinned deps have drifted — C++/native builds don't need them.
RUN pip3 install --no-cache-dir -r Tools/hvcc/requirements.txt || true

# Pre-build the patch libraries for the target platform so a later
# `make PATCHNAME=X patch` links immediately (no separate `make libs` needed).
# `make libs` needs only PLATFORM (no PATCHNAME) and produces the ARM archives
# libowlprg.a/libdaisysp.a plus the web libs. These are Cortex-M4 (OWL1/2) or
# Cortex-M7 (OWL3) specific, which is why PLATFORM is fixed per image: building
# a patch for a DIFFERENT platform in this image would silently reuse the wrong
# archives (the paths are not platform-keyed), so use the matching image.
RUN make PLATFORM=${PLATFORM} libs

# Record the toolchain versions in the image for provenance.
RUN echo "=== Toolchain versions (PLATFORM=${PLATFORM}) ===" \
&& arm-none-eabi-gcc --version | head -1 \
&& gcc --version | head -1 \
&& make --version | head -1 \
&& (faust --version 2>&1 | head -1) \
&& (soul --version 2>&1 | head -1) \
&& (echo "FirmwareSender: $(ls -1 /usr/local/bin/FirmwareSender)") \
&& (echo "Prebuilt libs: $(ls -1 Libraries/libowlprg.a Libraries/libdaisysp.a 2>/dev/null)")

# Default: show the build help. Override with e.g.
# docker run --rm owlprogram:stable make PATCHNAME=Template patch
CMD ["make", "help"]

# ---------------------------------------------------------------------------
# What this image can build:
#
# ARM patch (default OWL2/OWL3): make PATCHNAME=Template patch
# Native build + unit tests: make test / make check
# Web (emscripten, OwlServer): make web / make libs
# FAUST patch: make PATCHSOURCE=<dir> FAUST=<Name> patch
# SOUL patch: make PATCHSOURCE=<dir> SOUL=<Name> patch
# Heavy / Pure Data patch: make HEAVY=<Name> patch
#
# Device upload with FirmwareSender (make load / store / sysex) needs the OWL
# connected over USB-MIDI AND passed into the container, e.g.:
# docker run --rm -it --device=/dev/snd \
# -v /dev/bus/usb:/dev/bus/usb owlprogram:stable \
# make PATCHNAME=Template OWLDEVICE="OWL-*" load
#
# Installed compilers/tools:
# * host gcc/g++ 9 (native build + unit tests)
# * arm-none-eabi-gcc 9 (ARM cross build — the pinned old toolchain)
# * emscripten 2.0.10 (web build used by OwlServer)
# * faust 2.20.2 (FAUST DSP patches)
# * soul 1.0.82 (SOUL patches — prebuilt ROLI binary)
# * FirmwareSender (MIDI sysex packaging + device upload)
# * hvcc / Heavy (Pure Data patches)
# ---------------------------------------------------------------------------
Loading
Loading