libcw/cwFlow.cpp
kevin b57693f4e4 cwFlow,cwFlowNet,cwFlowTypes,cwFlowProc : Many changes.
Added ability to set types at proc instantiation time.
Added ability to have multi-type variables which get resolved at proc. instantiation time.
Added ability to proc 'log' attribute to print selected variables as they change.
Values are now coerced to the variable type in var_set().
Added new proc's : list,add,preset.
2024-04-30 19:58:10 -04:00

667 lines
20 KiB
C++

#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwMem.h"
#include "cwText.h"
#include "cwNumericConvert.h"
#include "cwObject.h"
#include "cwAudioFile.h"
#include "cwVectOps.h"
#include "cwMtx.h"
#include "cwDspTypes.h" // coeff_t, sample_t, srate_t ...
#include "cwTime.h"
#include "cwMidiDecls.h"
#include "cwFlowDecl.h"
#include "cwFlow.h"
#include "cwFlowTypes.h"
#include "cwFlowNet.h"
#include "cwFlowProc.h"
namespace cw
{
namespace flow
{
typedef struct library_str
{
const char* label;
class_members_t* members;
} library_t;
library_t g_library[] = {
{ "poly", &poly::members },
{ "midi_in", &midi_in::members },
{ "midi_out", &midi_out::members },
{ "audio_in", &audio_in::members },
{ "audio_out", &audio_out::members },
{ "audio_file_in", &audio_file_in::members },
{ "audio_file_out", &audio_file_out::members },
{ "audio_gain", &audio_gain::members },
{ "audio_split", &audio_split::members },
{ "audio_duplicate", &audio_duplicate::members },
{ "audio_merge", &audio_merge::members },
{ "audio_mix", &audio_mix::members },
{ "sine_tone", &sine_tone::members },
{ "pv_analysis", &pv_analysis::members },
{ "pv_synthesis", &pv_synthesis::members },
{ "spec_dist", &spec_dist::members },
{ "compressor", &compressor::members },
{ "limiter", &limiter::members },
{ "audio_delay", &audio_delay::members },
{ "dc_filter", &dc_filter::members },
{ "balance", &balance::members },
{ "audio_meter", &audio_meter::members },
{ "audio_marker", &audio_marker::members },
{ "xfade_ctl", &xfade_ctl::members },
{ "poly_mixer", &poly_mixer::members },
{ "sample_hold", &sample_hold::members },
{ "number", &number::members },
{ "timer", &timer::members },
{ "counter", &counter::members },
{ "list", &list::members },
{ "add", &add::members },
{ "preset", &preset::members },
{ nullptr, nullptr }
};
class_members_t* _find_library_record( const char* label )
{
for(library_t* l = g_library; l->label != nullptr; ++l)
if( textCompare(l->label,label) == 0)
return l->members;
return nullptr;
}
flow_t* _handleToPtr(handle_t h)
{ return handleToPtr<handle_t,flow_t>(h); }
rc_t _is_var_flag_set( const object_t* var_flags_obj, const char* flag_label, const char* classLabel, const char* varLabel, bool&is_set_flag_ref )
{
rc_t rc = kOkRC;
is_set_flag_ref = false;
if( var_flags_obj != nullptr )
{
for(unsigned k=0; k<var_flags_obj->child_count(); ++k)
{
const object_t* tag_obj = var_flags_obj->child_ele(k);
const char* tag = nullptr;
if( tag_obj != nullptr && tag_obj->is_string() && (rc=tag_obj->value(tag))==kOkRC && tag != nullptr )
{
if( strcmp(tag,flag_label) == 0 )
is_set_flag_ref = true;
}
else
{
rc = cwLogError(kSyntaxErrorRC,"An invalid or non-string value was found in a flow class '%s' variable:'%s' 'flags' field.",classLabel,varLabel);
}
}
}
return rc;
}
rc_t _parse_class_cfg(flow_t* p, const object_t* classCfg)
{
rc_t rc = kOkRC;
if( !classCfg->is_dict() )
return cwLogError(kSyntaxErrorRC,"The class description dictionary does not have dictionary syntax.");
p->classDescN = classCfg->child_count();
p->classDescA = mem::allocZ<class_desc_t>( p->classDescN );
// for each class description
for(unsigned i=0; i<p->classDescN; ++i)
{
const object_t* class_obj = classCfg->child_ele(i);
const object_t* varD = nullptr;
const object_t* presetD = nullptr;
class_desc_t* cd = p->classDescA + i;
cd->cfg = class_obj->pair_value();
cd->label = class_obj->pair_label();
// get the variable description
if((rc = cd->cfg->getv_opt("vars", varD,
"presets", presetD,
"poly_limit_cnt", cd->polyLimitN)) != kOkRC )
{
rc = cwLogError(rc,"Parsing failed while parsing class desc:'%s'", cwStringNullGuard(cd->label) );
goto errLabel;
}
// parse the preset dictionary
if( presetD != nullptr )
{
if( !presetD->is_dict() )
{
rc = cwLogError(rc,"The preset dictionary is not a dictionary on class desc:'%s'", cwStringNullGuard(cd->label) );
goto errLabel;
}
// for each preset in the class desc.
for(unsigned j=0; j<presetD->child_count(); ++j)
{
const object_t* pair = presetD->child_ele(j);
if( !pair->pair_value()->is_dict() )
{
rc = cwLogError(kSyntaxErrorRC,"The preset '%s' in class desc '%s' is not a dictionary.", cwStringNullGuard(pair->pair_label()), cwStringNullGuard(cd->label));
goto errLabel;
}
preset_t* preset = mem::allocZ< preset_t >();
preset->label = pair->pair_label();
preset->cfg = pair->pair_value();
preset->link = cd->presetL;
cd->presetL = preset;
}
}
// parse the variable dictionary
if( varD != nullptr )
{
if( !varD->is_dict() )
{
rc = cwLogError(rc,"The value dictionary is not a dictionary on class desc:'%s'", cwStringNullGuard(cd->label) );
goto errLabel;
}
// get the class member functions
if((cd->members = _find_library_record(cd->label)) == nullptr )
{
rc = cwLogError(kSyntaxErrorRC,"The '%s' class member function record could not be found..", cd->label );
goto errLabel;
}
// for each class value description
for(unsigned j=0; j<varD->child_count(); ++j)
{
const object_t* var_obj = varD->child_ele(j);
const object_t* var_flags_obj = nullptr;
const char* type_str = nullptr;
unsigned type_flag = 0;
bool srcVarFl = false;
bool srcOptFl = false;
var_desc_t* vd = mem::allocZ<var_desc_t>();
vd->label = var_obj->pair_label();
vd->cfg = var_obj->pair_value();
// get the variable description
if((rc = vd->cfg->getv("type", type_str,
"doc", vd->docText)) != kOkRC )
{
rc = cwLogError(rc,"Parsing failed on class:%s variable: '%s'.", cd->label, vd->label );
goto errLabel;
}
// convert the type string to a numeric type flag
if( (type_flag = value_type_label_to_flag( type_str )) == kInvalidTId )
{
rc = cwLogError(kSyntaxErrorRC,"Invalid type flag: '%s' class:'%s' value:'%s'.", type_str, cd->label, vd->label );
goto errLabel;
}
// get the variable description
if((rc = vd->cfg->getv_opt("flags", var_flags_obj,
"value",vd->val_cfg)) != kOkRC )
{
rc = cwLogError(rc,"Parsing optional fields failed on class:%s variable: '%s'.", cd->label, vd->label );
goto errLabel;
}
// check for 'src' flag
if((rc = _is_var_flag_set( var_flags_obj, "src", cd->label, vd->label, srcVarFl )) != kOkRC )
goto errLabel;
// check for 'src_opt' flag
if((rc = _is_var_flag_set( var_flags_obj, "src_opt", cd->label, vd->label, srcOptFl )) != kOkRC )
goto errLabel;
vd->type |= type_flag;
if( srcVarFl )
vd->flags |= kSrcVarFl;
if( srcOptFl )
vd->flags |= kSrcOptVarFl;
vd->link = cd->varDescL;
cd->varDescL = vd;
}
}
}
errLabel:
return rc;
}
/*
rc_t _setup_input( flow_t* p, instance_t* in_inst, const char* in_var_label, const char* src_proc_label, const char* src_var_label )
{
rc_t rc = kOkRC;
unsigned src_charN = textLength(src_label_arg);
variable_t* src_var = nullptr;
instance_t* src_inst = nullptr;
variable_t* in_var = nullptr;
char sbuf[ src_charN+1 ];
// copy the id into the buf
strncpy(sbuf,src_label_arg,src_charN+1);
// advance suffix to the '.'
char* suffix = sbuf;
while( *suffix && *suffix != '.')
++suffix;
// if a '.' suffix was found
if( *suffix )
{
*suffix = 0;
++suffix;
}
// locate source instance
if((rc = instance_find(p, sbuf, kBaseSfxId, src_inst )) != kOkRC )
{
rc = cwLogError(kSyntaxErrorRC,"The source instance '%s' was not found.", cwStringNullGuard(sbuf) );
goto errLabel;
}
// locate source value
if((rc = var_find( src_inst, suffix, kBaseSfxId, kAnyChIdx, src_var)) != kOkRC )
{
rc = cwLogError(rc,"The source var '%s' was not found on the source instance '%s'.", cwStringNullGuard(suffix), cwStringNullGuard(sbuf));
goto errLabel;
}
// locate input value
if((rc = var_find( in_inst, in_var_label, kBaseSfxId, kAnyChIdx, in_var )) != kOkRC )
{
rc = cwLogError(rc,"The input value '%s' was not found on the instance '%s'.", cwStringNullGuard(in_var_label), cwStringNullGuard(in_inst->label));
goto errLabel;
}
// verify that the src_value type is included in the in_value type flags
if( cwIsNotFlag(in_var->varDesc->type, src_var->varDesc->type) )
{
rc = cwLogError(kSyntaxErrorRC,"The type flags don't match on input:%s %s source:%s %s .", in_inst->label, in_var_label, src_inst->label, suffix);
goto errLabel;
}
if( src_var->value == nullptr )
{
rc = cwLogError(kSyntaxErrorRC,"The source value is null on the connection input:'%s' %s source:'%s' '%s' .", in_inst->label, in_var_label, src_inst->label, suffix);
goto errLabel;
}
_connect_vars( src_var, in_var );
//cwLogInfo("'%s:%s' connected to source '%s:%s' %p.", in_inst->label, in_var_label, src_inst->label, suffix, in_var->value );
errLabel:
return rc;
}
*/
rc_t _destroy( flow_t* p)
{
rc_t rc = kOkRC;
if( p == nullptr )
return rc;
network_destroy(p->net);
// release the class records
for(unsigned i=0; i<p->classDescN; ++i)
{
class_desc_t* cd = p->classDescA + i;
// release the var desc list
var_desc_t* vd0 = cd->varDescL;
var_desc_t* vd1 = nullptr;
while( vd0 != nullptr )
{
vd1 = vd0->link;
mem::release(vd0);
vd0 = vd1;
}
// release the preset list
preset_t* pr0 = cd->presetL;
preset_t* pr1 = nullptr;
while( pr0 != nullptr )
{
pr1 = pr0->link;
mem::release(pr0);
pr0 = pr1;
}
}
mem::release(p->classDescA);
mem::release(p);
return rc;
}
}
}
void cw::flow::print_abuf( const abuf_t* abuf )
{
printf("Abuf: sr:%7.1f chs:%3i frameN:%4i %p",abuf->srate,abuf->chN,abuf->frameN,abuf->buf);
}
void cw::flow::print_external_device( const external_device_t* dev )
{
printf("Dev: %10s type:%3i fl:0x%x : ", cwStringNullGuard(dev->devLabel),dev->typeId,dev->flags);
if( dev->typeId == kAudioDevTypeId )
print_abuf(dev->u.a.abuf);
printf("\n");
}
cw::rc_t cw::flow::create( handle_t& hRef,
const object_t& classCfg,
const object_t& flowCfg,
external_device_t* deviceA,
unsigned deviceN )
{
rc_t rc = kOkRC;
const object_t* networkCfg = nullptr;
bool printClassDictFl = false;
bool printNetworkFl = false;
if(( rc = destroy(hRef)) != kOkRC )
return rc;
flow_t* p = mem::allocZ<flow_t>();
p->flowCfg = &flowCfg; // TODO: duplicate cfg?
p->deviceA = deviceA;
p->deviceN = deviceN;
// parse the class description array
if((rc = _parse_class_cfg(p,&classCfg)) != kOkRC )
{
rc = cwLogError(kSyntaxErrorRC,"Error parsing the class description list.");
goto errLabel;
}
// parse the main audio file processor cfg record
if((rc = flowCfg.getv("framesPerCycle", p->framesPerCycle,
"network", networkCfg)) != kOkRC )
{
rc = cwLogError(kSyntaxErrorRC,"Error parsing the required flow configuration parameters.");
goto errLabel;
}
// parse the optional args
if((rc = flowCfg.getv_opt("maxCycleCount", p->maxCycleCount,
"multiPriPresetProbFl", p->multiPriPresetProbFl,
"multiSecPresetProbFl", p->multiSecPresetProbFl,
"multiPresetInterpFl", p->multiPresetInterpFl,
"printClassDictFl", printClassDictFl,
"printNetworkFl", printNetworkFl)) != kOkRC )
{
rc = cwLogError(kSyntaxErrorRC,"Error parsing the optional flow configuration parameters.");
goto errLabel;
}
for(unsigned i=0; i<deviceN; ++i)
if( deviceA[i].typeId == kAudioDevTypeId )
{
if( deviceA[i].u.a.abuf == NULL )
{
rc = cwLogError(kInvalidArgRC,"The audio '%s' device does not have a valid audio buffer.",cwStringNullGuard(deviceA[i].devLabel));
goto errLabel;
}
else
if( deviceA[i].u.a.abuf->frameN != p->framesPerCycle )
cwLogWarning("The audio frame count (%i) for audio device '%s' does not match the Flow framesPerCycle (%i).",deviceA[i].u.a.abuf->frameN,p->framesPerCycle);
}
// print the class dict
if( printClassDictFl )
class_dict_print( p );
// instantiate the network
if((rc = network_create(p,networkCfg,p->net)) != kOkRC )
{
rc = cwLogError(rc,"Network creation failed.");
goto errLabel;
}
if( printNetworkFl )
network_print(p->net);
hRef.set(p);
errLabel:
if( rc != kOkRC )
_destroy(p);
return rc;
}
cw::rc_t cw::flow::destroy( handle_t& hRef )
{
rc_t rc = kOkRC;
flow_t* p = nullptr;;
if( !hRef.isValid() )
return rc;
p = _handleToPtr(hRef);
_destroy(p);
hRef.clear();
return rc;
}
unsigned cw::flow::preset_cfg_flags( handle_t h )
{
flow_t* p = _handleToPtr(h);
unsigned flags = 0;
if( p->multiPriPresetProbFl )
flags |= kPriPresetProbFl;
if( p->multiSecPresetProbFl )
flags |= kSecPresetProbFl;
if( p->multiPresetInterpFl )
flags |= kInterpPresetFl;
return flags;
}
cw::rc_t cw::flow::exec_cycle( handle_t h )
{
return exec_cycle(_handleToPtr(h)->net);
}
cw::rc_t cw::flow::exec( handle_t h )
{
rc_t rc = kOkRC;
flow_t* p = _handleToPtr(h);
while( true )
{
rc = exec_cycle(p->net);
if( rc == kEofRC )
{
rc = kOkRC;
break;
}
p->cycleIndex += 1;
if( p->maxCycleCount > 0 && p->cycleIndex >= p->maxCycleCount )
{
cwLogInfo("'maxCycleCnt' reached: %i. Shutting down flow.",p->maxCycleCount);
break;
}
}
return rc;
}
cw::rc_t cw::flow::apply_preset( handle_t h, const char* presetLabel )
{
flow_t* p = _handleToPtr(h);
return network_apply_preset(p->net,presetLabel);
}
cw::rc_t cw::flow::apply_dual_preset( handle_t h, const char* presetLabel_0, const char* presetLabel_1, double coeff )
{
flow_t* p = _handleToPtr(h);
return network_apply_dual_preset(p->net,presetLabel_0, presetLabel_1, coeff );
}
cw::rc_t cw::flow::apply_preset( handle_t h, const multi_preset_selector_t& mps )
{
flow_t* p = _handleToPtr(h);
return network_apply_preset(p->net,mps);
}
cw::rc_t cw::flow::set_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, bool value )
{ return set_variable_value( _handleToPtr(h)->net, inst_label, var_label, chIdx, value ); }
cw::rc_t cw::flow::set_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, int value )
{ return set_variable_value( _handleToPtr(h)->net, inst_label, var_label, chIdx, value ); }
cw::rc_t cw::flow::set_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, unsigned value )
{ return set_variable_value( _handleToPtr(h)->net, inst_label, var_label, chIdx, value ); }
cw::rc_t cw::flow::set_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, float value )
{ return set_variable_value( _handleToPtr(h)->net, inst_label, var_label, chIdx, value ); }
cw::rc_t cw::flow::set_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, double value )
{ return set_variable_value( _handleToPtr(h)->net, inst_label, var_label, chIdx, value ); }
cw::rc_t cw::flow::get_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, bool& valueRef )
{ return get_variable_value( _handleToPtr(h)->net, inst_label, var_label, chIdx, valueRef ); }
cw::rc_t cw::flow::get_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, int& valueRef )
{ return get_variable_value( _handleToPtr(h)->net, inst_label, var_label, chIdx, valueRef ); }
cw::rc_t cw::flow::get_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, unsigned& valueRef )
{ return get_variable_value( _handleToPtr(h)->net, inst_label, var_label, chIdx, valueRef ); }
cw::rc_t cw::flow::get_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, float& valueRef )
{ return get_variable_value( _handleToPtr(h)->net, inst_label, var_label, chIdx, valueRef ); }
cw::rc_t cw::flow::get_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, double& valueRef )
{ return get_variable_value( _handleToPtr(h)->net, inst_label, var_label, chIdx, valueRef ); }
void cw::flow::print_class_list( handle_t h )
{
flow_t* p = _handleToPtr(h);
class_dict_print(p);
}
void cw::flow::print_network( handle_t h )
{
flow_t* p = _handleToPtr(h);
for(unsigned i=0; i<p->deviceN; ++i)
print_external_device( p->deviceA + i );
network_print(p->net);
}
cw::rc_t cw::flow::test( const object_t* cfg, int argc, const char* argv[] )
{
rc_t rc = kOkRC;
handle_t flowH;
object_t* class_cfg = nullptr;
const object_t* test_cases_cfg = nullptr;
const object_t* test_cfg = nullptr;
const char* flow_proc_fname;
if( argc < 2 || textLength(argv[1]) == 0 )
{
rc = cwLogError(kInvalidArgRC,"No 'test-case' label was given on the command line.");
goto errLabel;
}
if((rc = cfg->getv("flow_proc_fname",flow_proc_fname,
"test_cases",test_cases_cfg)) != kOkRC )
{
rc = cwLogError(rc,"The name of the flow_proc_dict file could not be parsed.");
goto errLabel;
}
// find the user requested test case
if((test_cfg = test_cases_cfg->find_child(argv[1])) == nullptr )
{
rc = cwLogError(kInvalidArgRC,"The test case named '%s' was not found.",argv[1]);
goto errLabel;
}
// parse the flow-proc-cfg
if((rc = objectFromFile(flow_proc_fname,class_cfg)) != kOkRC )
{
rc = cwLogError(rc,"The flow proc dict could not be read from '%s'.",cwStringNullGuard(flow_proc_fname));
goto errLabel;
}
// create the flow object
if((rc = create( flowH, *class_cfg, *test_cfg)) != kOkRC )
{
rc = cwLogError(rc,"Flow object create failed.");
goto errLabel;
}
//print_network(flowH);
// run the network
if((rc = exec( flowH )) != kOkRC )
rc = cwLogError(rc,"Execution failed.");
// destroy the flow object
if((rc = destroy(flowH)) != kOkRC )
{
rc = cwLogError(rc,"Close the flow object.");
goto errLabel;
}
errLabel:
if( class_cfg != nullptr )
class_cfg->free();
return rc;
}