xtc/src/midi/model.c

206 lines
6.9 KiB
C
Raw Normal View History

#include "../project/project_internal.h"
#include "../graph/graph_internal.h"
#include <string.h>
2026-10-27 01:37:21 +01:00
/**
* @brief Adds a MIDI clip and ensures an instrument output route exists.
*
* @param p Project model; borrowed for this call.
* @param track Stable track identifier.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_add_midi_clip(xtc_project *p, uint64_t track, uint64_t start, uint64_t length, uint64_t *out)
{
xtc_project *c = NULL;
xtc_result r;
size_t i, source = SIZE_MAX, output = SIZE_MAX;
uint64_t id;
2026-10-27 01:37:21 +01:00
if (!p || !out)
return XTC_ERR_INVALID_ARGUMENT;
r = xtc_project_clone(p, &c);
2026-10-27 01:37:21 +01:00
if (r == XTC_OK)
r = xtc_project_add_clip(c, track, "MIDI", start, length, &id);
if (r == XTC_OK) {
c->clips[c->clip_count - 1].midi = 1;
for (i = 0; i < c->graph->node_count; ++i) {
xtc_node *n = c->graph->nodes + i;
2026-10-27 01:37:21 +01:00
if (n->type == XTC_NODE_INSTRUMENT)
source = n->id;
if (n->type == XTC_NODE_OUTPUT)
output = n->id;
}
if (source == SIZE_MAX) {
r = xtc_graph_add_instrument(c->graph, &source);
2026-10-27 01:37:21 +01:00
if (r == XTC_OK && output == SIZE_MAX)
r = xtc_graph_add_output(c->graph, &output);
if (r == XTC_OK)
r = xtc_graph_connect(c->graph, source, output);
}
}
if (r == XTC_OK) {
2026-10-27 01:37:21 +01:00
xtc_graph *old = p->graph;
*p = *c;
c->graph = old;
*out = id;
}
2026-10-27 01:37:21 +01:00
xtc_project_destroy(c);
return r;
}
2026-10-27 01:37:21 +01:00
/**
* @brief Copies the number of notes in the project.
*
* @param p Project model; borrowed for this call.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_note_count(const xtc_project *p, size_t *out)
{
2026-10-27 01:37:21 +01:00
if (!p || !out)
return XTC_ERR_INVALID_ARGUMENT;
*out = p->note_count;
return XTC_OK;
}
2026-10-27 01:37:21 +01:00
/**
* @brief Copies a note snapshot selected by dense list index.
*
* @param p Project model; borrowed for this call.
* @param i Zero-based index in the dense list.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-27 01:37:21 +01:00
xtc_result xtc_project_get_note(const xtc_project *p, size_t i, xtc_note_info *out)
{
if (!p || !out)
return XTC_ERR_INVALID_ARGUMENT;
if (i >= p->note_count)
return XTC_ERR_NOT_FOUND;
*out = p->notes[i];
return XTC_OK;
}
/**
* @brief Validates a note's MIDI fields and range within a MIDI clip source.
*
* @param p Project model; borrowed for this call.
* @param clip Stable clip identifier.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param pitch MIDI note number, 0 to 127.
* @param velocity MIDI note velocity, 1 to 127.
* @param channel MIDI channel, 0 to 15.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-27 01:37:21 +01:00
static xtc_result valid_note(const xtc_project *p, uint64_t clip, uint64_t start, uint64_t length, unsigned int pitch,
unsigned int velocity, unsigned int channel)
{
xtc_clip_info c;
if (pitch > 127 || !velocity || velocity > 127 || channel > 15 || !length)
return XTC_ERR_INVALID_ARGUMENT;
if (xtc_project_find_clip(p, clip, &c) != XTC_OK)
return XTC_ERR_NOT_FOUND;
if (!c.midi)
return XTC_ERR_INVALID_STATE;
if (start >= c.source_length_tick || length > c.source_length_tick - start)
return XTC_ERR_INVALID_ARGUMENT;
return XTC_OK;
}
/**
* @brief Adds a validated MIDI note with a new stable identifier.
*
* @param p Project model; borrowed for this call.
* @param clip Stable clip identifier.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param pitch MIDI note number, 0 to 127.
* @param velocity MIDI note velocity, 1 to 127.
* @param channel MIDI channel, 0 to 15.
* @param out Receives the operation result.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-27 01:37:21 +01:00
xtc_result xtc_project_add_note(xtc_project *p, uint64_t clip, uint64_t start, uint64_t length, unsigned int pitch,
unsigned int velocity, unsigned int channel, uint64_t *out)
{
xtc_result r;
xtc_note_info n;
2026-10-27 01:37:21 +01:00
if (!p || !out)
return XTC_ERR_INVALID_ARGUMENT;
r = valid_note(p, clip, start, length, pitch, velocity, channel);
2026-10-27 01:37:21 +01:00
if (r != XTC_OK)
return r;
if (p->note_count == XTC_PROJECT_MAX_NOTES || p->next_note_id == UINT64_MAX)
return XTC_ERR_CAPACITY;
memset(&n, 0, sizeof(n));
2026-10-27 01:37:21 +01:00
n.id = p->next_note_id++;
n.clip_id = clip;
n.start_tick = start;
n.length_tick = length;
n.pitch = pitch;
n.velocity = velocity;
n.channel = channel;
p->notes[p->note_count++] = n;
*out = n.id;
return XTC_OK;
}
2026-10-27 01:37:21 +01:00
/**
* @brief Replaces a note's source range, pitch, velocity, and channel after validation.
*
* @param p Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param start Start position in musical ticks.
* @param length Duration in musical ticks.
* @param pitch MIDI note number, 0 to 127.
* @param velocity MIDI note velocity, 1 to 127.
* @param channel MIDI channel, 0 to 15.
* @return XTC_OK on success, or an error code describing the failure.
*/
2026-10-27 01:37:21 +01:00
xtc_result xtc_project_edit_note(xtc_project *p, uint64_t id, uint64_t start, uint64_t length, unsigned int pitch,
unsigned int velocity, unsigned int channel)
{
size_t i;
xtc_result r;
2026-10-27 01:37:21 +01:00
if (!p)
return XTC_ERR_INVALID_ARGUMENT;
for (i = 0; i < p->note_count; ++i)
if (p->notes[i].id == id)
break;
if (i == p->note_count)
return XTC_ERR_NOT_FOUND;
r = valid_note(p, p->notes[i].clip_id, start, length, pitch, velocity, channel);
2026-10-27 01:37:21 +01:00
if (r != XTC_OK)
return r;
p->notes[i].start_tick = start;
p->notes[i].length_tick = length;
p->notes[i].pitch = pitch;
p->notes[i].velocity = velocity;
p->notes[i].channel = channel;
return XTC_OK;
}
2026-10-27 01:37:21 +01:00
/**
* @brief Removes a note selected by stable identifier.
*
* @param p Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_remove_note(xtc_project *p, uint64_t id)
{
size_t i;
2026-10-27 01:37:21 +01:00
if (!p)
return XTC_ERR_INVALID_ARGUMENT;
for (i = 0; i < p->note_count; ++i)
if (p->notes[i].id == id)
break;
if (i == p->note_count)
return XTC_ERR_NOT_FOUND;
memmove(p->notes + i, p->notes + i + 1, (p->note_count - i - 1) * sizeof(*p->notes));
2026-10-27 01:37:21 +01:00
--p->note_count;
return XTC_OK;
}