xtc/src/project/project.c

222 lines
10 KiB
C

#include "project_internal.h"
#include "../graph/graph_internal.h"
#include <stdlib.h>
#include <string.h>
int xtc_project_valid_name(const char *name)
{
size_t length = 0U, i = 0U;
if (name == NULL) return 0;
while (length < XTC_PROJECT_NAME_BYTES && name[length] != '\0') ++length;
if (length == 0U || length == XTC_PROJECT_NAME_BYTES) return 0;
while (i < length) {
unsigned char byte = (unsigned char)name[i++];
size_t extra, j;
if (byte < 32U || byte == 127U) return 0;
if (byte < 128U) continue;
if (byte >= 194U && byte <= 223U) extra = 1U;
else if (byte >= 224U && byte <= 239U) extra = 2U;
else if (byte >= 240U && byte <= 244U) extra = 3U;
else return 0;
if (extra > length - i) return 0;
if ((byte == 224U && (unsigned char)name[i] < 160U) ||
(byte == 237U && (unsigned char)name[i] >= 160U) ||
(byte == 240U && (unsigned char)name[i] < 144U) ||
(byte == 244U && (unsigned char)name[i] >= 144U)) return 0;
for (j = 0U; j < extra; ++j) {
unsigned char tail = (unsigned char)name[i++];
if (tail < 128U || tail > 191U) return 0;
}
}
return 1;
}
static size_t track_index(const xtc_project *project, uint64_t id)
{
size_t i;
for (i = 0U; i < project->track_count; ++i) if (project->tracks[i].id == id) return i;
return SIZE_MAX;
}
static size_t clip_index(const xtc_project *project, uint64_t id)
{
size_t i;
for (i = 0U; i < project->clip_count; ++i) if (project->clips[i].id == id) return i;
return SIZE_MAX;
}
static int valid_range(uint64_t start, uint64_t length, uint64_t offset, uint64_t source)
{
return start <= XTC_PROJECT_MAX_TICK && length != 0U &&
length <= XTC_PROJECT_MAX_TICK - start && source <= XTC_PROJECT_MAX_TICK &&
offset <= source && length <= source - offset;
}
xtc_result xtc_project_create(xtc_project **out_project)
{
xtc_project *project;
xtc_result result;
if (out_project == NULL) return XTC_ERR_INVALID_ARGUMENT;
*out_project = NULL;
project = calloc(1U, sizeof(*project));
if (project == NULL) return XTC_ERR_OUT_OF_MEMORY;
result = xtc_graph_create(64U, &project->graph);
if (result != XTC_OK) { free(project); return result; }
project->graph->require_output_nodes = 1;
project->tempo = 120000U;
*out_project = project;
return XTC_OK;
}
void xtc_project_destroy(xtc_project *project)
{ if (project != NULL) { xtc_graph_destroy(project->graph); free(project); } }
const xtc_graph *xtc_project_graph(const xtc_project *project)
{ return project == NULL ? NULL : project->graph; }
xtc_result xtc_project_set_graph(xtc_project *project, const xtc_graph *source)
{
xtc_graph *copy;
xtc_result result;
if (project == NULL || source == NULL) return XTC_ERR_INVALID_ARGUMENT;
if (source->node_count > 64U || source->edge_count > 4096U) return XTC_ERR_CAPACITY;
result = xtc_graph_validate(source);
if (result != XTC_OK) return result;
result = xtc_graph_create(64U, &copy);
if (result != XTC_OK) return result;
copy->node_count = source->node_count; copy->edge_count = source->edge_count;
copy->next_id = source->next_id; copy->require_output_nodes = source->require_output_nodes;
memcpy(copy->nodes, source->nodes, source->node_count * sizeof(*copy->nodes));
memcpy(copy->edges, source->edges, source->edge_count * sizeof(*copy->edges));
xtc_graph_destroy(project->graph); project->graph = copy;
return XTC_OK;
}
xtc_result xtc_project_clone(const xtc_project *source, xtc_project **out_project)
{
xtc_project *copy;
xtc_result result;
if (out_project == NULL) return XTC_ERR_INVALID_ARGUMENT;
*out_project = NULL;
if (source == NULL) return XTC_ERR_INVALID_ARGUMENT;
copy = malloc(sizeof(*copy));
if (copy == NULL) return XTC_ERR_OUT_OF_MEMORY;
*copy = *source; copy->graph = NULL;
result = xtc_project_set_graph(copy, source->graph);
if (result != XTC_OK) { free(copy); return result; }
*out_project = copy;
return XTC_OK;
}
xtc_result xtc_project_get_counts(const xtc_project *project, size_t *tracks, size_t *clips)
{
if (project == NULL || tracks == NULL || clips == NULL) return XTC_ERR_INVALID_ARGUMENT;
*tracks = project->track_count; *clips = project->clip_count; return XTC_OK;
}
xtc_result xtc_project_get_track(const xtc_project *project, size_t index, xtc_track_info *track)
{
if (project == NULL || track == NULL) return XTC_ERR_INVALID_ARGUMENT;
if (index >= project->track_count) return XTC_ERR_NOT_FOUND;
*track = project->tracks[index]; return XTC_OK;
}
xtc_result xtc_project_get_clip(const xtc_project *project, size_t index, xtc_clip_info *clip)
{
if (project == NULL || clip == NULL) return XTC_ERR_INVALID_ARGUMENT;
if (index >= project->clip_count) return XTC_ERR_NOT_FOUND;
*clip = project->clips[index]; return XTC_OK;
}
xtc_result xtc_project_find_clip(const xtc_project *project, uint64_t id, xtc_clip_info *clip)
{
if (project == NULL) return XTC_ERR_INVALID_ARGUMENT;
return xtc_project_get_clip(project, clip_index(project, id), clip);
}
xtc_result xtc_project_add_track(xtc_project *project, const char *name, uint64_t *id)
{
xtc_track_info track = {0};
if (project == NULL || id == NULL || !xtc_project_valid_name(name)) return XTC_ERR_INVALID_ARGUMENT;
if (project->track_count == XTC_PROJECT_MAX_TRACKS || project->next_track_id == UINT64_MAX) return XTC_ERR_CAPACITY;
track.id = project->next_track_id++;
memcpy(track.name, name, strlen(name) + 1U);
project->tracks[project->track_count++] = track; *id = track.id; return XTC_OK;
}
xtc_result xtc_project_remove_track(xtc_project *project, uint64_t id)
{
size_t index, i, count = 0U;
if (project == NULL) return XTC_ERR_INVALID_ARGUMENT;
index = track_index(project, id);
if (index == SIZE_MAX) return XTC_ERR_NOT_FOUND;
for (i = 0U; i < project->clip_count; ++i)
if (project->clips[i].track_id != id) project->clips[count++] = project->clips[i];
project->clip_count = count;
memmove(project->tracks + index, project->tracks + index + 1U,
(project->track_count - index - 1U) * sizeof(*project->tracks));
--project->track_count; return XTC_OK;
}
xtc_result xtc_project_add_clip(xtc_project *project, uint64_t track, const char *name,
uint64_t start, uint64_t length, uint64_t *id)
{
xtc_clip_info clip = {0};
if (project == NULL || id == NULL || !xtc_project_valid_name(name) || !valid_range(start, length, 0U, length))
return XTC_ERR_INVALID_ARGUMENT;
if (track_index(project, track) == SIZE_MAX) return XTC_ERR_NOT_FOUND;
if (project->clip_count == XTC_PROJECT_MAX_CLIPS || project->next_clip_id == UINT64_MAX) return XTC_ERR_CAPACITY;
clip.id = project->next_clip_id++; clip.track_id = track;
clip.start_tick = start; clip.length_tick = clip.source_length_tick = length;
memcpy(clip.name, name, strlen(name) + 1U);
project->clips[project->clip_count++] = clip; *id = clip.id; return XTC_OK;
}
xtc_result xtc_project_remove_clip(xtc_project *project, uint64_t id)
{
size_t index;
if (project == NULL) return XTC_ERR_INVALID_ARGUMENT;
index = clip_index(project, id);
if (index == SIZE_MAX) return XTC_ERR_NOT_FOUND;
memmove(project->clips + index, project->clips + index + 1U,
(project->clip_count - index - 1U) * sizeof(*project->clips));
--project->clip_count; return XTC_OK;
}
xtc_result xtc_project_edit_clip(xtc_project *project, uint64_t id, uint64_t track,
uint64_t start, uint64_t length, uint64_t offset)
{
size_t index;
if (project == NULL) return XTC_ERR_INVALID_ARGUMENT;
index = clip_index(project, id);
if (index == SIZE_MAX || track_index(project, track) == SIZE_MAX) return XTC_ERR_NOT_FOUND;
if (!valid_range(start, length, offset, project->clips[index].source_length_tick)) return XTC_ERR_INVALID_ARGUMENT;
project->clips[index].track_id = track; project->clips[index].start_tick = start;
project->clips[index].length_tick = length; project->clips[index].offset_tick = offset;
return XTC_OK;
}
unsigned int xtc_project_tempo(const xtc_project *project) { return project == NULL ? 0U : project->tempo; }
xtc_result xtc_project_set_tempo(xtc_project *project, unsigned int tempo)
{
if (project == NULL || tempo < 20000U || tempo > 300000U) return XTC_ERR_INVALID_ARGUMENT;
project->tempo = tempo; return XTC_OK;
}
xtc_result xtc_project_tick_to_sample(const xtc_project *project, uint64_t tick, unsigned int rate, uint64_t *sample)
{
if (project == NULL || sample == NULL || rate == 0U || rate > 768000U) return XTC_ERR_INVALID_ARGUMENT;
if (tick > XTC_PROJECT_MAX_TICK) return XTC_ERR_CAPACITY;
*sample = tick * rate * UINT64_C(60000) / ((uint64_t)project->tempo * XTC_TICKS_PER_QUARTER);
return XTC_OK;
}
xtc_result xtc_project_sample_to_tick(const xtc_project *project, uint64_t sample, unsigned int rate, uint64_t *tick)
{
uint64_t scale, value, divisor, whole;
if (project == NULL || tick == NULL || rate == 0U || rate > 768000U) return XTC_ERR_INVALID_ARGUMENT;
scale = (uint64_t)project->tempo * XTC_TICKS_PER_QUARTER;
divisor = (uint64_t)rate * UINT64_C(60000);
whole = sample / divisor;
if (whole > XTC_PROJECT_MAX_TICK / scale) return XTC_ERR_CAPACITY;
value = whole * scale + (sample % divisor) * scale / divisor;
if (value > XTC_PROJECT_MAX_TICK) return XTC_ERR_CAPACITY;
*tick = value; return XTC_OK;
}
xtc_result xtc_project_validate(const xtc_project *project)
{
size_t i, j;
if (project->tempo < 20000U || project->tempo > 300000U ||
project->track_count > XTC_PROJECT_MAX_TRACKS || project->clip_count > XTC_PROJECT_MAX_CLIPS) return XTC_ERR_FORMAT;
for (i = 0U; i < project->track_count; ++i) {
if (project->tracks[i].id >= project->next_track_id || !xtc_project_valid_name(project->tracks[i].name)) return XTC_ERR_FORMAT;
for (j = 0U; j < i; ++j) if (project->tracks[j].id == project->tracks[i].id) return XTC_ERR_FORMAT;
}
for (i = 0U; i < project->clip_count; ++i) {
const xtc_clip_info *clip = project->clips + i;
if (clip->id >= project->next_clip_id || track_index(project, clip->track_id) == SIZE_MAX ||
!xtc_project_valid_name(clip->name) || !valid_range(clip->start_tick, clip->length_tick, clip->offset_tick, clip->source_length_tick)) return XTC_ERR_FORMAT;
for (j = 0U; j < i; ++j) if (project->clips[j].id == clip->id) return XTC_ERR_FORMAT;
}
return XTC_OK;
}