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"); +} +