16 changes: 16 additions & 0 deletions README.md
@@ -0,0 +1,16 @@
# 🌰 koriborosu 🌰

A simple Audio and MIDI file render, powered by [Carla Plugin Host](https://github.com/falkTX/Carla)

Still very much work-in-progress, but the idea is simply:

* Specify 1 input file to read (can be an audio or MIDI file, optionally also just a number of seconds)
* Specify what plugins to run after, in order of processing

Koriborosu will run the plugins and generate an audio file at the end with its output.

This can be used to:

* Simple render of a MIDI file to WAV via softsynth
* Render a MIDI file, with added MIDI filters on top, then synths, then some FX because why not
* Apply some FX on an audio file
4 changes: 4 additions & 0 deletions kuriborosu.kdev4
@@ -0,0 +1,4 @@
[Project]
CreatedFrom=Makefile
Manager=KDevCustomMakeManager
Name=kuriborosu
2 changes: 2 additions & 0 deletions src/.kdev_include_paths
@@ -0,0 +1,2 @@
/usr/include/carla/
/usr/include/carla/includes/
72 changes: 72 additions & 0 deletions src/Makefile
@@ -0,0 +1,72 @@
#!/usr/bin/make -f
# Makefile for ACE #
# ---------------- #
# Created by falkTX
#

include ../Makefile.base.mk

# --------------------------------------------------------------
# Project name, used for binaries

NAME = kuriborosu

# --------------------------------------------------------------
# Files to build

FILES = \
kuriborosu.c

# ---------------------------------------------------------------------------------------------------------------------
# Basic setup

TARGET_DIR = ../bin
BUILD_DIR = ../build/$(NAME)

# BASE_FLAGS += -I.
BASE_FLAGS += $(shell pkg-config --cflags carla-host-plugin sndfile)
LINK_FLAGS += $(shell pkg-config --libs carla-host-plugin sndfile)

# BUILD_C_FLAGS += -I.
# BUILD_CXX_FLAGS += -I.

# ---------------------------------------------------------------------------------------------------------------------
# Set files to build

OBJS = $(FILES:%=$(BUILD_DIR)/%.o)

# ---------------------------------------------------------------------------------------------------------------------
# Default build target

TARGET = $(TARGET_DIR)/$(NAME)

all: build

build: $(TARGET)

# ---------------------------------------------------------------------------------------------------------------------
# Build commands

$(TARGET): $(OBJS)
-@mkdir -p $(shell dirname $@)
@echo "Linking $(NAME)"
$(SILENT)$(CXX) $^ $(LINK_FLAGS) -o $@

$(BUILD_DIR)/%.c.o: %.c
-@mkdir -p "$(shell dirname $(BUILD_DIR)/$<)"
@echo "Compiling $<"
$(SILENT)$(CC) $< $(BUILD_C_FLAGS) -c -o $@

$(BUILD_DIR)/%.cpp.o: %.cpp
-@mkdir -p "$(shell dirname $(BUILD_DIR)/$<)"
@echo "Compiling $<"
$(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@

clean:
rm -rf $(BUILD_DIR)

# ---------------------------------------------------------------------------------------------------------------------

-include $(OBJS:%.o=%.d)

# ---------------------------------------------------------------------------------------------------------------------
170 changes: 170 additions & 0 deletions src/kuriborosu.c
@@ -0,0 +1,170 @@
// TODO license header here

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sndfile.h>

#include "CarlaNativePlugin.h"

typedef struct {
uint32_t buffer_size;
double sample_rate;
NativeTimeInfo time;
} Koriborosu;

#define koriborosu ((Koriborosu*)handle)

static uint32_t get_buffer_size(NativeHostHandle handle)
{
return koriborosu->buffer_size;
}

static double get_sample_rate(NativeHostHandle handle)
{
return koriborosu->sample_rate;
}

static bool is_offline(NativeHostHandle handle)
{
return true;
}

static const NativeTimeInfo* get_time_info(NativeHostHandle handle)
{
return &koriborosu->time;
}

static bool write_midi_event(NativeHostHandle handle, const NativeMidiEvent* event)
{
return false;
}

static void ui_parameter_changed(NativeHostHandle handle, uint32_t index, float value)
{
}

static void ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program)
{
}

static void ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value)
{
}

