Signed-off-by: falkTX <falktx@falktx.com>main
@@ -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 | |||
@@ -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; | |||
} |
@@ -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); |
@@ -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; | |||
} |
@@ -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; | |||
} |