Browse Source

Rework to split kuribu/kuriborosu and host code, adapt to any carla

Signed-off-by: falkTX <falktx@falktx.com>
main
falkTX 3 years ago
parent
commit
b83003d94f
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
5 changed files with 592 additions and 290 deletions
  1. +12
    -17
      src/Makefile
  2. +413
    -0
      src/host.c
  3. +31
    -0
      src/host.h
  4. +22
    -273
      src/kuriborosu.c
  5. +114
    -0
      src/kuribu.c

+ 12
- 17
src/Makefile View File

@@ -6,17 +6,6 @@

include ../Makefile.base.mk

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

NAME = kuriborosu

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

FILES = \
kuriborosu.c

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

@@ -31,23 +20,29 @@ BASE_FLAGS += -Wno-unused-parameter
# ---------------------------------------------------------------------------------------------------------------------
# Set files to build

OBJS = $(FILES:%=$(BUILD_DIR)/%.o)
FILES = host.c kuriborosu.c kuribu.c
OBJS = $(FILES:%=$(BUILD_DIR)/%.o)

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

TARGET = $(TARGET_DIR)/$(NAME)
TARGETS = $(TARGET_DIR)/kuriborosu $(TARGET_DIR)/kuribu

all: build

build: $(TARGET)
build: $(TARGETS)

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

$(TARGET): $(OBJS)
$(TARGET_DIR)/kuriborosu: $(BUILD_DIR)/kuriborosu.c.o $(BUILD_DIR)/host.c.o
-@mkdir -p $(shell dirname $@)
@echo "Linking kuriborosu"
$(SILENT)$(CXX) $^ $(LINK_FLAGS) -o $@

$(TARGET_DIR)/kuribu: $(BUILD_DIR)/kuribu.c.o $(BUILD_DIR)/host.c.o
-@mkdir -p $(shell dirname $@)
@echo "Linking $(NAME)"
@echo "Linking kuribu"
$(SILENT)$(CXX) $^ $(LINK_FLAGS) -o $@

$(BUILD_DIR)/%.c.o: %.c


+ 413
- 0
src/host.c View File

@@ -0,0 +1,413 @@
// TODO license header here

#include "host.h"

#include <float.h>
#include <math.h>

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

#include <sndfile.h>

typedef struct _Kuriborosu {
uint32_t buffer_size;
uint32_t sample_rate;
const NativePluginDescriptor* plugin_descriptor;
NativePluginHandle plugin_handle;
NativeHostDescriptor host_descriptor;
CarlaHostHandle carla_handle;
NativeTimeInfo time;
bool plugin_needs_idle;
} Kuriborosu;

#define kuriborosu ((Kuriborosu*)handle)

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

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

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

// unused
(void)handle;
}

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

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

// unused
(void)handle;
(void)event;
}

static void ui_parameter_changed(const NativeHostHandle handle, const uint32_t index, const float value)
{
return;

// unused
(void)handle;
(void)index;
(void)value;
}

static void ui_midi_program_changed(const NativeHostHandle handle, const uint8_t channel, const uint32_t bank, const uint32_t program)
{
return;

// unused
(void)handle;
(void)channel;
(void)bank;
(void)program;
}

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

// unused
(void)handle;
(void)key;
(void)value;
}

static void ui_closed(const NativeHostHandle handle)
{
return;

// unused
(void)handle;
}

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

// unused
(void)handle;
(void)isDir;
(void)title;
(void)filter;
}

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

// unused
(void)handle;
(void)isDir;
(void)title;
(void)filter;
}

static intptr_t dispatcher(const NativeHostHandle handle,
const NativeHostDispatcherOpcode opcode, const int32_t index, const intptr_t value, void* const ptr, const float opt)
{
switch (opcode)
{
case NATIVE_HOST_OPCODE_REQUEST_IDLE:
kuriborosu->plugin_needs_idle = true;
return 1;
default:
break;
}

return 0;

// unused
(void)index;
(void)value;
(void)ptr;
(void)opt;
}

#undef kuriborosu

static void carla_stderr2(const char* const fmt, ...)
{
va_list args;
va_start(args, fmt);
fprintf(stderr, "\x1b[31m");
vfprintf(stderr, fmt, args);
fprintf(stderr, "\x1b[0m\n");
fflush(stderr);
va_end(args);
}

