↹lature

Repository: https://git.sr.ht/~jaxter184/tlature

From the README:

↹lature is a tracker-style DAW. Its most unique features include:

  • First-class support for Open Sound Control
  • CLAP plugin support
  • Terminal UI
  • Keyboard-focused workflow with Kakoune/vim style keybindings
  • Headless mode for portable playback on a SBC or dedicated live performance machine
  • A human-readable and git-friendly file format

villain origin story

Around 2017, I was reverse-engineering the Bitwig Studio internal device files to try and create my own version of the modular environment that they had been teasing since before the 1.0 release. Screenshots of it are hard to find, but there's one at the bottom of this article.

Bitwig stores its device files in /opt/bitwig-studio/Library/devices on Linux, and in some old macOS versions, they stored them in JSON format. Though the binary format is (as far as I know) completely bespoke, it is vaguely reminiscent of BSON.

One of the other interesting things that they were doing before 3.0 was embedding their own (JIT?) compiled language called Nitro. As far as I know, there exists no documentation for it online, and I am hesitant to post any example code since basically all existing Nitro code is copyrighted by Bitwig, but it basically looks like C with some special plumbing keywords.

Fast forward a few months, and Bitwig 3.0 is released, along with some very cool new devices, including "The Grid". The Grid was a more abstract, "friendly" interface that I assume is meant to act as a substitute for access to the real modular environment that they use internally to create their native devices, with the target audience leaning more toward musicians than programmers. Notably, it lacked any tools for creating inline UIs or doing frequency-domain processing.

But worse (for me), the devices files in the new version were all encrypted (I think). This meant that any new devices released from then on were completely inaccessible to me. While this wasn't too big of a deal since I wanted to make my own devices anyway, this reminded me that this sort of introspection into the inner workings of Bitwig Studio isn't necessarily something Bitwig Gmbh the corporation appreciates. The company's goal is, by definition, to make money, and one of the main ways they do that is by creating new devices that users need to buy additional updates to access. If there were some pack of homebrew devices, this would make it harder for them to sell updates.

I don't harbor too much ill will towards Bitwig, and there are myriad potential reasons for doing this that aren't in conflict with my goals (and are, in many cases, in sync):

  • They want to prevent their future DSP from being accessible by competitors like Ableton
  • They might have been getting support requests from confused people trying to use my tool without realizing it wasn't an official Bitwig tool (I tried to match the Bitwig color scheme with my device editor tool, which in retrospect, was a poor choice)

The true reason(s) I can only speculate about, but either way, I had learned a lot about programming practices and tools from this adventure. pytwig (the library I made that I assume Bitwig is OK with because they haven't asked me to take it down yet) was my first "real" programming project used by people other than just me, and while the project itself could have been anything (perhaps if I had pursued VST development to the point of making and releasing an actual plugin), I think it gave me the confidence I needed to execute on a project that better represented my own vision. Plus, before this, I had been primarily using the python IDE and notepad++, and working on this project forced me into more .

Fast forward a few more years: free-audio (backed by Bitwig and u-he) releases the CLAP specification, a C-based API for creating audio processing devices. (That's a surprise tool that can help us later)

Honorary mention (but not technically relevant to ↹lature (yet)): dawproject, a DAW interchange format.

motivation

Features that I want in a DAW:

  • text-based save files
    • better git-friendliness
    • readable/editable in a text editor
  • modal editing
    • preferably keyboard-only
    • ideally in a TUI
  • can play audio on a raspberry pi
  • screen reader compatible
  • package manager for parts that are non-redistributable due to copyright
  • documentation for music
  • capable of integrating a voice synthesizer into the software

Reaper comes really close, but I think only technically. I feel like it doesn't fulfill the spirit of text-editability, and the modal editing only comes with a plugins like reaper-keys or reaper-nvim. I think in order to actually work with a keyboard-only workflow, you have to really rethink what exactly the needs of a DAW are.

Plus, the subtext for all of these features is that fundamentally, I want this DAW to be one that panders to my every whim. No matter how ridiculous the feature, if it's something I want, it should be something this DAW does.

Anyway, if these requirements don't speak to you the way they speak to me, you're probably better off using Bitwig or Reaper (both very good softwares that I still use!).

general design philosophy

  • views should be limited in scope to a few of the most heavily used parts of the interface
  • similarly, common actions should be bound to the easiest keybinds, and uncommon actions should be behind a "command line" bar
  • all actions should be accessible by a "scripting" language.
  • actions should generally default to the "normal" case, but should be configurable to esoteric cases.

prior art

FOSS DAWs

TUI music software

trackers

  • 1987 - the ultimate soundtracker
  • 1989 - OctaMED
  • 1994 - ScreamTracker 3
  • 1995 - Impulse Tracker
  • 1997 - OpenMPT
  • 2002 - renoise
  • 2005 - MilkyTracker
  • 2006 - SchismTracker
  • 2008 - sunvox
  • 2011 - deflemask
  • 2015 - famitracker
  • 2020 - Dirtywave M8
  • 2020 - bintracker
  • 2021 - Furnace

devlog

Controller support

Controller support is implemented using JACK. I initially used midir, but I couldn't figure out how to synchronize notes within the buffer. While the approach that minimizes average latency would be for all the notes to trigger at the beginning of the audio frame, I have heard that psychoacoustically, stable jitter is more important to playability than latency. Plus, at large buffer sizes, having all the messages in a frame trigger at the beginning would likely be noticably distracting.

To implement this, I first add an additional port to the JackProcess struct:

pub struct JackProcess {
	// ...
	in_port_midi: jack::Port<jack::MidiIn>,
}

Then search for a controller and connect it to the port:

for ea_port in active_client.as_client().ports(None, Some("8 bit raw midi"), jack::PortFlags::IS_OUTPUT) {
	if ea_port.contains("HXCSTR") {
		log::debug!("found port {ea_port:?}");
		active_client.as_client().connect_ports_by_name(&ea_port, &format!("{}:midi_in", active_client.as_client().name()))
			.expect(&format!(r#"Could not connect ↹lature midi input to "{:?}"."#, ea_port));
	}
}

Lastly, read the input port in the process loop, adapting the push_event function that I use for auditioning cells:

for jack::RawMidi { time, bytes } in self.in_port_midi.iter(ps) {
	if bytes.len() == 3 {
		let msg = OscMessage {
			addr: "/HXCSTR".to_string(),
			args: vec![OscType::Midi(OscMidiMessage {
				port: 1,
				status: bytes[0],
				data1: bytes[1],
				data2: bytes[2],
			})],
		};
		pd.push_event_root((time as u64, msg));
	}
}

Note that the controller search and OSC message are hardcoded with the string "HXCSTR". Ideally, I'd have both a configurable method to automatically connect controllers as well as a manual menu system in the TUI to set up such connections.

I added controller support so I could practice playing HXCSTR, which unfortunately only supports MIDI at this point in time, but OSC support would likely be similarly trivial.