diff --git a/cores/mist/acia.v b/cores/mist/acia.v
index 2686522..dc6cc31 100644
--- a/cores/mist/acia.v
+++ b/cores/mist/acia.v
@@ -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;
diff --git a/cores/mist/mist_top.v b/cores/mist/mist_top.v
index babeecb..ef96467 100644
--- a/cores/mist/mist_top.v
+++ b/cores/mist/mist_top.v
@@ -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
);
diff --git a/cores/mist/user_io.v b/cores/mist/user_io.v
index e919fed..4b9791c 100644
--- a/cores/mist/user_io.v
+++ b/cores/mist/user_io.v
@@ -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
diff --git a/tools/ttymidi/Makefile b/tools/ttymidi/Makefile
new file mode 100644
index 0000000..9d89b5d
--- /dev/null
+++ b/tools/ttymidi/Makefile
@@ -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
diff --git a/tools/ttymidi/README b/tools/ttymidi/README
new file mode 100644
index 0000000..9af340d
--- /dev/null
+++ b/tools/ttymidi/README
@@ -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).
+
diff --git a/tools/ttymidi/src/ttymidi.c b/tools/ttymidi/src/ttymidi.c
new file mode 100644
index 0000000..1705972
--- /dev/null
+++ b/tools/ttymidi/src/ttymidi.c
@@ -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 .
+*/
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+// Linux-specific
+#include
+#include
+#include
+
+#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");
+}
+