nco_tone example¶
Wire a just-makeit object to an external C library — the Doppler NCO —
demonstrating find_package, extra_link_libs, and opaque state holding a
library-owned handle.
TL;DR — see it work first¶
External dependency
This example links against the Doppler DSP library. The test runner auto-fetches a prebuilt tarball when Doppler is not already installed; no manual step is needed unless you are working offline.
Prerequisites¶
Or with pip if just-makeit is already installed:
What it demonstrates¶
find_packageintegration —jm new --find-package Dopplerwiresfind_package(Doppler REQUIRED)into the rootCMakeLists.txtextra_link_libs— link the component's OBJECT library against afind_package-resolved target (doppler::doppler-static)- Opaque state holding a library handle —
nco_state_t *from Doppler is declared opaque;create_implinitialises it,destroy_impltears it down -DDoppler_DIRon the cmake configure line — pointing CMake at an installed library that lives outside the project tree
1. Write the fragment¶
# tone.toml
[tone]
arg_type = "void"
return_type = "float _Complex"
mutable = "true"
extra_link_libs = ["doppler::doppler-static"]
create_impl = """
obj->nco = nco_create(norm_freq, 0);
if (!obj->nco) { free(obj); return NULL; }
"""
destroy_impl = """
nco_destroy(state->nco);
"""
[[tone.state]]
name = "norm_freq"
type = "double"
default = "0.0"
[[tone.state]]
name = "nco"
type = "nco_state_t *"
opaque = true
norm_freq is the normalized frequency (cycles/sample) passed to the
constructor; create_impl forwards it to nco_create. The nco_state_t *
field is invisible to Python — it is created in create_impl, updated in
step(), and released in destroy_impl.
2. Create a project and apply¶
--find-package Doppler writes find_package(Doppler REQUIRED) and a
-DDoppler_DIR hint comment into the root CMakeLists.txt.
3. Build (with Doppler installed)¶
cmake -B build \
-DDoppler_DIR=/path/to/doppler/lib/cmake/Doppler \
&& cmake --build build
ctest --test-dir build
The example's test runner auto-fetches a prebuilt tarball and passes
-DDoppler_DIR automatically when running through just-makeit example nco_tone.
4. Implement step()¶
First make Doppler's NCO header and <math.h> visible by adding them after
the always-present clib_common.h include:
/* native/inc/tone/tone_core.h */
#include "clib_common.h"
#include "nco/nco_core.h"
#include <math.h>
Then fill in step() — advance the NCO one sample and map its phase
accumulator to a unit-magnitude complex exponential:
static inline float _Complex
tone_step(tone_state_t *state)
{
uint32_t phase;
nco_steps_u32(state->nco, 1, &phase);
/* phase in [0, 2^32) maps to angle in [0, 2*pi) */
float angle = (float)phase
* (float)(2.0 * 3.14159265358979323846 / 4294967296.0);
return cosf(angle) + I * sinf(angle);
}
The phase generation is delegated to the Doppler NCO — just-makeit owns the Python binding glue.
5. Use from Python¶
import numpy as np
from tone_demo import Tone
# 0.25 cycles/sample — a quarter turn of the unit circle each step
osc = Tone(norm_freq=0.25)
# Generate 1024 complex samples
buf = osc.steps(1024)
print(buf.dtype) # complex64
print(buf.shape) # (1024,)
# Power check: |e^{jωt}| = 1
print(abs(buf).mean()) # ≈ 1.0
# Four quarter-circle steps cycle through 1, j, -1, -j
print([osc.step() for _ in range(4)]) # ≈ [1, 1j, -1, -1j]
Key concepts¶
extra_link_libs links the OBJECT library to a CMake target. The component's
CMake block grows a target_link_libraries(tone_core ... doppler::doppler-static)
line, making the Doppler headers and static library available during compilation
and linking.
find_package at the project level, linking at the component level. The
project-level find_package(Doppler REQUIRED) makes the doppler::doppler-static
import target available. The component-level extra_link_libs consumes it.
Other components in the same project that don't use Doppler are unaffected.
Opaque state delegates lifetime to the library. The nco_state_t * is
created and destroyed by Doppler's own API (nco_create /
nco_destroy); just-makeit's create_impl / destroy_impl are the
bridge. Python never sees the handle.
See also¶
- C library distribution — how the generated combined library and pkg-config file let C consumers link the same code
- Declarative scaffolding —
c_depsandfind_packages - Scaffold commands —
--find-package