mirror of
https://github.com/torvalds/linux.git
synced 2024-12-12 06:02:38 +00:00
1a59d1b8e0
Based on 1 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 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 general public license for more details you should have received a copy of the gnu general public license along with this program if not write to the free software foundation inc 59 temple place suite 330 boston ma 02111 1307 usa extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1334 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.113240726@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
717 lines
19 KiB
C
717 lines
19 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* GM/GS/XG midi module.
|
|
*
|
|
* Copyright (C) 1999 Steve Ratcliffe
|
|
*
|
|
* Based on awe_wave.c by Takashi Iwai
|
|
*/
|
|
/*
|
|
* This module is used to keep track of the current midi state.
|
|
* It can be used for drivers that are required to emulate midi when
|
|
* the hardware doesn't.
|
|
*
|
|
* It was written for a AWE64 driver, but there should be no AWE specific
|
|
* code in here. If there is it should be reported as a bug.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
#include <linux/module.h>
|
|
#include <sound/core.h>
|
|
#include <sound/seq_kernel.h>
|
|
#include <sound/seq_midi_emul.h>
|
|
#include <sound/initval.h>
|
|
#include <sound/asoundef.h>
|
|
|
|
MODULE_AUTHOR("Takashi Iwai / Steve Ratcliffe");
|
|
MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI emulation.");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
/* Prototypes for static functions */
|
|
static void note_off(struct snd_midi_op *ops, void *drv,
|
|
struct snd_midi_channel *chan,
|
|
int note, int vel);
|
|
static void do_control(struct snd_midi_op *ops, void *private,
|
|
struct snd_midi_channel_set *chset,
|
|
struct snd_midi_channel *chan,
|
|
int control, int value);
|
|
static void rpn(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
|
|
struct snd_midi_channel_set *chset);
|
|
static void nrpn(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
|
|
struct snd_midi_channel_set *chset);
|
|
static void sysex(struct snd_midi_op *ops, void *private, unsigned char *sysex,
|
|
int len, struct snd_midi_channel_set *chset);
|
|
static void all_sounds_off(struct snd_midi_op *ops, void *private,
|
|
struct snd_midi_channel *chan);
|
|
static void all_notes_off(struct snd_midi_op *ops, void *private,
|
|
struct snd_midi_channel *chan);
|
|
static void snd_midi_reset_controllers(struct snd_midi_channel *chan);
|
|
static void reset_all_channels(struct snd_midi_channel_set *chset);
|
|
|
|
|
|
/*
|
|
* Process an event in a driver independent way. This means dealing
|
|
* with RPN, NRPN, SysEx etc that are defined for common midi applications
|
|
* such as GM, GS and XG.
|
|
* There modes that this module will run in are:
|
|
* Generic MIDI - no interpretation at all, it will just save current values
|
|
* of controllers etc.
|
|
* GM - You can use all gm_ prefixed elements of chan. Controls, RPN, NRPN,
|
|
* SysEx will be interpreded as defined in General Midi.
|
|
* GS - You can use all gs_ prefixed elements of chan. Codes for GS will be
|
|
* interpreted.
|
|
* XG - You can use all xg_ prefixed elements of chan. Codes for XG will
|
|
* be interpreted.
|
|
*/
|
|
void
|
|
snd_midi_process_event(struct snd_midi_op *ops,
|
|
struct snd_seq_event *ev,
|
|
struct snd_midi_channel_set *chanset)
|
|
{
|
|
struct snd_midi_channel *chan;
|
|
void *drv;
|
|
int dest_channel = 0;
|
|
|
|
if (ev == NULL || chanset == NULL) {
|
|
pr_debug("ALSA: seq_midi_emul: ev or chanbase NULL (snd_midi_process_event)\n");
|
|
return;
|
|
}
|
|
if (chanset->channels == NULL)
|
|
return;
|
|
|
|
if (snd_seq_ev_is_channel_type(ev)) {
|
|
dest_channel = ev->data.note.channel;
|
|
if (dest_channel >= chanset->max_channels) {
|
|
pr_debug("ALSA: seq_midi_emul: dest channel is %d, max is %d\n",
|
|
dest_channel, chanset->max_channels);
|
|
return;
|
|
}
|
|
}
|
|
|
|
chan = chanset->channels + dest_channel;
|
|
drv = chanset->private_data;
|
|
|
|
/* EVENT_NOTE should be processed before queued */
|
|
if (ev->type == SNDRV_SEQ_EVENT_NOTE)
|
|
return;
|
|
|
|
/* Make sure that we don't have a note on that should really be
|
|
* a note off */
|
|
if (ev->type == SNDRV_SEQ_EVENT_NOTEON && ev->data.note.velocity == 0)
|
|
ev->type = SNDRV_SEQ_EVENT_NOTEOFF;
|
|
|
|
/* Make sure the note is within array range */
|
|
if (ev->type == SNDRV_SEQ_EVENT_NOTEON ||
|
|
ev->type == SNDRV_SEQ_EVENT_NOTEOFF ||
|
|
ev->type == SNDRV_SEQ_EVENT_KEYPRESS) {
|
|
if (ev->data.note.note >= 128)
|
|
return;
|
|
}
|
|
|
|
switch (ev->type) {
|
|
case SNDRV_SEQ_EVENT_NOTEON:
|
|
if (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON) {
|
|
if (ops->note_off)
|
|
ops->note_off(drv, ev->data.note.note, 0, chan);
|
|
}
|
|
chan->note[ev->data.note.note] = SNDRV_MIDI_NOTE_ON;
|
|
if (ops->note_on)
|
|
ops->note_on(drv, ev->data.note.note, ev->data.note.velocity, chan);
|
|
break;
|
|
case SNDRV_SEQ_EVENT_NOTEOFF:
|
|
if (! (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON))
|
|
break;
|
|
if (ops->note_off)
|
|
note_off(ops, drv, chan, ev->data.note.note, ev->data.note.velocity);
|
|
break;
|
|
case SNDRV_SEQ_EVENT_KEYPRESS:
|
|
if (ops->key_press)
|
|
ops->key_press(drv, ev->data.note.note, ev->data.note.velocity, chan);
|
|
break;
|
|
case SNDRV_SEQ_EVENT_CONTROLLER:
|
|
do_control(ops, drv, chanset, chan,
|
|
ev->data.control.param, ev->data.control.value);
|
|
break;
|
|
case SNDRV_SEQ_EVENT_PGMCHANGE:
|
|
chan->midi_program = ev->data.control.value;
|
|
break;
|
|
case SNDRV_SEQ_EVENT_PITCHBEND:
|
|
chan->midi_pitchbend = ev->data.control.value;
|
|
if (ops->control)
|
|
ops->control(drv, MIDI_CTL_PITCHBEND, chan);
|
|
break;
|
|
case SNDRV_SEQ_EVENT_CHANPRESS:
|
|
chan->midi_pressure = ev->data.control.value;
|
|
if (ops->control)
|
|
ops->control(drv, MIDI_CTL_CHAN_PRESSURE, chan);
|
|
break;
|
|
case SNDRV_SEQ_EVENT_CONTROL14:
|
|
/* Best guess is that this is any of the 14 bit controller values */
|
|
if (ev->data.control.param < 32) {
|
|
/* set low part first */
|
|
chan->control[ev->data.control.param + 32] =
|
|
ev->data.control.value & 0x7f;
|
|
do_control(ops, drv, chanset, chan,
|
|
ev->data.control.param,
|
|
((ev->data.control.value>>7) & 0x7f));
|
|
} else
|
|
do_control(ops, drv, chanset, chan,
|
|
ev->data.control.param,
|
|
ev->data.control.value);
|
|
break;
|
|
case SNDRV_SEQ_EVENT_NONREGPARAM:
|
|
/* Break it back into its controller values */
|
|
chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED;
|
|
chan->control[MIDI_CTL_MSB_DATA_ENTRY]
|
|
= (ev->data.control.value >> 7) & 0x7f;
|
|
chan->control[MIDI_CTL_LSB_DATA_ENTRY]
|
|
= ev->data.control.value & 0x7f;
|
|
chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB]
|
|
= (ev->data.control.param >> 7) & 0x7f;
|
|
chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB]
|
|
= ev->data.control.param & 0x7f;
|
|
nrpn(ops, drv, chan, chanset);
|
|
break;
|
|
case SNDRV_SEQ_EVENT_REGPARAM:
|
|
/* Break it back into its controller values */
|
|
chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED;
|
|
chan->control[MIDI_CTL_MSB_DATA_ENTRY]
|
|
= (ev->data.control.value >> 7) & 0x7f;
|
|
chan->control[MIDI_CTL_LSB_DATA_ENTRY]
|
|
= ev->data.control.value & 0x7f;
|
|
chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB]
|
|
= (ev->data.control.param >> 7) & 0x7f;
|
|
chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB]
|
|
= ev->data.control.param & 0x7f;
|
|
rpn(ops, drv, chan, chanset);
|
|
break;
|
|
case SNDRV_SEQ_EVENT_SYSEX:
|
|
if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE) {
|
|
unsigned char sysexbuf[64];
|
|
int len;
|
|
len = snd_seq_expand_var_event(ev, sizeof(sysexbuf), sysexbuf, 1, 0);
|
|
if (len > 0)
|
|
sysex(ops, drv, sysexbuf, len, chanset);
|
|
}
|
|
break;
|
|
case SNDRV_SEQ_EVENT_SONGPOS:
|
|
case SNDRV_SEQ_EVENT_SONGSEL:
|
|
case SNDRV_SEQ_EVENT_CLOCK:
|
|
case SNDRV_SEQ_EVENT_START:
|
|
case SNDRV_SEQ_EVENT_CONTINUE:
|
|
case SNDRV_SEQ_EVENT_STOP:
|
|
case SNDRV_SEQ_EVENT_QFRAME:
|
|
case SNDRV_SEQ_EVENT_TEMPO:
|
|
case SNDRV_SEQ_EVENT_TIMESIGN:
|
|
case SNDRV_SEQ_EVENT_KEYSIGN:
|
|
goto not_yet;
|
|
case SNDRV_SEQ_EVENT_SENSING:
|
|
break;
|
|
case SNDRV_SEQ_EVENT_CLIENT_START:
|
|
case SNDRV_SEQ_EVENT_CLIENT_EXIT:
|
|
case SNDRV_SEQ_EVENT_CLIENT_CHANGE:
|
|
case SNDRV_SEQ_EVENT_PORT_START:
|
|
case SNDRV_SEQ_EVENT_PORT_EXIT:
|
|
case SNDRV_SEQ_EVENT_PORT_CHANGE:
|
|
case SNDRV_SEQ_EVENT_ECHO:
|
|
not_yet:
|
|
default:
|
|
/*pr_debug("ALSA: seq_midi_emul: Unimplemented event %d\n", ev->type);*/
|
|
break;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(snd_midi_process_event);
|
|
|
|
|
|
/*
|
|
* release note
|
|
*/
|
|
static void
|
|
note_off(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
|
|
int note, int vel)
|
|
{
|
|
if (chan->gm_hold) {
|
|
/* Hold this note until pedal is turned off */
|
|
chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED;
|
|
} else if (chan->note[note] & SNDRV_MIDI_NOTE_SOSTENUTO) {
|
|
/* Mark this note as release; it will be turned off when sostenuto
|
|
* is turned off */
|
|
chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED;
|
|
} else {
|
|
chan->note[note] = 0;
|
|
if (ops->note_off)
|
|
ops->note_off(drv, note, vel, chan);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Do all driver independent operations for this controller and pass
|
|
* events that need to take place immediately to the driver.
|
|
*/
|
|
static void
|
|
do_control(struct snd_midi_op *ops, void *drv, struct snd_midi_channel_set *chset,
|
|
struct snd_midi_channel *chan, int control, int value)
|
|
{
|
|
int i;
|
|
|
|
if (control >= ARRAY_SIZE(chan->control))
|
|
return;
|
|
|
|
/* Switches */
|
|
if ((control >=64 && control <=69) || (control >= 80 && control <= 83)) {
|
|
/* These are all switches; either off or on so set to 0 or 127 */
|
|
value = (value >= 64)? 127: 0;
|
|
}
|
|
chan->control[control] = value;
|
|
|
|
switch (control) {
|
|
case MIDI_CTL_SUSTAIN:
|
|
if (value == 0) {
|
|
/* Sustain has been released, turn off held notes */
|
|
for (i = 0; i < 128; i++) {
|
|
if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) {
|
|
chan->note[i] = SNDRV_MIDI_NOTE_OFF;
|
|
if (ops->note_off)
|
|
ops->note_off(drv, i, 0, chan);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MIDI_CTL_PORTAMENTO:
|
|
break;
|
|
case MIDI_CTL_SOSTENUTO:
|
|
if (value) {
|
|
/* Mark each note that is currently held down */
|
|
for (i = 0; i < 128; i++) {
|
|
if (chan->note[i] & SNDRV_MIDI_NOTE_ON)
|
|
chan->note[i] |= SNDRV_MIDI_NOTE_SOSTENUTO;
|
|
}
|
|
} else {
|
|
/* release all notes that were held */
|
|
for (i = 0; i < 128; i++) {
|
|
if (chan->note[i] & SNDRV_MIDI_NOTE_SOSTENUTO) {
|
|
chan->note[i] &= ~SNDRV_MIDI_NOTE_SOSTENUTO;
|
|
if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) {
|
|
chan->note[i] = SNDRV_MIDI_NOTE_OFF;
|
|
if (ops->note_off)
|
|
ops->note_off(drv, i, 0, chan);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MIDI_CTL_MSB_DATA_ENTRY:
|
|
chan->control[MIDI_CTL_LSB_DATA_ENTRY] = 0;
|
|
/* fall through */
|
|
case MIDI_CTL_LSB_DATA_ENTRY:
|
|
if (chan->param_type == SNDRV_MIDI_PARAM_TYPE_REGISTERED)
|
|
rpn(ops, drv, chan, chset);
|
|
else
|
|
nrpn(ops, drv, chan, chset);
|
|
break;
|
|
case MIDI_CTL_REGIST_PARM_NUM_LSB:
|
|
case MIDI_CTL_REGIST_PARM_NUM_MSB:
|
|
chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED;
|
|
break;
|
|
case MIDI_CTL_NONREG_PARM_NUM_LSB:
|
|
case MIDI_CTL_NONREG_PARM_NUM_MSB:
|
|
chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED;
|
|
break;
|
|
|
|
case MIDI_CTL_ALL_SOUNDS_OFF:
|
|
all_sounds_off(ops, drv, chan);
|
|
break;
|
|
|
|
case MIDI_CTL_ALL_NOTES_OFF:
|
|
all_notes_off(ops, drv, chan);
|
|
break;
|
|
|
|
case MIDI_CTL_MSB_BANK:
|
|
if (chset->midi_mode == SNDRV_MIDI_MODE_XG) {
|
|
if (value == 127)
|
|
chan->drum_channel = 1;
|
|
else
|
|
chan->drum_channel = 0;
|
|
}
|
|
break;
|
|
case MIDI_CTL_LSB_BANK:
|
|
break;
|
|
|
|
case MIDI_CTL_RESET_CONTROLLERS:
|
|
snd_midi_reset_controllers(chan);
|
|
break;
|
|
|
|
case MIDI_CTL_SOFT_PEDAL:
|
|
case MIDI_CTL_LEGATO_FOOTSWITCH:
|
|
case MIDI_CTL_HOLD2:
|
|
case MIDI_CTL_SC1_SOUND_VARIATION:
|
|
case MIDI_CTL_SC2_TIMBRE:
|
|
case MIDI_CTL_SC3_RELEASE_TIME:
|
|
case MIDI_CTL_SC4_ATTACK_TIME:
|
|
case MIDI_CTL_SC5_BRIGHTNESS:
|
|
case MIDI_CTL_E1_REVERB_DEPTH:
|
|
case MIDI_CTL_E2_TREMOLO_DEPTH:
|
|
case MIDI_CTL_E3_CHORUS_DEPTH:
|
|
case MIDI_CTL_E4_DETUNE_DEPTH:
|
|
case MIDI_CTL_E5_PHASER_DEPTH:
|
|
goto notyet;
|
|
notyet:
|
|
default:
|
|
if (ops->control)
|
|
ops->control(drv, control, chan);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* initialize the MIDI status
|
|
*/
|
|
void
|
|
snd_midi_channel_set_clear(struct snd_midi_channel_set *chset)
|
|
{
|
|
int i;
|
|
|
|
chset->midi_mode = SNDRV_MIDI_MODE_GM;
|
|
chset->gs_master_volume = 127;
|
|
|
|
for (i = 0; i < chset->max_channels; i++) {
|
|
struct snd_midi_channel *chan = chset->channels + i;
|
|
memset(chan->note, 0, sizeof(chan->note));
|
|
|
|
chan->midi_aftertouch = 0;
|
|
chan->midi_pressure = 0;
|
|
chan->midi_program = 0;
|
|
chan->midi_pitchbend = 0;
|
|
snd_midi_reset_controllers(chan);
|
|
chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
|
|
chan->gm_rpn_fine_tuning = 0;
|
|
chan->gm_rpn_coarse_tuning = 0;
|
|
|
|
if (i == 9)
|
|
chan->drum_channel = 1;
|
|
else
|
|
chan->drum_channel = 0;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(snd_midi_channel_set_clear);
|
|
|
|
/*
|
|
* Process a rpn message.
|
|
*/
|
|
static void
|
|
rpn(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
|
|
struct snd_midi_channel_set *chset)
|
|
{
|
|
int type;
|
|
int val;
|
|
|
|
if (chset->midi_mode != SNDRV_MIDI_MODE_NONE) {
|
|
type = (chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] << 8) |
|
|
chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB];
|
|
val = (chan->control[MIDI_CTL_MSB_DATA_ENTRY] << 7) |
|
|
chan->control[MIDI_CTL_LSB_DATA_ENTRY];
|
|
|
|
switch (type) {
|
|
case 0x0000: /* Pitch bend sensitivity */
|
|
/* MSB only / 1 semitone per 128 */
|
|
chan->gm_rpn_pitch_bend_range = val;
|
|
break;
|
|
|
|
case 0x0001: /* fine tuning: */
|
|
/* MSB/LSB, 8192=center, 100/8192 cent step */
|
|
chan->gm_rpn_fine_tuning = val - 8192;
|
|
break;
|
|
|
|
case 0x0002: /* coarse tuning */
|
|
/* MSB only / 8192=center, 1 semitone per 128 */
|
|
chan->gm_rpn_coarse_tuning = val - 8192;
|
|
break;
|
|
|
|
case 0x7F7F: /* "lock-in" RPN */
|
|
/* ignored */
|
|
break;
|
|
}
|
|
}
|
|
/* should call nrpn or rpn callback here.. */
|
|
}
|
|
|
|
/*
|
|
* Process an nrpn message.
|
|
*/
|
|
static void
|
|
nrpn(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan,
|
|
struct snd_midi_channel_set *chset)
|
|
{
|
|
/* parse XG NRPNs here if possible */
|
|
if (ops->nrpn)
|
|
ops->nrpn(drv, chan, chset);
|
|
}
|
|
|
|
|
|
/*
|
|
* convert channel parameter in GS sysex
|
|
*/
|
|
static int
|
|
get_channel(unsigned char cmd)
|
|
{
|
|
int p = cmd & 0x0f;
|
|
if (p == 0)
|
|
p = 9;
|
|
else if (p < 10)
|
|
p--;
|
|
return p;
|
|
}
|
|
|
|
|
|
/*
|
|
* Process a sysex message.
|
|
*/
|
|
static void
|
|
sysex(struct snd_midi_op *ops, void *private, unsigned char *buf, int len,
|
|
struct snd_midi_channel_set *chset)
|
|
{
|
|
/* GM on */
|
|
static unsigned char gm_on_macro[] = {
|
|
0x7e,0x7f,0x09,0x01,
|
|
};
|
|
/* XG on */
|
|
static unsigned char xg_on_macro[] = {
|
|
0x43,0x10,0x4c,0x00,0x00,0x7e,0x00,
|
|
};
|
|
/* GS prefix
|
|
* drum channel: XX=0x1?(channel), YY=0x15, ZZ=on/off
|
|
* reverb mode: XX=0x01, YY=0x30, ZZ=0-7
|
|
* chorus mode: XX=0x01, YY=0x38, ZZ=0-7
|
|
* master vol: XX=0x00, YY=0x04, ZZ=0-127
|
|
*/
|
|
static unsigned char gs_pfx_macro[] = {
|
|
0x41,0x10,0x42,0x12,0x40,/*XX,YY,ZZ*/
|
|
};
|
|
|
|
int parsed = SNDRV_MIDI_SYSEX_NOT_PARSED;
|
|
|
|
if (len <= 0 || buf[0] != 0xf0)
|
|
return;
|
|
/* skip first byte */
|
|
buf++;
|
|
len--;
|
|
|
|
/* GM on */
|
|
if (len >= (int)sizeof(gm_on_macro) &&
|
|
memcmp(buf, gm_on_macro, sizeof(gm_on_macro)) == 0) {
|
|
if (chset->midi_mode != SNDRV_MIDI_MODE_GS &&
|
|
chset->midi_mode != SNDRV_MIDI_MODE_XG) {
|
|
chset->midi_mode = SNDRV_MIDI_MODE_GM;
|
|
reset_all_channels(chset);
|
|
parsed = SNDRV_MIDI_SYSEX_GM_ON;
|
|
}
|
|
}
|
|
|
|
/* GS macros */
|
|
else if (len >= 8 &&
|
|
memcmp(buf, gs_pfx_macro, sizeof(gs_pfx_macro)) == 0) {
|
|
if (chset->midi_mode != SNDRV_MIDI_MODE_GS &&
|
|
chset->midi_mode != SNDRV_MIDI_MODE_XG)
|
|
chset->midi_mode = SNDRV_MIDI_MODE_GS;
|
|
|
|
if (buf[5] == 0x00 && buf[6] == 0x7f && buf[7] == 0x00) {
|
|
/* GS reset */
|
|
parsed = SNDRV_MIDI_SYSEX_GS_RESET;
|
|
reset_all_channels(chset);
|
|
}
|
|
|
|
else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x15) {
|
|
/* drum pattern */
|
|
int p = get_channel(buf[5]);
|
|
if (p < chset->max_channels) {
|
|
parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL;
|
|
if (buf[7])
|
|
chset->channels[p].drum_channel = 1;
|
|
else
|
|
chset->channels[p].drum_channel = 0;
|
|
}
|
|
|
|
} else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x21) {
|
|
/* program */
|
|
int p = get_channel(buf[5]);
|
|
if (p < chset->max_channels &&
|
|
! chset->channels[p].drum_channel) {
|
|
parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL;
|
|
chset->channels[p].midi_program = buf[7];
|
|
}
|
|
|
|
} else if (buf[5] == 0x01 && buf[6] == 0x30) {
|
|
/* reverb mode */
|
|
parsed = SNDRV_MIDI_SYSEX_GS_REVERB_MODE;
|
|
chset->gs_reverb_mode = buf[7];
|
|
|
|
} else if (buf[5] == 0x01 && buf[6] == 0x38) {
|
|
/* chorus mode */
|
|
parsed = SNDRV_MIDI_SYSEX_GS_CHORUS_MODE;
|
|
chset->gs_chorus_mode = buf[7];
|
|
|
|
} else if (buf[5] == 0x00 && buf[6] == 0x04) {
|
|
/* master volume */
|
|
parsed = SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME;
|
|
chset->gs_master_volume = buf[7];
|
|
|
|
}
|
|
}
|
|
|
|
/* XG on */
|
|
else if (len >= (int)sizeof(xg_on_macro) &&
|
|
memcmp(buf, xg_on_macro, sizeof(xg_on_macro)) == 0) {
|
|
int i;
|
|
chset->midi_mode = SNDRV_MIDI_MODE_XG;
|
|
parsed = SNDRV_MIDI_SYSEX_XG_ON;
|
|
/* reset CC#0 for drums */
|
|
for (i = 0; i < chset->max_channels; i++) {
|
|
if (chset->channels[i].drum_channel)
|
|
chset->channels[i].control[MIDI_CTL_MSB_BANK] = 127;
|
|
else
|
|
chset->channels[i].control[MIDI_CTL_MSB_BANK] = 0;
|
|
}
|
|
}
|
|
|
|
if (ops->sysex)
|
|
ops->sysex(private, buf - 1, len + 1, parsed, chset);
|
|
}
|
|
|
|
/*
|
|
* all sound off
|
|
*/
|
|
static void
|
|
all_sounds_off(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan)
|
|
{
|
|
int n;
|
|
|
|
if (! ops->note_terminate)
|
|
return;
|
|
for (n = 0; n < 128; n++) {
|
|
if (chan->note[n]) {
|
|
ops->note_terminate(drv, n, chan);
|
|
chan->note[n] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* all notes off
|
|
*/
|
|
static void
|
|
all_notes_off(struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan)
|
|
{
|
|
int n;
|
|
|
|
if (! ops->note_off)
|
|
return;
|
|
for (n = 0; n < 128; n++) {
|
|
if (chan->note[n] == SNDRV_MIDI_NOTE_ON)
|
|
note_off(ops, drv, chan, n, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialise a single midi channel control block.
|
|
*/
|
|
static void snd_midi_channel_init(struct snd_midi_channel *p, int n)
|
|
{
|
|
if (p == NULL)
|
|
return;
|
|
|
|
memset(p, 0, sizeof(struct snd_midi_channel));
|
|
p->private = NULL;
|
|
p->number = n;
|
|
|
|
snd_midi_reset_controllers(p);
|
|
p->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
|
|
p->gm_rpn_fine_tuning = 0;
|
|
p->gm_rpn_coarse_tuning = 0;
|
|
|
|
if (n == 9)
|
|
p->drum_channel = 1; /* Default ch 10 as drums */
|
|
}
|
|
|
|
/*
|
|
* Allocate and initialise a set of midi channel control blocks.
|
|
*/
|
|
static struct snd_midi_channel *snd_midi_channel_init_set(int n)
|
|
{
|
|
struct snd_midi_channel *chan;
|
|
int i;
|
|
|
|
chan = kmalloc_array(n, sizeof(struct snd_midi_channel), GFP_KERNEL);
|
|
if (chan) {
|
|
for (i = 0; i < n; i++)
|
|
snd_midi_channel_init(chan+i, i);
|
|
}
|
|
|
|
return chan;
|
|
}
|
|
|
|
/*
|
|
* reset all midi channels
|
|
*/
|
|
static void
|
|
reset_all_channels(struct snd_midi_channel_set *chset)
|
|
{
|
|
int ch;
|
|
for (ch = 0; ch < chset->max_channels; ch++) {
|
|
struct snd_midi_channel *chan = chset->channels + ch;
|
|
snd_midi_reset_controllers(chan);
|
|
chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */
|
|
chan->gm_rpn_fine_tuning = 0;
|
|
chan->gm_rpn_coarse_tuning = 0;
|
|
|
|
if (ch == 9)
|
|
chan->drum_channel = 1;
|
|
else
|
|
chan->drum_channel = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Allocate and initialise a midi channel set.
|
|
*/
|
|
struct snd_midi_channel_set *snd_midi_channel_alloc_set(int n)
|
|
{
|
|
struct snd_midi_channel_set *chset;
|
|
|
|
chset = kmalloc(sizeof(*chset), GFP_KERNEL);
|
|
if (chset) {
|
|
chset->channels = snd_midi_channel_init_set(n);
|
|
chset->private_data = NULL;
|
|
chset->max_channels = n;
|
|
}
|
|
return chset;
|
|
}
|
|
EXPORT_SYMBOL(snd_midi_channel_alloc_set);
|
|
|
|
/*
|
|
* Reset the midi controllers on a particular channel to default values.
|
|
*/
|
|
static void snd_midi_reset_controllers(struct snd_midi_channel *chan)
|
|
{
|
|
memset(chan->control, 0, sizeof(chan->control));
|
|
chan->gm_volume = 127;
|
|
chan->gm_expression = 127;
|
|
chan->gm_pan = 64;
|
|
}
|
|
|
|
|
|
/*
|
|
* Free a midi channel set.
|
|
*/
|
|
void snd_midi_channel_free_set(struct snd_midi_channel_set *chset)
|
|
{
|
|
if (chset == NULL)
|
|
return;
|
|
kfree(chset->channels);
|
|
kfree(chset);
|
|
}
|
|
EXPORT_SYMBOL(snd_midi_channel_free_set);
|