Kadent
- 23 Devlogs
- 229 Total hours
Kadent is a DAW software with a custom language called KASL designed to create synthesizers and effects. Backed by Cranelift, KASL can be compiled very quickly and can be run inside Kadent seamlessly.
Kadent is a DAW software with a custom language called KASL designed to create synthesizers and effects. Backed by Cranelift, KASL can be compiled very quickly and can be run inside Kadent seamlessly.
It’s been a while since my last devlog!
During this time I’ve added an Acknowledgements view where you can look at license text and other informations of the Fonts and Crates that I’ve used.
It was a bit difficult to implement because there are so many crates, but I’m satisfied with the result.
I’ve implemented error highlighting in the code editor. I also added a list of errors at the bottom of the code editor, and I can jump to the position of the error by clicking the error.
TextEdit in egui does not support highlighting character by character and instead it only highlighting by each sections, so red underlined part may be bigger than the actual error range, but I guess it still looks nice.
I’ve created a simple binary serialization crate called “sode” for Kadent. This guarantees that the output binary won’t change even if the struct is modified so I thought it would be better than serde or that sort of serialization library.
impl Encode for Note {
fn encode(&self, e: &mut Encoder) -> Result<(), EncodeError> {
e.field(0, &self.start)?;
e.field(1, &self.duration)?;
e.field(2, &self.pitch)?;
e.field(3, &self.velocity)?;
Ok(())
}
}
impl Decode for Note {
fn decode(d: &mut ValueDecoder) -> Result<Self, DecodeError> {
let d = d.to_field_decoder()?;
Ok(Note::new(
d.field(0)?.ok_or(DecodeError::InvalidData)?,
d.field(1)?.ok_or(DecodeError::InvalidData)?,
d.field(2)?.ok_or(DecodeError::InvalidData)?,
d.field(3)?.ok_or(DecodeError::InvalidData)?,
))
}
}
Audio regions require resampling because the sample rate of the source data may differ from the required sample rate.
So far, the audio engine for Kadent has been resampling the entire audio data before playback or exporting. However, resampling audio data in advance takes a lot of CPU and increases the time it takes to prepare for playback.
This time I made it resample the audio data on demand during playback. This makes audio thread not to store the massive resampled audio data.
*sample rate: a value that indicates how many samples are in a single second
*sample: a value that represents the sound wave’s loudness at a specific moment in time
This time, I’ve mainly implemented two features: waveform rendering and timeline scroll bar.
I’ve added a waveform rendering logic to the timeline. I handled it by generating peak value data to render it quicker than calculating the peak value while rendering.
After all, it looks pretty nice and scales perfectly when zooming with my trackpad.
Another feature I’ve added is a timeline scroll bar. It may not look appealing, but its calculation was quite hard actually. I had to calculate the ratio of the visible area and the entire timeline to get the width of the scroll bar. I think it is lot better than egui’s builtin scroll bar, so I’m quite satisfied with the outcome!
I’ve implemented a feature that the user can adjust the project’s export range just by dragging the handle in the ruler.
Also the actual number is shown in the status bar while the handle is being dragged so the user can know the exact beat where is going to be exported.
I’ve streamlined the note processing logic in the audio engine. So far, the audio engine handled sequenced notes (notes that you can see in the editor) and the realtime MIDI notes that you can play using some MIDI controllers differently. It also lacked maintainability.
That’s why I’ve completely rebuilt the logic to unify the process. It also contributed to reduce the amount of code, and I’m happy with that.
I’ve added a new status bar, which shows some informations such as sample rate, buffer size and the selected content, at the bottom of the window.
The node graph used to be rendered on the front of the header, but I finally fixed this by using Panel to limit the area where painter can draw the graph.
I’ve improved the design of the menu by adjusting the button style. It took quite a long time because of egui’s a little bit confusing styling API.
I’ve refactored the audio engine by using integer ticks to manage time instead of f32 beats. By using integer ticks, it can be calculated even faster and more accurately.
I’ve implemented a logic in the audio engine, that allows the Kadent DAW to switch the output device.
It was challenging because Mixer and MPSC consumers cannot be cloned. Finally I used Arc and Mutex in order to share the context with multiple threads.
let callback_ctx = Arc::new(Mutex::new(OutputCallbackContext {
mixer,
command_cons,
midi_cons,
vu_prod,
pending_project: pending_arc,
}));
I used try_lock instead of normal lock, which may block the thread, as you can see in the screenshot.
So far, KASL compiler didn’t allow us to import external programs with the same name, because the name of the namespace conflicts. Today I have added new alias feature, which can be used like this:
import foo as bar
With this syntax, you can import a program named foo.kasl and refer to it with bar.
This can also improve readability when importing a file with a long name!
I wanted to try compiling Kadent to application that can be installed using an installer, instead of a simple executable binary. I found Taskfile, which is a build tool similar to make but uses a yaml file to define workflows. I found it very useful so I adopted it and it worked very well! Currently I only have workflow for macOS compilation but I want to add Windows and Linux support soon!
I’ve finally finished implementing the splash screen for Kadent. On the left side, there are two buttons for creating or opening a project. The right panel shows the recent projects.
The biggest challenge in implementing the splash screen was to layout UI components. It was quite hard to center the logo and the buttons. I spent around 1hrs dealing with that problem.
Here’s how I solved it:
const BUTTON_WIDTH: f32 = 300.0;
const BUTTON_HEIGHT: f32 = 42.0;
const CONTENT_HEIGHT: f32 = 60.0 + 12.0 + 16.0 + 24.0 + 12.0 + BUTTON_HEIGHT * 2.0;
impl SplashUi {
pub(super) fn splash_controls(&mut self, ui: &mut egui::Ui) -> Option<EditorTransition> {
ui.vertical_centered(|ui| {
// Add space to vertically center the UI parts
ui.add_space(full_height / 2.0 - CONTENT_HEIGHT / 2.0);
// UI Components
})
.inner
}
}
Since centering both horizontally and vertically while calculating the height of the inner content is painful, I eventually hardcoded the height of the UI components in order to center the UI vertically.
I’ve also designed a logo for Kadent in the meantime. I tweaked the font called “Inter”, which is available on Google Fonts, for the logo.
As my codebase became bigger, it started to be a bit complicated, so I’ve refactored the codebase. New folder structure is consist of four directories in src/ directory: commands, core, storage, and ui.
commands directory contains the audio engine communication logic. It has some functions like add_note that adds a new note to the specified track and sends the updated project to the audio thread.
core directory contains KASLNode implementation and some metadata. KASLNode is a specific node that can literally execute KASL programs. Metadata is a data that are not necessarily tied to audio engine. It includes track colors, track names, region names, etc.
storage directory contains some implementation for storing and loading files. It has a logic to save and load the Kadent project file.
ui directory contains the UI implementation. Inside the ui directory, there are components, theme, and workspaces directories. components directory has some common UI parts used in various parts of the DAW. theme defines colors and size used in the UI. workspaces contains the UI implementation for the editor and the splash screen.
I’m currently implementing a splash screen for Kadent. It used to have just two buttons to create or open a project. I’ve just finished implementing “Recent Projects” list. (shown on the right side)
Implemented multithread track processing for the audio engine using Rayon!
I used to think implementing multithreading would be a nightmare, but I utilized Rayon which was actually very intuitive and easy to use.
The screenshot shows the exact code where the tracks are being processed in parallel.
I am working on a WASM backend for my KASL language!