Commit 0b5288f5 authored by Takashi Iwai's avatar Takashi Iwai

ALSA: ump: Add legacy raw MIDI support

This patch extends the UMP core code to support the legacy MIDI 1.0
rawmidi devices.  When the new kconfig CONFIG_SND_UMP_LEGACY_RAWMIDI
is set, the UMP core allows to attach an additional rawmidi device for
each UMP Endpoint.  The rawmidi device contains 16 substreams where
each substream corresponds to a UMP Group belonging to the EP.  The
device reads/writes the legacy MIDI 1.0 byte streams and translates
from/to UMP packets.

The legacy rawmidi devices are exclusive with the UMP rawmidi devices,
hence both of them can't be opened at the same time unless the UMP
rawmidi is opened in APPEND mode.
Reviewed-by: default avatarJaroslav Kysela <perex@perex.cz>
Link: https://lore.kernel.org/r/20230523075358.9672-15-tiwai@suse.deSigned-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 6b41e64a
......@@ -10,6 +10,7 @@
struct snd_ump_endpoint;
struct snd_ump_block;
struct snd_ump_ops;
struct ump_cvt_to_ump;
struct snd_ump_endpoint {
struct snd_rawmidi core; /* raw UMP access */
......@@ -23,6 +24,24 @@ struct snd_ump_endpoint {
void (*private_free)(struct snd_ump_endpoint *ump);
struct list_head block_list; /* list of snd_ump_block objects */
/* intermediate buffer for UMP input */
u32 input_buf[4];
int input_buf_head;
int input_pending;
#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
struct mutex open_mutex;
spinlock_t legacy_locks[2];
struct snd_rawmidi *legacy_rmidi;
struct snd_rawmidi_substream *legacy_substreams[2][SNDRV_UMP_MAX_GROUPS];
/* for legacy output; need to open the actual substream unlike input */
int legacy_out_opens;
struct snd_rawmidi_file legacy_out_rfile;
struct ump_cvt_to_ump *out_cvts;
#endif
};
/* ops filled by UMP drivers */
......@@ -54,6 +73,17 @@ int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk,
int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count);
int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count);
#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
char *id, int device);
#else
static inline int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
char *id, int device)
{
return 0;
}
#endif
/*
* Some definitions for UMP
*/
......
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Universal MIDI Packet (UMP): Message Definitions
*/
#ifndef __SOUND_UMP_MSG_H
#define __SOUND_UMP_MSG_H
/* MIDI 1.0 / 2.0 Status Code (4bit) */
enum {
UMP_MSG_STATUS_PER_NOTE_RCC = 0x0,
UMP_MSG_STATUS_PER_NOTE_ACC = 0x1,
UMP_MSG_STATUS_RPN = 0x2,
UMP_MSG_STATUS_NRPN = 0x3,
UMP_MSG_STATUS_RELATIVE_RPN = 0x4,
UMP_MSG_STATUS_RELATIVE_NRPN = 0x5,
UMP_MSG_STATUS_PER_NOTE_PITCH_BEND = 0x6,
UMP_MSG_STATUS_NOTE_OFF = 0x8,
UMP_MSG_STATUS_NOTE_ON = 0x9,
UMP_MSG_STATUS_POLY_PRESSURE = 0xa,
UMP_MSG_STATUS_CC = 0xb,
UMP_MSG_STATUS_PROGRAM = 0xc,
UMP_MSG_STATUS_CHANNEL_PRESSURE = 0xd,
UMP_MSG_STATUS_PITCH_BEND = 0xe,
UMP_MSG_STATUS_PER_NOTE_MGMT = 0xf,
};
/* MIDI 1.0 Channel Control (7bit) */
enum {
UMP_CC_BANK_SELECT = 0,
UMP_CC_MODULATION = 1,
UMP_CC_BREATH = 2,
UMP_CC_FOOT = 4,
UMP_CC_PORTAMENTO_TIME = 5,
UMP_CC_DATA = 6,
UMP_CC_VOLUME = 7,
UMP_CC_BALANCE = 8,
UMP_CC_PAN = 10,
UMP_CC_EXPRESSION = 11,
UMP_CC_EFFECT_CONTROL_1 = 12,
UMP_CC_EFFECT_CONTROL_2 = 13,
UMP_CC_GP_1 = 16,
UMP_CC_GP_2 = 17,
UMP_CC_GP_3 = 18,
UMP_CC_GP_4 = 19,
UMP_CC_BANK_SELECT_LSB = 32,
UMP_CC_MODULATION_LSB = 33,
UMP_CC_BREATH_LSB = 34,
UMP_CC_FOOT_LSB = 36,
UMP_CC_PORTAMENTO_TIME_LSB = 37,
UMP_CC_DATA_LSB = 38,
UMP_CC_VOLUME_LSB = 39,
UMP_CC_BALANCE_LSB = 40,
UMP_CC_PAN_LSB = 42,
UMP_CC_EXPRESSION_LSB = 43,
UMP_CC_EFFECT1_LSB = 44,
UMP_CC_EFFECT2_LSB = 45,
UMP_CC_GP_1_LSB = 48,
UMP_CC_GP_2_LSB = 49,
UMP_CC_GP_3_LSB = 50,
UMP_CC_GP_4_LSB = 51,
UMP_CC_SUSTAIN = 64,
UMP_CC_PORTAMENTO_SWITCH = 65,
UMP_CC_SOSTENUTO = 66,
UMP_CC_SOFT_PEDAL = 67,
UMP_CC_LEGATO = 68,
UMP_CC_HOLD_2 = 69,
UMP_CC_SOUND_CONTROLLER_1 = 70,
UMP_CC_SOUND_CONTROLLER_2 = 71,
UMP_CC_SOUND_CONTROLLER_3 = 72,
UMP_CC_SOUND_CONTROLLER_4 = 73,
UMP_CC_SOUND_CONTROLLER_5 = 74,
UMP_CC_SOUND_CONTROLLER_6 = 75,
UMP_CC_SOUND_CONTROLLER_7 = 76,
UMP_CC_SOUND_CONTROLLER_8 = 77,
UMP_CC_SOUND_CONTROLLER_9 = 78,
UMP_CC_SOUND_CONTROLLER_10 = 79,
UMP_CC_GP_5 = 80,
UMP_CC_GP_6 = 81,
UMP_CC_GP_7 = 82,
UMP_CC_GP_8 = 83,
UMP_CC_PORTAMENTO_CONTROL = 84,
UMP_CC_EFFECT_1 = 91,
UMP_CC_EFFECT_2 = 92,
UMP_CC_EFFECT_3 = 93,
UMP_CC_EFFECT_4 = 94,
UMP_CC_EFFECT_5 = 95,
UMP_CC_DATA_INC = 96,
UMP_CC_DATA_DEC = 97,
UMP_CC_NRPN_LSB = 98,
UMP_CC_NRPN_MSB = 99,
UMP_CC_RPN_LSB = 100,
UMP_CC_RPN_MSB = 101,
UMP_CC_ALL_SOUND_OFF = 120,
UMP_CC_RESET_ALL = 121,
UMP_CC_LOCAL_CONTROL = 122,
UMP_CC_ALL_NOTES_OFF = 123,
UMP_CC_OMNI_OFF = 124,
UMP_CC_OMNI_ON = 125,
UMP_CC_POLY_OFF = 126,
UMP_CC_POLY_ON = 127,
};
/* MIDI 1.0 / 2.0 System Messages (0xfx) */
enum {
UMP_SYSTEM_STATUS_MIDI_TIME_CODE = 0xf1,
UMP_SYSTEM_STATUS_SONG_POSITION = 0xf2,
UMP_SYSTEM_STATUS_SONG_SELECT = 0xf3,
UMP_SYSTEM_STATUS_TUNE_REQUEST = 0xf6,
UMP_SYSTEM_STATUS_TIMING_CLOCK = 0xf8,
UMP_SYSTEM_STATUS_START = 0xfa,
UMP_SYSTEM_STATUS_CONTINUE = 0xfb,
UMP_SYSTEM_STATUS_STOP = 0xfc,
UMP_SYSTEM_STATUS_ACTIVE_SENSING = 0xfe,
UMP_SYSTEM_STATUS_RESET = 0xff,
};
/* MIDI 1.0 Realtime and SysEx status messages (0xfx) */
enum {
UMP_MIDI1_MSG_REALTIME = 0xf0, /* mask */
UMP_MIDI1_MSG_SYSEX_START = 0xf0,
UMP_MIDI1_MSG_SYSEX_END = 0xf7,
};
/*
* UMP Message Definitions
*/
/* MIDI 1.0 Note Off / Note On (32bit) */
struct snd_ump_midi1_msg_note {
#ifdef __BIG_ENDIAN_BITFIELD
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 note:8;
u32 velocity:8;
#else
u32 velocity:8;
u32 note:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
#endif
} __packed;
/* MIDI 1.0 Poly Pressure (32bit) */
struct snd_ump_midi1_msg_paf {
#ifdef __BIG_ENDIAN_BITFIELD
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 note:8;
u32 data:8;
#else
u32 data:8;
u32 note:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
#endif
} __packed;
/* MIDI 1.0 Control Change (32bit) */
struct snd_ump_midi1_msg_cc {
#ifdef __BIG_ENDIAN_BITFIELD
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 index:8;
u32 data:8;
#else
u32 data:8;
u32 index:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
#endif
} __packed;
/* MIDI 1.0 Program Change (32bit) */
struct snd_ump_midi1_msg_program {
#ifdef __BIG_ENDIAN_BITFIELD
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 program:8;
u32 reserved:8;
#else
#endif
u32 reserved:8;
u32 program:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
} __packed;
/* MIDI 1.0 Channel Pressure (32bit) */
struct snd_ump_midi1_msg_caf {
#ifdef __BIG_ENDIAN_BITFIELD
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 data:8;
u32 reserved:8;
#else
u32 reserved:8;
u32 data:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
#endif
} __packed;
/* MIDI 1.0 Pitch Bend (32bit) */
struct snd_ump_midi1_msg_pitchbend {
#ifdef __BIG_ENDIAN_BITFIELD
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 data_lsb:8;
u32 data_msb:8;
#else
u32 data_msb:8;
u32 data_lsb:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
#endif
} __packed;
/* System Common and Real Time messages (32bit); no channel field */
struct snd_ump_system_msg {
#ifdef __BIG_ENDIAN_BITFIELD
u32 type:4;
u32 group:4;
u32 status:8;
u32 parm1:8;
u32 parm2:8;
#else
u32 parm2:8;
u32 parm1:8;
u32 status:8;
u32 group:4;
u32 type:4;
#endif
} __packed;
/* MIDI 1.0 UMP CVM (32bit) */
union snd_ump_midi1_msg {
struct snd_ump_midi1_msg_note note;
struct snd_ump_midi1_msg_paf paf;
struct snd_ump_midi1_msg_cc cc;
struct snd_ump_midi1_msg_program pg;
struct snd_ump_midi1_msg_caf caf;
struct snd_ump_midi1_msg_pitchbend pb;
struct snd_ump_system_msg system;
u32 raw;
};
/* MIDI 2.0 Note Off / Note On (64bit) */
struct snd_ump_midi2_msg_note {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 note:8;
u32 attribute_type:8;
/* 1 */
u32 velocity:16;
u32 attribute_data:16;
#else
/* 0 */
u32 attribute_type:8;
u32 note:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
/* 1 */
u32 attribute_data:16;
u32 velocity:16;
#endif
} __packed;
/* MIDI 2.0 Poly Pressure (64bit) */
struct snd_ump_midi2_msg_paf {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 note:8;
u32 reserved:8;
/* 1 */
u32 data;
#else
/* 0 */
u32 reserved:8;
u32 note:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
/* 1 */
u32 data;
#endif
} __packed;
/* MIDI 2.0 Per-Note Controller (64bit) */
struct snd_ump_midi2_msg_pernote_cc {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 note:8;
u32 index:8;
/* 1 */
u32 data;
#else
/* 0 */
u32 index:8;
u32 note:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
/* 1 */
u32 data;
#endif
} __packed;
/* MIDI 2.0 Per-Note Management (64bit) */
struct snd_ump_midi2_msg_pernote_mgmt {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 note:8;
u32 flags:8;
/* 1 */
u32 reserved;
#else
/* 0 */
u32 flags:8;
u32 note:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
/* 1 */
u32 reserved;
#endif
} __packed;
/* MIDI 2.0 Control Change (64bit) */
struct snd_ump_midi2_msg_cc {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 index:8;
u32 reserved:8;
/* 1 */
u32 data;
#else
/* 0 */
u32 reserved:8;
u32 index:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
/* 1 */
u32 data;
#endif
} __packed;
/* MIDI 2.0 Registered Controller (RPN) / Assignable Controller (NRPN) (64bit) */
struct snd_ump_midi2_msg_rpn {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 bank:8;
u32 index:8;
/* 1 */
u32 data;
#else
/* 0 */
u32 index:8;
u32 bank:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
/* 1 */
u32 data;
#endif
} __packed;
/* MIDI 2.0 Program Change (64bit) */
struct snd_ump_midi2_msg_program {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 reserved:15;
u32 bank_valid:1;
/* 1 */
u32 program:8;
u32 reserved2:8;
u32 bank_msb:8;
u32 bank_lsb:8;
#else
/* 0 */
u32 bank_valid:1;
u32 reserved:15;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
/* 1 */
u32 bank_lsb:8;
u32 bank_msb:8;
u32 reserved2:8;
u32 program:8;
#endif
} __packed;
/* MIDI 2.0 Channel Pressure (64bit) */
struct snd_ump_midi2_msg_caf {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 reserved:16;
/* 1 */
u32 data;
#else
/* 0 */
u32 reserved:16;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
/* 1 */
u32 data;
#endif
} __packed;
/* MIDI 2.0 Pitch Bend (64bit) */
struct snd_ump_midi2_msg_pitchbend {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 reserved:16;
/* 1 */
u32 data;
#else
/* 0 */
u32 reserved:16;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
/* 1 */
u32 data;
#endif
} __packed;
/* MIDI 2.0 Per-Note Pitch Bend (64bit) */
struct snd_ump_midi2_msg_pernote_pitchbend {
#ifdef __BIG_ENDIAN_BITFIELD
/* 0 */
u32 type:4;
u32 group:4;
u32 status:4;
u32 channel:4;
u32 note:8;
u32 reserved:8;
/* 1 */
u32 data;
#else
/* 0 */
u32 reserved:8;
u32 note:8;
u32 channel:4;
u32 status:4;
u32 group:4;
u32 type:4;
/* 1 */
u32 data;
#endif
} __packed;
/* MIDI 2.0 UMP CVM (64bit) */
union snd_ump_midi2_msg {
struct snd_ump_midi2_msg_note note;
struct snd_ump_midi2_msg_paf paf;
struct snd_ump_midi2_msg_pernote_cc pernote_cc;
struct snd_ump_midi2_msg_pernote_mgmt pernote_mgmt;
struct snd_ump_midi2_msg_cc cc;
struct snd_ump_midi2_msg_rpn rpn;
struct snd_ump_midi2_msg_program pg;
struct snd_ump_midi2_msg_caf caf;
struct snd_ump_midi2_msg_pitchbend pb;
struct snd_ump_midi2_msg_pernote_pitchbend pernote_pb;
u32 raw[2];
};
#endif /* __SOUND_UMP_MSG_H */
......@@ -30,6 +30,15 @@ config SND_UMP
tristate
select SND_RAWMIDI
config SND_UMP_LEGACY_RAWMIDI
bool "Legacy raw MIDI support for UMP streams"
depends on SND_UMP
help
This option enables the legacy raw MIDI support for UMP streams.
When this option is set, an additional rawmidi device for the
legacy MIDI 1.0 byte streams is created for each UMP Endpoint.
The device contains 16 substreams corresponding to UMP groups.
config SND_COMPRESS_OFFLOAD
tristate
......
......@@ -29,6 +29,7 @@ snd-pcm-dmaengine-objs := pcm_dmaengine.o
snd-ctl-led-objs := control_led.o
snd-rawmidi-objs := rawmidi.o
snd-ump-objs := ump.o
snd-ump-$(CONFIG_SND_UMP_LEGACY_RAWMIDI) += ump_convert.o
snd-timer-objs := timer.o
snd-hrtimer-objs := hrtimer.o
snd-rtctimer-objs := rtctimer.o
......
......@@ -11,6 +11,7 @@
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/ump.h>
#include "ump_convert.h"
#define ump_err(ump, fmt, args...) dev_err(&(ump)->core.dev, fmt, ##args)
#define ump_warn(ump, fmt, args...) dev_warn(&(ump)->core.dev, fmt, ##args)
......@@ -29,6 +30,23 @@ static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,
int up);
static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream);
#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
static int process_legacy_output(struct snd_ump_endpoint *ump,
u32 *buffer, int count);
static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
int words);
#else
static inline int process_legacy_output(struct snd_ump_endpoint *ump,
u32 *buffer, int count)
{
return 0;
}
static inline void process_legacy_input(struct snd_ump_endpoint *ump,
const u32 *src, int words)
{
}
#endif
static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = {
.dev_register = snd_ump_dev_register,
.dev_unregister = snd_ump_dev_unregister,
......@@ -65,6 +83,10 @@ static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi)
if (ump->private_free)
ump->private_free(ump);
#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
snd_ump_convert_free(ump);
#endif
}
/**
......@@ -110,6 +132,11 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device,
if (!ump)
return -ENOMEM;
INIT_LIST_HEAD(&ump->block_list);
#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
mutex_init(&ump->open_mutex);
spin_lock_init(&ump->legacy_locks[0]);
spin_lock_init(&ump->legacy_locks[1]);
#endif
err = snd_rawmidi_init(&ump->core, card, id, device,
output, input, info_flags);
if (err < 0) {
......@@ -206,6 +233,33 @@ static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream)
ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
}
/* number of 32bit words per message type */
static unsigned char ump_packet_words[0x10] = {
1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4
};
/* parse the UMP packet data;
* the data is copied onto ump->input_buf[].
* When a full packet is completed, returns the number of words (from 1 to 4).
* OTOH, if the packet is incomplete, returns 0.
*/
static int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val)
{
int words;
if (!ump->input_pending)
ump->input_pending = ump_packet_words[ump_message_type(val)];
ump->input_buf[ump->input_buf_head++] = val;
ump->input_pending--;
if (!ump->input_pending) {
words = ump->input_buf_head;
ump->input_buf_head = 0;
return words;
}
return 0;
}
/**
* snd_ump_receive - transfer UMP packets from the device
* @ump: the UMP endpoint
......@@ -218,9 +272,18 @@ static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream)
*/
int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count)
{
struct snd_rawmidi_substream *substream =
ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT];
struct snd_rawmidi_substream *substream;
const u32 *p = buffer;
int n, words = count >> 2;
while (words--) {
n = snd_ump_receive_ump_val(ump, *p++);
if (!n)
continue;
process_legacy_input(ump, ump->input_buf, n);
}
substream = ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT];
if (!substream)
return 0;
return snd_rawmidi_receive(substream, (const char *)buffer, count);
......@@ -241,10 +304,15 @@ int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count)
{
struct snd_rawmidi_substream *substream =
ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT];
int err;
if (!substream)
return -ENODEV;
return snd_rawmidi_transmit(substream, (char *)buffer, count);
err = snd_rawmidi_transmit(substream, (char *)buffer, count);
/* received either data or an error? */
if (err)
return err;
return process_legacy_output(ump, buffer, count);
}
EXPORT_SYMBOL_GPL(snd_ump_transmit);
......@@ -386,5 +454,189 @@ static void snd_ump_proc_read(struct snd_info_entry *entry,
}
}
#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
/*
* Legacy rawmidi support
*/
static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream)
{
struct snd_ump_endpoint *ump = substream->rmidi->private_data;
int dir = substream->stream;
int group = substream->number;
int err;
mutex_lock(&ump->open_mutex);
if (ump->legacy_substreams[dir][group]) {
err = -EBUSY;
goto unlock;
}
if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
if (!ump->legacy_out_opens) {
err = snd_rawmidi_kernel_open(&ump->core, 0,
SNDRV_RAWMIDI_LFLG_OUTPUT |
SNDRV_RAWMIDI_LFLG_APPEND,
&ump->legacy_out_rfile);
if (err < 0)
goto unlock;
}
ump->legacy_out_opens++;
snd_ump_reset_convert_to_ump(ump, group);
}
spin_lock_irq(&ump->legacy_locks[dir]);
ump->legacy_substreams[dir][group] = substream;
spin_unlock_irq(&ump->legacy_locks[dir]);
unlock:
mutex_unlock(&ump->open_mutex);
return 0;
}
static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream)
{
struct snd_ump_endpoint *ump = substream->rmidi->private_data;
int dir = substream->stream;
int group = substream->number;
mutex_lock(&ump->open_mutex);
spin_lock_irq(&ump->legacy_locks[dir]);
ump->legacy_substreams[dir][group] = NULL;
spin_unlock_irq(&ump->legacy_locks[dir]);
if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
if (!--ump->legacy_out_opens)
snd_rawmidi_kernel_release(&ump->legacy_out_rfile);
}
mutex_unlock(&ump->open_mutex);
return 0;
}
static void snd_ump_legacy_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_ump_endpoint *ump = substream->rmidi->private_data;
int dir = substream->stream;
ump->ops->trigger(ump, dir, up);
}
static void snd_ump_legacy_drain(struct snd_rawmidi_substream *substream)
{
struct snd_ump_endpoint *ump = substream->rmidi->private_data;
if (ump->ops->drain)
ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
}
static int snd_ump_legacy_dev_register(struct snd_rawmidi *rmidi)
{
/* dummy, just for avoiding create superfluous seq clients */
return 0;
}
static const struct snd_rawmidi_ops snd_ump_legacy_input_ops = {
.open = snd_ump_legacy_open,
.close = snd_ump_legacy_close,
.trigger = snd_ump_legacy_trigger,
};
static const struct snd_rawmidi_ops snd_ump_legacy_output_ops = {
.open = snd_ump_legacy_open,
.close = snd_ump_legacy_close,
.trigger = snd_ump_legacy_trigger,
.drain = snd_ump_legacy_drain,
};
static const struct snd_rawmidi_global_ops snd_ump_legacy_ops = {
.dev_register = snd_ump_legacy_dev_register,
};
static int process_legacy_output(struct snd_ump_endpoint *ump,
u32 *buffer, int count)
{
struct snd_rawmidi_substream *substream;
struct ump_cvt_to_ump *ctx;
const int dir = SNDRV_RAWMIDI_STREAM_OUTPUT;
unsigned char c;
int group, size = 0;
unsigned long flags;
if (!ump->out_cvts || !ump->legacy_out_opens)
return 0;
spin_lock_irqsave(&ump->legacy_locks[dir], flags);
for (group = 0; group < SNDRV_UMP_MAX_GROUPS; group++) {
substream = ump->legacy_substreams[dir][group];
if (!substream)
continue;
ctx = &ump->out_cvts[group];
while (!ctx->ump_bytes &&
snd_rawmidi_transmit(substream, &c, 1) > 0)
snd_ump_convert_to_ump(ump, group, c);
if (ctx->ump_bytes && ctx->ump_bytes <= count) {
size = ctx->ump_bytes;
memcpy(buffer, ctx->ump, size);
ctx->ump_bytes = 0;
break;
}
}
spin_unlock_irqrestore(&ump->legacy_locks[dir], flags);
return size;
}
static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
int words)
{
struct snd_rawmidi_substream *substream;
unsigned char buf[16];
unsigned char group;
unsigned long flags;
const int dir = SNDRV_RAWMIDI_STREAM_INPUT;
int size;
size = snd_ump_convert_from_ump(ump, src, buf, &group);
if (size <= 0)
return;
spin_lock_irqsave(&ump->legacy_locks[dir], flags);
substream = ump->legacy_substreams[dir][group];
if (substream)
snd_rawmidi_receive(substream, buf, size);
spin_unlock_irqrestore(&ump->legacy_locks[dir], flags);
}
int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
char *id, int device)
{
struct snd_rawmidi *rmidi;
bool input, output;
int err;
err = snd_ump_convert_init(ump);
if (err < 0)
return err;
input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT;
output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT;
err = snd_rawmidi_new(ump->core.card, id, device,
output ? 16 : 0, input ? 16 : 0,
&rmidi);
if (err < 0) {
snd_ump_convert_free(ump);
return err;
}
if (input)
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&snd_ump_legacy_input_ops);
if (output)
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&snd_ump_legacy_output_ops);
rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP;
rmidi->ops = &snd_ump_legacy_ops;
rmidi->private_data = ump;
ump->legacy_rmidi = rmidi;
ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id);
return 0;
}
EXPORT_SYMBOL_GPL(snd_ump_attach_legacy_rawmidi);
#endif /* CONFIG_SND_UMP_LEGACY_RAWMIDI */
MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver");
MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Helpers for UMP <-> MIDI 1.0 byte stream conversion
*/
#include <linux/module.h>
#include <linux/export.h>
#include <sound/core.h>
#include <sound/asound.h>
#include <sound/ump.h>
#include "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);
}
/*
* UMP -> MIDI 1 byte stream conversion
*/
/* convert a UMP System message to MIDI 1.0 byte stream */
static int cvt_ump_system_to_legacy(u32 data, unsigned char *buf)
{
buf[0] = ump_message_status_channel(data);
switch (ump_message_status_code(data)) {
case UMP_SYSTEM_STATUS_MIDI_TIME_CODE:
case UMP_SYSTEM_STATUS_SONG_SELECT:
buf[1] = (data >> 8) & 0x7f;
return 1;
case UMP_SYSTEM_STATUS_SONG_POSITION:
buf[1] = (data >> 8) & 0x7f;
buf[2] = data & 0x7f;
return 3;
default:
return 1;
}
}
/* convert a UMP MIDI 1.0 Channel Voice message to MIDI 1.0 byte stream */
static int cvt_ump_midi1_to_legacy(u32 data, unsigned char *buf)
{
buf[0] = ump_message_status_channel(data);
buf[1] = (data >> 8) & 0xff;
switch (ump_message_status_code(data)) {
case UMP_MSG_STATUS_PROGRAM:
case UMP_MSG_STATUS_CHANNEL_PRESSURE:
return 2;
default:
buf[2] = data & 0xff;
return 3;
}
}
/* convert a UMP MIDI 2.0 Channel Voice message to MIDI 1.0 byte stream */
static int cvt_ump_midi2_to_legacy(const union snd_ump_midi2_msg *midi2,
unsigned char *buf)
{
unsigned char status = midi2->note.status;
unsigned char channel = midi2->note.channel;
u16 v;
buf[0] = (status << 4) | channel;
switch (status) {
case UMP_MSG_STATUS_NOTE_OFF:
case UMP_MSG_STATUS_NOTE_ON:
buf[1] = midi2->note.note;
buf[2] = downscale_16_to_7bit(midi2->note.velocity);
if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2])
buf[2] = 1;
return 3;
case UMP_MSG_STATUS_POLY_PRESSURE:
buf[1] = midi2->paf.note;
buf[2] = downscale_32_to_7bit(midi2->paf.data);
return 3;
case UMP_MSG_STATUS_CC:
buf[1] = midi2->cc.index;
buf[2] = downscale_32_to_7bit(midi2->cc.data);
return 3;
case UMP_MSG_STATUS_CHANNEL_PRESSURE:
buf[1] = downscale_32_to_7bit(midi2->caf.data);
return 2;
case UMP_MSG_STATUS_PROGRAM:
if (midi2->pg.bank_valid) {
buf[0] = channel | (UMP_MSG_STATUS_CC << 4);
buf[1] = UMP_CC_BANK_SELECT;
buf[2] = midi2->pg.bank_msb;
buf[3] = channel | (UMP_MSG_STATUS_CC << 4);
buf[4] = UMP_CC_BANK_SELECT_LSB;
buf[5] = midi2->pg.bank_lsb;
buf[6] = channel | (UMP_MSG_STATUS_PROGRAM << 4);
buf[7] = midi2->pg.program;
return 8;
}
buf[1] = midi2->pg.program;
return 2;
case UMP_MSG_STATUS_PITCH_BEND:
v = downscale_32_to_14bit(midi2->pb.data);
buf[1] = v & 0x7f;
buf[2] = v >> 7;
return 3;
case UMP_MSG_STATUS_RPN:
case UMP_MSG_STATUS_NRPN:
buf[0] = channel | (UMP_MSG_STATUS_CC << 4);
buf[1] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB;
buf[2] = midi2->rpn.bank;
buf[3] = buf[0];
buf[4] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB;
buf[5] = midi2->rpn.index;
buf[6] = buf[0];
buf[7] = UMP_CC_DATA;
v = downscale_32_to_14bit(midi2->rpn.data);
buf[8] = v >> 7;
buf[9] = buf[0];
buf[10] = UMP_CC_DATA_LSB;
buf[11] = v & 0x7f;
return 12;
default:
return 0;
}
}
/* convert a UMP 7-bit SysEx message to MIDI 1.0 byte stream */
static int cvt_ump_sysex7_to_legacy(const u32 *data, unsigned char *buf)
{
unsigned char status;
unsigned char bytes;
int size, offset;
status = ump_sysex_message_status(*data);
if (status > UMP_SYSEX_STATUS_END)
return 0; // unsupported, skip
bytes = ump_sysex_message_length(*data);
if (bytes > 6)
return 0; // skip
size = 0;
if (status == UMP_SYSEX_STATUS_SINGLE ||
status == UMP_SYSEX_STATUS_START) {
buf[0] = UMP_MIDI1_MSG_SYSEX_START;
size = 1;
}
offset = 8;
for (; bytes; bytes--, size++) {
buf[size] = (*data >> offset) & 0x7f;
if (!offset) {
offset = 24;
data++;
} else {
offset -= 8;
}
}
if (status == UMP_SYSEX_STATUS_SINGLE ||
status == UMP_SYSEX_STATUS_END)
buf[size++] = UMP_MIDI1_MSG_SYSEX_END;
return size;
}
/* convert from a UMP packet @data to MIDI 1.0 bytes at @buf;
* the target group is stored at @group_ret,
* returns the number of bytes of MIDI 1.0 stream
*/
int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump,
const u32 *data,
unsigned char *buf,
unsigned char *group_ret)
{
*group_ret = ump_message_group(*data);
switch (ump_message_type(*data)) {
case UMP_MSG_TYPE_SYSTEM:
return cvt_ump_system_to_legacy(*data, buf);
case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE:
return cvt_ump_midi1_to_legacy(*data, buf);
case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE:
return cvt_ump_midi2_to_legacy((const union snd_ump_midi2_msg *)data,
buf);
case UMP_MSG_TYPE_DATA:
return cvt_ump_sysex7_to_legacy(data, buf);
}
return 0;
}
/*
* MIDI 1 byte stream -> UMP conversion
*/
/* convert MIDI 1.0 SysEx to a UMP packet */
static int cvt_legacy_sysex_to_ump(struct ump_cvt_to_ump *cvt,
unsigned char group, u32 *data, bool finish)
{
unsigned char status;
bool start = cvt->in_sysex == 1;
int i, offset;
if (start && finish)
status = UMP_SYSEX_STATUS_SINGLE;
else if (start)
status = UMP_SYSEX_STATUS_START;
else if (finish)
status = UMP_SYSEX_STATUS_END;
else
status = UMP_SYSEX_STATUS_CONTINUE;
*data = ump_compose(UMP_MSG_TYPE_DATA, group, status, cvt->len);
offset = 8;
for (i = 0; i < cvt->len; i++) {
*data |= cvt->buf[i] << offset;
if (!offset) {
offset = 24;
data++;
} else
offset -= 8;
}
cvt->len = 0;
if (finish)
cvt->in_sysex = 0;
else
cvt->in_sysex++;
return 8;
}
/* convert to a UMP System message */
static int cvt_legacy_system_to_ump(struct ump_cvt_to_ump *cvt,
unsigned char group, u32 *data)
{
data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, cvt->buf[0]);
if (cvt->cmd_bytes > 1)
data[0] |= cvt->buf[1] << 8;
if (cvt->cmd_bytes > 2)
data[0] |= cvt->buf[2];
return 4;
}
static void fill_rpn(struct ump_cvt_to_ump_bank *cc,
union snd_ump_midi2_msg *midi2)
{
if (cc->rpn_set) {
midi2->rpn.status = UMP_MSG_STATUS_RPN;
midi2->rpn.bank = cc->cc_rpn_msb;
midi2->rpn.index = cc->cc_rpn_lsb;
cc->rpn_set = 0;
cc->cc_rpn_msb = cc->cc_rpn_lsb = 0;
} else {
midi2->rpn.status = UMP_MSG_STATUS_NRPN;
midi2->rpn.bank = cc->cc_nrpn_msb;
midi2->rpn.index = cc->cc_nrpn_lsb;
cc->nrpn_set = 0;
cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0;
}
midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) |
cc->cc_data_lsb);
cc->cc_data_msb = cc->cc_data_lsb = 0;
}
/* convert to a MIDI 1.0 Channel Voice message */
static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump,
struct ump_cvt_to_ump *cvt,
unsigned char group, u32 *data,
unsigned char bytes)
{
const unsigned char *buf = cvt->buf;
struct ump_cvt_to_ump_bank *cc;
union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)data;
unsigned char status, channel;
BUILD_BUG_ON(sizeof(union snd_ump_midi1_msg) != 4);
BUILD_BUG_ON(sizeof(union snd_ump_midi2_msg) != 8);
/* for MIDI 1.0 UMP, it's easy, just pack it into UMP */
if (ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) {
data[0] = ump_compose(UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE,
group, 0, buf[0]);
data[0] |= buf[1] << 8;
if (bytes > 2)
data[0] |= buf[2];
return 4;
}
status = *buf >> 4;
channel = *buf & 0x0f;
cc = &cvt->bank[channel];
/* special handling: treat note-on with 0 velocity as note-off */
if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2])
status = UMP_MSG_STATUS_NOTE_OFF;
/* initialize the packet */
data[0] = ump_compose(UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE,
group, status, channel);
data[1] = 0;
switch (status) {
case UMP_MSG_STATUS_NOTE_ON:
if (!buf[2])
status = UMP_MSG_STATUS_NOTE_OFF;
fallthrough;
case UMP_MSG_STATUS_NOTE_OFF:
midi2->note.note = buf[1];
midi2->note.velocity = upscale_7_to_16bit(buf[2]);
break;
case UMP_MSG_STATUS_POLY_PRESSURE:
midi2->paf.note = buf[1];
midi2->paf.data = upscale_7_to_32bit(buf[2]);
break;
case UMP_MSG_STATUS_CC:
switch (buf[1]) {
case UMP_CC_RPN_MSB:
cc->rpn_set = 1;
cc->cc_rpn_msb = buf[2];
return 0; // skip
case UMP_CC_RPN_LSB:
cc->rpn_set = 1;
cc->cc_rpn_lsb = buf[2];
return 0; // skip
case UMP_CC_NRPN_MSB:
cc->nrpn_set = 1;
cc->cc_nrpn_msb = buf[2];
return 0; // skip
case UMP_CC_NRPN_LSB:
cc->nrpn_set = 1;
cc->cc_nrpn_lsb = buf[2];
return 0; // skip
case UMP_CC_DATA:
cc->cc_data_msb = buf[2];
return 0; // skip
case UMP_CC_BANK_SELECT:
cc->bank_set = 1;
cc->cc_bank_msb = buf[2];
return 0; // skip
case UMP_CC_BANK_SELECT_LSB:
cc->bank_set = 1;
cc->cc_bank_lsb = buf[2];
return 0; // skip
case UMP_CC_DATA_LSB:
cc->cc_data_lsb = buf[2];
if (cc->rpn_set || cc->nrpn_set)
fill_rpn(cc, midi2);
else
return 0; // skip
break;
default:
midi2->cc.index = buf[1];
midi2->cc.data = upscale_7_to_32bit(buf[2]);
break;
}
break;
case UMP_MSG_STATUS_PROGRAM:
midi2->pg.program = buf[1];
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;
cc->cc_bank_msb = cc->cc_bank_lsb = 0;
}
break;
case UMP_MSG_STATUS_CHANNEL_PRESSURE:
midi2->caf.data = upscale_7_to_32bit(buf[1]);
break;
case UMP_MSG_STATUS_PITCH_BEND:
midi2->pb.data = upscale_14_to_32bit(buf[1] | (buf[2] << 7));
break;
default:
return 0;
}
return 8;
}
static int do_convert_to_ump(struct snd_ump_endpoint *ump,
unsigned char group, unsigned char c, u32 *data)
{
/* bytes for 0x80-0xf0 */
static unsigned char cmd_bytes[8] = {
3, 3, 3, 3, 2, 2, 3, 0
};
/* bytes for 0xf0-0xff */
static unsigned char system_bytes[16] = {
0, 2, 3, 2, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1
};
struct ump_cvt_to_ump *cvt = &ump->out_cvts[group];
unsigned char bytes;
if (c == UMP_MIDI1_MSG_SYSEX_START) {
cvt->in_sysex = 1;
cvt->len = 0;
return 0;
}
if (c == UMP_MIDI1_MSG_SYSEX_END) {
if (!cvt->in_sysex)
return 0; /* skip */
return cvt_legacy_sysex_to_ump(cvt, group, data, true);
}
if ((c & 0xf0) == UMP_MIDI1_MSG_REALTIME) {
bytes = system_bytes[c & 0x0f];
if (!bytes)
return 0; /* skip */
if (bytes == 1) {
data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, c);
return 4;
}
cvt->buf[0] = c;
cvt->len = 1;
cvt->cmd_bytes = bytes;
cvt->in_sysex = 0; /* abort SysEx */
return 0;
}
if (c & 0x80) {
bytes = cmd_bytes[(c >> 8) & 7];
cvt->buf[0] = c;
cvt->len = 1;
cvt->cmd_bytes = bytes;
cvt->in_sysex = 0; /* abort SysEx */
return 0;
}
if (cvt->in_sysex) {
cvt->buf[cvt->len++] = c;
if (cvt->len == 6)
return cvt_legacy_sysex_to_ump(cvt, group, data, false);
return 0;
}
if (!cvt->len)
return 0;
cvt->buf[cvt->len++] = c;
if (cvt->len < cvt->cmd_bytes)
return 0;
cvt->len = 1;
if ((cvt->buf[0] & 0xf0) == UMP_MIDI1_MSG_REALTIME)
return cvt_legacy_system_to_ump(cvt, group, data);
return cvt_legacy_cmd_to_ump(ump, cvt, group, data, cvt->cmd_bytes);
}
/* feed a MIDI 1.0 byte @c and convert to a UMP packet;
* the target group is @group,
* the result is stored in out_cvts[group].ump[] and out_cvts[group].ump_bytes
*/
void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump,
unsigned char group, unsigned char c)
{
struct ump_cvt_to_ump *cvt = &ump->out_cvts[group];
cvt->ump_bytes = do_convert_to_ump(ump, group, c, cvt->ump);
}
/* reset the converter context, called at each open */
void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump,
unsigned char group)
{
memset(&ump->out_cvts[group], 0, sizeof(*ump->out_cvts));
}
/* initialize converters */
int snd_ump_convert_init(struct snd_ump_endpoint *ump)
{
ump->out_cvts = kcalloc(16, sizeof(*ump->out_cvts), GFP_KERNEL);
if (!ump->out_cvts)
return -ENOMEM;
return 0;
}
/* release resources */
void snd_ump_convert_free(struct snd_ump_endpoint *ump)
{
kfree(ump->out_cvts);
ump->out_cvts = NULL;
}
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef __UMP_CONVERT_H
#define __UMP_CONVERT_H
#include <sound/ump_msg.h>
/* context for converting from legacy control messages to UMP packet */
struct ump_cvt_to_ump_bank {
bool rpn_set;
bool nrpn_set;
bool bank_set;
unsigned char cc_rpn_msb, cc_rpn_lsb;
unsigned char cc_nrpn_msb, cc_nrpn_lsb;
unsigned char cc_data_msb, cc_data_lsb;
unsigned char cc_bank_msb, cc_bank_lsb;
};
/* context for converting from MIDI1 byte stream to UMP packet */
struct ump_cvt_to_ump {
/* MIDI1 intermediate buffer */
unsigned char buf[4];
int len;
int cmd_bytes;
/* UMP output packet */
u32 ump[4];
int ump_bytes;
/* various status */
unsigned int in_sysex;
struct ump_cvt_to_ump_bank bank[16]; /* per channel */
};
int snd_ump_convert_init(struct snd_ump_endpoint *ump);
void snd_ump_convert_free(struct snd_ump_endpoint *ump);
int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump,
const u32 *data, unsigned char *dst,
unsigned char *group_ret);
void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump,
unsigned char group, unsigned char c);
void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump,
unsigned char group);
#endif /* __UMP_CONVERT_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment