xtc/src/mixer/model.c

166 lines
5.9 KiB
C

#include "../project/project_internal.h"
#include <math.h>
#include <string.h>
/**
* @brief Finds the dense index of a project track by stable ID.
*
* @param p Project to search; must not be NULL.
* @param id Stable track identifier.
* @return Dense track index, or SIZE_MAX when no track has the ID.
*/
static size_t find_track(const xtc_project *p, uint64_t id)
{
size_t i;
for (i = 0; i < p->track_count; ++i)
if (p->tracks[i].id == id)
return i;
return SIZE_MAX;
}
/**
* @brief Sets a track's gain, pan, mute, and solo controls after validation.
*
* @param p Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param gain Linear gain, 0 to 4 for track controls.
* @param pan Pan position from -1 (left) to 1 (right).
* @param mute Track mute flag, 0 or 1.
* @param solo Track solo flag, 0 or 1.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_set_mix(xtc_project *p, uint64_t id, float gain, float pan, int mute, int solo)
{
size_t i;
if (!p || !isfinite(gain) || gain < 0 || gain > 4 || !isfinite(pan) || pan < -1 || pan > 1 ||
(mute != 0 && mute != 1) || (solo != 0 && solo != 1))
return XTC_ERR_INVALID_ARGUMENT;
i = find_track(p, id);
if (i == SIZE_MAX)
return XTC_ERR_NOT_FOUND;
p->tracks[i].gain = gain;
p->tracks[i].pan = pan;
p->tracks[i].mute = mute;
p->tracks[i].solo = solo;
return XTC_OK;
}
/**
* @brief Inserts an automation point, replacing any point at the same track, parameter, and tick.
*
* @param p Project model; borrowed for this call.
* @param a Automation point to read or update.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_set_automation(xtc_project *p, const xtc_automation_point *a)
{
size_t i;
if (!p || !a || a->step > 1 || !isfinite(a->value) || a->tick > XTC_PROJECT_MAX_TICK ||
(a->parameter != XTC_AUTOMATION_GAIN && a->parameter != XTC_AUTOMATION_PAN) ||
a->value < (a->parameter == XTC_AUTOMATION_GAIN ? 0 : -1) ||
a->value > (a->parameter == XTC_AUTOMATION_GAIN ? 4 : 1))
return XTC_ERR_INVALID_ARGUMENT;
if (find_track(p, a->track_id) == SIZE_MAX)
return XTC_ERR_NOT_FOUND;
for (i = 0; i < p->automation_count; ++i)
if (p->automation[i].track_id == a->track_id && p->automation[i].parameter == a->parameter &&
p->automation[i].tick == a->tick)
break;
if (i == XTC_MAX_AUTOMATION_POINTS)
return XTC_ERR_CAPACITY;
if (i == p->automation_count)
++p->automation_count;
p->automation[i] = *a;
return XTC_OK;
}
/**
* @brief Removes the automation point at the specified track, parameter, and tick.
*
* @param p Project model; borrowed for this call.
* @param id Stable identifier of the object to find or edit.
* @param param Automation lane selector.
* @param tick Musical position in ticks.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_remove_automation(xtc_project *p, uint64_t id, xtc_automation_parameter param, uint64_t tick)
{
size_t i;
if (!p || (param != XTC_AUTOMATION_GAIN && param != XTC_AUTOMATION_PAN) || tick > XTC_PROJECT_MAX_TICK)
return XTC_ERR_INVALID_ARGUMENT;
for (i = 0; i < p->automation_count; ++i)
if (p->automation[i].track_id == id && p->automation[i].parameter == param && p->automation[i].tick == tick)
break;
if (i == p->automation_count)
return XTC_ERR_NOT_FOUND;
memmove(p->automation + i, p->automation + i + 1, (--p->automation_count - i) * sizeof(*p->automation));
return XTC_OK;
}
/**
* @brief Copies the number of automation points in the project.
*
* @param p Project model; borrowed for this call.
* @param n Receives the item count.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_automation_count(const xtc_project *p, size_t *n)
{
if (!p || !n)
return XTC_ERR_INVALID_ARGUMENT;
*n = p->automation_count;
return XTC_OK;
}
/**
* @brief Copies an automation point selected by dense list index.
*
* @param p Project model; borrowed for this call.
* @param i Zero-based index in the dense list.
* @param a Automation point to read or update.
* @return XTC_OK on success, or an error code describing the failure.
*/
xtc_result xtc_project_get_automation(const xtc_project *p, size_t i, xtc_automation_point *a)
{
if (!p || !a)
return XTC_ERR_INVALID_ARGUMENT;
if (i >= p->automation_count)
return XTC_ERR_NOT_FOUND;
*a = p->automation[i];
return XTC_OK;
}
xtc_result xtc_project_add_bus(xtc_project *p, const char *name, uint64_t *out)
{
xtc_result r = xtc_project_add_track(p, name, out);
if (r == XTC_OK) p->tracks[p->track_count-1].is_bus = 1;
return r;
}
xtc_result xtc_project_route_track(xtc_project *p, uint64_t id, uint64_t bus)
{
size_t i, destination;
if (!p) return XTC_ERR_INVALID_ARGUMENT;
i = find_track(p, id);
if (i == SIZE_MAX) return XTC_ERR_NOT_FOUND;
if (p->tracks[i].is_bus) return XTC_ERR_INVALID_STATE;
destination = find_track(p, bus);
if (bus != UINT64_MAX && (destination == SIZE_MAX || !p->tracks[destination].is_bus)) return XTC_ERR_INVALID_ARGUMENT;
p->tracks[i].bus_id = bus;
return XTC_OK;
}
void xtc_peak_meter_reset(xtc_peak_meter *m)
{
if (m) { m->db = -90; m->clipped = 0; }
}
xtc_result xtc_peak_meter_update(xtc_peak_meter *m, float peak, double seconds)
{
float db, released;
if (!m || !isfinite(peak) || peak < 0 || !isfinite(seconds) || seconds < 0 || seconds > 60)
return XTC_ERR_INVALID_ARGUMENT;
db = peak > 0 ? 20.0f * log10f(peak) : -90;
if (db < -90) db = -90;
released = m->db - (float)(20 * seconds);
m->db = db > released ? db : released;
if (peak >= 1) m->clipped = 1;
return XTC_OK;
}