Repository navigation
Releases: zednaked/Nirbija
Release list
0.6.0 — the FM-1
The FM-1: a hardware synth as an instrument of the mixer.
The FM-1 strip
FM-1 is in the picker, next to the Drone and the Sampler. It is the FM-1
synthesizer with Jangada 1.0.1 or later
on USB, and a strip with it finds the device by itself: the strip's input is
the FM-1's USB audio, what the strip plays goes to the FM-1 (MIDI channel n to
track n), and the clock goes along (set the FM-1 to GLO > CLK USB to follow
it). Plug it in after the strip was made and the strip finds it within two
seconds.
- The editor shows the four tracks as the device has them, read from it:
each track's engine and its eight EDIT parameters by their own names
(ANALOG's WAVE, CUT, RES…; FM6's ALG, FB…), the envelope, the sends and the
LFO, as bars that move the FM-1 as they move. A knob turned on the FM-1
moves its bar here; an engine changed there changes the bars. - The session keeps the FM-1's whole project - sounds, patterns and mix
of the four tracks - read after the device changes and settles, or with
Read from FM-1. Opening the session gives it back, before anything is read:
with the FM-1's song playing it waits for it to stop, with the FM-1 away it
waits for it to come back. Send back does it by hand. - Scenes change the FM-1's sounds. CAPTURE keeps the sound a track plays
now (engine, every value, an FM6 track's own patch) as one of the session's
sounds and puts it on the track's SOUND; with Record scene lit the scene
takes it and plays it whole on its bar line. Entering the scene again
plays it again, over whatever the hand did meanwhile. The editor's bars
are scene targets like any plugin's knobs.
One step sequencer per track
A Step Sequencer above the FM-1 plays one of its tracks: the header says
which (→ FM-1 · T2), and a button next to it picks it - every lane onto that
track's channel at once. DRUMS puts the lanes on channel 10 and tunes the
eight of them to kick, snare, closed and open hat, clap, tom, crash and ride,
with the grid's rows named after the kit's pieces. Stack four sequencers above
the FM-1, one per track, each with its own sixteen patterns.
sessions/build-fm1-full.py builds that strip: sessions/run-jam.sh jam-fm1-full.
Underneath
- The engine gives a hardware instrument a pair of MIDI ports that carry
SysEx (extN_out/extN_in); the audio thread only moves bytes, and a
thread of the FM-1's own talks to the device: one request at a time,
pushes apart from replies, SysEx reassembled from pieces, values from the
scenes gathered into one frame (Jangada's editor protocol v8). - PipeWire names the FM-1's USB audio after its USB ids ("pid.codes Test
PID"), with "Jangada" only in the node name: the strip asks PipeWire which
source is the FM-1. - Fixed: the port lists hid every JACK client whose name only began with
ours ("nirbija-something"), as if it were Nirbija itself.
0.5.1 — scenes switch
- Scenes are a switch in the session menu (Scenes: on/off), off in a new
session. Off, the ribbon and the strips' ON/OFF are hidden, nothing plays
or records a scene, and every strip a scene had switched off fades back
in; the scenes stay in the session for when they are switched on. A
session saved with scenes opens with them on.
0.5.0 — scenes
Scenes: a song played part to part on the bar line. Also the fix for 0.4.0's
scan cache, which listed every CLAP and VST3 plugin as not installed from the
second launch on.
Scenes
A song is now a row of scenes across the top of the mixer, each as wide as
it is long. A scene holds what changes from one part to the next - which
strips play, where their faders sit, which pattern each Step Sequencer is on,
and how many bars the way there takes. The list walks on by itself at the
end of each scene; a click or Alt+1…9 plays another from the next bar line,
Hold repeats the one playing, and the last one stays.
A scene is recorded by touching: with Record scene lit, every fader, ON/OFF
and pattern you move on a strip that follows scenes goes into it, and
nothing else does. A strip can ignore scenes altogether (its colour stripe
turns to dashes). Touching a control a scene is walking takes it back until
the next scene starts. Scenes are followed by who they are, not where they
sit: moving or removing one while the song plays leaves the one playing
alone, and removing the one playing hands the song to the next on the bar
line.
Scenes ride plugin knobs too: the FX Pad's sixteen amounts, the Drone, the
Arpeggiator, the Chord and any LV2, CLAP or VST3 parameter walk from where
they are to where the scene wants them over its fade, and a mode, a waveform
or a switch jumps on the line - each format says which parameters those
are. The plugin's own editor follows the walk (the FX Pad's pads move by
themselves, a VST3's knobs turn), and a knob turned in the plugin's own
window is recorded into the scene, or taken from it, like one turned in
Nirbija's. A plugin slot whose pattern or knobs the scene holds carries the
fader's dot in its corner, and a ring once you take it back.
The change lands on the frame of the bar line. A strip switched off fades
along a raised cosine over the scene's bars, a fader walks in decibels, and
a pattern switches on the same frame the scene starts; tests/scene_conductor.cpp
measures the largest step between samples through all of it, with bar lines
both mid-block and on a block edge. The strip's new ON/OFF is its own gain,
apart from the fader and from M, which stays the player's. Scenes live in
the session and in undo; design/scenes.md has the whole design, and the
plugin parameters of phase two and three.
CLAP and VST3 plugins from a warm scan cache
0.4.0 cached the plugin scan but not each plugin's format, so from the second
launch on every CLAP and VST3 plugin was listed as Internal: a session that
named one said "not installed here", and one inserted then was saved as
Internal. The cache now stamps the format back on what it reads
(tests/scan_cache.cpp), and a session saved by 0.4.0 finds its plugins by
uid.
The metronome and the sampler on every beat
The grid the metronome, the sampler and the looper punch on looked for the
next line strictly ahead of each block, and the beat count, summed block by
block, drifts a hair either side of a line that should sit on a block's
edge. Every such beat was lost: at 125 BPM with 256-frame blocks the click
dropped them all, at 93.75 BPM on 64 frames it clicked 8 times in 454, and a
quantised sampler take started a beat late. A line now lands on its nearest
frame and belongs to exactly one block (tests/beat_grid.cpp); the looper
already worked that way and now shares the code.
Install
chmod +x nirbija-0.5.0-x86_64.AppImage && ./nirbija-0.5.0-x86_64.AppImageThe AppImage carries its own Qt. nirbija-0.5.0-x86_64.tar.gz is the binary tree for a machine with Qt 6 already; SHA256SUMS.txt has the checksums.
0.4.0 — made to be played
A drone synth, a sampler with packs, a looper and a step sequencer were already inside the mixer. This release is about playing them live: you edit while it plays, and nothing may click, step or drop out.
Nothing parks the master
Looper Rec, Undo, Clear and Multiply, the autosave during a take, undoing a strip, loading a kit — none of them pause the audio any more. The looper keeps two tapes and copies each frame just before a pass overwrites it, on the audio thread, so an undo is one pointer swap. A test fails on any allocation inside its process().
Every change is a slope, every event on its frame
- The looper punches on the exact frame of the bar, crossfades its wrap and the edges of a take, and ramps gain, play and pitch.
- The step sequencer switches pattern on the downbeat itself; gates and ratchets end on their frame; an external MIDI clock no longer skips a step.
- A sampler pad retriggered or swapped fades out instead of cutting; there is a pool of thirty-two voices.
- FX Pad gate, cutter, stutter and reverse, and the file player's loop, stop and swap, lose their clicks.
- Inserting or bypassing a latent plugin no longer jumps every other strip: delay compensation crossfades.
- MIDI reaches every plugin in time order.
Steadier
- Two crashes that could happen when adding or removing a strip while playing are gone.
- When PipeWire raises the buffer size, plugins are reactivated with the new maximum instead of being overrun.
- CLAP: the note dialect a plugin asks for is honoured (CLAP-only synths used to stay silent), and the host answers restart, parameter and latency requests. VST3: plugins can mark themselves dirty, restarts are real, the controller state is saved. LV2: a sample-rate change keeps the plugin's state.
- Loading a broken session no longer wipes yours; a missing plugin is shown and its state kept.
Lighter
- The window opens before the plugin scan, and scans are cached.
- A silent mixer costs the interface next to nothing; editors redraw only what changed.
- Plugin editors resize when the plugin asks, take the keyboard, and close when the plugin says so.
- The render callback: 16 strips with inserts and sends take about 0.1 ms per 256-frame block, 2% of its time.
Also
- A third sampler pack, Long Chops: sixteen recorded phrases to trim and chop.
- The AppImage is built with link-time optimisation and stripped: 6.9 MB of binary, down from 11.3.
CMakePresets.json, an ArchPKGBUILD, AppStream metadata, CI on every push with ThreadSanitizer and the Qt interface, and 16 new tests.
The whole list is in CHANGELOG.md.
Heads-up: the metronome and count-in click now share one sound (1000/1500 Hz) across the app; for the audio thread to lock its memory, add your user to the realtime (Arch) or audio group.
0.4.0-rc1 — resizing the interface
For testing the one thing 0.3.0 could not be tested for on another screen.
Resize it while it is open
- Ctrl+= bigger, Ctrl+- smaller, Ctrl+0 back to the starting size
- A toast says where it landed — the range stops at 60% and 300%, and without
the toast the keys feel dead once you reach either end - The size is remembered per machine, not in the session: a jam opened on
a laptop has no business resizing the desktop it came from NIRBIJA_UI_SCALEstill sets it at startup, and wins when it is given
Both spellings of the plus key work, the shifted one and the one on the pad.
F1 lists the keys.
What to look for
The interface is built from one number, and twenty size tokens stopped being
constants so the tree relays itself instead of keeping what it was built with.
If something does not follow — a strip that keeps its old width, text that
stays small, a control that stops lining up — that is the thing to report.
Running it
chmod +x nirbija-0.4.0-x86_64.AppImage
./nirbija-0.4.0-x86_64.AppImageNeeds jack or pipewire-jack on the host. Sessions to try are in the source
tarball under sessions/ — jam-techno, jam-goth, jam-slag — loaded from
File → Load session….
0.3.0 — plugins of its own
Three MIDI plugins live in the host now, a strip can be saved and handed to someone, and the transport can be told what to do by an external clock.
Instruments of its own
ECOSYSTEM.md counted the gap: of the MIDI plugins installed on a typical Linux machine, nearly all process notes that already exist and almost none make any. These do.
- Step Sequencer — sixteen steps against the transport, with a grid to play it on. Monophonic, which stops being a limit once they chain:
sessions/jam-goth.jsonstacks four ahead of DrumGizmo and gets a four-voice kit. - Arpeggiator — up, down, up-down, down-up, as played, random, chord, with octaves, gate and latch.
- Script — a MIDI plugin written in Lua, inside the mixer, without compiling anything. The script never runs on the audio thread: it compiles lookup tables on the UI thread and the realtime side only indexes them. Sandboxed by allow list, with an instruction budget.
None of the three needed anything new from the host — the MIDI chain between inserts was already carrying notes from one plugin to the next.
Strips you can keep
Save strip… from a strip's name, Load strip… from the add-strip square. The whole chain travels with every plugin's state, so a sequencer arrives with its pattern and a synth with its patch.
When the file wants a plugin this machine does not have, the strip still arrives — with a hole, and the hole named on screen.
Following an external clock
An ext button and a clock_in port: start, stop, song position and tempo can come from elsewhere.
Fixed
- Duplicating a strip left the chain empty, which is not a copy of anything.
- Two strips could wear the same colour: the accent came from the graph slot, and channels and buses draw slots from separate pools.
Sessions to try
jam-techno, jam-goth and jam-slag are in sessions/ — load one from File → Load session….
Running it
chmod +x nirbija-0.3.0-x86_64.AppImage
./nirbija-0.3.0-x86_64.AppImage --version # no display needed
./nirbija-0.3.0-x86_64.AppImageNeeds jack or pipewire-jack on the host: the JACK client library is deliberately not bundled so it matches the server that is running. Lua is bundled. If an OpenGL plugin editor opens black, try __GLX_VENDOR_LIBRARY_NAME=mesa.
Building from source now needs Lua 5.4 alongside the existing dependencies.
0.2.0 — the picker knows what a plugin is
Supersedes the v0.1.0-rc1 and v0.1.0-rc2 prereleases. rc1 must not be used: any plugin whose editor is built on Qt Widgets aborted the host.
The picker knows what a plugin is
All three formats state their own classification and none of it was being
read — LV2 a class hierarchy, CLAP a feature list, VST3 a subCategories
string. The picker filters by instrument / effect / midi / analyser /
utility and shows what the plugin calls itself beside the maker, so
reverb finds Dragonfly without knowing it is called Dragonfly.
Where a label is unreadable the ports decide: nothing with no audio ports at
all is an audio effect, whatever it claims. On the machine that built this,
286 installed plugins come out as 148 effects, 45 midi, 44 analysers, 31
utilities and 18 instruments, with nothing left unclassified.
Fixed
- A Qt Widgets plugin editor aborted the host. padthv1 caught it. The
firstQWidgeta plugin builds callsqFatalunless the application
object is aQApplication, andqFatalcannot be recovered from — the
editor took the mixer down with it. - Right-click on an insert or output slot asked for the menu and clicked
at the same time, so the editor opened over the menu. - Clicking anything drawn over the add-strip square added a channel, which
is where a stray empty strip in a freshly loaded session came from.
The last two share a cause: a Qt pointer handler sits on the pointer delivery
path rather than in the stacking order, so it answers taps that belong to
whatever is drawn on top of it.
Running it
chmod +x nirbija-0.2.0-x86_64.AppImage
./nirbija-0.2.0-x86_64.AppImage --version # no display needed
./nirbija-0.2.0-x86_64.AppImageNeeds jack or pipewire-jack on the host: the JACK client library is
deliberately not bundled, so it matches the server that is actually running.
If an OpenGL plugin editor opens black, try
__GLX_VENDOR_LIBRARY_NAME=mesa.
The AppImage is 61 MB rather than 55 — Qt Widgets travels with it now, which
is what stops the crash above.
0.1.0-rc2 — three fixes found by testing rc1
Replaces rc1. Do not use the rc1 artifacts: any plugin whose editor is built on Qt Widgets aborted the host on rc1.
Fixed since rc1
- A Qt Widgets plugin editor killed Nirbija. padthv1 was the one that
caught it. The firstQWidgeta plugin builds callsqFatalunless the
application object is aQApplication, andqFatalaborts - the editor
took the mixer down with it. The app is aQApplicationnow; the interface
is still QML and draws no widget of its own. - Right-click on an insert or output slot produced the menu and a click
at the same time, so the editor opened over the menu that had just been
asked for. - Clicking anything drawn over the add-strip square added a channel. That
is where the stray empty strip in a freshly loaded session came from.
The last two share a cause: a Qt pointer handler sits on the delivery path
rather than in the stacking order, so it answers taps that belong to whatever
is drawn on top. Both are MouseAreas now.
Also new
The picker reads what each format declares about itself - LV2 class, CLAP
features, VST3 subCategories - and filters by instrument / effect / midi /
analyser / utility, showing the plugin own words beside the maker. On the
machine that built this: 148 effects, 45 midi, 44 analysers, 31 utilities,
18 instruments, nothing unclassified.
sessions/try-picker-kinds.json in the source tree is a session with one
plugin of each kind and an empty strip to open the picker from.
Note
The AppImage is 61 MB rather than 55: Qt Widgets travels with it now, which
is what stops the crash above.
Needs jack or pipewire-jack on the host - the JACK client library is
deliberately not bundled so it matches the running server.
0.1.0-rc1 — packaging test
Test build, not the release. The v0.1.0 tag is held until this has run somewhere other than the machine that built it.
Built on Arch, Qt 6.11.1, from release/0.1.0.
What to run
chmod +x nirbija-0.1.0-x86_64.AppImage
./nirbija-0.1.0-x86_64.AppImage --version # no display needed
./nirbija-0.1.0-x86_64.AppImageNeeds jack or pipewire-jack on the host: the JACK client library is
deliberately not bundled, so it matches the server that is actually running.
What the test is really for
Plugin editors. The plugins come from the host system and link against the
host Qt; the app runs on the Qt inside the image. Two Qt copies in one
process is the classic way an AppImage plugin host dies. Opening a JUCE or
Qt based editor - Surge XT, Stochas - is the hard case. Dragonfly (LV2/DPF)
does not stress it.
If an OpenGL editor opens black:
__GLX_VENDOR_LIBRARY_NAME=mesa ./nirbija-0.1.0-x86_64.AppImageAlso attached
nirbija-0.1.0-x86_64.tar.gz— plain tree with aninstall.shthat takes
a prefix. Same-distro only; on another Arch box it should work.nirbija-0.1.0-src.tar.gz— same as cloning the branch.