static void carla_safe_assert(const char* const assertion, const char* const file, const int line)
{
carla_stderr2("Kuriborosu assertion failure: \"%s\" in file %s, line %i", assertion, file, line);
}

Kuriborosu* kuriborosu_host_init(const uint32_t buffer_size, const uint32_t sample_rate)
{
Kuriborosu* const kuri = (Kuriborosu*)malloc(sizeof(Kuriborosu));

if (kuri == NULL)
return NULL;

memset(kuri, 0, sizeof(Kuriborosu));
kuri->buffer_size = buffer_size;
kuri->sample_rate = sample_rate;

kuri->host_descriptor.handle = kuri;
kuri->host_descriptor.resourceDir = carla_get_library_folder();
kuri->host_descriptor.get_buffer_size = get_buffer_size;
kuri->host_descriptor.get_sample_rate = get_sample_rate;
kuri->host_descriptor.is_offline = is_offline;
kuri->host_descriptor.get_time_info = get_time_info;
kuri->host_descriptor.write_midi_event = write_midi_event;
kuri->host_descriptor.ui_parameter_changed = ui_parameter_changed;
kuri->host_descriptor.ui_midi_program_changed = ui_midi_program_changed;
kuri->host_descriptor.ui_custom_data_changed = ui_custom_data_changed;
kuri->host_descriptor.ui_closed = ui_closed;
kuri->host_descriptor.ui_open_file = ui_open_file;
kuri->host_descriptor.ui_save_file = ui_save_file;
kuri->host_descriptor.dispatcher = dispatcher;

kuri->plugin_descriptor = carla_get_native_rack_plugin();

if (kuri->plugin_descriptor == NULL)
{
fprintf(stderr, "Failed to load Carla-Rack plugin\n");
goto error;
}

kuri->plugin_handle = kuri->plugin_descriptor->instantiate(&kuri->host_descriptor);

if (kuri->plugin_handle == NULL)
{
fprintf(stderr, "Failed to instantiate Carla-Rack plugin\n");
goto error;
}

kuri->carla_handle = carla_create_native_plugin_host_handle(kuri->plugin_descriptor,
kuri->plugin_handle);

if (kuri->carla_handle == NULL)
{
fprintf(stderr, "Failed to create Carla-Rack host handle\n");
goto cleanup;
}

kuri->plugin_descriptor->activate(kuri->plugin_handle);

return kuri;

cleanup:
kuri->plugin_descriptor->cleanup(kuri->plugin_handle);

error:
free(kuri);
return NULL;
}

void kuriborosu_host_destroy(Kuriborosu* const kuri)
{
CARLA_SAFE_ASSERT_RETURN(kuri != NULL,);

kuri->plugin_descriptor->deactivate(kuri->plugin_handle);
kuri->plugin_descriptor->cleanup(kuri->plugin_handle);
carla_host_handle_free(kuri->carla_handle);
}

bool kuriborosu_host_load_file(Kuriborosu* const kuri, const char* const filename)
{
CARLA_SAFE_ASSERT_RETURN(kuri != NULL, false);
CARLA_SAFE_ASSERT_RETURN(filename != NULL, false);

const uint32_t plugin_id = carla_get_current_plugin_count(kuri->carla_handle);

if (carla_load_file(kuri->carla_handle, filename))
{
// Disable audiofile looping
if (strcmp(carla_get_real_plugin_name(kuri->carla_handle, plugin_id), "Audio File") == 0)
{
const uint32_t parameter_count = carla_get_parameter_count(kuri->carla_handle, plugin_id);

for (uint32_t i=0; i<parameter_count; ++i)
{
const CarlaParameterInfo* const info = carla_get_parameter_info(kuri->carla_handle, plugin_id, i);

if (strcmp(info->name, "Loop Mode") == 0)
{
carla_set_parameter_value(kuri->carla_handle, plugin_id, i, 0.0f);
break;
}
}
}

return true;
}

fprintf(stderr, "Failed to load file %s, error was: %s\n",
filename, carla_get_last_error(kuri->carla_handle));
return false;
}

