You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
462 lines
16 KiB
462 lines
16 KiB
3 years ago
|
/*
|
||
|
Calf Box, an open source musical instrument.
|
||
|
Copyright (C) 2010-2011 Krzysztof Foltman
|
||
|
|
||
|
This program is free software: you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation, either version 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "blob.h"
|
||
|
#include "config.h"
|
||
|
#include "config-api.h"
|
||
|
#include "pattern.h"
|
||
|
#include "pattern-maker.h"
|
||
|
#include "song.h"
|
||
|
|
||
|
#include <glib.h>
|
||
|
|
||
|
extern void cbox_song_remove_pattern(struct cbox_song *song, struct cbox_midi_pattern *pattern);
|
||
|
|
||
|
CBOX_CLASS_DEFINITION_ROOT(cbox_midi_pattern)
|
||
|
|
||
|
struct cbox_midi_pattern *cbox_midi_pattern_new_metronome(struct cbox_song *song, int ts, uint64_t ppqn_factor)
|
||
|
{
|
||
|
struct cbox_midi_pattern_maker *m = cbox_midi_pattern_maker_new(ppqn_factor);
|
||
|
|
||
|
int length = (int)ppqn_factor;
|
||
|
int channel = cbox_config_get_int("metronome", "channel", 10);
|
||
|
int accnote = cbox_config_get_note("metronome", "note_accent", 37);
|
||
|
int note = cbox_config_get_note("metronome", "note", 37);
|
||
|
|
||
|
for (int i = 0; i < ts; i++)
|
||
|
{
|
||
|
int accent = !i && ts != 1;
|
||
|
cbox_midi_pattern_maker_add(m, length * i, 0x90 + channel - 1, accent ? accnote : note, accent ? 127 : 100);
|
||
|
cbox_midi_pattern_maker_add(m, length * i + 1, 0x80 + channel - 1, accent ? accnote : note, 0);
|
||
|
}
|
||
|
|
||
|
struct cbox_midi_pattern *p = cbox_midi_pattern_maker_create_pattern(m, song, g_strdup_printf("click-%d", ts));
|
||
|
p->loop_end = length * ts;
|
||
|
|
||
|
cbox_midi_pattern_maker_destroy(m);
|
||
|
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
void cbox_midi_pattern_destroyfunc(struct cbox_objhdr *objhdr)
|
||
|
{
|
||
|
struct cbox_midi_pattern *pattern = CBOX_H2O(objhdr);
|
||
|
if (pattern->owner)
|
||
|
cbox_song_remove_pattern(pattern->owner, pattern);
|
||
|
g_free(pattern->name);
|
||
|
if (pattern->events != NULL)
|
||
|
free(pattern->events);
|
||
|
free(pattern);
|
||
|
}
|
||
|
|
||
|
#if USE_LIBSMF
|
||
|
static int cbox_midi_pattern_load_smf_into(struct cbox_midi_pattern_maker *m, const char *smf)
|
||
|
{
|
||
|
int length = 0;
|
||
|
if (!cbox_midi_pattern_maker_load_smf(m, smf, &length, NULL))
|
||
|
{
|
||
|
g_error("Cannot load SMF file %s", smf);
|
||
|
return -1;
|
||
|
}
|
||
|
return length;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static int cbox_midi_pattern_load_melodic_into(struct cbox_midi_pattern_maker *m, const char *name, int start_pos, int transpose, int transpose_to_note, uint64_t ppqn_factor)
|
||
|
{
|
||
|
gchar *cfg_section = g_strdup_printf("pattern:%s", name);
|
||
|
|
||
|
if (!cbox_config_has_section(cfg_section))
|
||
|
{
|
||
|
g_error("Melodic pattern '%s' not found", name);
|
||
|
g_free(cfg_section);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
gchar *smf = cbox_config_get_string(cfg_section, "smf");
|
||
|
#if USE_LIBSMF
|
||
|
if (smf)
|
||
|
return cbox_midi_pattern_load_smf_into(m, smf);
|
||
|
#else
|
||
|
if (smf)
|
||
|
g_warning("libsmf disabled at build time, MIDI import functionality not available.");
|
||
|
#endif
|
||
|
|
||
|
int length = ppqn_factor * cbox_config_get_int(cfg_section, "beats", 4);
|
||
|
int gchannel = cbox_config_get_int(cfg_section, "channel", 1);
|
||
|
int gswing = cbox_config_get_int(cfg_section, "swing", 0);
|
||
|
int gres = cbox_config_get_int(cfg_section, "resolution", 4);
|
||
|
int orignote = cbox_config_get_note(cfg_section, "base_note", 24);
|
||
|
if (transpose_to_note != -1)
|
||
|
transpose += transpose_to_note - orignote;
|
||
|
|
||
|
for (int t = 1; ; t++)
|
||
|
{
|
||
|
gchar *tname = g_strdup_printf("track%d", t);
|
||
|
char *trkname = cbox_config_get_string(cfg_section, tname);
|
||
|
g_free(tname);
|
||
|
if (trkname)
|
||
|
{
|
||
|
tname = g_strdup_printf("%s_vel", trkname);
|
||
|
int vel = cbox_config_get_note(cfg_section, tname, 100);
|
||
|
g_free(tname);
|
||
|
tname = g_strdup_printf("%s_res", trkname);
|
||
|
int res = cbox_config_get_note(cfg_section, tname, gres);
|
||
|
g_free(tname);
|
||
|
tname = g_strdup_printf("%s_channel", trkname);
|
||
|
int channel = cbox_config_get_note(cfg_section, tname, gchannel);
|
||
|
g_free(tname);
|
||
|
tname = g_strdup_printf("%s_swing", trkname);
|
||
|
int swing = cbox_config_get_int(cfg_section, tname, gswing);
|
||
|
g_free(tname);
|
||
|
tname = g_strdup_printf("%s_notes", trkname);
|
||
|
const char *notes = cbox_config_get_string(cfg_section, tname);
|
||
|
g_free(tname);
|
||
|
if (!notes)
|
||
|
{
|
||
|
g_error("Invalid track %s", trkname);
|
||
|
}
|
||
|
const char *s = notes;
|
||
|
int t = 0;
|
||
|
while(1)
|
||
|
{
|
||
|
if (!*s)
|
||
|
break;
|
||
|
|
||
|
gchar *note;
|
||
|
const char *comma = strchr(s, ',');
|
||
|
if (comma)
|
||
|
{
|
||
|
note = g_strndup(s, comma - s);
|
||
|
s = comma + 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
note = g_strdup(s);
|
||
|
s += strlen(s);
|
||
|
}
|
||
|
|
||
|
if (*note)
|
||
|
{
|
||
|
int pitch = note_from_string(note);
|
||
|
|
||
|
int pos = t * ppqn_factor / res + start_pos;
|
||
|
if (t & 1)
|
||
|
pos += ppqn_factor * swing / (res * 24);
|
||
|
|
||
|
int pos2 = (t + 1) * ppqn_factor / res + start_pos;
|
||
|
if (t & 1)
|
||
|
pos2 += ppqn_factor * swing / (res * 24);
|
||
|
|
||
|
pitch += transpose;
|
||
|
|
||
|
cbox_midi_pattern_maker_add(m, pos, 0x90 + channel - 1, pitch, vel);
|
||
|
cbox_midi_pattern_maker_add(m, pos2 - 1, 0x80 + channel - 1, pitch, 0);
|
||
|
}
|
||
|
t++;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
g_free(cfg_section);
|
||
|
|
||
|
return length;
|
||
|
}
|
||
|
|
||
|
static int cbox_midi_pattern_load_drum_into(struct cbox_midi_pattern_maker *m, const char *name, int start_pos, uint64_t ppqn_factor)
|
||
|
{
|
||
|
gchar *cfg_section = g_strdup_printf("drumpattern:%s", name);
|
||
|
|
||
|
if (!cbox_config_has_section(cfg_section))
|
||
|
{
|
||
|
g_error("Drum pattern '%s' not found", name);
|
||
|
g_free(cfg_section);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
gchar *smf = cbox_config_get_string(cfg_section, "smf");
|
||
|
#if USE_LIBSMF
|
||
|
if (smf)
|
||
|
return cbox_midi_pattern_load_smf_into(m, smf);
|
||
|
#else
|
||
|
if (smf)
|
||
|
g_warning("libsmf disabled at build time, MIDI import functionality not available.");
|
||
|
#endif
|
||
|
|
||
|
int length = ppqn_factor * cbox_config_get_int(cfg_section, "beats", 4);
|
||
|
int channel = cbox_config_get_int(cfg_section, "channel", 10);
|
||
|
int gswing = cbox_config_get_int(cfg_section, "swing", 0);
|
||
|
int gres = cbox_config_get_int(cfg_section, "resolution", 4);
|
||
|
|
||
|
for (int t = 1; ; t++)
|
||
|
{
|
||
|
gchar *tname = g_strdup_printf("track%d", t);
|
||
|
char *trkname = cbox_config_get_string(cfg_section, tname);
|
||
|
g_free(tname);
|
||
|
if (trkname)
|
||
|
{
|
||
|
tname = g_strdup_printf("%s_note", trkname);
|
||
|
int note = cbox_config_get_note(cfg_section, tname, -1);
|
||
|
g_free(tname);
|
||
|
tname = g_strdup_printf("%s_res", trkname);
|
||
|
int res = cbox_config_get_note(cfg_section, tname, gres);
|
||
|
g_free(tname);
|
||
|
tname = g_strdup_printf("%s_swing", trkname);
|
||
|
int swing = cbox_config_get_int(cfg_section, tname, gswing);
|
||
|
g_free(tname);
|
||
|
tname = g_strdup_printf("%s_trigger", trkname);
|
||
|
const char *trigger = cbox_config_get_string(cfg_section, tname);
|
||
|
g_free(tname);
|
||
|
if (!trigger || note == -1)
|
||
|
{
|
||
|
g_error("Invalid track %s", trkname);
|
||
|
}
|
||
|
int t = 0;
|
||
|
for (int i = 0; trigger[i]; i++)
|
||
|
{
|
||
|
int pos = t * ppqn_factor / res + start_pos;
|
||
|
if (t & 1)
|
||
|
pos += ppqn_factor * swing / (res * 24);
|
||
|
if (trigger[i] >= '1' && trigger[i] <= '9')
|
||
|
{
|
||
|
int amt = (trigger[i] - '0') * 127 / 9;
|
||
|
cbox_midi_pattern_maker_add(m, pos, 0x90 + channel - 1, note, amt);
|
||
|
cbox_midi_pattern_maker_add(m, pos + 1, 0x80 + channel - 1, note, 0);
|
||
|
t++;
|
||
|
}
|
||
|
if (trigger[i] == 'F') // flam
|
||
|
{
|
||
|
int dflam = ppqn_factor / 4;
|
||
|
int rnd = rand() & 7;
|
||
|
dflam += rnd / 2;
|
||
|
cbox_midi_pattern_maker_add(m, pos - dflam, 0x90 + channel - 1, note, 90+rnd);
|
||
|
cbox_midi_pattern_maker_add(m, pos - dflam + 1, 0x80 + channel - 1, note, 0);
|
||
|
cbox_midi_pattern_maker_add(m, pos , 0x90 + channel - 1, note, 120 + rnd);
|
||
|
cbox_midi_pattern_maker_add(m, pos + 1, 0x80 + channel - 1, note, 0);
|
||
|
t++;
|
||
|
}
|
||
|
if (trigger[i] == 'D') // drag
|
||
|
{
|
||
|
pos = (t + 1) * ppqn_factor / res + start_pos;
|
||
|
//if (!(t & 1))
|
||
|
// pos += ppqn_factor * swing / (res * 24);
|
||
|
float dflam = ppqn_factor/8.0;
|
||
|
int rnd = rand() & 7;
|
||
|
cbox_midi_pattern_maker_add(m, pos - dflam*2, 0x90 + channel - 1, note, 70+rnd);
|
||
|
cbox_midi_pattern_maker_add(m, pos - dflam*2 + 1, 0x80 + channel - 1, note, 0);
|
||
|
cbox_midi_pattern_maker_add(m, pos - dflam, 0x90 + channel - 1, note, 60+rnd);
|
||
|
cbox_midi_pattern_maker_add(m, pos - dflam + 1, 0x80 + channel - 1, note, 0);
|
||
|
t++;
|
||
|
}
|
||
|
else if (trigger[i] == '.')
|
||
|
t++;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
g_free(cfg_section);
|
||
|
|
||
|
return length;
|
||
|
}
|
||
|
|
||
|
struct cbox_midi_pattern *cbox_midi_pattern_load(struct cbox_song *song, const char *name, int is_drum, uint64_t ppqn_factor)
|
||
|
{
|
||
|
struct cbox_midi_pattern_maker *m = cbox_midi_pattern_maker_new(ppqn_factor);
|
||
|
|
||
|
int length = 0;
|
||
|
if (is_drum)
|
||
|
length = cbox_midi_pattern_load_drum_into(m, name, 0, ppqn_factor);
|
||
|
else
|
||
|
length = cbox_midi_pattern_load_melodic_into(m, name, 0, 0, -1, ppqn_factor);
|
||
|
struct cbox_midi_pattern *p = cbox_midi_pattern_maker_create_pattern(m, song, g_strdup(name));
|
||
|
p->loop_end = length;
|
||
|
|
||
|
cbox_midi_pattern_maker_destroy(m);
|
||
|
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
struct cbox_midi_pattern *cbox_midi_pattern_load_track(struct cbox_song *song, const char *name, int is_drum, uint64_t ppqn_factor)
|
||
|
{
|
||
|
int length = 0;
|
||
|
struct cbox_midi_pattern_maker *m = cbox_midi_pattern_maker_new(ppqn_factor);
|
||
|
|
||
|
gchar *cfg_section = g_strdup_printf(is_drum ? "drumtrack:%s" : "track:%s", name);
|
||
|
|
||
|
if (!cbox_config_has_section(cfg_section))
|
||
|
{
|
||
|
g_error("Drum track '%s' not found", name);
|
||
|
g_free(cfg_section);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
for (int p = 1; ; p++)
|
||
|
{
|
||
|
gchar *pname = g_strdup_printf("pos%d", p);
|
||
|
char *patname = cbox_config_get_string(cfg_section, pname);
|
||
|
g_free(pname);
|
||
|
if (patname)
|
||
|
{
|
||
|
int tplen = 0;
|
||
|
char *comma = strchr(patname, ',');
|
||
|
while(*patname)
|
||
|
{
|
||
|
char *v = comma ? g_strndup(patname, comma - patname) : g_strdup(patname);
|
||
|
patname = comma ? comma + 1 : patname + strlen(patname);
|
||
|
|
||
|
int xpval = 0, xpnote = -1;
|
||
|
if (!is_drum)
|
||
|
{
|
||
|
char *xp = strchr(v, '+');
|
||
|
if (xp)
|
||
|
{
|
||
|
*xp = '\0';
|
||
|
xpval = atoi(xp + 1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
xp = strchr(v, '=');
|
||
|
if (xp)
|
||
|
{
|
||
|
*xp = '\0';
|
||
|
xpnote = note_from_string(xp + 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
int plen = 0;
|
||
|
int is_drum_pat = is_drum;
|
||
|
int nofs = 0;
|
||
|
if (*v == '@')
|
||
|
{
|
||
|
nofs = 1;
|
||
|
is_drum_pat = !is_drum_pat;
|
||
|
}
|
||
|
if (is_drum_pat)
|
||
|
plen = cbox_midi_pattern_load_drum_into(m, v + nofs, length, ppqn_factor);
|
||
|
else
|
||
|
plen = cbox_midi_pattern_load_melodic_into(m, v + nofs, length, xpval, xpnote, ppqn_factor);
|
||
|
g_free(v);
|
||
|
if (plen < 0)
|
||
|
{
|
||
|
cbox_midi_pattern_maker_destroy(m);
|
||
|
return NULL;
|
||
|
}
|
||
|
if (plen > tplen)
|
||
|
tplen = plen;
|
||
|
if (*patname)
|
||
|
comma = strchr(patname, ',');
|
||
|
}
|
||
|
length += tplen;
|
||
|
}
|
||
|
else
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
g_free(cfg_section);
|
||
|
|
||
|
struct cbox_midi_pattern *p = cbox_midi_pattern_maker_create_pattern(m, song, g_strdup(name));
|
||
|
p->loop_end = length;
|
||
|
|
||
|
cbox_midi_pattern_maker_destroy(m);
|
||
|
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
struct cbox_midi_pattern *cbox_midi_pattern_new_from_blob(struct cbox_song *song, const struct cbox_blob *blob, int length, uint64_t ppqn_factor)
|
||
|
{
|
||
|
struct cbox_midi_pattern_maker *m = cbox_midi_pattern_maker_new(ppqn_factor);
|
||
|
|
||
|
struct cbox_blob_serialized_event event;
|
||
|
for (size_t i = 0; i < blob->size; i += sizeof(event))
|
||
|
{
|
||
|
// not sure about alignment guarantees of Python buffers
|
||
|
memcpy(&event, ((uint8_t *)blob->data) + i, sizeof(event));
|
||
|
cbox_midi_pattern_maker_add(m, event.time, event.cmd, event.byte1, event.byte2);
|
||
|
}
|
||
|
|
||
|
struct cbox_midi_pattern *p = cbox_midi_pattern_maker_create_pattern(m, song, g_strdup("unnamed-blob"));
|
||
|
p->loop_end = length;
|
||
|
|
||
|
cbox_midi_pattern_maker_destroy(m);
|
||
|
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
struct cbox_blob *cbox_midi_pattern_to_blob(struct cbox_midi_pattern *pat, int *length)
|
||
|
{
|
||
|
if (length)
|
||
|
*length = pat->loop_end;
|
||
|
|
||
|
struct cbox_blob_serialized_event event;
|
||
|
int size = 0;
|
||
|
for (uint32_t i = 0; i < pat->event_count; i++)
|
||
|
{
|
||
|
// currently sysex events and the like are not supported
|
||
|
if (pat->events[i].size < 4)
|
||
|
size += sizeof(event);
|
||
|
}
|
||
|
|
||
|
struct cbox_blob *blob = cbox_blob_new(size);
|
||
|
|
||
|
size = 0;
|
||
|
uint8_t *data = blob->data;
|
||
|
for (uint32_t i = 0; i < pat->event_count; i++)
|
||
|
{
|
||
|
// currently sysex events and the like are not supported
|
||
|
const struct cbox_midi_event *src = &pat->events[i];
|
||
|
if (src->size < 4)
|
||
|
{
|
||
|
event.time = src->time;
|
||
|
event.len = src->size;
|
||
|
memcpy(&event.cmd, &src->data_inline[0], event.len);
|
||
|
memcpy(data + size, &event, sizeof(event));
|
||
|
size += sizeof(event);
|
||
|
}
|
||
|
}
|
||
|
return blob;
|
||
|
}
|
||
|
|
||
|
gboolean cbox_midi_pattern_process_cmd(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
|
||
|
{
|
||
|
struct cbox_midi_pattern *p = ct->user_data;
|
||
|
|
||
|
if (!strcmp(cmd->command, "/status") && !strcmp(cmd->arg_types, ""))
|
||
|
{
|
||
|
if (!cbox_check_fb_channel(fb, cmd->command, error))
|
||
|
return FALSE;
|
||
|
|
||
|
return cbox_execute_on(fb, NULL, "/event_count", "i", error, (int)p->event_count) &&
|
||
|
cbox_execute_on(fb, NULL, "/loop_end", "i", error, (int)p->loop_end) &&
|
||
|
cbox_execute_on(fb, NULL, "/name", "s", error, p->name) &&
|
||
|
CBOX_OBJECT_DEFAULT_STATUS(p, fb, error)
|
||
|
;
|
||
|
}
|
||
|
else if (!strcmp(cmd->command, "/name") && !strcmp(cmd->arg_types, "s"))
|
||
|
{
|
||
|
char *old_name = p->name;
|
||
|
p->name = g_strdup(CBOX_ARG_S(cmd, 0));
|
||
|
g_free(old_name);
|
||
|
return TRUE;
|
||
|
}
|
||
|
return cbox_object_default_process_cmd(ct, fb, cmd, error);
|
||
|
}
|