[ALSA] snd-ca0106: Add midi support.
authorJames Courtier-Dutton <James@superbug.co.uk>
Thu, 20 Oct 2005 20:57:51 +0000 (22:57 +0200)
committerJaroslav Kysela <perex@suse.cz>
Fri, 4 Nov 2005 12:19:26 +0000 (13:19 +0100)
Modules: PCI drivers,CA0106 driver

Author: Tilman Kranz <tilde@tk-sls.de>

Signed-off-by: James Courtier-Dutton <James@superbug.co.uk>
sound/pci/Kconfig
sound/pci/ca0106/Makefile
sound/pci/ca0106/ca0106.h
sound/pci/ca0106/ca0106_main.c
sound/pci/ca0106/ca_midi.c [new file with mode: 0644]
sound/pci/ca0106/ca_midi.h [new file with mode: 0644]

index d140e93..0fb16cf 100644 (file)
@@ -184,6 +184,7 @@ config SND_CA0106
        tristate "SB Audigy LS / Live 24bit"
        depends on SND
        select SND_AC97_CODEC
+       select SND_RAWMIDI
        help
          Say Y here to include support for the Sound Blaster Audigy LS
          and Live 24bit.
index 89c6cee..dcbae7b 100644 (file)
@@ -1,3 +1,3 @@
-snd-ca0106-objs := ca0106_main.o ca0106_proc.o ca0106_mixer.o
+snd-ca0106-objs := ca0106_main.o ca0106_proc.o ca0106_mixer.o ca_midi.o
 
 obj-$(CONFIG_SND_CA0106) += snd-ca0106.o
index da09cab..9a4b640 100644 (file)
 #define PLAYBACK_VOLUME2        0x6a            /* Playback Analog volume per channel. Does not effect AC3 output */
                                                /* Similar to register 0x66, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */
 #define UNKNOWN6b               0x6b            /* Unknown. Readonly. Default 00400000 00400000 00400000 00400000 */
-#define UART_A_DATA            0x6c            /* Uart, used in setting sample rates, bits per sample etc. */
-#define UART_A_CMD             0x6d            /* Uart, used in setting sample rates, bits per sample etc. */
-#define UART_B_DATA            0x6e            /* Uart, Unknown. */
-#define UART_B_CMD             0x6f            /* Uart, Unknown. */
+#define MIDI_UART_A_DATA               0x6c            /* Midi Uart A Data */
+#define MIDI_UART_A_CMD                0x6d            /* Midi Uart A Command/Status */
+#define MIDI_UART_B_DATA               0x6e            /* Midi Uart B Data (currently unused) */
+#define MIDI_UART_B_CMD                0x6f            /* Midi Uart B Command/Status (currently unused) */
+
+/* unique channel identifier for midi->channel */
+
+#define CA0106_MIDI_CHAN_A             0x1
+#define CA0106_MIDI_CHAN_B             0x2
+
+/* from mpu401 */
+
+#define CA0106_MIDI_INPUT_AVAIL        0x80
+#define CA0106_MIDI_OUTPUT_READY       0x40
+#define CA0106_MPU401_RESET            0xff
+#define CA0106_MPU401_ENTER_UART       0x3f
+#define CA0106_MPU401_ACK              0xfe
+
 #define SAMPLE_RATE_TRACKER_STATUS 0x70         /* Readonly. Default 00108000 00108000 00500000 00500000 */
                                                /* Estimated sample rate [19:0] Relative to 48kHz. 0x8000 =  1.0
                                                 * Rate Locked [20]
 #define CONTROL_CENTER_LFE_CHANNEL 1
 #define CONTROL_UNKNOWN_CHANNEL 2
 
+#include "ca_midi.h"
+
 typedef struct snd_ca0106_channel ca0106_channel_t;
 typedef struct snd_ca0106 ca0106_t;
 typedef struct snd_ca0106_pcm ca0106_pcm_t;
@@ -592,6 +608,9 @@ struct snd_ca0106 {
        int capture_mic_line_in;
 
        struct snd_dma_buffer buffer;
+
+       ca_midi_t midi;
+       ca_midi_t midi2;
 };
 
 int __devinit snd_ca0106_mixer(ca0106_t *emu);
index f6920ef..aaaef9e 100644 (file)
@@ -338,6 +338,18 @@ static void snd_ca0106_intr_enable(ca0106_t *emu, unsigned int intrenb)
        spin_unlock_irqrestore(&emu->emu_lock, flags);
 }
 
+static void snd_ca0106_intr_disable(ca0106_t *emu, unsigned int intrenb)
+{
+       unsigned long flags;
+       unsigned int enable;
+  
+       spin_lock_irqsave(&emu->emu_lock, flags);
+       enable = inl(emu->port + INTE) & ~intrenb;
+       outl(enable, emu->port + INTE);
+       spin_unlock_irqrestore(&emu->emu_lock, flags);
+}
+
+
 static void snd_ca0106_pcm_free_substream(snd_pcm_runtime_t *runtime)
 {
        kfree(runtime->private_data);
@@ -1040,6 +1052,15 @@ static irqreturn_t snd_ca0106_interrupt(int irq, void *dev_id,
 
         snd_ca0106_ptr_write(chip, EXTENDED_INT, 0, stat76);
        spin_lock(&chip->emu_lock);
+
+       if (chip->midi.dev_id &&
+         (status & (chip->midi.ipr_tx|chip->midi.ipr_rx))) {
+               if (chip->midi.interrupt)
+                       chip->midi.interrupt(&chip->midi, status);
+               else
+                       chip->midi.interrupt_disable(&chip->midi, chip->midi.tx_enable | chip->midi.rx_enable);
+       }
+
        // acknowledge the interrupt if necessary
        outl(status, chip->port+IPR);
 
@@ -1309,6 +1330,82 @@ static int __devinit snd_ca0106_create(snd_card_t *card,
        return 0;
 }
 
+
+void ca0106_midi_interrupt_enable(ca_midi_t *midi, int intr){
+       snd_ca0106_intr_enable((ca0106_t *)(midi->dev_id), intr);
+}
+
+void ca0106_midi_interrupt_disable(ca_midi_t *midi, int intr){
+       snd_ca0106_intr_disable((ca0106_t *)(midi->dev_id), intr);
+}
+
+unsigned char ca0106_midi_read(ca_midi_t *midi, int idx){
+       return (unsigned char)snd_ca0106_ptr_read((ca0106_t *)(midi->dev_id), midi->port + idx, 0);
+}
+
+void ca0106_midi_write(ca_midi_t *midi, int data, int idx){
+       snd_ca0106_ptr_write((ca0106_t *)(midi->dev_id), midi->port + idx, 0, data);
+}
+
+snd_card_t *ca0106_dev_id_card(void *dev_id){
+       return ((ca0106_t *)dev_id)->card;
+}
+
+int ca0106_dev_id_port(void *dev_id){
+       return ((ca0106_t *)dev_id)->port;
+}
+
+static int __devinit snd_ca0106_midi(ca0106_t *chip, unsigned int channel)
+{
+       ca_midi_t *midi;
+       char *name;
+       int err;
+
+        if(channel==CA0106_MIDI_CHAN_B) {
+               name = "CA0106 MPU-401 (UART) B";
+               midi =  &chip->midi2;
+               midi->tx_enable = INTE_MIDI_TX_B;
+               midi->rx_enable = INTE_MIDI_RX_B;
+               midi->ipr_tx = IPR_MIDI_TX_B;
+               midi->ipr_rx = IPR_MIDI_RX_B;
+               midi->port = MIDI_UART_B_DATA;
+       } else {
+               name = "CA0106 MPU-401 (UART)";
+               midi =  &chip->midi;
+               midi->tx_enable = INTE_MIDI_TX_A;
+               midi->rx_enable = INTE_MIDI_TX_B;
+               midi->ipr_tx = IPR_MIDI_TX_A;
+               midi->ipr_rx = IPR_MIDI_RX_A;
+               midi->port = MIDI_UART_A_DATA;
+       }
+
+       midi->reset = CA0106_MPU401_RESET;
+       midi->enter_uart = CA0106_MPU401_ENTER_UART;
+       midi->ack = CA0106_MPU401_ACK;
+
+       midi->input_avail = CA0106_MIDI_INPUT_AVAIL;
+       midi->output_ready = CA0106_MIDI_OUTPUT_READY;
+
+       midi->channel = channel;
+
+       midi->interrupt_enable = ca0106_midi_interrupt_enable;
+       midi->interrupt_disable = ca0106_midi_interrupt_disable;
+
+       midi->read = ca0106_midi_read;
+       midi->write = ca0106_midi_write;
+
+       midi->get_dev_id_card = ca0106_dev_id_card;
+       midi->get_dev_id_port = ca0106_dev_id_port;
+
+       midi->dev_id = chip;
+       
+       if ((err = ca_midi_init(chip, midi, 0, name)) < 0)
+               return err;
+
+       return 0;
+}
+
+
 static int __devinit snd_ca0106_probe(struct pci_dev *pci,
                                        const struct pci_device_id *pci_id)
 {
@@ -1360,6 +1457,20 @@ static int __devinit snd_ca0106_probe(struct pci_dev *pci,
                return err;
        }
 
+#ifdef CONFIG_SND_DEBUG_DETECT
+       printk("ca0106: probe for MIDI channel A ...");
+#endif
+       if ((err = snd_ca0106_midi(chip,CA0106_MIDI_CHAN_A)) < 0) {
+               snd_card_free(card);
+#ifdef CONFIG_SND_DEBUG_DETECT
+               printk(" failed, err=0x%x\n",err);
+#endif
+               return err;
+       }
+#ifdef CONFIG_SND_DEBUG_DETECT
+       printk(" done.\n");
+#endif
+
        snd_ca0106_proc_init(chip);
 
        if ((err = snd_card_register(card)) < 0) {
diff --git a/sound/pci/ca0106/ca_midi.c b/sound/pci/ca0106/ca_midi.c
new file mode 100644 (file)
index 0000000..f439d14
--- /dev/null
@@ -0,0 +1,304 @@
+/* 
+ *  Copyright 10/16/2005 Tilman Kranz <tilde@tk-sls.de>
+ *  Creative Audio MIDI, for the CA0106 Driver
+ *  Version: 0.0.1
+ *
+ *  Changelog:
+ *    Implementation is based on mpu401 and emu10k1x and
+ *    tested with ca0106.
+ *
+ *   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
+ *
+ *
+ */
+
+#include <linux/spinlock.h>
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/rawmidi.h>
+
+#include "ca_midi.h"
+
+#define ca_midi_write_data(midi, data) midi->write(midi, data, 0)
+#define ca_midi_write_cmd(midi, data)  midi->write(midi, data, 1)
+#define ca_midi_read_data(midi)                midi->read(midi, 0)
+#define ca_midi_read_stat(midi)                midi->read(midi, 1)
+#define ca_midi_input_avail(midi)      (!(ca_midi_read_stat(midi) & midi->input_avail))
+#define ca_midi_output_ready(midi)     (!(ca_midi_read_stat(midi) & midi->output_ready))
+
+static void ca_midi_clear_rx(ca_midi_t *midi)
+{
+       int timeout = 100000;
+       for (; timeout > 0 && ca_midi_input_avail(midi); timeout--)
+               ca_midi_read_data(midi);
+#ifdef CONFIG_SND_DEBUG
+       if (timeout <= 0)
+               snd_printk(KERN_ERR "ca_midi_clear_rx: timeout (status = 0x%x)\n", ca_midi_read_stat(midi));
+#endif
+}
+
+void ca_midi_interrupt(ca_midi_t *midi, unsigned int status) {
+       unsigned char byte;
+
+       if (midi->rmidi == NULL) {
+               midi->interrupt_disable(midi,midi->tx_enable | midi->rx_enable);
+               return;
+       }
+
+       spin_lock(&midi->input_lock);
+       if ((status & midi->ipr_rx) && ca_midi_input_avail(midi)) {
+               if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
+                       ca_midi_clear_rx(midi);
+               } else {
+                       byte = ca_midi_read_data(midi);
+                       if(midi->substream_input)
+                               snd_rawmidi_receive(midi->substream_input, &byte, 1);
+
+
+               }
+       }
+       spin_unlock(&midi->input_lock);
+
+       spin_lock(&midi->output_lock);
+       if ((status & midi->ipr_tx) && ca_midi_output_ready(midi)) {
+               if (midi->substream_output &&
+                   snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) {
+                       ca_midi_write_data(midi, byte);
+               } else {
+                       midi->interrupt_disable(midi,midi->tx_enable);
+               }
+       }
+       spin_unlock(&midi->output_lock);
+
+}
+
+static void ca_midi_cmd(ca_midi_t *midi, unsigned char cmd, int ack)
+{
+       unsigned long flags;
+       int timeout, ok;
+
+       spin_lock_irqsave(&midi->input_lock, flags);
+       ca_midi_write_data(midi, 0x00);
+       /* ca_midi_clear_rx(midi); */
+
+       ca_midi_write_cmd(midi, cmd);
+       if (ack) {
+               ok = 0;
+               timeout = 10000;
+               while (!ok && timeout-- > 0) {
+                       if (ca_midi_input_avail(midi)) {
+                               if (ca_midi_read_data(midi) == midi->ack)
+                                       ok = 1;
+                       }
+               }
+               if (!ok && ca_midi_read_data(midi) == midi->ack)
+                       ok = 1;
+       } else {
+               ok = 1;
+       }
+       spin_unlock_irqrestore(&midi->input_lock, flags);
+       if (!ok)
+               snd_printk(KERN_ERR "ca_midi_cmd: 0x%x failed at 0x%x (status = 0x%x, data = 0x%x)!!!\n",
+                          cmd,
+                          midi->get_dev_id_port(midi->dev_id),
+                          ca_midi_read_stat(midi),
+                          ca_midi_read_data(midi));
+}
+
+static int ca_midi_input_open(snd_rawmidi_substream_t * substream)
+{
+       ca_midi_t *midi = (ca_midi_t *)substream->rmidi->private_data;
+       unsigned long flags;
+       
+       snd_assert(midi->dev_id, return -ENXIO);
+       spin_lock_irqsave(&midi->open_lock, flags);
+       midi->midi_mode |= CA_MIDI_MODE_INPUT;
+       midi->substream_input = substream;
+       if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {
+               spin_unlock_irqrestore(&midi->open_lock, flags);
+               ca_midi_cmd(midi, midi->reset, 1);
+               ca_midi_cmd(midi, midi->enter_uart, 1);
+       } else {
+               spin_unlock_irqrestore(&midi->open_lock, flags);
+       }
+       return 0;
+}
+
+static int ca_midi_output_open(snd_rawmidi_substream_t * substream)
+{
+       ca_midi_t *midi = (ca_midi_t *)substream->rmidi->private_data;
+       unsigned long flags;
+
+       snd_assert(midi->dev_id, return -ENXIO);
+       spin_lock_irqsave(&midi->open_lock, flags);
+       midi->midi_mode |= CA_MIDI_MODE_OUTPUT;
+       midi->substream_output = substream;
+       if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
+               spin_unlock_irqrestore(&midi->open_lock, flags);
+               ca_midi_cmd(midi, midi->reset, 1);
+               ca_midi_cmd(midi, midi->enter_uart, 1);
+       } else {
+               spin_unlock_irqrestore(&midi->open_lock, flags);
+       }
+       return 0;
+}
+
+static int ca_midi_input_close(snd_rawmidi_substream_t * substream)
+{
+       ca_midi_t *midi = (ca_midi_t *)substream->rmidi->private_data;
+       unsigned long flags;
+
+       snd_assert(midi->dev_id, return -ENXIO);
+       spin_lock_irqsave(&midi->open_lock, flags);
+       midi->interrupt_disable(midi,midi->rx_enable);
+       midi->midi_mode &= ~CA_MIDI_MODE_INPUT;
+       midi->substream_input = NULL;
+       if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {
+               spin_unlock_irqrestore(&midi->open_lock, flags);
+               ca_midi_cmd(midi, midi->reset, 0);
+       } else {
+               spin_unlock_irqrestore(&midi->open_lock, flags);
+       }
+       return 0;
+}
+
+static int ca_midi_output_close(snd_rawmidi_substream_t * substream)
+{
+       ca_midi_t *midi = (ca_midi_t *)substream->rmidi->private_data;
+       unsigned long flags;
+       snd_assert(midi->dev_id, return -ENXIO);
+       
+       spin_lock_irqsave(&midi->open_lock, flags);
+
+       midi->interrupt_disable(midi,midi->tx_enable);
+       midi->midi_mode &= ~CA_MIDI_MODE_OUTPUT;
+       midi->substream_output = NULL;
+       
+       if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
+               spin_unlock_irqrestore(&midi->open_lock, flags);
+               ca_midi_cmd(midi, midi->reset, 0);
+       } else {
+               spin_unlock_irqrestore(&midi->open_lock, flags);
+       }
+       return 0;
+}
+
+static void ca_midi_input_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+       ca_midi_t *midi = (ca_midi_t *)substream->rmidi->private_data;
+       snd_assert(midi->dev_id, return);
+
+       if (up) {
+               midi->interrupt_enable(midi,midi->rx_enable);
+       } else {
+               midi->interrupt_disable(midi, midi->rx_enable);
+       }
+}
+
+static void ca_midi_output_trigger(snd_rawmidi_substream_t * substream, int up)
+{
+       ca_midi_t *midi = (ca_midi_t *)substream->rmidi->private_data;
+       unsigned long flags;
+
+       snd_assert(midi->dev_id, return);
+
+       if (up) {
+               int max = 4;
+               unsigned char byte;
+
+               spin_lock_irqsave(&midi->output_lock, flags);
+       
+               /* try to send some amount of bytes here before interrupts */
+               while (max > 0) {
+                       if (ca_midi_output_ready(midi)) {
+                               if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT) ||
+                                   snd_rawmidi_transmit(substream, &byte, 1) != 1) {
+                                       /* no more data */
+                                       spin_unlock_irqrestore(&midi->output_lock, flags);
+                                       return;
+                               }
+                               ca_midi_write_data(midi, byte);
+                               max--;
+                       } else {
+                               break;
+                       }
+               }
+
+               spin_unlock_irqrestore(&midi->output_lock, flags);
+               midi->interrupt_enable(midi,midi->tx_enable);
+
+       } else {
+               midi->interrupt_disable(midi,midi->tx_enable);
+       }
+}
+
+static snd_rawmidi_ops_t ca_midi_output =
+{
+       .open =         ca_midi_output_open,
+       .close =        ca_midi_output_close,
+       .trigger =      ca_midi_output_trigger,
+};
+
+static snd_rawmidi_ops_t ca_midi_input =
+{
+       .open =         ca_midi_input_open,
+       .close =        ca_midi_input_close,
+       .trigger =      ca_midi_input_trigger,
+};
+
+void ca_midi_free(ca_midi_t *midi) {
+       midi->interrupt = NULL;
+       midi->interrupt_enable = NULL;
+       midi->interrupt_disable = NULL;
+       midi->read = NULL;
+       midi->write = NULL;
+       midi->get_dev_id_card = NULL;
+       midi->get_dev_id_port = NULL;
+       midi->rmidi = NULL;
+}
+
+static void ca_rmidi_free(snd_rawmidi_t *rmidi)
+{
+       ca_midi_free((ca_midi_t *)rmidi->private_data);
+}
+
+int __devinit ca_midi_init(void *dev_id, ca_midi_t *midi, int device, char *name)
+{
+       snd_rawmidi_t *rmidi;
+       int err;
+
+       if ((err = snd_rawmidi_new(midi->get_dev_id_card(midi->dev_id), name, device, 1, 1, &rmidi)) < 0)
+               return err;
+
+       midi->dev_id = dev_id;
+       midi->interrupt = ca_midi_interrupt;
+
+       spin_lock_init(&midi->open_lock);
+       spin_lock_init(&midi->input_lock);
+       spin_lock_init(&midi->output_lock);
+
+       strcpy(rmidi->name, name);
+       snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &ca_midi_output);
+       snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &ca_midi_input);
+       rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
+                            SNDRV_RAWMIDI_INFO_INPUT |
+                            SNDRV_RAWMIDI_INFO_DUPLEX;
+       rmidi->private_data = midi;
+       rmidi->private_free = ca_rmidi_free;
+       
+       midi->rmidi = rmidi;
+       return 0;
+}
+
diff --git a/sound/pci/ca0106/ca_midi.h b/sound/pci/ca0106/ca_midi.h
new file mode 100644 (file)
index 0000000..95c8e1c
--- /dev/null
@@ -0,0 +1,72 @@
+/* 
+ *  Copyright 10/16/2005 Tilman Kranz <tilde@tk-sls.de>
+ *  Creative Audio MIDI, for the CA0106 Driver
+ *  Version: 0.0.1
+ *
+ *  Changelog:
+ *    See ca_midi.c
+ *
+ *   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
+ *
+ */
+
+#include<linux/spinlock.h>
+#include<sound/rawmidi.h>
+#include<sound/mpu401.h>
+
+#define CA_MIDI_MODE_INPUT     MPU401_MODE_INPUT
+#define CA_MIDI_MODE_OUTPUT    MPU401_MODE_OUTPUT
+
+typedef struct ca_midi ca_midi_t;
+struct ca_midi {
+
+       snd_rawmidi_t *rmidi;
+       snd_rawmidi_substream_t *substream_input;
+       snd_rawmidi_substream_t *substream_output;
+
+       void *dev_id;
+
+       spinlock_t input_lock;
+       spinlock_t output_lock;
+       spinlock_t open_lock;
+
+       unsigned int channel;
+
+       unsigned int midi_mode;
+       int port;
+       int tx_enable, rx_enable;
+       int ipr_tx, ipr_rx;            
+       
+       int input_avail, output_ready;
+       int ack, reset, enter_uart;
+
+       void (*interrupt)(ca_midi_t *midi, unsigned int status);
+       void (*interrupt_enable)(ca_midi_t *midi, int intr);
+       void (*interrupt_disable)(ca_midi_t *midi, int intr);
+
+       unsigned char (*read)(ca_midi_t *midi, int idx);
+       void (*write)(ca_midi_t *midi, int data, int idx);
+
+       /* get info from dev_id */
+       snd_card_t *(*get_dev_id_card)(void *dev_id);
+       int (*get_dev_id_port)(void *dev_id);
+};
+
+void ca_midi_interrupt(ca_midi_t *midi, unsigned int status);
+
+int __devinit ca_midi_init(void *card, ca_midi_t *midi, int device, char *name);
+
+void ca_midi_free(ca_midi_t *midi);
+