Browse Source

update template

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

1
template/calfbox/.gitignore

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

2
template/calfbox/configure.ac

@ -109,7 +109,7 @@ if test "$JACK_ENABLED" = "yes"; then
fi fi
if test "$PYTHON_ENABLED" = "yes"; then 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 fi
# Generate Automake conditionals # Generate Automake conditionals

16086
template/calfbox/configure~

File diff suppressed because it is too large

56
template/calfbox/py/cbox.py

@ -1041,6 +1041,8 @@ class DocScene(DocObj):
def move_layer(self, old_pos, new_pos): def move_layer(self, old_pos, new_pos):
self.cmd("/move_layer", None, int(old_pos + 1), int(new_pos + 1)) 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): def add_layer(self, aux, pos = None):
if pos is None: if pos is None:
return self.cmd_makeobj("/add_layer", 0, aux) return self.cmd_makeobj("/add_layer", 0, aux)
@ -1104,6 +1106,9 @@ class SamplerProgram(DocObj):
return self.get_thing("/regions", '/region', [SamplerLayer]) return self.get_thing("/regions", '/region', [SamplerLayer])
def get_global(self): def get_global(self):
return self.cmd_makeobj("/global") 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): def get_control_inits(self):
return self.get_thing("/control_inits", '/control_init', [(int, int)]) return self.get_thing("/control_inits", '/control_init', [(int, int)])
def new_group(self): def new_group(self):
@ -1130,13 +1135,61 @@ Document.classmap['sampler_program'] = SamplerProgram
class SamplerLayer(DocObj): class SamplerLayer(DocObj):
class Status: class Status:
parent_program = SamplerProgram parent_program = SamplerProgram
parent_group = DocObj parent = DocObj
level = str
def get_children(self): 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]) 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): 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) return self.get_thing("/as_string", '/value', str)
def as_string_full(self): 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) return self.get_thing("/as_string_full", '/value', str)
def set_param(self, key, value): def set_param(self, key, value):
self.cmd("/set_param", None, key, str(value)) self.cmd("/set_param", None, key, str(value))
def unset_param(self, key): def unset_param(self, key):
@ -1144,4 +1197,3 @@ class SamplerLayer(DocObj):
def new_child(self): def new_child(self):
return self.cmd_makeobj("/new_child") return self.cmd_makeobj("/new_child")
Document.classmap['sampler_layer'] = SamplerLayer 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)

90
template/calfbox/sampler_layer.c

@ -274,6 +274,7 @@ enum sampler_layer_param_type
slpt_voice_nif, slpt_voice_nif,
slpt_prevoice_nif, slpt_prevoice_nif,
slpt_flex_lfo, slpt_flex_lfo,
slpt_nonfunctional,
slpt_reserved, slpt_reserved,
}; };
@ -349,6 +350,8 @@ SAMPLER_FIXED_FIELDS(PROC_FIELD_SETHASFUNC)
{ name, LOFS(prevoice_nifs), slpt_prevoice_nif, 0, variant, nif, NULL, NULL }, { name, LOFS(prevoice_nifs), slpt_prevoice_nif, 0, variant, nif, NULL, NULL },
#define FIELD_ALIAS(alias, name) \ #define FIELD_ALIAS(alias, name) \
{ alias, -1, slpt_alias, 0, 0, name, NULL, NULL }, { 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) \ #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 }, \ { #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("reloffset_oncc#", "reloffset_cc#")
FIELD_ALIAS("delay_oncc#", "delay_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 }, { "genericmod_#_#_#_#", -1, slpt_generic_modulation, 0, 0, NULL, NULL, NULL },
}; };
#define NPARAMS (sizeof(sampler_layer_params) / sizeof(sampler_layer_params[0])) #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) static int compare_entries(const void *p1, const void *p2)
{ {
const struct sampler_layer_param_entry *e1 = p1, *e2 = 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_reserved:
case slpt_invalid: case slpt_invalid:
case slpt_alias: case slpt_alias:
case slpt_nonfunctional:
printf("Unhandled parameter type of parameter %s\n", e->name); printf("Unhandled parameter type of parameter %s\n", e->name);
assert(0); assert(0);
return FALSE; 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); 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_float:
case slpt_dBamp: case slpt_dBamp:
default: default:
@ -1026,16 +1056,27 @@ int sampler_layer_unapply_fixed_param(struct sampler_layer *l, const char *key,
return -1; 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) 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; struct sampler_layer *layer = ct->user_data;
if (!strcmp(cmd->command, "/status") && !strcmp(cmd->arg_types, "")) 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)) if (!cbox_check_fb_channel(fb, cmd->command, error))
return FALSE; return FALSE;
if (!((!layer->parent_program || cbox_execute_on(fb, NULL, "/parent_program", "o", error, layer->parent_program)) && 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)) && (!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))) CBOX_OBJECT_DEFAULT_STATUS(layer, fb, error)))
return FALSE; return FALSE;
return TRUE; return TRUE;
@ -1049,6 +1090,53 @@ static gboolean sampler_layer_process_cmd(struct cbox_command_target *ct, struct
g_free(res); g_free(res);
return result; 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")) if (!strcmp(cmd->command, "/set_param") && !strcmp(cmd->arg_types, "ss"))
{ {
const char *key = CBOX_ARG_S(cmd, 0); const char *key = CBOX_ARG_S(cmd, 0);
@ -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); l->computed.eff_waveform = cbox_wavebank_get_waveform(p->name, p->tarfile, p->sample_dir, l->sample, &error);
if (!l->computed.eff_waveform) 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); g_error_free(error);
} }
} }