static void ui_closed(NativeHostHandle handle)
{
}

static const char* ui_open_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter)
{
// not supported
return NULL;
}

static const char* ui_save_file(NativeHostHandle handle, bool isDir, const char* title, const char* filter)
{
// not supported
return NULL;
}

static intptr_t dispatcher(NativeHostHandle handle,
NativeHostDispatcherOpcode opcode, int32_t index, intptr_t value, void* ptr, float opt)
{
return 0;
}

#undef koriborosu

// TODO argument handling
int main()
{
// TODO read from CLI args
uint32_t opts_buffer_size = 1024;
uint32_t opts_sample_rate = 48000;
const char* inmidi = "/home/falktx/Personal/Muzyks/Projects/MIDI-Classic/clairdelune.mid";
const char* outwav = "/Shared/Personal/FOSS/GIT/falkTX/kuriborosu/testfile.wav";
const char* pluginuri1 = "http://drobilla.net/plugins/mda/Piano";
const char* pluginuri2 = "https://github.com/michaelwillis/dragonfly-reverb";

Koriborosu kori;
memset(&kori, 0, sizeof(kori));
kori.buffer_size = opts_buffer_size;
kori.sample_rate = opts_sample_rate;

NativeHostDescriptor hdesc;
memset(&hdesc, 0, sizeof(hdesc));
hdesc.handle = &kori;
hdesc.resourceDir = carla_get_library_folder();
hdesc.get_buffer_size = get_buffer_size;
hdesc.get_sample_rate = get_sample_rate;
hdesc.is_offline = is_offline;
hdesc.get_time_info = get_time_info;
hdesc.write_midi_event = write_midi_event;
hdesc.ui_parameter_changed = ui_parameter_changed;
hdesc.ui_midi_program_changed = ui_midi_program_changed;
hdesc.ui_custom_data_changed = ui_custom_data_changed;
hdesc.ui_closed = ui_closed;
hdesc.ui_open_file = ui_open_file;
hdesc.ui_save_file = ui_save_file;
hdesc.dispatcher = dispatcher;

const NativePluginDescriptor* const pdesc = carla_get_native_rack_plugin();
const NativePluginHandle phandle = pdesc->instantiate(&hdesc);
pdesc->activate(&hdesc);

const CarlaHostHandle hhandle = carla_create_native_plugin_host_handle(pdesc, phandle);

carla_load_file(hhandle, inmidi);
carla_add_plugin(hhandle, BINARY_NATIVE, PLUGIN_LV2, "", "", pluginuri1, 0, NULL, 0x0);
carla_add_plugin(hhandle, BINARY_NATIVE, PLUGIN_LV2, "", "", pluginuri2, 0, NULL, 0x0);

// TODO read from audio/midi plugin
uint32_t file_frames = 100 * opts_sample_rate;

SF_INFO sf_fmt = {
.frames = 0,
.samplerate = opts_sample_rate,
.channels = 2,
.format = SF_FORMAT_WAV|SF_FORMAT_PCM_16,
.sections = 0,
.seekable = 0,
};
SNDFILE* const file = sf_open(outwav, SFM_WRITE, &sf_fmt);

float* bufN = calloc(1, sizeof(float)*opts_buffer_size);
float* bufL = malloc(sizeof(float)*opts_buffer_size);
float* bufR = malloc(sizeof(float)*opts_buffer_size);
float* bufI = malloc(sizeof(float)*opts_buffer_size*2);

float* inbuf[2] = { bufN, bufN };
float* outbuf[2] = { bufL, bufR };

kori.time.playing = true;

for (uint32_t i = 0; i < file_frames; i += opts_buffer_size)
{
kori.time.frame = i;
pdesc->process(phandle, inbuf, outbuf, opts_buffer_size, NULL, 0);

// interleave
for (uint32_t j = 0, k = 0; j < opts_buffer_size*2; j += 2, ++k)
{
bufI[j+0] = bufL[k];
bufI[j+1] = bufR[k];
}

sf_writef_float(file, bufI, opts_buffer_size);
}

free(bufN);
free(bufL);
free(bufR);
sf_close(file);
pdesc->deactivate(phandle);
pdesc->cleanup(phandle);
carla_host_handle_free(hhandle);
return EXIT_SUCCESS;
}