mirror of
https://github.com/mist-devel/mist-board.git
synced 2026-02-26 16:23:32 +00:00
MIDI out redirection
This commit is contained in:
@@ -13,14 +13,19 @@ module acia (
|
||||
output midi_out,
|
||||
input midi_in,
|
||||
|
||||
// data from io controller to acia
|
||||
// data from io controller to ikbd acia
|
||||
input ikbd_strobe_in,
|
||||
input [7:0] ikbd_data_in,
|
||||
|
||||
// data from acia to io controller
|
||||
// data from ikbd acia to io controller
|
||||
output ikbd_data_out_available,
|
||||
input ikbd_strobe_out,
|
||||
output [7:0] ikbd_data_out
|
||||
output [7:0] ikbd_data_out,
|
||||
|
||||
// data from midi acia to io controller
|
||||
output midi_data_out_available,
|
||||
input midi_strobe_out,
|
||||
output [7:0] midi_data_out
|
||||
);
|
||||
|
||||
// --- ikbd output fifo ---
|
||||
@@ -32,7 +37,7 @@ io_fifo ikbd_out_fifo (
|
||||
.in_clk (!clk), // latch incoming data on negedge
|
||||
.in (din),
|
||||
.in_strobe (1'b0),
|
||||
.in_enable (sel && ~ds && ~rw && (addr == 2'd1)),
|
||||
.in_enable (sel && ~ds && ~rw && (addr == 2'd1)), // ikbd acia data write
|
||||
|
||||
.out_clk (clk),
|
||||
.out (ikbd_data_out),
|
||||
@@ -61,6 +66,26 @@ io_fifo ikbd_in_fifo (
|
||||
.data_available (ikbd_rx_data_available)
|
||||
);
|
||||
|
||||
// --- midi output fifo ---
|
||||
// filled by the CPU when writing to the acia data register
|
||||
// emptied by the io controller when reading via SPI
|
||||
// This happens in parallel to the real midi generation, so
|
||||
// physical and USB MIDI can be used at the same time
|
||||
io_fifo midi_out_fifo (
|
||||
.reset (reset),
|
||||
|
||||
.in_clk (!clk), // latch incoming data on negedge
|
||||
.in (din),
|
||||
.in_strobe (1'b0),
|
||||
.in_enable (sel && ~ds && ~rw && (addr == 2'd3)), // midi acia data write
|
||||
|
||||
.out_clk (clk),
|
||||
.out (midi_data_out),
|
||||
.out_strobe (midi_strobe_out),
|
||||
.out_enable (1'b0),
|
||||
|
||||
.data_available (midi_data_out_available)
|
||||
);
|
||||
// timer to let bytes arrive at a reasonable speed
|
||||
reg [13:0] readTimer;
|
||||
|
||||
|
||||
@@ -51,9 +51,6 @@ wire [22:0] video_address;
|
||||
wire st_de, st_hs, st_vs;
|
||||
wire [15:0] video_adj;
|
||||
|
||||
// on-board io
|
||||
wire [1:0] buttons;
|
||||
|
||||
// generate dtack for all implemented peripherals
|
||||
wire io_dtack = vreg_sel || mmu_sel || mfp_sel || mfp_iack ||
|
||||
acia_sel || psg_sel || dma_sel || auto_iack || blitter_sel ||
|
||||
@@ -330,16 +327,21 @@ acia acia (
|
||||
.dout (acia_data_out),
|
||||
.irq (acia_irq ),
|
||||
|
||||
// MIDI interface
|
||||
// physical MIDI interface
|
||||
.midi_out (UART_TX ),
|
||||
.midi_in (UART_RX ),
|
||||
|
||||
|
||||
// ikbd interface
|
||||
.ikbd_data_out_available (ikbd_data_from_acia_available),
|
||||
.ikbd_strobe_out (ikbd_strobe_from_acia),
|
||||
.ikbd_data_out (ikbd_data_from_acia),
|
||||
.ikbd_strobe_in (ikbd_strobe_to_acia),
|
||||
.ikbd_data_in (ikbd_data_to_acia)
|
||||
.ikbd_data_in (ikbd_data_to_acia),
|
||||
|
||||
// redirected midi interface
|
||||
.midi_data_out_available (midi_data_from_acia_available),
|
||||
.midi_strobe_out (midi_strobe_from_acia),
|
||||
.midi_data_out (midi_data_from_acia)
|
||||
);
|
||||
|
||||
wire [23:1] blitter_master_addr;
|
||||
@@ -1057,6 +1059,11 @@ wire [7:0] ikbd_data_from_acia;
|
||||
wire ikbd_strobe_from_acia;
|
||||
wire ikbd_data_from_acia_available;
|
||||
|
||||
// connection to transfer midi data from acia to io controller
|
||||
wire [7:0] midi_data_from_acia;
|
||||
wire midi_strobe_from_acia;
|
||||
wire midi_data_from_acia_available;
|
||||
|
||||
// connection to transfer serial/rs232 data from mfp to io controller
|
||||
wire [7:0] serial_data_from_mfp;
|
||||
wire serial_strobe_from_mfp;
|
||||
@@ -1071,11 +1078,11 @@ wire parallel_data_out_available;
|
||||
|
||||
//// user io has an extra spi channel outside minimig core ////
|
||||
user_io user_io(
|
||||
// the spi interface
|
||||
.SPI_CLK(SPI_SCK),
|
||||
.SPI_SS_IO(CONF_DATA0),
|
||||
.SPI_MISO(user_io_sdo),
|
||||
.SPI_MOSI(SPI_DI),
|
||||
.BUTTONS(buttons),
|
||||
|
||||
// ikbd interface
|
||||
.ikbd_strobe_out(ikbd_strobe_from_acia),
|
||||
@@ -1096,6 +1103,11 @@ user_io user_io(
|
||||
.parallel_data_out(parallel_data_out),
|
||||
.parallel_data_out_available(parallel_data_out_available),
|
||||
|
||||
// midi interface
|
||||
.midi_strobe_out(midi_strobe_from_acia),
|
||||
.midi_data_out(midi_data_from_acia),
|
||||
.midi_data_out_available(midi_data_from_acia_available),
|
||||
|
||||
.CORE_TYPE(8'ha3) // mist core id
|
||||
);
|
||||
|
||||
|
||||
@@ -28,6 +28,11 @@ module user_io(
|
||||
input parallel_data_out_available,
|
||||
input [7:0] parallel_data_out,
|
||||
|
||||
// midi data from acia to io controller
|
||||
output reg midi_strobe_out,
|
||||
input midi_data_out_available,
|
||||
input [7:0] midi_data_out,
|
||||
|
||||
output [1:0] BUTTONS,
|
||||
output [1:0] SWITCHES
|
||||
);
|
||||
@@ -68,6 +73,14 @@ module user_io(
|
||||
else
|
||||
SPI_MISO <= parallel_data_out[15-cnt];
|
||||
end
|
||||
|
||||
// midi->io controller
|
||||
if(cmd == 8) begin
|
||||
if(!toggle)
|
||||
SPI_MISO <= midi_data_out_available;
|
||||
else
|
||||
SPI_MISO <= midi_data_out[15-cnt];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -79,6 +92,7 @@ module user_io(
|
||||
ikbd_strobe_out <= 1'b0;
|
||||
serial_strobe_out <= 1'b0;
|
||||
parallel_strobe_out <= 1'b0;
|
||||
midi_strobe_out <= 1'b0;
|
||||
end else begin
|
||||
sbuf[6:1] <= sbuf[5:0];
|
||||
sbuf[0] <= SPI_MOSI;
|
||||
@@ -103,6 +117,7 @@ module user_io(
|
||||
serial_strobe_in <= 1'b0;
|
||||
serial_strobe_out <= 1'b0;
|
||||
parallel_strobe_out <= 1'b0;
|
||||
midi_strobe_out <= 1'b0;
|
||||
end
|
||||
|
||||
// payload byte
|
||||
@@ -135,6 +150,10 @@ module user_io(
|
||||
// give strobe after second parallel byte (toggle ==1)
|
||||
if((cmd == 6) && toggle)
|
||||
parallel_strobe_out <= 1'b1;
|
||||
|
||||
// give strobe after second midi byte (toggle ==1)
|
||||
if((cmd == 8) && toggle)
|
||||
midi_strobe_out <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
8
tools/ttymidi/Makefile
Normal file
8
tools/ttymidi/Makefile
Normal file
@@ -0,0 +1,8 @@
|
||||
all:
|
||||
gcc src/ttymidi.c -o ttymidi -lasound -lpthread
|
||||
clean:
|
||||
rm ttymidi
|
||||
install:
|
||||
install -m 0755 ttymidi /usr/local/bin
|
||||
uninstall:
|
||||
rm /usr/local/bin/ttymidi
|
||||
87
tools/ttymidi/README
Normal file
87
tools/ttymidi/README
Normal file
@@ -0,0 +1,87 @@
|
||||
ttyMIDI is a GPL-licensed program that allows external serial devices to
|
||||
interface with the ALSA sequencer.
|
||||
|
||||
|
||||
COMPILATION
|
||||
|
||||
The ttyMIDI source code is comprised of a single C file. To compile it, just
|
||||
run the following command:
|
||||
|
||||
make
|
||||
|
||||
This program depends on libasound2, so you should have the development headers
|
||||
for that installed. In Debian or Ubuntu, you can install it by running:
|
||||
|
||||
apt-get install libasound2-dev
|
||||
|
||||
After you compile ttyMIDI, you may wish to copy it to /usr/bin for easy
|
||||
access. This can be done simply by following command:
|
||||
|
||||
sudo make install
|
||||
|
||||
USAGE
|
||||
|
||||
First, you need an external device that can send MIDI commands through the
|
||||
serial port. To find out more about programming an external device, read the
|
||||
TTYMIDI SPECIFICATION section. If you are using an Arduino board
|
||||
(http://www.arduino.cc), read the instructions under the arduino folder. Once
|
||||
your device is programmed and connected to your PC's serial port, follow the
|
||||
instructions below.
|
||||
|
||||
To connect to ttyS0 at 2400bps:
|
||||
|
||||
ttymidi -s /dev/ttyS0 -b 2400
|
||||
|
||||
To connect to ttyUSB port at default speed (115200bps) and display information
|
||||
about incoming MIDI events:
|
||||
|
||||
ttymidi -s /dev/ttyUSB0 -v
|
||||
|
||||
ttyMIDI creates an ALSA MIDI output port that can be interfaced to any
|
||||
compatible program. This is done in the following manner:
|
||||
|
||||
ttymidi -s /dev/ttyUSB0 & # start ttyMIDI
|
||||
timidity -iA & # start some ALSA compatible MIDI program
|
||||
aconnect -i # list available MIDI input clients
|
||||
aconnect -o # list available MIDI output clients
|
||||
aconnect 128:0 129:0 # where 128 and 129 are the client numbers for
|
||||
# ttyMIDI and timidity
|
||||
|
||||
Further, ttyMIDI creates an ALSA MIDI input port that feeds incoming MIDI events
|
||||
back to the serial port. Before better documentation exists, check the header file of
|
||||
the ardumidi library to figure out how to read this data at the Arduino end.
|
||||
|
||||
If you would like to use a GUI to connect your MIDI clients, there are many
|
||||
available. One of my favorites is qjackctl.
|
||||
|
||||
|
||||
TTYMIDI SPECIFICATION
|
||||
|
||||
The message format expected by ttyMIDI is based on what I could gather about the
|
||||
MIDI specification online. I tried to make it as similar as possible to the
|
||||
specification, but some differences exist. The good news is that as long as you
|
||||
follow the specification described below, everything should work.
|
||||
|
||||
Every MIDI command is sent through the serial port as 3 bytes. The first byte
|
||||
contains the command type and channel. After that, 2 parameter bytes are
|
||||
transmitted. To simplify the decoding process, ttyMIDI does not support
|
||||
"running status", and it also forces every command into 3 bytes. So even
|
||||
commands which only have 1 parameter must transmit byte #3 (transmitting a 0 in
|
||||
this case). This is described in more details in the table below:
|
||||
|
||||
byte1 byte2 byte3 Command name
|
||||
|
||||
0x80-0x8F Key # (0-127) Off Velocity (0-127) Note OFF
|
||||
0x90-0x90 Key # (0-127) On Velocity (0-127) Note ON
|
||||
0xA0-0xA0 Key # (0-127) Pressure (0-127) Poly Key Pressure
|
||||
0xB0-0xB0 Control # (0-127) Control Value (0-127) Control Change
|
||||
0xC0-0xC0 Program # (0-127) Not Used (send 0) Program Change
|
||||
0xD0-0xD0 Pressure Value (0-127) Not Used (send 0) Mono Key Pressure (Channel Pressure)
|
||||
0xE0-0xE0 Range LSB (0-127) Range MSB (0-127) Pitch Bend
|
||||
|
||||
Not implemented:
|
||||
0xF0-0xF0 Manufacturer's ID Model ID System
|
||||
|
||||
Byte #1 is given as COMMAND + CHANNEL. So, for example, 0xE3 is the Pitch Bend
|
||||
command (0xE0) for channel 4 (0x03).
|
||||
|
||||
590
tools/ttymidi/src/ttymidi.c
Normal file
590
tools/ttymidi/src/ttymidi.c
Normal file
@@ -0,0 +1,590 @@
|
||||
/*
|
||||
This file is part of ttymidi.
|
||||
|
||||
ttymidi 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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
ttymidi 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 ttymidi. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <termios.h>
|
||||
#include <stdio.h>
|
||||
#include <argp.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
// Linux-specific
|
||||
#include <linux/serial.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <asm/ioctls.h>
|
||||
|
||||
#define FALSE 0
|
||||
#define TRUE 1
|
||||
|
||||
#define MAX_DEV_STR_LEN 32
|
||||
#define MAX_MSG_SIZE 1024
|
||||
|
||||
/* change this definition for the correct port */
|
||||
//#define _POSIX_SOURCE 1 /* POSIX compliant source */
|
||||
|
||||
int run;
|
||||
int serial;
|
||||
int port_out_id;
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
// Program options
|
||||
|
||||
static struct argp_option options[] =
|
||||
{
|
||||
{"serialdevice" , 's', "DEV" , 0, "Serial device to use. Default = /dev/ttyUSB0" },
|
||||
{"baudrate" , 'b', "BAUD", 0, "Serial port baud rate. Default = 115200" },
|
||||
{"verbose" , 'v', 0 , 0, "For debugging: Produce verbose output" },
|
||||
{"printonly" , 'p', 0 , 0, "Super debugging: Print values read from serial -- and do nothing else" },
|
||||
{"quiet" , 'q', 0 , 0, "Don't produce any output, even when the print command is sent" },
|
||||
{"name" , 'n', "NAME", 0, "Name of the Alsa MIDI client. Default = ttymidi" },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
typedef struct _arguments
|
||||
{
|
||||
int silent, verbose, printonly;
|
||||
char serialdevice[MAX_DEV_STR_LEN];
|
||||
int baudrate;
|
||||
char name[MAX_DEV_STR_LEN];
|
||||
} arguments_t;
|
||||
|
||||
void exit_cli(int sig)
|
||||
{
|
||||
run = FALSE;
|
||||
printf("\rttymidi closing down ... ");
|
||||
}
|
||||
|
||||
static error_t parse_opt (int key, char *arg, struct argp_state *state)
|
||||
{
|
||||
/* Get the input argument from argp_parse, which we
|
||||
know is a pointer to our arguments structure. */
|
||||
arguments_t *arguments = state->input;
|
||||
int baud_temp;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case 'p':
|
||||
arguments->printonly = 1;
|
||||
break;
|
||||
case 'q':
|
||||
arguments->silent = 1;
|
||||
break;
|
||||
case 'v':
|
||||
arguments->verbose = 1;
|
||||
break;
|
||||
case 's':
|
||||
if (arg == NULL) break;
|
||||
strncpy(arguments->serialdevice, arg, MAX_DEV_STR_LEN);
|
||||
break;
|
||||
case 'n':
|
||||
if (arg == NULL) break;
|
||||
strncpy(arguments->name, arg, MAX_DEV_STR_LEN);
|
||||
break;
|
||||
case 'b':
|
||||
if (arg == NULL) break;
|
||||
baud_temp = strtol(arg, NULL, 0);
|
||||
if (baud_temp != EINVAL && baud_temp != ERANGE)
|
||||
switch (baud_temp)
|
||||
{
|
||||
case 1200 : arguments->baudrate = B1200 ; break;
|
||||
case 2400 : arguments->baudrate = B2400 ; break;
|
||||
case 4800 : arguments->baudrate = B4800 ; break;
|
||||
case 9600 : arguments->baudrate = B9600 ; break;
|
||||
case 19200 : arguments->baudrate = B19200 ; break;
|
||||
case 38400 : arguments->baudrate = B38400 ; break;
|
||||
case 57600 : arguments->baudrate = B57600 ; break;
|
||||
case 115200 : arguments->baudrate = B115200; break;
|
||||
default: printf("Baud rate %i is not supported.\n",baud_temp); exit(1);
|
||||
}
|
||||
|
||||
case ARGP_KEY_ARG:
|
||||
case ARGP_KEY_END:
|
||||
break;
|
||||
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void arg_set_defaults(arguments_t *arguments)
|
||||
{
|
||||
char *serialdevice_temp = "/dev/ttyUSB0";
|
||||
arguments->printonly = 0;
|
||||
arguments->silent = 0;
|
||||
arguments->verbose = 0;
|
||||
arguments->baudrate = B115200;
|
||||
char *name_tmp = (char *)"ttymidi";
|
||||
strncpy(arguments->serialdevice, serialdevice_temp, MAX_DEV_STR_LEN);
|
||||
strncpy(arguments->name, name_tmp, MAX_DEV_STR_LEN);
|
||||
}
|
||||
|
||||
const char *argp_program_version = "ttymidi 0.60";
|
||||
const char *argp_program_bug_address = "tvst@hotmail.com";
|
||||
static char doc[] = "ttymidi - Connect serial port devices to ALSA MIDI programs!";
|
||||
static struct argp argp = { options, parse_opt, 0, doc };
|
||||
arguments_t arguments;
|
||||
|
||||
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
// MIDI stuff
|
||||
|
||||
int open_seq(snd_seq_t** seq)
|
||||
{
|
||||
int port_out_id, port_in_id; // actually port_in_id is not needed nor used anywhere
|
||||
|
||||
if (snd_seq_open(seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0)
|
||||
{
|
||||
fprintf(stderr, "Error opening ALSA sequencer.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
snd_seq_set_client_name(*seq, arguments.name);
|
||||
|
||||
if ((port_out_id = snd_seq_create_simple_port(*seq, "MIDI out",
|
||||
SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
|
||||
SND_SEQ_PORT_TYPE_APPLICATION)) < 0)
|
||||
{
|
||||
fprintf(stderr, "Error creating sequencer port.\n");
|
||||
}
|
||||
|
||||
if ((port_in_id = snd_seq_create_simple_port(*seq, "MIDI in",
|
||||
SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
|
||||
SND_SEQ_PORT_TYPE_APPLICATION)) < 0)
|
||||
{
|
||||
fprintf(stderr, "Error creating sequencer port.\n");
|
||||
}
|
||||
|
||||
return port_out_id;
|
||||
}
|
||||
|
||||
void parse_midi_command(snd_seq_t* seq, int port_out_id, char *buf)
|
||||
{
|
||||
/*
|
||||
MIDI COMMANDS
|
||||
-------------------------------------------------------------------
|
||||
name status param 1 param 2
|
||||
-------------------------------------------------------------------
|
||||
note off 0x80+C key # velocity
|
||||
note on 0x90+C key # velocity
|
||||
poly key pressure 0xA0+C key # pressure value
|
||||
control change 0xB0+C control # control value
|
||||
program change 0xC0+C program # --
|
||||
mono key pressure 0xD0+C pressure value --
|
||||
pitch bend 0xE0+C range (LSB) range (MSB)
|
||||
system 0xF0+C manufacturer model
|
||||
-------------------------------------------------------------------
|
||||
C is the channel number, from 0 to 15;
|
||||
-------------------------------------------------------------------
|
||||
source: http://ftp.ec.vanderbilt.edu/computermusic/musc216site/MIDI.Commands.html
|
||||
|
||||
In this program the pitch bend range will be transmitter as
|
||||
one single 8-bit number. So the end result is that MIDI commands
|
||||
will be transmitted as 3 bytes, starting with the operation byte:
|
||||
|
||||
buf[0] --> operation/channel
|
||||
buf[1] --> param1
|
||||
buf[2] --> param2 (param2 not transmitted on program change or key press)
|
||||
*/
|
||||
|
||||
snd_seq_event_t ev;
|
||||
snd_seq_ev_clear(&ev);
|
||||
snd_seq_ev_set_direct(&ev);
|
||||
snd_seq_ev_set_source(&ev, port_out_id);
|
||||
snd_seq_ev_set_subs(&ev);
|
||||
|
||||
int operation, channel, param1, param2;
|
||||
|
||||
operation = buf[0] & 0xF0;
|
||||
channel = buf[0] & 0x0F;
|
||||
param1 = buf[1];
|
||||
param2 = buf[2];
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case 0x80:
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Serial 0x%x Note off %03u %03u %03u\n", operation, channel, param1, param2);
|
||||
snd_seq_ev_set_noteoff(&ev, channel, param1, param2);
|
||||
break;
|
||||
|
||||
case 0x90:
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Serial 0x%x Note on %03u %03u %03u\n", operation, channel, param1, param2);
|
||||
snd_seq_ev_set_noteon(&ev, channel, param1, param2);
|
||||
break;
|
||||
|
||||
case 0xA0:
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Serial 0x%x Pressure change %03u %03u %03u\n", operation, channel, param1, param2);
|
||||
snd_seq_ev_set_keypress(&ev, channel, param1, param2);
|
||||
break;
|
||||
|
||||
case 0xB0:
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Serial 0x%x Controller change %03u %03u %03u\n", operation, channel, param1, param2);
|
||||
snd_seq_ev_set_controller(&ev, channel, param1, param2);
|
||||
break;
|
||||
|
||||
case 0xC0:
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Serial 0x%x Program change %03u %03u\n", operation, channel, param1);
|
||||
snd_seq_ev_set_pgmchange(&ev, channel, param1);
|
||||
break;
|
||||
|
||||
case 0xD0:
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Serial 0x%x Channel change %03u %03u\n", operation, channel, param1);
|
||||
snd_seq_ev_set_chanpress(&ev, channel, param1);
|
||||
break;
|
||||
|
||||
case 0xE0:
|
||||
param1 = (param1 & 0x7F) + ((param2 & 0x7F) << 7);
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Serial 0x%x Pitch bend %03u %05i\n", operation, channel, param1);
|
||||
snd_seq_ev_set_pitchbend(&ev, channel, param1 - 8192); // in alsa MIDI we want signed int
|
||||
break;
|
||||
|
||||
/* Not implementing system commands (0xF0) */
|
||||
|
||||
default:
|
||||
if (!arguments.silent)
|
||||
printf("0x%x Unknown MIDI cmd %03u %03u %03u\n", operation, channel, param1, param2);
|
||||
break;
|
||||
}
|
||||
|
||||
snd_seq_event_output_direct(seq, &ev);
|
||||
snd_seq_drain_output(seq);
|
||||
}
|
||||
|
||||
void write_midi_action_to_serial_port(snd_seq_t* seq_handle)
|
||||
{
|
||||
snd_seq_event_t* ev;
|
||||
char bytes[] = {0x00, 0x00, 0xFF};
|
||||
|
||||
do
|
||||
{
|
||||
snd_seq_event_input(seq_handle, &ev);
|
||||
|
||||
switch (ev->type)
|
||||
{
|
||||
|
||||
case SND_SEQ_EVENT_NOTEOFF:
|
||||
bytes[0] = 0x80 + ev->data.control.channel;
|
||||
bytes[1] = ev->data.note.note;
|
||||
bytes[2] = ev->data.note.velocity;
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Alsa 0x%x Note off %03u %03u %03u\n", bytes[0]&0xF0, bytes[0]&0xF, bytes[1], bytes[2]);
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_NOTEON:
|
||||
bytes[0] = 0x90 + ev->data.control.channel;
|
||||
bytes[1] = ev->data.note.note;
|
||||
bytes[2] = ev->data.note.velocity;
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Alsa 0x%x Note on %03u %03u %03u\n", bytes[0]&0xF0, bytes[0]&0xF, bytes[1], bytes[2]);
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_KEYPRESS:
|
||||
bytes[0] = 0x90 + ev->data.control.channel;
|
||||
bytes[1] = ev->data.note.note;
|
||||
bytes[2] = ev->data.note.velocity;
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Alsa 0x%x Pressure change %03u %03u %03u\n", bytes[0]&0xF0, bytes[0]&0xF, bytes[1], bytes[2]);
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_CONTROLLER:
|
||||
bytes[0] = 0xB0 + ev->data.control.channel;
|
||||
bytes[1] = ev->data.control.param;
|
||||
bytes[2] = ev->data.control.value;
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Alsa 0x%x Controller change %03u %03u %03u\n", bytes[0]&0xF0, bytes[0]&0xF, bytes[1], bytes[2]);
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_PGMCHANGE:
|
||||
bytes[0] = 0xC0 + ev->data.control.channel;
|
||||
bytes[1] = ev->data.control.value;
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Alsa 0x%x Program change %03u %03u %03u\n", bytes[0]&0xF0, bytes[0]&0xF, bytes[1], bytes[2]);
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_CHANPRESS:
|
||||
bytes[0] = 0xD0 + ev->data.control.channel;
|
||||
bytes[1] = ev->data.control.value;
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Alsa 0x%x Channel change %03u %03u %03u\n", bytes[0]&0xF0, bytes[0]&0xF, bytes[1], bytes[2]);
|
||||
break;
|
||||
|
||||
case SND_SEQ_EVENT_PITCHBEND:
|
||||
bytes[0] = 0xE0 + ev->data.control.channel;
|
||||
ev->data.control.value += 8192;
|
||||
bytes[1] = (int)ev->data.control.value & 0x7F;
|
||||
bytes[2] = (int)ev->data.control.value >> 7;
|
||||
if (!arguments.silent && arguments.verbose)
|
||||
printf("Alsa 0x%x Pitch bend %03u %5d\n", bytes[0]&0xF0, bytes[0]&0xF, ev->data.control.value);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (bytes[0]!=0x00)
|
||||
{
|
||||
bytes[1] = (bytes[1] & 0x7F); // just to be sure that one bit is really zero
|
||||
if (bytes[2]==0xFF) {
|
||||
write(serial, bytes, 2);
|
||||
} else {
|
||||
bytes[2] = (bytes[2] & 0x7F);
|
||||
write(serial, bytes, 3);
|
||||
}
|
||||
}
|
||||
|
||||
snd_seq_free_event(ev);
|
||||
|
||||
} while (snd_seq_event_input_pending(seq_handle, 0) > 0);
|
||||
}
|
||||
|
||||
|
||||
void* read_midi_from_alsa(void* seq)
|
||||
{
|
||||
int npfd;
|
||||
struct pollfd* pfd;
|
||||
snd_seq_t* seq_handle;
|
||||
|
||||
seq_handle = seq;
|
||||
|
||||
npfd = snd_seq_poll_descriptors_count(seq_handle, POLLIN);
|
||||
pfd = (struct pollfd*) alloca(npfd * sizeof(struct pollfd));
|
||||
snd_seq_poll_descriptors(seq_handle, pfd, npfd, POLLIN);
|
||||
|
||||
while (run)
|
||||
{
|
||||
if (poll(pfd,npfd, 100) > 0)
|
||||
{
|
||||
write_midi_action_to_serial_port(seq_handle);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\nStopping [PC]->[Hardware] communication...");
|
||||
}
|
||||
|
||||
void* read_midi_from_serial_port(void* seq)
|
||||
{
|
||||
char buf[3], msg[MAX_MSG_SIZE];
|
||||
int i, msglen;
|
||||
|
||||
/* Lets first fast forward to first status byte... */
|
||||
if (!arguments.printonly) {
|
||||
do read(serial, buf, 1);
|
||||
while (buf[0] >> 7 == 0);
|
||||
}
|
||||
|
||||
while (run)
|
||||
{
|
||||
/*
|
||||
* super-debug mode: only print to screen whatever
|
||||
* comes through the serial port.
|
||||
*/
|
||||
|
||||
if (arguments.printonly)
|
||||
{
|
||||
read(serial, buf, 1);
|
||||
printf("%x\t", (int) buf[0]&0xFF);
|
||||
fflush(stdout);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* so let's align to the beginning of a midi command.
|
||||
*/
|
||||
|
||||
int i = 1;
|
||||
|
||||
while (i < 3) {
|
||||
read(serial, buf+i, 1);
|
||||
|
||||
if (buf[i] >> 7 != 0) {
|
||||
/* Status byte received and will always be first bit!*/
|
||||
buf[0] = buf[i];
|
||||
i = 1;
|
||||
} else {
|
||||
/* Data byte received */
|
||||
if (i == 2) {
|
||||
/* It was 2nd data byte so we have a MIDI event
|
||||
process! */
|
||||
i = 3;
|
||||
} else {
|
||||
/* Lets figure out are we done or should we read one more byte. */
|
||||
if ((buf[0] & 0xF0) == 0xC0 || (buf[0] & 0xF0) == 0xD0) {
|
||||
i = 3;
|
||||
} else {
|
||||
i = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* print comment message (the ones that start with 0xFF 0x00 0x00 */
|
||||
if (buf[0] == (char) 0xFF && buf[1] == (char) 0x00 && buf[2] == (char) 0x00)
|
||||
{
|
||||
read(serial, buf, 1);
|
||||
msglen = buf[0];
|
||||
if (msglen > MAX_MSG_SIZE-1) msglen = MAX_MSG_SIZE-1;
|
||||
|
||||
read(serial, msg, msglen);
|
||||
|
||||
if (arguments.silent) continue;
|
||||
|
||||
/* make sure the string ends with a null character */
|
||||
msg[msglen] = 0;
|
||||
|
||||
puts("0xFF Non-MIDI message: ");
|
||||
puts(msg);
|
||||
putchar('\n');
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* parse MIDI message */
|
||||
else parse_midi_command(seq, port_out_id, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
// Main program
|
||||
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
//arguments arguments;
|
||||
struct termios oldtio, newtio;
|
||||
struct serial_struct ser_info;
|
||||
char* modem_device = "/dev/ttyS0";
|
||||
snd_seq_t *seq;
|
||||
|
||||
arg_set_defaults(&arguments);
|
||||
argp_parse(&argp, argc, argv, 0, 0, &arguments);
|
||||
|
||||
/*
|
||||
* Open MIDI output port
|
||||
*/
|
||||
|
||||
port_out_id = open_seq(&seq);
|
||||
|
||||
/*
|
||||
* Open modem device for reading and not as controlling tty because we don't
|
||||
* want to get killed if linenoise sends CTRL-C.
|
||||
*/
|
||||
|
||||
serial = open(arguments.serialdevice, O_RDWR | O_NOCTTY );
|
||||
|
||||
if (serial < 0)
|
||||
{
|
||||
perror(arguments.serialdevice);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/* save current serial port settings */
|
||||
tcgetattr(serial, &oldtio);
|
||||
|
||||
/* clear struct for new port settings */
|
||||
bzero(&newtio, sizeof(newtio));
|
||||
|
||||
/*
|
||||
* BAUDRATE : Set bps rate. You could also use cfsetispeed and cfsetospeed.
|
||||
* CRTSCTS : output hardware flow control (only used if the cable has
|
||||
* all necessary lines. See sect. 7 of Serial-HOWTO)
|
||||
* CS8 : 8n1 (8bit, no parity, 1 stopbit)
|
||||
* CLOCAL : local connection, no modem contol
|
||||
* CREAD : enable receiving characters
|
||||
*/
|
||||
newtio.c_cflag = arguments.baudrate | CS8 | CLOCAL | CREAD; // CRTSCTS removed
|
||||
|
||||
/*
|
||||
* IGNPAR : ignore bytes with parity errors
|
||||
* ICRNL : map CR to NL (otherwise a CR input on the other computer
|
||||
* will not terminate input)
|
||||
* otherwise make device raw (no other input processing)
|
||||
*/
|
||||
newtio.c_iflag = IGNPAR;
|
||||
|
||||
/* Raw output */
|
||||
newtio.c_oflag = 0;
|
||||
|
||||
/*
|
||||
* ICANON : enable canonical input
|
||||
* disable all echo functionality, and don't send signals to calling program
|
||||
*/
|
||||
newtio.c_lflag = 0; // non-canonical
|
||||
|
||||
/*
|
||||
* set up: we'll be reading 4 bytes at a time.
|
||||
*/
|
||||
newtio.c_cc[VTIME] = 0; /* inter-character timer unused */
|
||||
newtio.c_cc[VMIN] = 1; /* blocking read until n character arrives */
|
||||
|
||||
/*
|
||||
* now clean the modem line and activate the settings for the port
|
||||
*/
|
||||
tcflush(serial, TCIFLUSH);
|
||||
tcsetattr(serial, TCSANOW, &newtio);
|
||||
|
||||
// Linux-specific: enable low latency mode (FTDI "nagling off")
|
||||
// ioctl(serial, TIOCGSERIAL, &ser_info);
|
||||
// ser_info.flags |= ASYNC_LOW_LATENCY;
|
||||
// ioctl(serial, TIOCSSERIAL, &ser_info);
|
||||
|
||||
if (arguments.printonly)
|
||||
{
|
||||
printf("Super debug mode: Only printing the signal to screen. Nothing else.\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* read commands
|
||||
*/
|
||||
|
||||
/* Starting thread that is polling alsa midi in port */
|
||||
pthread_t midi_out_thread, midi_in_thread;
|
||||
int iret1, iret2;
|
||||
run = TRUE;
|
||||
iret1 = pthread_create(&midi_out_thread, NULL, read_midi_from_alsa, (void*) seq);
|
||||
/* And also thread for polling serial data. As serial is currently read in
|
||||
blocking mode, by this we can enable ctrl+c quiting and avoid zombie
|
||||
alsa ports when killing app with ctrl+z */
|
||||
iret2 = pthread_create(&midi_in_thread, NULL, read_midi_from_serial_port, (void*) seq);
|
||||
signal(SIGINT, exit_cli);
|
||||
signal(SIGTERM, exit_cli);
|
||||
|
||||
while (run)
|
||||
{
|
||||
sleep(100);
|
||||
}
|
||||
|
||||
void* status;
|
||||
pthread_join(midi_out_thread, &status);
|
||||
|
||||
/* restore the old port settings */
|
||||
tcsetattr(serial, TCSANOW, &oldtio);
|
||||
printf("\ndone!\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user