kobodl/sound/a_patch.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

754 lines
17 KiB
C

/*(LGPL)
---------------------------------------------------------------------------
a_patch.c - Audio Engine "instrument" definitions
---------------------------------------------------------------------------
* Copyright (C) 2001-2003, 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 <string.h>
#include "kobolog.h"
#include "a_struct.h"
#include "a_patch.h"
#include "a_sequencer.h"
#define EDBG(x)
#define EGDBG(x)
static int _is_open = 0;
#define CHECKINIT if(!_is_open) audio_patch_open();
/*
* Common tools for mono and poly...
*/
static inline int __calc_randpitch(audio_patch_t *p, int pitch)
{
static int rnd = 16576;
int rp = p->param[APP_RANDPITCH];
if(rp)
{
rnd *= 1566083941UL;
rnd++;
rnd &= 0xffffffffUL;
pitch += (rnd % rp) - rp/2;
}
return pitch;
}
static inline int __get_wave(audio_patch_t *p)
{
int wave = p->param[APP_WAVE];
if((wave < 0) || (wave >= AUDIO_MAX_WAVES))
return -1;
if(AF_MIDI == wavetab[wave].format)
return -1;
return wave;
}
/* Update base vol/send levels after control change. */
static inline void __env_control_recalc(audio_patch_t *p, audio_channel_t *c,
audio_voice_t *v)
{
patch_closure_t *clos = &v->closure;
clos->lvol = clos->rvol = clos->velvol;
clos->lvol *= 64 - (c->ctl[ACC_PAN] >> 10);
clos->rvol *= 64 + (c->ctl[ACC_PAN] >> 10);
clos->lvol >>= 6;
clos->rvol >>= 6;
clos->lsend = c->ctl[ACC_SEND] >> (16-VOL_BITS);
clos->lsend *= clos->velvol;
clos->lsend >>= 16;
clos->rsend = clos->lsend;
clos->lsend *= 64 - (c->ctl[ACC_PAN] >> 10);
clos->rsend *= 64 + (c->ctl[ACC_PAN] >> 10);
clos->lsend >>= 6;
clos->rsend >>= 6;
}
/* Initialize envelope to PES_START. */
static inline void __env_start(audio_patch_t *p, audio_channel_t *c,
audio_voice_t *v)
{
patch_closure_t *clos = &v->closure;
clos->env_state = PES_START;
clos->env_next = aev_timer;
}
static inline void __env_stop(audio_patch_t *p, audio_channel_t *c,
audio_voice_t *v)
{
patch_closure_t *clos = &v->closure;
if(p->param[APP_ENV_T3] >= 0)
return;
EGDBG(log_printf(D3LOG, "stop!\n");)
switch (clos->env_state)
{
case PES_START:
case PES_START2:
case PES_DELAY:
case PES_ATTACK:
case PES_HOLD:
case PES_DECAY:
clos->queued_stop = 1;
if(p->param[APP_ENV_SKIP])
{
clos->env_next = aev_timer;
clos->env_state = PES_SUSTAIN;
}
break;
case PES_SUSTAIN: /* No interactive sustain! */
case PES_RELEASE: /* Release already active... */
case PES_DEAD:
break;
case PES_WAIT:
clos->env_next = aev_timer;
clos->env_state = PES_SUSTAIN;
break;
}
}
/*
FIXME: Support longer times than 65535 timestamp units.
FIXME: When that's done, this will need fixing as well...
*/
#define S2S(x) ((((x)>>2) * a_settings.samplerate) >> 14)
static inline void __env_run(audio_patch_t *p, audio_channel_t *c,
audio_voice_t *v, unsigned frames)
{
patch_closure_t *clos = &v->closure;
int lvol, rvol, lsend, rsend;
aev_timestamp_t t;
while((t = (unsigned)((clos->env_next - aev_timer) &
AEV_TIMESTAMP_MASK)) < frames)
{
int duration;
int target = 0;
aev_timestamp_t timestamp = (aev_timestamp_t)
(clos->env_next - aev_timer) &
AEV_TIMESTAMP_MASK;
EGDBG(log_printf(D3LOG, "%d: ", clos->env_next);)
switch(clos->env_state)
{
case PES_START:
duration = 0;
target = p->param[APP_ENV_L0];
clos->env_state = PES_START2;
clos->queued_stop = 0;
break;
case PES_START2:
duration = S2S(p->param[APP_ENV_DELAY]);
target = p->param[APP_ENV_L0];
clos->env_state = PES_DELAY;
clos->queued_stop = 0;
break;
case PES_DELAY:
duration = S2S(p->param[APP_ENV_T1]);
target = p->param[APP_ENV_L1];
clos->env_state = PES_ATTACK;
break;
case PES_ATTACK:
duration = S2S(p->param[APP_ENV_HOLD]);
target = p->param[APP_ENV_L1];
clos->env_state = PES_HOLD;
break;
case PES_HOLD:
duration = S2S(p->param[APP_ENV_T2]);
target = p->param[APP_ENV_L2];
clos->env_state = PES_DECAY;
break;
case PES_DECAY:
if(p->param[APP_ENV_T3] >= 0)
{
/* Timed sustain or no sustain */
target = p->param[APP_ENV_L2];
duration = S2S(p->param[APP_ENV_T3]);
clos->env_state = PES_SUSTAIN;
break;
}
else if(!clos->queued_stop)
{
/* Interactive sustain */
target = p->param[APP_ENV_L2];
duration = 30000;
clos->env_state = PES_WAIT;
break;
}
/* Fall though if interactive and stopped! */
case PES_SUSTAIN:
duration = S2S(p->param[APP_ENV_T4]);
target = 0;
clos->env_state = PES_RELEASE;
break;
case PES_WAIT:
/* Interactive sustain */
target = p->param[APP_ENV_L2];
duration = 30000;
break;
case PES_RELEASE:
(void)aev_send0(&v->port, timestamp, VE_STOP);
--c->playing;
case PES_DEAD:
clos->env_next += 30000;
default: /* Warning supressor */
continue;
}
EGDBG(log_printf(D3LOG, " (duration: %d, target: %d)\n", duration, target);)
lvol = (clos->lvol >> 1) * target >> 15;
rvol = (clos->rvol >> 1) * target >> 15;
lsend = (clos->lsend >> 1) * target >> 15;
rsend = (clos->rsend >> 1) * target >> 15;
(void)aev_sendi2(&v->port, t, VE_IRAMP,
VIC_LVOL, lvol, duration);
(void)aev_sendi2(&v->port, t, VE_IRAMP,
VIC_RVOL, rvol, duration);
if(v->use_double)
{
(void)aev_sendi2(&v->port, t, VE_IRAMP,
VIC_LSEND, lsend, duration);
(void)aev_sendi2(&v->port, t, VE_IRAMP,
VIC_RSEND, rsend, duration);
}
clos->env_next += duration;
}
}
static void _env_run_all(audio_patch_t *p, audio_channel_t *c, unsigned frames)
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
__env_run(p, c, v, frames);
v = chan_get_next_voice(v);
}
}
static inline void __start_voice(audio_patch_t *p, audio_channel_t *c,
audio_voice_t *v, int wave, int pitch, int velocity)
{
patch_closure_t *clos = &v->closure;
/* Apply random pitch */
pitch = __calc_randpitch(p, pitch);
/* Store base pitch and vel to closure */
clos->pitch = pitch;
clos->velocity = velocity;
/* Transform to get actual pitch */
pitch += c->ctl[ACC_PITCH];
pitch -= 60<<16;
/* Start voice! */
(void)aev_sendi1(&v->port, 0, VE_SET, VC_PRIM_BUS,
c->ctl[ACC_PRIM_BUS]);
(void)aev_sendi1(&v->port, 0, VE_SET, VC_SEND_BUS,
c->ctl[ACC_SEND_BUS]);
(void)aev_send1(&v->port, 0, VE_START, wave);
(void)aev_sendi1(&v->port, 0, VE_SET, VC_PITCH, pitch);
/* Calculate "base volume" for envelope */
clos->velvol = c->ctl[ACC_VOLUME] >> (16-VOL_BITS);
clos->velvol *= velocity;
clos->velvol >>= 16;
__env_control_recalc(p, c, v);
/* Initialize and start envelope */
__env_start(p, c, v);
++c->playing;
}
/*----------------------------------------------------------
Default patches for polyphonic wave playback
----------------------------------------------------------*/
static inline void poly_start(audio_patch_t *p, audio_channel_t *c, int tag,
int pitch, int velocity)
{
int voice;
int wave = __get_wave(p);
if(wave < 0)
return;
voice = voice_alloc(c);
if(voice < 0)
return;
__start_voice(p, c, voicetab + voice, wave, pitch, velocity);
voicetab[voice].tag = tag;
}
static inline void poly_stop(audio_patch_t *p, audio_channel_t *c, int tag, int velocity)
{
if(tag < 0)
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
__env_stop(p, c, v);
v = chan_get_next_voice(v);
}
}
else
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
if(v->tag == tag)
{
__env_stop(p, c, v);
return;
}
v = chan_get_next_voice(v);
}
}
}
static inline void poly_control(audio_patch_t *p, audio_channel_t *c, int tag,
int ctl, int arg)
{
int mask;
if(AVT_ALL == tag)
mask = tag = 0;
else
mask = -1;
switch(ctl)
{
case ACC_GROUP:
case ACC_PRIORITY:
case ACC_PATCH:
break;
case ACC_PRIM_BUS:
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
if((v->tag & mask) == tag)
v->c[VC_PRIM_BUS] = arg;
v = chan_get_next_voice(v);
}
break;
}
case ACC_SEND_BUS:
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
if((v->tag & mask) == tag)
v->c[VC_SEND_BUS] = arg;
v = chan_get_next_voice(v);
}
break;
}
case ACC_PITCH:
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
int pitch;
if((v->tag & mask) == tag)
{
pitch = c->ctl[ACC_PITCH];
pitch += v->closure.pitch;
pitch -= 60<<16;
(void)aev_sendi1(&v->port, 0, VE_SET,
VC_PITCH, pitch);
}
v = chan_get_next_voice(v);
}
break;
}
case ACC_PAN:
case ACC_VOLUME:
case ACC_SEND:
{
audio_voice_t *v = chan_get_first_voice(c);
while(v)
{
if((v->tag & mask) == tag)
{
/*
TODO: Ramping...
*/
__env_control_recalc(p, c, v);
}
v = chan_get_next_voice(v);
}
break;
}
}
}
static void poly_process(audio_patch_t *p, audio_channel_t *c, unsigned frames)
{
unsigned eframes = frames ? frames : 1;
while(aev_next(&c->port, 0) < eframes)
{
aev_event_t *ev = aev_read(&c->port);
switch(ev->type)
{
case CE_START:
EDBG(log_printf(DLOG, "poly: CE_START %d\n", p->param[APP_WAVE]);)
poly_start(p, c, ev->arg1, ev->arg2, ev->arg3);
break;
case CE_STOP:
EDBG(log_printf(DLOG, "poly: CE_STOP\n");)
poly_stop(p, c, ev->arg1, ev->arg2);
break;
case CE_CONTROL:
EDBG(log_printf(DLOG, "poly: CE_CONTROL %d = %d\n", ev->index, ev->arg2);)
c->ctl[ev->index] = ev->arg2;
if(AVT_FUTURE != ev->arg1)
poly_control(p, c, ev->arg1, ev->index, ev->arg2);
break;
}
aev_free(ev);
}
/*
FIXME: We need to split buffers or something for control
FIXME: events that affect the envelope generators!
*/
if(frames)
_env_run_all(p, c, frames);
}
/*----------------------------------------------------------
Default patches for monophonic wave playback
----------------------------------------------------------*/
static void mono_start(audio_patch_t *p, audio_channel_t *c, int tag,
int pitch, int velocity)
{
int wave = __get_wave(p);
if(wave < 0)
return;
if(voice_alloc(c) < 0)
return;
__start_voice(p, c, c->voices, wave, pitch, velocity);
c->voices->tag = tag;
c->playing = 1;
}
static void mono_stop(audio_patch_t *p, audio_channel_t *c, int tag, int velocity)
{
if(!c->voices)
return;
if(c->voices->channel != c)
return;
__env_stop(p, c, c->voices);
c->playing = 0;
}
static void mono_control(audio_patch_t * p, audio_channel_t *c, int tag,
int ctl, int arg)
{
audio_voice_t *v = c->voices;
if(!v)
return;
if(v->channel != c)
return;
switch (ctl)
{
case ACC_GROUP: /* We don't care. */
case ACC_PRIORITY: /* Only for *new* voices. */
case ACC_PATCH: /* We just got selected. And? :-) */
break;
case ACC_PRIM_BUS:
(void)aev_sendi1(&v->port, 0, VE_SET, VC_PRIM_BUS, arg);
break;
case ACC_SEND_BUS:
(void)aev_sendi1(&v->port, 0, VE_SET, VC_SEND_BUS, arg);
break;
case ACC_PITCH:
(void)aev_sendi1(&v->port, 0, VE_SET, VC_PITCH, arg);
break;
case ACC_PAN:
case ACC_VOLUME:
case ACC_SEND:
__env_control_recalc(p, c, v);
break;
}
}
static void mono_process(audio_patch_t *p, audio_channel_t *c, unsigned frames)
{
unsigned eframes = frames ? frames : 1;
while(aev_next(&c->port, 0) < eframes)
{
aev_event_t *ev = aev_read(&c->port);
switch(ev->type)
{
case CE_START:
EDBG(log_printf(DLOG, "mono: CE_START %d\n", p->param[APP_WAVE]);)
mono_start(p, c, ev->arg1, ev->arg2, ev->arg3);
break;
case CE_STOP:
EDBG(log_printf(DLOG, "mono: CE_STOP\n");)
mono_stop(p, c, ev->arg1, ev->arg2);
break;
case CE_CONTROL:
EDBG(log_printf(DLOG, "mono: CE_CONTROL %d = %d\n", ev->index, ev->arg2);)
c->ctl[ev->index] = ev->arg2;
if(ACC_PATCH == ev->index)
p = patchtab + c->ctl[ACC_PATCH];
if(AVT_FUTURE != ev->arg1)
mono_control(p, c, ev->arg1, ev->index, ev->arg2);
break;
}
aev_free(ev);
}
if(frames)
_env_run_all(p, c, frames);
}
/*----------------------------------------------------------
Default patch for MIDI playback
----------------------------------------------------------*/
static void midi_start(audio_patch_t *p, audio_channel_t *c, int tag,
int pitch, int velocity)
{
int wave = p->param[APP_WAVE];
if((wave < 0) || (wave >= AUDIO_MAX_WAVES))
return;
if(AF_MIDI != wavetab[wave].format)
return; /* Can't play this! */
pitch = __calc_randpitch(p, pitch);
pitch += c->ctl[ACC_PITCH];
pitch -= 60<<16;
if(pitch < 0)
pitch = 0;
else if(pitch > 128<<16)
pitch = 128<<16;
if(sequencer_play(wavetab[wave].data.midi, tag, pitch, velocity) < 0)
return;
++c->playing;
}
static void midi_process(audio_patch_t *p, audio_channel_t *c, unsigned frames)
{
unsigned eframes = frames ? frames : 1;
while(aev_next(&c->port, 0) < eframes)
{
aev_event_t *ev = aev_read(&c->port);
switch(ev->type)
{
case CE_START:
EDBG(log_printf(DLOG, "midi: CE_START %d\n", p->param[APP_WAVE]);)
midi_start(p, c, ev->arg1, ev->arg2, ev->arg3);
break;
case CE_STOP:
EDBG(log_printf(DLOG, "midi: CE_STOP\n");)
/*
FIXME: Handle cid == -1 correctly!
*/
c->playing = 0;
sequencer_stop(ev->arg1);
break;
case CE_CONTROL:
EDBG(log_printf(DLOG, "midi: CE_CONTROL %d = %d\n", ev->index, ev->arg2);)
c->ctl[ev->index] = ev->arg2;
break;
}
aev_free(ev);
}
/* This is where to run the sequencer. */
}
/*----------------------------------------------------------
User EEL Patch Driver
----------------------------------------------------------*/
static void eel_process(audio_patch_t *p, audio_channel_t *c, unsigned frames)
{
unsigned eframes = frames ? frames : 1;
while(aev_next(&c->port, 0) < eframes)
{
aev_event_t *ev = aev_read(&c->port);
switch(ev->type)
{
case CE_START:
EDBG(log_printf(DLOG, "eel: CE_START %d\n", p->param[APP_WAVE]);)
break;
case CE_STOP:
EDBG(log_printf(DLOG, "eel: CE_STOP\n");)
break;
case CE_CONTROL:
EDBG(log_printf(DLOG, "eel: CE_CONTROL %d = %d\n", ev->index, ev->arg2);)
c->ctl[ev->index] = ev->arg2;
break;
}
aev_free(ev);
}
/* Run timer driven EEL code here. */
}
/*----------------------------------------------------------
Dummy Patch Driver
----------------------------------------------------------*/
static void dummy_process(audio_patch_t *p, audio_channel_t *c, unsigned frames)
{
unsigned eframes = frames ? frames : 1;
while(aev_next(&c->port, 0) < eframes)
{
aev_event_t *ev = aev_read(&c->port);
switch(ev->type)
{
case CE_START:
EDBG(log_printf(DLOG, "dummy: CE_START %d\n", p->param[APP_WAVE]);)
break;
case CE_STOP:
EDBG(log_printf(DLOG, "dummy: CE_STOP\n");)
break;
case CE_CONTROL:
EDBG(log_printf(DLOG, "dummy: CE_CONTROL %d = %d\n", ev->index, ev->arg2);)
c->ctl[ev->index] = ev->arg2;
break;
}
aev_free(ev);
}
}
/*----------------------------------------------------------
Patch programming API
----------------------------------------------------------*/
/* Set parameter for a patch */
void audio_patch_param(int pid, int pparam, int value)
{
CHECKINIT
if(pid < 0)
return;
if(pid >= AUDIO_MAX_PATCHES)
return;
if(pparam < 0)
return;
if(pparam > APP_LAST)
return;
if(APP_DRIVER == pparam)
{
switch(value)
{
case PD_MONO:
patchtab[pid].process = mono_process;
break;
case PD_POLY:
patchtab[pid].process = poly_process;
break;
case PD_MIDI:
patchtab[pid].process = midi_process;
break;
case PD_EEL:
patchtab[pid].process = eel_process;
break;
default:
patchtab[pid].process = dummy_process;
log_printf(ELOG, "a_patch.c: Illegal patch"
" driver selected!\n");
break;
}
}
patchtab[pid].param[pparam] = value;
}
/*----------------------------------------------------------
Global init
----------------------------------------------------------*/
void audio_patch_open(void)
{
int i;
if(_is_open)
return;
memset(patchtab, 0, sizeof(patchtab));
for(i = 0; i < AUDIO_MAX_PATCHES; ++i)
{
/*
patchtab[i].param[APP_DRIVER] = PD_NONE;
patchtab[i].process = dummy_process;
*/
/*
FIXME: This hack is just until the EEL<->patch binding is sorted out.
*/
patchtab[i].param[APP_DRIVER] = PD_POLY;
patchtab[i].process = poly_process;
patchtab[i].param[APP_WAVE] = i;
patchtab[i].param[APP_ENV_L0] = 1<<16;
patchtab[i].param[APP_ENV_L1] = 1<<16;
patchtab[i].param[APP_ENV_L2] = 1<<16;
patchtab[i].param[APP_ENV_T3] = -1;
patchtab[i].param[APP_ENV_T4] = (1<<16) / 100;
patchtab[i].param[APP_ENV2VOL] = 1<<16;
}
_is_open = 1;
}
void audio_patch_close(void)
{
if(!_is_open)
return;
memset(patchtab, 0, sizeof(patchtab));
_is_open = 0;
}