#include "project_internal.h" #include "../graph/graph_internal.h" #include #include 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, ©); 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; }