mirror of
https://github.com/torvalds/linux.git
synced 2024-11-26 14:12:06 +00:00
98ea612dd1
RPN with 127:127 is treated as a Null RPN, just to reset the parameters, and it's not translated to MIDI2. Although the current code can work as is in most cases, better to implement the RPN reset explicitly for Null message. Link: https://patch.msgid.link/20240731130528.12600-6-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de>
1287 lines
38 KiB
C
1287 lines
38 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* ALSA sequencer event conversion between UMP and legacy clients
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/string.h>
|
|
#include <sound/core.h>
|
|
#include <sound/ump.h>
|
|
#include <sound/ump_msg.h>
|
|
#include "seq_ump_convert.h"
|
|
|
|
/*
|
|
* Upgrade / downgrade value bits
|
|
*/
|
|
static u8 downscale_32_to_7bit(u32 src)
|
|
{
|
|
return src >> 25;
|
|
}
|
|
|
|
static u16 downscale_32_to_14bit(u32 src)
|
|
{
|
|
return src >> 18;
|
|
}
|
|
|
|
static u8 downscale_16_to_7bit(u16 src)
|
|
{
|
|
return src >> 9;
|
|
}
|
|
|
|
static u16 upscale_7_to_16bit(u8 src)
|
|
{
|
|
u16 val, repeat;
|
|
|
|
val = (u16)src << 9;
|
|
if (src <= 0x40)
|
|
return val;
|
|
repeat = src & 0x3f;
|
|
return val | (repeat << 3) | (repeat >> 3);
|
|
}
|
|
|
|
static u32 upscale_7_to_32bit(u8 src)
|
|
{
|
|
u32 val, repeat;
|
|
|
|
val = src << 25;
|
|
if (src <= 0x40)
|
|
return val;
|
|
repeat = src & 0x3f;
|
|
return val | (repeat << 19) | (repeat << 13) |
|
|
(repeat << 7) | (repeat << 1) | (repeat >> 5);
|
|
}
|
|
|
|
static u32 upscale_14_to_32bit(u16 src)
|
|
{
|
|
u32 val, repeat;
|
|
|
|
val = src << 18;
|
|
if (src <= 0x2000)
|
|
return val;
|
|
repeat = src & 0x1fff;
|
|
return val | (repeat << 5) | (repeat >> 8);
|
|
}
|
|
|
|
static unsigned char get_ump_group(struct snd_seq_client_port *port)
|
|
{
|
|
return port->ump_group ? (port->ump_group - 1) : 0;
|
|
}
|
|
|
|
/* create a UMP header */
|
|
#define make_raw_ump(port, type) \
|
|
ump_compose(type, get_ump_group(port), 0, 0)
|
|
|
|
/*
|
|
* UMP -> MIDI1 sequencer event
|
|
*/
|
|
|
|
/* MIDI 1.0 CVM */
|
|
|
|
/* encode note event */
|
|
static void ump_midi1_to_note_ev(const union snd_ump_midi1_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
ev->data.note.channel = val->note.channel;
|
|
ev->data.note.note = val->note.note;
|
|
ev->data.note.velocity = val->note.velocity;
|
|
}
|
|
|
|
/* encode one parameter controls */
|
|
static void ump_midi1_to_ctrl_ev(const union snd_ump_midi1_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
ev->data.control.channel = val->caf.channel;
|
|
ev->data.control.value = val->caf.data;
|
|
}
|
|
|
|
/* encode pitch wheel change */
|
|
static void ump_midi1_to_pitchbend_ev(const union snd_ump_midi1_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
ev->data.control.channel = val->pb.channel;
|
|
ev->data.control.value = (val->pb.data_msb << 7) | val->pb.data_lsb;
|
|
ev->data.control.value -= 8192;
|
|
}
|
|
|
|
/* encode midi control change */
|
|
static void ump_midi1_to_cc_ev(const union snd_ump_midi1_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
ev->data.control.channel = val->cc.channel;
|
|
ev->data.control.param = val->cc.index;
|
|
ev->data.control.value = val->cc.data;
|
|
}
|
|
|
|
/* Encoding MIDI 1.0 UMP packet */
|
|
struct seq_ump_midi1_to_ev {
|
|
int seq_type;
|
|
void (*encode)(const union snd_ump_midi1_msg *val, struct snd_seq_event *ev);
|
|
};
|
|
|
|
/* Encoders for MIDI1 status 0x80-0xe0 */
|
|
static struct seq_ump_midi1_to_ev midi1_msg_encoders[] = {
|
|
{SNDRV_SEQ_EVENT_NOTEOFF, ump_midi1_to_note_ev}, /* 0x80 */
|
|
{SNDRV_SEQ_EVENT_NOTEON, ump_midi1_to_note_ev}, /* 0x90 */
|
|
{SNDRV_SEQ_EVENT_KEYPRESS, ump_midi1_to_note_ev}, /* 0xa0 */
|
|
{SNDRV_SEQ_EVENT_CONTROLLER, ump_midi1_to_cc_ev}, /* 0xb0 */
|
|
{SNDRV_SEQ_EVENT_PGMCHANGE, ump_midi1_to_ctrl_ev}, /* 0xc0 */
|
|
{SNDRV_SEQ_EVENT_CHANPRESS, ump_midi1_to_ctrl_ev}, /* 0xd0 */
|
|
{SNDRV_SEQ_EVENT_PITCHBEND, ump_midi1_to_pitchbend_ev}, /* 0xe0 */
|
|
};
|
|
|
|
static int cvt_ump_midi1_to_event(const union snd_ump_midi1_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
unsigned char status = val->note.status;
|
|
|
|
if (status < 0x8 || status > 0xe)
|
|
return 0; /* invalid - skip */
|
|
status -= 8;
|
|
ev->type = midi1_msg_encoders[status].seq_type;
|
|
ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
|
|
midi1_msg_encoders[status].encode(val, ev);
|
|
return 1;
|
|
}
|
|
|
|
/* MIDI System message */
|
|
|
|
/* encode one parameter value*/
|
|
static void ump_system_to_one_param_ev(const union snd_ump_midi1_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
ev->data.control.value = val->system.parm1;
|
|
}
|
|
|
|
/* encode song position */
|
|
static void ump_system_to_songpos_ev(const union snd_ump_midi1_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
ev->data.control.value = (val->system.parm2 << 7) | val->system.parm1;
|
|
}
|
|
|
|
/* Encoders for 0xf0 - 0xff */
|
|
static struct seq_ump_midi1_to_ev system_msg_encoders[] = {
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf0 */
|
|
{SNDRV_SEQ_EVENT_QFRAME, ump_system_to_one_param_ev}, /* 0xf1 */
|
|
{SNDRV_SEQ_EVENT_SONGPOS, ump_system_to_songpos_ev}, /* 0xf2 */
|
|
{SNDRV_SEQ_EVENT_SONGSEL, ump_system_to_one_param_ev}, /* 0xf3 */
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf4 */
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf5 */
|
|
{SNDRV_SEQ_EVENT_TUNE_REQUEST, NULL}, /* 0xf6 */
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf7 */
|
|
{SNDRV_SEQ_EVENT_CLOCK, NULL}, /* 0xf8 */
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf9 */
|
|
{SNDRV_SEQ_EVENT_START, NULL}, /* 0xfa */
|
|
{SNDRV_SEQ_EVENT_CONTINUE, NULL}, /* 0xfb */
|
|
{SNDRV_SEQ_EVENT_STOP, NULL}, /* 0xfc */
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xfd */
|
|
{SNDRV_SEQ_EVENT_SENSING, NULL}, /* 0xfe */
|
|
{SNDRV_SEQ_EVENT_RESET, NULL}, /* 0xff */
|
|
};
|
|
|
|
static int cvt_ump_system_to_event(const union snd_ump_midi1_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
unsigned char status = val->system.status;
|
|
|
|
if ((status & 0xf0) != UMP_MIDI1_MSG_REALTIME)
|
|
return 0; /* invalid status - skip */
|
|
status &= 0x0f;
|
|
ev->type = system_msg_encoders[status].seq_type;
|
|
ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
|
|
if (ev->type == SNDRV_SEQ_EVENT_NONE)
|
|
return 0;
|
|
if (system_msg_encoders[status].encode)
|
|
system_msg_encoders[status].encode(val, ev);
|
|
return 1;
|
|
}
|
|
|
|
/* MIDI 2.0 CVM */
|
|
|
|
/* encode note event */
|
|
static int ump_midi2_to_note_ev(const union snd_ump_midi2_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
ev->data.note.channel = val->note.channel;
|
|
ev->data.note.note = val->note.note;
|
|
ev->data.note.velocity = downscale_16_to_7bit(val->note.velocity);
|
|
/* correct note-on velocity 0 to 1;
|
|
* it's no longer equivalent as not-off for MIDI 2.0
|
|
*/
|
|
if (ev->type == SNDRV_SEQ_EVENT_NOTEON &&
|
|
!ev->data.note.velocity)
|
|
ev->data.note.velocity = 1;
|
|
return 1;
|
|
}
|
|
|
|
/* encode pitch wheel change */
|
|
static int ump_midi2_to_pitchbend_ev(const union snd_ump_midi2_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
ev->data.control.channel = val->pb.channel;
|
|
ev->data.control.value = downscale_32_to_14bit(val->pb.data);
|
|
ev->data.control.value -= 8192;
|
|
return 1;
|
|
}
|
|
|
|
/* encode midi control change */
|
|
static int ump_midi2_to_cc_ev(const union snd_ump_midi2_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
ev->data.control.channel = val->cc.channel;
|
|
ev->data.control.param = val->cc.index;
|
|
ev->data.control.value = downscale_32_to_7bit(val->cc.data);
|
|
return 1;
|
|
}
|
|
|
|
/* encode midi program change */
|
|
static int ump_midi2_to_pgm_ev(const union snd_ump_midi2_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
int size = 1;
|
|
|
|
ev->data.control.channel = val->pg.channel;
|
|
if (val->pg.bank_valid) {
|
|
ev->type = SNDRV_SEQ_EVENT_CONTROL14;
|
|
ev->data.control.param = UMP_CC_BANK_SELECT;
|
|
ev->data.control.value = (val->pg.bank_msb << 7) | val->pg.bank_lsb;
|
|
ev[1] = ev[0];
|
|
ev++;
|
|
ev->type = SNDRV_SEQ_EVENT_PGMCHANGE;
|
|
size = 2;
|
|
}
|
|
ev->data.control.value = val->pg.program;
|
|
return size;
|
|
}
|
|
|
|
/* encode one parameter controls */
|
|
static int ump_midi2_to_ctrl_ev(const union snd_ump_midi2_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
ev->data.control.channel = val->caf.channel;
|
|
ev->data.control.value = downscale_32_to_7bit(val->caf.data);
|
|
return 1;
|
|
}
|
|
|
|
/* encode RPN/NRPN */
|
|
static int ump_midi2_to_rpn_ev(const union snd_ump_midi2_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
ev->data.control.channel = val->rpn.channel;
|
|
ev->data.control.param = (val->rpn.bank << 7) | val->rpn.index;
|
|
ev->data.control.value = downscale_32_to_14bit(val->rpn.data);
|
|
return 1;
|
|
}
|
|
|
|
/* Encoding MIDI 2.0 UMP Packet */
|
|
struct seq_ump_midi2_to_ev {
|
|
int seq_type;
|
|
int (*encode)(const union snd_ump_midi2_msg *val, struct snd_seq_event *ev);
|
|
};
|
|
|
|
/* Encoders for MIDI2 status 0x00-0xf0 */
|
|
static struct seq_ump_midi2_to_ev midi2_msg_encoders[] = {
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x00 */
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x10 */
|
|
{SNDRV_SEQ_EVENT_REGPARAM, ump_midi2_to_rpn_ev}, /* 0x20 */
|
|
{SNDRV_SEQ_EVENT_NONREGPARAM, ump_midi2_to_rpn_ev}, /* 0x30 */
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x40 */
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x50 */
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x60 */
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x70 */
|
|
{SNDRV_SEQ_EVENT_NOTEOFF, ump_midi2_to_note_ev}, /* 0x80 */
|
|
{SNDRV_SEQ_EVENT_NOTEON, ump_midi2_to_note_ev}, /* 0x90 */
|
|
{SNDRV_SEQ_EVENT_KEYPRESS, ump_midi2_to_note_ev}, /* 0xa0 */
|
|
{SNDRV_SEQ_EVENT_CONTROLLER, ump_midi2_to_cc_ev}, /* 0xb0 */
|
|
{SNDRV_SEQ_EVENT_PGMCHANGE, ump_midi2_to_pgm_ev}, /* 0xc0 */
|
|
{SNDRV_SEQ_EVENT_CHANPRESS, ump_midi2_to_ctrl_ev}, /* 0xd0 */
|
|
{SNDRV_SEQ_EVENT_PITCHBEND, ump_midi2_to_pitchbend_ev}, /* 0xe0 */
|
|
{SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf0 */
|
|
};
|
|
|
|
static int cvt_ump_midi2_to_event(const union snd_ump_midi2_msg *val,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
unsigned char status = val->note.status;
|
|
|
|
ev->type = midi2_msg_encoders[status].seq_type;
|
|
if (ev->type == SNDRV_SEQ_EVENT_NONE)
|
|
return 0; /* skip */
|
|
ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
|
|
return midi2_msg_encoders[status].encode(val, ev);
|
|
}
|
|
|
|
/* parse and compose for a sysex var-length event */
|
|
static int cvt_ump_sysex7_to_event(const u32 *data, unsigned char *buf,
|
|
struct snd_seq_event *ev)
|
|
{
|
|
unsigned char status;
|
|
unsigned char bytes;
|
|
u32 val;
|
|
int size = 0;
|
|
|
|
val = data[0];
|
|
status = ump_sysex_message_status(val);
|
|
bytes = ump_sysex_message_length(val);
|
|
if (bytes > 6)
|
|
return 0; // skip
|
|
|
|
if (status == UMP_SYSEX_STATUS_SINGLE ||
|
|
status == UMP_SYSEX_STATUS_START) {
|
|
buf[0] = UMP_MIDI1_MSG_SYSEX_START;
|
|
size = 1;
|
|
}
|
|
|
|
if (bytes > 0)
|
|
buf[size++] = (val >> 8) & 0x7f;
|
|
if (bytes > 1)
|
|
buf[size++] = val & 0x7f;
|
|
val = data[1];
|
|
if (bytes > 2)
|
|
buf[size++] = (val >> 24) & 0x7f;
|
|
if (bytes > 3)
|
|
buf[size++] = (val >> 16) & 0x7f;
|
|
if (bytes > 4)
|
|
buf[size++] = (val >> 8) & 0x7f;
|
|
if (bytes > 5)
|
|
buf[size++] = val & 0x7f;
|
|
|
|
if (status == UMP_SYSEX_STATUS_SINGLE ||
|
|
status == UMP_SYSEX_STATUS_END)
|
|
buf[size++] = UMP_MIDI1_MSG_SYSEX_END;
|
|
|
|
ev->type = SNDRV_SEQ_EVENT_SYSEX;
|
|
ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
|
|
ev->data.ext.len = size;
|
|
ev->data.ext.ptr = buf;
|
|
return 1;
|
|
}
|
|
|
|
/* convert UMP packet from MIDI 1.0 to MIDI 2.0 and deliver it */
|
|
static int cvt_ump_midi1_to_midi2(struct snd_seq_client *dest,
|
|
struct snd_seq_client_port *dest_port,
|
|
struct snd_seq_event *__event,
|
|
int atomic, int hop)
|
|
{
|
|
struct snd_seq_ump_event *event = (struct snd_seq_ump_event *)__event;
|
|
struct snd_seq_ump_event ev_cvt;
|
|
const union snd_ump_midi1_msg *midi1 = (const union snd_ump_midi1_msg *)event->ump;
|
|
union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)ev_cvt.ump;
|
|
struct ump_cvt_to_ump_bank *cc;
|
|
|
|
ev_cvt = *event;
|
|
memset(&ev_cvt.ump, 0, sizeof(ev_cvt.ump));
|
|
|
|
midi2->note.type = UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE;
|
|
midi2->note.group = midi1->note.group;
|
|
midi2->note.status = midi1->note.status;
|
|
midi2->note.channel = midi1->note.channel;
|
|
switch (midi1->note.status) {
|
|
case UMP_MSG_STATUS_NOTE_ON:
|
|
case UMP_MSG_STATUS_NOTE_OFF:
|
|
midi2->note.note = midi1->note.note;
|
|
midi2->note.velocity = upscale_7_to_16bit(midi1->note.velocity);
|
|
break;
|
|
case UMP_MSG_STATUS_POLY_PRESSURE:
|
|
midi2->paf.note = midi1->paf.note;
|
|
midi2->paf.data = upscale_7_to_32bit(midi1->paf.data);
|
|
break;
|
|
case UMP_MSG_STATUS_CC:
|
|
cc = &dest_port->midi2_bank[midi1->note.channel];
|
|
switch (midi1->cc.index) {
|
|
case UMP_CC_BANK_SELECT:
|
|
cc->bank_set = 1;
|
|
cc->cc_bank_msb = midi1->cc.data;
|
|
return 0; // skip
|
|
case UMP_CC_BANK_SELECT_LSB:
|
|
cc->bank_set = 1;
|
|
cc->cc_bank_lsb = midi1->cc.data;
|
|
return 0; // skip
|
|
}
|
|
midi2->cc.index = midi1->cc.index;
|
|
midi2->cc.data = upscale_7_to_32bit(midi1->cc.data);
|
|
break;
|
|
case UMP_MSG_STATUS_PROGRAM:
|
|
midi2->pg.program = midi1->pg.program;
|
|
cc = &dest_port->midi2_bank[midi1->note.channel];
|
|
if (cc->bank_set) {
|
|
midi2->pg.bank_valid = 1;
|
|
midi2->pg.bank_msb = cc->cc_bank_msb;
|
|
midi2->pg.bank_lsb = cc->cc_bank_lsb;
|
|
cc->bank_set = 0;
|
|
}
|
|
break;
|
|
case UMP_MSG_STATUS_CHANNEL_PRESSURE:
|
|
midi2->caf.data = upscale_7_to_32bit(midi1->caf.data);
|
|
break;
|
|
case UMP_MSG_STATUS_PITCH_BEND:
|
|
midi2->pb.data = upscale_14_to_32bit((midi1->pb.data_msb << 7) |
|
|
midi1->pb.data_lsb);
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return __snd_seq_deliver_single_event(dest, dest_port,
|
|
(struct snd_seq_event *)&ev_cvt,
|
|
atomic, hop);
|
|
}
|
|
|
|
/* convert UMP packet from MIDI 2.0 to MIDI 1.0 and deliver it */
|
|
static int cvt_ump_midi2_to_midi1(struct snd_seq_client *dest,
|
|
struct snd_seq_client_port *dest_port,
|
|
struct snd_seq_event *__event,
|
|
int atomic, int hop)
|
|
{
|
|
struct snd_seq_ump_event *event = (struct snd_seq_ump_event *)__event;
|
|
struct snd_seq_ump_event ev_cvt;
|
|
union snd_ump_midi1_msg *midi1 = (union snd_ump_midi1_msg *)ev_cvt.ump;
|
|
const union snd_ump_midi2_msg *midi2 = (const union snd_ump_midi2_msg *)event->ump;
|
|
int err;
|
|
u16 v;
|
|
|
|
ev_cvt = *event;
|
|
memset(&ev_cvt.ump, 0, sizeof(ev_cvt.ump));
|
|
|
|
midi1->note.type = UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE;
|
|
midi1->note.group = midi2->note.group;
|
|
midi1->note.status = midi2->note.status;
|
|
midi1->note.channel = midi2->note.channel;
|
|
switch (midi2->note.status) {
|
|
case UMP_MSG_STATUS_NOTE_ON:
|
|
case UMP_MSG_STATUS_NOTE_OFF:
|
|
midi1->note.note = midi2->note.note;
|
|
midi1->note.velocity = downscale_16_to_7bit(midi2->note.velocity);
|
|
break;
|
|
case UMP_MSG_STATUS_POLY_PRESSURE:
|
|
midi1->paf.note = midi2->paf.note;
|
|
midi1->paf.data = downscale_32_to_7bit(midi2->paf.data);
|
|
break;
|
|
case UMP_MSG_STATUS_CC:
|
|
midi1->cc.index = midi2->cc.index;
|
|
midi1->cc.data = downscale_32_to_7bit(midi2->cc.data);
|
|
break;
|
|
case UMP_MSG_STATUS_PROGRAM:
|
|
if (midi2->pg.bank_valid) {
|
|
midi1->cc.status = UMP_MSG_STATUS_CC;
|
|
midi1->cc.index = UMP_CC_BANK_SELECT;
|
|
midi1->cc.data = midi2->pg.bank_msb;
|
|
err = __snd_seq_deliver_single_event(dest, dest_port,
|
|
(struct snd_seq_event *)&ev_cvt,
|
|
atomic, hop);
|
|
if (err < 0)
|
|
return err;
|
|
midi1->cc.index = UMP_CC_BANK_SELECT_LSB;
|
|
midi1->cc.data = midi2->pg.bank_lsb;
|
|
err = __snd_seq_deliver_single_event(dest, dest_port,
|
|
(struct snd_seq_event *)&ev_cvt,
|
|
atomic, hop);
|
|
if (err < 0)
|
|
return err;
|
|
midi1->note.status = midi2->note.status;
|
|
}
|
|
midi1->pg.program = midi2->pg.program;
|
|
break;
|
|
case UMP_MSG_STATUS_CHANNEL_PRESSURE:
|
|
midi1->caf.data = downscale_32_to_7bit(midi2->caf.data);
|
|
break;
|
|
case UMP_MSG_STATUS_PITCH_BEND:
|
|
v = downscale_32_to_14bit(midi2->pb.data);
|
|
midi1->pb.data_msb = v >> 7;
|
|
midi1->pb.data_lsb = v & 0x7f;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return __snd_seq_deliver_single_event(dest, dest_port,
|
|
(struct snd_seq_event *)&ev_cvt,
|
|
atomic, hop);
|
|
}
|
|
|
|
/* convert UMP to a legacy ALSA seq event and deliver it */
|
|
static int cvt_ump_to_any(struct snd_seq_client *dest,
|
|
struct snd_seq_client_port *dest_port,
|
|
struct snd_seq_event *event,
|
|
unsigned char type,
|
|
int atomic, int hop)
|
|
{
|
|
struct snd_seq_event ev_cvt[2]; /* up to two events */
|
|
struct snd_seq_ump_event *ump_ev = (struct snd_seq_ump_event *)event;
|
|
/* use the second event as a temp buffer for saving stack usage */
|
|
unsigned char *sysex_buf = (unsigned char *)(ev_cvt + 1);
|
|
unsigned char flags = event->flags & ~SNDRV_SEQ_EVENT_UMP;
|
|
int i, len, err;
|
|
|
|
ev_cvt[0] = ev_cvt[1] = *event;
|
|
ev_cvt[0].flags = flags;
|
|
ev_cvt[1].flags = flags;
|
|
switch (type) {
|
|
case UMP_MSG_TYPE_SYSTEM:
|
|
len = cvt_ump_system_to_event((union snd_ump_midi1_msg *)ump_ev->ump,
|
|
ev_cvt);
|
|
break;
|
|
case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE:
|
|
len = cvt_ump_midi1_to_event((union snd_ump_midi1_msg *)ump_ev->ump,
|
|
ev_cvt);
|
|
break;
|
|
case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE:
|
|
len = cvt_ump_midi2_to_event((union snd_ump_midi2_msg *)ump_ev->ump,
|
|
ev_cvt);
|
|
break;
|
|
case UMP_MSG_TYPE_DATA:
|
|
len = cvt_ump_sysex7_to_event(ump_ev->ump, sysex_buf, ev_cvt);
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < len; i++) {
|
|
err = __snd_seq_deliver_single_event(dest, dest_port,
|
|
&ev_cvt[i], atomic, hop);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Replace UMP group field with the destination and deliver */
|
|
static int deliver_with_group_convert(struct snd_seq_client *dest,
|
|
struct snd_seq_client_port *dest_port,
|
|
struct snd_seq_ump_event *ump_ev,
|
|
int atomic, int hop)
|
|
{
|
|
struct snd_seq_ump_event ev = *ump_ev;
|
|
|
|
/* rewrite the group to the destination port */
|
|
ev.ump[0] &= ~(0xfU << 24);
|
|
/* fill with the new group; the dest_port->ump_group field is 1-based */
|
|
ev.ump[0] |= ((dest_port->ump_group - 1) << 24);
|
|
|
|
return __snd_seq_deliver_single_event(dest, dest_port,
|
|
(struct snd_seq_event *)&ev,
|
|
atomic, hop);
|
|
}
|
|
|
|
/* apply the UMP event filter; return true to skip the event */
|
|
static bool ump_event_filtered(struct snd_seq_client *dest,
|
|
const struct snd_seq_ump_event *ev)
|
|
{
|
|
unsigned char group;
|
|
|
|
group = ump_message_group(ev->ump[0]);
|
|
if (ump_is_groupless_msg(ump_message_type(ev->ump[0])))
|
|
return dest->group_filter & (1U << 0);
|
|
/* check the bitmap for 1-based group number */
|
|
return dest->group_filter & (1U << (group + 1));
|
|
}
|
|
|
|
/* Convert from UMP packet and deliver */
|
|
int snd_seq_deliver_from_ump(struct snd_seq_client *source,
|
|
struct snd_seq_client *dest,
|
|
struct snd_seq_client_port *dest_port,
|
|
struct snd_seq_event *event,
|
|
int atomic, int hop)
|
|
{
|
|
struct snd_seq_ump_event *ump_ev = (struct snd_seq_ump_event *)event;
|
|
unsigned char type;
|
|
|
|
if (snd_seq_ev_is_variable(event))
|
|
return 0; // skip, no variable event for UMP, so far
|
|
if (ump_event_filtered(dest, ump_ev))
|
|
return 0; // skip if group filter is set and matching
|
|
type = ump_message_type(ump_ev->ump[0]);
|
|
|
|
if (snd_seq_client_is_ump(dest)) {
|
|
if (snd_seq_client_is_midi2(dest) &&
|
|
type == UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE)
|
|
return cvt_ump_midi1_to_midi2(dest, dest_port,
|
|
event, atomic, hop);
|
|
else if (!snd_seq_client_is_midi2(dest) &&
|
|
type == UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE)
|
|
return cvt_ump_midi2_to_midi1(dest, dest_port,
|
|
event, atomic, hop);
|
|
/* non-EP port and different group is set? */
|
|
if (dest_port->ump_group &&
|
|
!ump_is_groupless_msg(type) &&
|
|
ump_message_group(*ump_ev->ump) + 1 != dest_port->ump_group)
|
|
return deliver_with_group_convert(dest, dest_port,
|
|
ump_ev, atomic, hop);
|
|
/* copy as-is */
|
|
return __snd_seq_deliver_single_event(dest, dest_port,
|
|
event, atomic, hop);
|
|
}
|
|
|
|
return cvt_ump_to_any(dest, dest_port, event, type, atomic, hop);
|
|
}
|
|
|
|
/*
|
|
* MIDI1 sequencer event -> UMP conversion
|
|
*/
|
|
|
|
/* Conversion to UMP MIDI 1.0 */
|
|
|
|
/* convert note on/off event to MIDI 1.0 UMP */
|
|
static int note_ev_to_ump_midi1(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi1_msg *data,
|
|
unsigned char status)
|
|
{
|
|
if (!event->data.note.velocity)
|
|
status = UMP_MSG_STATUS_NOTE_OFF;
|
|
data->note.status = status;
|
|
data->note.channel = event->data.note.channel & 0x0f;
|
|
data->note.velocity = event->data.note.velocity & 0x7f;
|
|
data->note.note = event->data.note.note & 0x7f;
|
|
return 1;
|
|
}
|
|
|
|
/* convert CC event to MIDI 1.0 UMP */
|
|
static int cc_ev_to_ump_midi1(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi1_msg *data,
|
|
unsigned char status)
|
|
{
|
|
data->cc.status = status;
|
|
data->cc.channel = event->data.control.channel & 0x0f;
|
|
data->cc.index = event->data.control.param;
|
|
data->cc.data = event->data.control.value;
|
|
return 1;
|
|
}
|
|
|
|
/* convert one-parameter control event to MIDI 1.0 UMP */
|
|
static int ctrl_ev_to_ump_midi1(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi1_msg *data,
|
|
unsigned char status)
|
|
{
|
|
data->caf.status = status;
|
|
data->caf.channel = event->data.control.channel & 0x0f;
|
|
data->caf.data = event->data.control.value & 0x7f;
|
|
return 1;
|
|
}
|
|
|
|
/* convert pitchbend event to MIDI 1.0 UMP */
|
|
static int pitchbend_ev_to_ump_midi1(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi1_msg *data,
|
|
unsigned char status)
|
|
{
|
|
int val = event->data.control.value + 8192;
|
|
|
|
val = clamp(val, 0, 0x3fff);
|
|
data->pb.status = status;
|
|
data->pb.channel = event->data.control.channel & 0x0f;
|
|
data->pb.data_msb = (val >> 7) & 0x7f;
|
|
data->pb.data_lsb = val & 0x7f;
|
|
return 1;
|
|
}
|
|
|
|
/* convert 14bit control event to MIDI 1.0 UMP; split to two events */
|
|
static int ctrl14_ev_to_ump_midi1(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi1_msg *data,
|
|
unsigned char status)
|
|
{
|
|
data->cc.status = UMP_MSG_STATUS_CC;
|
|
data->cc.channel = event->data.control.channel & 0x0f;
|
|
data->cc.index = event->data.control.param & 0x7f;
|
|
if (event->data.control.param < 0x20) {
|
|
data->cc.data = (event->data.control.value >> 7) & 0x7f;
|
|
data[1] = data[0];
|
|
data[1].cc.index = event->data.control.param | 0x20;
|
|
data[1].cc.data = event->data.control.value & 0x7f;
|
|
return 2;
|
|
}
|
|
|
|
data->cc.data = event->data.control.value & 0x7f;
|
|
return 1;
|
|
}
|
|
|
|
/* convert RPN/NRPN event to MIDI 1.0 UMP; split to four events */
|
|
static int rpn_ev_to_ump_midi1(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi1_msg *data,
|
|
unsigned char status)
|
|
{
|
|
bool is_rpn = (status == UMP_MSG_STATUS_RPN);
|
|
|
|
data->cc.status = UMP_MSG_STATUS_CC;
|
|
data->cc.channel = event->data.control.channel & 0x0f;
|
|
data[1] = data[2] = data[3] = data[0];
|
|
|
|
data[0].cc.index = is_rpn ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB;
|
|
data[0].cc.data = (event->data.control.param >> 7) & 0x7f;
|
|
data[1].cc.index = is_rpn ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB;
|
|
data[1].cc.data = event->data.control.param & 0x7f;
|
|
data[2].cc.index = UMP_CC_DATA;
|
|
data[2].cc.data = (event->data.control.value >> 7) & 0x7f;
|
|
data[3].cc.index = UMP_CC_DATA_LSB;
|
|
data[3].cc.data = event->data.control.value & 0x7f;
|
|
return 4;
|
|
}
|
|
|
|
/* convert system / RT message to UMP */
|
|
static int system_ev_to_ump_midi1(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi1_msg *data,
|
|
unsigned char status)
|
|
{
|
|
data->system.type = UMP_MSG_TYPE_SYSTEM; // override
|
|
data->system.status = status;
|
|
return 1;
|
|
}
|
|
|
|
/* convert system / RT message with 1 parameter to UMP */
|
|
static int system_1p_ev_to_ump_midi1(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi1_msg *data,
|
|
unsigned char status)
|
|
{
|
|
data->system.type = UMP_MSG_TYPE_SYSTEM; // override
|
|
data->system.status = status;
|
|
data->system.parm1 = event->data.control.value & 0x7f;
|
|
return 1;
|
|
}
|
|
|
|
/* convert system / RT message with two parameters to UMP */
|
|
static int system_2p_ev_to_ump_midi1(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi1_msg *data,
|
|
unsigned char status)
|
|
{
|
|
data->system.type = UMP_MSG_TYPE_SYSTEM; // override
|
|
data->system.status = status;
|
|
data->system.parm1 = event->data.control.value & 0x7f;
|
|
data->system.parm2 = (event->data.control.value >> 7) & 0x7f;
|
|
return 1;
|
|
}
|
|
|
|
/* Conversion to UMP MIDI 2.0 */
|
|
|
|
/* convert note on/off event to MIDI 2.0 UMP */
|
|
static int note_ev_to_ump_midi2(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char status)
|
|
{
|
|
if (!event->data.note.velocity)
|
|
status = UMP_MSG_STATUS_NOTE_OFF;
|
|
data->note.status = status;
|
|
data->note.channel = event->data.note.channel & 0x0f;
|
|
data->note.note = event->data.note.note & 0x7f;
|
|
data->note.velocity = upscale_7_to_16bit(event->data.note.velocity & 0x7f);
|
|
return 1;
|
|
}
|
|
|
|
/* convert PAF event to MIDI 2.0 UMP */
|
|
static int paf_ev_to_ump_midi2(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char status)
|
|
{
|
|
data->paf.status = status;
|
|
data->paf.channel = event->data.note.channel & 0x0f;
|
|
data->paf.note = event->data.note.note & 0x7f;
|
|
data->paf.data = upscale_7_to_32bit(event->data.note.velocity & 0x7f);
|
|
return 1;
|
|
}
|
|
|
|
static void reset_rpn(struct ump_cvt_to_ump_bank *cc)
|
|
{
|
|
cc->rpn_set = 0;
|
|
cc->nrpn_set = 0;
|
|
cc->cc_rpn_msb = cc->cc_rpn_lsb = 0;
|
|
cc->cc_data_msb = cc->cc_data_lsb = 0;
|
|
cc->cc_data_msb_set = cc->cc_data_lsb_set = 0;
|
|
}
|
|
|
|
/* set up the MIDI2 RPN/NRPN packet data from the parsed info */
|
|
static int fill_rpn(struct ump_cvt_to_ump_bank *cc,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char channel,
|
|
bool flush)
|
|
{
|
|
if (!(cc->cc_data_lsb_set || cc->cc_data_msb_set))
|
|
return 0; // skip
|
|
/* when not flushing, wait for complete data set */
|
|
if (!flush && (!cc->cc_data_lsb_set || !cc->cc_data_msb_set))
|
|
return 0; // skip
|
|
|
|
if (cc->rpn_set) {
|
|
data->rpn.status = UMP_MSG_STATUS_RPN;
|
|
data->rpn.bank = cc->cc_rpn_msb;
|
|
data->rpn.index = cc->cc_rpn_lsb;
|
|
} else if (cc->nrpn_set) {
|
|
data->rpn.status = UMP_MSG_STATUS_NRPN;
|
|
data->rpn.bank = cc->cc_nrpn_msb;
|
|
data->rpn.index = cc->cc_nrpn_lsb;
|
|
} else {
|
|
return 0; // skip
|
|
}
|
|
|
|
data->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) |
|
|
cc->cc_data_lsb);
|
|
data->rpn.channel = channel;
|
|
|
|
reset_rpn(cc);
|
|
return 1;
|
|
}
|
|
|
|
/* convert CC event to MIDI 2.0 UMP */
|
|
static int cc_ev_to_ump_midi2(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char status)
|
|
{
|
|
unsigned char channel = event->data.control.channel & 0x0f;
|
|
unsigned char index = event->data.control.param & 0x7f;
|
|
unsigned char val = event->data.control.value & 0x7f;
|
|
struct ump_cvt_to_ump_bank *cc = &dest_port->midi2_bank[channel];
|
|
int ret;
|
|
|
|
/* process special CC's (bank/rpn/nrpn) */
|
|
switch (index) {
|
|
case UMP_CC_RPN_MSB:
|
|
ret = fill_rpn(cc, data, channel, true);
|
|
cc->rpn_set = 1;
|
|
cc->cc_rpn_msb = val;
|
|
if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
|
|
reset_rpn(cc);
|
|
return ret;
|
|
case UMP_CC_RPN_LSB:
|
|
ret = fill_rpn(cc, data, channel, true);
|
|
cc->rpn_set = 1;
|
|
cc->cc_rpn_lsb = val;
|
|
if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
|
|
reset_rpn(cc);
|
|
return ret;
|
|
case UMP_CC_NRPN_MSB:
|
|
ret = fill_rpn(cc, data, channel, true);
|
|
cc->nrpn_set = 1;
|
|
cc->cc_nrpn_msb = val;
|
|
return ret;
|
|
case UMP_CC_NRPN_LSB:
|
|
ret = fill_rpn(cc, data, channel, true);
|
|
cc->nrpn_set = 1;
|
|
cc->cc_nrpn_lsb = val;
|
|
return ret;
|
|
case UMP_CC_DATA:
|
|
cc->cc_data_msb_set = 1;
|
|
cc->cc_data_msb = val;
|
|
return fill_rpn(cc, data, channel, false);
|
|
case UMP_CC_BANK_SELECT:
|
|
cc->bank_set = 1;
|
|
cc->cc_bank_msb = val;
|
|
return 0; // skip
|
|
case UMP_CC_BANK_SELECT_LSB:
|
|
cc->bank_set = 1;
|
|
cc->cc_bank_lsb = val;
|
|
return 0; // skip
|
|
case UMP_CC_DATA_LSB:
|
|
cc->cc_data_lsb_set = 1;
|
|
cc->cc_data_lsb = val;
|
|
return fill_rpn(cc, data, channel, false);
|
|
}
|
|
|
|
data->cc.status = status;
|
|
data->cc.channel = channel;
|
|
data->cc.index = index;
|
|
data->cc.data = upscale_7_to_32bit(event->data.control.value & 0x7f);
|
|
return 1;
|
|
}
|
|
|
|
/* convert one-parameter control event to MIDI 2.0 UMP */
|
|
static int ctrl_ev_to_ump_midi2(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char status)
|
|
{
|
|
data->caf.status = status;
|
|
data->caf.channel = event->data.control.channel & 0x0f;
|
|
data->caf.data = upscale_7_to_32bit(event->data.control.value & 0x7f);
|
|
return 1;
|
|
}
|
|
|
|
/* convert program change event to MIDI 2.0 UMP */
|
|
static int pgm_ev_to_ump_midi2(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char status)
|
|
{
|
|
unsigned char channel = event->data.control.channel & 0x0f;
|
|
struct ump_cvt_to_ump_bank *cc = &dest_port->midi2_bank[channel];
|
|
|
|
data->pg.status = status;
|
|
data->pg.channel = channel;
|
|
data->pg.program = event->data.control.value & 0x7f;
|
|
if (cc->bank_set) {
|
|
data->pg.bank_valid = 1;
|
|
data->pg.bank_msb = cc->cc_bank_msb;
|
|
data->pg.bank_lsb = cc->cc_bank_lsb;
|
|
cc->bank_set = 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* convert pitchbend event to MIDI 2.0 UMP */
|
|
static int pitchbend_ev_to_ump_midi2(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char status)
|
|
{
|
|
int val = event->data.control.value + 8192;
|
|
|
|
val = clamp(val, 0, 0x3fff);
|
|
data->pb.status = status;
|
|
data->pb.channel = event->data.control.channel & 0x0f;
|
|
data->pb.data = upscale_14_to_32bit(val);
|
|
return 1;
|
|
}
|
|
|
|
/* convert 14bit control event to MIDI 2.0 UMP; split to two events */
|
|
static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char status)
|
|
{
|
|
unsigned char channel = event->data.control.channel & 0x0f;
|
|
unsigned char index = event->data.control.param & 0x7f;
|
|
struct ump_cvt_to_ump_bank *cc = &dest_port->midi2_bank[channel];
|
|
unsigned char msb, lsb;
|
|
int ret;
|
|
|
|
msb = (event->data.control.value >> 7) & 0x7f;
|
|
lsb = event->data.control.value & 0x7f;
|
|
/* process special CC's (bank/rpn/nrpn) */
|
|
switch (index) {
|
|
case UMP_CC_BANK_SELECT:
|
|
cc->cc_bank_msb = msb;
|
|
fallthrough;
|
|
case UMP_CC_BANK_SELECT_LSB:
|
|
cc->bank_set = 1;
|
|
cc->cc_bank_lsb = lsb;
|
|
return 0; // skip
|
|
case UMP_CC_RPN_MSB:
|
|
case UMP_CC_RPN_LSB:
|
|
ret = fill_rpn(cc, data, channel, true);
|
|
cc->cc_rpn_msb = msb;
|
|
cc->cc_rpn_lsb = lsb;
|
|
cc->rpn_set = 1;
|
|
if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
|
|
reset_rpn(cc);
|
|
return ret;
|
|
case UMP_CC_NRPN_MSB:
|
|
case UMP_CC_NRPN_LSB:
|
|
ret = fill_rpn(cc, data, channel, true);
|
|
cc->cc_nrpn_msb = msb;
|
|
cc->nrpn_set = 1;
|
|
cc->cc_nrpn_lsb = lsb;
|
|
return ret;
|
|
case UMP_CC_DATA:
|
|
case UMP_CC_DATA_LSB:
|
|
cc->cc_data_msb_set = cc->cc_data_lsb_set = 1;
|
|
cc->cc_data_msb = msb;
|
|
cc->cc_data_lsb = lsb;
|
|
return fill_rpn(cc, data, channel, false);
|
|
}
|
|
|
|
data->cc.status = UMP_MSG_STATUS_CC;
|
|
data->cc.channel = channel;
|
|
data->cc.index = index;
|
|
if (event->data.control.param < 0x20) {
|
|
data->cc.data = upscale_7_to_32bit(msb);
|
|
data[1] = data[0];
|
|
data[1].cc.index = event->data.control.param | 0x20;
|
|
data[1].cc.data = upscale_7_to_32bit(lsb);
|
|
return 2;
|
|
}
|
|
|
|
data->cc.data = upscale_7_to_32bit(lsb);
|
|
return 1;
|
|
}
|
|
|
|
/* convert RPN/NRPN event to MIDI 2.0 UMP */
|
|
static int rpn_ev_to_ump_midi2(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char status)
|
|
{
|
|
data->rpn.status = status;
|
|
data->rpn.channel = event->data.control.channel;
|
|
data->rpn.bank = (event->data.control.param >> 7) & 0x7f;
|
|
data->rpn.index = event->data.control.param & 0x7f;
|
|
data->rpn.data = upscale_14_to_32bit(event->data.control.value & 0x3fff);
|
|
return 1;
|
|
}
|
|
|
|
/* convert system / RT message to UMP */
|
|
static int system_ev_to_ump_midi2(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char status)
|
|
{
|
|
return system_ev_to_ump_midi1(event, dest_port,
|
|
(union snd_ump_midi1_msg *)data,
|
|
status);
|
|
}
|
|
|
|
/* convert system / RT message with 1 parameter to UMP */
|
|
static int system_1p_ev_to_ump_midi2(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char status)
|
|
{
|
|
return system_1p_ev_to_ump_midi1(event, dest_port,
|
|
(union snd_ump_midi1_msg *)data,
|
|
status);
|
|
}
|
|
|
|
/* convert system / RT message with two parameters to UMP */
|
|
static int system_2p_ev_to_ump_midi2(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char status)
|
|
{
|
|
return system_2p_ev_to_ump_midi1(event, dest_port,
|
|
(union snd_ump_midi1_msg *)data,
|
|
status);
|
|
}
|
|
|
|
struct seq_ev_to_ump {
|
|
int seq_type;
|
|
unsigned char status;
|
|
int (*midi1_encode)(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi1_msg *data,
|
|
unsigned char status);
|
|
int (*midi2_encode)(const struct snd_seq_event *event,
|
|
struct snd_seq_client_port *dest_port,
|
|
union snd_ump_midi2_msg *data,
|
|
unsigned char status);
|
|
};
|
|
|
|
static const struct seq_ev_to_ump seq_ev_ump_encoders[] = {
|
|
{ SNDRV_SEQ_EVENT_NOTEON, UMP_MSG_STATUS_NOTE_ON,
|
|
note_ev_to_ump_midi1, note_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_NOTEOFF, UMP_MSG_STATUS_NOTE_OFF,
|
|
note_ev_to_ump_midi1, note_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_KEYPRESS, UMP_MSG_STATUS_POLY_PRESSURE,
|
|
note_ev_to_ump_midi1, paf_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_CONTROLLER, UMP_MSG_STATUS_CC,
|
|
cc_ev_to_ump_midi1, cc_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_PGMCHANGE, UMP_MSG_STATUS_PROGRAM,
|
|
ctrl_ev_to_ump_midi1, pgm_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_CHANPRESS, UMP_MSG_STATUS_CHANNEL_PRESSURE,
|
|
ctrl_ev_to_ump_midi1, ctrl_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_PITCHBEND, UMP_MSG_STATUS_PITCH_BEND,
|
|
pitchbend_ev_to_ump_midi1, pitchbend_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_CONTROL14, 0,
|
|
ctrl14_ev_to_ump_midi1, ctrl14_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_NONREGPARAM, UMP_MSG_STATUS_NRPN,
|
|
rpn_ev_to_ump_midi1, rpn_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_REGPARAM, UMP_MSG_STATUS_RPN,
|
|
rpn_ev_to_ump_midi1, rpn_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_QFRAME, UMP_SYSTEM_STATUS_MIDI_TIME_CODE,
|
|
system_1p_ev_to_ump_midi1, system_1p_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_SONGPOS, UMP_SYSTEM_STATUS_SONG_POSITION,
|
|
system_2p_ev_to_ump_midi1, system_2p_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_SONGSEL, UMP_SYSTEM_STATUS_SONG_SELECT,
|
|
system_1p_ev_to_ump_midi1, system_1p_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_TUNE_REQUEST, UMP_SYSTEM_STATUS_TUNE_REQUEST,
|
|
system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_CLOCK, UMP_SYSTEM_STATUS_TIMING_CLOCK,
|
|
system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_START, UMP_SYSTEM_STATUS_START,
|
|
system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_CONTINUE, UMP_SYSTEM_STATUS_CONTINUE,
|
|
system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_STOP, UMP_SYSTEM_STATUS_STOP,
|
|
system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_SENSING, UMP_SYSTEM_STATUS_ACTIVE_SENSING,
|
|
system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
|
|
{ SNDRV_SEQ_EVENT_RESET, UMP_SYSTEM_STATUS_RESET,
|
|
system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
|
|
};
|
|
|
|
static const struct seq_ev_to_ump *find_ump_encoder(int type)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(seq_ev_ump_encoders); i++)
|
|
if (seq_ev_ump_encoders[i].seq_type == type)
|
|
return &seq_ev_ump_encoders[i];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void setup_ump_event(struct snd_seq_ump_event *dest,
|
|
const struct snd_seq_event *src)
|
|
{
|
|
memcpy(dest, src, sizeof(*src));
|
|
dest->type = 0;
|
|
dest->flags |= SNDRV_SEQ_EVENT_UMP;
|
|
dest->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
|
|
memset(dest->ump, 0, sizeof(dest->ump));
|
|
}
|
|
|
|
/* Convert ALSA seq event to UMP MIDI 1.0 and deliver it */
|
|
static int cvt_to_ump_midi1(struct snd_seq_client *dest,
|
|
struct snd_seq_client_port *dest_port,
|
|
struct snd_seq_event *event,
|
|
int atomic, int hop)
|
|
{
|
|
const struct seq_ev_to_ump *encoder;
|
|
struct snd_seq_ump_event ev_cvt;
|
|
union snd_ump_midi1_msg data[4];
|
|
int i, n, err;
|
|
|
|
encoder = find_ump_encoder(event->type);
|
|
if (!encoder)
|
|
return __snd_seq_deliver_single_event(dest, dest_port,
|
|
event, atomic, hop);
|
|
|
|
data->raw = make_raw_ump(dest_port, UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE);
|
|
n = encoder->midi1_encode(event, dest_port, data, encoder->status);
|
|
if (!n)
|
|
return 0;
|
|
|
|
setup_ump_event(&ev_cvt, event);
|
|
for (i = 0; i < n; i++) {
|
|
ev_cvt.ump[0] = data[i].raw;
|
|
err = __snd_seq_deliver_single_event(dest, dest_port,
|
|
(struct snd_seq_event *)&ev_cvt,
|
|
atomic, hop);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Convert ALSA seq event to UMP MIDI 2.0 and deliver it */
|
|
static int cvt_to_ump_midi2(struct snd_seq_client *dest,
|
|
struct snd_seq_client_port *dest_port,
|
|
struct snd_seq_event *event,
|
|
int atomic, int hop)
|
|
{
|
|
const struct seq_ev_to_ump *encoder;
|
|
struct snd_seq_ump_event ev_cvt;
|
|
union snd_ump_midi2_msg data[2];
|
|
int i, n, err;
|
|
|
|
encoder = find_ump_encoder(event->type);
|
|
if (!encoder)
|
|
return __snd_seq_deliver_single_event(dest, dest_port,
|
|
event, atomic, hop);
|
|
|
|
data->raw[0] = make_raw_ump(dest_port, UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE);
|
|
data->raw[1] = 0;
|
|
n = encoder->midi2_encode(event, dest_port, data, encoder->status);
|
|
if (!n)
|
|
return 0;
|
|
|
|
setup_ump_event(&ev_cvt, event);
|
|
for (i = 0; i < n; i++) {
|
|
memcpy(ev_cvt.ump, &data[i], sizeof(data[i]));
|
|
err = __snd_seq_deliver_single_event(dest, dest_port,
|
|
(struct snd_seq_event *)&ev_cvt,
|
|
atomic, hop);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Fill up a sysex7 UMP from the byte stream */
|
|
static void fill_sysex7_ump(struct snd_seq_client_port *dest_port,
|
|
u32 *val, u8 status, u8 *buf, int len)
|
|
{
|
|
memset(val, 0, 8);
|
|
memcpy((u8 *)val + 2, buf, len);
|
|
#ifdef __LITTLE_ENDIAN
|
|
swab32_array(val, 2);
|
|
#endif
|
|
val[0] |= ump_compose(UMP_MSG_TYPE_DATA, get_ump_group(dest_port),
|
|
status, len);
|
|
}
|
|
|
|
/* Convert sysex var event to UMP sysex7 packets and deliver them */
|
|
static int cvt_sysex_to_ump(struct snd_seq_client *dest,
|
|
struct snd_seq_client_port *dest_port,
|
|
struct snd_seq_event *event,
|
|
int atomic, int hop)
|
|
{
|
|
struct snd_seq_ump_event ev_cvt;
|
|
unsigned char status;
|
|
u8 buf[8], *xbuf;
|
|
int offset = 0;
|
|
int len, err;
|
|
bool finished = false;
|
|
|
|
if (!snd_seq_ev_is_variable(event))
|
|
return 0;
|
|
|
|
setup_ump_event(&ev_cvt, event);
|
|
while (!finished) {
|
|
len = snd_seq_expand_var_event_at(event, sizeof(buf), buf, offset);
|
|
if (len <= 0)
|
|
break;
|
|
if (WARN_ON(len > sizeof(buf)))
|
|
break;
|
|
|
|
xbuf = buf;
|
|
status = UMP_SYSEX_STATUS_CONTINUE;
|
|
/* truncate the sysex start-marker */
|
|
if (*xbuf == UMP_MIDI1_MSG_SYSEX_START) {
|
|
status = UMP_SYSEX_STATUS_START;
|
|
len--;
|
|
offset++;
|
|
xbuf++;
|
|
}
|
|
|
|
/* if the last of this packet or the 1st byte of the next packet
|
|
* is the end-marker, finish the transfer with this packet
|
|
*/
|
|
if (len > 0 && len < 8 &&
|
|
xbuf[len - 1] == UMP_MIDI1_MSG_SYSEX_END) {
|
|
if (status == UMP_SYSEX_STATUS_START)
|
|
status = UMP_SYSEX_STATUS_SINGLE;
|
|
else
|
|
status = UMP_SYSEX_STATUS_END;
|
|
len--;
|
|
finished = true;
|
|
}
|
|
|
|
len = min(len, 6);
|
|
fill_sysex7_ump(dest_port, ev_cvt.ump, status, xbuf, len);
|
|
err = __snd_seq_deliver_single_event(dest, dest_port,
|
|
(struct snd_seq_event *)&ev_cvt,
|
|
atomic, hop);
|
|
if (err < 0)
|
|
return err;
|
|
offset += len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Convert to UMP packet and deliver */
|
|
int snd_seq_deliver_to_ump(struct snd_seq_client *source,
|
|
struct snd_seq_client *dest,
|
|
struct snd_seq_client_port *dest_port,
|
|
struct snd_seq_event *event,
|
|
int atomic, int hop)
|
|
{
|
|
if (dest->group_filter & (1U << dest_port->ump_group))
|
|
return 0; /* group filtered - skip the event */
|
|
if (event->type == SNDRV_SEQ_EVENT_SYSEX)
|
|
return cvt_sysex_to_ump(dest, dest_port, event, atomic, hop);
|
|
else if (snd_seq_client_is_midi2(dest))
|
|
return cvt_to_ump_midi2(dest, dest_port, event, atomic, hop);
|
|
else
|
|
return cvt_to_ump_midi1(dest, dest_port, event, atomic, hop);
|
|
}
|