bool kuriborosu_host_load_plugin(Kuriborosu* kuri, const char* filenameOrUID)
{
CARLA_SAFE_ASSERT_RETURN(kuri != NULL, false);
CARLA_SAFE_ASSERT_RETURN(filenameOrUID != NULL, false);

if (carla_add_plugin(kuri->carla_handle, BINARY_NATIVE, PLUGIN_LV2, "", "", filenameOrUID, 0, NULL, 0x0))
return true;

fprintf(stderr, "Failed to load plugin %s, error was: %s\n",
filenameOrUID, carla_get_last_error(kuri->carla_handle));
return false;
}

bool kuriborosu_host_render_to_file(Kuriborosu* const kuri, const file_render_options_t* const options)
{
CARLA_SAFE_ASSERT_RETURN(kuri != NULL, false);
CARLA_SAFE_ASSERT_RETURN(options != NULL, false);

const uint32_t buffer_size = kuri->buffer_size;
const uint32_t sample_rate = kuri->sample_rate;

float* const bufN = malloc(sizeof(float)*buffer_size*2);
float* const bufL = malloc(sizeof(float)*buffer_size);
float* const bufR = malloc(sizeof(float)*buffer_size);

if (bufN == NULL || bufL == NULL || bufR == NULL)
{
fprintf(stderr, "Out of memory\n");
return false;
}

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

// TODO check file NULL or error

if (file == NULL)
{
goto free;
}

// Turn on clipping and normalization of floats (-1.0 - 1.0)
sf_command(file, SFC_SET_CLIPPING, NULL, SF_TRUE);
sf_command(file, SFC_SET_NORM_FLOAT, NULL, SF_TRUE);

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

kuri->time.playing = true;

for (uint32_t i = 0; i < options->frames; i += buffer_size)
{
kuri->time.frame = i;
memset(bufN, 0, sizeof(float)*buffer_size*2);
kuri->plugin_descriptor->process(kuri->plugin_handle, inbuf, outbuf, buffer_size, NULL, 0);

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

sf_writef_float(file, bufN, buffer_size);

if (kuri->plugin_needs_idle)
{
kuri->plugin_needs_idle = false;
kuri->plugin_descriptor->dispatcher(kuri->plugin_handle, NATIVE_PLUGIN_OPCODE_IDLE, 0, 0, NULL, 0.0f);
}
}

if (options->tail_mode == tail_mode_continue_until_silence)
{
// keep going a bit until silence, maximum 5 seconds
const uint32_t until_silence = 5 * sample_rate;
kuri->time.playing = false;

for (uint32_t i = 0; i < until_silence; i += buffer_size)
{
memset(bufN, 0, sizeof(float)*buffer_size*2);
kuri->plugin_descriptor->process(kuri->plugin_handle, inbuf, outbuf, buffer_size, NULL, 0);

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

sf_writef_float(file, bufN, buffer_size);

if (kuri->plugin_needs_idle)
{
kuri->plugin_needs_idle = false;
kuri->plugin_descriptor->dispatcher(kuri->plugin_handle, NATIVE_PLUGIN_OPCODE_IDLE, 0, 0, NULL, 0.0f);
}

if (fabsf(bufN[buffer_size-1]) < __FLT_EPSILON__)
break;
}
}

free:
free(bufN);
free(bufL);
free(bufR);
sf_close(file);

return true;
}

double get_file_length_from_last_plugin(Kuriborosu* const kuri)
{
static const double fallback = 60.0;
CARLA_SAFE_ASSERT_RETURN(kuri != NULL, fallback);

const uint32_t next_plugin_id = carla_get_current_plugin_count(kuri->carla_handle);
CARLA_SAFE_ASSERT_RETURN(next_plugin_id != 0, fallback);

const uint32_t plugin_id = next_plugin_id - 1;
const char* const plugin_name = carla_get_real_plugin_name(kuri->carla_handle, plugin_id);
CARLA_SAFE_ASSERT_RETURN(plugin_name != NULL, fallback);

if (strcmp(plugin_name, "Audio File") == 0 || strcmp(plugin_name, "MIDI File") == 0)
{
const uint32_t parameter_count = carla_get_parameter_count(kuri->carla_handle, plugin_id);

for (uint32_t i=0; i<parameter_count; ++i)
{
const CarlaParameterInfo* const info = carla_get_parameter_info(kuri->carla_handle, plugin_id, i);

if (strcmp(info->name, "Length") == 0)
return carla_get_current_parameter_value(kuri->carla_handle, plugin_id, i);
}
}

return fallback;
}

