kobodl/sound/a_channel.c
Ville Lindholm dbc223eb84
Initial commit
Import existing source tree; original VCS history is no longer available.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-28 16:35:31 +03:00

251 lines
6.7 KiB
C

/*(LGPL)
---------------------------------------------------------------------------
a_channel.c - Audio Engine high level control abstraction
---------------------------------------------------------------------------
* Copyright (C) 2001, David Olofson
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdlib.h>
#include "kobolog.h"
#include "a_globals.h"
#include "a_struct.h"
#include "a_control.h"
#include "a_sequencer.h"
#include "a_tools.h"
#define SDBG(x)
void channel_stop_all(void)
{
int i;
for(i = 0; i < AUDIO_MAX_VOICES; ++i)
voice_kill(voicetab + i);
for(i = 0; i < AUDIO_MAX_CHANNELS; ++i)
channeltab[i].playing = 0; /* Reset "voice" count */
sequencer_stop(-1);
}
/*
FIXME: What if there are sounds playing when an ACC_PATCH comes in!?
FIXME: Maybe patch plugins should have a "detach + release" feature?
FIXME:
FIXME: Another alternative would be some way of forwarding events to
FIXME: "unknown" tags (easy now that the controller must provide the
FIXME: tags!) on to whatever patch they were started by. That is, an
FIXME: actively managed, per channel "patch history". "Affect all"
FIXME: events (-1 tags) would have to be processed *and* forwarded.
FIXME:
FIXME: Patch plugins could just pass events for unknown tags to some
FIXME: "forward to previous patch" event port, that the engine checks
FIXME: after running a patch plugin. Of course, this would require
FIXME: that all patches actually remember tags, even if they don't
FIXME: really care about them... In fact, it would be a problem even
FIXME: for patches that *do* use tags, as those will generally
FIXME: "remember" only tags that actually get the required resources.
FIXME: (That is, if a poly patch runs out of voices, it'll start
FIXME: forgetting tags...)
FIXME:
FIXME: A much simpler solution would be to just keep patches running
FIXME: until they say they have no more "contexts" active. Events
FIXME: would have to be duplicated - or we could have all plugins
FIXME: just pass events on to a "garbage port", that actually becomes
FIXME: a forwarding port when needed. In the normal case, the engine
FIXME: would just flush this port when appropriate.
*/
/*
FIXME: Maybe we actually want to pass ACC_PATCH change events
FIXME: on to patch drivers? If so, should they be sent to
FIXME: the old or new patch, or both? That question alone
FIXME: seems to suggest that ACC_PATCH should die here, but
FIXME: then again, some drivers might need these events...
*/
static inline void channel_process(audio_channel_t *c, unsigned frames)
{
aev_event_t *first_ev = c->port.first;
unsigned split_frames = frames;
/*
* This one's rather ugly... Better ideas, anyone?
*/
aev_timestamp_t timer_save = aev_timer;
while(1)
{
aev_event_t *ev = c->port.first;
aev_event_t *prev_ev = NULL;
aev_event_t *rerun_ev = NULL;
while(ev && (AEV_TIME(ev->frame, 0) < split_frames))
{
if(ev->type != CE_CONTROL)
{
prev_ev = ev;
ev = ev->next;
continue;
}
c->rctl[ev->index] = ev->arg2;
switch(ev->index)
{
/* case_ACC_NOTRANSFORM:*/
case ACC_GROUP:
case ACC_PRIORITY:
break;
case ACC_PATCH:
{
aev_event_t *del_ev = ev;
SDBG(log_printf(D3LOG, "(patch) ");)
if(c->ctl[ACC_PATCH] == c->rctl[ACC_PATCH])
{
SDBG(log_printf(D3LOG, "[IGNORE]\n");)
ev = ev->next;
if(prev_ev)
prev_ev->next = ev;
else
c->port.first = ev;
}
else if(ev == first_ev)
{
SDBG(log_printf(D3LOG, "[FIRST]\n");)
c->ctl[ACC_PATCH] = c->rctl[ACC_PATCH];
ev = ev->next;
c->port.first = ev;
first_ev = NULL;
}
else
{
SDBG(log_printf(D3LOG, "[SPLIT]\n");)
split_frames = AEV_TIME(ev->frame, 0);
rerun_ev = ev->next;
/*
* Note that we really must break the list!
* Going by the timestams is insufficient,
* as there may be more events for the same
* frame.
*/
if(prev_ev)
prev_ev->next = NULL;
else
c->port.first = NULL;
ev = NULL;
}
aev_free(del_ev);
continue;
}
case ACC_PRIM_BUS:
case ACC_SEND_BUS:
break;
case_ACC_ADDTRANSFORM:
{
audio_group_t *g;
int min, max;
g = grouptab + c->rctl[ACC_GROUP];
min = addtrans_min[ev->index -
ACC_ADDTRANSFORM_FIRST];
max = addtrans_max[ev->index -
ACC_ADDTRANSFORM_FIRST];
ev->arg2 += g->ctl[ev->index];
ev->arg2 -= addtrans_bias[ev->index -
ACC_ADDTRANSFORM_FIRST];
if(ev->arg2 < min)
ev->arg2 = min;
else if(ev->arg2 > max)
ev->arg2 = max;
break;
}
case_ACC_MULTRANSFORM:
{
audio_group_t *g;
g = grouptab + c->rctl[ACC_GROUP];
ev->arg2 = fixmul(ev->arg2, g->ctl[ev->index]);
break;
}
}
prev_ev = ev;
ev = ev->next;
}
/* Run the patch plugin! */
/* if(c->ctl[ACC_PATCH] < AUDIO_MAX_PATCHES)*/
{
audio_patch_t *p = patchtab + c->ctl[ACC_PATCH];
p->process(p, c, split_frames);
}
/* Apply last patch change, if any */
c->ctl[ACC_PATCH] = c->rctl[ACC_PATCH];
frames -= split_frames;
if(!frames)
break; /* Done! */
SDBG(log_printf(D3LOG, "split!\n");)
/* Set up event port and timing for next fragment */
aev_advance_timer(split_frames);
split_frames = frames;
c->port.first = rerun_ev;
}
aev_timer = timer_save;
}
void channel_process_all(unsigned frames)
{
int i;
for(i = 0; i < AUDIO_MAX_CHANNELS; ++i)
channel_process(channeltab + i, frames);
}
static int _wasinit = 0;
void audio_channel_open(void)
{
int i;
if(_wasinit)
return;
memset(channeltab, 0, sizeof(channeltab));
for(i = 0; i < AUDIO_MAX_CHANNELS; ++i)
{
char *buf = malloc(64);
snprintf(buf, 64, "Audio Channel %d", i);
aev_port_init(&channeltab[i].port, buf);
acc_copya(&channeltab[i].rctl, &a_channel_def_ctl);
acc_copya(&channeltab[i].ctl, &a_channel_def_ctl);
channeltab[i].voices = NULL;
}
_wasinit = 1;
}
void audio_channel_close(void)
{
int i;
if(!_wasinit)
return;
for(i = 0; i < AUDIO_MAX_CHANNELS; ++i)
{
aev_flush(&channeltab[i].port);
free((char *)channeltab[i].port.name);
}
memset(channeltab, 0, sizeof(channeltab));
_wasinit = 0;
}