4
template/calfbox/sampler_prg.c

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

5
template/calfbox/setup.py

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

4
template/calfbox/sfzloader.c

@ -275,7 +275,9 @@ gboolean sampler_module_load_program_sfz(struct sampler_module *m, struct sample
if (is_from_string) if (is_from_string)
status = load_sfz_from_string(sfz, strlen(sfz), &c, error); status = load_sfz_from_string(sfz, strlen(sfz), &c, error);
else 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 (!status)
{ {
if (ls.region) if (ls.region)

11
template/calfbox/sfzparser.c

@ -434,6 +434,11 @@ restore:
return ok; 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) gboolean load_sfz_from_string(const char *buf, int len, struct sfz_parser_client *c, GError **error)
{ {
struct sfz_parser_state s; 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; 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) gboolean load_sfz_into_state(struct sfz_parser_state *s, const char *name)
{ {
g_clear_error(s->error); g_clear_error(s->error);
FILE *f; FILE *f;
int len = -1; int len = -1;
if (s->tarfile) 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); struct cbox_taritem *item = cbox_tarfile_get_item_by_name(s->tarfile, name, TRUE);
if (!item) if (!item)
{ {

2
template/calfbox/tarfile.h

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

26
template/calfbox/wavebank.c

@ -252,8 +252,8 @@ void cbox_wavebank_init()
// XXXKF this should not be a real waveform // XXXKF this should not be a real waveform
cbox_wavebank_add_std_waveform("*silence", func_silence, NULL, 0); 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("*saw", func_saw, NULL, 11);
cbox_wavebank_add_std_waveform("*sqr", func_sqr, NULL, 11); cbox_wavebank_add_std_waveform("*square", func_sqr, NULL, 11);
cbox_wavebank_add_std_waveform("*tri", func_tri, 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) struct cbox_waveform *cbox_wavebank_get_waveform(const char *context_name, struct cbox_tarfile *tarfile, const char *sample_dir, const char *filename, GError **error)
@ -267,6 +267,10 @@ struct cbox_waveform *cbox_wavebank_get_waveform(const char *context_name, struc
// Built in waveforms don't go through path canonicalization // Built in waveforms don't go through path canonicalization
if (filename[0] == '*') 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); gpointer value = g_hash_table_lookup(bank.waveforms_by_name, filename);
if (value) if (value)
{ {
@ -320,6 +324,24 @@ struct cbox_waveform *cbox_wavebank_get_waveform(const char *context_name, struc
struct cbox_taritem *taritem = NULL; struct cbox_taritem *taritem = NULL;
if (tarfile) 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); taritem = cbox_tarfile_get_item_by_name(tarfile, pathname, TRUE);
if (taritem) if (taritem)
sndfile = cbox_tarfile_opensndfile(tarfile, taritem, &waveform->sndstream, &waveform->info); sndfile = cbox_tarfile_opensndfile(tarfile, taritem, &waveform->sndstream, &waveform->info);

3
template/engine/api.py

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

25
template/engine/duration.py

@ -127,3 +127,28 @@ def jackBBTicksToDuration(beatTypeAsTraditionalNumber, jackTicks, jackBeatTicks)
#print (beatTypeAsTraditionalNumber, beatTypeDuration, jackTicks, jackBeatTicks) #print (beatTypeAsTraditionalNumber, beatTypeDuration, jackTicks, jackBeatTicks)
factor = beatTypeDuration / jackBeatTicks factor = beatTypeDuration / jackBeatTicks
return jackTicks * factor 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): 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. 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. 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): def __init__(self, parentInput):
self.parentInput = 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.callbacks3 = {} #keys are tuples
self.callbacks2 = {} #keys are tuples self.callbacks2 = {} #keys are tuples
self.active = True self.active = True

16
template/qtgui/helper.py

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

6
template/qtgui/mainwindow.py

@ -134,10 +134,14 @@ class MainWindow(QtWidgets.QMainWindow):
self.initiGuiSharedDataToSave() 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.start() #The event loop must be started after the qt app
api.session.eventLoop.fastConnect(self.nsmClient.reactToMessage) api.session.eventLoop.fastConnect(self.nsmClient.reactToMessage)
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. api.startEngine(self.nsmClient) #Load the file, start the eventLoop. Triggers all the callbacks that makes us draw.
if api.session.guiWasSavedAsNSMVisible: if api.session.guiWasSavedAsNSMVisible:

3
template/qtgui/menu.py

@ -213,7 +213,7 @@ class Menu(object):
menuAction = getattr(self.ui, submenu).menuAction() menuAction = getattr(self.ui, submenu).menuAction()
raise NotImplementedError #TODO 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. parameterstrings must already be translated.
@ -250,6 +250,7 @@ class Menu(object):
action.setIcon(ico) action.setIcon(ico)
if checkable: if checkable:
action.setCheckable(True) action.setCheckable(True)
action.setChecked(startChecked)
self.connectMenuEntry(actionAsString, connectedFunction, shortcut) self.connectMenuEntry(actionAsString, connectedFunction, shortcut)

Loading…
Cancel
Save