Browse Source

update template

master
Nils 2 years ago
parent
commit
7ece5754c5
  1. 1
      template/calfbox/.gitignore
  2. 2
      template/calfbox/configure.ac
  3. 56
      template/calfbox/py/cbox.py
  4. 183
      template/calfbox/sampler_api_example5.py
  5. 126
      template/calfbox/sampler_layer.c
  6. 4
      template/calfbox/sampler_prg.c
  7. 5
      template/calfbox/setup.py
  8. 14
      template/calfbox/sfzloader.c
  9. 15
      template/calfbox/sfzparser.c
  10. 2
      template/calfbox/tarfile.h
  11. 80
      template/calfbox/wavebank.c
  12. 3
      template/engine/api.py
  13. 27
      template/engine/duration.py
  14. 8
      template/engine/input_midi.py
  15. 16
      template/qtgui/helper.py
  16. 8
      template/qtgui/mainwindow.py
  17. 3
      template/qtgui/menu.py

1
template/calfbox/.gitignore

@ -4,6 +4,7 @@ config.h.in
config.h.in~
config.h
configure
configure~
compile
depcomp
install-sh

2
template/calfbox/configure.ac

@ -109,7 +109,7 @@ if test "$JACK_ENABLED" = "yes"; then
fi
if test "$PYTHON_ENABLED" = "yes"; then
PKG_CHECK_MODULES(PYTHON_DEPS, python3 >= 3.0, true, AC_MSG_ERROR([python 3.0 or newer is required (or use --without-python)]))
PKG_CHECK_MODULES(PYTHON_DEPS, python3-embed >= 3.0, true, AC_MSG_ERROR([python 3.0 or newer is required (or use --without-python)]))
fi
# Generate Automake conditionals

56
template/calfbox/py/cbox.py

@ -1041,6 +1041,8 @@ class DocScene(DocObj):
def move_layer(self, old_pos, new_pos):
self.cmd("/move_layer", None, int(old_pos + 1), int(new_pos + 1))
#Layer positions are 0 for "append" and other positions are 1...n which need to be unique
def add_layer(self, aux, pos = None):
if pos is None:
return self.cmd_makeobj("/add_layer", 0, aux)
@ -1104,6 +1106,9 @@ class SamplerProgram(DocObj):
return self.get_thing("/regions", '/region', [SamplerLayer])
def get_global(self):
return self.cmd_makeobj("/global")
def get_hierarchy(self):
"""see SamplerLayer.get_hierarchy"""
return {self.get_global() : self.get_global().get_hierarchy()}
def get_control_inits(self):
return self.get_thing("/control_inits", '/control_init', [(int, int)])
def new_group(self):
@ -1130,13 +1135,61 @@ Document.classmap['sampler_program'] = SamplerProgram
class SamplerLayer(DocObj):
class Status:
parent_program = SamplerProgram
parent_group = DocObj
parent = DocObj
level = str
def get_children(self):
"""Return all children SamplerLayer.
The hierarchy is always global-master-group-region
Will be empty if this is
an sfz <region>, which has no further children.
"""
return self.get_thing("/get_children", '/child', [SamplerLayer])
def get_hierarchy(self):
"""Returns either a level of hierarchy, e.g. <global> or <group>
or None, if this is a childless layer, such as a <region>.
The hierarchy is always global-master-group-region.
Regions alre always on the fourth level. But not all levels might have regions.
Hint: Print with pprint during development."""
children = self.get_children()
if children:
result = {}
for childLayer in children:
result[childLayer] = childLayer.get_hierarchy()
else:
result = None
return result
def as_dict(self):
"""Returns a dictionary of parameters set at this level of the
layer hierarchy."""
return self.get_thing("/as_list", '/value', {str: str})
def as_dict_full(self):
"""Returns a dictionary of parameters set either at this level of the
layer hierarchy or at one of the ancestors."""
return self.get_thing("/as_list_full", '/value', {str: str})
def as_string(self):
"""A space separated string of all sampler values at this level
in the hierarchy, for example ampeg_decay.
This only includes non-default values, e.g. from the sfz file"""
return self.get_thing("/as_string", '/value', str)
def as_string_full(self):
"""A space separated string of all sampler values at this level
in the hierarchy, for example ampeg_decay.
This includes all default values.
To access the values as dict with number data types use
get_params_full().
'_oncc1' will be converted to '_cc1'
"""
return self.get_thing("/as_string_full", '/value', str)
def set_param(self, key, value):
self.cmd("/set_param", None, key, str(value))
def unset_param(self, key):
@ -1144,4 +1197,3 @@ class SamplerLayer(DocObj):
def new_child(self):
return self.cmd_makeobj("/new_child")
Document.classmap['sampler_layer'] = SamplerLayer

183
template/calfbox/sampler_api_example5.py

