@@ -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
Expand All
@@ -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"
Expand All
@@ -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" );
Expand All
@@ -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 ;
Expand All
@@ -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
{
Expand All
@@ -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 ;
Expand All
@@ -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 )
Expand All
@@ -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 ;
}