+ 31
- 0
src/host.h View File

@@ -0,0 +1,31 @@
// TODO license header here

#pragma once

#include "CarlaNativePlugin.h"

typedef struct _Kuriborosu Kuriborosu;

typedef enum tail_mode_t {
// no tail
tail_mode_none,
// keep going a bit until silence, maximum 5 seconds
tail_mode_continue_until_silence,
// loop once, TODO
tail_mode_looping
} tail_mode_t;

typedef struct FILE_RENDER_OPTIONS_T {
const char* filename;
uint32_t frames;
tail_mode_t tail_mode;
} file_render_options_t;

Kuriborosu* kuriborosu_host_init(uint32_t buffer_size, uint32_t sample_rate);
void kuriborosu_host_destroy(Kuriborosu* kuri);

bool kuriborosu_host_load_file(Kuriborosu* kuri, const char* filename);
bool kuriborosu_host_load_plugin(Kuriborosu* kuri, const char* filenameOrUID);
bool kuriborosu_host_render_to_file(Kuriborosu* kuri, const file_render_options_t* options);

double get_file_length_from_last_plugin(Kuriborosu* kuri);

+ 22
- 273
src/kuriborosu.c View File

@@ -1,108 +1,12 @@
// TODO license header here

#include <float.h>
#include <math.h>
#include "host.h"

#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;
bool plugin_needs_idle;
} 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)
{
switch (opcode)
{
case NATIVE_HOST_OPCODE_REQUEST_IDLE:
koriborosu->plugin_needs_idle = true;
return 1;
default:
break;
}

return 0;
}

#undef koriborosu

static double get_file_length_from_plugin(const CarlaHostHandle handle)
{
if (strcmp(carla_get_real_plugin_name(handle, 0), "Audio File") == 0)
return carla_get_current_parameter_value(handle, 0, 5 /* kParameterInfoLength */);
else if (strcmp(carla_get_real_plugin_name(handle, 0), "MIDI File") == 0)
// NOTE WIP, parameter index can change
return carla_get_current_parameter_value(handle, 0, 0 /* kParameterInfoLength */);

// default value
return 60.0;
}

int main(int argc, char* argv[])
{
// TODO use more advanced opts
@@ -111,7 +15,7 @@ int main(int argc, char* argv[])

if (argc >= 2 && strcmp(argv[1], "--version") == 0)
{
printf("koriborosu v0.0.0, using Carla v" CARLA_VERSION_STRING "\n"
printf("kuriborosu v0.0.0, using Carla v" CARLA_VERSION_STRING "\n"
"Copyright 2021 Filipe Coelho <falktx@falktx.com>\n"
"License: ???\n"
"This is free software: you are free to change and redistribute it.\n"
@@ -121,7 +25,7 @@ int main(int argc, char* argv[])

if (argc < 4 || strcmp(argv[1], "--help") == 0)
{
printf("Usage: koriborosu [INFILE|NUMSECONDS] OUTFILE PLUGIN1 PLUGIN2... etc\n"
printf("Usage: kuriborosu [INFILE|NUMSECONDS] OUTFILE PLUGIN1 PLUGIN2... etc\n"
"Where the first argument can be a filename for input file, or number of seconds to render (useful for self-generators).\n\n"
" --help Display this help and exit\n"
" --version Display version information and exit\n");
@@ -131,62 +35,10 @@ int main(int argc, char* argv[])
const char* infile = argv[1];
const char* outwav = argv[2];

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();

if (pdesc == NULL)
{
fprintf(stderr, "Failed to load Carla-Rack plugin\n");
return EXIT_FAILURE;
}

const NativePluginHandle phandle = pdesc->instantiate(&hdesc);

if (phandle == NULL)
{
fprintf(stderr, "Failed to instantiate Carla-Rack plugin\n");
return EXIT_FAILURE;
}

pdesc->activate(&hdesc);
Kuriborosu* const kuri = kuriborosu_host_init(opts_buffer_size, opts_sample_rate);

if (phandle == NULL)
{
fprintf(stderr, "Failed to activate Carla-Rack plugin\n");
pdesc->cleanup(phandle);
if (kuri == NULL)
return EXIT_FAILURE;
}

int ret = EXIT_FAILURE;

const CarlaHostHandle hhandle = carla_create_native_plugin_host_handle(pdesc, phandle);

if (hhandle == NULL)
{
fprintf(stderr, "Failed to create Carla-Rack host handle\n");
goto deactivate;
}

uint32_t file_frames;

@@ -195,23 +47,10 @@ int main(int argc, char* argv[])
const bool isfile = strchr(infile, '.') != NULL || strchr(infile, '/') != NULL;
if (isfile)
{
if (! carla_load_file(hhandle, infile))
{
fprintf(stderr, "Failed to load input file, error was: %s\n", carla_get_last_error(hhandle));
goto deactivate;
}
if (! kuriborosu_host_load_file(kuri, infile))
goto error;

if (strcmp(carla_get_real_plugin_name(hhandle, 0), "Audio File") == 0)
{
// Disable looping
carla_set_parameter_value(hhandle, 0, 0 /* kParameterLooping */, 0.0f);
#if 0
// TESTING reduce overall volume of audio file to prevent clipping
carla_set_volume(hhandle, 0, 0.5f);
#endif
}

file_frames = (uint32_t)(get_file_length_from_plugin(hhandle) * opts_sample_rate + 0.5);
file_frames = (uint32_t)(get_file_length_from_last_plugin(kuri) * opts_sample_rate + 0.5);
}
else
{
@@ -220,7 +59,7 @@ int main(int argc, char* argv[])
if (seconds <= 0 || seconds > 60*60)
{
fprintf(stderr, "Invalid number of seconds %i\n", seconds);
goto deactivate;
goto error;
}

file_frames = (uint32_t)seconds * opts_sample_rate;
@@ -229,7 +68,7 @@ int main(int argc, char* argv[])
if (file_frames > 60*60*opts_sample_rate)
{
fprintf(stderr, "Output file unexpectedly big, bailing out\n");
goto deactivate;
goto error;
}

for (int i = 3; i < argc; ++i)
@@ -238,112 +77,22 @@ int main(int argc, char* argv[])

// check if file
if (plugin_arg[0] == '.' || plugin_arg[0] == '/')
{
if (! carla_load_file(hhandle, plugin_arg))
fprintf(stderr, "Failed to load file %s, error was: %s\n", plugin_arg, carla_get_last_error(hhandle));
}
kuriborosu_host_load_file(kuri, plugin_arg);
else
{
if (! carla_add_plugin(hhandle, BINARY_NATIVE, PLUGIN_LV2, "", "", plugin_arg, 0, NULL, 0x0))
fprintf(stderr, "Failed to load plugin %s, error was: %s\n", plugin_arg, carla_get_last_error(hhandle));
}
kuriborosu_host_load_plugin(kuri, plugin_arg);
}

SF_INFO sf_fmt = {
.frames = 0,
.samplerate = opts_sample_rate,
.channels = 2,
.format = SF_FORMAT_WAV|SF_FORMAT_PCM_16,
.sections = 0,
.seekable = 0,
const file_render_options_t options = {
.filename = outwav,
.frames = file_frames,
.tail_mode = isfile ? tail_mode_continue_until_silence : tail_mode_none,
};
SNDFILE* const file = sf_open(outwav, SFM_WRITE, &sf_fmt);

// TODO check file NULL or error

// Turn on clipping and normalization of floats (-1.0 - 1.0)
sf_command(file, SFC_SET_CLIPPING, NULL, SF_TRUE);
sf_command(file, SFC_SET_NORM_FLOAT, NULL, SF_TRUE);

float* bufN = malloc(sizeof(float)*opts_buffer_size*2);
float* bufL = malloc(sizeof(float)*opts_buffer_size);
float* bufR = malloc(sizeof(float)*opts_buffer_size);

if (bufN == NULL || bufL == NULL || bufR == NULL)
{
fprintf(stderr, "Out of memory\n");
goto free;
}

float* inbuf[2] = { bufN, bufN + opts_buffer_size };
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;
memset(bufN, 0, sizeof(float)*opts_buffer_size*2);
pdesc->process(phandle, inbuf, outbuf, opts_buffer_size, NULL, 0);

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

sf_writef_float(file, bufN, opts_buffer_size);

if (kori.plugin_needs_idle)
{
kori.plugin_needs_idle = false;
pdesc->dispatcher(phandle, NATIVE_PLUGIN_OPCODE_IDLE, 0, 0, NULL, 0.0f);
}
}

if (isfile)
{
// keep going a bit until silence, maximum 5 seconds
const uint32_t until_silence = 5 * opts_sample_rate;
kori.time.playing = false;

for (uint32_t i = 0; i < until_silence; i += opts_buffer_size)
{
memset(bufN, 0, sizeof(float)*opts_buffer_size*2);
pdesc->process(phandle, inbuf, outbuf, opts_buffer_size, NULL, 0);

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

sf_writef_float(file, bufN, opts_buffer_size);

if (kori.plugin_needs_idle)
{
kori.plugin_needs_idle = false;
pdesc->dispatcher(phandle, NATIVE_PLUGIN_OPCODE_IDLE, 0, 0, NULL, 0.0f);
}

if (fabsf(bufN[opts_buffer_size-1]) < __FLT_EPSILON__)
break;
}
}

ret = EXIT_SUCCESS;
kuriborosu_host_render_to_file(kuri, &options);

free:
free(bufN);
free(bufL);
free(bufR);
sf_close(file);
kuriborosu_host_destroy(kuri);
return EXIT_SUCCESS;

deactivate:
pdesc->deactivate(phandle);
pdesc->cleanup(phandle);
carla_host_handle_free(hhandle);
return ret;
error:
kuriborosu_host_destroy(kuri);
return EXIT_FAILURE;
}

+ 114
- 0
src/kuribu.c View File

@@ -0,0 +1,114 @@
// TODO license header here

#include "host.h"

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

static void print_help()
{
printf("Usage: kuribu [INFILE|NUMSECONDS] OUTFILE PLUGIN1 PLUGIN2... etc\n"
"Where the first argument can be a filename for input file, or number of seconds to render (useful for self-generators).\n\n"
" --help Display this help and exit\n"
" --version Display version information and exit\n");
}

static void print_version()
{
printf("kuribu v0.0.0, using Carla v" CARLA_VERSION_STRING "\n"
"Copyright 2021 Filipe Coelho <falktx@falktx.com>\n"
"License: ???\n"
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n");
}

int main(int argc, char* argv[])
{
if (argc < 4)
{
print_help();
return EXIT_FAILURE;
}

for (int i = 1; i < argc; ++i)
{
if (strcmp(argv[i], "--help") == 0)
{
print_version();
return EXIT_SUCCESS;
}
if (strcmp(argv[i], "--version") == 0)
{
print_version();
return EXIT_SUCCESS;
}
}

const char* infile = argv[1];
const char* outwav = argv[2];

const uint32_t buffer_size = 1024;
const uint32_t sample_rate = 48000;

Kuriborosu* const kuri = kuriborosu_host_init(buffer_size, sample_rate);

if (kuri == NULL)
return EXIT_FAILURE;

uint32_t file_frames;

// Check if input file argument is actually seconds
// FIXME some isalpha() check??
const bool isfile = strchr(infile, '.') != NULL || strchr(infile, '/') != NULL;
if (isfile)
{
if (! kuriborosu_host_load_file(kuri, infile))
goto error;

file_frames = (uint32_t)(get_file_length_from_last_plugin(kuri) * sample_rate + 0.5);
}
else
{
const int seconds = atoi(infile);

if (seconds <= 0 || seconds > 60*60)
{
fprintf(stderr, "Invalid number of seconds %i\n", seconds);
goto error;
}

file_frames = (uint32_t)seconds * sample_rate;
}

if (file_frames > 60*60*sample_rate)
{
fprintf(stderr, "Output file unexpectedly big, bailing out\n");
goto error;
}

for (int i = 3; i < argc; ++i)
{
const char* const plugin_arg = argv[i];

// check if file
if (plugin_arg[0] == '.' || plugin_arg[0] == '/')
kuriborosu_host_load_file(kuri, plugin_arg);
else
kuriborosu_host_load_plugin(kuri, plugin_arg);
}

const file_render_options_t options = {
.filename = outwav,
.frames = file_frames,
.tail_mode = isfile ? tail_mode_continue_until_silence : tail_mode_none,
};
kuriborosu_host_render_to_file(kuri, &options);

kuriborosu_host_destroy(kuri);
return EXIT_SUCCESS;

error:
kuriborosu_host_destroy(kuri);
return EXIT_FAILURE;
}

Loading…
Cancel
Save