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>
352 lines
7.8 KiB
C
352 lines
7.8 KiB
C
/*(LGPL)
|
|
---------------------------------------------------------------------------
|
|
a_midicon.c - Engine MIDI Control Implementation
|
|
---------------------------------------------------------------------------
|
|
* Copyright (C) 2001, 2002, 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 "kobolog.h"
|
|
#include "a_midicon.h"
|
|
#include "a_control.h"
|
|
#include "a_math.h"
|
|
#include "a_sequencer.h"
|
|
|
|
|
|
static int first_channel = 0;
|
|
|
|
#define MIDI_MAP_CH(x) ((x)+first_channel)
|
|
#define CH_MAP_MIDI(x) ((x)-first_channel)
|
|
|
|
static float fs = 44100;
|
|
|
|
/* Exponential table which maps [0, 127] ==> [0, 65535] */
|
|
static unsigned short explut[128];
|
|
|
|
typedef struct midi_channel_t
|
|
{
|
|
/* Volume & velocity */
|
|
int volume; /* Linear [0, 65535] */
|
|
int pan; /* Linear [-65536, 65535] */
|
|
int velocity[128]; /* For each key, linear [0, 65535] */
|
|
|
|
/* Send control */
|
|
int send; /* Linear [0, 65535+] */
|
|
|
|
/* Pitch */
|
|
int bend_depth;
|
|
int bend;
|
|
int f_pitch;
|
|
/* Key Management */
|
|
int last;
|
|
signed char next[128]; /* Index-linked list :-) */
|
|
signed char prev[128]; /* Index-linked list :-) */
|
|
|
|
/* Voice Management */
|
|
int poly;
|
|
int tag[128]; /* To keep track of individual notes */
|
|
|
|
/* Modulation */
|
|
unsigned mod;
|
|
|
|
/* Bus Control */
|
|
unsigned ifx_slot;
|
|
} midi_channel_t;
|
|
|
|
static midi_channel_t m[16];
|
|
|
|
static void __init(void)
|
|
{
|
|
int i, j;
|
|
for(i = 0; i < 16; ++i)
|
|
{
|
|
m[i].volume = 100*512;
|
|
for(j = 0; j < 128; ++j)
|
|
m[i].velocity[j] = 0;
|
|
m[i].bend_depth = 12;
|
|
m[i].bend = 0;
|
|
m[i].f_pitch = 0;
|
|
m[i].last = -1;
|
|
memset(m[i].next, -1, sizeof(m[i].next));
|
|
memset(m[i].prev, -1, sizeof(m[i].prev));
|
|
m[i].poly = 1;
|
|
memset(m[i].tag, -1, sizeof(m[i].tag));
|
|
m[i].mod = 0;
|
|
}
|
|
for(i = 0; i < 128; ++i)
|
|
{
|
|
float linear = (float)(i+1) / 128;
|
|
explut[i] = (unsigned short)((0.25*linear + 0.5*linear*linear +
|
|
0.25*linear*linear*linear) * 65535.0);
|
|
}
|
|
#if 0
|
|
for(i = 0; i < 128; ++i)
|
|
{
|
|
int j;
|
|
for(j=0; j < (explut[i]>>9); ++j)
|
|
log_printf(D3LOG, "#");
|
|
log_printf(D3LOG, "\n");
|
|
}
|
|
for(i = 0; i < 128; ++i)
|
|
log_printf(D3LOG, "%d ==> %f\n", i, (float)explut[i]/65536.0);
|
|
#endif
|
|
}
|
|
|
|
|
|
static inline void __poly(unsigned ch, unsigned on)
|
|
{
|
|
m[ch].poly = (int)on;
|
|
/*
|
|
FIXME: Maybe reimplement this somehow...?
|
|
FIXME: acc_set(MIDI_MAP_CH(ch), ACC_DETACH, on ? ADM_POLY : ADM_MONO);
|
|
*/
|
|
}
|
|
|
|
|
|
static inline void __press(unsigned ch, unsigned key)
|
|
{
|
|
m[ch].next[key] = -1;
|
|
m[ch].prev[key] = m[ch].last;
|
|
m[ch].next[m[ch].last] = (char)key;
|
|
m[ch].last = (char)key;
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns the new last key (-1 if none),
|
|
* or -2 if there's no change.
|
|
*/
|
|
static inline int __release(unsigned ch, unsigned key)
|
|
{
|
|
if(m[ch].prev[key] != -1)
|
|
m[ch].next[m[ch].prev[key]] = m[ch].next[key];
|
|
if(m[ch].next[key] != -1)
|
|
{
|
|
m[ch].prev[m[ch].next[key]] = m[ch].prev[key];
|
|
m[ch].next[key] = m[ch].prev[key] = -1;
|
|
return -2;
|
|
}
|
|
else
|
|
{
|
|
m[ch].last = m[ch].prev[key];
|
|
m[ch].next[key] = m[ch].prev[key] = -1;
|
|
return m[ch].last;
|
|
}
|
|
}
|
|
|
|
|
|
/* Returns 1 if the specified key on the specified channel is down. */
|
|
static inline int __is_down(unsigned ch, int key)
|
|
{
|
|
return (m[ch].tag[key] != -1);
|
|
}
|
|
|
|
|
|
static void midicon_note_off(unsigned ch, unsigned pitch, unsigned vel)
|
|
{
|
|
__release(ch, pitch);
|
|
(void)ce_stop(channeltab + MIDI_MAP_CH(ch), 0,
|
|
(int)pitch, (int)explut[vel]);
|
|
}
|
|
|
|
|
|
static void midicon_note_on(unsigned ch, unsigned pitch, unsigned vel)
|
|
{
|
|
if(!vel)
|
|
{
|
|
midicon_note_off(ch, pitch, 64);
|
|
return;
|
|
}
|
|
m[ch].velocity[pitch] = (int)explut[vel];
|
|
(void)ce_start(channeltab + MIDI_MAP_CH(ch), 0, (int)pitch,
|
|
(int)pitch << 16, m[ch].velocity[pitch]);
|
|
__press(ch, pitch);
|
|
}
|
|
|
|
|
|
static void midicon_pitch_bend(unsigned ch, int bend)
|
|
{
|
|
m[ch].bend = bend << 3;
|
|
m[ch].bend *= m[ch].bend_depth;
|
|
(void)ce_control(channeltab + MIDI_MAP_CH(ch), 0,
|
|
-1, ACC_PITCH, m[ch].bend + (60<<16));
|
|
}
|
|
|
|
|
|
|
|
static void midicon_control_change(unsigned ch, unsigned ctrl, unsigned amt)
|
|
{
|
|
audio_channel_t *c = channeltab + MIDI_MAP_CH(ch);
|
|
switch (ctrl)
|
|
{
|
|
case 1:
|
|
m[ch].mod = amt;
|
|
break;
|
|
case 7:
|
|
m[ch].volume = (int)explut[amt];
|
|
(void)ce_control(c, 0, -1, ACC_VOLUME, m[ch].volume);
|
|
break;
|
|
case 10:
|
|
m[ch].pan = (amt << 10) - 65536;
|
|
(void)ce_control(c, 0, -1, ACC_PAN, m[ch].pan);
|
|
break;
|
|
|
|
/* Bus Control */
|
|
case 39: /* Select IFX Slot */
|
|
m[ch].ifx_slot = (unsigned)amt;
|
|
break;
|
|
|
|
case 40: /* IFX Type */
|
|
if(ch >= AUDIO_MAX_BUSSES)
|
|
break;
|
|
if(m[ch].ifx_slot > AUDIO_MAX_INSERTS-1)
|
|
break;
|
|
bus_ctl_set(ch, m[ch].ifx_slot, ABC_FX_TYPE, (int)amt);
|
|
break;
|
|
case 41: /* IFX P1 (Time 1) */
|
|
case 42: /* IFX P2 (Time 2) */
|
|
case 43: /* IFX P3 (Depth 1) */
|
|
case 44: /* IFX P4 (Depth 2)*/
|
|
case 45: /* IFX P5 (Rate) */
|
|
case 46: /* IFX P6 (Mode/Special) */
|
|
if(ch >= AUDIO_MAX_BUSSES)
|
|
break;
|
|
if(m[ch].ifx_slot > AUDIO_MAX_INSERTS-1)
|
|
break;
|
|
if(ctrl != 46)
|
|
amt = (unsigned)(explut[amt]) << 1;
|
|
bus_ctl_set(ch, m[ch].ifx_slot, ctrl - 41 + ABC_FX_PARAM_1,
|
|
(int)amt);
|
|
break;
|
|
|
|
case 47: /* IFX Wet -> master */
|
|
if(ch >= AUDIO_MAX_BUSSES)
|
|
break;
|
|
if(m[ch].ifx_slot > AUDIO_MAX_INSERTS-1)
|
|
break;
|
|
bus_ctl_set(ch, m[ch].ifx_slot, ABC_SEND_MASTER,
|
|
explut[amt] << 1);
|
|
break;
|
|
|
|
case 48: /* Sends to other busses */
|
|
case 49:
|
|
case 50:
|
|
case 51:
|
|
case 52:
|
|
case 53:
|
|
case 54:
|
|
case 55:
|
|
if(ch >= AUDIO_MAX_BUSSES)
|
|
break;
|
|
if(m[ch].ifx_slot > AUDIO_MAX_INSERTS-1)
|
|
break;
|
|
bus_ctl_set(ch, m[ch].ifx_slot, ABC_SEND_BUS_0 + ctrl-48,
|
|
explut[amt] << 1);
|
|
break;
|
|
|
|
case 88: /* Primary output bus */
|
|
(void)ce_control(c, 0, -1, ACC_PRIM_BUS, amt-1);
|
|
break;
|
|
case 89: /* Send bus */
|
|
(void)ce_control(c, 0, -1, ACC_SEND_BUS, amt-1);
|
|
break;
|
|
case 91: /* Send Level ("Reverb") */
|
|
m[ch].send = (int)explut[amt] << 1;
|
|
(void)ce_control(c, 0, -1, ACC_SEND, m[ch].send);
|
|
break;
|
|
|
|
case 120: /* All Sound Off */
|
|
case 123: /* All Notes Off */
|
|
if(m[ch].poly)
|
|
{
|
|
int note = m[ch].last;
|
|
while(note >= 0)
|
|
{
|
|
int n = note;
|
|
note = m[ch].prev[note];
|
|
midicon_note_off(ch, (unsigned)n, 64);
|
|
}
|
|
}
|
|
else
|
|
midicon_note_off(ch, (unsigned)m[ch].last, 64);
|
|
break;
|
|
case 121: /* Reset All Controllers */
|
|
__poly(ch, 1);
|
|
m[ch].mod = 0;
|
|
m[ch].volume = 100*512;
|
|
m[ch].send = 0;
|
|
m[ch].pan = 0;
|
|
(void)ce_control(c, 0, -1, ACC_VOLUME, m[ch].volume);
|
|
(void)ce_control(c, 0, -1, ACC_SEND, m[ch].send);
|
|
(void)ce_control(c, 0, -1, ACC_PAN, m[ch].pan);
|
|
midicon_pitch_bend(ch, 0);
|
|
break;
|
|
case 126: /* Mono */
|
|
__poly(ch, 0);
|
|
break;
|
|
case 127: /* Poly */
|
|
__poly(ch, 1);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void midicon_program_change(unsigned ch, unsigned prog)
|
|
{
|
|
(void)ce_control(channeltab + MIDI_MAP_CH(ch), 0, -2, ACC_PATCH, (int)prog);
|
|
}
|
|
|
|
|
|
midisock_t midicon_midisock = {
|
|
midicon_note_off,
|
|
midicon_note_on,
|
|
NULL, /* poly_pressure */
|
|
midicon_control_change,
|
|
midicon_program_change,
|
|
NULL, /* channel_pressure */
|
|
midicon_pitch_bend
|
|
};
|
|
|
|
|
|
int midicon_open(float framerate, int first_ch)
|
|
{
|
|
unsigned i;
|
|
aev_client("midicon_open()");
|
|
fs = framerate;
|
|
first_channel = first_ch;
|
|
__init();
|
|
for(i = 0; i < 16; ++i)
|
|
__poly(i, 1);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void midicon_close(void)
|
|
{
|
|
aev_client("midicon_close()");
|
|
}
|
|
|
|
|
|
void midicon_process(unsigned frames)
|
|
{
|
|
aev_client("midicon_process()");
|
|
/*
|
|
* This is where MIDI clock and stuff goes.
|
|
*/
|
|
}
|