@ -0,0 +1,183 @@
from calfbox import cbox
from pprint import pprint
def cmd_dumper(cmd, fb, args):
print ("%s(%s)" % (cmd, ",".join(list(map(repr,args)))))
cbox.init_engine()
cbox.start_audio(cmd_dumper)
global Document
Document = cbox.Document
scene = Document.get_scene()
scene.clear()
instrument = scene.add_new_instrument_layer("test_sampler", "sampler").get_instrument()
pgm_no = instrument.engine.get_unused_program()
#A pseudo sfz file that features all kind of labels
sfzString = """
<region> region_label=Region-2a //This is put into implicitMaster0 and implicitGroup0
<global> //One global per file
global_label=Global1
//Prepare keyswitches to test labels later
sw_default=60
sw_lokey=60
sw_hikey=70
//<control> //is not part of the hierarchy. control has no label.
master_label=ImplicitMaster0 //Not parsed? -> Unlabeled
group_label=ImplicitGroup0 //Not parsed? -> Unlabeled
<region> region_label=Region-1a //This is put into implicitMaster0 and implicitGroup0
<group> group_label=Group0 //master_label=WrongLabel //This is a label in the wrong place. It will override the group_label
<region> region_label=Region0a
<master> master_label=Master1 Label
sw_last=60
sw_label=Keyswitch For Master 1
group_label=ImplicitGroup1 //Not parsed? -> Unlabeled
<group> group_label=Group1
<region> region_label=Region1a sw_down=68
<region> region_label=Region1b sw_last=67
<master> master_label=Master2
sw_last=61
sw_label=Keyswitch For Master 2
group_label=ImplicitGroup2 //Not parsed? -> Unlabeled
<group> group_label=Group2
<region> region_label=Region2a
<region> wrongOpcode=hello world foo bar region_label=Region2b //a wrong opcode. Will throw a warning on load but is nevertheless available in our python data
<group> group_label=Group3
<region> sample=*saw pitch_oncc1=40 label_cc1=Detune second oscillator
<region> key=72 label_key72=A very special key
"""
pgm = instrument.engine.load_patch_from_string(pgm_no, '.', sfzString, 'test_sampler_hierarchy')
print ("Program:", pgm, pgm.status()) # -> Program: SamplerProgram<a58c6888-19c5-4b0f-be03-c4ce83b6eeee>
print ("Control Inits:", pgm.get_control_inits()) #Empty . Is this <control> ? No.
globalHierarchy = pgm.get_global() # -> Single SamplerLayer. Literally sfz <global>. But not the global scope, e.g. no under any <tag>.
#If there is no <global> tag in the .sfz this will still create a root SamplerLayer
print ("Global:", globalHierarchy)
print("\nShow all SamplerLayer in their global/master/group/region hierarchy through indentations.\n" + "=" * 80)
def recurse(item, level = 0):
status = item.status()
print (" " * level + str(status))
for subitem in item.get_children():
recurse(subitem, level + 1)
recurse(globalHierarchy)
print("\nShow all non-engine-default sfz opcodes and values in a global/master/group/region hierarchy.\n" + "=" * 80)
def recurse2(item, level = 0):
status = item.status()
data = item.as_dict()
children = item.get_children()
if data or children:
print (" " * level + "<%s> %s" % (item.status().level, data))
for subitem in children:
recurse2(subitem, level + 1)
recurse2(globalHierarchy)
print("\nAs an example get all Keyswitch Labels and their keys.\n" + "=" * 80)
def findKeyswitches(program):
"""Returns a tuple: dict, sw_lokey, sw_highkey
dict with key=keystring e.g. c#4
and value=(opcode,label). label can be empty.
Only existing keyswitches are included, not every number from 0-127.
Two special keys "sw_lokey" and "sw_hikeys" are returned and show the total range of possible
keyswitches.
This is just a function to find the keyswitches in our example, not every advanced scenario
with sw_previous for context-sensitive-regions (which isn't really a keyswitch),
nor sw_lokey, sw_hikey and multiple parallel switches per key.
Specifically it just searches for sw_last, sw_down and sw_up and assumes that any level (master,
group, region) can only use one of the three.
sw_down and sw_up are implementation-dependent. We must assume that there are instruments that
use these opcodes without specifying sw_lokey and sw_hikey. sw_last requires the range.
For these reasons we cannot do sanity-checking here. We just report all single-key keyswitches.
Finally it assumes that there is one sw_lokey and one sw_hikey in the whole file, and it
must be in global. No actual keyswitches will be in global.
"""
def findKS(data, writeInResult):
if "sw_label" in data:
label = data["sw_label"]
else:
label = ""
if "sw_last" in data:
writeInResult[data["sw_last"]] = "sw_last", label
elif "sw_down" in data:
writeInResult[data["sw_down"]] = "sw_down", label
elif "sw_up" in data:
writeInResult[data["sw_up"]] = "sw_up", label
result = {} # int:tuple(string)
hierarchy = program.get_hierarchy()
for k,v in hierarchy.items(): #Global
globalData = k.as_dict()
swlokeyValue = globalData["sw_lokey"] if "sw_lokey" in globalData else ""
swhikeyValue = globalData["sw_hikey"] if "sw_hikey" in globalData else ""
for k1,v1 in v.items(): #Master
findKS(k1.as_dict(), result)
if v1:
for k2,v2 in v1.items(): #Group
findKS(k2.as_dict(), result)
if v2:
for k3,v3 in v2.items(): #Regions
findKS(k3.as_dict(), result)
return (result, swlokeyValue, swhikeyValue)
keyswitches = findKeyswitches(pgm)
pprint(keyswitches)
scene.send_midi_event(0x80, 61 ,64) #Trigger Keyswitch c#4 . Default for this example is 60/c4
#The following were just stages during development, now handled by recurse and recurse2() above
"""
hierarchy = pgm.get_hierarchy() #starts with global and dicts down with get_children(). First single entry layer is get_global()
print ("Complete Hierarchy")
pprint(hierarchy)
for k,v in hierarchy.items(): #Global
print (k.as_string())
for k1,v1 in v.items(): #Master
print (k1.as_string())
if v1:
for k2,v2 in v1.items(): #Group
print (k2.as_string())
if v2:
for k3,v3 in v2.items(): #Regions
print (k3.as_string())
"""
print("Ready!")
while True:
cbox.call_on_idle(cmd_dumper)

126
template/calfbox/sampler_layer.c

@ -274,6 +274,7 @@ enum sampler_layer_param_type
slpt_voice_nif,
slpt_prevoice_nif,
slpt_flex_lfo,
slpt_nonfunctional,
slpt_reserved,
};
@ -349,6 +350,8 @@ SAMPLER_FIXED_FIELDS(PROC_FIELD_SETHASFUNC)
{ name, LOFS(prevoice_nifs), slpt_prevoice_nif, 0, variant, nif, NULL, NULL },
#define FIELD_ALIAS(alias, name) \
{ alias, -1, slpt_alias, 0, 0, name, NULL, NULL },
#define FIELD_NONFUNCTIONAL(name) \
{ name, -1, slpt_nonfunctional, 0, 0, NULL, NULL, NULL },
#define PROC_SUBSTRUCT_FIELD_DESCRIPTOR(name, index, def_value, parent, parent_name, parent_index, parent_struct) \
{ #parent_name "_" #name, offsetof(struct sampler_layer_data, parent) + offsetof(struct parent_struct, name), slpt_float, def_value, parent_index * 100 + index, NULL, sampler_layer_data_##parent##_set_has_##name, sampler_layer_data_##parent##_get_has_##name }, \
@ -473,10 +476,21 @@ struct sampler_layer_param_entry sampler_layer_params[] = {
FIELD_ALIAS("reloffset_oncc#", "reloffset_cc#")
FIELD_ALIAS("delay_oncc#", "delay_cc#")
//NONFUNCTIONAL Opcodes can still be looked up and used as strings in a GUI
FIELD_NONFUNCTIONAL("region_label") //ARIA
FIELD_NONFUNCTIONAL("group_label") //ARIA
FIELD_NONFUNCTIONAL("master_label") //ARIA
FIELD_NONFUNCTIONAL("global_label") //ARIA
FIELD_NONFUNCTIONAL("sw_label") //Keyswitch. ARIA
FIELD_NONFUNCTIONAL("label_cc#") //ARIA
FIELD_NONFUNCTIONAL("label_key#") //sfizz opcode
{ "genericmod_#_#_#_#", -1, slpt_generic_modulation, 0, 0, NULL, NULL, NULL },
};
#define NPARAMS (sizeof(sampler_layer_params) / sizeof(sampler_layer_params[0]))
static void sampler_layer_apply_unknown(struct sampler_layer *l, const char *key, const char *value);
static int compare_entries(const void *p1, const void *p2)
{
const struct sampler_layer_param_entry *e1 = p1, *e2 = p2;
@ -700,6 +714,7 @@ gboolean sampler_layer_param_entry_set_from_ptr(const struct sampler_layer_param
case slpt_reserved:
case slpt_invalid:
case slpt_alias:
case slpt_nonfunctional:
printf("Unhandled parameter type of parameter %s\n", e->name);
assert(0);
return FALSE;
@ -788,6 +803,21 @@ gboolean sampler_layer_param_entry_set_from_string(const struct sampler_layer_pa
}
return sampler_layer_param_entry_set_from_ptr(e, l, set_local_value, &number, args, error);
}
case slpt_nonfunctional:
{
char *argptr = strchr(e->name, '#');
if (argptr)
{
int rootlen = argptr - e->name;
char buf[128];
strncpy(buf, e->name, rootlen);
sprintf(buf + rootlen, "%d", args[0]);
sampler_layer_apply_unknown(l, buf, value);
}
else
sampler_layer_apply_unknown(l, e->name, value);
return TRUE;
}
case slpt_float:
case slpt_dBamp:
default:
@ -1026,16 +1056,27 @@ int sampler_layer_unapply_fixed_param(struct sampler_layer *l, const char *key,
return -1;
}
static int count_ancestors(struct sampler_layer *l)
{
int ancestors = 0;
for (; l->parent; l = l->parent)
++ancestors;
assert(ancestors < 4);
return ancestors;
}
static gboolean sampler_layer_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
{
struct sampler_layer *layer = ct->user_data;
if (!strcmp(cmd->command, "/status") && !strcmp(cmd->arg_types, ""))
{
static const char *layer_types[] = { "global", "master", "group", "region" };
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
if (!((!layer->parent_program || cbox_execute_on(fb, NULL, "/parent_program", "o", error, layer->parent_program)) &&
(!layer->parent || cbox_execute_on(fb, NULL, "/parent", "o", error, layer->parent)) &&
if (!((!layer->parent_program || cbox_execute_on(fb, NULL, "/parent_program", "o", error, layer->parent_program)) &&
(!layer->parent || cbox_execute_on(fb, NULL, "/parent", "o", error, layer->parent)) &&
cbox_execute_on(fb, NULL, "/level", "s", error, layer_types[count_ancestors(layer)]) &&
CBOX_OBJECT_DEFAULT_STATUS(layer, fb, error)))
return FALSE;
return TRUE;
@ -1049,6 +1090,53 @@ static gboolean sampler_layer_process_cmd(struct cbox_command_target *ct, struct
g_free(res);
return result;
}
if ((!strcmp(cmd->command, "/as_list") || !strcmp(cmd->command, "/as_list_full")) && !strcmp(cmd->arg_types, ""))
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
// Temporary, kludgy implementation
gchar *res = sampler_layer_to_string(layer, !strcmp(cmd->command, "/as_list_full"));
const gchar *iter = res;
gboolean result = TRUE;
while(iter) {
while(isspace(*iter))
++iter;
const gchar *pkey = iter;
const gchar *eq = iter;
while(*eq && *eq != '=')
++eq;
if (*eq == '=')
{
gchar *key = g_strndup(pkey, eq - pkey);
const gchar *pvalue = eq + 1;
while(*pvalue && isspace(*pvalue))
++pvalue;
const gchar *pend = pvalue;
while(*pend && *pend != '=')
++pend;
if (*pend == '=' )
{
while(pend > pvalue && (isalnum(pend[-1]) || pend[-1] == '_'))
pend--;
iter = pend;
}
else
iter = NULL;
while(pend > pvalue && isspace(pend[-1]))
pend--;
gchar *value = g_strndup(pvalue, pend - pvalue);
result = cbox_execute_on(fb, NULL, "/value", "ss", error, key, value);
g_free(value);
g_free(key);
if (!result)
break;
}
else
break;
}
g_free(res);
return result;
}
if (!strcmp(cmd->command, "/set_param") && !strcmp(cmd->arg_types, "ss"))
{
const char *key = CBOX_ARG_S(cmd, 0);
@ -1148,7 +1236,7 @@ struct sampler_layer *sampler_layer_new(struct sampler_module *m, struct sampler
memset(l, 0, sizeof(struct sampler_layer));
CBOX_OBJECT_HEADER_INIT(l, sampler_layer, doc);
cbox_command_target_init(&l->cmd_target, sampler_layer_process_cmd, l);
l->module = m;
l->child_layers = g_hash_table_new(NULL, NULL);
if (parent)
@ -1205,7 +1293,7 @@ struct sampler_layer *sampler_layer_new(struct sampler_module *m, struct sampler
#define PROC_FIELDS_CLONE_dahdsr(name, parname, index) \
DAHDSR_FIELDS(PROC_SUBSTRUCT_CLONE, name, dst, src) \
if (!copy_hasattr) \
DAHDSR_FIELDS(PROC_SUBSTRUCT_RESET_HAS_FIELD, name, dst)
DAHDSR_FIELDS(PROC_SUBSTRUCT_RESET_HAS_FIELD, name, dst)
#define PROC_FIELDS_CLONE_lfo(name, parname, index) \
LFO_FIELDS(PROC_SUBSTRUCT_CLONE, name, dst, src) \
if (!copy_hasattr) \
@ -1291,11 +1379,11 @@ static inline int sampler_filter_num_stages(float cutoff, enum sampler_filter_ty
#define END_VALUE_amp_velcurve (l->amp_veltrack < 0 ? dB2gain(l->amp_veltrack * 84.0 / 100.0) : 1)
#define IS_QUADRATIC_amp_velcurve l->velcurve_quadratic
#define PROC_FIELDS_FINALISER(type, name, def_value)
#define PROC_FIELDS_FINALISER(type, name, def_value)
#define PROC_FIELDS_FINALISER_string(name)
#define PROC_FIELDS_FINALISER_midicurve(name) \
sampler_midi_curve_interpolate(&l->name, l->computed.eff_##name, START_VALUE_##name, END_VALUE_##name, IS_QUADRATIC_##name);
#define PROC_FIELDS_FINALISER_enum(type, name, def_value)
#define PROC_FIELDS_FINALISER_enum(type, name, def_value)
#define PROC_FIELDS_FINALISER_dBamp(type, name, def_value) \
l->name##_linearized = dB2gain(l->name);
#define PROC_FIELDS_FINALISER_dahdsr(name, parname, index) \
@ -1319,7 +1407,7 @@ void sampler_layer_data_finalize(struct sampler_layer_data *l, struct sampler_la
l->computed.eff_waveform = cbox_wavebank_get_waveform(p->name, p->tarfile, p->sample_dir, l->sample, &error);
if (!l->computed.eff_waveform)
{
g_warning("Cannot load waveform %s: %s", l->sample, error ? error->message : "unknown error");
g_warning("Cannot load waveform \"%s\" in sample_dir \"%s\" : \"%s\"", l->sample, p->sample_dir, error ? error->message : "unknown error");
g_error_free(error);
}
}
@ -1330,7 +1418,7 @@ void sampler_layer_data_finalize(struct sampler_layer_data *l, struct sampler_la
l->computed.eff_is_silent = !l->sample || !strcmp(l->sample, "*silence");
l->sample_changed = FALSE;
}
l->computed.eff_use_keyswitch = ((l->sw_down != -1) || (l->sw_up != -1) || (l->sw_last != -1) || (l->sw_previous != -1));
l->computed.eff_use_simple_trigger_logic =
(l->seq_length == 1 && l->seq_position == 1) &&
@ -1457,7 +1545,7 @@ void sampler_layer_load_overrides(struct sampler_layer *l, const char *cfg_secti
char *imp = cbox_config_get_string(cfg_section, "import");
if (imp)
sampler_layer_load_overrides(l, imp);
struct layer_foreach_struct lfs = {
.layer = l,
.cfg_section = cfg_section
@ -1478,7 +1566,7 @@ static void sampler_layer_apply_unknown(struct sampler_layer *l, const char *key
{
if (!l->unknown_keys)
l->unknown_keys = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
g_hash_table_insert(l->unknown_keys, g_strdup(key), g_strdup(value));
}
@ -1628,16 +1716,16 @@ gchar *sampler_layer_to_string(struct sampler_layer *lr, gboolean show_inherited
char floatbuf[G_ASCII_DTOSTR_BUF_SIZE];
int floatbufsize = G_ASCII_DTOSTR_BUF_SIZE;
SAMPLER_FIXED_FIELDS(PROC_FIELDS_TO_FILEPTR)
for(struct sampler_noteinitfunc *nd = l->voice_nifs; nd; nd = nd->next)
{
if (!nd->value.has_value && !nd->value.has_curve && !nd->value.has_step && !show_inherited)
continue;
#define PROC_ENVSTAGE_NAME(name, index, def_value) #name,
#define PROC_ENVSTAGE_NAME(name, index, def_value) #name,
static const char *env_stages[] = { DAHDSR_FIELDS(PROC_ENVSTAGE_NAME) "start" };
uint32_t v = nd->key.variant;
g_ascii_dtostr(floatbuf, floatbufsize, nd->value.value);
if (nd->key.notefunc_voice == sampler_nif_addrandom && v >= 0 && v <= 2)
g_string_append_printf(outstr, " %s_random=%s", addrandom_variants[v], floatbuf);
else if (nd->key.notefunc_voice == sampler_nif_vel2pitch)
@ -1825,7 +1913,7 @@ gchar *sampler_layer_to_string(struct sampler_layer *lr, gboolean show_inherited
while(g_hash_table_iter_next(&hti, (gpointer *)&key, (gpointer *)&value))
g_string_append_printf(outstr, " %s=%s", key, value);
}
gchar *res = outstr->str;
g_string_free(outstr, FALSE);
return res;
@ -1935,7 +2023,7 @@ static int sampler_layer_update_cmd_prepare(void *data)
struct sampler_layer_update_cmd *cmd = data;
cmd->old_data = cmd->layer->runtime;
cmd->new_data = calloc(1, sizeof(struct sampler_layer_data));
sampler_layer_data_clone(cmd->new_data, &cmd->layer->data, TRUE);
sampler_layer_data_finalize(cmd->new_data, cmd->layer->parent ? &cmd->layer->parent->data : NULL, cmd->layer->parent_program);
if (cmd->layer->runtime == NULL)
@ -1952,7 +2040,7 @@ static int sampler_layer_update_cmd_prepare(void *data)
static int sampler_layer_update_cmd_execute(void *data)
{
struct sampler_layer_update_cmd *cmd = data;
for (int i = 0; i < 16; i++)
{
FOREACH_VOICE(cmd->module->channels[i].voices_running, v)
@ -1983,7 +2071,7 @@ static int sampler_layer_update_cmd_execute(void *data)
static void sampler_layer_update_cmd_cleanup(void *data)
{
struct sampler_layer_update_cmd *cmd = data;
sampler_layer_data_destroy(cmd->old_data);
free(cmd);
}
@ -2008,13 +2096,13 @@ void sampler_layer_update(struct sampler_layer *l)
.execute = sampler_layer_update_cmd_execute,
.cleanup = sampler_layer_update_cmd_cleanup,
};
struct sampler_layer_update_cmd *lcmd = malloc(sizeof(struct sampler_layer_update_cmd));
lcmd->module = l->module;
lcmd->layer = l;
lcmd->new_data = NULL;
lcmd->old_data = NULL;
cbox_rt_execute_cmd_async(l->module->module.rt, &rtcmd, lcmd);
}

4
template/calfbox/sampler_prg.c

@ -74,9 +74,11 @@ retry:
(l->sw_previous == -1 || l->sw_previous == c->previous_note)))
{
gboolean play = lr->current_seq_position == 1;
lr->current_seq_position++;
lr->current_seq_position--;
if (lr->current_seq_position > l->seq_length)
lr->current_seq_position = 1;
else if (lr->current_seq_position < 1)
lr->current_seq_position = l->seq_length;
if (play)
return lr;
}

5
template/calfbox/setup.py

@ -123,8 +123,7 @@ if support_ext_module:
eargs.append('-mfpu=neon')
eargs.append('-ffast-math')
ext_modules.append([
ext_modules.append(
Extension('_cbox', csources,
extra_compile_args = eargs,
include_dirs=['.'],
@ -133,7 +132,7 @@ if support_ext_module:
undef_macros=['NDEBUG'],
depends = ['setup.py'] + headers
)
])
)
setup(name="CalfBox",
version="0.0.0.2", description="Assorted music-related code",
author="Krzysztof Foltman", author_email="wdev@foltman.com",

14
template/calfbox/sfzloader.c

@ -128,7 +128,7 @@ static gboolean load_sfz_group(struct sfz_parser_client *client)
static gboolean load_sfz_region(struct sfz_parser_client *client)
{
struct sfz_load_state *ls = client->user_data;
ls->target = ls->region = sampler_layer_new(ls->m, ls->program, ls->group);
// g_warning("-- start region");
return TRUE;
@ -154,7 +154,7 @@ static gboolean load_sfz_curve(struct sfz_parser_client *client)
static gboolean load_sfz_key_value(struct sfz_parser_client *client, const char *key, const char *value)
{
struct sfz_load_state *ls = client->user_data;
if (ls->section_type == slst_curve)
{
if (key[0] == 'v' && isdigit(key[1]))
@ -218,14 +218,14 @@ static gboolean load_sfz_key_value(struct sfz_parser_client *client, const char
g_warning("Unrecognized SFZ key in control section: %s", key);
return TRUE;
}
struct sampler_layer *l = ls->target;
if (!ls->target)
{
g_warning("Parameter '%s' entered outside of global, master, region or group", key);
return TRUE;
}
if (!sampler_layer_apply_param(l, key, value, ls->error))
return FALSE;
@ -275,7 +275,9 @@ gboolean sampler_module_load_program_sfz(struct sampler_module *m, struct sample
if (is_from_string)
status = load_sfz_from_string(sfz, strlen(sfz), &c, error);
else
status = load_sfz(sfz, prg->tarfile, &c, error);
{
status = load_sfz(sfz, prg->tarfile, &c, error); //Loads the audio files but also sets fields, like prg->sample_dir. After this we cannot modify any values anymore.
}
if (!status)
{
if (ls.region)
@ -284,7 +286,7 @@ gboolean sampler_module_load_program_sfz(struct sampler_module *m, struct sample
}
end_token(&c);
prg->all_layers = g_slist_reverse(prg->all_layers);
sampler_program_update_layers(prg);
return TRUE;

15
template/calfbox/sfzparser.c

@ -434,6 +434,11 @@ restore:
return ok;
}
/*
* This is not only called when literally constructing a sfz string
* but also when loading a null instrument e.g. to first create the jack ports and only later
* actually load the sfz and samples, which can be costly.
*/
gboolean load_sfz_from_string(const char *buf, int len, struct sfz_parser_client *c, GError **error)
{
struct sfz_parser_state s;
@ -449,13 +454,17 @@ gboolean load_sfz_from_string(const char *buf, int len, struct sfz_parser_client
return result;
}
/*
* Called once per sfz.
* Does not load samples, but only the sfz file.
*/
gboolean load_sfz_into_state(struct sfz_parser_state *s, const char *name)
{
g_clear_error(s->error);
FILE *f;
int len = -1;
if (s->tarfile)
{
{ //This only extracts the .sfz file itself and will not attempt to load any sample waveforms, eventhough cbox_tarfile_get_item_by_name will later be used to extract the sample as well.
struct cbox_taritem *item = cbox_tarfile_get_item_by_name(s->tarfile, name, TRUE);
if (!item)
{
@ -479,14 +488,14 @@ gboolean load_sfz_into_state(struct sfz_parser_state *s, const char *name)
g_set_error(s->error, G_FILE_ERROR, g_file_error_from_errno (errno), "Cannot open '%s'", name);
return FALSE;
}
if (len == -1)
{
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
}
unsigned char *buf = malloc(len + 1);
buf[len] = '\0';
if (fread(buf, 1, len, f) != (size_t)len)

2
template/calfbox/tarfile.h

@ -38,7 +38,7 @@ struct cbox_tarfile
int refs;
GHashTable *items_byname;
GHashTable *items_byname_nc;
char *file_pathname;
char *file_pathname; //full path to the .tar file with filename.ext
};
struct cbox_tarpool

80
template/calfbox/wavebank.c

@ -70,7 +70,7 @@ static void my_fft_main(complex float output[STD_WAVEFORM_FRAMES])
int invi = STD_WAVEFORM_BITS - i - 1;
int disp = 1 << i;
int mask = disp - 1;
for (int j = 0; j < STD_WAVEFORM_FRAMES / 2; j++)
{
int jj1 = (j & mask) + ((j & ~mask) << 1); // insert 0 at i'th bit to get the left arm of the butterfly
@ -93,9 +93,9 @@ static void my_fft_r2c(complex float output[STD_WAVEFORM_FRAMES], int16_t input[
// Copy + bit reversal addressing
for (int i = 0; i < STD_WAVEFORM_FRAMES; i++)
output[i] = input[map_table[i]] * (1.0 / STD_WAVEFORM_FRAMES);
my_fft_main(output);
}
static void my_ifft_c2r(int16_t output[STD_WAVEFORM_FRAMES], complex float input[STD_WAVEFORM_FRAMES])
@ -116,7 +116,7 @@ static void my_ifft_c2r(int16_t output[STD_WAVEFORM_FRAMES], complex float input
if (fabs(value) > maxv)
maxv = fabs(value);
output[i] = (int16_t)value;
}
}
}
struct wave_bank
@ -168,7 +168,7 @@ void cbox_waveform_generate_levels(struct cbox_waveform *waveform, int levels, d
complex float output[STD_WAVEFORM_FRAMES], bandlimited[STD_WAVEFORM_FRAMES];
my_fft_r2c(output, waveform->data);
int N = STD_WAVEFORM_FRAMES;
waveform->levels = calloc(levels, sizeof(struct cbox_waveform_level));
double rate = 65536.0 * 65536.0; // / waveform->info.frames;
double orig_rate = 65536.0 * 65536.0; // / waveform->info.frames;
@ -176,7 +176,7 @@ void cbox_waveform_generate_levels(struct cbox_waveform *waveform, int levels, d
{
int harmonics = N / 2 / (rate / orig_rate);
bandlimited[0] = 0;
if (harmonics > 0)
{
for (int j = 1; j <= harmonics; j++)
@ -187,7 +187,7 @@ void cbox_waveform_generate_levels(struct cbox_waveform *waveform, int levels, d
for (int j = harmonics; j <= N / 2; j++)
bandlimited[j] = bandlimited [N - j] = 0;
}
waveform->levels[i].data = calloc(N + MAX_INTERPOLATION_ORDER, sizeof(int16_t));
my_ifft_c2r(waveform->levels[i].data, bandlimited);
memcpy(waveform->levels[i].data + N, waveform->levels[i].data, MAX_INTERPOLATION_ORDER * sizeof(int16_t));
@ -204,11 +204,11 @@ void cbox_wavebank_add_std_waveform(const char *name, float (*getfunc)(float v,
for (int i = 0; i < nsize; i++)
{
float v = getfunc(i * 1.0 / nsize, user_data);
if (fabs(v) > 1)
if (fabs(v) > 1)
v = (v < 0) ? -1 : 1;
// cannot use full scale here, because bandlimiting will introduce
// some degree of overshoot
wave[i] = (int16_t)(25000 * v);
wave[i] = (int16_t)(25000 * v);
}
struct cbox_waveform *waveform = calloc(1, sizeof(struct cbox_waveform));
waveform->data = wave;
@ -225,10 +225,10 @@ void cbox_wavebank_add_std_waveform(const char *name, float (*getfunc)(float v,
waveform->loop_end = nsize;
waveform->levels = NULL;
waveform->level_count = 0;
if (levels)
cbox_waveform_generate_levels(waveform, levels, 2);
g_hash_table_insert(bank.waveforms_by_name, waveform->canonical_name, waveform);
g_hash_table_insert(bank.waveforms_by_id, &waveform->id, waveform);
bank.std_waveforms = g_slist_prepend(bank.std_waveforms, waveform);
@ -247,13 +247,13 @@ void cbox_wavebank_init()
bank.waveforms_by_id = g_hash_table_new(g_int_hash, g_int_equal);
bank.std_waveforms = NULL;
bank.streaming_prefetch_size = cbox_config_get_int("streaming", "prefetch_size", 65536);
cbox_wavebank_add_std_waveform("*sine", func_sine, NULL, 0);
// XXXKF this should not be a real waveform
cbox_wavebank_add_std_waveform("*silence", func_silence, NULL, 0);
cbox_wavebank_add_std_waveform("*saw", func_saw, NULL, 11);
cbox_wavebank_add_std_waveform("*sqr", func_sqr, NULL, 11);
cbox_wavebank_add_std_waveform("*tri", func_tri, NULL, 11);
cbox_wavebank_add_std_waveform("*square", func_sqr, NULL, 11);
cbox_wavebank_add_std_waveform("*triangle", func_tri, NULL, 11);
}
struct cbox_waveform *cbox_wavebank_get_waveform(const char *context_name, struct cbox_tarfile *tarfile, const char *sample_dir, const char *filename, GError **error)
@ -263,10 +263,14 @@ struct cbox_waveform *cbox_wavebank_get_waveform(const char *context_name, struc
g_set_error(error, CBOX_WAVEFORM_ERROR, CBOX_WAVEFORM_ERROR_FAILED, "%s: no filename specified", context_name);
return NULL;
}
// Built in waveforms don't go through path canonicalization
if (filename[0] == '*')
{
if (!strcmp(filename + 1, "sqr"))
filename = "*square";
else if (!strcmp(filename + 1, "tri"))
filename = "*triangle";
gpointer value = g_hash_table_lookup(bank.waveforms_by_name, filename);
if (value)
{
@ -275,7 +279,7 @@ struct cbox_waveform *cbox_wavebank_get_waveform(const char *context_name, struc
return waveform;
}
}
gchar *value_copy = g_strdup(filename);
for (int i = 0; value_copy[i]; i++)
{
@ -309,17 +313,35 @@ struct cbox_waveform *cbox_wavebank_get_waveform(const char *context_name, struc
{
g_free(pathname);
g_free(canonical);
struct cbox_waveform *waveform = value;
cbox_waveform_ref(waveform);
return waveform;
}
struct cbox_waveform *waveform = calloc(1, sizeof(struct cbox_waveform));
SNDFILE *sndfile = NULL;
struct cbox_taritem *taritem = NULL;
if (tarfile)
{
if (strcmp(sample_dir, ".") == 0)
{
//Potential path lookup problem:
//This is a sample without sfz default_path opcode inside a tar.
//We need to set the sample dir to the position of the .sfz file within the .tar
//because we also assume that the sample paths in regions are relative to the .sfz path.
//If the sfz is in the tar root this is a redundant action, but if the sfz is itself
//in a subdirectoy we need to adjust the path now.
// XXXNH sample_dir will not be updated in the struct itself and thus reported as "." in python etc.
//context_name is the sfz file, filename the sample file without leading ./ and sample_dir just a dot.
gchar *sfz_dir = g_path_get_dirname(context_name); //take the path of the sfz file...
pathname = g_build_filename(sfz_dir, filename, NULL); //... and prefix the sample filename with it.
g_free(sfz_dir);
}
taritem = cbox_tarfile_get_item_by_name(tarfile, pathname, TRUE);
if (taritem)
sndfile = cbox_tarfile_opensndfile(tarfile, taritem, &waveform->sndstream, &waveform->info);
@ -339,7 +361,7 @@ struct cbox_waveform *cbox_wavebank_get_waveform(const char *context_name, struc
uint32_t nshorts;
if (waveform->info.channels != 1 && waveform->info.channels != 2)
{
g_set_error(error, CBOX_WAVEFORM_ERROR, CBOX_WAVEFORM_ERROR_FAILED,
g_set_error(error, CBOX_WAVEFORM_ERROR, CBOX_WAVEFORM_ERROR_FAILED,
"%s: cannot open file '%s': unsupported channel count %d", context_name, pathname, (int)waveform->info.channels);
sf_close(sndfile);
free(canonical);
@ -364,7 +386,7 @@ struct cbox_waveform *cbox_wavebank_get_waveform(const char *context_name, struc
waveform->preloaded_frames = preloaded_frames;
waveform->tarfile = tarfile;
waveform->taritem = taritem;
if (sf_command(sndfile, SFC_GET_INSTRUMENT, &instrument, sizeof(SF_INSTRUMENT)))
{
for (int i = 0; i < instrument.loop_count; i++)
@ -389,7 +411,7 @@ struct cbox_waveform *cbox_wavebank_get_waveform(const char *context_name, struc
bank.maxbytes = bank.bytes;
g_hash_table_insert(bank.waveforms_by_name, waveform->canonical_name, waveform);
g_hash_table_insert(bank.waveforms_by_id, &waveform->id, waveform);
return waveform;
}
@ -419,10 +441,10 @@ void cbox_wavebank_foreach(void (*cb)(void *, struct cbox_waveform *), void *use
gpointer key, value;
g_hash_table_iter_init (&iter, bank.waveforms_by_id);
while (g_hash_table_iter_next (&iter, &key, &value))
while (g_hash_table_iter_next (&iter, &key, &value))
{
(*cb)(user_data, value);
}
}
}
void cbox_wavebank_close()
@ -451,7 +473,7 @@ void cbox_waveform_unref(struct cbox_waveform *waveform)
{
if (--waveform->refcount > 0)
return;
g_hash_table_remove(bank.waveforms_by_name, waveform->canonical_name);
g_hash_table_remove(bank.waveforms_by_id, &waveform->id);
bank.bytes -= waveform->bytes;
@ -463,7 +485,7 @@ void cbox_waveform_unref(struct cbox_waveform *waveform)
free(waveform->levels);
free(waveform->data);
free(waveform);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -478,7 +500,7 @@ struct waves_foreach_data
void wave_list_cb(void *user_data, struct cbox_waveform *waveform)
{
struct waves_foreach_data *wfd = user_data;
wfd->success = wfd->success && cbox_execute_on(wfd->fb, NULL, "/waveform", "i", wfd->error, (int)waveform->id);
}
@ -488,7 +510,7 @@ static gboolean waves_process_cmd(struct cbox_command_target *ct, struct cbox_co
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
// XXXKF this only supports 4GB - not a big deal for now yet?
return cbox_execute_on(fb, NULL, "/bytes", "i", error, (int)cbox_wavebank_get_bytes()) &&
cbox_execute_on(fb, NULL, "/max_bytes", "i", error, (int)cbox_wavebank_get_maxbytes()) &&
@ -499,7 +521,7 @@ static gboolean waves_process_cmd(struct cbox_command_target *ct, struct cbox_co
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
struct waves_foreach_data wfd = { fb, error, TRUE };
cbox_wavebank_foreach(wave_list_cb, &wfd);
return wfd.success;
@ -508,7 +530,7 @@ static gboolean waves_process_cmd(struct cbox_command_target *ct, struct cbox_co
{
if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE;
int id = CBOX_ARG_I(cmd, 0);
struct cbox_waveform *waveform = cbox_wavebank_peek_waveform_by_id(id);
if (waveform == NULL)

3
template/engine/api.py

@ -308,6 +308,9 @@ def startEngine(nsmClient):
logger.info("Template api engine started")
def isStandaloneMode():
return session.standaloneMode
def _deprecated_updatePlayback():
"""The only place in the program to update the cbox playback besides startEngine.
We only need to update it after a user action, which always goes through the api.

27
template/engine/duration.py

@ -123,7 +123,32 @@ def jackBBTicksToDuration(beatTypeAsTraditionalNumber, jackTicks, jackBeatTicks)
if None in (beatTypeAsTraditionalNumber, jackTicks, jackBeatTicks):
return None
else:
beatTypeDuration = traditionalNumberToBaseDuration[beatTypeAsTraditionalNumber]
beatTypeDuration = traditionalNumberToBaseDuration[beatTypeAsTraditionalNumber]
#print (beatTypeAsTraditionalNumber, beatTypeDuration, jackTicks, jackBeatTicks)
factor = beatTypeDuration / jackBeatTicks
return jackTicks * factor
def lyDurationLogToTicks(durationLog):
return 2**(8 - durationLog) * 6
ticksToLyDurationLogDict = {
DM: -3, # Maxima
DL: -2, # Longa
DB: -1, # Breve
D1: 0, # Whole
D2: 1, #Half
D4: 2, #Quarter
D8: 3, #Eighth
D16: 4, #Sixteenth
D32: 5, #1/32
D64: 6, #1/64
D128: 7, #1/128
D256: 8, #1/256
}
def ticksToLilypond(ticks):
if ticks in ticksToLyDurationLogDict:
return ticksToLyDurationLogDict[ticks]
else:
raise ValueError(f"{ticks} not in duration.py ticksToLyDurationLogDict")

8
template/engine/input_midi.py

@ -104,6 +104,10 @@ class MidiInput(object):
class MidiProcessor(object):
"""
The parameter parentInput MUST be a an object that has the attribute "cboxMidiPortUid" of
type cbox.JackIO.create_midi_input(portName)
There are two principal modes: Step Entry and Live Recording.
Add your function to callbacks3 for notes and CC or callbacks2 for program change and Channel Pressure.
@ -129,6 +133,10 @@ class MidiProcessor(object):
def __init__(self, parentInput):
self.parentInput = parentInput
if not hasattr(parentInput, "cboxMidiPortUid"):
raise ValueError("argument 'parentInput' must be an object with the attribute cboxMidiPortUid, returned from cbox.JackIO.create_midi_input(portName)")
self.callbacks3 = {} #keys are tuples
self.callbacks2 = {} #keys are tuples
self.active = True

16
template/qtgui/helper.py

@ -175,6 +175,7 @@ def setPaletteAndFont(qtApp):
qtApp.setFont(font)
return fPalBlue
class ToggleSwitch(QtWidgets.QAbstractButton):
"""From https://stackoverflow.com/questions/14780517/toggle-switch-in-qt/38102598
@ -214,10 +215,11 @@ class ToggleSwitch(QtWidgets.QAbstractButton):
self._offset = self._base_offset
palette = self.palette()
if self._thumb_radius > self._track_radius:
self._track_color = {
True: palette.highlight(),
False: palette.dark(),
False: palette.mid(),
}
self._thumb_color = {
True: palette.highlight(),
@ -225,7 +227,7 @@ class ToggleSwitch(QtWidgets.QAbstractButton):
}
self._text_color = {
True: palette.highlightedText().color(),
False: palette.dark().color(),
False: palette.mid().color(),
}
self._thumb_text = {
True: '',
@ -239,17 +241,17 @@ class ToggleSwitch(QtWidgets.QAbstractButton):
}
self._track_color = {
True: palette.highlight(),
False: palette.dark(),
False: palette.mid(),
}
self._text_color = {
True: palette.highlight().color(),
False: palette.dark().color(),
False: palette.mid().color(),
}
self._thumb_text = {
True: '',
False: '',
}
self._track_opacity = 1
self._track_opacity = 0.7
@QtCore.pyqtProperty(int)
def offset(self):
@ -337,3 +339,7 @@ class ToggleSwitch(QtWidgets.QAbstractButton):
def enterEvent(self, event): # pylint: disable=invalid-name
self.setCursor(QtCore.Qt.PointingHandCursor)
super().enterEvent(event)
class FancySwitch(ToggleSwitch):
pass

8
template/qtgui/mainwindow.py

@ -134,11 +134,15 @@ class MainWindow(QtWidgets.QMainWindow):
self.initiGuiSharedDataToSave()
def start(self):
def start(self, additionalData:dict=None):
api.session.eventLoop.start() #The event loop must be started after the qt app
api.session.eventLoop.fastConnect(self.nsmClient.reactToMessage)
api.startEngine(self.nsmClient) #Load the file, start the eventLoop. Triggers all the callbacks that makes us draw.
if additionalData: #e.g. tembro global sample directory
api.startEngine(self.nsmClient, additionalData) #Load the file, start the eventLoop. Triggers all the callbacks that makes us draw.
else:
api.startEngine(self.nsmClient) #Load the file, start the eventLoop. Triggers all the callbacks that makes us draw.
if api.session.guiWasSavedAsNSMVisible:
self.showGUI()

3
template/qtgui/menu.py

@ -213,7 +213,7 @@ class Menu(object):
menuAction = getattr(self.ui, submenu).menuAction()
raise NotImplementedError #TODO
def addMenuEntry(self, submenu, actionAsString:str, text:str, connectedFunction=None, shortcut:str="", tooltip:str="", iconResource:str="", checkable=False):
def addMenuEntry(self, submenu, actionAsString:str, text:str, connectedFunction=None, shortcut:str="", tooltip:str="", iconResource:str="", checkable=False, startChecked=False):
"""
parameterstrings must already be translated.
@ -250,6 +250,7 @@ class Menu(object):
action.setIcon(ico)
if checkable:
action.setCheckable(True)
action.setChecked(startChecked)
self.connectMenuEntry(actionAsString, connectedFunction, shortcut)

Loading…
Cancel
Save